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