1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-02-05 13:25:19 +02:00

Add step to prepare the version for an artifact (#1343)

This commit is contained in:
Oliver Nocon 2020-04-03 16:34:40 +02:00 committed by GitHub
parent f482f439b0
commit 999197b919
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1931 additions and 3 deletions

1
.gitignore vendored
View File

@ -18,6 +18,7 @@ targets/
documentation/docs-gen
consumer-test/**/workspace
.pipeline/commonPipelineEnvironment
*.code-workspace
/piper

View File

@ -0,0 +1,351 @@
package cmd
import (
"bytes"
"fmt"
"os"
"strings"
"text/template"
"time"
"github.com/SAP/jenkins-library/pkg/command"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/SAP/jenkins-library/pkg/versioning"
"github.com/pkg/errors"
"github.com/go-git/go-git/v5"
gitConfig "github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
)
type gitRepository interface {
CreateTag(string, plumbing.Hash, *git.CreateTagOptions) (*plumbing.Reference, error)
CreateRemote(*gitConfig.RemoteConfig) (*git.Remote, error)
DeleteRemote(string) error
Push(*git.PushOptions) error
Remote(string) (*git.Remote, error)
ResolveRevision(plumbing.Revision) (*plumbing.Hash, error)
Worktree() (*git.Worktree, error)
}
type gitWorktree interface {
Add(string) (plumbing.Hash, error)
Checkout(*git.CheckoutOptions) error
Commit(string, *git.CommitOptions) (plumbing.Hash, error)
}
func getGitWorktree(repository gitRepository) (gitWorktree, error) {
return repository.Worktree()
}
func artifactPrepareVersion(config artifactPrepareVersionOptions, telemetryData *telemetry.CustomData, commonPipelineEnvironment *artifactPrepareVersionCommonPipelineEnvironment) {
c := command.Command{}
// reroute command output to logging framework
c.Stdout(log.Entry().Writer())
c.Stderr(log.Entry().Writer())
// open local .git repository
repository, err := openGit()
if err != nil {
log.Entry().WithError(err).Fatal("git repository required - none available")
}
err = runArtifactPrepareVersion(&config, telemetryData, commonPipelineEnvironment, nil, &c, repository, getGitWorktree)
if err != nil {
log.Entry().WithError(err).Fatal("artifactPrepareVersion failed")
}
log.Entry().Info("SUCCESS")
}
var sshAgentAuth = ssh.NewSSHAgentAuth
func runArtifactPrepareVersion(config *artifactPrepareVersionOptions, telemetryData *telemetry.CustomData, commonPipelineEnvironment *artifactPrepareVersionCommonPipelineEnvironment, artifact versioning.Artifact, runner execRunner, repository gitRepository, getWorktree func(gitRepository) (gitWorktree, error)) error {
telemetryData.Custom1Label = "buildTool"
telemetryData.Custom1 = config.BuildTool
// Options for artifact
artifactOpts := versioning.Options{
GlobalSettingsFile: config.GlobalSettingsFile,
M2Path: config.M2Path,
ProjectSettingsFile: config.ProjectSettingsFile,
}
var err error
if artifact == nil {
artifact, err = versioning.GetArtifact(config.BuildTool, config.FilePath, &artifactOpts, runner)
if err != nil {
return errors.Wrap(err, "failed to retrieve artifact")
}
}
versioningType := config.VersioningType
// support former groovy versioning template and translate into new options
if len(config.VersioningTemplate) > 0 {
versioningType, _, config.IncludeCommitID = templateCompatibility(config.VersioningTemplate)
}
version, err := artifact.GetVersion()
if err != nil {
return errors.Wrap(err, "failed to retrieve version")
}
log.Entry().Infof("Version before automatic versioning: %v", version)
gitCommit, err := getGitCommitID(repository)
if err != nil {
return err
}
gitCommitID := gitCommit.String()
newVersion := version
if versioningType == "cloud" {
versioningTempl, err := versioningTemplate(artifact.VersioningScheme())
if err != nil {
return errors.Wrapf(err, "failed to get versioning template for scheme '%v'", artifact.VersioningScheme())
}
now := time.Now()
newVersion, err = calculateNewVersion(versioningTempl, version, gitCommitID, config.IncludeCommitID, now)
if err != nil {
return errors.Wrap(err, "failed to calculate new version")
}
worktree, err := getWorktree(repository)
if err != nil {
return errors.Wrap(err, "failed to retrieve git worktree")
}
// opening repository does not seem to consider already existing files properly
// behavior in case we do not run initializeWorktree:
// git.Add(".") will add the complete workspace instead of only changed files
err = initializeWorktree(gitCommit, worktree)
if err != nil {
return err
}
// only update version in build descriptor if required in order to save prossing time (e.g. maven case)
if newVersion != version {
err = artifact.SetVersion(newVersion)
if err != nil {
return errors.Wrap(err, "failed to write version")
}
}
//ToDo: what about closure in current Groovy step. Discard the possibility or provide extension mechanism?
// commit changes and push to repository (including new version tag)
gitCommitID, err = pushChanges(config, newVersion, repository, worktree, now)
if err != nil {
return errors.Wrapf(err, "failed to push changes for version '%v'", newVersion)
}
}
log.Entry().Infof("New version: '%v'", newVersion)
commonPipelineEnvironment.git.commitID = gitCommitID
commonPipelineEnvironment.artifactVersion = newVersion
return nil
}
func openGit() (gitRepository, error) {
workdir, _ := os.Getwd()
return git.PlainOpen(workdir)
}
func getGitCommitID(repository gitRepository) (plumbing.Hash, error) {
commitID, err := repository.ResolveRevision(plumbing.Revision("HEAD"))
if err != nil {
return plumbing.Hash{}, errors.Wrap(err, "failed to retrieve git commit ID")
}
return *commitID, nil
}
func versioningTemplate(scheme string) (string, error) {
// generally: timestamp acts as build number providing a proper order
switch scheme {
case "maven":
// according to https://www.mojohaus.org/versions-maven-plugin/version-rules.html
return "{{.Version}}{{if .Timestamp}}-{{.Timestamp}}{{if .CommitID}}_{{.CommitID}}{{end}}{{end}}", nil
case "pep440":
// according to https://www.python.org/dev/peps/pep-0440/
return "{{.Version}}{{if .Timestamp}}.{{.Timestamp}}{{if .CommitID}}+{{.CommitID}}{{end}}{{end}}", nil
case "semver2":
// according to https://semver.org/spec/v2.0.0.html
return "{{.Version}}{{if .Timestamp}}-{{.Timestamp}}{{if .CommitID}}+{{.CommitID}}{{end}}{{end}}", nil
}
return "", fmt.Errorf("versioning scheme '%v' not supported", scheme)
}
func calculateNewVersion(versioningTemplate, currentVersion, commitID string, includeCommitID bool, t time.Time) (string, error) {
tmpl, err := template.New("version").Parse(versioningTemplate)
if err != nil {
return "", errors.Wrapf(err, "failed to create version template: %v", versioningTemplate)
}
buf := new(bytes.Buffer)
versionParts := struct {
Version string
Timestamp string
CommitID string
}{
Version: currentVersion,
Timestamp: t.Format("20060102150405"),
}
if includeCommitID {
versionParts.CommitID = commitID
}
err = tmpl.Execute(buf, versionParts)
if err != nil {
return "", errors.Wrapf(err, "failed to execute versioning template: %v", versioningTemplate)
}
newVersion := buf.String()
if len(newVersion) == 0 {
return "", fmt.Errorf("failed calculate version, new version is '%v'", newVersion)
}
return buf.String(), nil
}
func initializeWorktree(gitCommit plumbing.Hash, worktree gitWorktree) error {
// checkout current revision in order to work on that
err := worktree.Checkout(&git.CheckoutOptions{Hash: gitCommit, Keep: true})
if err != nil {
return errors.Wrap(err, "failed to initialize worktree")
}
return nil
}
func pushChanges(config *artifactPrepareVersionOptions, newVersion string, repository gitRepository, worktree gitWorktree, t time.Time) (string, error) {
var commitID string
commit, err := addAndCommit(worktree, newVersion, t)
if err != nil {
return commit.String(), err
}
commitID = commit.String()
tag := fmt.Sprintf("%v%v", config.TagPrefix, newVersion)
_, err = repository.CreateTag(tag, commit, nil)
if err != nil {
return commitID, err
}
ref := gitConfig.RefSpec(fmt.Sprintf("refs/tags/%v:refs/tags/%v", tag, tag))
pushOptions := git.PushOptions{
RefSpecs: []gitConfig.RefSpec{gitConfig.RefSpec(ref)},
}
currentRemoteOrigin, err := repository.Remote("origin")
if err != nil {
return commitID, errors.Wrap(err, "failed to retrieve current remote origin")
}
var updatedRemoteOrigin *git.Remote
urls := originUrls(repository)
if len(urls) == 0 {
return commitID, fmt.Errorf("no remote url maintained")
}
if strings.HasPrefix(urls[0], "http") {
if len(config.Username) == 0 || len(config.Password) == 0 {
// handling compatibility: try to use ssh in case no credentials are available
log.Entry().Info("git username/password missing - switching to ssh")
remoteURL := convertHTTPToSSHURL(urls[0])
// update remote origin url to point to ssh url instead of http(s) url
err = repository.DeleteRemote("origin")
if err != nil {
return commitID, errors.Wrap(err, "failed to update remote origin - remove")
}
updatedRemoteOrigin, err = repository.CreateRemote(&gitConfig.RemoteConfig{Name: "origin", URLs: []string{remoteURL}})
if err != nil {
return commitID, errors.Wrap(err, "failed to update remote origin - create")
}
pushOptions.Auth, err = sshAgentAuth("git")
if err != nil {
return commitID, errors.Wrap(err, "failed to retrieve ssh authentication")
}
log.Entry().Infof("using remote '%v'", remoteURL)
} else {
pushOptions.Auth = &http.BasicAuth{Username: config.Username, Password: config.Password}
}
} else {
pushOptions.Auth, err = sshAgentAuth("git")
if err != nil {
return commitID, errors.Wrap(err, "failed to retrieve ssh authentication")
}
}
err = repository.Push(&pushOptions)
if err != nil {
return commitID, err
}
if updatedRemoteOrigin != currentRemoteOrigin {
err = repository.DeleteRemote("origin")
if err != nil {
return commitID, errors.Wrap(err, "failed to restore remote origin - remove")
}
_, err := repository.CreateRemote(currentRemoteOrigin.Config())
if err != nil {
return commitID, errors.Wrap(err, "failed to restore remote origin - create")
}
}
return commitID, nil
}
func addAndCommit(worktree gitWorktree, newVersion string, t time.Time) (plumbing.Hash, error) {
_, err := worktree.Add(".")
if err != nil {
return plumbing.Hash{}, errors.Wrap(err, "failed to execute 'git add .'")
}
//maybe more options are required: https://github.com/go-git/go-git/blob/master/_examples/commit/main.go
commit, err := worktree.Commit(fmt.Sprintf("update version %v", newVersion), &git.CommitOptions{Author: &object.Signature{Name: "Project Piper", When: t}})
if err != nil {
return commit, errors.Wrap(err, "failed to commit new version")
}
return commit, nil
}
func originUrls(repository gitRepository) []string {
remote, err := repository.Remote("origin")
if err != nil || remote == nil {
return []string{}
}
return remote.Config().URLs
}
func convertHTTPToSSHURL(url string) string {
sshURL := strings.Replace(url, "https://", "git@", 1)
return strings.Replace(sshURL, "/", ":", 1)
}
func templateCompatibility(groovyTemplate string) (versioningType string, useTimestamp bool, useCommitID bool) {
useTimestamp = strings.Contains(groovyTemplate, "${timestamp}")
useCommitID = strings.Contains(groovyTemplate, "${commitId")
versioningType = "library"
if useTimestamp {
versioningType = "cloud"
}
return
}

View File

@ -0,0 +1,261 @@
// Code generated by piper's step-generator. DO NOT EDIT.
package cmd
import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperenv"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/spf13/cobra"
)
type artifactPrepareVersionOptions struct {
BuildTool string `json:"buildTool,omitempty"`
DockerVersionSource string `json:"dockerVersionSource,omitempty"`
FilePath string `json:"filePath,omitempty"`
GitUserEMail string `json:"gitUserEMail,omitempty"`
GitUserName string `json:"gitUserName,omitempty"`
GlobalSettingsFile string `json:"globalSettingsFile,omitempty"`
IncludeCommitID bool `json:"includeCommitId,omitempty"`
M2Path string `json:"m2Path,omitempty"`
Password string `json:"password,omitempty"`
ProjectSettingsFile string `json:"projectSettingsFile,omitempty"`
TagPrefix string `json:"tagPrefix,omitempty"`
Username string `json:"username,omitempty"`
VersioningTemplate string `json:"versioningTemplate,omitempty"`
VersioningType string `json:"versioningType,omitempty"`
}
type artifactPrepareVersionCommonPipelineEnvironment struct {
artifactVersion string
git struct {
commitID string
}
}
func (p *artifactPrepareVersionCommonPipelineEnvironment) persist(path, resourceName string) {
content := []struct {
category string
name string
value string
}{
{category: "", name: "artifactVersion", value: p.artifactVersion},
{category: "git", name: "commitId", value: p.git.commitID},
}
errCount := 0
for _, param := range content {
err := piperenv.SetResourceParameter(path, resourceName, filepath.Join(param.category, param.name), param.value)
if err != nil {
log.Entry().WithError(err).Error("Error persisting piper environment.")
errCount++
}
}
if errCount > 0 {
os.Exit(1)
}
}
// ArtifactPrepareVersionCommand Prepares and potentially updates the artifact's version before building the artifact.
func ArtifactPrepareVersionCommand() *cobra.Command {
metadata := artifactPrepareVersionMetadata()
var stepConfig artifactPrepareVersionOptions
var startTime time.Time
var commonPipelineEnvironment artifactPrepareVersionCommonPipelineEnvironment
var createArtifactPrepareVersionCmd = &cobra.Command{
Use: "artifactPrepareVersion",
Short: "Prepares and potentially updates the artifact's version before building the artifact.",
Long: `Prepares and potentially updates the artifact's version before building the artifact.
The continuous delivery process requires that each build is done with a unique version number.
The version generated using this step will contain:
* Version (major.minor.patch) from descriptor file in master repository is preserved. Developers should be able to autonomously decide on increasing either part of this version number.
* Timestamp
* CommitId (by default the long version of the hash)
Optionally, but enabled by default, the new version is pushed as a new tag into the source code repository (e.g. GitHub).
If this option is chosen, git credentials and the repository URL needs to be provided.
Since you might not want to configure the git credentials in Jenkins, committing and pushing can be disabled using the ` + "`" + `commitVersion` + "`" + ` parameter as described below.
If you require strict reproducibility of your builds, this should be used.`,
PreRunE: func(cmd *cobra.Command, args []string) error {
startTime = time.Now()
log.SetStepName("artifactPrepareVersion")
log.SetVerbose(GeneralConfig.Verbose)
return PrepareConfig(cmd, &metadata, "artifactPrepareVersion", &stepConfig, config.OpenPiperFile)
},
Run: func(cmd *cobra.Command, args []string) {
telemetryData := telemetry.CustomData{}
telemetryData.ErrorCode = "1"
handler := func() {
commonPipelineEnvironment.persist(GeneralConfig.EnvRootPath, "commonPipelineEnvironment")
telemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds())
telemetry.Send(&telemetryData)
}
log.DeferExitHandler(handler)
defer handler()
telemetry.Initialize(GeneralConfig.NoTelemetry, "artifactPrepareVersion")
artifactPrepareVersion(stepConfig, &telemetryData, &commonPipelineEnvironment)
telemetryData.ErrorCode = "0"
},
}
addArtifactPrepareVersionFlags(createArtifactPrepareVersionCmd, &stepConfig)
return createArtifactPrepareVersionCmd
}
func addArtifactPrepareVersionFlags(cmd *cobra.Command, stepConfig *artifactPrepareVersionOptions) {
cmd.Flags().StringVar(&stepConfig.BuildTool, "buildTool", os.Getenv("PIPER_buildTool"), "Defines the tool which is used for building the artifact.")
cmd.Flags().StringVar(&stepConfig.DockerVersionSource, "dockerVersionSource", os.Getenv("PIPER_dockerVersionSource"), "For Docker only: Specifies the source to be used for for generating the automatic version. * This can either be the version of the base image - as retrieved from the `FROM` statement within the Dockerfile, e.g. `FROM jenkins:2.46.2` * Alternatively the name of an environment variable defined in the Docker image can be used which contains the version number, e.g. `ENV MY_VERSION 1.2.3`")
cmd.Flags().StringVar(&stepConfig.FilePath, "filePath", os.Getenv("PIPER_filePath"), "Defines a custom path to the descriptor file. Build tool specific defaults are used (e.g. maven: pom.xml, npm: package.json, mta: mta.yaml)")
cmd.Flags().StringVar(&stepConfig.GitUserEMail, "gitUserEMail", os.Getenv("PIPER_gitUserEMail"), "Allows to overwrite the global git setting 'user.email' available on your Jenkins server.")
cmd.Flags().StringVar(&stepConfig.GitUserName, "gitUserName", os.Getenv("PIPER_gitUserName"), "Allows to overwrite the global git setting 'user.name' available on your Jenkins server.")
cmd.Flags().StringVar(&stepConfig.GlobalSettingsFile, "globalSettingsFile", os.Getenv("PIPER_globalSettingsFile"), "Maven only - Path to the mvn settings file that should be used as global settings file.")
cmd.Flags().BoolVar(&stepConfig.IncludeCommitID, "includeCommitId", true, "Defines if the automatically generated version (versioningType 'cloud') should include the commit id hash .")
cmd.Flags().StringVar(&stepConfig.M2Path, "m2Path", os.Getenv("PIPER_m2Path"), "Maven only - Path to the location of the local repository that should be used.")
cmd.Flags().StringVar(&stepConfig.Password, "password", os.Getenv("PIPER_password"), "Password/token for git authentication")
cmd.Flags().StringVar(&stepConfig.ProjectSettingsFile, "projectSettingsFile", os.Getenv("PIPER_projectSettingsFile"), "Maven only - Path to the mvn settings file that should be used as project settings file.")
cmd.Flags().StringVar(&stepConfig.TagPrefix, "tagPrefix", "build_", "Defines the prefix which is used for the git tag which is written during the versioning run.")
cmd.Flags().StringVar(&stepConfig.Username, "username", os.Getenv("PIPER_username"), "User name for git authentication")
cmd.Flags().StringVar(&stepConfig.VersioningTemplate, "versioningTemplate", os.Getenv("PIPER_versioningTemplate"), "DEPRECATED: Defines the template for the automatic version which will be created")
cmd.Flags().StringVar(&stepConfig.VersioningType, "versioningType", "cloud", "Defines the type of versioning (cloud: fully automatic, library: manual, libraryTag (not available yet): automatic based on latest tag)")
cmd.MarkFlagRequired("buildTool")
}
// retrieve step metadata
func artifactPrepareVersionMetadata() config.StepData {
var theMetaData = config.StepData{
Metadata: config.StepMetadata{
Name: "artifactPrepareVersion",
Aliases: []config.Alias{{Name: "artifactSetVersion", Deprecated: false}, {Name: "setVersion", Deprecated: true}},
},
Spec: config.StepSpec{
Inputs: config.StepInputs{
Parameters: []config.StepParameters{
{
Name: "buildTool",
ResourceRef: []config.ResourceReference{},
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "dockerVersionSource",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "filePath",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "gitUserEMail",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "gitUserName",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "globalSettingsFile",
ResourceRef: []config.ResourceReference{},
Scope: []string{"GENERAL", "STEPS", "STAGES", "PARAMETERS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "maven/globalSettingsFile"}},
},
{
Name: "includeCommitId",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "bool",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "m2Path",
ResourceRef: []config.ResourceReference{},
Scope: []string{"GENERAL", "STEPS", "STAGES", "PARAMETERS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "maven/m2Path"}},
},
{
Name: "password",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "projectSettingsFile",
ResourceRef: []config.ResourceReference{},
Scope: []string{"GENERAL", "STEPS", "STAGES", "PARAMETERS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "maven/projectSettingsFile"}},
},
{
Name: "tagPrefix",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "username",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "versioningTemplate",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "versioningType",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
},
},
},
}
return theMetaData
}

View File

@ -0,0 +1,16 @@
package cmd
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestArtifactPrepareVersionCommand(t *testing.T) {
testCmd := ArtifactPrepareVersionCommand()
// only high level testing performed - details are tested in step generation procudure
assert.Equal(t, "artifactPrepareVersion", testCmd.Use, "command name incorrect")
}

View File

@ -0,0 +1,598 @@
package cmd
import (
"fmt"
"testing"
"time"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/stretchr/testify/assert"
"github.com/go-git/go-git/v5"
gitConfig "github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
)
type artifactVersioningMock struct {
originalVersion string
newVersion string
getVersionError string
setVersionError string
initCalled bool
versioningScheme string
}
func (a *artifactVersioningMock) VersioningScheme() string {
return a.versioningScheme
}
func (a *artifactVersioningMock) GetVersion() (string, error) {
if len(a.getVersionError) > 0 {
return "", fmt.Errorf(a.getVersionError)
}
return a.originalVersion, nil
}
func (a *artifactVersioningMock) SetVersion(version string) error {
if len(a.setVersionError) > 0 {
return fmt.Errorf(a.setVersionError)
}
a.newVersion = version
return nil
}
type gitRepositoryMock struct {
createRemoteConfigs []*gitConfig.RemoteConfig
createRemoteCalls int
createRemoteError []string
deleteRemoteNames []string
deleteRemoteCalls int
deleteRemoteError []string
pushCalled bool
pushOptions *git.PushOptions
pushError string
remote *git.Remote
remoteError string
revision string
revisionHash plumbing.Hash
revisionError string
tag string
tagHash plumbing.Hash
tagError string
worktree *git.Worktree
worktreeError string
}
func (r *gitRepositoryMock) CreateTag(name string, hash plumbing.Hash, opts *git.CreateTagOptions) (*plumbing.Reference, error) {
if len(r.tagError) > 0 {
return nil, fmt.Errorf(r.tagError)
}
r.tag = name
r.tagHash = hash
return nil, nil
}
func (r *gitRepositoryMock) CreateRemote(config *gitConfig.RemoteConfig) (*git.Remote, error) {
r.createRemoteCalls++
if len(r.createRemoteError) >= r.createRemoteCalls && len(r.createRemoteError[r.createRemoteCalls-1]) > 0 {
return nil, fmt.Errorf(r.createRemoteError[r.createRemoteCalls-1])
}
r.createRemoteConfigs = append(r.createRemoteConfigs, config)
return nil, nil
}
func (r *gitRepositoryMock) DeleteRemote(name string) error {
r.deleteRemoteCalls++
if len(r.deleteRemoteError) >= r.deleteRemoteCalls && len(r.deleteRemoteError[r.deleteRemoteCalls-1]) > 0 {
return fmt.Errorf(r.deleteRemoteError[r.deleteRemoteCalls-1])
}
r.deleteRemoteNames = append(r.deleteRemoteNames, name)
return nil
}
func (r *gitRepositoryMock) Push(o *git.PushOptions) error {
if len(r.pushError) > 0 {
return fmt.Errorf(r.pushError)
}
r.pushCalled = true
r.pushOptions = o
return nil
}
func (r *gitRepositoryMock) Remote(name string) (*git.Remote, error) {
if len(r.remoteError) > 0 {
return &git.Remote{}, fmt.Errorf(r.remoteError)
}
return r.remote, nil
}
func (r *gitRepositoryMock) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, error) {
if len(r.revisionError) > 0 {
return nil, fmt.Errorf(r.revisionError)
}
r.revision = rev.String()
return &r.revisionHash, nil
}
func (r *gitRepositoryMock) Worktree() (*git.Worktree, error) {
if len(r.worktreeError) > 0 {
return nil, fmt.Errorf(r.worktreeError)
}
return r.worktree, nil
}
type gitWorktreeMock struct {
addPath string
addError string
checkoutError string
checkoutOpts *git.CheckoutOptions
commitHash plumbing.Hash
commitMsg string
commitOpts *git.CommitOptions
commitError string
}
func (w *gitWorktreeMock) Add(path string) (plumbing.Hash, error) {
if len(w.addError) > 0 {
return plumbing.Hash{}, fmt.Errorf(w.addError)
}
w.addPath = path
return plumbing.Hash{}, nil
}
func (w *gitWorktreeMock) Checkout(opts *git.CheckoutOptions) error {
if len(w.checkoutError) > 0 {
return fmt.Errorf(w.checkoutError)
}
w.checkoutOpts = opts
return nil
}
func (w *gitWorktreeMock) Commit(msg string, opts *git.CommitOptions) (plumbing.Hash, error) {
if len(w.commitError) > 0 {
return plumbing.Hash{}, fmt.Errorf(w.commitError)
}
w.commitMsg = msg
w.commitOpts = opts
return w.commitHash, nil
}
func TestRunArtifactPrepareVersion(t *testing.T) {
t.Run("success case - cloud", func(t *testing.T) {
config := artifactPrepareVersionOptions{
BuildTool: "maven",
IncludeCommitID: true,
Password: "****",
TagPrefix: "v",
Username: "testUser",
VersioningType: "cloud",
}
telemetryData := telemetry.CustomData{}
cpe := artifactPrepareVersionCommonPipelineEnvironment{}
versioningMock := artifactVersioningMock{
originalVersion: "1.2.3",
versioningScheme: "maven",
}
worktree := gitWorktreeMock{
commitHash: plumbing.ComputeHash(plumbing.CommitObject, []byte{2, 3, 4}),
}
conf := gitConfig.RemoteConfig{Name: "origin", URLs: []string{"https://my.test.server"}}
repo := gitRepositoryMock{
revisionHash: plumbing.ComputeHash(plumbing.CommitObject, []byte{1, 2, 3}),
remote: git.NewRemote(nil, &conf),
}
err := runArtifactPrepareVersion(&config, &telemetryData, &cpe, &versioningMock, nil, &repo, func(r gitRepository) (gitWorktree, error) { return &worktree, nil })
assert.NoError(t, err)
assert.Contains(t, versioningMock.newVersion, "1.2.3")
assert.Contains(t, versioningMock.newVersion, fmt.Sprintf("_%v", repo.revisionHash.String()))
assert.Equal(t, "HEAD", repo.revision)
assert.Contains(t, repo.tag, "v1.2.3")
assert.Equal(t, &git.CheckoutOptions{Hash: repo.revisionHash, Keep: true}, worktree.checkoutOpts)
assert.True(t, repo.pushCalled)
assert.Contains(t, cpe.artifactVersion, "1.2.3")
assert.Equal(t, worktree.commitHash.String(), cpe.git.commitID)
assert.Equal(t, telemetry.CustomData{Custom1Label: "buildTool", Custom1: "maven"}, telemetryData)
})
t.Run("success case - compatibility", func(t *testing.T) {
config := artifactPrepareVersionOptions{
BuildTool: "maven",
VersioningType: "cloud",
VersioningTemplate: "${version}",
}
cpe := artifactPrepareVersionCommonPipelineEnvironment{}
versioningMock := artifactVersioningMock{
originalVersion: "1.2.3",
versioningScheme: "maven",
}
worktree := gitWorktreeMock{}
repo := gitRepositoryMock{}
err := runArtifactPrepareVersion(&config, &telemetry.CustomData{}, &cpe, &versioningMock, nil, &repo, func(r gitRepository) (gitWorktree, error) { return &worktree, nil })
assert.NoError(t, err)
assert.Equal(t, "1.2.3", cpe.artifactVersion)
})
t.Run("success case - library", func(t *testing.T) {
config := artifactPrepareVersionOptions{
BuildTool: "maven",
VersioningType: "library",
}
cpe := artifactPrepareVersionCommonPipelineEnvironment{}
versioningMock := artifactVersioningMock{
originalVersion: "1.2.3",
versioningScheme: "maven",
}
worktree := gitWorktreeMock{
commitHash: plumbing.ComputeHash(plumbing.CommitObject, []byte{2, 3, 4}),
}
repo := gitRepositoryMock{
revisionHash: plumbing.ComputeHash(plumbing.CommitObject, []byte{1, 2, 3}),
}
err := runArtifactPrepareVersion(&config, &telemetry.CustomData{}, &cpe, &versioningMock, nil, &repo, func(r gitRepository) (gitWorktree, error) { return &worktree, nil })
assert.NoError(t, err)
assert.Equal(t, "1.2.3", cpe.artifactVersion)
assert.Equal(t, repo.revisionHash.String(), cpe.git.commitID)
})
t.Run("error - failed to retrive version", func(t *testing.T) {
config := artifactPrepareVersionOptions{}
versioningMock := artifactVersioningMock{
getVersionError: "getVersion error",
}
err := runArtifactPrepareVersion(&config, &telemetry.CustomData{}, nil, &versioningMock, nil, nil, nil)
assert.EqualError(t, err, "failed to retrieve version: getVersion error")
})
t.Run("error - failed to retrive git commit ID", func(t *testing.T) {
config := artifactPrepareVersionOptions{}
versioningMock := artifactVersioningMock{
originalVersion: "1.2.3",
versioningScheme: "maven",
}
repo := gitRepositoryMock{revisionError: "revision error"}
err := runArtifactPrepareVersion(&config, &telemetry.CustomData{}, nil, &versioningMock, nil, &repo, nil)
assert.EqualError(t, err, "failed to retrieve git commit ID: revision error")
})
t.Run("error - versioning template", func(t *testing.T) {
config := artifactPrepareVersionOptions{
VersioningType: "cloud",
}
versioningMock := artifactVersioningMock{
originalVersion: "1.2.3",
versioningScheme: "notSupported",
}
repo := gitRepositoryMock{}
err := runArtifactPrepareVersion(&config, &telemetry.CustomData{}, nil, &versioningMock, nil, &repo, nil)
assert.Contains(t, fmt.Sprint(err), "failed to get versioning template for scheme 'notSupported'")
})
t.Run("error - failed to retrieve git worktree", func(t *testing.T) {
config := artifactPrepareVersionOptions{
VersioningType: "cloud",
}
versioningMock := artifactVersioningMock{
originalVersion: "1.2.3",
versioningScheme: "maven",
}
repo := gitRepositoryMock{}
err := runArtifactPrepareVersion(&config, &telemetry.CustomData{}, nil, &versioningMock, nil, &repo, func(r gitRepository) (gitWorktree, error) { return nil, fmt.Errorf("worktree error") })
assert.EqualError(t, err, "failed to retrieve git worktree: worktree error")
})
t.Run("error - failed to initialize git worktree: ", func(t *testing.T) {
config := artifactPrepareVersionOptions{
VersioningType: "cloud",
}
versioningMock := artifactVersioningMock{
originalVersion: "1.2.3",
versioningScheme: "maven",
}
worktree := gitWorktreeMock{checkoutError: "checkout error"}
repo := gitRepositoryMock{}
err := runArtifactPrepareVersion(&config, &telemetry.CustomData{}, nil, &versioningMock, nil, &repo, func(r gitRepository) (gitWorktree, error) { return &worktree, nil })
assert.EqualError(t, err, "failed to initialize worktree: checkout error")
})
t.Run("error - failed to set version", func(t *testing.T) {
config := artifactPrepareVersionOptions{
VersioningType: "cloud",
}
versioningMock := artifactVersioningMock{
originalVersion: "1.2.3",
setVersionError: "setVersion error",
versioningScheme: "maven",
}
worktree := gitWorktreeMock{}
repo := gitRepositoryMock{}
err := runArtifactPrepareVersion(&config, &telemetry.CustomData{}, nil, &versioningMock, nil, &repo, func(r gitRepository) (gitWorktree, error) { return &worktree, nil })
assert.EqualError(t, err, "failed to write version: setVersion error")
})
t.Run("error - failed to push changes", func(t *testing.T) {
config := artifactPrepareVersionOptions{
VersioningType: "cloud",
}
versioningMock := artifactVersioningMock{
originalVersion: "1.2.3",
versioningScheme: "maven",
}
worktree := gitWorktreeMock{addError: "add error"}
repo := gitRepositoryMock{}
err := runArtifactPrepareVersion(&config, &telemetry.CustomData{}, nil, &versioningMock, nil, &repo, func(r gitRepository) (gitWorktree, error) { return &worktree, nil })
assert.Contains(t, fmt.Sprint(err), "failed to push changes for version '1.2.3")
})
}
func TestVersioningTemplate(t *testing.T) {
tt := []struct {
scheme string
expected string
expectedErr string
}{
{scheme: "maven", expected: "{{.Version}}{{if .Timestamp}}-{{.Timestamp}}{{if .CommitID}}_{{.CommitID}}{{end}}{{end}}"},
{scheme: "semver2", expected: "{{.Version}}{{if .Timestamp}}-{{.Timestamp}}{{if .CommitID}}+{{.CommitID}}{{end}}{{end}}"},
{scheme: "pep440", expected: "{{.Version}}{{if .Timestamp}}.{{.Timestamp}}{{if .CommitID}}+{{.CommitID}}{{end}}{{end}}"},
{scheme: "notSupported", expected: "", expectedErr: "versioning scheme 'notSupported' not supported"},
}
for _, test := range tt {
scheme, err := versioningTemplate(test.scheme)
assert.Equal(t, test.expected, scheme)
if len(test.expectedErr) == 0 {
assert.NoError(t, err)
} else {
assert.EqualError(t, err, test.expectedErr)
}
}
}
func TestCalculateNewVersion(t *testing.T) {
currentVersion := "1.2.3"
testTime := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
commitID := plumbing.ComputeHash(plumbing.CommitObject, []byte{1, 2, 3}).String()
tt := []struct {
versioningTemplate string
includeCommitID bool
expected string
expectedErr string
}{
{versioningTemplate: "", expectedErr: "failed calculate version, new version is ''"},
{versioningTemplate: "{{.Version}}{{if .Timestamp}}-{{.Timestamp}}{{if .CommitID}}+{{.CommitID}}{{end}}{{end}}", expected: "1.2.3-20200101000000"},
{versioningTemplate: "{{.Version}}{{if .Timestamp}}-{{.Timestamp}}{{if .CommitID}}+{{.CommitID}}{{end}}{{end}}", includeCommitID: true, expected: "1.2.3-20200101000000+428ecf70bc22df0ba3dcf194b5ce53e769abab07"},
}
for _, test := range tt {
version, err := calculateNewVersion(test.versioningTemplate, currentVersion, commitID, test.includeCommitID, testTime)
assert.Equal(t, test.expected, version)
if len(test.expectedErr) == 0 {
assert.NoError(t, err)
} else {
assert.EqualError(t, err, test.expectedErr)
}
}
}
func TestPushChanges(t *testing.T) {
newVersion := "1.2.3"
testTime := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
conf := gitConfig.RemoteConfig{Name: "origin", URLs: []string{"https://my.test.server"}}
remote := git.NewRemote(nil, &conf)
t.Run("success - username/password", func(t *testing.T) {
config := artifactPrepareVersionOptions{Username: "testUser", Password: "****"}
repo := gitRepositoryMock{remote: remote}
worktree := gitWorktreeMock{commitHash: plumbing.ComputeHash(plumbing.CommitObject, []byte{1, 2, 3})}
commitID, err := pushChanges(&config, newVersion, &repo, &worktree, testTime)
assert.NoError(t, err)
assert.Equal(t, "428ecf70bc22df0ba3dcf194b5ce53e769abab07", commitID)
assert.Equal(t, "update version 1.2.3", worktree.commitMsg)
assert.Equal(t, &git.CommitOptions{Author: &object.Signature{Name: "Project Piper", When: testTime}}, worktree.commitOpts)
assert.Equal(t, "1.2.3", repo.tag)
assert.Equal(t, "428ecf70bc22df0ba3dcf194b5ce53e769abab07", repo.tagHash.String())
assert.Equal(t, &git.PushOptions{RefSpecs: []gitConfig.RefSpec{"refs/tags/1.2.3:refs/tags/1.2.3"}, Auth: &http.BasicAuth{Username: config.Username, Password: config.Password}}, repo.pushOptions)
})
t.Run("success - ssh fallback", func(t *testing.T) {
config := artifactPrepareVersionOptions{}
repo := gitRepositoryMock{remote: remote}
worktree := gitWorktreeMock{commitHash: plumbing.ComputeHash(plumbing.CommitObject, []byte{1, 2, 3})}
originalSSHAgentAuth := sshAgentAuth
sshAgentAuth = func(u string) (*ssh.PublicKeysCallback, error) { return &ssh.PublicKeysCallback{}, nil }
commitID, err := pushChanges(&config, newVersion, &repo, &worktree, testTime)
sshAgentAuth = originalSSHAgentAuth
assert.NoError(t, err)
assert.Equal(t, "428ecf70bc22df0ba3dcf194b5ce53e769abab07", commitID)
assert.Equal(t, "update version 1.2.3", worktree.commitMsg)
assert.Equal(t, &git.CommitOptions{Author: &object.Signature{Name: "Project Piper", When: testTime}}, worktree.commitOpts)
assert.Equal(t, "1.2.3", repo.tag)
assert.Equal(t, "428ecf70bc22df0ba3dcf194b5ce53e769abab07", repo.tagHash.String())
assert.Equal(t, &git.PushOptions{RefSpecs: []gitConfig.RefSpec{"refs/tags/1.2.3:refs/tags/1.2.3"}, Auth: &ssh.PublicKeysCallback{}}, repo.pushOptions)
})
t.Run("success - ssh", func(t *testing.T) {
confSSH := gitConfig.RemoteConfig{Name: "origin", URLs: []string{"git@my.test.server"}}
remoteSSH := git.NewRemote(nil, &confSSH)
config := artifactPrepareVersionOptions{}
repo := gitRepositoryMock{remote: remoteSSH}
worktree := gitWorktreeMock{commitHash: plumbing.ComputeHash(plumbing.CommitObject, []byte{1, 2, 3})}
originalSSHAgentAuth := sshAgentAuth
sshAgentAuth = func(u string) (*ssh.PublicKeysCallback, error) { return &ssh.PublicKeysCallback{}, nil }
commitID, err := pushChanges(&config, newVersion, &repo, &worktree, testTime)
sshAgentAuth = originalSSHAgentAuth
assert.NoError(t, err)
assert.Equal(t, "428ecf70bc22df0ba3dcf194b5ce53e769abab07", commitID)
assert.Equal(t, &git.PushOptions{RefSpecs: []gitConfig.RefSpec{"refs/tags/1.2.3:refs/tags/1.2.3"}, Auth: &ssh.PublicKeysCallback{}}, repo.pushOptions)
})
t.Run("error - git add", func(t *testing.T) {
config := artifactPrepareVersionOptions{}
repo := gitRepositoryMock{}
worktree := gitWorktreeMock{addError: "add error", commitHash: plumbing.ComputeHash(plumbing.CommitObject, []byte{1, 2, 3})}
commitID, err := pushChanges(&config, newVersion, &repo, &worktree, testTime)
assert.Equal(t, "0000000000000000000000000000000000000000", commitID)
assert.EqualError(t, err, "failed to execute 'git add .': add error")
})
t.Run("error - commit", func(t *testing.T) {
config := artifactPrepareVersionOptions{}
repo := gitRepositoryMock{}
worktree := gitWorktreeMock{commitError: "commit error", commitHash: plumbing.ComputeHash(plumbing.CommitObject, []byte{1, 2, 3})}
commitID, err := pushChanges(&config, newVersion, &repo, &worktree, testTime)
assert.Equal(t, "0000000000000000000000000000000000000000", commitID)
assert.EqualError(t, err, "failed to commit new version: commit error")
})
t.Run("error - create tag", func(t *testing.T) {
config := artifactPrepareVersionOptions{}
repo := gitRepositoryMock{tagError: "tag error"}
worktree := gitWorktreeMock{commitHash: plumbing.ComputeHash(plumbing.CommitObject, []byte{1, 2, 3})}
commitID, err := pushChanges(&config, newVersion, &repo, &worktree, testTime)
assert.Equal(t, "428ecf70bc22df0ba3dcf194b5ce53e769abab07", commitID)
assert.EqualError(t, err, "tag error")
})
t.Run("error - no remote url", func(t *testing.T) {
config := artifactPrepareVersionOptions{}
repo := gitRepositoryMock{}
worktree := gitWorktreeMock{commitHash: plumbing.ComputeHash(plumbing.CommitObject, []byte{1, 2, 3})}
commitID, err := pushChanges(&config, newVersion, &repo, &worktree, testTime)
assert.Equal(t, "428ecf70bc22df0ba3dcf194b5ce53e769abab07", commitID)
assert.EqualError(t, err, "no remote url maintained")
})
t.Run("error - ssh fallback", func(t *testing.T) {
config := artifactPrepareVersionOptions{}
worktree := gitWorktreeMock{commitHash: plumbing.ComputeHash(plumbing.CommitObject, []byte{1, 2, 3})}
sshSuccess := func(u string) (*ssh.PublicKeysCallback, error) { return nil, nil }
sshFailure := func(u string) (*ssh.PublicKeysCallback, error) { return nil, fmt.Errorf("ssh error") }
tt := []struct {
repo gitRepositoryMock
sshAgentAuth func(string) (*ssh.PublicKeysCallback, error)
expectedError string
}{
{repo: gitRepositoryMock{remote: remote, deleteRemoteError: []string{"delete error"}}, sshAgentAuth: sshSuccess, expectedError: "failed to update remote origin - remove: delete error"},
{repo: gitRepositoryMock{remote: remote, createRemoteError: []string{"update error"}}, sshAgentAuth: sshSuccess, expectedError: "failed to update remote origin - create: update error"},
{repo: gitRepositoryMock{remote: remote}, sshAgentAuth: sshFailure, expectedError: "failed to retrieve ssh authentication: ssh error"},
{repo: gitRepositoryMock{remote: remote, deleteRemoteError: []string{"", "delete error"}}, sshAgentAuth: sshSuccess, expectedError: "failed to restore remote origin - remove: delete error"},
{repo: gitRepositoryMock{remote: remote, createRemoteError: []string{"", "update error"}}, sshAgentAuth: sshSuccess, expectedError: "failed to restore remote origin - create: update error"},
}
originalSSHAgentAuth := sshAgentAuth
for _, test := range tt {
sshAgentAuth = test.sshAgentAuth
commitID, err := pushChanges(&config, newVersion, &test.repo, &worktree, testTime)
sshAgentAuth = originalSSHAgentAuth
assert.Equal(t, "428ecf70bc22df0ba3dcf194b5ce53e769abab07", commitID)
assert.EqualError(t, err, test.expectedError)
}
})
t.Run("error - push", func(t *testing.T) {
config := artifactPrepareVersionOptions{Username: "testUser", Password: "****"}
repo := gitRepositoryMock{remote: remote, pushError: "push error"}
worktree := gitWorktreeMock{commitHash: plumbing.ComputeHash(plumbing.CommitObject, []byte{1, 2, 3})}
commitID, err := pushChanges(&config, newVersion, &repo, &worktree, testTime)
assert.Equal(t, "428ecf70bc22df0ba3dcf194b5ce53e769abab07", commitID)
assert.EqualError(t, err, "push error")
})
}
func TestTemplateCompatibility(t *testing.T) {
tt := []struct {
groovy string
versioningType string
timestamp bool
commitID bool
}{
{groovy: `${version}`, versioningType: "library", timestamp: false, commitID: false},
{groovy: `${version}-${timestamp}`, versioningType: "cloud", timestamp: true, commitID: false},
{groovy: `${version}-${timestamp}${commitId?"_"+commitId:""`, versioningType: "cloud", timestamp: true, commitID: true},
}
for _, test := range tt {
versioningType, timestamp, commitID := templateCompatibility(test.groovy)
assert.Equal(t, test.versioningType, versioningType)
assert.Equal(t, test.timestamp, timestamp)
assert.Equal(t, test.commitID, commitID)
}
}
func TestConvertHTTPToSSHURL(t *testing.T) {
tt := []struct {
httpURL string
expected string
}{
{httpURL: "https://my.test.server/owner/repo.git", expected: "git@my.test.server:owner/repo.git"},
}
for _, test := range tt {
assert.Equal(t, test.expected, convertHTTPToSSHURL(test.httpURL))
}
}

View File

@ -45,6 +45,7 @@ var GeneralConfig GeneralConfigOptions
// Execute is the starting point of the piper command line tool
func Execute() {
rootCmd.AddCommand(ArtifactPrepareVersionCommand())
rootCmd.AddCommand(ConfigCommand())
rootCmd.AddCommand(VersionCommand())
rootCmd.AddCommand(DetectExecuteScanCommand())

2
go.mod
View File

@ -8,6 +8,7 @@ require (
github.com/bmatcuk/doublestar v1.2.2
github.com/docker/docker v1.4.2-0.20200114201811-16a3519d870b // indirect
github.com/ghodss/yaml v1.0.0
github.com/go-git/go-git/v5 v5.0.0
github.com/google/go-cmp v0.3.1
github.com/google/go-containerregistry v0.0.0-20200131185320-aec8da010de2
github.com/google/go-github/v28 v28.1.1
@ -19,6 +20,5 @@ require (
github.com/stretchr/testify v1.4.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
)

47
go.sum
View File

@ -37,9 +37,15 @@ github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.27.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@ -75,6 +81,7 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc
github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -104,13 +111,27 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-git-fixtures/v4 v4.0.1 h1:q+IFMfLx200Q3scvt2hN79JsEzy4AmBTp/pqnefH+Bc=
github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
github.com/go-git/go-git/v5 v5.0.0 h1:k5RWPm4iJwYtfWoxIJy4wJX9ON7ihPeZZYC1fLYDnpg=
github.com/go-git/go-git/v5 v5.0.0/go.mod h1:oYD8y9kWsGINPFJoLdaScGCN6dlKg23blmClfZwtUVA=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
@ -191,6 +212,9 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
@ -200,6 +224,8 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@ -213,6 +239,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@ -223,6 +251,7 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -237,6 +266,8 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@ -283,6 +314,8 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
@ -319,6 +352,8 @@ github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/vdemeester/k8s-pkg-credentialprovider v0.0.0-20200107171650-7c61ffa44238/go.mod h1:JwQJCMWpUDqjZrB5jpw0f5VbN7U95zxFy1ZDpoEarGo=
github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
@ -334,6 +369,7 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -341,6 +377,8 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -373,6 +411,8 @@ golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
@ -393,6 +433,7 @@ golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -405,6 +446,8 @@ golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -462,6 +505,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
@ -474,6 +519,8 @@ gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

89
pkg/versioning/maven.go Normal file
View File

@ -0,0 +1,89 @@
package versioning
import (
"fmt"
"io"
"github.com/SAP/jenkins-library/pkg/command"
"github.com/SAP/jenkins-library/pkg/maven"
"github.com/pkg/errors"
)
type mavenExecRunner interface {
Stdout(out io.Writer)
Stderr(err io.Writer)
RunExecutable(e string, p ...string) error
}
type mavenRunner interface {
Execute(*maven.ExecuteOptions, mavenExecRunner) (string, error)
Evaluate(string, string, mavenExecRunner) (string, error)
}
// Maven ...
type Maven struct {
PomPath string
Runner mavenRunner
ExecRunner mavenExecRunner
ProjectSettingsFile string
GlobalSettingsFile string
M2Path string
}
// InitBuildDescriptor ...
func (m *Maven) init() {
if len(m.PomPath) == 0 {
m.PomPath = "pom.xml"
}
if m.ExecRunner == nil {
m.ExecRunner = &command.Command{}
}
}
// VersioningScheme ...
func (m *Maven) VersioningScheme() string {
return "maven"
}
// GetVersion ...
func (m *Maven) GetVersion() (string, error) {
m.init()
version, err := m.Runner.Evaluate(m.PomPath, "project.version", m.ExecRunner)
if err != nil {
return "", errors.Wrap(err, "Maven - getting version failed")
}
//ToDo: how to deal with SNAPSHOT replacement?
return version, nil
}
// SetVersion ...
func (m *Maven) SetVersion(version string) error {
m.init()
groupID, err := m.Runner.Evaluate(m.PomPath, "project.groupId", m.ExecRunner)
if err != nil {
return errors.Wrap(err, "Maven - getting groupId failed")
}
opts := maven.ExecuteOptions{
PomPath: m.PomPath,
ProjectSettingsFile: m.ProjectSettingsFile,
GlobalSettingsFile: m.GlobalSettingsFile,
M2Path: m.M2Path,
Goals: []string{"org.codehaus.mojo:versions-maven-plugin:2.7:set"},
Defines: []string{
fmt.Sprintf("-DnewVersion=%v", version),
fmt.Sprintf("-DgroupId=%v", groupID),
"-DartifactId=*",
"-DoldVersion=*",
"-DgenerateBackupPoms=false",
},
}
_, err = m.Runner.Execute(&opts, m.ExecRunner)
if err != nil {
return errors.Wrapf(err, "Maven - setting version %v failed", version)
}
return nil
}

View File

@ -0,0 +1,121 @@
package versioning
import (
"fmt"
"testing"
"github.com/SAP/jenkins-library/pkg/maven"
"github.com/stretchr/testify/assert"
)
type mavenMockRunner struct {
evaluateErrorString string
executeErrorString string
stdout string
opts *maven.ExecuteOptions
expression string
pomFile string
}
func (m *mavenMockRunner) Evaluate(pomFile, expression string, runner mavenExecRunner) (string, error) {
m.pomFile = pomFile
m.expression = expression
if len(m.evaluateErrorString) > 0 {
return "", fmt.Errorf(m.evaluateErrorString)
}
return m.stdout, nil
}
func (m *mavenMockRunner) Execute(opts *maven.ExecuteOptions, runner mavenExecRunner) (string, error) {
m.opts = opts
if len(m.executeErrorString) > 0 {
return "", fmt.Errorf(m.executeErrorString)
}
if opts.ReturnStdout {
return m.stdout, nil
}
return "", nil
}
func TestMavenGetVersion(t *testing.T) {
t.Run("success case", func(t *testing.T) {
runner := mavenMockRunner{
stdout: "1.2.3",
}
mvn := &Maven{
Runner: &runner,
PomPath: "path/to/pom.xml",
}
version, err := mvn.GetVersion()
assert.NoError(t, err)
assert.Equal(t, "1.2.3", version)
assert.Equal(t, "project.version", runner.expression)
assert.Equal(t, "path/to/pom.xml", runner.pomFile)
})
t.Run("error case", func(t *testing.T) {
runner := mavenMockRunner{
stdout: "1.2.3",
evaluateErrorString: "maven eval failed",
}
mvn := &Maven{
Runner: &runner,
}
version, err := mvn.GetVersion()
assert.EqualError(t, err, "Maven - getting version failed: maven eval failed")
assert.Equal(t, "", version)
})
}
func TestMavenSetVersion(t *testing.T) {
t.Run("success case", func(t *testing.T) {
runner := mavenMockRunner{
stdout: "testGroup",
}
mvn := &Maven{
Runner: &runner,
PomPath: "path/to/pom.xml",
ProjectSettingsFile: "project-settings.xml",
GlobalSettingsFile: "global-settings.xml",
M2Path: "m2/path",
}
expectedOptions := maven.ExecuteOptions{
PomPath: "path/to/pom.xml",
Defines: []string{"-DnewVersion=1.2.4", "-DgroupId=testGroup", "-DartifactId=*", "-DoldVersion=*", "-DgenerateBackupPoms=false"},
Goals: []string{"org.codehaus.mojo:versions-maven-plugin:2.7:set"},
ProjectSettingsFile: "project-settings.xml",
GlobalSettingsFile: "global-settings.xml",
M2Path: "m2/path",
}
err := mvn.SetVersion("1.2.4")
assert.NoError(t, err)
assert.Equal(t, &expectedOptions, runner.opts)
})
t.Run("evaluate error", func(t *testing.T) {
runner := mavenMockRunner{
stdout: "testGroup",
evaluateErrorString: "maven eval failed",
}
mvn := &Maven{
Runner: &runner,
PomPath: "path/to/pom.xml",
}
err := mvn.SetVersion("1.2.4")
assert.EqualError(t, err, "Maven - getting groupId failed: maven eval failed")
})
t.Run("execute error", func(t *testing.T) {
runner := mavenMockRunner{
stdout: "testGroup",
executeErrorString: "maven exec failed",
}
mvn := &Maven{
Runner: &runner,
PomPath: "path/to/pom.xml",
}
err := mvn.SetVersion("1.2.4")
assert.EqualError(t, err, "Maven - setting version 1.2.4 failed: maven exec failed")
})
}

78
pkg/versioning/npm.go Normal file
View File

@ -0,0 +1,78 @@
package versioning
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"github.com/pkg/errors"
)
// Npm ...
type Npm struct {
PackageJSONPath string
PackageJSONContent map[string]interface{}
ReadFile func(string) ([]byte, error)
WriteFile func(string, []byte, os.FileMode) error
}
// InitBuildDescriptor ...
func (n *Npm) init() {
if len(n.PackageJSONPath) == 0 {
n.PackageJSONPath = "package.json"
}
if n.ReadFile == nil {
n.ReadFile = ioutil.ReadFile
}
if n.WriteFile == nil {
n.WriteFile = ioutil.WriteFile
}
}
// VersioningScheme ...
func (n *Npm) VersioningScheme() string {
return "semver2"
}
// GetVersion ...
func (n *Npm) GetVersion() (string, error) {
n.init()
content, err := n.ReadFile(n.PackageJSONPath)
if err != nil {
return "", errors.Wrapf(err, "failed to read file '%v'", n.PackageJSONPath)
}
err = json.Unmarshal(content, &n.PackageJSONContent)
if err != nil {
return "", errors.Wrap(err, "failed to read package.json content")
}
return fmt.Sprint(n.PackageJSONContent["version"]), nil
}
// SetVersion ...
func (n *Npm) SetVersion(version string) error {
n.init()
if n.PackageJSONContent == nil {
_, err := n.GetVersion()
if err != nil {
return err
}
}
n.PackageJSONContent["version"] = version
content, err := json.MarshalIndent(n.PackageJSONContent, "", " ")
if err != nil {
return errors.Wrap(err, "failed to create json content for package.json")
}
err = n.WriteFile(n.PackageJSONPath, content, 0700)
if err != nil {
return errors.Wrapf(err, "failed to write file '%v'", n.PackageJSONPath)
}
return nil
}

View File

@ -0,0 +1,73 @@
package versioning
import (
"fmt"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestInit(t *testing.T) {
t.Run("default", func(t *testing.T) {
npm := Npm{}
npm.init()
assert.Equal(t, "package.json", npm.PackageJSONPath)
})
t.Run("no default", func(t *testing.T) {
npm := Npm{PackageJSONPath: "my/package.json"}
npm.init()
assert.Equal(t, "my/package.json", npm.PackageJSONPath)
})
}
func TestVersioningScheme(t *testing.T) {
npm := Npm{}
assert.Equal(t, "semver2", npm.VersioningScheme())
}
func TestGetVersion(t *testing.T) {
t.Run("success case", func(t *testing.T) {
npm := Npm{
PackageJSONPath: "my/package.json",
ReadFile: func(filename string) ([]byte, error) { return []byte(`{"name": "test","version": "1.2.3"}`), nil },
}
version, err := npm.GetVersion()
assert.NoError(t, err)
assert.Equal(t, "1.2.3", version)
})
t.Run("error case", func(t *testing.T) {
npm := Npm{
PackageJSONPath: "my/package.json",
ReadFile: func(filename string) ([]byte, error) { return []byte{}, fmt.Errorf("read error") },
}
_, err := npm.GetVersion()
assert.EqualError(t, err, "failed to read file 'my/package.json': read error")
})
}
func TestSetVersion(t *testing.T) {
t.Run("success case", func(t *testing.T) {
var content []byte
npm := Npm{
PackageJSONPath: "my/package.json",
ReadFile: func(filename string) ([]byte, error) { return []byte(`{"name": "test","version": "1.2.3"}`), nil },
WriteFile: func(filename string, filecontent []byte, mode os.FileMode) error { content = filecontent; return nil },
}
err := npm.SetVersion("1.2.4")
assert.NoError(t, err)
assert.Contains(t, string(content), "1.2.4")
})
t.Run("error case", func(t *testing.T) {
npm := Npm{
PackageJSONPath: "my/package.json",
ReadFile: func(filename string) ([]byte, error) { return []byte(`{"name": "test","version": "1.2.3"}`), nil },
WriteFile: func(filename string, filecontent []byte, mode os.FileMode) error { return fmt.Errorf("write error") },
}
err := npm.SetVersion("1.2.4")
assert.EqualError(t, err, "failed to write file 'my/package.json': write error")
})
}

View File

@ -0,0 +1,54 @@
package versioning
import (
"fmt"
"github.com/SAP/jenkins-library/pkg/maven"
)
// Artifact ...
type Artifact interface {
VersioningScheme() string
GetVersion() (string, error)
SetVersion(string) error
}
// Options ...
type Options struct {
ProjectSettingsFile string
GlobalSettingsFile string
M2Path string
}
type mvnRunner struct{}
func (m *mvnRunner) Execute(options *maven.ExecuteOptions, execRunner mavenExecRunner) (string, error) {
return maven.Execute(options, execRunner)
}
func (m *mvnRunner) Evaluate(pomFile, expression string, execRunner mavenExecRunner) (string, error) {
return maven.Evaluate(pomFile, expression, execRunner)
}
// GetArtifact ...
func GetArtifact(buildTool, buildDescriptorFilePath string, opts *Options, execRunner mavenExecRunner) (Artifact, error) {
var artifact Artifact
switch buildTool {
case "maven":
artifact = &Maven{
Runner: &mvnRunner{},
ExecRunner: execRunner,
PomPath: buildDescriptorFilePath,
ProjectSettingsFile: opts.ProjectSettingsFile,
GlobalSettingsFile: opts.GlobalSettingsFile,
M2Path: opts.M2Path,
}
case "npm":
artifact = &Npm{
PackageJSONPath: buildDescriptorFilePath,
}
default:
return artifact, fmt.Errorf("build tool '%v' not supported", buildTool)
}
return artifact, nil
}

View File

@ -0,0 +1,26 @@
package versioning
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetArtifact(t *testing.T) {
t.Run("maven", func(t *testing.T) {
maven, err := GetArtifact("maven", "my/pom.xml", &Options{}, nil)
assert.NoError(t, err)
assert.Equal(t, "maven", maven.VersioningScheme())
})
t.Run("npm", func(t *testing.T) {
npm, err := GetArtifact("npm", "my/package.json", &Options{}, nil)
assert.NoError(t, err)
assert.Equal(t, "semver2", npm.VersioningScheme())
})
t.Run("not supported build tool", func(t *testing.T) {
_, err := GetArtifact("nosupport", "whatever", &Options{}, nil)
assert.EqualError(t, err, "build tool 'nosupport' not supported")
})
}

View File

@ -0,0 +1,150 @@
metadata:
name: artifactPrepareVersion
aliases:
- name: artifactSetVersion
- name: setVersion
deprecated: true
description: Prepares and potentially updates the artifact's version before building the artifact.
longDescription: |-
Prepares and potentially updates the artifact's version before building the artifact.
The continuous delivery process requires that each build is done with a unique version number.
The version generated using this step will contain:
* Version (major.minor.patch) from descriptor file in master repository is preserved. Developers should be able to autonomously decide on increasing either part of this version number.
* Timestamp
* CommitId (by default the long version of the hash)
Optionally, but enabled by default, the new version is pushed as a new tag into the source code repository (e.g. GitHub).
If this option is chosen, git credentials and the repository URL needs to be provided.
Since you might not want to configure the git credentials in Jenkins, committing and pushing can be disabled using the `commitVersion` parameter as described below.
If you require strict reproducibility of your builds, this should be used.
spec:
inputs:
params:
- name: buildTool
type: string
description: Defines the tool which is used for building the artifact.
mandatory: true
scope:
- GENERAL
- PARAMETERS
- STAGES
- STEPS
- name: dockerVersionSource
type: string
description: "For Docker only: Specifies the source to be used for for generating the automatic version. * This can either be the version of the base image - as retrieved from the `FROM` statement within the Dockerfile, e.g. `FROM jenkins:2.46.2` * Alternatively the name of an environment variable defined in the Docker image can be used which contains the version number, e.g. `ENV MY_VERSION 1.2.3`"
scope:
- PARAMETERS
- STAGES
- STEPS
- name: filePath
type: string
description: "Defines a custom path to the descriptor file. Build tool specific defaults are used (e.g. maven: pom.xml, npm: package.json, mta: mta.yaml)"
scope:
- PARAMETERS
- STAGES
- STEPS
- name: gitUserEMail
type: string
description: Allows to overwrite the global git setting 'user.email' available on your Jenkins server.
scope:
- PARAMETERS
- STAGES
- STEPS
- name: gitUserName
type: string
description: Allows to overwrite the global git setting 'user.name' available on your Jenkins server.
scope:
- PARAMETERS
- STAGES
- STEPS
- name: globalSettingsFile
aliases:
- name: maven/globalSettingsFile
type: string
description: Maven only - Path to the mvn settings file that should be used as global settings file.
scope:
- GENERAL
- STEPS
- STAGES
- PARAMETERS
- name: includeCommitId
type: bool
description: Defines if the automatically generated version (versioningType 'cloud') should include the commit id hash .
scope:
- PARAMETERS
- STAGES
- STEPS
default: true
- name: m2Path
aliases:
- name: maven/m2Path
type: string
description: Maven only - Path to the location of the local repository that should be used.
scope:
- GENERAL
- STEPS
- STAGES
- PARAMETERS
- name: password
type: string
description: Password/token for git authentication
scope:
- PARAMETERS
- STAGES
- STEPS
- name: projectSettingsFile
aliases:
- name: maven/projectSettingsFile
type: string
description: Maven only - Path to the mvn settings file that should be used as project settings file.
scope:
- GENERAL
- STEPS
- STAGES
- PARAMETERS
- name: tagPrefix
type: string
description: Defines the prefix which is used for the git tag which is written during the versioning run.
scope:
- PARAMETERS
- STAGES
- STEPS
default: build_
- name: username
type: string
description: User name for git authentication
scope:
- PARAMETERS
- STAGES
- STEPS
- name: versioningTemplate
type: string
description: "DEPRECATED: Defines the template for the automatic version which will be created"
mandatory: false
scope:
- PARAMETERS
- STAGES
- STEPS
- name: versioningType
type: string
description: "Defines the type of versioning (cloud: fully automatic, library: manual, libraryTag (not available yet): automatic based on latest tag)"
scope:
- PARAMETERS
- STAGES
- STEPS
default: cloud
secrets:
- name: gitHttpsCredentialsId
type: jenkins
- name: gitSshKeyCredentialsId
type: jenkins
outputs:
resources:
- name: commonPipelineEnvironment
type: piperEnvironment
params:
- name: artifactVersion
- name: git/commitId

View File

@ -47,6 +47,7 @@ public class CommonStepsTest extends BasePiperTest{
// all steps not adopting the usual pattern of working with the script.
def whitelistScriptReference = [
'artifactPrepareVersion',
'commonPipelineEnvironment',
'checkmarxExecuteScan',
'kubernetesDeploy',
@ -111,6 +112,7 @@ public class CommonStepsTest extends BasePiperTest{
}
private static fieldRelatedWhitelist = [
'artifactPrepareVersion',
'durationMeasure', // only expects parameters via signature
'prepareDefaultValues', // special step (infrastructure)
'piperPipeline', // special step (infrastructure)

View File

@ -164,6 +164,37 @@ class PiperExecuteBinTest extends BasePiperTest {
assertThat(credentials[1], allOf(hasEntry('credentialsId', 'credToken'), hasEntry('variable', 'PIPER_credToken')))
}
@Test
void testPiperExecuteBinSSHCredentials() {
shellCallRule.setReturnValue('./piper getConfig --contextConfig --stepMetadata \'.pipeline/tmp/metadata/test.yaml\'', '{"sshCredentialsId":"sshKey", "tokenCredentialsId":"credToken"}')
List sshKey = []
helper.registerAllowedMethod("sshagent", [List, Closure], {s, c ->
sshKey = s
c()
})
List stepCredentials = [
[type: 'token', id: 'tokenCredentialsId', env: ['PIPER_credToken']],
[type: 'ssh', id: 'sshCredentialsId'],
]
stepRule.step.piperExecuteBin(
[
juStabUtils: utils,
jenkinsUtilsStub: jenkinsUtils,
testParam: "This is test content",
script: nullScript
],
'testStep',
'metadata/test.yaml',
stepCredentials
)
// asserts
assertThat(credentials.size(), is(1))
assertThat(credentials[0], allOf(hasEntry('credentialsId', 'credToken'), hasEntry('variable', 'PIPER_credToken')))
assertThat(sshKey, is(['sshKey']))
}
@Test
void testPiperExecuteBinNoDockerNoCredentials() {
shellCallRule.setReturnValue('./piper getConfig --contextConfig --stepMetadata \'.pipeline/tmp/metadata/test.yaml\'', '{}')

View File

@ -0,0 +1,16 @@
import com.sap.piper.PiperGoUtils
import com.sap.piper.Utils
import groovy.transform.Field
import static com.sap.piper.Prerequisites.checkScript
@Field String STEP_NAME = getClass().getName()
@Field String METADATA_FILE = 'metadata/versioning.yaml'
void call(Map parameters = [:]) {
List credentials = [
[type: 'ssh', id: 'gitSshKeyCredentialsId'],
[type: 'usernamePassword', id: 'gitHttpsCredentialsId', env: ['PIPER_username', 'PIPER_password']],
]
piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials)
}

View File

@ -94,6 +94,7 @@ void dockerWrapper(script, config, body) {
void credentialWrapper(config, List credentialInfo, body) {
if (credentialInfo.size() > 0) {
def creds = []
def sshCreds = []
credentialInfo.each { cred ->
switch(cred.type) {
case "file":
@ -105,12 +106,24 @@ void credentialWrapper(config, List credentialInfo, body) {
case "usernamePassword":
if (config[cred.id]) creds.add(usernamePassword(credentialsId: config[cred.id], usernameVariable: cred.env[0], passwordVariable: cred.env[1]))
break
case "ssh":
if (config[cred.id]) sshCreds.add(config[cred.id])
break
default:
error ("invalid credential type: ${cred.type}")
}
}
withCredentials(creds) {
body()
if (sshCreds.size() > 0) {
sshagent (sshCreds) {
withCredentials(creds) {
body()
}
}
} else {
withCredentials(creds) {
body()
}
}
} else {
body()