1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-11-06 09:09:19 +02:00

Extending mavenBuild step with an option to perform maven deploy with publish flag (#2833)

* modifying detect.maven.excluded.scopes from TEST to test

* new maven alt deployment flags

* changing flag names

* tlsCertificate addtion

* adding publish flags

* new flags

* publish flag

* enhance maven builds

* enhance maven builds

* creating new settings xml

* updating project settings

* changing interface for artifactPreparation that uses the same maven util niterface

* adding general scope to maven params

* global reference

* removing vault tmp

* debuging deployment user

* more debug

* maven build paras

* using smaller case

* adding incorrect error check

* adding deployment flags

* code refactor

* unit tests

* changing scope of paramter for tls certs

* new scope for tls

* remove trailing space in mavenBuild.yaml

* trailing space fix

* typo fix and jenkins secret

* including jenkins credentials for repo pass in the maven build groovy

Co-authored-by: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com>
This commit is contained in:
Anil Keshav
2021-06-01 09:24:36 +02:00
committed by GitHub
parent e24b38da24
commit a830a35800
10 changed files with 662 additions and 23 deletions

View File

@@ -59,6 +59,8 @@ type artifactPrepareVersionUtils interface {
FileExists(filename string) (bool, error)
Copy(src, dest string) (int64, error)
MkdirAll(path string, perm os.FileMode) error
FileWrite(path string, content []byte, perm os.FileMode) error
FileRead(path string) ([]byte, error)
}
type artifactPrepareVersionUtilsBundle struct {

View File

@@ -1,9 +1,19 @@
package cmd
import (
"io/ioutil"
"os"
"path"
"path/filepath"
"github.com/SAP/jenkins-library/pkg/command"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/maven"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/pkg/errors"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
)
func mavenBuild(config mavenBuildOptions, telemetryData *telemetry.CustomData) {
@@ -16,6 +26,8 @@ func mavenBuild(config mavenBuildOptions, telemetryData *telemetry.CustomData) {
}
func runMavenBuild(config *mavenBuildOptions, telemetryData *telemetry.CustomData, utils maven.Utils) error {
downloadClient := &piperhttp.Client{}
var flags = []string{"-update-snapshots", "--batch-mode"}
exists, _ := utils.FileExists("integration-tests/pom.xml")
@@ -67,5 +79,123 @@ func runMavenBuild(config *mavenBuildOptions, telemetryData *telemetry.CustomDat
}
_, err := maven.Execute(&mavenOptions, utils)
if err == nil {
if config.Publish && !config.Verify {
log.Entry().Infof("publish detected, running mvn deploy")
runner := &command.Command{}
fileUtils := &piperutils.Files{}
if (len(config.AltDeploymentRepositoryID) > 0) && (len(config.AltDeploymentRepositoryPassword) > 0) && (len(config.AltDeploymentRepositoryUser) > 0) {
projectSettingsFilePath, err := createOrUpdateProjectSettingsXML(config.ProjectSettingsFile, config.AltDeploymentRepositoryID, config.AltDeploymentRepositoryUser, config.AltDeploymentRepositoryPassword, utils)
if err != nil {
return errors.Wrap(err, "Could not create or update project settings xml")
}
mavenOptions.ProjectSettingsFile = projectSettingsFilePath
}
deployFlags := []string{"-Dmaven.main.skip=true", "-Dmaven.test.skip=true", "-Dmaven.install.skip=true"}
if (len(config.AltDeploymentRepositoryID) > 0) && (len(config.AltDeploymentRepositoryURL) > 0) {
deployFlags = append(deployFlags, "-DaltDeploymentRepository="+config.AltDeploymentRepositoryID+"::default::"+config.AltDeploymentRepositoryURL)
}
if err := loadRemoteRepoCertificates(config.CustomTLSCertificateLinks, downloadClient, &deployFlags, runner, fileUtils); err != nil {
log.SetErrorCategory(log.ErrorInfrastructure)
return err
}
mavenOptions.Flags = deployFlags
mavenOptions.Goals = []string{"deploy"}
mavenOptions.Defines = []string{}
_, err := maven.Execute(&mavenOptions, utils)
return err
} else {
log.Entry().Infof("publish not detected, ignoring maven deploy")
}
}
return err
}
func createOrUpdateProjectSettingsXML(projectSettingsFile string, altDeploymentRepositoryID string, altDeploymentRepositoryUser string, altDeploymentRepositoryPassword string, utils maven.Utils) (string, error) {
if len(projectSettingsFile) > 0 {
projectSettingsFilePath, err := maven.UpdateProjectSettingsXML(projectSettingsFile, altDeploymentRepositoryID, altDeploymentRepositoryUser, altDeploymentRepositoryPassword, utils)
if err != nil {
return "", errors.Wrap(err, "Could not update settings xml")
}
return projectSettingsFilePath, nil
} else {
projectSettingsFilePath, err := maven.CreateNewProjectSettingsXML(altDeploymentRepositoryID, altDeploymentRepositoryUser, altDeploymentRepositoryPassword, utils)
if err != nil {
return "", errors.Wrap(err, "Could not create settings xml")
}
return projectSettingsFilePath, nil
}
}
func loadRemoteRepoCertificates(certificateList []string, client piperhttp.Downloader, flags *[]string, runner command.ExecRunner, fileUtils piperutils.FileUtils) error {
trustStore := filepath.Join(getWorkingDirForTrustStore(), ".pipeline", "keystore.jks")
log.Entry().Infof("using trust store %s", trustStore)
if exists, _ := fileUtils.FileExists(trustStore); exists {
maven_opts := "-Djavax.net.ssl.trustStore=" + trustStore + " -Djavax.net.ssl.trustStorePassword=changeit"
err := os.Setenv("MAVEN_OPTS", maven_opts)
if err != nil {
return errors.Wrap(err, "Could not create MAVEN_OPTS environment variable ")
}
log.Entry().WithField("trust store", trustStore).Info("Using local trust store")
}
if len(certificateList) > 0 {
keytoolOptions := []string{
"-import",
"-noprompt",
"-storepass", "changeit",
"-keystore", trustStore,
}
tmpFolder := getTempDirForCertFile()
defer os.RemoveAll(tmpFolder) // clean up
for _, certificate := range certificateList {
filename := path.Base(certificate) // decode?
target := filepath.Join(tmpFolder, filename)
log.Entry().WithField("source", certificate).WithField("target", target).Info("Downloading TLS certificate")
// download certificate
if err := client.DownloadFile(certificate, target, nil, nil); err != nil {
return errors.Wrapf(err, "Download of TLS certificate failed")
}
options := append(keytoolOptions, "-file", target)
options = append(options, "-alias", filename)
// add certificate to keystore
if err := runner.RunExecutable("keytool", options...); err != nil {
return errors.Wrap(err, "Adding certificate to keystore failed")
}
}
maven_opts := "-Djavax.net.ssl.trustStore=.pipeline/keystore.jks -Djavax.net.ssl.trustStorePassword=changeit"
err := os.Setenv("MAVEN_OPTS", maven_opts)
if err != nil {
return errors.Wrap(err, "Could not create MAVEN_OPTS environment variable ")
}
log.Entry().WithField("trust store", trustStore).Info("Using local trust store")
} else {
log.Entry().Debug("Download of TLS certificates skipped")
}
return nil
}
func getWorkingDirForTrustStore() string {
workingDir, err := os.Getwd()
if err != nil {
log.Entry().WithError(err).WithField("path", workingDir).Debug("Retrieving of work directory failed")
}
return workingDir
}
func getTempDirForCertFile() string {
tmpFolder, err := ioutil.TempDir(".", "temp-")
if err != nil {
log.Entry().WithError(err).WithField("path", tmpFolder).Debug("Creating temp directory failed")
}
return tmpFolder
}

View File

@@ -15,14 +15,20 @@ import (
)
type mavenBuildOptions struct {
PomPath string `json:"pomPath,omitempty"`
Flatten bool `json:"flatten,omitempty"`
Verify bool `json:"verify,omitempty"`
ProjectSettingsFile string `json:"projectSettingsFile,omitempty"`
GlobalSettingsFile string `json:"globalSettingsFile,omitempty"`
M2Path string `json:"m2Path,omitempty"`
LogSuccessfulMavenTransfers bool `json:"logSuccessfulMavenTransfers,omitempty"`
CreateBOM bool `json:"createBOM,omitempty"`
PomPath string `json:"pomPath,omitempty"`
Flatten bool `json:"flatten,omitempty"`
Verify bool `json:"verify,omitempty"`
ProjectSettingsFile string `json:"projectSettingsFile,omitempty"`
GlobalSettingsFile string `json:"globalSettingsFile,omitempty"`
M2Path string `json:"m2Path,omitempty"`
LogSuccessfulMavenTransfers bool `json:"logSuccessfulMavenTransfers,omitempty"`
CreateBOM bool `json:"createBOM,omitempty"`
AltDeploymentRepositoryPassword string `json:"altDeploymentRepositoryPassword,omitempty"`
AltDeploymentRepositoryUser string `json:"altDeploymentRepositoryUser,omitempty"`
AltDeploymentRepositoryURL string `json:"altDeploymentRepositoryUrl,omitempty"`
AltDeploymentRepositoryID string `json:"altDeploymentRepositoryID,omitempty"`
CustomTLSCertificateLinks []string `json:"customTlsCertificateLinks,omitempty"`
Publish bool `json:"publish,omitempty"`
}
// MavenBuildCommand This step will install the maven project into the local maven repository.
@@ -54,6 +60,7 @@ supports ci friendly versioning by flattening the pom before installing.`,
log.SetErrorCategory(log.ErrorConfiguration)
return err
}
log.RegisterSecret(stepConfig.AltDeploymentRepositoryPassword)
if len(GeneralConfig.HookConfig.SentryConfig.Dsn) > 0 {
sentryHook := log.NewSentryHook(GeneralConfig.HookConfig.SentryConfig.Dsn, GeneralConfig.CorrelationID)
@@ -108,6 +115,12 @@ func addMavenBuildFlags(cmd *cobra.Command, stepConfig *mavenBuildOptions) {
cmd.Flags().StringVar(&stepConfig.M2Path, "m2Path", os.Getenv("PIPER_m2Path"), "Path to the location of the local repository that should be used.")
cmd.Flags().BoolVar(&stepConfig.LogSuccessfulMavenTransfers, "logSuccessfulMavenTransfers", false, "Configures maven to log successful downloads. This is set to `false` by default to reduce the noise in build logs.")
cmd.Flags().BoolVar(&stepConfig.CreateBOM, "createBOM", false, "Creates the bill of materials (BOM) using CycloneDX Maven plugin.")
cmd.Flags().StringVar(&stepConfig.AltDeploymentRepositoryPassword, "altDeploymentRepositoryPassword", os.Getenv("PIPER_altDeploymentRepositoryPassword"), "Password for the alternative deployment repository to which the project artifacts should be deployed ( other than those specified in <distributionManagement> ). This password will be updated in settings.xml . When no settings.xml is provided a new one is created corresponding with <servers> tag")
cmd.Flags().StringVar(&stepConfig.AltDeploymentRepositoryUser, "altDeploymentRepositoryUser", os.Getenv("PIPER_altDeploymentRepositoryUser"), "User for the alternative deployment repository to which the project artifacts should be deployed ( other than those specified in <distributionManagement> ). This user will be updated in settings.xml . When no settings.xml is provided a new one is created corresponding with <servers> tag")
cmd.Flags().StringVar(&stepConfig.AltDeploymentRepositoryURL, "altDeploymentRepositoryUrl", os.Getenv("PIPER_altDeploymentRepositoryUrl"), "Url for the alternative deployment repository to which the project artifacts should be deployed ( other than those specified in <distributionManagement> ). This Url will be updated in settings.xml . When no settings.xml is provided a new one is created corresponding with <servers> tag")
cmd.Flags().StringVar(&stepConfig.AltDeploymentRepositoryID, "altDeploymentRepositoryID", os.Getenv("PIPER_altDeploymentRepositoryID"), "Id for the alternative deployment repository to which the project artifacts should be deployed ( other than those specified in <distributionManagement> ). This id will be updated in settings.xml and will be used as a flag with DaltDeploymentRepository along with mavenAltDeploymentRepositoryUrl during maven deploy . When no settings.xml is provided a new one is created corresponding with <servers> tag")
cmd.Flags().StringSliceVar(&stepConfig.CustomTLSCertificateLinks, "customTlsCertificateLinks", []string{}, "List of download links to custom TLS certificates. This is required to ensure trusted connections to instances with repositories (like nexus) when publish flag is set to true.")
cmd.Flags().BoolVar(&stepConfig.Publish, "publish", false, "Configures maven to run the deploy plugin to publish artifacts to a repository.")
}
@@ -155,12 +168,17 @@ func mavenBuildMetadata() config.StepData {
Aliases: []config.Alias{{Name: "maven/projectSettingsFile"}},
},
{
Name: "globalSettingsFile",
ResourceRef: []config.ResourceReference{},
Scope: []string{"GENERAL", "STEPS", "STAGES", "PARAMETERS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "maven/globalSettingsFile"}},
Name: "globalSettingsFile",
ResourceRef: []config.ResourceReference{
{
Name: "commonPipelineEnvironment",
Param: "custom/mavenGlobalSettingsFile",
},
},
Scope: []string{"GENERAL", "STEPS", "STAGES", "PARAMETERS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "maven/globalSettingsFile"}},
},
{
Name: "m2Path",
@@ -186,6 +204,85 @@ func mavenBuildMetadata() config.StepData {
Mandatory: false,
Aliases: []config.Alias{{Name: "maven/createBOM"}},
},
{
Name: "altDeploymentRepositoryPassword",
ResourceRef: []config.ResourceReference{
{
Name: "commonPipelineEnvironment",
Param: "custom/repositoryPassword",
},
{
Name: "altDeploymentRepositoryPasswordId",
Type: "secret",
},
{
Name: "",
Paths: []string{"$(vaultPath)/alt-deployment-repository-passowrd", "$(vaultBasePath)/$(vaultPipelineName)/alt-deployment-repository-passowrd", "$(vaultBasePath)/GROUP-SECRETS/alt-deployment-repository-passowrd"},
Type: "vaultSecretFile",
},
},
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "altDeploymentRepositoryUser",
ResourceRef: []config.ResourceReference{
{
Name: "commonPipelineEnvironment",
Param: "custom/repositoryUsername",
},
},
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "altDeploymentRepositoryUrl",
ResourceRef: []config.ResourceReference{
{
Name: "commonPipelineEnvironment",
Param: "custom/repositoryUrl",
},
},
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "altDeploymentRepositoryID",
ResourceRef: []config.ResourceReference{
{
Name: "commonPipelineEnvironment",
Param: "custom/repositoryId",
},
},
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "customTlsCertificateLinks",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "[]string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "publish",
ResourceRef: []config.ResourceReference{},
Scope: []string{"STEPS", "STAGES", "PARAMETERS"},
Type: "bool",
Mandatory: false,
Aliases: []config.Alias{{Name: "maven/publish"}},
},
},
},
Containers: []config.Container{

View File

@@ -77,4 +77,43 @@ func TestMavenBuild(t *testing.T) {
assert.Contains(t, mockedUtils.Calls[0].Params, "-DoutputFormat=xml")
})
t.Run("mavenBuild include install and deploy when publish is true", func(t *testing.T) {
mockedUtils := newMavenMockUtils()
config := mavenBuildOptions{Publish: true, Verify: false}
err := runMavenBuild(&config, nil, &mockedUtils)
assert.Nil(t, err)
assert.Contains(t, mockedUtils.Calls[0].Params, "install")
assert.NotContains(t, mockedUtils.Calls[0].Params, "verify")
assert.Contains(t, mockedUtils.Calls[1].Params, "deploy")
})
t.Run("mavenBuild with deploy must skip build, install and test", func(t *testing.T) {
mockedUtils := newMavenMockUtils()
config := mavenBuildOptions{Publish: true, Verify: false}
err := runMavenBuild(&config, nil, &mockedUtils)
assert.Nil(t, err)
assert.Contains(t, mockedUtils.Calls[1].Params, "-Dmaven.main.skip=true")
assert.Contains(t, mockedUtils.Calls[1].Params, "-Dmaven.test.skip=true")
assert.Contains(t, mockedUtils.Calls[1].Params, "-Dmaven.install.skip=true")
})
t.Run("mavenBuild with deploy must include alt repo id and url when passed as parameter", func(t *testing.T) {
mockedUtils := newMavenMockUtils()
config := mavenBuildOptions{Publish: true, Verify: false, AltDeploymentRepositoryID: "ID", AltDeploymentRepositoryURL: "http://sampleRepo.com"}
err := runMavenBuild(&config, nil, &mockedUtils)
assert.Nil(t, err)
assert.Contains(t, mockedUtils.Calls[1].Params, "-DaltDeploymentRepository=ID::default::http://sampleRepo.com")
})
}