mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-01-04 04:07:16 +02:00
Implement nexusUpload command and tests (#1255)
Artifacts to upload are assembled for MTA projects and Maven projects with optional application sub-module. Then maven deploy:deploy-file is used as backend to upload bundles of artifacts plus sub-artifacts. Co-authored-by: Florian Wilhelm <florian.wilhelm02@sap.com>
This commit is contained in:
parent
b39080409c
commit
20b65d5a2e
464
cmd/nexusUpload.go
Normal file
464
cmd/nexusUpload.go
Normal file
@ -0,0 +1,464 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"os"
|
||||
"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/nexus"
|
||||
"github.com/SAP/jenkins-library/pkg/piperenv"
|
||||
"github.com/SAP/jenkins-library/pkg/piperutils"
|
||||
"github.com/SAP/jenkins-library/pkg/telemetry"
|
||||
"github.com/ghodss/yaml"
|
||||
)
|
||||
|
||||
// nexusUploadUtils defines an interface for utility functionality used from external packages,
|
||||
// so it can be easily mocked for testing.
|
||||
type nexusUploadUtils interface {
|
||||
fileExists(path string) (bool, error)
|
||||
fileRead(path string) ([]byte, error)
|
||||
fileWrite(path string, content []byte, perm os.FileMode) error
|
||||
fileRemove(path string)
|
||||
usesMta() bool
|
||||
usesMaven() bool
|
||||
getEnvParameter(path, name string) string
|
||||
getExecRunner() execRunner
|
||||
evaluate(pomFile, expression string) (string, error)
|
||||
}
|
||||
|
||||
type utilsBundle struct {
|
||||
projectStructure piperutils.ProjectStructure
|
||||
fileUtils piperutils.Files
|
||||
execRunner *command.Command
|
||||
}
|
||||
|
||||
func newUtilsBundle() *utilsBundle {
|
||||
return &utilsBundle{
|
||||
projectStructure: piperutils.ProjectStructure{},
|
||||
fileUtils: piperutils.Files{},
|
||||
}
|
||||
}
|
||||
|
||||
func (u *utilsBundle) fileExists(path string) (bool, error) {
|
||||
return u.fileUtils.FileExists(path)
|
||||
}
|
||||
|
||||
func (u *utilsBundle) fileRead(path string) ([]byte, error) {
|
||||
return u.fileUtils.FileRead(path)
|
||||
}
|
||||
|
||||
func (u *utilsBundle) fileWrite(filePath string, content []byte, perm os.FileMode) error {
|
||||
parent := filepath.Dir(filePath)
|
||||
if parent != "" {
|
||||
err := u.fileUtils.MkdirAll(parent, 0775)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return u.fileUtils.FileWrite(filePath, content, perm)
|
||||
}
|
||||
|
||||
func (u *utilsBundle) fileRemove(path string) {
|
||||
err := os.Remove(path)
|
||||
if err != nil {
|
||||
log.Entry().WithError(err).Warnf("Failed to remove file '%s'.", path)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *utilsBundle) usesMta() bool {
|
||||
return u.projectStructure.UsesMta()
|
||||
}
|
||||
|
||||
func (u *utilsBundle) usesMaven() bool {
|
||||
return u.projectStructure.UsesMaven()
|
||||
}
|
||||
|
||||
func (u *utilsBundle) getEnvParameter(path, name string) string {
|
||||
return piperenv.GetParameter(path, name)
|
||||
}
|
||||
|
||||
func (u *utilsBundle) getExecRunner() execRunner {
|
||||
if u.execRunner == nil {
|
||||
u.execRunner = &command.Command{}
|
||||
u.execRunner.Stdout(log.Entry().Writer())
|
||||
u.execRunner.Stderr(log.Entry().Writer())
|
||||
}
|
||||
return u.execRunner
|
||||
}
|
||||
|
||||
func (u *utilsBundle) evaluate(pomFile, expression string) (string, error) {
|
||||
return maven.Evaluate(pomFile, expression, u.getExecRunner())
|
||||
}
|
||||
|
||||
func nexusUpload(options nexusUploadOptions, _ *telemetry.CustomData) {
|
||||
utils := newUtilsBundle()
|
||||
uploader := nexus.Upload{}
|
||||
|
||||
err := runNexusUpload(utils, &uploader, &options)
|
||||
if err != nil {
|
||||
log.Entry().WithError(err).Fatal("step execution failed")
|
||||
}
|
||||
}
|
||||
|
||||
func runNexusUpload(utils nexusUploadUtils, uploader nexus.Uploader, options *nexusUploadOptions) error {
|
||||
err := uploader.SetRepoURL(options.Url, options.Version, options.Repository)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if utils.usesMta() {
|
||||
log.Entry().Info("MTA project structure detected")
|
||||
return uploadMTA(utils, uploader, options)
|
||||
} else if utils.usesMaven() {
|
||||
log.Entry().Info("Maven project structure detected")
|
||||
return uploadMaven(utils, uploader, options)
|
||||
} else {
|
||||
return fmt.Errorf("unsupported project structure")
|
||||
}
|
||||
}
|
||||
|
||||
func uploadMTA(utils nexusUploadUtils, uploader nexus.Uploader, options *nexusUploadOptions) error {
|
||||
if options.GroupID == "" {
|
||||
return fmt.Errorf("the 'groupId' parameter needs to be provided for MTA projects")
|
||||
}
|
||||
var mtaPath string
|
||||
exists, _ := utils.fileExists("mta.yaml")
|
||||
if exists {
|
||||
mtaPath = "mta.yaml"
|
||||
// Give this file precedence, but it would be even better if
|
||||
// ProjectStructure could be asked for the mta file it detected.
|
||||
} else {
|
||||
// This will fail anyway if the file doesn't exist
|
||||
mtaPath = "mta.yml"
|
||||
}
|
||||
version, err := getVersionFromMtaFile(utils, mtaPath)
|
||||
var artifactID = options.ArtifactID
|
||||
if artifactID == "" {
|
||||
artifactID = utils.getEnvParameter(".pipeline/commonPipelineEnvironment/configuration", "artifactId")
|
||||
if artifactID == "" {
|
||||
err = fmt.Errorf("the 'artifactId' parameter was not provided and could not be retrieved from the Common Pipeline Environment")
|
||||
} else {
|
||||
log.Entry().Debugf("mtar artifact id from CPE: '%s'", artifactID)
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
err = uploader.SetInfo(options.GroupID, artifactID, version)
|
||||
if err == nexus.ErrEmptyVersion {
|
||||
err = fmt.Errorf("the project descriptor file 'mta.yaml' has an invalid version: %w", err)
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
err = addArtifact(utils, uploader, mtaPath, "", "yaml")
|
||||
}
|
||||
if err == nil {
|
||||
mtarFilePath := utils.getEnvParameter(".pipeline/commonPipelineEnvironment", "mtarFilePath")
|
||||
log.Entry().Debugf("mtar file path: '%s'", mtarFilePath)
|
||||
err = addArtifact(utils, uploader, mtarFilePath, "", "mtar")
|
||||
}
|
||||
if err == nil {
|
||||
err = uploadArtifacts(utils, uploader, options, false)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type mtaYaml struct {
|
||||
ID string `json:"ID"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
func getVersionFromMtaFile(utils nexusUploadUtils, filePath string) (string, error) {
|
||||
mtaYamlContent, err := utils.fileRead(filePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not read from required project descriptor file '%s'",
|
||||
filePath)
|
||||
}
|
||||
return getVersionFromMtaYaml(mtaYamlContent, filePath)
|
||||
}
|
||||
|
||||
func getVersionFromMtaYaml(mtaYamlContent []byte, filePath string) (string, error) {
|
||||
var mtaYaml mtaYaml
|
||||
err := yaml.Unmarshal(mtaYamlContent, &mtaYaml)
|
||||
if err != nil {
|
||||
// Eat the original error as it is unhelpful and confusingly mentions JSON, while the
|
||||
// user thinks it should parse YAML (it is transposed by the implementation).
|
||||
return "", fmt.Errorf("failed to parse contents of the project descriptor file '%s'",
|
||||
filePath)
|
||||
}
|
||||
return mtaYaml.Version, nil
|
||||
}
|
||||
|
||||
func createMavenExecuteOptions(options *nexusUploadOptions) maven.ExecuteOptions {
|
||||
mavenOptions := maven.ExecuteOptions{
|
||||
ReturnStdout: false,
|
||||
M2Path: options.M2Path,
|
||||
GlobalSettingsFile: options.GlobalSettingsFile,
|
||||
}
|
||||
return mavenOptions
|
||||
}
|
||||
|
||||
const settingsServerID = "artifact.deployment.nexus"
|
||||
|
||||
const nexusMavenSettings = `<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
|
||||
<servers>
|
||||
<server>
|
||||
<id>artifact.deployment.nexus</id>
|
||||
<username>${env.NEXUS_username}</username>
|
||||
<password>${env.NEXUS_password}</password>
|
||||
</server>
|
||||
</servers>
|
||||
</settings>
|
||||
`
|
||||
|
||||
const settingsPath = ".pipeline/nexusMavenSettings.xml"
|
||||
|
||||
func setupNexusCredentialsSettingsFile(utils nexusUploadUtils, options *nexusUploadOptions,
|
||||
mavenOptions *maven.ExecuteOptions) (string, error) {
|
||||
if options.User == "" || options.Password == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
err := utils.fileWrite(settingsPath, []byte(nexusMavenSettings), os.ModePerm)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to write maven settings file to '%s': %w", settingsPath, err)
|
||||
}
|
||||
|
||||
log.Entry().Debugf("Writing nexus credentials to environment")
|
||||
utils.getExecRunner().SetEnv([]string{"NEXUS_username=" + options.User, "NEXUS_password=" + options.Password})
|
||||
|
||||
mavenOptions.ProjectSettingsFile = settingsPath
|
||||
mavenOptions.Defines = append(mavenOptions.Defines, "-DrepositoryId="+settingsServerID)
|
||||
return settingsPath, nil
|
||||
}
|
||||
|
||||
type artifactDefines struct {
|
||||
file string
|
||||
packaging string
|
||||
files string
|
||||
classifiers string
|
||||
types string
|
||||
}
|
||||
|
||||
const deployGoal = "org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy-file"
|
||||
|
||||
func uploadArtifacts(utils nexusUploadUtils, uploader nexus.Uploader, options *nexusUploadOptions,
|
||||
generatePOM bool) error {
|
||||
if uploader.GetGroupID() == "" {
|
||||
return fmt.Errorf("no group ID was provided, or could be established from project files")
|
||||
}
|
||||
|
||||
artifacts := uploader.GetArtifacts()
|
||||
if len(artifacts) == 0 {
|
||||
return errors.New("no artifacts to upload")
|
||||
}
|
||||
|
||||
var defines []string
|
||||
defines = append(defines, "-Durl=http://"+uploader.GetRepoURL())
|
||||
defines = append(defines, "-DgroupId="+uploader.GetGroupID())
|
||||
defines = append(defines, "-Dversion="+uploader.GetArtifactsVersion())
|
||||
defines = append(defines, "-DartifactId="+uploader.GetArtifactsID())
|
||||
|
||||
mavenOptions := createMavenExecuteOptions(options)
|
||||
mavenOptions.Goals = []string{deployGoal}
|
||||
mavenOptions.Defines = defines
|
||||
|
||||
settingsFile, err := setupNexusCredentialsSettingsFile(utils, options, &mavenOptions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing credential settings for maven failed: %w", err)
|
||||
}
|
||||
if settingsFile != "" {
|
||||
defer utils.fileRemove(settingsFile)
|
||||
}
|
||||
|
||||
// iterate over the artifact descriptions, the first one is the main artifact, the following ones are
|
||||
// sub-artifacts.
|
||||
var d artifactDefines
|
||||
for i, artifact := range artifacts {
|
||||
if i == 0 {
|
||||
d.file = artifact.File
|
||||
d.packaging = artifact.Type
|
||||
} else {
|
||||
// Note: It is important to append the comma, even when the list is empty
|
||||
// or the appended item is empty. So classifiers could end up like ",,classes".
|
||||
// This is needed to match the third classifier "classes" to the third sub-artifact.
|
||||
d.files = appendItemToString(d.files, artifact.File, i == 1)
|
||||
d.classifiers = appendItemToString(d.classifiers, artifact.Classifier, i == 1)
|
||||
d.types = appendItemToString(d.types, artifact.Type, i == 1)
|
||||
}
|
||||
}
|
||||
|
||||
err = uploadArtifactsBundle(d, generatePOM, mavenOptions, utils.getExecRunner())
|
||||
if err != nil {
|
||||
return fmt.Errorf("uploading artifacts for ID '%s' failed: %w", uploader.GetArtifactsID(), err)
|
||||
}
|
||||
uploader.Clear()
|
||||
return nil
|
||||
}
|
||||
|
||||
// appendItemToString appends a comma this is not the first item, regardless of whether
|
||||
// list or item are empty.
|
||||
func appendItemToString(list, item string, first bool) string {
|
||||
if !first {
|
||||
list += ","
|
||||
}
|
||||
return list + item
|
||||
}
|
||||
|
||||
func uploadArtifactsBundle(d artifactDefines, generatePOM bool, mavenOptions maven.ExecuteOptions,
|
||||
execRunner execRunner) error {
|
||||
if d.file == "" {
|
||||
return fmt.Errorf("no file specified")
|
||||
}
|
||||
|
||||
var defines []string
|
||||
|
||||
defines = append(defines, "-Dfile="+d.file)
|
||||
defines = append(defines, "-Dpackaging="+d.packaging)
|
||||
if !generatePOM {
|
||||
defines = append(defines, "-DgeneratePom=false")
|
||||
}
|
||||
|
||||
if len(d.files) > 0 {
|
||||
defines = append(defines, "-Dfiles="+d.files)
|
||||
defines = append(defines, "-Dclassifiers="+d.classifiers)
|
||||
defines = append(defines, "-Dtypes="+d.types)
|
||||
}
|
||||
|
||||
mavenOptions.Defines = append(mavenOptions.Defines, defines...)
|
||||
_, err := maven.Execute(&mavenOptions, execRunner)
|
||||
return err
|
||||
}
|
||||
|
||||
func addArtifact(utils nexusUploadUtils, uploader nexus.Uploader, filePath, classifier, fileType string) error {
|
||||
exists, _ := utils.fileExists(filePath)
|
||||
if !exists {
|
||||
return fmt.Errorf("artifact file not found '%s'", filePath)
|
||||
}
|
||||
artifact := nexus.ArtifactDescription{
|
||||
File: filePath,
|
||||
Type: fileType,
|
||||
Classifier: classifier,
|
||||
}
|
||||
return uploader.AddArtifact(artifact)
|
||||
}
|
||||
|
||||
var errPomNotFound = errors.New("pom.xml not found")
|
||||
|
||||
func uploadMaven(utils nexusUploadUtils, uploader nexus.Uploader, options *nexusUploadOptions) error {
|
||||
err := uploadMavenArtifacts(utils, uploader, options, "", "target", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Test if a sub-folder "application" exists and upload the artifacts from there as well.
|
||||
// This means there are built-in assumptions about the project structure (archetype),
|
||||
// that nexusUpload supports. To make this more flexible should be the scope of another PR.
|
||||
err = uploadMavenArtifacts(utils, uploader, options, "application", "application/target",
|
||||
options.AdditionalClassifiers)
|
||||
if err == errPomNotFound {
|
||||
// Ignore for missing application module
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func uploadMavenArtifacts(utils nexusUploadUtils, uploader nexus.Uploader, options *nexusUploadOptions,
|
||||
pomPath, targetFolder, additionalClassifiers string) error {
|
||||
pomFile := composeFilePath(pomPath, "pom", "xml")
|
||||
exists, _ := utils.fileExists(pomFile)
|
||||
if !exists {
|
||||
return errPomNotFound
|
||||
}
|
||||
groupID, _ := utils.evaluate(pomFile, "project.groupId")
|
||||
if groupID == "" {
|
||||
groupID = options.GroupID
|
||||
}
|
||||
artifactID, err := utils.evaluate(pomFile, "project.artifactId")
|
||||
var artifactsVersion string
|
||||
if err == nil {
|
||||
artifactsVersion, err = utils.evaluate(pomFile, "project.version")
|
||||
}
|
||||
if err == nil {
|
||||
err = uploader.SetInfo(groupID, artifactID, artifactsVersion)
|
||||
}
|
||||
var finalBuildName string
|
||||
if err == nil {
|
||||
finalBuildName, _ = utils.evaluate(pomFile, "project.build.finalName")
|
||||
if finalBuildName == "" {
|
||||
// Fallback to using artifactID as base-name of artifact files.
|
||||
finalBuildName = artifactID
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
err = addArtifact(utils, uploader, pomFile, "", "pom")
|
||||
}
|
||||
if err == nil {
|
||||
err = addMavenTargetArtifact(utils, uploader, pomFile, targetFolder, finalBuildName)
|
||||
}
|
||||
if err == nil {
|
||||
err = addMavenTargetSubArtifacts(utils, uploader, additionalClassifiers, targetFolder, finalBuildName)
|
||||
}
|
||||
if err == nil {
|
||||
err = uploadArtifacts(utils, uploader, options, true)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func addMavenTargetArtifact(utils nexusUploadUtils, uploader nexus.Uploader,
|
||||
pomFile, targetFolder, finalBuildName string) error {
|
||||
packaging, err := utils.evaluate(pomFile, "project.packaging")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if packaging == "pom" {
|
||||
// Only pom.xml itself is the artifact
|
||||
return nil
|
||||
}
|
||||
if packaging == "" {
|
||||
packaging = "jar"
|
||||
}
|
||||
filePath := composeFilePath(targetFolder, finalBuildName, packaging)
|
||||
return addArtifact(utils, uploader, filePath, "", packaging)
|
||||
}
|
||||
|
||||
func addMavenTargetSubArtifacts(utils nexusUploadUtils, uploader nexus.Uploader,
|
||||
additionalClassifiers, targetFolder, finalBuildName string) error {
|
||||
if additionalClassifiers == "" {
|
||||
return nil
|
||||
}
|
||||
classifiers, err := getClassifiers(additionalClassifiers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, classifier := range classifiers {
|
||||
if classifier.Classifier == "" || classifier.FileType == "" {
|
||||
return fmt.Errorf("invalid additional classifier description (classifier: '%s', type: '%s')",
|
||||
classifier.Classifier, classifier.FileType)
|
||||
}
|
||||
filePath := composeFilePath(targetFolder, finalBuildName+"-"+classifier.Classifier, classifier.FileType)
|
||||
err = addArtifact(utils, uploader, filePath, classifier.Classifier, classifier.FileType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func composeFilePath(folder, name, extension string) string {
|
||||
fileName := name + "." + extension
|
||||
return filepath.Join(folder, fileName)
|
||||
}
|
||||
|
||||
type classifierDescription struct {
|
||||
Classifier string `json:"classifier"`
|
||||
FileType string `json:"type"`
|
||||
}
|
||||
|
||||
func getClassifiers(classifiersAsJSON string) ([]classifierDescription, error) {
|
||||
var classifiers []classifierDescription
|
||||
err := json.Unmarshal([]byte(classifiersAsJSON), &classifiers)
|
||||
return classifiers, err
|
||||
}
|
175
cmd/nexusUpload_generated.go
Normal file
175
cmd/nexusUpload_generated.go
Normal file
@ -0,0 +1,175 @@
|
||||
// Code generated by piper's step-generator. DO NOT EDIT.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/config"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/SAP/jenkins-library/pkg/telemetry"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type nexusUploadOptions struct {
|
||||
Version string `json:"version,omitempty"`
|
||||
Url string `json:"url,omitempty"`
|
||||
Repository string `json:"repository,omitempty"`
|
||||
GroupID string `json:"groupId,omitempty"`
|
||||
ArtifactID string `json:"artifactId,omitempty"`
|
||||
GlobalSettingsFile string `json:"globalSettingsFile,omitempty"`
|
||||
M2Path string `json:"m2Path,omitempty"`
|
||||
AdditionalClassifiers string `json:"additionalClassifiers,omitempty"`
|
||||
User string `json:"user,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
// NexusUploadCommand Upload artifacts to Nexus
|
||||
func NexusUploadCommand() *cobra.Command {
|
||||
metadata := nexusUploadMetadata()
|
||||
var stepConfig nexusUploadOptions
|
||||
var startTime time.Time
|
||||
|
||||
var createNexusUploadCmd = &cobra.Command{
|
||||
Use: "nexusUpload",
|
||||
Short: "Upload artifacts to Nexus",
|
||||
Long: `Upload build artifacts to a Nexus Repository Manager`,
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
startTime = time.Now()
|
||||
log.SetStepName("nexusUpload")
|
||||
log.SetVerbose(GeneralConfig.Verbose)
|
||||
return PrepareConfig(cmd, &metadata, "nexusUpload", &stepConfig, config.OpenPiperFile)
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
telemetryData := telemetry.CustomData{}
|
||||
telemetryData.ErrorCode = "1"
|
||||
handler := func() {
|
||||
telemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds())
|
||||
telemetry.Send(&telemetryData)
|
||||
}
|
||||
log.DeferExitHandler(handler)
|
||||
defer handler()
|
||||
telemetry.Initialize(GeneralConfig.NoTelemetry, "nexusUpload")
|
||||
nexusUpload(stepConfig, &telemetryData)
|
||||
telemetryData.ErrorCode = "0"
|
||||
},
|
||||
}
|
||||
|
||||
addNexusUploadFlags(createNexusUploadCmd, &stepConfig)
|
||||
return createNexusUploadCmd
|
||||
}
|
||||
|
||||
func addNexusUploadFlags(cmd *cobra.Command, stepConfig *nexusUploadOptions) {
|
||||
cmd.Flags().StringVar(&stepConfig.Version, "version", "nexus3", "The Nexus Repository Manager version. Currently supported are 'nexus2' and 'nexus3'.")
|
||||
cmd.Flags().StringVar(&stepConfig.Url, "url", os.Getenv("PIPER_url"), "URL of the nexus. The scheme part of the URL will not be considered, because only http is supported.")
|
||||
cmd.Flags().StringVar(&stepConfig.Repository, "repository", os.Getenv("PIPER_repository"), "Name of the nexus repository.")
|
||||
cmd.Flags().StringVar(&stepConfig.GroupID, "groupId", os.Getenv("PIPER_groupId"), "Group ID of the artifacts. Only used in MTA projects, ignored for Maven.")
|
||||
cmd.Flags().StringVar(&stepConfig.ArtifactID, "artifactId", os.Getenv("PIPER_artifactId"), "The artifact ID used for both the .mtar and mta.yaml files deployed for MTA projects, ignored for Maven.")
|
||||
cmd.Flags().StringVar(&stepConfig.GlobalSettingsFile, "globalSettingsFile", os.Getenv("PIPER_globalSettingsFile"), "Path to the mvn settings file that should be used as global settings file.")
|
||||
cmd.Flags().StringVar(&stepConfig.M2Path, "m2Path", os.Getenv("PIPER_m2Path"), "The path to the local .m2 directory, only used for Maven projects.")
|
||||
cmd.Flags().StringVar(&stepConfig.AdditionalClassifiers, "additionalClassifiers", os.Getenv("PIPER_additionalClassifiers"), "List of additional classifiers that should be deployed to nexus. Each item is a map of a type and a classifier name.")
|
||||
cmd.Flags().StringVar(&stepConfig.User, "user", os.Getenv("PIPER_user"), "User")
|
||||
cmd.Flags().StringVar(&stepConfig.Password, "password", os.Getenv("PIPER_password"), "Password")
|
||||
|
||||
cmd.MarkFlagRequired("url")
|
||||
cmd.MarkFlagRequired("repository")
|
||||
}
|
||||
|
||||
// retrieve step metadata
|
||||
func nexusUploadMetadata() config.StepData {
|
||||
var theMetaData = config.StepData{
|
||||
Metadata: config.StepMetadata{
|
||||
Name: "nexusUpload",
|
||||
Aliases: []config.Alias{{Name: "mavenExecute", Deprecated: false}},
|
||||
},
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{
|
||||
Name: "version",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "url",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "repository",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "groupId",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "artifactId",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
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", "PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{{Name: "maven/m2Path"}},
|
||||
},
|
||||
{
|
||||
Name: "additionalClassifiers",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "user",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "password",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return theMetaData
|
||||
}
|
16
cmd/nexusUpload_generated_test.go
Normal file
16
cmd/nexusUpload_generated_test.go
Normal file
@ -0,0 +1,16 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNexusUploadCommand(t *testing.T) {
|
||||
|
||||
testCmd := NexusUploadCommand()
|
||||
|
||||
// only high level testing performed - details are tested in step generation procudure
|
||||
assert.Equal(t, "nexusUpload", testCmd.Use, "command name incorrect")
|
||||
|
||||
}
|
732
cmd/nexusUpload_test.go
Normal file
732
cmd/nexusUpload_test.go
Normal file
@ -0,0 +1,732 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/SAP/jenkins-library/pkg/maven"
|
||||
"github.com/SAP/jenkins-library/pkg/mock"
|
||||
"github.com/SAP/jenkins-library/pkg/nexus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type mockUtilsBundle struct {
|
||||
mta bool
|
||||
maven bool
|
||||
files map[string][]byte
|
||||
removedFiles map[string][]byte
|
||||
properties map[string]map[string]string
|
||||
cpe map[string]string
|
||||
execRunner mock.ExecMockRunner
|
||||
}
|
||||
|
||||
func newMockUtilsBundle(usesMta, usesMaven bool) mockUtilsBundle {
|
||||
utils := mockUtilsBundle{mta: usesMta, maven: usesMaven}
|
||||
utils.files = map[string][]byte{}
|
||||
utils.removedFiles = map[string][]byte{}
|
||||
utils.properties = map[string]map[string]string{}
|
||||
utils.cpe = map[string]string{}
|
||||
return utils
|
||||
}
|
||||
|
||||
func (m *mockUtilsBundle) usesMta() bool {
|
||||
return m.mta
|
||||
}
|
||||
|
||||
func (m *mockUtilsBundle) usesMaven() bool {
|
||||
return m.maven
|
||||
}
|
||||
|
||||
func (m *mockUtilsBundle) fileExists(path string) (bool, error) {
|
||||
content := m.files[path]
|
||||
if content == nil {
|
||||
return false, fmt.Errorf("'%s': %w", path, os.ErrNotExist)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (m *mockUtilsBundle) fileRead(path string) ([]byte, error) {
|
||||
content := m.files[path]
|
||||
if content == nil {
|
||||
return nil, fmt.Errorf("could not read '%s'", path)
|
||||
}
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func (m *mockUtilsBundle) fileWrite(path string, content []byte, _ os.FileMode) error {
|
||||
m.files[path] = content
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockUtilsBundle) fileRemove(path string) {
|
||||
contents := m.files[path]
|
||||
m.files[path] = nil
|
||||
if contents != nil {
|
||||
m.removedFiles[path] = contents
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mockUtilsBundle) getEnvParameter(path, name string) string {
|
||||
path = path + "/" + name
|
||||
return m.cpe[path]
|
||||
}
|
||||
|
||||
func (m *mockUtilsBundle) getExecRunner() execRunner {
|
||||
return &m.execRunner
|
||||
}
|
||||
|
||||
func (m *mockUtilsBundle) setProperty(pomFile, expression, value string) {
|
||||
pom := m.properties[pomFile]
|
||||
if pom == nil {
|
||||
pom = map[string]string{}
|
||||
m.properties[pomFile] = pom
|
||||
}
|
||||
pom[expression] = value
|
||||
}
|
||||
|
||||
func (m *mockUtilsBundle) evaluate(pomFile, expression string) (string, error) {
|
||||
pom := m.properties[pomFile]
|
||||
if pom == nil {
|
||||
return "", fmt.Errorf("pom file '%s' not found", pomFile)
|
||||
}
|
||||
value := pom[expression]
|
||||
if value == "<empty>" {
|
||||
return "", nil
|
||||
}
|
||||
if value == "" {
|
||||
return "", fmt.Errorf("property '%s' not found in '%s'", expression, pomFile)
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
type mockUploader struct {
|
||||
nexus.Upload
|
||||
uploadedArtifacts []nexus.ArtifactDescription
|
||||
}
|
||||
|
||||
func (m *mockUploader) Clear() {
|
||||
// Clear is called after a successful upload. Record the artifacts that are present before
|
||||
// they are cleared. This way we can later peek into the set of all artifacts that were
|
||||
// uploaded across multiple bundles.
|
||||
m.uploadedArtifacts = append(m.uploadedArtifacts, m.GetArtifacts()...)
|
||||
m.Upload.Clear()
|
||||
}
|
||||
|
||||
func createOptions() nexusUploadOptions {
|
||||
return nexusUploadOptions{
|
||||
Repository: "maven-releases",
|
||||
GroupID: "my.group.id",
|
||||
ArtifactID: "artifact.id",
|
||||
Version: "nexus3",
|
||||
Url: "localhost:8081",
|
||||
}
|
||||
}
|
||||
|
||||
var testMtaYml = []byte(`
|
||||
_schema-version: 2.1.0
|
||||
ID: test
|
||||
version: 0.3.0
|
||||
|
||||
modules:
|
||||
|
||||
- name: java
|
||||
type: java
|
||||
path: srv
|
||||
`)
|
||||
|
||||
var testMtaYmlNoVersion = []byte(`
|
||||
_schema-version: 2.1.0
|
||||
ID: test
|
||||
|
||||
modules:
|
||||
- name: java
|
||||
type: java
|
||||
`)
|
||||
|
||||
var testPomXml = []byte(`
|
||||
<project>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.mycompany.app</groupId>
|
||||
<artifactId>my-app</artifactId>
|
||||
<version>1.0</version>
|
||||
</project>
|
||||
`)
|
||||
|
||||
func TestUploadMTAProjects(t *testing.T) {
|
||||
t.Run("Uploading MTA project without groupId parameter fails", func(t *testing.T) {
|
||||
utils := newMockUtilsBundle(true, false)
|
||||
utils.files["mta.yaml"] = testMtaYml
|
||||
utils.cpe[".pipeline/commonPipelineEnvironment/mtarFilePath"] = "test.mtar"
|
||||
uploader := mockUploader{}
|
||||
options := createOptions()
|
||||
options.GroupID = ""
|
||||
|
||||
err := runNexusUpload(&utils, &uploader, &options)
|
||||
assert.EqualError(t, err, "the 'groupId' parameter needs to be provided for MTA projects")
|
||||
assert.Equal(t, 0, len(uploader.GetArtifacts()))
|
||||
assert.Equal(t, 0, len(uploader.uploadedArtifacts))
|
||||
})
|
||||
t.Run("Uploading MTA project without artifactId parameter fails", func(t *testing.T) {
|
||||
utils := newMockUtilsBundle(true, false)
|
||||
utils.files["mta.yaml"] = testMtaYml
|
||||
utils.cpe[".pipeline/commonPipelineEnvironment/mtarFilePath"] = "test.mtar"
|
||||
uploader := mockUploader{}
|
||||
options := createOptions()
|
||||
options.ArtifactID = ""
|
||||
|
||||
err := runNexusUpload(&utils, &uploader, &options)
|
||||
assert.EqualError(t, err, "the 'artifactId' parameter was not provided and could not be retrieved from the Common Pipeline Environment")
|
||||
assert.Equal(t, 0, len(uploader.GetArtifacts()))
|
||||
assert.Equal(t, 0, len(uploader.uploadedArtifacts))
|
||||
})
|
||||
t.Run("Uploading MTA project fails due to missing yaml file", func(t *testing.T) {
|
||||
utils := newMockUtilsBundle(true, false)
|
||||
utils.cpe[".pipeline/commonPipelineEnvironment/mtarFilePath"] = "test.mtar"
|
||||
uploader := mockUploader{}
|
||||
options := createOptions()
|
||||
|
||||
err := runNexusUpload(&utils, &uploader, &options)
|
||||
assert.EqualError(t, err, "could not read from required project descriptor file 'mta.yml'")
|
||||
assert.Equal(t, 0, len(uploader.GetArtifacts()))
|
||||
assert.Equal(t, 0, len(uploader.uploadedArtifacts))
|
||||
})
|
||||
t.Run("Uploading MTA project fails due to garbage YAML content", func(t *testing.T) {
|
||||
utils := newMockUtilsBundle(true, false)
|
||||
utils.files["mta.yaml"] = []byte("garbage")
|
||||
utils.cpe[".pipeline/commonPipelineEnvironment/mtarFilePath"] = "test.mtar"
|
||||
uploader := mockUploader{}
|
||||
options := createOptions()
|
||||
|
||||
err := runNexusUpload(&utils, &uploader, &options)
|
||||
assert.EqualError(t, err,
|
||||
"failed to parse contents of the project descriptor file 'mta.yaml'")
|
||||
assert.Equal(t, 0, len(uploader.GetArtifacts()))
|
||||
assert.Equal(t, 0, len(uploader.uploadedArtifacts))
|
||||
})
|
||||
t.Run("Uploading MTA project fails due invalid version in YAML content", func(t *testing.T) {
|
||||
utils := newMockUtilsBundle(true, false)
|
||||
utils.files["mta.yaml"] = []byte(testMtaYmlNoVersion)
|
||||
utils.cpe[".pipeline/commonPipelineEnvironment/mtarFilePath"] = "test.mtar"
|
||||
uploader := mockUploader{}
|
||||
options := createOptions()
|
||||
|
||||
err := runNexusUpload(&utils, &uploader, &options)
|
||||
assert.EqualError(t, err,
|
||||
"the project descriptor file 'mta.yaml' has an invalid version: version must not be empty")
|
||||
assert.Equal(t, 0, len(uploader.GetArtifacts()))
|
||||
assert.Equal(t, 0, len(uploader.uploadedArtifacts))
|
||||
})
|
||||
t.Run("Test uploading mta.yaml project fails due to missing mtar file", func(t *testing.T) {
|
||||
utils := newMockUtilsBundle(true, false)
|
||||
utils.files["mta.yaml"] = testMtaYml
|
||||
utils.cpe[".pipeline/commonPipelineEnvironment/mtarFilePath"] = "test.mtar"
|
||||
uploader := mockUploader{}
|
||||
options := createOptions()
|
||||
|
||||
err := runNexusUpload(&utils, &uploader, &options)
|
||||
assert.EqualError(t, err, "artifact file not found 'test.mtar'")
|
||||
|
||||
assert.Equal(t, "0.3.0", uploader.GetArtifactsVersion())
|
||||
assert.Equal(t, "artifact.id", uploader.GetArtifactsID())
|
||||
|
||||
// Check the artifacts that /would/ have been uploaded
|
||||
artifacts := uploader.GetArtifacts()
|
||||
if assert.Equal(t, 1, len(artifacts)) {
|
||||
assert.Equal(t, "mta.yaml", artifacts[0].File)
|
||||
assert.Equal(t, "yaml", artifacts[0].Type)
|
||||
}
|
||||
assert.Equal(t, 0, len(uploader.uploadedArtifacts))
|
||||
})
|
||||
t.Run("Test uploading mta.yaml project works", func(t *testing.T) {
|
||||
utils := newMockUtilsBundle(true, false)
|
||||
utils.files["mta.yaml"] = testMtaYml
|
||||
utils.files["test.mtar"] = []byte("contentsOfMtar")
|
||||
utils.cpe[".pipeline/commonPipelineEnvironment/mtarFilePath"] = "test.mtar"
|
||||
uploader := mockUploader{}
|
||||
options := createOptions()
|
||||
|
||||
err := runNexusUpload(&utils, &uploader, &options)
|
||||
assert.NoError(t, err, "expected mta.yaml project upload to work")
|
||||
|
||||
assert.Equal(t, "0.3.0", uploader.GetArtifactsVersion())
|
||||
assert.Equal(t, "artifact.id", uploader.GetArtifactsID())
|
||||
|
||||
artifacts := uploader.uploadedArtifacts
|
||||
if assert.Equal(t, 2, len(artifacts)) {
|
||||
assert.Equal(t, "mta.yaml", artifacts[0].File)
|
||||
assert.Equal(t, "yaml", artifacts[0].Type)
|
||||
|
||||
assert.Equal(t, "test.mtar", artifacts[1].File)
|
||||
assert.Equal(t, "mtar", artifacts[1].Type)
|
||||
}
|
||||
})
|
||||
t.Run("Test uploading mta.yml project works", func(t *testing.T) {
|
||||
utils := newMockUtilsBundle(true, false)
|
||||
utils.files["mta.yml"] = testMtaYml
|
||||
utils.files["test.mtar"] = []byte("contentsOfMtar")
|
||||
utils.cpe[".pipeline/commonPipelineEnvironment/mtarFilePath"] = "test.mtar"
|
||||
uploader := mockUploader{}
|
||||
options := createOptions()
|
||||
|
||||
err := runNexusUpload(&utils, &uploader, &options)
|
||||
assert.NoError(t, err, "expected mta.yml project upload to work")
|
||||
|
||||
assert.Equal(t, "0.3.0", uploader.GetArtifactsVersion())
|
||||
assert.Equal(t, "artifact.id", uploader.GetArtifactsID())
|
||||
|
||||
artifacts := uploader.uploadedArtifacts
|
||||
if assert.Equal(t, 2, len(artifacts)) {
|
||||
assert.Equal(t, "mta.yml", artifacts[0].File)
|
||||
assert.Equal(t, "yaml", artifacts[0].Type)
|
||||
|
||||
assert.Equal(t, "test.mtar", artifacts[1].File)
|
||||
assert.Equal(t, "mtar", artifacts[1].Type)
|
||||
}
|
||||
})
|
||||
t.Run("Test uploading mta.yml project works with artifactID from CPE", func(t *testing.T) {
|
||||
utils := newMockUtilsBundle(true, false)
|
||||
utils.files["mta.yml"] = testMtaYml
|
||||
utils.files["test.mtar"] = []byte("contentsOfMtar")
|
||||
utils.cpe[".pipeline/commonPipelineEnvironment/mtarFilePath"] = "test.mtar"
|
||||
utils.cpe[".pipeline/commonPipelineEnvironment/configuration/artifactId"] = "my-artifact-id"
|
||||
uploader := mockUploader{}
|
||||
options := createOptions()
|
||||
// Clear artifact ID to trigger reading it from the CPE
|
||||
options.ArtifactID = ""
|
||||
|
||||
err := runNexusUpload(&utils, &uploader, &options)
|
||||
assert.NoError(t, err, "expected mta.yml project upload to work")
|
||||
assert.Equal(t, "my-artifact-id", uploader.GetArtifactsID())
|
||||
|
||||
artifacts := uploader.uploadedArtifacts
|
||||
if assert.Equal(t, 2, len(artifacts)) {
|
||||
assert.Equal(t, "mta.yml", artifacts[0].File)
|
||||
assert.Equal(t, "yaml", artifacts[0].Type)
|
||||
|
||||
assert.Equal(t, "test.mtar", artifacts[1].File)
|
||||
assert.Equal(t, "mtar", artifacts[1].Type)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestUploadArtifacts(t *testing.T) {
|
||||
t.Run("Uploading MTA project fails without info", func(t *testing.T) {
|
||||
utils := newMockUtilsBundle(false, true)
|
||||
uploader := mockUploader{}
|
||||
options := createOptions()
|
||||
|
||||
err := uploadArtifacts(&utils, &uploader, &options, false)
|
||||
assert.EqualError(t, err, "no group ID was provided, or could be established from project files")
|
||||
})
|
||||
t.Run("Uploading MTA project fails without any artifacts", func(t *testing.T) {
|
||||
utils := newMockUtilsBundle(false, true)
|
||||
uploader := mockUploader{}
|
||||
options := createOptions()
|
||||
|
||||
_ = uploader.SetInfo(options.GroupID, "some.id", "3.0")
|
||||
|
||||
err := uploadArtifacts(&utils, &uploader, &options, false)
|
||||
assert.EqualError(t, err, "no artifacts to upload")
|
||||
})
|
||||
t.Run("Uploading MTA project fails for unknown reasons", func(t *testing.T) {
|
||||
utils := newMockUtilsBundle(false, true)
|
||||
|
||||
// Configure mocked execRunner to fail
|
||||
utils.execRunner.ShouldFailOnCommand = map[string]error{}
|
||||
utils.execRunner.ShouldFailOnCommand["mvn"] = fmt.Errorf("failed")
|
||||
|
||||
uploader := mockUploader{}
|
||||
options := createOptions()
|
||||
_ = uploader.SetInfo(options.GroupID, "some.id", "3.0")
|
||||
_ = uploader.AddArtifact(nexus.ArtifactDescription{
|
||||
File: "mta.yaml",
|
||||
Type: "yaml",
|
||||
})
|
||||
_ = uploader.AddArtifact(nexus.ArtifactDescription{
|
||||
File: "artifact.mtar",
|
||||
Type: "yaml",
|
||||
})
|
||||
|
||||
err := uploadArtifacts(&utils, &uploader, &options, false)
|
||||
assert.EqualError(t, err, "uploading artifacts for ID 'some.id' failed: failed to run executable, command: '[mvn -Durl=http:// -DgroupId=my.group.id -Dversion=3.0 -DartifactId=some.id -Dfile=mta.yaml -Dpackaging=yaml -DgeneratePom=false -Dfiles=artifact.mtar -Dclassifiers= -Dtypes=yaml --batch-mode "+deployGoal+"]', error: failed")
|
||||
})
|
||||
t.Run("Uploading bundle generates correct maven parameters", func(t *testing.T) {
|
||||
utils := newMockUtilsBundle(false, true)
|
||||
uploader := mockUploader{}
|
||||
options := createOptions()
|
||||
|
||||
_ = uploader.SetRepoURL("localhost:8081", "nexus3", "maven-releases")
|
||||
_ = uploader.SetInfo(options.GroupID, "my.artifact", "4.0")
|
||||
_ = uploader.AddArtifact(nexus.ArtifactDescription{
|
||||
File: "mta.yaml",
|
||||
Type: "yaml",
|
||||
})
|
||||
_ = uploader.AddArtifact(nexus.ArtifactDescription{
|
||||
File: "pom.yml",
|
||||
Type: "pom",
|
||||
})
|
||||
|
||||
err := uploadArtifacts(&utils, &uploader, &options, false)
|
||||
assert.NoError(t, err, "expected upload as two bundles to work")
|
||||
assert.Equal(t, 1, len(utils.execRunner.Calls))
|
||||
|
||||
expectedParameters1 := []string{
|
||||
"-Durl=http://localhost:8081/repository/maven-releases/",
|
||||
"-DgroupId=my.group.id",
|
||||
"-Dversion=4.0",
|
||||
"-DartifactId=my.artifact",
|
||||
"-Dfile=mta.yaml",
|
||||
"-Dpackaging=yaml",
|
||||
"-DgeneratePom=false",
|
||||
"-Dfiles=pom.yml",
|
||||
"-Dclassifiers=",
|
||||
"-Dtypes=pom",
|
||||
"--batch-mode",
|
||||
deployGoal}
|
||||
assert.Equal(t, len(expectedParameters1), len(utils.execRunner.Calls[0].Params))
|
||||
assert.Equal(t, mock.ExecCall{Exec: "mvn", Params: expectedParameters1}, utils.execRunner.Calls[0])
|
||||
})
|
||||
}
|
||||
|
||||
func TestUploadMavenProjects(t *testing.T) {
|
||||
t.Run("Uploading Maven project fails due to missing pom.xml", func(t *testing.T) {
|
||||
utils := newMockUtilsBundle(false, true)
|
||||
uploader := mockUploader{}
|
||||
options := createOptions()
|
||||
|
||||
err := runNexusUpload(&utils, &uploader, &options)
|
||||
assert.EqualError(t, err, "pom.xml not found")
|
||||
assert.Equal(t, 0, len(uploader.uploadedArtifacts))
|
||||
})
|
||||
t.Run("Test uploading Maven project with POM packaging works", func(t *testing.T) {
|
||||
utils := newMockUtilsBundle(false, true)
|
||||
utils.setProperty("pom.xml", "project.version", "1.0")
|
||||
utils.setProperty("pom.xml", "project.groupId", "com.mycompany.app")
|
||||
utils.setProperty("pom.xml", "project.artifactId", "my-app")
|
||||
utils.setProperty("pom.xml", "project.packaging", "pom")
|
||||
utils.setProperty("pom.xml", "project.build.finalName", "my-app-1.0")
|
||||
utils.files["pom.xml"] = testPomXml
|
||||
uploader := mockUploader{}
|
||||
options := createOptions()
|
||||
|
||||
err := runNexusUpload(&utils, &uploader, &options)
|
||||
assert.NoError(t, err, "expected Maven upload to work")
|
||||
assert.Equal(t, "1.0", uploader.GetArtifactsVersion())
|
||||
assert.Equal(t, "my-app", uploader.GetArtifactsID())
|
||||
|
||||
artifacts := uploader.uploadedArtifacts
|
||||
if assert.Equal(t, 1, len(artifacts)) {
|
||||
assert.Equal(t, "pom.xml", artifacts[0].File)
|
||||
assert.Equal(t, "pom", artifacts[0].Type)
|
||||
}
|
||||
})
|
||||
t.Run("Test uploading Maven project with JAR packaging works", func(t *testing.T) {
|
||||
utils := newMockUtilsBundle(false, true)
|
||||
utils.setProperty("pom.xml", "project.version", "1.0")
|
||||
utils.setProperty("pom.xml", "project.groupId", "com.mycompany.app")
|
||||
utils.setProperty("pom.xml", "project.artifactId", "my-app")
|
||||
utils.setProperty("pom.xml", "project.packaging", "jar")
|
||||
utils.setProperty("pom.xml", "project.build.finalName", "my-app-1.0")
|
||||
utils.files["pom.xml"] = testPomXml
|
||||
utils.files["target/my-app-1.0.jar"] = []byte("contentsOfJar")
|
||||
uploader := mockUploader{}
|
||||
options := createOptions()
|
||||
|
||||
err := runNexusUpload(&utils, &uploader, &options)
|
||||
assert.NoError(t, err, "expected Maven upload to work")
|
||||
|
||||
assert.Equal(t, "1.0", uploader.GetArtifactsVersion())
|
||||
assert.Equal(t, "my-app", uploader.GetArtifactsID())
|
||||
|
||||
artifacts := uploader.uploadedArtifacts
|
||||
if assert.Equal(t, 2, len(artifacts)) {
|
||||
assert.Equal(t, "pom.xml", artifacts[0].File)
|
||||
assert.Equal(t, "pom", artifacts[0].Type)
|
||||
|
||||
assert.Equal(t, "target/my-app-1.0.jar", artifacts[1].File)
|
||||
assert.Equal(t, "jar", artifacts[1].Type)
|
||||
}
|
||||
})
|
||||
t.Run("Test uploading Maven project with fall-back to JAR packaging works", func(t *testing.T) {
|
||||
utils := newMockUtilsBundle(false, true)
|
||||
utils.setProperty("pom.xml", "project.version", "1.0")
|
||||
utils.setProperty("pom.xml", "project.groupId", "com.mycompany.app")
|
||||
utils.setProperty("pom.xml", "project.artifactId", "my-app")
|
||||
utils.setProperty("pom.xml", "project.packaging", "<empty>")
|
||||
utils.setProperty("pom.xml", "project.build.finalName", "my-app-1.0")
|
||||
utils.files["pom.xml"] = testPomXml
|
||||
utils.files["target/my-app-1.0.jar"] = []byte("contentsOfJar")
|
||||
uploader := mockUploader{}
|
||||
options := createOptions()
|
||||
|
||||
err := runNexusUpload(&utils, &uploader, &options)
|
||||
assert.NoError(t, err, "expected Maven upload to work")
|
||||
assert.Equal(t, "1.0", uploader.GetArtifactsVersion())
|
||||
assert.Equal(t, "my-app", uploader.GetArtifactsID())
|
||||
|
||||
artifacts := uploader.uploadedArtifacts
|
||||
if assert.Equal(t, 2, len(artifacts)) {
|
||||
assert.Equal(t, "pom.xml", artifacts[0].File)
|
||||
assert.Equal(t, "pom", artifacts[0].Type)
|
||||
|
||||
assert.Equal(t, "target/my-app-1.0.jar", artifacts[1].File)
|
||||
assert.Equal(t, "jar", artifacts[1].Type)
|
||||
}
|
||||
})
|
||||
t.Run("Test uploading Maven project with fall-back to group id from parameters works", func(t *testing.T) {
|
||||
utils := newMockUtilsBundle(false, true)
|
||||
utils.setProperty("pom.xml", "project.version", "1.0")
|
||||
utils.setProperty("pom.xml", "project.artifactId", "my-app")
|
||||
utils.setProperty("pom.xml", "project.packaging", "pom")
|
||||
utils.setProperty("pom.xml", "project.build.finalName", "my-app-1.0")
|
||||
utils.files["pom.xml"] = testPomXml
|
||||
uploader := mockUploader{}
|
||||
options := createOptions()
|
||||
options.GroupID = "awesome.group"
|
||||
|
||||
err := runNexusUpload(&utils, &uploader, &options)
|
||||
assert.NoError(t, err, "expected Maven upload to work")
|
||||
|
||||
assert.Equal(t, "localhost:8081/repository/maven-releases/",
|
||||
uploader.GetRepoURL())
|
||||
assert.Equal(t, "1.0", uploader.GetArtifactsVersion())
|
||||
assert.Equal(t, "my-app", uploader.GetArtifactsID())
|
||||
|
||||
artifacts := uploader.uploadedArtifacts
|
||||
if assert.Equal(t, 1, len(artifacts)) {
|
||||
assert.Equal(t, "pom.xml", artifacts[0].File)
|
||||
assert.Equal(t, "pom", artifacts[0].Type)
|
||||
}
|
||||
})
|
||||
t.Run("Test uploading Maven project with application module and finalName works", func(t *testing.T) {
|
||||
utils := newMockUtilsBundle(false, true)
|
||||
utils.setProperty("pom.xml", "project.version", "1.0")
|
||||
utils.setProperty("pom.xml", "project.groupId", "com.mycompany.app")
|
||||
utils.setProperty("pom.xml", "project.artifactId", "my-app")
|
||||
utils.setProperty("pom.xml", "project.packaging", "pom")
|
||||
utils.setProperty("pom.xml", "project.build.finalName", "my-app-1.0")
|
||||
utils.setProperty("application/pom.xml", "project.version", "1.0")
|
||||
utils.setProperty("application/pom.xml", "project.groupId", "com.mycompany.app")
|
||||
utils.setProperty("application/pom.xml", "project.artifactId", "my-app-app")
|
||||
utils.setProperty("application/pom.xml", "project.packaging", "jar")
|
||||
utils.setProperty("application/pom.xml", "project.build.finalName", "final-artifact")
|
||||
utils.files["pom.xml"] = testPomXml
|
||||
utils.files["application/pom.xml"] = testPomXml
|
||||
utils.files["application/target/final-artifact.jar"] = []byte("contentsOfJar")
|
||||
utils.files["application/target/final-artifact-classes.jar"] = []byte("contentsOfClassesJar")
|
||||
uploader := mockUploader{}
|
||||
options := createOptions()
|
||||
options.AdditionalClassifiers = `
|
||||
[
|
||||
{
|
||||
"classifier" : "classes",
|
||||
"type" : "jar"
|
||||
}
|
||||
]
|
||||
`
|
||||
|
||||
err := runNexusUpload(&utils, &uploader, &options)
|
||||
assert.NoError(t, err, "expected upload of maven project with application module to succeed")
|
||||
assert.Equal(t, "1.0", uploader.GetArtifactsVersion())
|
||||
assert.Equal(t, "my-app-app", uploader.GetArtifactsID())
|
||||
|
||||
artifacts := uploader.uploadedArtifacts
|
||||
if assert.Equal(t, 4, len(artifacts)) {
|
||||
assert.Equal(t, "pom.xml", artifacts[0].File)
|
||||
assert.Equal(t, "pom", artifacts[0].Type)
|
||||
|
||||
assert.Equal(t, "application/pom.xml", artifacts[1].File)
|
||||
assert.Equal(t, "pom", artifacts[1].Type)
|
||||
|
||||
assert.Equal(t, "application/target/final-artifact.jar", artifacts[2].File)
|
||||
assert.Equal(t, "jar", artifacts[2].Type)
|
||||
|
||||
assert.Equal(t, "application/target/final-artifact-classes.jar", artifacts[3].File)
|
||||
assert.Equal(t, "jar", artifacts[3].Type)
|
||||
}
|
||||
if assert.Equal(t, 2, len(utils.execRunner.Calls)) {
|
||||
expectedParameters1 := []string{
|
||||
"-Durl=http://localhost:8081/repository/maven-releases/",
|
||||
"-DgroupId=com.mycompany.app",
|
||||
"-Dversion=1.0",
|
||||
"-DartifactId=my-app",
|
||||
"-Dfile=pom.xml",
|
||||
"-Dpackaging=pom",
|
||||
"--batch-mode",
|
||||
deployGoal}
|
||||
assert.Equal(t, len(expectedParameters1), len(utils.execRunner.Calls[0].Params))
|
||||
assert.Equal(t, mock.ExecCall{Exec: "mvn", Params: expectedParameters1}, utils.execRunner.Calls[0])
|
||||
|
||||
expectedParameters2 := []string{
|
||||
"-Durl=http://localhost:8081/repository/maven-releases/",
|
||||
"-DgroupId=com.mycompany.app",
|
||||
"-Dversion=1.0",
|
||||
"-DartifactId=my-app-app",
|
||||
"-Dfile=application/pom.xml",
|
||||
"-Dpackaging=pom",
|
||||
"-Dfiles=application/target/final-artifact.jar,application/target/final-artifact-classes.jar",
|
||||
"-Dclassifiers=,classes",
|
||||
"-Dtypes=jar,jar",
|
||||
"--batch-mode",
|
||||
deployGoal}
|
||||
assert.Equal(t, len(expectedParameters2), len(utils.execRunner.Calls[1].Params))
|
||||
assert.Equal(t, mock.ExecCall{Exec: "mvn", Params: expectedParameters2}, utils.execRunner.Calls[1])
|
||||
}
|
||||
})
|
||||
t.Run("Test uploading Maven project fails without packaging", func(t *testing.T) {
|
||||
utils := newMockUtilsBundle(false, true)
|
||||
utils.setProperty("pom.xml", "project.version", "1.0")
|
||||
utils.setProperty("pom.xml", "project.groupId", "com.mycompany.app")
|
||||
utils.setProperty("pom.xml", "project.artifactId", "my-app")
|
||||
utils.files["pom.xml"] = testPomXml
|
||||
utils.files["target/my-app-1.0.jar"] = []byte("contentsOfJar")
|
||||
uploader := mockUploader{}
|
||||
options := createOptions()
|
||||
|
||||
err := runNexusUpload(&utils, &uploader, &options)
|
||||
assert.EqualError(t, err, "property 'project.packaging' not found in 'pom.xml'")
|
||||
|
||||
artifacts := uploader.GetArtifacts()
|
||||
if assert.Equal(t, 1, len(artifacts)) {
|
||||
assert.Equal(t, "pom.xml", artifacts[0].File)
|
||||
assert.Equal(t, "pom", artifacts[0].Type)
|
||||
}
|
||||
assert.Equal(t, 0, len(uploader.uploadedArtifacts))
|
||||
})
|
||||
t.Run("Write credentials settings", func(t *testing.T) {
|
||||
utils := newMockUtilsBundle(false, true)
|
||||
utils.setProperty("pom.xml", "project.version", "1.0")
|
||||
utils.setProperty("pom.xml", "project.groupId", "com.mycompany.app")
|
||||
utils.setProperty("pom.xml", "project.artifactId", "my-app")
|
||||
utils.setProperty("pom.xml", "project.packaging", "pom")
|
||||
utils.setProperty("pom.xml", "project.build.finalName", "my-app-1.0")
|
||||
utils.files["pom.xml"] = testPomXml
|
||||
uploader := mockUploader{}
|
||||
options := createOptions()
|
||||
options.User = "admin"
|
||||
options.Password = "admin123"
|
||||
|
||||
err := runNexusUpload(&utils, &uploader, &options)
|
||||
assert.NoError(t, err, "expected Maven upload to work")
|
||||
|
||||
assert.Equal(t, 1, len(utils.execRunner.Calls))
|
||||
expectedParameters1 := []string{
|
||||
"--settings",
|
||||
settingsPath,
|
||||
"-Durl=http://localhost:8081/repository/maven-releases/",
|
||||
"-DgroupId=com.mycompany.app",
|
||||
"-Dversion=1.0",
|
||||
"-DartifactId=my-app",
|
||||
"-DrepositoryId=" + settingsServerID,
|
||||
"-Dfile=pom.xml",
|
||||
"-Dpackaging=pom",
|
||||
"--batch-mode",
|
||||
deployGoal}
|
||||
assert.Equal(t, len(expectedParameters1), len(utils.execRunner.Calls[0].Params))
|
||||
assert.Equal(t, mock.ExecCall{Exec: "mvn", Params: expectedParameters1}, utils.execRunner.Calls[0])
|
||||
|
||||
expectedEnv := []string{"NEXUS_username=admin", "NEXUS_password=admin123"}
|
||||
assert.Equal(t, 2, len(utils.execRunner.Env))
|
||||
assert.Equal(t, expectedEnv, utils.execRunner.Env)
|
||||
|
||||
assert.Nil(t, utils.files[settingsPath])
|
||||
assert.NotNil(t, utils.removedFiles[settingsPath])
|
||||
})
|
||||
}
|
||||
|
||||
func TestUploadUnknownProjectFails(t *testing.T) {
|
||||
utils := newMockUtilsBundle(false, false)
|
||||
uploader := mockUploader{}
|
||||
options := createOptions()
|
||||
|
||||
err := runNexusUpload(&utils, &uploader, &options)
|
||||
assert.EqualError(t, err, "unsupported project structure")
|
||||
}
|
||||
|
||||
func TestAdditionalClassifierEmpty(t *testing.T) {
|
||||
t.Run("Empty additional classifiers", func(t *testing.T) {
|
||||
utils := newMockUtilsBundle(false, false)
|
||||
client, err := testAdditionalClassifierArtifacts(&utils, "")
|
||||
assert.NoError(t, err, "expected empty additional classifiers to succeed")
|
||||
assert.Equal(t, 0, len(client.GetArtifacts()))
|
||||
})
|
||||
t.Run("Additional classifiers is invalid JSON", func(t *testing.T) {
|
||||
utils := newMockUtilsBundle(false, false)
|
||||
client, err := testAdditionalClassifierArtifacts(&utils, "some random string")
|
||||
assert.Error(t, err, "expected invalid additional classifiers to fail")
|
||||
assert.Equal(t, 0, len(client.GetArtifacts()))
|
||||
})
|
||||
t.Run("Classifiers valid but wrong JSON", func(t *testing.T) {
|
||||
json := `
|
||||
[
|
||||
{
|
||||
"classifier" : "source",
|
||||
"type" : "jar"
|
||||
},
|
||||
{}
|
||||
]
|
||||
`
|
||||
utils := newMockUtilsBundle(false, false)
|
||||
utils.files["some folder/artifact-id-source.jar"] = []byte("contentsOfJar")
|
||||
client, err := testAdditionalClassifierArtifacts(&utils, json)
|
||||
assert.Error(t, err, "expected invalid additional classifiers to fail")
|
||||
assert.Equal(t, 1, len(client.GetArtifacts()))
|
||||
})
|
||||
t.Run("Classifiers valid but does not exist", func(t *testing.T) {
|
||||
json := `
|
||||
[
|
||||
{
|
||||
"classifier" : "source",
|
||||
"type" : "jar"
|
||||
}
|
||||
]
|
||||
`
|
||||
utils := newMockUtilsBundle(false, false)
|
||||
client, err := testAdditionalClassifierArtifacts(&utils, json)
|
||||
assert.EqualError(t, err, "artifact file not found 'some folder/artifact-id-source.jar'")
|
||||
assert.Equal(t, 0, len(client.GetArtifacts()))
|
||||
})
|
||||
t.Run("Additional classifiers is valid JSON", func(t *testing.T) {
|
||||
json := `
|
||||
[
|
||||
{
|
||||
"classifier" : "source",
|
||||
"type" : "jar"
|
||||
},
|
||||
{
|
||||
"classifier" : "classes",
|
||||
"type" : "jar"
|
||||
}
|
||||
]
|
||||
`
|
||||
utils := newMockUtilsBundle(false, false)
|
||||
utils.files["some folder/artifact-id-source.jar"] = []byte("contentsOfJar")
|
||||
utils.files["some folder/artifact-id-classes.jar"] = []byte("contentsOfJar")
|
||||
client, err := testAdditionalClassifierArtifacts(&utils, json)
|
||||
assert.NoError(t, err, "expected valid additional classifiers to succeed")
|
||||
assert.Equal(t, 2, len(client.GetArtifacts()))
|
||||
})
|
||||
}
|
||||
|
||||
func testAdditionalClassifierArtifacts(utils nexusUploadUtils, additionalClassifiers string) (*nexus.Upload, error) {
|
||||
client := nexus.Upload{}
|
||||
_ = client.SetInfo("group.id", "artifact-id", "1.0")
|
||||
return &client, addMavenTargetSubArtifacts(utils, &client, additionalClassifiers,
|
||||
"some folder", "artifact-id")
|
||||
}
|
||||
|
||||
func TestSetupNexusCredentialsSettingsFile(t *testing.T) {
|
||||
utils := newMockUtilsBundle(false, true)
|
||||
options := nexusUploadOptions{User: "admin", Password: "admin123"}
|
||||
mavenOptions := maven.ExecuteOptions{}
|
||||
settingsPath, err := setupNexusCredentialsSettingsFile(&utils, &options, &mavenOptions)
|
||||
|
||||
assert.NoError(t, err, "expected setting up credentials settings.xml to work")
|
||||
assert.Equal(t, 0, len(utils.execRunner.Calls))
|
||||
expectedEnv := []string{"NEXUS_username=admin", "NEXUS_password=admin123"}
|
||||
assert.Equal(t, 2, len(utils.execRunner.Env))
|
||||
assert.Equal(t, expectedEnv, utils.execRunner.Env)
|
||||
|
||||
assert.True(t, settingsPath != "")
|
||||
assert.NotNil(t, utils.files[settingsPath])
|
||||
}
|
@ -61,6 +61,7 @@ func Execute() {
|
||||
rootCmd.AddCommand(MavenExecuteCommand())
|
||||
rootCmd.AddCommand(MavenBuildCommand())
|
||||
rootCmd.AddCommand(MavenExecuteStaticCodeChecksCommand())
|
||||
rootCmd.AddCommand(NexusUploadCommand())
|
||||
|
||||
addRootFlags(rootCmd)
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
|
81
documentation/architecture/decisions/nexusUpload.md
Normal file
81
documentation/architecture/decisions/nexusUpload.md
Normal file
@ -0,0 +1,81 @@
|
||||
# Artifact Deployment to Nexus
|
||||
|
||||
## Status
|
||||
|
||||
Accepted
|
||||
|
||||
## Context
|
||||
|
||||
The nexusUpload step shall upload (deploy) build artifacts to a Nexus Repository Manager. Nexus version 2 and 3 need to be supported.
|
||||
Per module, there can be an artifact and multiple sub-artifacts, which need to be deployed as a unit, optionally together with the project descriptor file (i.e. pom.xml or mta.yaml).
|
||||
A Nexus contains repositories of different type. For example, a "release" repository does not allow updating existing artifacts, while a "snapshot" repository allows for multiple builds of the same snapshot version, with the notion of a "latest" build.
|
||||
Depending on the type of repository, a certain directory layout has to be obeyed, and `maven-metadata.xml` files have to be maintained in order to be compatible with tools consuming the artifacts. The Nexus itself may also have mechanisms in place, for example to automatically purge old builds in snapshot releases.
|
||||
All this makes it important to make compatible deployments.
|
||||
|
||||
### Alternatives
|
||||
|
||||
* [Apache Maven Deploy Plugin](http://maven.apache.org/plugins/maven-deploy-plugin/)
|
||||
* Maven lifecycle phase : deploy
|
||||
* Uploading artifacts manually
|
||||
|
||||
### Pros and Cons
|
||||
|
||||
#### Apache Maven Deploy Plugin (deploy:deploy-file)
|
||||
|
||||
For this option, we only consider the goal `deploy:deploy-file`.
|
||||
|
||||
##### :+1:
|
||||
|
||||
- Official maven plugin for deployment, which is perfect if you only care whether the artifacts are deployed correctly.
|
||||
|
||||
##### :-1:
|
||||
|
||||
- Knowledge about which artifacts to deploy has to be obtained manually.
|
||||
- A list of parameters has to be generated before using the plugin, including `artifactId` and `version`, which is the same case as the `Uploading artifacts manually`. For maven projects, the parameters can be obtained using the `evaluate` goal of the `maven-help-plugin`. There is however a performance impact, since a maven command line has to be executed for each parameter, multiplied by the number of modules. This is not a problem for `Maven lifecycle phase : deploy`.
|
||||
- Credential info has to be stored in a `settings.xml`, which introduces additional implementation. Credentials can be passed via environment variables.
|
||||
|
||||
#### Maven lifecycle phase: deploy
|
||||
|
||||
By default, the maven lifecycle phase `deploy` binds to the goal `deploy:deploy` of the `Apache Maven Deploy Plugin`.
|
||||
|
||||
##### :+1:
|
||||
|
||||
- Same as the `Apache Maven Deploy Plugin`
|
||||
- You don't have to obtain and pass the parameters as for `Apache Maven Deploy Plugin`, because `package` phase is executed implicitly and makes the parameters ready before `deploy` phase.
|
||||
- Supports multi-module Maven projects and any project structure.
|
||||
|
||||
##### :-1:
|
||||
|
||||
- Same case as the `Apache Maven Deploy Plugin` for handling credentials.
|
||||
- Cannot be used for non-Maven projects (i.e. MTA)
|
||||
- As a maven phase, a list of phases is triggered implicitly before this phase, including `compile`, `test` and `package`.
|
||||
To follow the build-once principle, all these phases have to be skipped.
|
||||
However, it's not possible to skip some of the maven goals binding to certain phases.
|
||||
For example, if the `<packaging>` tag of the `pom.xml` is set to `jar`, then the `jar:jar` goal of the [`Apache Maven JAR Plugin`](https://maven.apache.org/plugins/maven-jar-plugin/) is bound to the `package` phase.
|
||||
Unfortunately, however, `Apache Maven JAR Plugin` does not provide an option to skip the the `jar:jar` goal. There may be a [solution](https://stackoverflow.com/questions/47673545/how-to-skip-jar-deploy-in-maven-and-deploy-the-assembly-only), but it seems to require modifying the pom and could also be different depending on the used packaging.
|
||||
**This is the main reason why we cannot use this option.**
|
||||
|
||||
#### Uploading artifacts manually
|
||||
|
||||
Files can be uploaded to the Nexus by simple HTTP PUT requests, using basic authentication if necessary. Meta-data files have to be downloaded, updated and re-uploaded after successful upload of the artifacts.
|
||||
|
||||
##### :+1:
|
||||
|
||||
- Without the pain of handling the credentials, which was mentioned above in `Apache Maven Deploy Plugin` section.
|
||||
- Gives full control over the implementation.
|
||||
|
||||
##### :-1:
|
||||
|
||||
- Same as the `Apache Maven Deploy Plugin`. Knowledge about which artifacts to deploy has to be obtained manually.
|
||||
- Same as the `Apache Maven Deploy Plugin`. A list of parameters has to be prepared.
|
||||
- Introduces complexity for maintaining maven-metadata.xml. For example there is a great difference between "release" and "snapshot" deployments. The later have a build number and another directory structure on the nexus (arbitrary number of builds per version, with metadata for each build and for the version).
|
||||
- Has the greatest maintenance-overhead.
|
||||
|
||||
### Decision
|
||||
|
||||
`Apache Maven Deploy Plugin` is chosen, because:
|
||||
|
||||
- `Maven lifecycle phase: deploy` conflicts with the build-once principle.
|
||||
- Credentials handling is not very complex to implement.
|
||||
- Has the fine-grained control needed over which artifacts are deployed.
|
||||
- Maintains maven-metadata.xml correctly for various types of deployments.
|
2
go.mod
2
go.mod
@ -17,7 +17,7 @@ require (
|
||||
github.com/spf13/cobra v0.0.5
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/testcontainers/testcontainers-go v0.1.0
|
||||
github.com/testcontainers/testcontainers-go v0.2.0
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.4
|
||||
|
4
go.sum
4
go.sum
@ -310,8 +310,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/testcontainers/testcontainers-go v0.1.0 h1:R7CZ/dgrendXg5T+gPffAZ2sRQq38kqdPelICJshXLo=
|
||||
github.com/testcontainers/testcontainers-go v0.1.0/go.mod h1:5aBi+1PJmFixVc3b349A7NrhyTRYkMDpZEtT5MFxCs8=
|
||||
github.com/testcontainers/testcontainers-go v0.2.0 h1:7PED4vniZMXNkPln4zg9U9ZDp4MmD/LuX0LGYTnByE0=
|
||||
github.com/testcontainers/testcontainers-go v0.2.0/go.mod h1:5aBi+1PJmFixVc3b349A7NrhyTRYkMDpZEtT5MFxCs8=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
|
156
integration/integration_nexus_upload_test.go
Normal file
156
integration/integration_nexus_upload_test.go
Normal file
@ -0,0 +1,156 @@
|
||||
// +build integration
|
||||
// can be execute with go test -tags=integration ./integration/...
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/command"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
"github.com/testcontainers/testcontainers-go/wait"
|
||||
)
|
||||
|
||||
func TestNexusUpload(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
req := testcontainers.ContainerRequest{
|
||||
Image: "sonatype/nexus3:3.21.1",
|
||||
ExposedPorts: []string{"8081/tcp"},
|
||||
Env: map[string]string{"NEXUS_SECURITY_RANDOMPASSWORD": "false"},
|
||||
WaitingFor: wait.ForLog("Started Sonatype Nexus").WithStartupTimeout(5 * time.Minute), // Nexus takes more than one minute to boot
|
||||
}
|
||||
nexusContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
|
||||
ContainerRequest: req,
|
||||
Started: true,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
defer nexusContainer.Terminate(ctx)
|
||||
ip, err := nexusContainer.Host(ctx)
|
||||
assert.NoError(t, err)
|
||||
port, err := nexusContainer.MappedPort(ctx, "8081")
|
||||
assert.NoError(t, err, "Could not map port for nexus container")
|
||||
nexusIpAndPort := fmt.Sprintf("%s:%s", ip, port.Port())
|
||||
url := "http://" + nexusIpAndPort
|
||||
resp, err := http.Get(url)
|
||||
assert.Equal(t, resp.StatusCode, http.StatusOK)
|
||||
|
||||
cmd := command.Command{}
|
||||
cmd.SetDir("testdata/TestNexusIntegration/mta")
|
||||
|
||||
piperOptions := []string{
|
||||
"nexusUpload",
|
||||
"--groupId=mygroup",
|
||||
"--artifactId=mymta",
|
||||
"--user=admin",
|
||||
"--password=admin123",
|
||||
"--repository=maven-releases",
|
||||
"--url=" + nexusIpAndPort,
|
||||
}
|
||||
|
||||
err = cmd.RunExecutable(getPiperExecutable(), piperOptions...)
|
||||
assert.NoError(t, err, "Calling piper with arguments %v failed.", piperOptions)
|
||||
|
||||
cmd = command.Command{}
|
||||
cmd.SetDir("testdata/TestNexusIntegration/maven")
|
||||
|
||||
piperOptions = []string{
|
||||
"nexusUpload",
|
||||
"--user=admin",
|
||||
"--password=admin123",
|
||||
"--repository=maven-releases",
|
||||
"--url=" + nexusIpAndPort,
|
||||
}
|
||||
|
||||
err = cmd.RunExecutable(getPiperExecutable(), piperOptions...)
|
||||
assert.NoError(t, err, "Calling piper with arguments %v failed.", piperOptions)
|
||||
|
||||
resp, err = http.Get(url + "/repository/maven-releases/com/mycompany/app/my-app/1.0/my-app-1.0.pom")
|
||||
assert.NoError(t, err, "Downloading artifact failed")
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode, "Get my-app-1.0.pom: %s", resp.Status)
|
||||
|
||||
resp, err = http.Get(url + "/repository/maven-releases/com/mycompany/app/my-app/1.0/my-app-1.0.jar")
|
||||
assert.NoError(t, err, "Downloading artifact failed")
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode, "Get my-app-1.0.jar: %s", resp.Status)
|
||||
|
||||
resp, err = http.Get(url + "/repository/maven-releases/mygroup/mymta/0.3.0/mymta-0.3.0.yaml")
|
||||
assert.NoError(t, err, "Downloading artifact failed")
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode, "Get mymta-0.3.0.yaml: %s", resp.Status)
|
||||
|
||||
resp, err = http.Get(url + "/repository/maven-releases/mygroup/mymta/0.3.0/mymta-0.3.0.mtar")
|
||||
assert.NoError(t, err, "Downloading artifact failed")
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode, "Get mymta-0.3.0.mtar: %s", resp.Status)
|
||||
}
|
||||
|
||||
func TestNexus2Upload(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
req := testcontainers.ContainerRequest{
|
||||
Image: "sonatype/nexus:2.14.16-01",
|
||||
ExposedPorts: []string{"8081/tcp"},
|
||||
WaitingFor: wait.ForLog("org.sonatype.nexus.bootstrap.jetty.JettyServer - Running").WithStartupTimeout(5 * time.Minute), // Nexus takes more than one minute to boot
|
||||
}
|
||||
nexusContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
|
||||
ContainerRequest: req,
|
||||
Started: true,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
defer nexusContainer.Terminate(ctx)
|
||||
ip, err := nexusContainer.Host(ctx)
|
||||
assert.NoError(t, err)
|
||||
port, err := nexusContainer.MappedPort(ctx, "8081")
|
||||
assert.NoError(t, err, "Could not map port for nexus container")
|
||||
nexusIpAndPort := fmt.Sprintf("%s:%s", ip, port.Port())
|
||||
url := "http://" + nexusIpAndPort + "/nexus/"
|
||||
|
||||
cmd := command.Command{}
|
||||
cmd.SetDir("testdata/TestNexusIntegration/mta")
|
||||
|
||||
piperOptions := []string{
|
||||
"nexusUpload",
|
||||
"--groupId=mygroup",
|
||||
"--artifactId=mymta",
|
||||
"--user=admin",
|
||||
"--password=admin123",
|
||||
"--repository=releases",
|
||||
"--version=nexus2",
|
||||
"--url=" + nexusIpAndPort + "/nexus/",
|
||||
}
|
||||
|
||||
err = cmd.RunExecutable(getPiperExecutable(), piperOptions...)
|
||||
assert.NoError(t, err, "Calling piper with arguments %v failed.", piperOptions)
|
||||
|
||||
cmd = command.Command{}
|
||||
cmd.SetDir("testdata/TestNexusIntegration/maven")
|
||||
|
||||
piperOptions = []string{
|
||||
"nexusUpload",
|
||||
"--user=admin",
|
||||
"--password=admin123",
|
||||
"--repository=releases",
|
||||
"--version=nexus2",
|
||||
"--url=" + nexusIpAndPort + "/nexus/",
|
||||
}
|
||||
|
||||
err = cmd.RunExecutable(getPiperExecutable(), piperOptions...)
|
||||
assert.NoError(t, err, "Calling piper with arguments %v failed.", piperOptions)
|
||||
|
||||
resp, err := http.Get(url + "content/repositories/releases/com/mycompany/app/my-app/1.0/my-app-1.0.pom")
|
||||
assert.NoError(t, err, "Downloading artifact failed")
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode, "Get my-app-1.0.pom: %s", resp.Status)
|
||||
|
||||
resp, err = http.Get(url + "content/repositories/releases/com/mycompany/app/my-app/1.0/my-app-1.0.jar")
|
||||
assert.NoError(t, err, "Downloading artifact failed")
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode, "Get my-app-1.0.jar: %s", resp.Status)
|
||||
|
||||
resp, err = http.Get(url + "content/repositories/releases/mygroup/mymta/0.3.0/mymta-0.3.0.yaml")
|
||||
assert.NoError(t, err, "Downloading artifact failed")
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode, "Get mymta-0.3.0.yaml: %s", resp.Status)
|
||||
|
||||
resp, err = http.Get(url + "content/repositories/releases/mygroup/mymta/0.3.0/mymta-0.3.0.mtar")
|
||||
assert.NoError(t, err, "Downloading artifact failed")
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode, "Get mymta-0.3.0.mtar: %s", resp.Status)
|
||||
}
|
7
integration/testdata/TestNexusIntegration/maven/pom.xml
vendored
Normal file
7
integration/testdata/TestNexusIntegration/maven/pom.xml
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
<project>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.mycompany.app</groupId>
|
||||
<artifactId>my-app</artifactId>
|
||||
<version>1.0</version>
|
||||
<packaging>jar</packaging>
|
||||
</project>
|
BIN
integration/testdata/TestNexusIntegration/maven/target/my-app-1.0.jar
vendored
Normal file
BIN
integration/testdata/TestNexusIntegration/maven/target/my-app-1.0.jar
vendored
Normal file
Binary file not shown.
1
integration/testdata/TestNexusIntegration/mta/.pipeline/commonPipelineEnvironment/mtarFilePath
vendored
Executable file
1
integration/testdata/TestNexusIntegration/mta/.pipeline/commonPipelineEnvironment/mtarFilePath
vendored
Executable file
@ -0,0 +1 @@
|
||||
test.mtar
|
10
integration/testdata/TestNexusIntegration/mta/mta.yaml
vendored
Normal file
10
integration/testdata/TestNexusIntegration/mta/mta.yaml
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
_schema-version: 2.1.0
|
||||
ID: test
|
||||
version: 0.3.0
|
||||
|
||||
modules:
|
||||
|
||||
- name: java
|
||||
type: java
|
||||
path: srv
|
||||
|
BIN
integration/testdata/TestNexusIntegration/mta/test.mtar
vendored
Normal file
BIN
integration/testdata/TestNexusIntegration/mta/test.mtar
vendored
Normal file
Binary file not shown.
@ -1,78 +1,106 @@
|
||||
package nexus
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
piperHttp "github.com/SAP/jenkins-library/pkg/http"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/sirupsen/logrus"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ArtifactDescription describes a single artifact that can be uploaded to a Nexus repository manager.
|
||||
// The File string must point to an existing file. The Classifier can be empty.
|
||||
type ArtifactDescription struct {
|
||||
ID string `json:"artifactId"`
|
||||
Classifier string `json:"classifier"`
|
||||
Type string `json:"type"`
|
||||
File string `json:"file"`
|
||||
}
|
||||
|
||||
// Upload holds state for an upload session. Call SetBaseURL(), SetArtifactsVersion() and add at least
|
||||
// one artifact via AddArtifact(). Then call UploadArtifacts().
|
||||
// Upload combines information about an artifact and its sub-artifacts which are supposed to be uploaded together.
|
||||
// Call SetRepoURL(), SetArtifactsVersion(), SetArtifactID(), and add at least one artifact via AddArtifact().
|
||||
type Upload struct {
|
||||
baseURL string
|
||||
version string
|
||||
Username string
|
||||
Password string
|
||||
artifacts []ArtifactDescription
|
||||
Logger *logrus.Entry
|
||||
repoURL string
|
||||
groupID string
|
||||
version string
|
||||
artifactID string
|
||||
artifacts []ArtifactDescription
|
||||
}
|
||||
|
||||
// Uploader provides an interface to the nexus upload for configuring the target Nexus Repository and
|
||||
// adding artifacts.
|
||||
// Uploader provides an interface for configuring the target Nexus Repository and adding artifacts.
|
||||
type Uploader interface {
|
||||
SetBaseURL(nexusURL, nexusVersion, repository, groupID string) error
|
||||
SetArtifactsVersion(version string) error
|
||||
SetRepoURL(nexusURL, nexusVersion, repository string) error
|
||||
GetRepoURL() string
|
||||
SetInfo(groupID, artifactsID, version string) error
|
||||
GetGroupID() string
|
||||
GetArtifactsID() string
|
||||
GetArtifactsVersion() string
|
||||
AddArtifact(artifact ArtifactDescription) error
|
||||
GetArtifacts() []ArtifactDescription
|
||||
UploadArtifacts() error
|
||||
Clear()
|
||||
}
|
||||
|
||||
func (nexusUpload *Upload) initLogger() {
|
||||
if nexusUpload.Logger == nil {
|
||||
nexusUpload.Logger = log.Entry().WithField("package", "SAP/jenkins-library/pkg/nexus")
|
||||
}
|
||||
}
|
||||
|
||||
// SetBaseURL constructs the base URL for all uploaded artifacts. No parameter can be empty.
|
||||
func (nexusUpload *Upload) SetBaseURL(nexusURL, nexusVersion, repository, groupID string) error {
|
||||
baseURL, err := getBaseURL(nexusURL, nexusVersion, repository, groupID)
|
||||
// SetRepoURL constructs the base URL to the Nexus repository. No parameter can be empty.
|
||||
func (nexusUpload *Upload) SetRepoURL(nexusURL, nexusVersion, repository string) error {
|
||||
repoURL, err := getBaseURL(nexusURL, nexusVersion, repository)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nexusUpload.baseURL = baseURL
|
||||
nexusUpload.repoURL = repoURL
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetArtifactsVersion sets the common version for all uploaded artifacts. The version is external to
|
||||
// GetRepoURL returns the base URL for the nexus repository.
|
||||
func (nexusUpload *Upload) GetRepoURL() string {
|
||||
return nexusUpload.repoURL
|
||||
}
|
||||
|
||||
// ErrEmptyGroupID is returned from SetInfo, if groupID is empty.
|
||||
var ErrEmptyGroupID = errors.New("groupID must not be empty")
|
||||
|
||||
// ErrEmptyArtifactID is returned from SetInfo, if artifactID is empty.
|
||||
var ErrEmptyArtifactID = errors.New("artifactID must not be empty")
|
||||
|
||||
// ErrInvalidArtifactID is returned from SetInfo, if artifactID contains slashes.
|
||||
var ErrInvalidArtifactID = errors.New("artifactID may not include slashes")
|
||||
|
||||
// ErrEmptyVersion is returned from SetInfo, if version is empty.
|
||||
var ErrEmptyVersion = errors.New("version must not be empty")
|
||||
|
||||
// SetInfo sets the common info for all uploaded artifacts. This info is external to
|
||||
// the artifact descriptions so that it is consistent for all of them.
|
||||
func (nexusUpload *Upload) SetArtifactsVersion(version string) error {
|
||||
if version == "" {
|
||||
return errors.New("version must not be empty")
|
||||
func (nexusUpload *Upload) SetInfo(groupID, artifactID, version string) error {
|
||||
if groupID == "" {
|
||||
return ErrEmptyGroupID
|
||||
}
|
||||
if artifactID == "" {
|
||||
return ErrEmptyArtifactID
|
||||
}
|
||||
if strings.Contains(artifactID, "/") {
|
||||
return ErrInvalidArtifactID
|
||||
}
|
||||
if version == "" {
|
||||
return ErrEmptyVersion
|
||||
}
|
||||
nexusUpload.groupID = groupID
|
||||
nexusUpload.artifactID = artifactID
|
||||
nexusUpload.version = version
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetArtifactsVersion returns the common version for all artifacts.
|
||||
func (nexusUpload *Upload) GetArtifactsVersion() string {
|
||||
return nexusUpload.version
|
||||
}
|
||||
|
||||
// GetGroupID returns the common groupId for all artifacts.
|
||||
func (nexusUpload *Upload) GetGroupID() string {
|
||||
return nexusUpload.groupID
|
||||
}
|
||||
|
||||
// GetArtifactsID returns the common artifactId for all artifacts.
|
||||
func (nexusUpload *Upload) GetArtifactsID() string {
|
||||
return nexusUpload.artifactID
|
||||
}
|
||||
|
||||
// AddArtifact adds a single artifact to be uploaded later via UploadArtifacts(). If an identical artifact
|
||||
// description is already contained in the Upload, the function does nothing and returns no error.
|
||||
func (nexusUpload *Upload) AddArtifact(artifact ArtifactDescription) error {
|
||||
@ -89,12 +117,9 @@ func (nexusUpload *Upload) AddArtifact(artifact ArtifactDescription) error {
|
||||
}
|
||||
|
||||
func validateArtifact(artifact ArtifactDescription) error {
|
||||
if artifact.File == "" || artifact.ID == "" || artifact.Type == "" {
|
||||
return fmt.Errorf("Artifact.File (%v), ID (%v) or Type (%v) is empty",
|
||||
artifact.File, artifact.ID, artifact.Type)
|
||||
}
|
||||
if strings.Contains(artifact.ID, "/") {
|
||||
return fmt.Errorf("Artifact.ID may not include slashes")
|
||||
if artifact.File == "" || artifact.Type == "" {
|
||||
return fmt.Errorf("Artifact.File (%v) or Type (%v) is empty",
|
||||
artifact.File, artifact.Type)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -115,60 +140,12 @@ func (nexusUpload *Upload) GetArtifacts() []ArtifactDescription {
|
||||
return artifacts
|
||||
}
|
||||
|
||||
// UploadArtifacts performs the actual upload of all added artifacts to the Nexus server.
|
||||
func (nexusUpload *Upload) UploadArtifacts() error {
|
||||
client := nexusUpload.createHTTPClient()
|
||||
return nexusUpload.uploadArtifacts(client)
|
||||
// Clear removes any contained artifact descriptions.
|
||||
func (nexusUpload *Upload) Clear() {
|
||||
nexusUpload.artifacts = []ArtifactDescription{}
|
||||
}
|
||||
|
||||
func (nexusUpload *Upload) uploadArtifacts(client piperHttp.Sender) error {
|
||||
if nexusUpload.baseURL == "" {
|
||||
return fmt.Errorf("the nexus.Upload needs to be configured by calling SetBaseURL() first")
|
||||
}
|
||||
if nexusUpload.version == "" {
|
||||
return fmt.Errorf("the nexus.Upload needs to be configured by calling SetArtifactsVersion() first")
|
||||
}
|
||||
if len(nexusUpload.artifacts) == 0 {
|
||||
return fmt.Errorf("no artifacts to upload, call AddArtifact() first")
|
||||
}
|
||||
|
||||
nexusUpload.initLogger()
|
||||
|
||||
for _, artifact := range nexusUpload.artifacts {
|
||||
url := getArtifactURL(nexusUpload.baseURL, nexusUpload.version, artifact)
|
||||
|
||||
var err error
|
||||
err = uploadHash(client, artifact.File, url+".md5", md5.New(), 16)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = uploadHash(client, artifact.File, url+".sha1", sha1.New(), 20)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = uploadFile(client, artifact.File, url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Reset all artifacts already uploaded, so the object could be re-used
|
||||
nexusUpload.artifacts = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nexusUpload *Upload) createHTTPClient() *piperHttp.Client {
|
||||
client := piperHttp.Client{}
|
||||
clientOptions := piperHttp.ClientOptions{
|
||||
Username: nexusUpload.Username,
|
||||
Password: nexusUpload.Password,
|
||||
Logger: nexusUpload.Logger,
|
||||
}
|
||||
client.SetOptions(clientOptions)
|
||||
return &client
|
||||
}
|
||||
|
||||
func getBaseURL(nexusURL, nexusVersion, repository, groupID string) (string, error) {
|
||||
func getBaseURL(nexusURL, nexusVersion, repository string) (string, error) {
|
||||
if nexusURL == "" {
|
||||
return "", errors.New("nexusURL must not be empty")
|
||||
}
|
||||
@ -179,9 +156,6 @@ func getBaseURL(nexusURL, nexusVersion, repository, groupID string) (string, err
|
||||
if repository == "" {
|
||||
return "", errors.New("repository must not be empty")
|
||||
}
|
||||
if groupID == "" {
|
||||
return "", errors.New("groupID must not be empty")
|
||||
}
|
||||
baseURL := nexusURL
|
||||
switch nexusVersion {
|
||||
case "nexus2":
|
||||
@ -191,85 +165,8 @@ func getBaseURL(nexusURL, nexusVersion, repository, groupID string) (string, err
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported Nexus version '%s', must be 'nexus2' or 'nexus3'", nexusVersion)
|
||||
}
|
||||
groupPath := strings.ReplaceAll(groupID, ".", "/")
|
||||
baseURL += repository + "/" + groupPath + "/"
|
||||
baseURL += repository + "/"
|
||||
// Replace any double slashes, as nexus does not like them
|
||||
baseURL = strings.ReplaceAll(baseURL, "//", "/")
|
||||
return baseURL, nil
|
||||
}
|
||||
|
||||
func getArtifactURL(baseURL, version string, artifact ArtifactDescription) string {
|
||||
url := baseURL
|
||||
|
||||
// Generate artifact name including optional classifier
|
||||
artifactName := artifact.ID + "-" + version
|
||||
if len(artifact.Classifier) > 0 {
|
||||
artifactName += "-" + artifact.Classifier
|
||||
}
|
||||
artifactName += "." + artifact.Type
|
||||
|
||||
url += artifact.ID + "/" + version + "/" + artifactName
|
||||
|
||||
// Remove any double slashes, as Nexus does not like them, and prepend protocol
|
||||
url = "http://" + strings.ReplaceAll(url, "//", "/")
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
func uploadFile(client piperHttp.Sender, filePath, url string) error {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open artifact file %s: %w", filePath, err)
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
err = uploadToNexus(client, file, url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to upload artifact file %s: %w", filePath, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func uploadHash(client piperHttp.Sender, filePath, url string, hash hash.Hash, length int) error {
|
||||
hashReader, err := generateHashReader(filePath, hash, length)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate hash %w", err)
|
||||
}
|
||||
err = uploadToNexus(client, hashReader, url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to upload hash %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func uploadToNexus(client piperHttp.Sender, stream io.Reader, url string) error {
|
||||
response, err := client.SendRequest(http.MethodPut, url, stream, nil, nil)
|
||||
if err == nil {
|
||||
log.Entry().Info("Uploaded '"+url+"', response: ", response.StatusCode)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func generateHashReader(filePath string, hash hash.Hash, length int) (io.Reader, error) {
|
||||
// Open file
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
// Read file and feed the hash
|
||||
_, err = io.Copy(hash, file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the requested number of bytes from the hash
|
||||
hashInBytes := hash.Sum(nil)[:length]
|
||||
|
||||
// Convert the bytes to a string
|
||||
hexString := hex.EncodeToString(hashInBytes)
|
||||
|
||||
// Finally create an io.Reader wrapping the string
|
||||
return strings.NewReader(hexString), nil
|
||||
}
|
||||
|
@ -1,12 +1,8 @@
|
||||
package nexus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -15,7 +11,6 @@ func TestAddArtifact(t *testing.T) {
|
||||
nexusUpload := Upload{}
|
||||
|
||||
err := nexusUpload.AddArtifact(ArtifactDescription{
|
||||
ID: "artifact.id",
|
||||
Classifier: "",
|
||||
Type: "pom",
|
||||
File: "pom.xml",
|
||||
@ -24,42 +19,14 @@ func TestAddArtifact(t *testing.T) {
|
||||
assert.NoError(t, err, "Expected to add valid artifact")
|
||||
assert.True(t, len(nexusUpload.artifacts) == 1)
|
||||
|
||||
assert.True(t, nexusUpload.artifacts[0].ID == "artifact.id")
|
||||
assert.True(t, nexusUpload.artifacts[0].Classifier == "")
|
||||
assert.True(t, nexusUpload.artifacts[0].Type == "pom")
|
||||
assert.True(t, nexusUpload.artifacts[0].File == "pom.xml")
|
||||
})
|
||||
t.Run("Test missing ID", func(t *testing.T) {
|
||||
nexusUpload := Upload{}
|
||||
|
||||
err := nexusUpload.AddArtifact(ArtifactDescription{
|
||||
ID: "",
|
||||
Classifier: "",
|
||||
Type: "pom",
|
||||
File: "pom.xml",
|
||||
})
|
||||
|
||||
assert.Error(t, err, "Expected to fail adding invalid artifact")
|
||||
assert.True(t, len(nexusUpload.artifacts) == 0)
|
||||
})
|
||||
t.Run("Test invalid ID", func(t *testing.T) {
|
||||
nexusUpload := Upload{}
|
||||
|
||||
err := nexusUpload.AddArtifact(ArtifactDescription{
|
||||
ID: "artifact/id",
|
||||
Classifier: "",
|
||||
Type: "pom",
|
||||
File: "pom.xml",
|
||||
})
|
||||
|
||||
assert.Error(t, err, "Expected to fail adding invalid artifact")
|
||||
assert.True(t, len(nexusUpload.artifacts) == 0)
|
||||
})
|
||||
t.Run("Test missing type", func(t *testing.T) {
|
||||
nexusUpload := Upload{}
|
||||
|
||||
err := nexusUpload.AddArtifact(ArtifactDescription{
|
||||
ID: "artifact",
|
||||
Classifier: "",
|
||||
Type: "",
|
||||
File: "pom.xml",
|
||||
@ -72,7 +39,6 @@ func TestAddArtifact(t *testing.T) {
|
||||
nexusUpload := Upload{}
|
||||
|
||||
err := nexusUpload.AddArtifact(ArtifactDescription{
|
||||
ID: "artifact",
|
||||
Classifier: "",
|
||||
Type: "pom",
|
||||
File: "",
|
||||
@ -85,13 +51,11 @@ func TestAddArtifact(t *testing.T) {
|
||||
nexusUpload := Upload{}
|
||||
|
||||
_ = nexusUpload.AddArtifact(ArtifactDescription{
|
||||
ID: "blob",
|
||||
Classifier: "",
|
||||
Type: "pom",
|
||||
File: "pom.xml",
|
||||
})
|
||||
err := nexusUpload.AddArtifact(ArtifactDescription{
|
||||
ID: "blob",
|
||||
Classifier: "",
|
||||
Type: "pom",
|
||||
File: "pom.xml",
|
||||
@ -105,7 +69,6 @@ func TestGetArtifacts(t *testing.T) {
|
||||
nexusUpload := Upload{}
|
||||
|
||||
err := nexusUpload.AddArtifact(ArtifactDescription{
|
||||
ID: "artifact.id",
|
||||
Classifier: "",
|
||||
Type: "pom",
|
||||
File: "pom.xml",
|
||||
@ -115,245 +78,119 @@ func TestGetArtifacts(t *testing.T) {
|
||||
artifacts := nexusUpload.GetArtifacts()
|
||||
// Overwrite array entry in the returned array...
|
||||
artifacts[0] = ArtifactDescription{
|
||||
ID: "another.id",
|
||||
Classifier: "",
|
||||
Type: "pom",
|
||||
File: "pom.xml",
|
||||
Type: "jar",
|
||||
File: "app.jar",
|
||||
}
|
||||
// ... but expect the entry in nexusUpload object to be unchanged
|
||||
assert.True(t, nexusUpload.artifacts[0].ID == "artifact.id")
|
||||
assert.Equal(t, "pom", nexusUpload.artifacts[0].Type)
|
||||
assert.Equal(t, "pom.xml", nexusUpload.artifacts[0].File)
|
||||
}
|
||||
|
||||
func TestGetBaseURL(t *testing.T) {
|
||||
// Invalid parameters to getBaseURL() already tested via SetBaseURL() tests
|
||||
// Invalid parameters to getBaseURL() already tested via SetRepoURL() tests
|
||||
t.Run("Test base URL for nexus2 is sensible", func(t *testing.T) {
|
||||
baseURL, err := getBaseURL("localhost:8081/nexus", "nexus2", "maven-releases", "some.group.id")
|
||||
baseURL, err := getBaseURL("localhost:8081/nexus", "nexus2", "maven-releases")
|
||||
assert.NoError(t, err, "Expected getBaseURL() to succeed")
|
||||
assert.Equal(t, "localhost:8081/nexus/content/repositories/maven-releases/some/group/id/", baseURL)
|
||||
assert.Equal(t, "localhost:8081/nexus/content/repositories/maven-releases/", baseURL)
|
||||
})
|
||||
t.Run("Test base URL for nexus3 is sensible", func(t *testing.T) {
|
||||
baseURL, err := getBaseURL("localhost:8081", "nexus3", "maven-releases", "some.group.id")
|
||||
baseURL, err := getBaseURL("localhost:8081", "nexus3", "maven-releases")
|
||||
assert.NoError(t, err, "Expected getBaseURL() to succeed")
|
||||
assert.Equal(t, "localhost:8081/repository/maven-releases/some/group/id/", baseURL)
|
||||
assert.Equal(t, "localhost:8081/repository/maven-releases/", baseURL)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetBaseURL(t *testing.T) {
|
||||
t.Run("Test no host provided", func(t *testing.T) {
|
||||
nexusUpload := Upload{}
|
||||
err := nexusUpload.SetBaseURL("", "nexus3", "maven-releases", "some.group.id")
|
||||
assert.Error(t, err, "Expected SetBaseURL() to fail (no host)")
|
||||
err := nexusUpload.SetRepoURL("", "nexus3", "maven-releases")
|
||||
assert.Error(t, err, "Expected SetRepoURL() to fail (no host)")
|
||||
})
|
||||
t.Run("Test host wrongly includes protocol http://", func(t *testing.T) {
|
||||
nexusUpload := Upload{}
|
||||
err := nexusUpload.SetBaseURL("htTp://localhost:8081", "nexus3", "maven-releases", "some.group.id")
|
||||
assert.Error(t, err, "Expected SetBaseURL() to fail (invalid host)")
|
||||
err := nexusUpload.SetRepoURL("htTp://localhost:8081", "nexus3", "maven-releases")
|
||||
assert.Error(t, err, "Expected SetRepoURL() to fail (invalid host)")
|
||||
})
|
||||
t.Run("Test host wrongly includes protocol https://", func(t *testing.T) {
|
||||
nexusUpload := Upload{}
|
||||
err := nexusUpload.SetBaseURL("htTpS://localhost:8081", "nexus3", "maven-releases", "some.group.id")
|
||||
assert.Error(t, err, "Expected SetBaseURL() to fail (invalid host)")
|
||||
err := nexusUpload.SetRepoURL("htTpS://localhost:8081", "nexus3", "maven-releases")
|
||||
assert.Error(t, err, "Expected SetRepoURL() to fail (invalid host)")
|
||||
})
|
||||
t.Run("Test invalid version provided", func(t *testing.T) {
|
||||
nexusUpload := Upload{}
|
||||
err := nexusUpload.SetBaseURL("localhost:8081", "3", "maven-releases", "some.group.id")
|
||||
assert.Error(t, err, "Expected SetBaseURL() to fail (invalid nexus version)")
|
||||
err := nexusUpload.SetRepoURL("localhost:8081", "3", "maven-releases")
|
||||
assert.Error(t, err, "Expected SetRepoURL() to fail (invalid nexus version)")
|
||||
})
|
||||
t.Run("Test no repository provided", func(t *testing.T) {
|
||||
nexusUpload := Upload{}
|
||||
err := nexusUpload.SetBaseURL("localhost:8081", "nexus3", "", "some.group.id")
|
||||
assert.Error(t, err, "Expected SetBaseURL() to fail (no repository)")
|
||||
})
|
||||
t.Run("Test no group id provided", func(t *testing.T) {
|
||||
nexusUpload := Upload{}
|
||||
err := nexusUpload.SetBaseURL("localhost:8081", "nexus3", "maven-releases", "")
|
||||
assert.Error(t, err, "Expected SetBaseURL() to fail (no groupID)")
|
||||
err := nexusUpload.SetRepoURL("localhost:8081", "nexus3", "")
|
||||
assert.Error(t, err, "Expected SetRepoURL() to fail (no repository)")
|
||||
})
|
||||
t.Run("Test no nexus version provided", func(t *testing.T) {
|
||||
nexusUpload := Upload{}
|
||||
err := nexusUpload.SetBaseURL("localhost:8081", "nexus1", "maven-releases", "some.group.id")
|
||||
assert.Error(t, err, "Expected SetBaseURL() to fail (unsupported nexus version)")
|
||||
err := nexusUpload.SetRepoURL("localhost:8081", "nexus1", "maven-releases")
|
||||
assert.Error(t, err, "Expected SetRepoURL() to fail (unsupported nexus version)")
|
||||
})
|
||||
t.Run("Test unsupported nexus version provided", func(t *testing.T) {
|
||||
nexusUpload := Upload{}
|
||||
err := nexusUpload.SetBaseURL("localhost:8081", "nexus1", "maven-releases", "some.group.id")
|
||||
assert.Error(t, err, "Expected SetBaseURL() to fail (unsupported nexus version)")
|
||||
err := nexusUpload.SetRepoURL("localhost:8081", "nexus1", "maven-releases")
|
||||
assert.Error(t, err, "Expected SetRepoURL() to fail (unsupported nexus version)")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetArtifactsVersion(t *testing.T) {
|
||||
func TestSetInfo(t *testing.T) {
|
||||
t.Run("Test invalid artifact version", func(t *testing.T) {
|
||||
nexusUpload := Upload{}
|
||||
err := nexusUpload.SetArtifactsVersion("")
|
||||
assert.Error(t, err, "Expected SetArtifactsVersion() to fail (empty version)")
|
||||
err := nexusUpload.SetInfo("my.group", "artifact.id", "")
|
||||
assert.Error(t, err, "Expected SetInfo() to fail (empty version)")
|
||||
assert.Equal(t, "", nexusUpload.groupID)
|
||||
assert.Equal(t, "", nexusUpload.artifactID)
|
||||
assert.Equal(t, "", nexusUpload.version)
|
||||
})
|
||||
t.Run("Test valid artifact version", func(t *testing.T) {
|
||||
nexusUpload := Upload{}
|
||||
err := nexusUpload.SetArtifactsVersion("1.0.0-SNAPSHOT")
|
||||
assert.NoError(t, err, "Expected SetArtifactsVersion() to succeed")
|
||||
err := nexusUpload.SetInfo("my.group", "artifact.id", "1.0.0-SNAPSHOT")
|
||||
assert.NoError(t, err, "Expected SetInfo() to succeed")
|
||||
})
|
||||
}
|
||||
|
||||
type simpleHttpMock struct {
|
||||
responseStatus string
|
||||
responseError error
|
||||
}
|
||||
|
||||
func (m *simpleHttpMock) SendRequest(method, url string, body io.Reader, header http.Header, cookies []*http.Cookie) (*http.Response, error) {
|
||||
return &http.Response{Status: m.responseStatus}, m.responseError
|
||||
}
|
||||
|
||||
func (m *simpleHttpMock) SetOptions(options piperhttp.ClientOptions) {
|
||||
}
|
||||
|
||||
func TestUploadNoInit(t *testing.T) {
|
||||
var mockedHttp = simpleHttpMock{
|
||||
responseStatus: "200 OK",
|
||||
responseError: nil,
|
||||
}
|
||||
|
||||
t.Run("Expect that upload fails without base-URL", func(t *testing.T) {
|
||||
t.Run("Test empty artifactID", func(t *testing.T) {
|
||||
nexusUpload := Upload{}
|
||||
|
||||
err := nexusUpload.uploadArtifacts(&mockedHttp)
|
||||
assert.EqualError(t, err, "the nexus.Upload needs to be configured by calling SetBaseURL() first")
|
||||
err := nexusUpload.SetInfo("my.group", "", "1.0")
|
||||
assert.Error(t, err, "Expected to fail setting empty artifactID")
|
||||
assert.Equal(t, "", nexusUpload.groupID)
|
||||
assert.Equal(t, "", nexusUpload.artifactID)
|
||||
assert.Equal(t, "", nexusUpload.version)
|
||||
})
|
||||
|
||||
t.Run("Expect that upload fails without version", func(t *testing.T) {
|
||||
t.Run("Test empty groupID", func(t *testing.T) {
|
||||
nexusUpload := Upload{}
|
||||
_ = nexusUpload.SetBaseURL("localhost:8081", "nexus3", "maven-releases", "my.group.id")
|
||||
|
||||
err := nexusUpload.uploadArtifacts(&mockedHttp)
|
||||
assert.EqualError(t, err, "the nexus.Upload needs to be configured by calling SetArtifactsVersion() first")
|
||||
err := nexusUpload.SetInfo("", "id", "1.0")
|
||||
assert.Error(t, err, "Expected to fail setting empty groupID")
|
||||
assert.Equal(t, "", nexusUpload.groupID)
|
||||
assert.Equal(t, "", nexusUpload.artifactID)
|
||||
assert.Equal(t, "", nexusUpload.version)
|
||||
})
|
||||
|
||||
t.Run("Expect that upload fails without artifacts", func(t *testing.T) {
|
||||
t.Run("Test invalid ID", func(t *testing.T) {
|
||||
nexusUpload := Upload{}
|
||||
_ = nexusUpload.SetBaseURL("localhost:8081", "nexus3", "maven-releases", "my.group.id")
|
||||
_ = nexusUpload.SetArtifactsVersion("1.0")
|
||||
|
||||
err := nexusUpload.uploadArtifacts(&mockedHttp)
|
||||
assert.EqualError(t, err, "no artifacts to upload, call AddArtifact() first")
|
||||
err := nexusUpload.SetInfo("my.group", "artifact/id", "1.0.0-SNAPSHOT")
|
||||
assert.Error(t, err, "Expected to fail adding invalid artifact")
|
||||
assert.Equal(t, "", nexusUpload.groupID)
|
||||
assert.Equal(t, "", nexusUpload.artifactID)
|
||||
assert.Equal(t, "", nexusUpload.version)
|
||||
})
|
||||
}
|
||||
|
||||
type request struct {
|
||||
method string
|
||||
url string
|
||||
}
|
||||
|
||||
type requestReply struct {
|
||||
response string
|
||||
err error
|
||||
}
|
||||
|
||||
type httpMock struct {
|
||||
username string
|
||||
password string
|
||||
requestIndex int
|
||||
requestReplies []requestReply
|
||||
requests []request
|
||||
}
|
||||
|
||||
func (m *httpMock) SendRequest(method, url string, body io.Reader, header http.Header, cookies []*http.Cookie) (*http.Response, error) {
|
||||
// store the request
|
||||
m.requests = append(m.requests, request{method: method, url: url})
|
||||
|
||||
// Return the configured response for this request's index
|
||||
response := m.requestReplies[m.requestIndex].response
|
||||
err := m.requestReplies[m.requestIndex].err
|
||||
|
||||
m.requestIndex++
|
||||
|
||||
return &http.Response{Status: response}, err
|
||||
}
|
||||
|
||||
func (m *httpMock) SetOptions(options piperhttp.ClientOptions) {
|
||||
m.username = options.Username
|
||||
m.password = options.Password
|
||||
}
|
||||
|
||||
func (m *httpMock) appendReply(reply requestReply) {
|
||||
m.requestReplies = append(m.requestReplies, reply)
|
||||
}
|
||||
|
||||
func createConfiguredNexusUpload() Upload {
|
||||
func TestClear(t *testing.T) {
|
||||
nexusUpload := Upload{}
|
||||
_ = nexusUpload.SetBaseURL("localhost:8081", "nexus3", "maven-releases", "my.group.id")
|
||||
_ = nexusUpload.SetArtifactsVersion("1.0")
|
||||
_ = nexusUpload.AddArtifact(ArtifactDescription{ID: "artifact.id", Classifier: "", Type: "pom", File: "../../pom.xml"})
|
||||
return nexusUpload
|
||||
}
|
||||
|
||||
func TestUploadArtifacts(t *testing.T) {
|
||||
t.Run("Test upload works", func(t *testing.T) {
|
||||
var mockedHttp = httpMock{}
|
||||
// There will be three requests, md5, sha1 and the file itself
|
||||
mockedHttp.appendReply(requestReply{response: "200 OK", err: nil})
|
||||
mockedHttp.appendReply(requestReply{response: "200 OK", err: nil})
|
||||
mockedHttp.appendReply(requestReply{response: "200 OK", err: nil})
|
||||
|
||||
nexusUpload := createConfiguredNexusUpload()
|
||||
|
||||
err := nexusUpload.uploadArtifacts(&mockedHttp)
|
||||
assert.NoError(t, err, "Expected that uploading the artifact works")
|
||||
|
||||
assert.Equal(t, 3, mockedHttp.requestIndex, "Expected 3 HTTP requests")
|
||||
|
||||
assert.Equal(t, http.MethodPut, mockedHttp.requests[0].method)
|
||||
assert.Equal(t, http.MethodPut, mockedHttp.requests[1].method)
|
||||
assert.Equal(t, http.MethodPut, mockedHttp.requests[2].method)
|
||||
|
||||
assert.Equal(t, "http://localhost:8081/repository/maven-releases/my/group/id/artifact.id/1.0/artifact.id-1.0.pom.md5",
|
||||
mockedHttp.requests[0].url)
|
||||
assert.Equal(t, "http://localhost:8081/repository/maven-releases/my/group/id/artifact.id/1.0/artifact.id-1.0.pom.sha1",
|
||||
mockedHttp.requests[1].url)
|
||||
assert.Equal(t, "http://localhost:8081/repository/maven-releases/my/group/id/artifact.id/1.0/artifact.id-1.0.pom",
|
||||
mockedHttp.requests[2].url)
|
||||
})
|
||||
t.Run("Test upload fails at md5 hash", func(t *testing.T) {
|
||||
var mockedHttp = httpMock{}
|
||||
// There will be three requests, md5, sha1 and the file itself
|
||||
mockedHttp.appendReply(requestReply{response: "404 Error", err: fmt.Errorf("failed")})
|
||||
mockedHttp.appendReply(requestReply{response: "200 OK", err: nil})
|
||||
mockedHttp.appendReply(requestReply{response: "200 OK", err: nil})
|
||||
|
||||
nexusUpload := createConfiguredNexusUpload()
|
||||
|
||||
err := nexusUpload.uploadArtifacts(&mockedHttp)
|
||||
assert.Error(t, err, "Expected that uploading the artifact failed")
|
||||
|
||||
assert.Equal(t, 1, mockedHttp.requestIndex, "Expected only one HTTP requests")
|
||||
assert.Equal(t, 1, len(nexusUpload.artifacts), "Expected the artifact to be still present in the nexusUpload")
|
||||
})
|
||||
t.Run("Test upload fails at sha1 hash", func(t *testing.T) {
|
||||
var mockedHttp = httpMock{}
|
||||
// There will be three requests, md5, sha1 and the file itself
|
||||
mockedHttp.appendReply(requestReply{response: "200 OK", err: nil})
|
||||
mockedHttp.appendReply(requestReply{response: "404 Error", err: fmt.Errorf("failed")})
|
||||
mockedHttp.appendReply(requestReply{response: "200 OK", err: nil})
|
||||
|
||||
nexusUpload := createConfiguredNexusUpload()
|
||||
|
||||
err := nexusUpload.uploadArtifacts(&mockedHttp)
|
||||
assert.Error(t, err, "Expected that uploading the artifact failed")
|
||||
|
||||
assert.Equal(t, 2, mockedHttp.requestIndex, "Expected only two HTTP requests")
|
||||
assert.Equal(t, 1, len(nexusUpload.artifacts), "Expected the artifact to be still present in the nexusUpload")
|
||||
})
|
||||
t.Run("Test upload fails at file", func(t *testing.T) {
|
||||
var mockedHttp = httpMock{}
|
||||
// There will be three requests, md5, sha1 and the file itself
|
||||
mockedHttp.appendReply(requestReply{response: "200 OK", err: nil})
|
||||
mockedHttp.appendReply(requestReply{response: "200 OK", err: nil})
|
||||
mockedHttp.appendReply(requestReply{response: "404 Error", err: fmt.Errorf("failed")})
|
||||
|
||||
nexusUpload := createConfiguredNexusUpload()
|
||||
|
||||
err := nexusUpload.uploadArtifacts(&mockedHttp)
|
||||
assert.Error(t, err, "Expected that uploading the artifact failed")
|
||||
|
||||
assert.Equal(t, 3, mockedHttp.requestIndex, "Expected only three HTTP requests")
|
||||
assert.Equal(t, 1, len(nexusUpload.artifacts), "Expected the artifact to be still present in the nexusUpload")
|
||||
})
|
||||
|
||||
err := nexusUpload.AddArtifact(ArtifactDescription{
|
||||
Classifier: "",
|
||||
Type: "pom",
|
||||
File: "pom.xml",
|
||||
})
|
||||
assert.NoError(t, err, "Expected to succeed adding valid artifact")
|
||||
assert.Equal(t, 1, len(nexusUpload.GetArtifacts()))
|
||||
|
||||
nexusUpload.Clear()
|
||||
|
||||
assert.Equal(t, 0, len(nexusUpload.GetArtifacts()))
|
||||
}
|
||||
|
101
resources/metadata/nexusUpload.yaml
Normal file
101
resources/metadata/nexusUpload.yaml
Normal file
@ -0,0 +1,101 @@
|
||||
metadata:
|
||||
name: nexusUpload
|
||||
aliases:
|
||||
- name: mavenExecute
|
||||
deprecated: false
|
||||
description: Upload artifacts to Nexus
|
||||
longDescription: |
|
||||
Upload build artifacts to a Nexus Repository Manager
|
||||
spec:
|
||||
inputs:
|
||||
secrets:
|
||||
- name: nexusCredentialsId
|
||||
description: The technical username/password credential for accessing the nexus endpoint.
|
||||
type: jenkins
|
||||
params:
|
||||
- name: version
|
||||
type: string
|
||||
description: The Nexus Repository Manager version. Currently supported are 'nexus2' and 'nexus3'.
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: false
|
||||
default: nexus3
|
||||
- name: url
|
||||
type: string
|
||||
description: URL of the nexus. The scheme part of the URL will not be considered, because only http is supported.
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: true
|
||||
- name: repository
|
||||
type: string
|
||||
description: Name of the nexus repository.
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: true
|
||||
default:
|
||||
- name: groupId
|
||||
type: string
|
||||
description: Group ID of the artifacts. Only used in MTA projects, ignored for Maven.
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: false
|
||||
- name: artifactId
|
||||
type: string
|
||||
description: The artifact ID used for both the .mtar and mta.yaml files deployed for MTA projects, ignored for Maven.
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- name: globalSettingsFile
|
||||
type: string
|
||||
description: Path to the mvn settings file that should be used as global settings file.
|
||||
scope:
|
||||
- GENERAL
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
aliases:
|
||||
- name: maven/globalSettingsFile
|
||||
- name: m2Path
|
||||
type: string
|
||||
description: The path to the local .m2 directory, only used for Maven projects.
|
||||
scope:
|
||||
- GENERAL
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
aliases:
|
||||
- name: maven/m2Path
|
||||
- name: additionalClassifiers
|
||||
type: string
|
||||
description: List of additional classifiers that should be deployed to nexus. Each item is a map of a type and a classifier name.
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- name: user
|
||||
type: string
|
||||
description: User
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- name: password
|
||||
type: string
|
||||
description: Password
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
containers:
|
||||
- name: mvn
|
||||
image: maven:3.6-jdk-8
|
||||
imagePullPolicy: Never
|
@ -57,6 +57,7 @@ public class CommonStepsTest extends BasePiperTest{
|
||||
'prepareDefaultValues',
|
||||
'setupCommonPipelineEnvironment',
|
||||
'buildSetResult',
|
||||
'nexusUpload',
|
||||
]
|
||||
|
||||
List steps = getSteps().stream()
|
||||
@ -127,6 +128,7 @@ public class CommonStepsTest extends BasePiperTest{
|
||||
'xsDeploy', //implementing new golang pattern without fields
|
||||
'cloudFoundryDeleteService', //implementing new golang pattern without fields
|
||||
'mavenExecuteStaticCodeChecks', //implementing new golang pattern without fields
|
||||
'nexusUpload', //implementing new golang pattern without fields
|
||||
]
|
||||
|
||||
@Test
|
||||
|
81
test/groovy/NexusUploadTest.groovy
Normal file
81
test/groovy/NexusUploadTest.groovy
Normal file
@ -0,0 +1,81 @@
|
||||
import groovy.json.JsonSlurper
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.ExpectedException
|
||||
import org.junit.rules.RuleChain
|
||||
import util.*
|
||||
|
||||
import static org.hamcrest.Matchers.*
|
||||
import static org.junit.Assert.assertThat
|
||||
|
||||
class NexusUploadTest extends BasePiperTest {
|
||||
private ExpectedException exception = ExpectedException.none()
|
||||
|
||||
private JenkinsCredentialsRule credentialsRule = new JenkinsCredentialsRule(this)
|
||||
private JenkinsReadJsonRule readJsonRule = new JenkinsReadJsonRule(this)
|
||||
private JenkinsShellCallRule shellCallRule = new JenkinsShellCallRule(this)
|
||||
private JenkinsStepRule stepRule = new JenkinsStepRule(this)
|
||||
private JenkinsWriteFileRule writeFileRule = new JenkinsWriteFileRule(this)
|
||||
private JenkinsFileExistsRule fileExistsRule = new JenkinsFileExistsRule(this, [])
|
||||
//private JenkinsDockerExecuteRule dockerExecuteRule = new JenkinsDockerExecuteRule(this, [])
|
||||
|
||||
private List withEnvArgs = []
|
||||
|
||||
@Rule
|
||||
public RuleChain rules = Rules
|
||||
.getCommonRules(this)
|
||||
.around(exception)
|
||||
.around(new JenkinsReadYamlRule(this))
|
||||
.around(credentialsRule)
|
||||
.around(readJsonRule)
|
||||
.around(shellCallRule)
|
||||
.around(stepRule)
|
||||
.around(writeFileRule)
|
||||
.around(fileExistsRule)
|
||||
// .around(dockerExecuteRule)
|
||||
|
||||
@Before
|
||||
void init() {
|
||||
helper.registerAllowedMethod('fileExists', [Map], {
|
||||
return true
|
||||
})
|
||||
helper.registerAllowedMethod("readJSON", [Map], { m ->
|
||||
if (m.file == 'nexusUpload_reports.json')
|
||||
return [[target: "1234.pdf", mandatory: true]]
|
||||
if (m.file == 'nexusUpload_links.json')
|
||||
return []
|
||||
if (m.text != null)
|
||||
return new JsonSlurper().parseText(m.text)
|
||||
})
|
||||
helper.registerAllowedMethod("withEnv", [List, Closure], { arguments, closure ->
|
||||
arguments.each {arg ->
|
||||
withEnvArgs.add(arg.toString())
|
||||
}
|
||||
return closure()
|
||||
})
|
||||
// helper.registerAllowedMethod("dockerExecute", [Map, Closure], { map, closure ->
|
||||
// // ignore
|
||||
// })
|
||||
credentialsRule.withCredentials('idOfCxCredential', "admin", "admin123")
|
||||
shellCallRule.setReturnValue(
|
||||
'./piper getConfig --contextConfig --stepMetadata \'.pipeline/tmp/metadata/nexusUpload.yaml\'',
|
||||
'{"credentialsId": "idOfCxCredential", "verbose": false}'
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeployPom() {
|
||||
stepRule.step.nexusUpload(
|
||||
juStabUtils: utils,
|
||||
jenkinsUtilsStub: jenkinsUtils,
|
||||
testParam: "This is test content",
|
||||
script: nullScript,
|
||||
)
|
||||
// asserts
|
||||
assertThat(writeFileRule.files['.pipeline/tmp/metadata/nexusUpload.yaml'], containsString('name: nexusUpload'))
|
||||
assertThat(withEnvArgs[0], allOf(startsWith('PIPER_parametersJSON'),
|
||||
containsString('"testParam":"This is test content"')))
|
||||
assertThat(shellCallRule.shell[1], is('./piper nexusUpload'))
|
||||
}
|
||||
}
|
@ -164,6 +164,7 @@ class commonPipelineEnvironment implements Serializable {
|
||||
[filename: '.pipeline/commonPipelineEnvironment/git/branch', property: 'gitBranch'],
|
||||
[filename: '.pipeline/commonPipelineEnvironment/git/commitId', property: 'gitCommitId'],
|
||||
[filename: '.pipeline/commonPipelineEnvironment/git/commitMessage', property: 'gitCommitMessage'],
|
||||
[filename: '.pipeline/commonPipelineEnvironment/mtarFilePath', property: 'mtarFilePath'],
|
||||
]
|
||||
|
||||
void writeToDisk(script) {
|
||||
|
11
vars/nexusUpload.groovy
Normal file
11
vars/nexusUpload.groovy
Normal file
@ -0,0 +1,11 @@
|
||||
import groovy.transform.Field
|
||||
|
||||
@Field String STEP_NAME = getClass().getName()
|
||||
@Field String METADATA_FILE = 'metadata/nexusUpload.yaml'
|
||||
|
||||
//Metadata maintained in file project://resources/metadata/nexusUpload.yaml
|
||||
|
||||
void call(Map parameters = [:]) {
|
||||
List credentials = [[type: 'usernamePassword', id: 'nexusCredentialsId', env: ['PIPER_username', 'PIPER_password']]]
|
||||
piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials)
|
||||
}
|
@ -49,6 +49,7 @@ void dockerWrapper(script, config, body) {
|
||||
script: script,
|
||||
dockerImage: config.dockerImage,
|
||||
dockerWorkspace: config.dockerWorkspace,
|
||||
dockerOptions: config.dockerOptions,
|
||||
//ToDo: add additional dockerExecute parameters
|
||||
) {
|
||||
body()
|
||||
|
Loading…
Reference in New Issue
Block a user