1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-01-30 05:59:39 +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:
Oliver Nocon 2022-03-15 09:08:24 +01:00 committed by GitHub
parent 6651eaf6c8
commit 7ec512cb9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 630 additions and 44 deletions

View File

@ -124,11 +124,9 @@ func runArtifactPrepareVersion(config *artifactPrepareVersionOptions, telemetryD
} }
} }
versioningType := config.VersioningType
// support former groovy versioning template and translate into new options // support former groovy versioning template and translate into new options
if len(config.VersioningTemplate) > 0 { if len(config.VersioningTemplate) > 0 {
versioningType, _, config.IncludeCommitID = templateCompatibility(config.VersioningTemplate) config.VersioningType, _, config.IncludeCommitID = templateCompatibility(config.VersioningTemplate)
} }
version, err := artifact.GetVersion() version, err := artifact.GetVersion()
@ -148,18 +146,12 @@ func runArtifactPrepareVersion(config *artifactPrepareVersionOptions, telemetryD
commonPipelineEnvironment.git.headCommitID = gitCommitID commonPipelineEnvironment.git.headCommitID = gitCommitID
newVersion := version newVersion := version
if versioningType == "cloud" || versioningType == "cloud_noTag" { if config.VersioningType == "cloud" || config.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())
}
now := time.Now() 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 { if err != nil {
return errors.Wrap(err, "failed to calculate new version") return err
} }
worktree, err := getWorktree(repository) 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) // commit changes and push to repository (including new version tag)
gitCommitID, err = pushChanges(config, newVersion, repository, worktree, now) gitCommitID, err = pushChanges(config, newVersion, repository, worktree, now)
if err != nil { if err != nil {
@ -450,3 +448,71 @@ func templateCompatibility(groovyTemplate string) (versioningType string, useTim
return 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
}

View File

@ -18,25 +18,27 @@ import (
) )
type artifactPrepareVersionOptions struct { type artifactPrepareVersionOptions struct {
BuildTool string `json:"buildTool,omitempty" validate:"possible-values=custom docker dub golang gradle maven mta npm pip sbt yarn"` AdditionalTargetTools []string `json:"additionalTargetTools,omitempty" validate:"possible-values=custom docker dub golang gradle helm maven mta npm pip sbt yarn"`
CommitUserName string `json:"commitUserName,omitempty"` AdditionalTargetDescriptors []string `json:"additionalTargetDescriptors,omitempty"`
CustomVersionField string `json:"customVersionField,omitempty"` BuildTool string `json:"buildTool,omitempty" validate:"possible-values=custom docker dub golang gradle helm maven mta npm pip sbt yarn"`
CustomVersionSection string `json:"customVersionSection,omitempty"` CommitUserName string `json:"commitUserName,omitempty"`
CustomVersioningScheme string `json:"customVersioningScheme,omitempty" validate:"possible-values=docker maven pep440 semver2"` CustomVersionField string `json:"customVersionField,omitempty"`
DockerVersionSource string `json:"dockerVersionSource,omitempty"` CustomVersionSection string `json:"customVersionSection,omitempty"`
FetchCoordinates bool `json:"fetchCoordinates,omitempty"` CustomVersioningScheme string `json:"customVersioningScheme,omitempty" validate:"possible-values=docker maven pep440 semver2"`
FilePath string `json:"filePath,omitempty"` DockerVersionSource string `json:"dockerVersionSource,omitempty"`
GlobalSettingsFile string `json:"globalSettingsFile,omitempty"` FetchCoordinates bool `json:"fetchCoordinates,omitempty"`
IncludeCommitID bool `json:"includeCommitId,omitempty"` FilePath string `json:"filePath,omitempty"`
M2Path string `json:"m2Path,omitempty"` GlobalSettingsFile string `json:"globalSettingsFile,omitempty"`
Password string `json:"password,omitempty"` IncludeCommitID bool `json:"includeCommitId,omitempty"`
ProjectSettingsFile string `json:"projectSettingsFile,omitempty"` M2Path string `json:"m2Path,omitempty"`
ShortCommitID bool `json:"shortCommitId,omitempty"` Password string `json:"password,omitempty"`
TagPrefix string `json:"tagPrefix,omitempty"` ProjectSettingsFile string `json:"projectSettingsFile,omitempty"`
UnixTimestamp bool `json:"unixTimestamp,omitempty"` ShortCommitID bool `json:"shortCommitId,omitempty"`
Username string `json:"username,omitempty"` TagPrefix string `json:"tagPrefix,omitempty"`
VersioningTemplate string `json:"versioningTemplate,omitempty"` UnixTimestamp bool `json:"unixTimestamp,omitempty"`
VersioningType string `json:"versioningType,omitempty" validate:"possible-values=cloud cloud_noTag library"` 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 { type artifactPrepareVersionCommonPipelineEnvironment struct {
@ -236,6 +238,8 @@ Define ` + "`" + `buildTool: custom` + "`" + `, ` + "`" + `filePath: <path to yo
} }
func addArtifactPrepareVersionFlags(cmd *cobra.Command, stepConfig *artifactPrepareVersionOptions) { 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.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.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.") 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}}}, {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{ 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", Name: "buildTool",
ResourceRef: []config.ResourceReference{}, ResourceRef: []config.ResourceReference{},

View File

@ -2,19 +2,23 @@ package cmd
import ( import (
"fmt" "fmt"
"net/http"
"testing" "testing"
"time" "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/versioning"
"github.com/SAP/jenkins-library/pkg/telemetry" "github.com/ghodss/yaml"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"helm.sh/helm/v3/pkg/chart"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
gitConfig "github.com/go-git/go-git/v5/config" 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"
"github.com/go-git/go-git/v5/plumbing/object" "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" "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 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) { func TestRunArtifactPrepareVersion(t *testing.T) {
t.Run("success case - cloud", func(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, &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, "1.2.3", repo.tag)
assert.Equal(t, "428ecf70bc22df0ba3dcf194b5ce53e769abab07", repo.tagHash.String()) 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) { 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)) 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
View 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
View 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:")
})
}

View File

@ -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 // Options define build tool specific settings in order to properly retrieve e.g. the version / coordinates of an artifact
type Options struct { type Options struct {
ProjectSettingsFile string ProjectSettingsFile string
DockerImage string DockerImage string
GlobalSettingsFile string GlobalSettingsFile string
M2Path string M2Path string
VersionSource string VersionSource string
VersionSection string VersionSection string
VersionField string VersionField string
VersioningScheme string VersioningScheme string
HelmUpdateAppVersion bool
} }
// Utils defines the versioning operations for various build tools // Utils defines the versioning operations for various build tools
@ -106,6 +107,12 @@ func GetArtifact(buildTool, buildDescriptorFilePath string, opts *Options, utils
default: default:
artifact = &Versionfile{path: buildDescriptorFilePath} artifact = &Versionfile{path: buildDescriptorFilePath}
} }
case "helm":
artifact = &HelmChart{
path: buildDescriptorFilePath,
utils: utils,
updateAppVersion: opts.HelmUpdateAppVersion,
}
case "maven": case "maven":
if len(buildDescriptorFilePath) == 0 { if len(buildDescriptorFilePath) == 0 {
buildDescriptorFilePath = "pom.xml" buildDescriptorFilePath = "pom.xml"

View File

@ -1,11 +1,31 @@
package versioning package versioning
import ( import (
"net/http"
"testing" "testing"
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/stretchr/testify/assert" "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) { func TestGetArtifact(t *testing.T) {
t.Run("custom", func(t *testing.T) { t.Run("custom", func(t *testing.T) {
custom, err := GetArtifact("custom", "test.ini", &Options{VersionField: "theversion", VersionSection: "test"}, nil) 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()) 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) { t.Run("maven", func(t *testing.T) {
opts := Options{ opts := Options{
ProjectSettingsFile: "projectsettings.xml", ProjectSettingsFile: "projectsettings.xml",

View File

@ -82,6 +82,53 @@ spec:
- name: gitCredentialsId - name: gitCredentialsId
deprecated: true deprecated: true
params: 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 - name: buildTool
type: string type: string
description: Defines the tool which is used for building the artifact. description: Defines the tool which is used for building the artifact.
@ -97,6 +144,7 @@ spec:
- dub - dub
- golang - golang
- gradle - gradle
- helm
- maven - maven
- mta - mta
- npm - npm