You've already forked sap-jenkins-library
							
							
				mirror of
				https://github.com/SAP/jenkins-library.git
				synced 2025-10-30 23:57:50 +02:00 
			
		
		
		
	feat(artifactPrepareVersion): helm & propagate version (#3627)
* feat(artifactPrepareVersion): helm & propagate version * chore: small refactoring * chore: fix linting issue * fix version persistence
This commit is contained in:
		| @@ -124,11 +124,9 @@ func runArtifactPrepareVersion(config *artifactPrepareVersionOptions, telemetryD | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	versioningType := config.VersioningType | ||||
|  | ||||
| 	// support former groovy versioning template and translate into new options | ||||
| 	if len(config.VersioningTemplate) > 0 { | ||||
| 		versioningType, _, config.IncludeCommitID = templateCompatibility(config.VersioningTemplate) | ||||
| 		config.VersioningType, _, config.IncludeCommitID = templateCompatibility(config.VersioningTemplate) | ||||
| 	} | ||||
|  | ||||
| 	version, err := artifact.GetVersion() | ||||
| @@ -148,18 +146,12 @@ func runArtifactPrepareVersion(config *artifactPrepareVersionOptions, telemetryD | ||||
| 	commonPipelineEnvironment.git.headCommitID = gitCommitID | ||||
| 	newVersion := version | ||||
|  | ||||
| 	if versioningType == "cloud" || versioningType == "cloud_noTag" { | ||||
| 		versioningTempl, err := versioningTemplate(artifact.VersioningScheme()) | ||||
| 		if err != nil { | ||||
| 			log.SetErrorCategory(log.ErrorConfiguration) | ||||
| 			return errors.Wrapf(err, "failed to get versioning template for scheme '%v'", artifact.VersioningScheme()) | ||||
| 		} | ||||
|  | ||||
| 	if config.VersioningType == "cloud" || config.VersioningType == "cloud_noTag" { | ||||
| 		now := time.Now() | ||||
|  | ||||
| 		newVersion, err = calculateNewVersion(versioningTempl, version, gitCommitID, config.IncludeCommitID, config.ShortCommitID, config.UnixTimestamp, now) | ||||
| 		newVersion, err = calculateCloudVersion(artifact, config, version, gitCommitID, now) | ||||
| 		if err != nil { | ||||
| 			return errors.Wrap(err, "failed to calculate new version") | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		worktree, err := getWorktree(repository) | ||||
| @@ -185,9 +177,15 @@ func runArtifactPrepareVersion(config *artifactPrepareVersionOptions, telemetryD | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		//ToDo: what about closure in current Groovy step. Discard the possibility or provide extension mechanism? | ||||
| 		// propagate version information to additional descriptors | ||||
| 		if len(config.AdditionalTargetTools) > 0 { | ||||
| 			err = propagateVersion(config, utils, &artifactOpts, newVersion, gitCommitID, now) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if versioningType == "cloud" { | ||||
| 		if config.VersioningType == "cloud" { | ||||
| 			// commit changes and push to repository (including new version tag) | ||||
| 			gitCommitID, err = pushChanges(config, newVersion, repository, worktree, now) | ||||
| 			if err != nil { | ||||
| @@ -450,3 +448,71 @@ func templateCompatibility(groovyTemplate string) (versioningType string, useTim | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func calculateCloudVersion(artifact versioning.Artifact, config *artifactPrepareVersionOptions, version, gitCommitID string, timestamp time.Time) (string, error) { | ||||
| 	versioningTempl, err := versioningTemplate(artifact.VersioningScheme()) | ||||
| 	if err != nil { | ||||
| 		log.SetErrorCategory(log.ErrorConfiguration) | ||||
| 		return "", errors.Wrapf(err, "failed to get versioning template for scheme '%v'", artifact.VersioningScheme()) | ||||
| 	} | ||||
|  | ||||
| 	newVersion, err := calculateNewVersion(versioningTempl, version, gitCommitID, config.IncludeCommitID, config.ShortCommitID, config.UnixTimestamp, timestamp) | ||||
| 	if err != nil { | ||||
| 		return "", errors.Wrap(err, "failed to calculate new version") | ||||
| 	} | ||||
| 	return newVersion, nil | ||||
| } | ||||
|  | ||||
| func propagateVersion(config *artifactPrepareVersionOptions, utils artifactPrepareVersionUtils, artifactOpts *versioning.Options, newVersion, gitCommitID string, now time.Time) error { | ||||
| 	var err error | ||||
|  | ||||
| 	if len(config.AdditionalTargetDescriptors) > 0 && len(config.AdditionalTargetTools) != len(config.AdditionalTargetDescriptors) { | ||||
| 		log.SetErrorCategory(log.ErrorConfiguration) | ||||
| 		return fmt.Errorf("additionalTargetDescriptors cannot have a different number of entries than additionalTargetTools") | ||||
| 	} | ||||
|  | ||||
| 	for i, targetTool := range config.AdditionalTargetTools { | ||||
| 		if targetTool == config.BuildTool { | ||||
| 			// ignore configured build tool | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		var buildDescriptors []string | ||||
| 		if len(config.AdditionalTargetDescriptors) > 0 { | ||||
| 			buildDescriptors, err = utils.Glob(config.AdditionalTargetDescriptors[i]) | ||||
| 			if err != nil { | ||||
| 				log.SetErrorCategory(log.ErrorConfiguration) | ||||
| 				return fmt.Errorf("failed to retrieve build descriptors: %w", err) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if len(buildDescriptors) == 0 { | ||||
| 			buildDescriptors = append(buildDescriptors, "") | ||||
| 		} | ||||
|  | ||||
| 		// in case of helm, make sure that app version is adapted as well | ||||
| 		artifactOpts.HelmUpdateAppVersion = true | ||||
|  | ||||
| 		for _, buildDescriptor := range buildDescriptors { | ||||
| 			targetArtifact, err := versioning.GetArtifact(targetTool, buildDescriptor, artifactOpts, utils) | ||||
| 			if err != nil { | ||||
| 				log.SetErrorCategory(log.ErrorConfiguration) | ||||
| 				return fmt.Errorf("failed to retrieve artifact: %w", err) | ||||
| 			} | ||||
|  | ||||
| 			// Make sure that version type fits to target artifact | ||||
| 			var descriptorVersion string | ||||
| 			if config.VersioningType == "cloud" || config.VersioningType == "cloud_noTag" { | ||||
| 				descriptorVersion, err = calculateCloudVersion(targetArtifact, config, newVersion, gitCommitID, now) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| 			err = targetArtifact.SetVersion(descriptorVersion) | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("failed to set additional target version for '%v': %w", targetTool, err) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
| @@ -18,25 +18,27 @@ import ( | ||||
| ) | ||||
|  | ||||
| type artifactPrepareVersionOptions struct { | ||||
| 	BuildTool              string `json:"buildTool,omitempty" validate:"possible-values=custom docker dub golang gradle maven mta npm pip sbt yarn"` | ||||
| 	CommitUserName         string `json:"commitUserName,omitempty"` | ||||
| 	CustomVersionField     string `json:"customVersionField,omitempty"` | ||||
| 	CustomVersionSection   string `json:"customVersionSection,omitempty"` | ||||
| 	CustomVersioningScheme string `json:"customVersioningScheme,omitempty" validate:"possible-values=docker maven pep440 semver2"` | ||||
| 	DockerVersionSource    string `json:"dockerVersionSource,omitempty"` | ||||
| 	FetchCoordinates       bool   `json:"fetchCoordinates,omitempty"` | ||||
| 	FilePath               string `json:"filePath,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"` | ||||
| 	ShortCommitID          bool   `json:"shortCommitId,omitempty"` | ||||
| 	TagPrefix              string `json:"tagPrefix,omitempty"` | ||||
| 	UnixTimestamp          bool   `json:"unixTimestamp,omitempty"` | ||||
| 	Username               string `json:"username,omitempty"` | ||||
| 	VersioningTemplate     string `json:"versioningTemplate,omitempty"` | ||||
| 	VersioningType         string `json:"versioningType,omitempty" validate:"possible-values=cloud cloud_noTag library"` | ||||
| 	AdditionalTargetTools       []string `json:"additionalTargetTools,omitempty" validate:"possible-values=custom docker dub golang gradle helm maven mta npm pip sbt yarn"` | ||||
| 	AdditionalTargetDescriptors []string `json:"additionalTargetDescriptors,omitempty"` | ||||
| 	BuildTool                   string   `json:"buildTool,omitempty" validate:"possible-values=custom docker dub golang gradle helm maven mta npm pip sbt yarn"` | ||||
| 	CommitUserName              string   `json:"commitUserName,omitempty"` | ||||
| 	CustomVersionField          string   `json:"customVersionField,omitempty"` | ||||
| 	CustomVersionSection        string   `json:"customVersionSection,omitempty"` | ||||
| 	CustomVersioningScheme      string   `json:"customVersioningScheme,omitempty" validate:"possible-values=docker maven pep440 semver2"` | ||||
| 	DockerVersionSource         string   `json:"dockerVersionSource,omitempty"` | ||||
| 	FetchCoordinates            bool     `json:"fetchCoordinates,omitempty"` | ||||
| 	FilePath                    string   `json:"filePath,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"` | ||||
| 	ShortCommitID               bool     `json:"shortCommitId,omitempty"` | ||||
| 	TagPrefix                   string   `json:"tagPrefix,omitempty"` | ||||
| 	UnixTimestamp               bool     `json:"unixTimestamp,omitempty"` | ||||
| 	Username                    string   `json:"username,omitempty"` | ||||
| 	VersioningTemplate          string   `json:"versioningTemplate,omitempty"` | ||||
| 	VersioningType              string   `json:"versioningType,omitempty" validate:"possible-values=cloud cloud_noTag library"` | ||||
| } | ||||
|  | ||||
| type artifactPrepareVersionCommonPipelineEnvironment struct { | ||||
| @@ -236,6 +238,8 @@ Define ` + "`" + `buildTool: custom` + "`" + `, ` + "`" + `filePath: <path to yo | ||||
| } | ||||
|  | ||||
| func addArtifactPrepareVersionFlags(cmd *cobra.Command, stepConfig *artifactPrepareVersionOptions) { | ||||
| 	cmd.Flags().StringSliceVar(&stepConfig.AdditionalTargetTools, "additionalTargetTools", []string{}, "Additional buildTool targets where descriptors need to be updated besides the main `buildTool`.") | ||||
| 	cmd.Flags().StringSliceVar(&stepConfig.AdditionalTargetDescriptors, "additionalTargetDescriptors", []string{}, "Defines patterns for build descriptors which should be used for option [`additionalTargetTools`](additionaltargettools).") | ||||
| 	cmd.Flags().StringVar(&stepConfig.BuildTool, "buildTool", os.Getenv("PIPER_buildTool"), "Defines the tool which is used for building the artifact.") | ||||
| 	cmd.Flags().StringVar(&stepConfig.CommitUserName, "commitUserName", `Project Piper`, "Defines the user name which appears in version control for the versioning update (in case `versioningType: cloud`).") | ||||
| 	cmd.Flags().StringVar(&stepConfig.CustomVersionField, "customVersionField", os.Getenv("PIPER_customVersionField"), "For `buildTool: custom`: Defines the field which contains the version in the descriptor file.") | ||||
| @@ -274,6 +278,24 @@ func artifactPrepareVersionMetadata() config.StepData { | ||||
| 					{Name: "gitSshKeyCredentialsId", Description: "Jenkins 'SSH Username with private key' credentials ID ssh key for accessing your git repository. You can find details about how to generate an ssh key in the [GitHub documentation](https://docs.github.com/en/enterprise/2.15/user/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent).", Type: "jenkins", Aliases: []config.Alias{{Name: "gitCredentialsId", Deprecated: true}}}, | ||||
| 				}, | ||||
| 				Parameters: []config.StepParameters{ | ||||
| 					{ | ||||
| 						Name:        "additionalTargetTools", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS"}, | ||||
| 						Type:        "[]string", | ||||
| 						Mandatory:   false, | ||||
| 						Aliases:     []config.Alias{}, | ||||
| 						Default:     []string{}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "additionalTargetDescriptors", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS"}, | ||||
| 						Type:        "[]string", | ||||
| 						Mandatory:   false, | ||||
| 						Aliases:     []config.Alias{}, | ||||
| 						Default:     []string{}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "buildTool", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
|   | ||||
| @@ -2,19 +2,23 @@ package cmd | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/SAP/jenkins-library/pkg/mock" | ||||
| 	"github.com/SAP/jenkins-library/pkg/telemetry" | ||||
| 	"github.com/SAP/jenkins-library/pkg/versioning" | ||||
|  | ||||
| 	"github.com/SAP/jenkins-library/pkg/telemetry" | ||||
| 	"github.com/ghodss/yaml" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"helm.sh/helm/v3/pkg/chart" | ||||
|  | ||||
| 	"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" | ||||
| 	gitHttp "github.com/go-git/go-git/v5/plumbing/transport/http" | ||||
| 	"github.com/go-git/go-git/v5/plumbing/transport/ssh" | ||||
| ) | ||||
|  | ||||
| @@ -166,6 +170,24 @@ func (w *gitWorktreeMock) Commit(msg string, opts *git.CommitOptions) (plumbing. | ||||
| 	return w.commitHash, nil | ||||
| } | ||||
|  | ||||
| type artifactPrepareVersionMockUtils struct { | ||||
| 	*mock.ExecMockRunner | ||||
| 	*mock.FilesMock | ||||
| } | ||||
|  | ||||
| func newArtifactPrepareVersionMockUtils() *artifactPrepareVersionMockUtils { | ||||
| 	utils := artifactPrepareVersionMockUtils{ | ||||
| 		ExecMockRunner: &mock.ExecMockRunner{}, | ||||
| 		FilesMock:      &mock.FilesMock{}, | ||||
| 	} | ||||
| 	return &utils | ||||
| } | ||||
|  | ||||
| func (a *artifactPrepareVersionMockUtils) DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error { | ||||
| 	// so far no dedicated logic required for testing | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func TestRunArtifactPrepareVersion(t *testing.T) { | ||||
|  | ||||
| 	t.Run("success case - cloud", func(t *testing.T) { | ||||
| @@ -579,7 +601,7 @@ func TestPushChanges(t *testing.T) { | ||||
| 		assert.Equal(t, &git.CommitOptions{All: true, 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) | ||||
| 		assert.Equal(t, &git.PushOptions{RefSpecs: []gitConfig.RefSpec{"refs/tags/1.2.3:refs/tags/1.2.3"}, Auth: &gitHttp.BasicAuth{Username: config.Username, Password: config.Password}}, repo.pushOptions) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("success - ssh fallback", func(t *testing.T) { | ||||
| @@ -724,3 +746,79 @@ func TestConvertHTTPToSSHURL(t *testing.T) { | ||||
| 		assert.Equal(t, test.expected, convertHTTPToSSHURL(test.httpURL)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestPropagateVersion(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	gitCommitID := "theGitCommitId" | ||||
| 	testTime := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) //20200101000000 | ||||
|  | ||||
| 	t.Run("success case", func(t *testing.T) { | ||||
| 		config := artifactPrepareVersionOptions{ | ||||
| 			VersioningType:        "cloud", | ||||
| 			AdditionalTargetTools: []string{"helm"}, | ||||
| 		} | ||||
|  | ||||
| 		chartMetadata := chart.Metadata{Version: "1.2.3"} | ||||
| 		content, err := yaml.Marshal(chartMetadata) | ||||
| 		assert.NoError(t, err) | ||||
|  | ||||
| 		utils := newArtifactPrepareVersionMockUtils() | ||||
| 		utils.AddFile("myChart/Chart.yaml", content) | ||||
| 		artifactOpts := versioning.Options{} | ||||
|  | ||||
| 		err = propagateVersion(&config, utils, &artifactOpts, "1.2.4", gitCommitID, testTime) | ||||
| 		assert.NoError(t, err) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("success case - dedicated build descriptors", func(t *testing.T) { | ||||
| 		config := artifactPrepareVersionOptions{ | ||||
| 			VersioningType:              "cloud", | ||||
| 			AdditionalTargetTools:       []string{"helm"}, | ||||
| 			AdditionalTargetDescriptors: []string{"myChart/Chart.yaml"}, | ||||
| 		} | ||||
|  | ||||
| 		chartMetadata := chart.Metadata{Version: "1.2.3"} | ||||
| 		content, err := yaml.Marshal(chartMetadata) | ||||
| 		assert.NoError(t, err) | ||||
|  | ||||
| 		utils := newArtifactPrepareVersionMockUtils() | ||||
| 		utils.AddFile("myChart/Chart.yaml", content) | ||||
| 		artifactOpts := versioning.Options{} | ||||
|  | ||||
| 		err = propagateVersion(&config, utils, &artifactOpts, "1.2.4", gitCommitID, testTime) | ||||
| 		assert.NoError(t, err) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("success case - noop", func(t *testing.T) { | ||||
| 		config := artifactPrepareVersionOptions{} | ||||
| 		utils := newArtifactPrepareVersionMockUtils() | ||||
| 		artifactOpts := versioning.Options{} | ||||
|  | ||||
| 		err := propagateVersion(&config, utils, &artifactOpts, "1.2.4", gitCommitID, testTime) | ||||
| 		assert.NoError(t, err) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("error case - wrong config", func(t *testing.T) { | ||||
| 		config := artifactPrepareVersionOptions{ | ||||
| 			AdditionalTargetDescriptors: []string{"pom.xml"}, | ||||
| 			AdditionalTargetTools:       []string{"maven", "helm"}, | ||||
| 		} | ||||
| 		utils := newArtifactPrepareVersionMockUtils() | ||||
| 		artifactOpts := versioning.Options{} | ||||
|  | ||||
| 		err := propagateVersion(&config, utils, &artifactOpts, "1.2.4", gitCommitID, testTime) | ||||
| 		assert.EqualError(t, err, "additionalTargetDescriptors cannot have a different number of entries than additionalTargetTools") | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("error case - wrong target tool", func(t *testing.T) { | ||||
| 		config := artifactPrepareVersionOptions{ | ||||
| 			AdditionalTargetTools: []string{"notKnown"}, | ||||
| 		} | ||||
| 		utils := newArtifactPrepareVersionMockUtils() | ||||
| 		artifactOpts := versioning.Options{} | ||||
|  | ||||
| 		err := propagateVersion(&config, utils, &artifactOpts, "1.2.4", gitCommitID, testTime) | ||||
| 		assert.Contains(t, fmt.Sprint(err), "failed to retrieve artifact") | ||||
| 	}) | ||||
| } | ||||
|   | ||||
							
								
								
									
										96
									
								
								pkg/versioning/helm.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								pkg/versioning/helm.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | ||||
| package versioning | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/ghodss/yaml" | ||||
| 	"helm.sh/helm/v3/pkg/chart" | ||||
| ) | ||||
|  | ||||
| // JSONfile defines an artifact using a json file for versioning | ||||
| type HelmChart struct { | ||||
| 	path             string | ||||
| 	metadata         chart.Metadata | ||||
| 	utils            Utils | ||||
| 	updateAppVersion bool | ||||
| } | ||||
|  | ||||
| func (h *HelmChart) init() error { | ||||
| 	if h.utils == nil { | ||||
| 		return fmt.Errorf("no file utils provided") | ||||
| 	} | ||||
| 	if len(h.path) == 0 { | ||||
| 		charts, err := h.utils.Glob("**/Chart.yaml") | ||||
| 		if len(charts) == 0 || err != nil { | ||||
| 			return fmt.Errorf("failed to find a helm chart file") | ||||
| 		} | ||||
| 		// use first chart which can be found | ||||
| 		h.path = charts[0] | ||||
| 	} | ||||
|  | ||||
| 	if len(h.metadata.Version) == 0 { | ||||
| 		content, err := h.utils.FileRead(h.path) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("failed to read file '%v': %w", h.path, err) | ||||
| 		} | ||||
|  | ||||
| 		err = yaml.Unmarshal(content, &h.metadata) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("helm chart content invalid '%v': %w", h.path, err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // VersioningScheme returns the relevant versioning scheme | ||||
| func (h *HelmChart) VersioningScheme() string { | ||||
| 	return "semver2" | ||||
| } | ||||
|  | ||||
| // GetVersion returns the current version of the artifact with a JSON-based build descriptor | ||||
| func (h *HelmChart) GetVersion() (string, error) { | ||||
| 	if err := h.init(); err != nil { | ||||
| 		return "", fmt.Errorf("failed to init helm chart versioning: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	return h.metadata.Version, nil | ||||
| } | ||||
|  | ||||
| // SetVersion updates the version of the artifact with a JSON-based build descriptor | ||||
| func (h *HelmChart) SetVersion(version string) error { | ||||
| 	if err := h.init(); err != nil { | ||||
| 		return fmt.Errorf("failed to init helm chart versioning: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	h.metadata.Version = version | ||||
| 	if h.updateAppVersion { | ||||
| 		h.metadata.AppVersion = version | ||||
| 	} | ||||
|  | ||||
| 	content, err := yaml.Marshal(h.metadata) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to create chart content for '%v': %w", h.path, err) | ||||
| 	} | ||||
| 	err = h.utils.FileWrite(h.path, content, 666) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to write file '%v': %w", h.path, err) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GetCoordinates returns the coordinates | ||||
| func (h *HelmChart) GetCoordinates() (Coordinates, error) { | ||||
| 	result := Coordinates{} | ||||
| 	projectVersion, err := h.GetVersion() | ||||
| 	if err != nil { | ||||
| 		return result, err | ||||
| 	} | ||||
|  | ||||
| 	result.ArtifactID = h.metadata.Name | ||||
| 	result.Version = projectVersion | ||||
| 	result.GroupID = h.metadata.Home | ||||
|  | ||||
| 	return result, nil | ||||
| } | ||||
							
								
								
									
										218
									
								
								pkg/versioning/helm_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										218
									
								
								pkg/versioning/helm_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,218 @@ | ||||
| package versioning | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/ghodss/yaml" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"helm.sh/helm/v3/pkg/chart" | ||||
| ) | ||||
|  | ||||
| func TestHelmChartInit(t *testing.T) { | ||||
| 	t.Run("success case", func(t *testing.T) { | ||||
| 		chartMetadata := chart.Metadata{Version: "1.2.3"} | ||||
| 		content, err := yaml.Marshal(chartMetadata) | ||||
| 		assert.NoError(t, err) | ||||
|  | ||||
| 		fileUtils := newVersioningMockUtils() | ||||
| 		fileUtils.AddFile("testchart/Chart.yaml", content) | ||||
|  | ||||
| 		helmChart := HelmChart{ | ||||
| 			utils: fileUtils, | ||||
| 		} | ||||
|  | ||||
| 		err = helmChart.init() | ||||
|  | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.Equal(t, "1.2.3", helmChart.metadata.Version) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("success case - with chart path", func(t *testing.T) { | ||||
| 		chartMetadata := chart.Metadata{Version: "1.2.3"} | ||||
| 		content, err := yaml.Marshal(chartMetadata) | ||||
| 		assert.NoError(t, err) | ||||
|  | ||||
| 		fileUtils := newVersioningMockUtils() | ||||
| 		fileUtils.AddFile("chart1/Chart.yaml", []byte("")) | ||||
| 		fileUtils.AddFile("chart2/Chart.yaml", content) | ||||
| 		fileUtils.AddFile("chart3/Chart.yaml", []byte("")) | ||||
|  | ||||
| 		helmChart := HelmChart{ | ||||
| 			path:  "chart2/Chart.yaml", | ||||
| 			utils: fileUtils, | ||||
| 		} | ||||
|  | ||||
| 		err = helmChart.init() | ||||
|  | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.Equal(t, "1.2.3", helmChart.metadata.Version) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("error case - init failed with missing utils", func(t *testing.T) { | ||||
| 		helmChart := HelmChart{ | ||||
| 			path: "chart2/Chart.yaml", | ||||
| 		} | ||||
|  | ||||
| 		err := helmChart.init() | ||||
| 		assert.EqualError(t, err, "no file utils provided") | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("error case - init failed with missing chart", func(t *testing.T) { | ||||
| 		fileUtils := newVersioningMockUtils() | ||||
|  | ||||
| 		helmChart := HelmChart{ | ||||
| 			utils: fileUtils, | ||||
| 		} | ||||
|  | ||||
| 		err := helmChart.init() | ||||
| 		assert.EqualError(t, err, "failed to find a helm chart file") | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("error case - failed reading file", func(t *testing.T) { | ||||
| 		fileUtils := newVersioningMockUtils() | ||||
| 		fileUtils.FileReadErrors = map[string]error{"testchart/Chart.yaml": fmt.Errorf("read error")} | ||||
|  | ||||
| 		helmChart := HelmChart{ | ||||
| 			utils: fileUtils, | ||||
| 			path:  "testchart/Chart.yaml", | ||||
| 		} | ||||
|  | ||||
| 		err := helmChart.init() | ||||
| 		assert.EqualError(t, err, "failed to read file 'testchart/Chart.yaml': read error") | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("error case - chart invalid", func(t *testing.T) { | ||||
| 		fileUtils := newVersioningMockUtils() | ||||
| 		fileUtils.AddFile("testchart/Chart.yaml", []byte("{")) | ||||
|  | ||||
| 		helmChart := HelmChart{ | ||||
| 			utils: fileUtils, | ||||
| 			path:  "testchart/Chart.yaml", | ||||
| 		} | ||||
|  | ||||
| 		err := helmChart.init() | ||||
| 		assert.Contains(t, fmt.Sprint(err), "helm chart content invalid 'testchart/Chart.yaml'") | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestHelmChartVersioningScheme(t *testing.T) { | ||||
| 	helmChart := HelmChart{} | ||||
| 	assert.Equal(t, "semver2", helmChart.VersioningScheme()) | ||||
| } | ||||
|  | ||||
| func TestHelmChartGetVersion(t *testing.T) { | ||||
| 	t.Run("success case", func(t *testing.T) { | ||||
| 		chartMetadata := chart.Metadata{Version: "1.2.3"} | ||||
| 		content, err := yaml.Marshal(chartMetadata) | ||||
| 		assert.NoError(t, err) | ||||
|  | ||||
| 		fileUtils := newVersioningMockUtils() | ||||
| 		fileUtils.AddFile("testchart/Chart.yaml", content) | ||||
|  | ||||
| 		helmChart := HelmChart{ | ||||
| 			utils: fileUtils, | ||||
| 		} | ||||
|  | ||||
| 		version, err := helmChart.GetVersion() | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.Equal(t, "1.2.3", version) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("error case - init failed", func(t *testing.T) { | ||||
| 		fileUtils := newVersioningMockUtils() | ||||
|  | ||||
| 		helmChart := HelmChart{ | ||||
| 			utils: fileUtils, | ||||
| 		} | ||||
|  | ||||
| 		_, err := helmChart.GetVersion() | ||||
| 		assert.Contains(t, fmt.Sprint(err), "failed to init helm chart versioning:") | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestHelmChartSetVersion(t *testing.T) { | ||||
| 	t.Run("success case", func(t *testing.T) { | ||||
| 		fileUtils := newVersioningMockUtils() | ||||
|  | ||||
| 		helmChart := HelmChart{ | ||||
| 			utils:    fileUtils, | ||||
| 			path:     "testchart/Chart.yaml", | ||||
| 			metadata: chart.Metadata{Version: "1.2.3"}, | ||||
| 		} | ||||
|  | ||||
| 		err := helmChart.SetVersion("1.2.4") | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.Equal(t, "1.2.4", helmChart.metadata.Version) | ||||
|  | ||||
| 		fileContent, err := fileUtils.FileRead("testchart/Chart.yaml") | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.Contains(t, string(fileContent), "version: 1.2.4") | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("success case - update app version", func(t *testing.T) { | ||||
| 		fileUtils := newVersioningMockUtils() | ||||
|  | ||||
| 		helmChart := HelmChart{ | ||||
| 			utils:            fileUtils, | ||||
| 			path:             "testchart/Chart.yaml", | ||||
| 			metadata:         chart.Metadata{Version: "1.2.3"}, | ||||
| 			updateAppVersion: true, | ||||
| 		} | ||||
|  | ||||
| 		err := helmChart.SetVersion("1.2.4") | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.Equal(t, "1.2.4", helmChart.metadata.AppVersion) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("error case - init failed with missing chart", func(t *testing.T) { | ||||
| 		fileUtils := newVersioningMockUtils() | ||||
|  | ||||
| 		helmChart := HelmChart{ | ||||
| 			utils: fileUtils, | ||||
| 		} | ||||
|  | ||||
| 		err := helmChart.SetVersion("1.2.4") | ||||
| 		assert.Contains(t, fmt.Sprint(err), "failed to init helm chart versioning:") | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("error case - failed to write chart", func(t *testing.T) { | ||||
| 		fileUtils := newVersioningMockUtils() | ||||
| 		fileUtils.FileWriteError = fmt.Errorf("write error") | ||||
|  | ||||
| 		helmChart := HelmChart{ | ||||
| 			path:     "testchart/Chart.yaml", | ||||
| 			utils:    fileUtils, | ||||
| 			metadata: chart.Metadata{Version: "1.2.3"}, | ||||
| 		} | ||||
|  | ||||
| 		err := helmChart.SetVersion("1.2.4") | ||||
| 		assert.EqualError(t, err, "failed to write file 'testchart/Chart.yaml': write error") | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestHelmChartGetCoordinates(t *testing.T) { | ||||
| 	t.Run("success case", func(t *testing.T) { | ||||
| 		fileUtils := newVersioningMockUtils() | ||||
| 		helmChart := HelmChart{ | ||||
| 			utils:    fileUtils, | ||||
| 			path:     "testchart/Chart.yaml", | ||||
| 			metadata: chart.Metadata{Version: "1.2.3", Name: "myChart", Home: "myHome"}, | ||||
| 		} | ||||
|  | ||||
| 		coordinates, err := helmChart.GetCoordinates() | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.Equal(t, Coordinates{GroupID: "myHome", ArtifactID: "myChart", Version: "1.2.3"}, coordinates) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("error case - version retrieval failed", func(t *testing.T) { | ||||
| 		fileUtils := newVersioningMockUtils() | ||||
|  | ||||
| 		helmChart := HelmChart{ | ||||
| 			utils: fileUtils, | ||||
| 		} | ||||
|  | ||||
| 		_, err := helmChart.GetCoordinates() | ||||
| 		assert.Contains(t, fmt.Sprint(err), "failed to init helm chart versioning:") | ||||
| 	}) | ||||
| } | ||||
| @@ -27,14 +27,15 @@ type Artifact interface { | ||||
|  | ||||
| // Options define build tool specific settings in order to properly retrieve e.g. the version / coordinates of an artifact | ||||
| type Options struct { | ||||
| 	ProjectSettingsFile string | ||||
| 	DockerImage         string | ||||
| 	GlobalSettingsFile  string | ||||
| 	M2Path              string | ||||
| 	VersionSource       string | ||||
| 	VersionSection      string | ||||
| 	VersionField        string | ||||
| 	VersioningScheme    string | ||||
| 	ProjectSettingsFile  string | ||||
| 	DockerImage          string | ||||
| 	GlobalSettingsFile   string | ||||
| 	M2Path               string | ||||
| 	VersionSource        string | ||||
| 	VersionSection       string | ||||
| 	VersionField         string | ||||
| 	VersioningScheme     string | ||||
| 	HelmUpdateAppVersion bool | ||||
| } | ||||
|  | ||||
| // Utils defines the versioning operations for various build tools | ||||
| @@ -106,6 +107,12 @@ func GetArtifact(buildTool, buildDescriptorFilePath string, opts *Options, utils | ||||
| 		default: | ||||
| 			artifact = &Versionfile{path: buildDescriptorFilePath} | ||||
| 		} | ||||
| 	case "helm": | ||||
| 		artifact = &HelmChart{ | ||||
| 			path:             buildDescriptorFilePath, | ||||
| 			utils:            utils, | ||||
| 			updateAppVersion: opts.HelmUpdateAppVersion, | ||||
| 		} | ||||
| 	case "maven": | ||||
| 		if len(buildDescriptorFilePath) == 0 { | ||||
| 			buildDescriptorFilePath = "pom.xml" | ||||
|   | ||||
| @@ -1,11 +1,31 @@ | ||||
| package versioning | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/SAP/jenkins-library/pkg/mock" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| type versioningMockUtils struct { | ||||
| 	*mock.ExecMockRunner | ||||
| 	*mock.FilesMock | ||||
| } | ||||
|  | ||||
| func newVersioningMockUtils() *versioningMockUtils { | ||||
| 	utils := versioningMockUtils{ | ||||
| 		ExecMockRunner: &mock.ExecMockRunner{}, | ||||
| 		FilesMock:      &mock.FilesMock{}, | ||||
| 	} | ||||
| 	return &utils | ||||
| } | ||||
|  | ||||
| func (v *versioningMockUtils) DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error { | ||||
| 	// so far no dedicated logic required for testing | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func TestGetArtifact(t *testing.T) { | ||||
| 	t.Run("custom", func(t *testing.T) { | ||||
| 		custom, err := GetArtifact("custom", "test.ini", &Options{VersionField: "theversion", VersionSection: "test"}, nil) | ||||
| @@ -76,6 +96,17 @@ func TestGetArtifact(t *testing.T) { | ||||
| 		assert.Equal(t, "semver2", gradle.VersioningScheme()) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("helm", func(t *testing.T) { | ||||
| 		helm, err := GetArtifact("helm", "testchart/Chart.yaml", &Options{}, nil) | ||||
|  | ||||
| 		assert.NoError(t, err) | ||||
|  | ||||
| 		theType, ok := helm.(*HelmChart) | ||||
| 		assert.True(t, ok) | ||||
| 		assert.Equal(t, "testchart/Chart.yaml", theType.path) | ||||
| 		assert.Equal(t, "semver2", helm.VersioningScheme()) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("maven", func(t *testing.T) { | ||||
| 		opts := Options{ | ||||
| 			ProjectSettingsFile: "projectsettings.xml", | ||||
|   | ||||
| @@ -82,6 +82,53 @@ spec: | ||||
|           - name: gitCredentialsId | ||||
|             deprecated: true | ||||
|     params: | ||||
|       - name: additionalTargetTools | ||||
|         type: "[]string" | ||||
|         description: Additional buildTool targets where descriptors need to be updated besides the main `buildTool`. | ||||
|         longDescription: | | ||||
|           **Only for versioning types `cloud` and `cloud_noTag`.** This parameter allows you to propagate the version to other build-tool specific descriptors. | ||||
|           If the parameter [`additionalTargetDescriptors`](#additionaltargetdescriptors) is not defined the default build descriptors are used. | ||||
|  | ||||
|           One example is to propagate the version into a helm chart. | ||||
|           This can be achieved like | ||||
|  | ||||
|           ``` | ||||
|           steps: | ||||
|             artifactPrepareVersion: | ||||
|               additionalTargetTools: | ||||
|                 - helm | ||||
|           ``` | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|         possibleValues: | ||||
|           - custom | ||||
|           - docker | ||||
|           - dub | ||||
|           - golang | ||||
|           - gradle | ||||
|           - helm | ||||
|           - maven | ||||
|           - mta | ||||
|           - npm | ||||
|           - pip | ||||
|           - sbt | ||||
|           - yarn | ||||
|       - name: additionalTargetDescriptors | ||||
|         type: "[]string" | ||||
|         description: Defines patterns for build descriptors which should be used for option [`additionalTargetTools`](additionaltargettools). | ||||
|         longDescription: | | ||||
|           **Only for versioning types `cloud` and `cloud_noTag`.** In case default build descriptors cannot be used for [`additionalTargetTools`](additionaltargettools) this parameter allows to define a dedicated search pattern per build tool. | ||||
|           For each entry in [`additionalTargetTools`](additionaltargettools) a dedicated entry has to be maintained. | ||||
|  | ||||
|           You can use either a file name or a glob pattern like `**/package.json`. | ||||
|  | ||||
|           For `helm` the default value is `**/Chart.yaml`, thus typically no adaptions are required. | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|       - name: buildTool | ||||
|         type: string | ||||
|         description: Defines the tool which is used for building the artifact. | ||||
| @@ -97,6 +144,7 @@ spec: | ||||
|           - dub | ||||
|           - golang | ||||
|           - gradle | ||||
|           - helm | ||||
|           - maven | ||||
|           - mta | ||||
|           - npm | ||||
|   | ||||
		Reference in New Issue
	
	Block a user