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