github.com/SAP/jenkins-library@v1.362.0/cmd/artifactPrepareVersion.go (about)

     1  package cmd
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	netHttp "net/http"
     8  	"os"
     9  	"strings"
    10  	"text/template"
    11  	"time"
    12  
    13  	"github.com/SAP/jenkins-library/pkg/certutils"
    14  	piperhttp "github.com/SAP/jenkins-library/pkg/http"
    15  	"github.com/SAP/jenkins-library/pkg/piperutils"
    16  
    17  	"github.com/SAP/jenkins-library/pkg/command"
    18  	gitUtils "github.com/SAP/jenkins-library/pkg/git"
    19  	"github.com/SAP/jenkins-library/pkg/log"
    20  	"github.com/SAP/jenkins-library/pkg/orchestrator"
    21  	"github.com/SAP/jenkins-library/pkg/telemetry"
    22  	"github.com/SAP/jenkins-library/pkg/versioning"
    23  	"github.com/pkg/errors"
    24  
    25  	"github.com/go-git/go-git/v5"
    26  	gitConfig "github.com/go-git/go-git/v5/config"
    27  	"github.com/go-git/go-git/v5/plumbing"
    28  	"github.com/go-git/go-git/v5/plumbing/object"
    29  	"github.com/go-git/go-git/v5/plumbing/transport/http"
    30  	"github.com/go-git/go-git/v5/plumbing/transport/ssh"
    31  )
    32  
    33  type gitRepository interface {
    34  	CommitObject(plumbing.Hash) (*object.Commit, error)
    35  	CreateTag(string, plumbing.Hash, *git.CreateTagOptions) (*plumbing.Reference, error)
    36  	CreateRemote(*gitConfig.RemoteConfig) (*git.Remote, error)
    37  	DeleteRemote(string) error
    38  	Push(*git.PushOptions) error
    39  	Remote(string) (*git.Remote, error)
    40  	ResolveRevision(plumbing.Revision) (*plumbing.Hash, error)
    41  	Worktree() (*git.Worktree, error)
    42  }
    43  
    44  type gitWorktree interface {
    45  	Checkout(*git.CheckoutOptions) error
    46  	Commit(string, *git.CommitOptions) (plumbing.Hash, error)
    47  }
    48  
    49  func getGitWorktree(repository gitRepository) (gitWorktree, error) {
    50  	return repository.Worktree()
    51  }
    52  
    53  type artifactPrepareVersionUtils interface {
    54  	Stdout(out io.Writer)
    55  	Stderr(err io.Writer)
    56  	RunExecutable(e string, p ...string) error
    57  
    58  	DownloadFile(url, filename string, header netHttp.Header, cookies []*netHttp.Cookie) error
    59  	piperhttp.Sender
    60  
    61  	Glob(pattern string) (matches []string, err error)
    62  	FileExists(filename string) (bool, error)
    63  	Copy(src, dest string) (int64, error)
    64  	MkdirAll(path string, perm os.FileMode) error
    65  	FileWrite(path string, content []byte, perm os.FileMode) error
    66  	FileRead(path string) ([]byte, error)
    67  	FileRemove(path string) error
    68  
    69  	GetConfigProvider() (orchestrator.ConfigProvider, error)
    70  }
    71  
    72  type artifactPrepareVersionUtilsBundle struct {
    73  	*command.Command
    74  	*piperutils.Files
    75  	*piperhttp.Client
    76  }
    77  
    78  func (a *artifactPrepareVersionUtilsBundle) GetConfigProvider() (orchestrator.ConfigProvider, error) {
    79  	return orchestrator.GetOrchestratorConfigProvider(nil)
    80  }
    81  
    82  func newArtifactPrepareVersionUtilsBundle() artifactPrepareVersionUtils {
    83  	utils := artifactPrepareVersionUtilsBundle{
    84  		Command: &command.Command{},
    85  		Files:   &piperutils.Files{},
    86  		Client:  &piperhttp.Client{},
    87  	}
    88  	utils.Stdout(log.Writer())
    89  	utils.Stderr(log.Writer())
    90  	return &utils
    91  }
    92  
    93  func artifactPrepareVersion(config artifactPrepareVersionOptions, telemetryData *telemetry.CustomData, commonPipelineEnvironment *artifactPrepareVersionCommonPipelineEnvironment) {
    94  	utils := newArtifactPrepareVersionUtilsBundle()
    95  
    96  	// open local .git repository
    97  	repository, err := openGit()
    98  	if err != nil {
    99  		log.Entry().WithError(err).Fatal("git repository required - none available")
   100  	}
   101  
   102  	err = runArtifactPrepareVersion(&config, telemetryData, commonPipelineEnvironment, nil, utils, repository, getGitWorktree)
   103  	if err != nil {
   104  		log.Entry().WithError(err).Fatal("artifactPrepareVersion failed")
   105  	}
   106  }
   107  
   108  var sshAgentAuth = ssh.NewSSHAgentAuth
   109  
   110  func runArtifactPrepareVersion(config *artifactPrepareVersionOptions, telemetryData *telemetry.CustomData, commonPipelineEnvironment *artifactPrepareVersionCommonPipelineEnvironment, artifact versioning.Artifact, utils artifactPrepareVersionUtils, repository gitRepository, getWorktree func(gitRepository) (gitWorktree, error)) error {
   111  
   112  	telemetryData.BuildTool = config.BuildTool
   113  	telemetryData.FilePath = config.FilePath
   114  
   115  	// Options for artifact
   116  	artifactOpts := versioning.Options{
   117  		GlobalSettingsFile:      config.GlobalSettingsFile,
   118  		M2Path:                  config.M2Path,
   119  		ProjectSettingsFile:     config.ProjectSettingsFile,
   120  		VersionField:            config.CustomVersionField,
   121  		VersionSection:          config.CustomVersionSection,
   122  		VersioningScheme:        config.CustomVersioningScheme,
   123  		VersionSource:           config.DockerVersionSource,
   124  		CAPVersioningPreference: config.CAPVersioningPreference,
   125  	}
   126  
   127  	var err error
   128  	if artifact == nil {
   129  		artifact, err = versioning.GetArtifact(config.BuildTool, config.FilePath, &artifactOpts, utils)
   130  		if err != nil {
   131  			log.SetErrorCategory(log.ErrorConfiguration)
   132  			return errors.Wrap(err, "failed to retrieve artifact")
   133  		}
   134  	}
   135  
   136  	// support former groovy versioning template and translate into new options
   137  	if len(config.VersioningTemplate) > 0 {
   138  		config.VersioningType, _, config.IncludeCommitID = templateCompatibility(config.VersioningTemplate)
   139  	}
   140  
   141  	version, err := artifact.GetVersion()
   142  	if err != nil {
   143  		log.SetErrorCategory(log.ErrorConfiguration)
   144  		return errors.Wrap(err, "failed to retrieve version")
   145  	} else if len(version) == 0 {
   146  		log.SetErrorCategory(log.ErrorConfiguration)
   147  		return fmt.Errorf("version is empty - please check versioning configuration")
   148  	}
   149  	log.Entry().Infof("Version before automatic versioning: %v", version)
   150  
   151  	gitCommit, gitCommitMessage, err := getGitCommitID(repository)
   152  	if err != nil {
   153  		log.SetErrorCategory(log.ErrorConfiguration)
   154  		return err
   155  	}
   156  	gitCommitID := gitCommit.String()
   157  
   158  	commonPipelineEnvironment.git.headCommitID = gitCommitID
   159  	newVersion := version
   160  	now := time.Now()
   161  
   162  	if config.VersioningType == "cloud" || config.VersioningType == "cloud_noTag" {
   163  		// make sure that versioning does not create tags (when set to "cloud")
   164  		// for PR pipelines, optimized pipelines (= no build)
   165  		provider, err := utils.GetConfigProvider()
   166  		if err != nil {
   167  			log.Entry().WithError(err).Warning("Cannot infer config from CI environment")
   168  		}
   169  		if provider.IsPullRequest() || config.IsOptimizedAndScheduled {
   170  			config.VersioningType = "cloud_noTag"
   171  		}
   172  
   173  		newVersion, err = calculateCloudVersion(artifact, config, version, gitCommitID, now)
   174  		if err != nil {
   175  			return err
   176  		}
   177  
   178  		worktree, err := getWorktree(repository)
   179  		if err != nil {
   180  			log.SetErrorCategory(log.ErrorConfiguration)
   181  			return errors.Wrap(err, "failed to retrieve git worktree")
   182  		}
   183  
   184  		// opening repository does not seem to consider already existing files properly
   185  		// behavior in case we do not run initializeWorktree:
   186  		//   git.Add(".") will add the complete workspace instead of only changed files
   187  		err = initializeWorktree(gitCommit, worktree)
   188  		if err != nil {
   189  			return err
   190  		}
   191  
   192  		// only update version in build descriptor if required in order to save prossing time (e.g. maven case)
   193  		if newVersion != version {
   194  			err = artifact.SetVersion(newVersion)
   195  			if err != nil {
   196  				log.SetErrorCategory(log.ErrorConfiguration)
   197  				return errors.Wrap(err, "failed to write version")
   198  			}
   199  		}
   200  
   201  		// propagate version information to additional descriptors
   202  		if len(config.AdditionalTargetTools) > 0 {
   203  			err = propagateVersion(config, utils, &artifactOpts, version, gitCommitID, now)
   204  			if err != nil {
   205  				return err
   206  			}
   207  		}
   208  
   209  		if config.VersioningType == "cloud" {
   210  			certs, err := certutils.CertificateDownload(config.CustomTLSCertificateLinks, utils)
   211  			// commit changes and push to repository (including new version tag)
   212  			gitCommitID, err = pushChanges(config, newVersion, repository, worktree, now, certs)
   213  			if err != nil {
   214  				if strings.Contains(fmt.Sprint(err), "reference already exists") {
   215  					log.SetErrorCategory(log.ErrorCustom)
   216  				}
   217  				return errors.Wrapf(err, "failed to push changes for version '%v'", newVersion)
   218  			}
   219  		}
   220  	} else {
   221  		// propagate version information to additional descriptors
   222  		if len(config.AdditionalTargetTools) > 0 {
   223  			err = propagateVersion(config, utils, &artifactOpts, version, gitCommitID, now)
   224  			if err != nil {
   225  				return err
   226  			}
   227  		}
   228  	}
   229  
   230  	log.Entry().Infof("New version: '%v'", newVersion)
   231  
   232  	commonPipelineEnvironment.git.commitID = gitCommitID // this commitID changes and is not necessarily the HEAD commitID
   233  	commonPipelineEnvironment.artifactVersion = newVersion
   234  	commonPipelineEnvironment.originalArtifactVersion = version
   235  	commonPipelineEnvironment.git.commitMessage = gitCommitMessage
   236  
   237  	// we may replace GetVersion() above with GetCoordinates() at some point ...
   238  	coordinates, err := artifact.GetCoordinates()
   239  	if err != nil && !config.FetchCoordinates {
   240  		log.Entry().Warnf("fetchCoordinates is false and failed get artifact Coordinates")
   241  	} else if err != nil && config.FetchCoordinates {
   242  		return fmt.Errorf("failed to get coordinates: %w", err)
   243  	} else {
   244  		commonPipelineEnvironment.artifactID = coordinates.ArtifactID
   245  		commonPipelineEnvironment.groupID = coordinates.GroupID
   246  		commonPipelineEnvironment.packaging = coordinates.Packaging
   247  	}
   248  
   249  	return nil
   250  }
   251  
   252  func openGit() (gitRepository, error) {
   253  	workdir, _ := os.Getwd()
   254  	return gitUtils.PlainOpen(workdir)
   255  }
   256  
   257  func getGitCommitID(repository gitRepository) (plumbing.Hash, string, error) {
   258  	commitID, err := repository.ResolveRevision(plumbing.Revision("HEAD"))
   259  	if err != nil {
   260  		return plumbing.Hash{}, "", errors.Wrap(err, "failed to retrieve git commit ID")
   261  	}
   262  	// ToDo not too elegant to retrieve the commit message here, must be refactored sooner than later
   263  	// but to quickly address https://github.com/SAP/jenkins-library/pull/1515 let's revive this
   264  	commitObject, err := repository.CommitObject(*commitID)
   265  	if err != nil {
   266  		return *commitID, "", errors.Wrap(err, "failed to retrieve git commit message")
   267  	}
   268  	return *commitID, commitObject.Message, nil
   269  }
   270  
   271  func versioningTemplate(scheme string) (string, error) {
   272  	// generally: timestamp acts as build number providing a proper order
   273  	switch scheme {
   274  	case "docker":
   275  		// from Docker documentation:
   276  		// A tag name must be valid ASCII and may contain lowercase and uppercase letters, digits, underscores, periods and dashes.
   277  		// A tag name may not start with a period or a dash and may contain a maximum of 128 characters.
   278  		return "{{.Version}}{{if .Timestamp}}-{{.Timestamp}}{{if .CommitID}}-{{.CommitID}}{{end}}{{end}}", nil
   279  	case "maven":
   280  		// according to https://www.mojohaus.org/versions-maven-plugin/version-rules.html
   281  		return "{{.Version}}{{if .Timestamp}}-{{.Timestamp}}{{if .CommitID}}_{{.CommitID}}{{end}}{{end}}", nil
   282  	case "pep440":
   283  		// according to https://www.python.org/dev/peps/pep-0440/
   284  		return "{{.Version}}{{if .Timestamp}}.{{.Timestamp}}{{if .CommitID}}+{{.CommitID}}{{end}}{{end}}", nil
   285  	case "semver2":
   286  		// according to https://semver.org/spec/v2.0.0.html
   287  		return "{{.Version}}{{if .Timestamp}}-{{.Timestamp}}{{if .CommitID}}+{{.CommitID}}{{end}}{{end}}", nil
   288  	}
   289  	return "", fmt.Errorf("versioning scheme '%v' not supported", scheme)
   290  }
   291  
   292  func calculateNewVersion(versioningTemplate, currentVersion, commitID string, includeCommitID, shortCommitID, unixTimestamp bool, t time.Time) (string, error) {
   293  	tmpl, err := template.New("version").Parse(versioningTemplate)
   294  	if err != nil {
   295  		return "", errors.Wrapf(err, "failed to create version template: %v", versioningTemplate)
   296  	}
   297  
   298  	timestamp := t.Format("20060102150405")
   299  	if unixTimestamp {
   300  		timestamp = fmt.Sprint(t.Unix())
   301  	}
   302  
   303  	buf := new(bytes.Buffer)
   304  	versionParts := struct {
   305  		Version   string
   306  		Timestamp string
   307  		CommitID  string
   308  	}{
   309  		Version:   currentVersion,
   310  		Timestamp: timestamp,
   311  	}
   312  
   313  	if includeCommitID {
   314  		versionParts.CommitID = commitID
   315  		if shortCommitID {
   316  			versionParts.CommitID = commitID[0:7]
   317  		}
   318  	}
   319  
   320  	err = tmpl.Execute(buf, versionParts)
   321  	if err != nil {
   322  		return "", errors.Wrapf(err, "failed to execute versioning template: %v", versioningTemplate)
   323  	}
   324  
   325  	newVersion := buf.String()
   326  	if len(newVersion) == 0 {
   327  		return "", fmt.Errorf("failed calculate version, new version is '%v'", newVersion)
   328  	}
   329  	return buf.String(), nil
   330  }
   331  
   332  func initializeWorktree(gitCommit plumbing.Hash, worktree gitWorktree) error {
   333  	// checkout current revision in order to work on that
   334  	err := worktree.Checkout(&git.CheckoutOptions{Hash: gitCommit, Keep: true})
   335  	if err != nil {
   336  		return errors.Wrap(err, "failed to initialize worktree")
   337  	}
   338  
   339  	return nil
   340  }
   341  
   342  func pushChanges(config *artifactPrepareVersionOptions, newVersion string, repository gitRepository, worktree gitWorktree, t time.Time, certs []byte) (string, error) {
   343  
   344  	var commitID string
   345  
   346  	commit, err := addAndCommit(config, worktree, newVersion, t)
   347  	if err != nil {
   348  		return commit.String(), err
   349  	}
   350  
   351  	commitID = commit.String()
   352  
   353  	tag := fmt.Sprintf("%v%v", config.TagPrefix, newVersion)
   354  	_, err = repository.CreateTag(tag, commit, nil)
   355  	if err != nil {
   356  		return commitID, err
   357  	}
   358  
   359  	ref := gitConfig.RefSpec(fmt.Sprintf("refs/tags/%v:refs/tags/%v", tag, tag))
   360  
   361  	pushOptions := git.PushOptions{
   362  		RefSpecs: []gitConfig.RefSpec{gitConfig.RefSpec(ref)},
   363  		CABundle: certs,
   364  	}
   365  
   366  	currentRemoteOrigin, err := repository.Remote("origin")
   367  	if err != nil {
   368  		return commitID, errors.Wrap(err, "failed to retrieve current remote origin")
   369  	}
   370  	var updatedRemoteOrigin *git.Remote
   371  
   372  	urls := originUrls(repository)
   373  	if len(urls) == 0 {
   374  		log.SetErrorCategory(log.ErrorConfiguration)
   375  		return commitID, fmt.Errorf("no remote url maintained")
   376  	}
   377  	if strings.HasPrefix(urls[0], "http") {
   378  		if len(config.Username) == 0 || len(config.Password) == 0 {
   379  			// handling compatibility: try to use ssh in case no credentials are available
   380  			log.Entry().Info("git username/password missing - switching to ssh")
   381  
   382  			remoteURL := convertHTTPToSSHURL(urls[0])
   383  
   384  			// update remote origin url to point to ssh url instead of http(s) url
   385  			err = repository.DeleteRemote("origin")
   386  			if err != nil {
   387  				return commitID, errors.Wrap(err, "failed to update remote origin - remove")
   388  			}
   389  			updatedRemoteOrigin, err = repository.CreateRemote(&gitConfig.RemoteConfig{Name: "origin", URLs: []string{remoteURL}})
   390  			if err != nil {
   391  				return commitID, errors.Wrap(err, "failed to update remote origin - create")
   392  			}
   393  
   394  			pushOptions.Auth, err = sshAgentAuth("git")
   395  			if err != nil {
   396  				log.SetErrorCategory(log.ErrorConfiguration)
   397  				return commitID, errors.Wrap(err, "failed to retrieve ssh authentication")
   398  			}
   399  			log.Entry().Infof("using remote '%v'", remoteURL)
   400  		} else {
   401  			pushOptions.Auth = &http.BasicAuth{Username: config.Username, Password: config.Password}
   402  		}
   403  	} else {
   404  		pushOptions.Auth, err = sshAgentAuth("git")
   405  		if err != nil {
   406  			log.SetErrorCategory(log.ErrorConfiguration)
   407  			return commitID, errors.Wrap(err, "failed to retrieve ssh authentication")
   408  		}
   409  	}
   410  
   411  	err = repository.Push(&pushOptions)
   412  	if err != nil {
   413  		errText := fmt.Sprint(err)
   414  		switch {
   415  		case strings.Contains(errText, "ssh: handshake failed"):
   416  			log.SetErrorCategory(log.ErrorConfiguration)
   417  		case strings.Contains(errText, "Permission"):
   418  			log.SetErrorCategory(log.ErrorConfiguration)
   419  		case strings.Contains(errText, "authorization failed"):
   420  			log.SetErrorCategory(log.ErrorConfiguration)
   421  		case strings.Contains(errText, "authentication required"):
   422  			log.SetErrorCategory(log.ErrorConfiguration)
   423  		case strings.Contains(errText, "knownhosts:"):
   424  			err = errors.Wrap(err, "known_hosts file seems invalid")
   425  			log.SetErrorCategory(log.ErrorConfiguration)
   426  		case strings.Contains(errText, "unable to find any valid known_hosts file"):
   427  			log.SetErrorCategory(log.ErrorConfiguration)
   428  		case strings.Contains(errText, "connection timed out"):
   429  			log.SetErrorCategory(log.ErrorInfrastructure)
   430  		}
   431  		return commitID, err
   432  	}
   433  
   434  	if updatedRemoteOrigin != currentRemoteOrigin {
   435  		err = repository.DeleteRemote("origin")
   436  		if err != nil {
   437  			return commitID, errors.Wrap(err, "failed to restore remote origin - remove")
   438  		}
   439  		_, err := repository.CreateRemote(currentRemoteOrigin.Config())
   440  		if err != nil {
   441  			return commitID, errors.Wrap(err, "failed to restore remote origin - create")
   442  		}
   443  	}
   444  
   445  	return commitID, nil
   446  }
   447  
   448  func addAndCommit(config *artifactPrepareVersionOptions, worktree gitWorktree, newVersion string, t time.Time) (plumbing.Hash, error) {
   449  	//maybe more options are required: https://github.com/go-git/go-git/blob/master/_examples/commit/main.go
   450  	commit, err := worktree.Commit(fmt.Sprintf("update version %v", newVersion), &git.CommitOptions{All: true, Author: &object.Signature{Name: config.CommitUserName, When: t}})
   451  	if err != nil {
   452  		return commit, errors.Wrap(err, "failed to commit new version")
   453  	}
   454  	return commit, nil
   455  }
   456  
   457  func originUrls(repository gitRepository) []string {
   458  	remote, err := repository.Remote("origin")
   459  	if err != nil || remote == nil {
   460  		return []string{}
   461  	}
   462  	return remote.Config().URLs
   463  }
   464  
   465  func convertHTTPToSSHURL(url string) string {
   466  	sshURL := strings.Replace(url, "https://", "git@", 1)
   467  	return strings.Replace(sshURL, "/", ":", 1)
   468  }
   469  
   470  func templateCompatibility(groovyTemplate string) (versioningType string, useTimestamp bool, useCommitID bool) {
   471  	useTimestamp = strings.Contains(groovyTemplate, "${timestamp}")
   472  	useCommitID = strings.Contains(groovyTemplate, "${commitId")
   473  
   474  	versioningType = "library"
   475  
   476  	if useTimestamp {
   477  		versioningType = "cloud"
   478  	}
   479  
   480  	return
   481  }
   482  
   483  func calculateCloudVersion(artifact versioning.Artifact, config *artifactPrepareVersionOptions, version, gitCommitID string, timestamp time.Time) (string, error) {
   484  	versioningTempl, err := versioningTemplate(artifact.VersioningScheme())
   485  	if err != nil {
   486  		log.SetErrorCategory(log.ErrorConfiguration)
   487  		return "", errors.Wrapf(err, "failed to get versioning template for scheme '%v'", artifact.VersioningScheme())
   488  	}
   489  
   490  	newVersion, err := calculateNewVersion(versioningTempl, version, gitCommitID, config.IncludeCommitID, config.ShortCommitID, config.UnixTimestamp, timestamp)
   491  	if err != nil {
   492  		return "", errors.Wrap(err, "failed to calculate new version")
   493  	}
   494  	return newVersion, nil
   495  }
   496  
   497  func propagateVersion(config *artifactPrepareVersionOptions, utils artifactPrepareVersionUtils, artifactOpts *versioning.Options, version, gitCommitID string, now time.Time) error {
   498  	var err error
   499  
   500  	if len(config.AdditionalTargetDescriptors) > 0 && len(config.AdditionalTargetTools) != len(config.AdditionalTargetDescriptors) {
   501  		log.SetErrorCategory(log.ErrorConfiguration)
   502  		return fmt.Errorf("additionalTargetDescriptors cannot have a different number of entries than additionalTargetTools")
   503  	}
   504  
   505  	for i, targetTool := range config.AdditionalTargetTools {
   506  
   507  		var buildDescriptors []string
   508  		if len(config.AdditionalTargetDescriptors) > 0 {
   509  			buildDescriptors, err = utils.Glob(config.AdditionalTargetDescriptors[i])
   510  			if err != nil {
   511  				log.SetErrorCategory(log.ErrorConfiguration)
   512  				return fmt.Errorf("failed to retrieve build descriptors: %w", err)
   513  			}
   514  		}
   515  
   516  		if len(buildDescriptors) == 0 {
   517  			buildDescriptors = append(buildDescriptors, "")
   518  		}
   519  
   520  		// in case of helm, make sure that app version is adapted as well
   521  		artifactOpts.HelmUpdateAppVersion = true
   522  
   523  		for _, buildDescriptor := range buildDescriptors {
   524  			targetArtifact, err := versioning.GetArtifact(targetTool, buildDescriptor, artifactOpts, utils)
   525  			if err != nil {
   526  				log.SetErrorCategory(log.ErrorConfiguration)
   527  				return fmt.Errorf("failed to retrieve artifact: %w", err)
   528  			}
   529  
   530  			// Make sure that version type fits to target artifact
   531  			descriptorVersion := version
   532  			if config.VersioningType == "cloud" || config.VersioningType == "cloud_noTag" {
   533  				descriptorVersion, err = calculateCloudVersion(targetArtifact, config, version, gitCommitID, now)
   534  				if err != nil {
   535  					return err
   536  				}
   537  			}
   538  			err = targetArtifact.SetVersion(descriptorVersion)
   539  			if err != nil {
   540  				return fmt.Errorf("failed to set additional target version for '%v': %w", targetTool, err)
   541  			}
   542  		}
   543  	}
   544  	return nil
   545  }