1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-10-30 23:57:50 +02:00

artifactPrepareVersioning: support more buildTools (#1367)

* artifactPrepareVersioning: support more buildTools
This commit is contained in:
Oliver Nocon
2020-04-15 13:12:43 +02:00
committed by GitHub
parent 1068de9582
commit b9781ce50c
22 changed files with 1318 additions and 219 deletions

View File

@@ -67,12 +67,16 @@ func runArtifactPrepareVersion(config *artifactPrepareVersionOptions, telemetryD
telemetryData.Custom1Label = "buildTool"
telemetryData.Custom1 = config.BuildTool
telemetryData.Custom2Label = "filePath"
telemetryData.Custom2 = config.FilePath
// Options for artifact
artifactOpts := versioning.Options{
GlobalSettingsFile: config.GlobalSettingsFile,
M2Path: config.M2Path,
ProjectSettingsFile: config.ProjectSettingsFile,
VersionField: config.CustomversionField,
VersionSection: config.CustomVersionSection,
}
var err error

View File

@@ -16,19 +16,21 @@ import (
)
type artifactPrepareVersionOptions struct {
BuildTool string `json:"buildTool,omitempty"`
CommitUserName string `json:"commitUserName,omitempty"`
DockerVersionSource string `json:"dockerVersionSource,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"`
TagPrefix string `json:"tagPrefix,omitempty"`
Username string `json:"username,omitempty"`
VersioningTemplate string `json:"versioningTemplate,omitempty"`
VersioningType string `json:"versioningType,omitempty"`
BuildTool string `json:"buildTool,omitempty"`
CommitUserName string `json:"commitUserName,omitempty"`
CustomversionField string `json:"customversionField,omitempty"`
CustomVersionSection string `json:"customVersionSection,omitempty"`
DockerVersionSource string `json:"dockerVersionSource,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"`
TagPrefix string `json:"tagPrefix,omitempty"`
Username string `json:"username,omitempty"`
VersioningTemplate string `json:"versioningTemplate,omitempty"`
VersioningType string `json:"versioningType,omitempty"`
}
type artifactPrepareVersionCommonPipelineEnvironment struct {
@@ -107,7 +109,33 @@ The version is then either manually set by the team in the course of the develop
Unlike for the _Continuous Deloyment_ pattern descibed above, in this case there is no dedicated tagging required for the build process since the version is already available in the repository.
Configuration of this pattern is done via ` + "`" + `versioningType: library` + "`" + `.`,
Configuration of this pattern is done via ` + "`" + `versioningType: library` + "`" + `.
### Support of additional build tools
Besides the ` + "`" + `buildTools` + "`" + ` provided out of the box (like ` + "`" + `maven` + "`" + `, ` + "`" + `mta` + "`" + `, ` + "`" + `npm` + "`" + `, ...) it is possible to set ` + "`" + `buildTool: custom` + "`" + `.
This allows you to provide automatic versioning for tools using a:
#### file with the version as only content:
Define ` + "`" + `buildTool: custom` + "`" + ` as well as ` + "`" + `filePath: <path to your file>` + "`" + `
**Please note:** ` + "`" + `<path to your file>` + "`" + ` need to point either to a ` + "`" + `*.txt` + "`" + ` file or to a file without extension.
#### ` + "`" + `ini` + "`" + ` file containing the version:
Define ` + "`" + `buildTool: custom` + "`" + `, ` + "`" + `filePath: <path to your ini-file>` + "`" + ` as well as parameters ` + "`" + `versionSection` + "`" + ` and ` + "`" + `versionSource` + "`" + ` to point to the version location (section & parameter name) within the file.
**Please note:** ` + "`" + `<path to your file>` + "`" + ` need to point either to a ` + "`" + `*.cfg` + "`" + ` or a ` + "`" + `*.ini` + "`" + ` file.
#### ` + "`" + `json` + "`" + ` file containing the version:
Define ` + "`" + `buildTool: custom` + "`" + `, ` + "`" + `filePath: <path to your *.json file` + "`" + ` as well as parameter ` + "`" + `versionSource` + "`" + ` to point to the parameter containing the version.
#### ` + "`" + `yaml` + "`" + ` file containing the version
Define ` + "`" + `buildTool: custom` + "`" + `, ` + "`" + `filePath: <path to your *.yml/*.yaml file` + "`" + ` as well as parameter ` + "`" + `versionSource` + "`" + ` to point to the parameter containing the version.`,
PreRunE: func(cmd *cobra.Command, args []string) error {
startTime = time.Now()
log.SetStepName("artifactPrepareVersion")
@@ -135,9 +163,11 @@ Configuration of this pattern is done via ` + "`" + `versioningType: library` +
}
func addArtifactPrepareVersionFlags(cmd *cobra.Command, stepConfig *artifactPrepareVersionOptions) {
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. Supports `custom`, `dub`, `golang`, `maven`, `mta`, `npm`, `pip`, `sbt`.")
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.DockerVersionSource, "dockerVersionSource", os.Getenv("PIPER_dockerVersionSource"), "For Docker only: Specifies the source to be used for for generating the automatic version. * This can either be the version of the base image - as retrieved from the `FROM` statement within the Dockerfile, e.g. `FROM jenkins:2.46.2` * Alternatively the name of an environment variable defined in the Docker image can be used which contains the version number, e.g. `ENV MY_VERSION 1.2.3`.")
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.CustomVersionSection, "customVersionSection", os.Getenv("PIPER_customVersionSection"), "For `buildTool: custom`: Defines the section for version retrieval in vase a *.ini/*.cfg file is used.")
cmd.Flags().StringVar(&stepConfig.DockerVersionSource, "dockerVersionSource", os.Getenv("PIPER_dockerVersionSource"), "For `buildTool: docker`: Defines the source of the version. Can be `FROM`, any supported _buildTool_ or an environment variable name.")
cmd.Flags().StringVar(&stepConfig.FilePath, "filePath", os.Getenv("PIPER_filePath"), "Defines a custom path to the descriptor file. Build tool specific defaults are used (e.g. `maven: pom.xml`, `npm: package.json`, `mta: mta.yaml`).")
cmd.Flags().StringVar(&stepConfig.GlobalSettingsFile, "globalSettingsFile", os.Getenv("PIPER_globalSettingsFile"), "Maven only - Path to the mvn settings file that should be used as global settings file.")
cmd.Flags().BoolVar(&stepConfig.IncludeCommitID, "includeCommitId", true, "Defines if the automatically generated version (`versioningType: cloud`) should include the commit id hash.")
@@ -178,6 +208,22 @@ func artifactPrepareVersionMetadata() config.StepData {
Mandatory: false,
Aliases: []config.Alias{{Name: "gitUserName"}},
},
{
Name: "customversionField",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "customVersionSection",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "dockerVersionSource",
ResourceRef: []config.ResourceReference{},

View File

@@ -205,7 +205,7 @@ func TestRunArtifactPrepareVersion(t *testing.T) {
assert.Contains(t, cpe.artifactVersion, "1.2.3")
assert.Equal(t, worktree.commitHash.String(), cpe.git.commitID)
assert.Equal(t, telemetry.CustomData{Custom1Label: "buildTool", Custom1: "maven"}, telemetryData)
assert.Equal(t, telemetry.CustomData{Custom1Label: "buildTool", Custom1: "maven", Custom2Label: "filePath", Custom2: ""}, telemetryData)
})
t.Run("success case - cloud_noTag", func(t *testing.T) {

2
go.mod
View File

@@ -15,10 +15,12 @@ require (
github.com/google/uuid v1.1.1
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.4.2
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/spf13/cobra v0.0.5
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.4.0
github.com/testcontainers/testcontainers-go v0.2.0
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
gopkg.in/ini.v1 v1.55.0
gopkg.in/yaml.v2 v2.2.4
)

11
go.sum
View File

@@ -191,6 +191,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
@@ -223,6 +225,8 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
@@ -320,6 +324,10 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
@@ -469,6 +477,7 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
@@ -513,6 +522,8 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.55.0 h1:E8yzL5unfpW3M6fz/eB7Cb5MQAYSZ7GKo4Qth+N2sgQ=
gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=

130
pkg/versioning/docker.go Normal file
View File

@@ -0,0 +1,130 @@
package versioning
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/pkg/errors"
)
// Docker defines an artifact based on a Dockerfile
type Docker struct {
artifact Artifact
content []byte
execRunner mavenExecRunner
options *Options
path string
versionSource string
readFile func(string) ([]byte, error)
writeFile func(string, []byte, os.FileMode) error
}
func (d *Docker) init() {
if d.readFile == nil {
d.readFile = ioutil.ReadFile
}
if d.writeFile == nil {
d.writeFile = ioutil.WriteFile
}
}
func (d *Docker) initDockerfile() {
if len(d.path) == 0 {
d.path = "Dockerfile"
}
}
// VersioningScheme returns the relevant versioning scheme
func (d *Docker) VersioningScheme() string {
return "maven"
}
// GetVersion returns the current version of the artifact
func (d *Docker) GetVersion() (string, error) {
d.init()
var err error
switch d.versionSource {
case "FROM":
var err error
d.initDockerfile()
d.content, err = d.readFile(d.path)
if err != nil {
return "", errors.Wrapf(err, "failed to read file '%v'", d.path)
}
version := d.versionFromBaseImageTag()
if len(version) == 0 {
return "", fmt.Errorf("no version information available in FROM statement")
}
return version, nil
case "custom", "dub", "golang", "maven", "mta", "npm", "pip", "sbt":
d.artifact, err = GetArtifact(d.versionSource, d.path, d.options, d.execRunner)
if err != nil {
return "", err
}
return d.artifact.GetVersion()
default:
d.initDockerfile()
d.content, err = d.readFile(d.path)
if err != nil {
return "", errors.Wrapf(err, "failed to read file '%v'", d.path)
}
version := d.versionFromEnv(d.versionSource)
if len(version) == 0 {
return "", fmt.Errorf("no version information available in ENV '%v'", d.versionSource)
}
return version, nil
}
}
// SetVersion updates the version of the artifact
func (d *Docker) SetVersion(version string) error {
d.init()
dir := ""
if d.artifact != nil {
err := d.artifact.SetVersion(version)
if err != nil {
return err
}
dir = filepath.Dir(d.path)
}
err := d.writeFile(filepath.Join(dir, "VERSION"), []byte(version), 0700)
if err != nil {
return errors.Wrap(err, "failed to write file 'VERSION'")
}
return nil
}
func (d *Docker) versionFromEnv(env string) string {
lines := strings.Split(string(d.content), "\n")
for _, line := range lines {
if strings.HasPrefix(line, "ENV") && strings.Fields(line)[1] == env {
return strings.Fields(line)[2]
}
}
return ""
}
func (d *Docker) versionFromBaseImageTag() string {
lines := strings.Split(string(d.content), "\n")
for _, line := range lines {
if strings.HasPrefix(line, "FROM") {
imageParts := strings.Split(line, ":")
partsCount := len(imageParts)
if partsCount == 1 {
return ""
}
version := imageParts[partsCount-1]
return strings.TrimSpace(version)
}
}
return ""
}

View File

@@ -0,0 +1,160 @@
package versioning
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestDockerGetVersion(t *testing.T) {
t.Run("success case - FROM", func(t *testing.T) {
docker := Docker{
readFile: func(filename string) ([]byte, error) { return []byte("FROM test:1.2.3"), nil },
versionSource: "FROM",
}
version, err := docker.GetVersion()
assert.NoError(t, err)
assert.Equal(t, "1.2.3", version)
})
t.Run("error case - FROM failed", func(t *testing.T) {
docker := Docker{
readFile: func(filename string) ([]byte, error) { return []byte("FROM test"), nil },
versionSource: "FROM",
}
_, err := docker.GetVersion()
assert.EqualError(t, err, "no version information available in FROM statement")
})
t.Run("error case - FROM read error", func(t *testing.T) {
docker := Docker{
readFile: func(filename string) ([]byte, error) { return []byte{}, fmt.Errorf("read error") },
versionSource: "FROM",
}
_, err := docker.GetVersion()
assert.EqualError(t, err, "failed to read file 'Dockerfile': read error")
})
t.Run("success case - buildTool", func(t *testing.T) {
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal("Failed to create temporary directory")
}
// clean up tmp dir
defer os.RemoveAll(dir)
filePath := filepath.Join(dir, "package.json")
err = ioutil.WriteFile(filePath, []byte(`{"version": "1.2.3"}`), 0700)
if err != nil {
t.Fatal("Failed to create test file")
}
docker := Docker{
path: filePath,
versionSource: "npm",
}
version, err := docker.GetVersion()
assert.NoError(t, err)
assert.Equal(t, "1.2.3", version)
})
t.Run("success case - ENV", func(t *testing.T) {
docker := Docker{
readFile: func(filename string) ([]byte, error) { return []byte("FROM test:latest\n\nENV VERSION_ENV 1.2.3"), nil },
versionSource: "VERSION_ENV",
}
version, err := docker.GetVersion()
assert.NoError(t, err)
assert.Equal(t, "1.2.3", version)
})
t.Run("error case - ENV failed", func(t *testing.T) {
docker := Docker{
readFile: func(filename string) ([]byte, error) { return []byte("FROM test:latest\n\nENV VERSION_ENV 1.2.3"), nil },
versionSource: "NOT_FOUND",
}
_, err := docker.GetVersion()
assert.EqualError(t, err, "no version information available in ENV 'NOT_FOUND'")
})
t.Run("error case - ENV read error", func(t *testing.T) {
docker := Docker{
readFile: func(filename string) ([]byte, error) { return []byte{}, fmt.Errorf("read error") },
versionSource: "VERSION_ENV",
}
_, err := docker.GetVersion()
assert.EqualError(t, err, "failed to read file 'Dockerfile': read error")
})
}
func TestDockerSetVersion(t *testing.T) {
t.Run("success case", func(t *testing.T) {
var content []byte
docker := Docker{
readFile: func(filename string) ([]byte, error) { return []byte("FROM test:1.2.3"), nil },
writeFile: func(filename string, filecontent []byte, mode os.FileMode) error { content = filecontent; return nil },
versionSource: "FROM",
}
err := docker.SetVersion("1.2.4")
assert.NoError(t, err)
assert.Contains(t, string(content), "1.2.4")
})
t.Run("error case", func(t *testing.T) {
docker := Docker{
readFile: func(filename string) ([]byte, error) { return []byte("FROM test:1.2.3"), nil },
writeFile: func(filename string, filecontent []byte, mode os.FileMode) error { return fmt.Errorf("write error") },
versionSource: "FROM",
}
err := docker.SetVersion("1.2.4")
assert.EqualError(t, err, "failed to write file 'VERSION': write error")
})
t.Run("success case - buildTool", func(t *testing.T) {
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal("Failed to create temporary directory")
}
// clean up tmp dir
defer os.RemoveAll(dir)
filePath := filepath.Join(dir, "package.json")
err = ioutil.WriteFile(filePath, []byte(`{"version": "1.2.3"}`), 0700)
if err != nil {
t.Fatal("Failed to create test file")
}
docker := Docker{
path: filePath,
versionSource: "npm",
}
_, err = docker.GetVersion()
assert.NoError(t, err)
err = docker.SetVersion("1.2.4")
assert.NoError(t, err)
packageJSON, err := ioutil.ReadFile(filePath)
assert.Contains(t, string(packageJSON), `"version": "1.2.4"`)
versionContent, err := ioutil.ReadFile(filepath.Join(dir, "VERSION"))
assert.Equal(t, "1.2.4", string(versionContent))
})
}
func TestVersionFromBaseImageTag(t *testing.T) {
tt := []struct {
docker *Docker
expected string
}{
{docker: &Docker{content: []byte("")}, expected: ""},
{docker: &Docker{content: []byte("FROM test")}, expected: ""},
//{docker: &Docker{content: []byte("FROM test:latest")}, expected: ""},
{docker: &Docker{content: []byte("FROM test:1.2.3")}, expected: "1.2.3"},
{docker: &Docker{content: []byte("#COMMENT\nFROM test:1.2.3")}, expected: "1.2.3"},
//{docker: &Docker{content: []byte("FROM my.registry:55555/test")}, expected: ""},
//{docker: &Docker{content: []byte("FROM my.registry:55555/test:latest")}, expected: ""},
{docker: &Docker{content: []byte("FROM my.registry:55555/test:1.2.3")}, expected: "1.2.3"},
}
for _, test := range tt {
assert.Equal(t, test.expected, test.docker.versionFromBaseImageTag())
}
}

83
pkg/versioning/inifile.go Normal file
View File

@@ -0,0 +1,83 @@
package versioning
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"github.com/pkg/errors"
"gopkg.in/ini.v1"
)
// INIfile defines an artifact using a json file for versioning
type INIfile struct {
path string
content *ini.File
versionSection string
versionField string
readFile func(string) ([]byte, error)
writeFile func(string, []byte, os.FileMode) error
}
func (i *INIfile) init() error {
if len(i.versionField) == 0 {
i.versionField = "version"
}
if i.readFile == nil {
i.readFile = ioutil.ReadFile
}
if i.writeFile == nil {
i.writeFile = ioutil.WriteFile
}
if i.content == nil {
conf, err := i.readFile(i.path)
if err != nil {
return errors.Wrapf(err, "failed to read file '%v'", i.path)
}
i.content, err = ini.Load(conf)
if err != nil {
return errors.Wrapf(err, "failed to load content from file '%v'", i.path)
}
}
return nil
}
// VersioningScheme returns the relevant versioning scheme
func (i *INIfile) VersioningScheme() string {
return "semver2"
}
// GetVersion returns the current version of the artifact with a ini-file-based build descriptor
func (i *INIfile) GetVersion() (string, error) {
if i.content == nil {
err := i.init()
if err != nil {
return "", err
}
}
section := i.content.Section(i.versionSection)
if section.HasKey(i.versionField) {
return section.Key(i.versionField).String(), nil
}
return "", fmt.Errorf("field '%v' not found in section '%v'", i.versionField, i.versionSection)
}
// SetVersion updates the version of the artifact with a ini-file-based build descriptor
func (i *INIfile) SetVersion(version string) error {
if i.content == nil {
err := i.init()
if err != nil {
return err
}
}
section := i.content.Section(i.versionSection)
section.Key(i.versionField).SetValue(version)
var buf bytes.Buffer
i.content.WriteTo(&buf)
err := i.writeFile(i.path, buf.Bytes(), 0700)
if err != nil {
return errors.Wrapf(err, "failed to write file '%v'", i.path)
}
return nil
}

View File

@@ -0,0 +1,91 @@
package versioning
import (
"fmt"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestINIfileGetVersion(t *testing.T) {
t.Run("success case", func(t *testing.T) {
inifile := INIfile{
path: "my.cfg",
versionSection: "test",
readFile: func(filename string) ([]byte, error) { return []byte("[test]\nversion = 1.2.3 "), nil },
}
version, err := inifile.GetVersion()
assert.NoError(t, err)
assert.Equal(t, "1.2.3", version)
})
t.Run("error case - read error", func(t *testing.T) {
inifile := INIfile{
path: "my.cfg",
readFile: func(filename string) ([]byte, error) { return []byte{}, fmt.Errorf("read error") },
}
_, err := inifile.GetVersion()
assert.EqualError(t, err, "failed to read file 'my.cfg': read error")
})
t.Run("error case - load error", func(t *testing.T) {
inifile := INIfile{
path: "my.cfg",
readFile: func(filename string) ([]byte, error) { return []byte("1.2.3"), nil },
}
_, err := inifile.GetVersion()
assert.EqualError(t, err, "failed to load content from file 'my.cfg': key-value delimiter not found: 1.2.3")
})
t.Run("error case - field not found", func(t *testing.T) {
inifile := INIfile{
path: "my.cfg",
readFile: func(filename string) ([]byte, error) { return []byte("theversion = 1.2.3"), nil },
}
_, err := inifile.GetVersion()
assert.EqualError(t, err, "field 'version' not found in section ''")
})
}
func TestINIfileSetVersion(t *testing.T) {
t.Run("success case - flat", func(t *testing.T) {
var content []byte
inifile := INIfile{
path: "my.cfg",
versionField: "theversion",
readFile: func(filename string) ([]byte, error) { return []byte("theversion = 1.2.3"), nil },
writeFile: func(filename string, filecontent []byte, mode os.FileMode) error { content = filecontent; return nil },
}
err := inifile.SetVersion("1.2.4")
assert.NoError(t, err)
assert.Contains(t, string(content), "theversion = 1.2.4")
})
t.Run("success case - section", func(t *testing.T) {
var content []byte
inifile := INIfile{
path: "my.cfg",
versionField: "theversion",
versionSection: "test",
readFile: func(filename string) ([]byte, error) { return []byte("[test]\ntheversion = 1.2.3"), nil },
writeFile: func(filename string, filecontent []byte, mode os.FileMode) error { content = filecontent; return nil },
}
err := inifile.SetVersion("1.2.4")
assert.NoError(t, err)
assert.Contains(t, string(content), "[test]")
assert.Contains(t, string(content), "1.2.4")
assert.NotContains(t, string(content), "1.2.3")
})
t.Run("error case", func(t *testing.T) {
inifile := INIfile{
path: "my.cfg",
versionField: "theversion",
readFile: func(filename string) ([]byte, error) { return []byte("theversion = 1.2.3"), nil },
writeFile: func(filename string, filecontent []byte, mode os.FileMode) error { return fmt.Errorf("write error") },
}
err := inifile.SetVersion("1.2.4")
assert.EqualError(t, err, "failed to write file 'my.cfg': write error")
})
}

View File

@@ -0,0 +1,78 @@
package versioning
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"github.com/pkg/errors"
)
// JSONfile defines an artifact using a json file for versioning
type JSONfile struct {
path string
content map[string]interface{}
versionField string
readFile func(string) ([]byte, error)
writeFile func(string, []byte, os.FileMode) error
}
func (j *JSONfile) init() {
if len(j.versionField) == 0 {
j.versionField = "version"
}
if j.readFile == nil {
j.readFile = ioutil.ReadFile
}
if j.writeFile == nil {
j.writeFile = ioutil.WriteFile
}
}
// VersioningScheme returns the relevant versioning scheme
func (j *JSONfile) VersioningScheme() string {
return "semver2"
}
// GetVersion returns the current version of the artifact with a JSON-based build descriptor
func (j *JSONfile) GetVersion() (string, error) {
j.init()
content, err := j.readFile(j.path)
if err != nil {
return "", errors.Wrapf(err, "failed to read file '%v'", j.path)
}
err = json.Unmarshal(content, &j.content)
if err != nil {
return "", errors.Wrapf(err, "failed to read json content of file '%v'", j.content)
}
return fmt.Sprint(j.content[j.versionField]), nil
}
// SetVersion updates the version of the artifact with a JSON-based build descriptor
func (j *JSONfile) SetVersion(version string) error {
j.init()
if j.content == nil {
_, err := j.GetVersion()
if err != nil {
return err
}
}
j.content[j.versionField] = version
content, err := json.MarshalIndent(j.content, "", " ")
if err != nil {
return errors.Wrapf(err, "failed to create json content for '%v'", j.path)
}
err = j.writeFile(j.path, content, 0700)
if err != nil {
return errors.Wrapf(err, "failed to write file '%v'", j.path)
}
return nil
}

View File

@@ -0,0 +1,57 @@
package versioning
import (
"fmt"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestJSONfileGetVersion(t *testing.T) {
t.Run("success case", func(t *testing.T) {
jsonfile := JSONfile{
path: "my.json",
readFile: func(filename string) ([]byte, error) { return []byte(`{"version": "1.2.3"}`), nil },
}
version, err := jsonfile.GetVersion()
assert.NoError(t, err)
assert.Equal(t, "1.2.3", version)
})
t.Run("error case", func(t *testing.T) {
jsonfile := JSONfile{
path: "my.json",
versionField: "theversion",
readFile: func(filename string) ([]byte, error) { return []byte{}, fmt.Errorf("read error") },
}
_, err := jsonfile.GetVersion()
assert.EqualError(t, err, "failed to read file 'my.json': read error")
})
}
func TestJSONfileSetVersion(t *testing.T) {
t.Run("success case", func(t *testing.T) {
var content []byte
jsonfile := JSONfile{
path: "my.json",
versionField: "theversion",
readFile: func(filename string) ([]byte, error) { return []byte(`{"theversion": "1.2.3"}`), nil },
writeFile: func(filename string, filecontent []byte, mode os.FileMode) error { content = filecontent; return nil },
}
err := jsonfile.SetVersion("1.2.4")
assert.NoError(t, err)
assert.Contains(t, string(content), `"theversion": "1.2.4"`)
})
t.Run("error case", func(t *testing.T) {
jsonfile := JSONfile{
path: "my.json",
versionField: "theversion",
readFile: func(filename string) ([]byte, error) { return []byte(`{"theversion": "1.2.3"}`), nil },
writeFile: func(filename string, filecontent []byte, mode os.FileMode) error { return fmt.Errorf("write error") },
}
err := jsonfile.SetVersion("1.2.4")
assert.EqualError(t, err, "failed to write file 'my.json': write error")
})
}

View File

@@ -21,37 +21,36 @@ type mavenRunner interface {
Evaluate(string, string, mavenExecRunner) (string, error)
}
// Maven ...
// Maven defines a maven artifact used for versioning
type Maven struct {
PomPath string
Runner mavenRunner
ExecRunner mavenExecRunner
ProjectSettingsFile string
GlobalSettingsFile string
M2Path string
pomPath string
runner mavenRunner
execRunner mavenExecRunner
projectSettingsFile string
globalSettingsFile string
m2Path string
}
// InitBuildDescriptor ...
func (m *Maven) init() {
if len(m.PomPath) == 0 {
m.PomPath = "pom.xml"
if len(m.pomPath) == 0 {
m.pomPath = "pom.xml"
}
if m.ExecRunner == nil {
m.ExecRunner = &command.Command{}
if m.execRunner == nil {
m.execRunner = &command.Command{}
}
}
// VersioningScheme ...
// VersioningScheme returns the relevant versioning scheme
func (m *Maven) VersioningScheme() string {
return "maven"
}
// GetVersion ...
// GetVersion returns the current version of the artifact
func (m *Maven) GetVersion() (string, error) {
m.init()
version, err := m.Runner.Evaluate(m.PomPath, "project.version", m.ExecRunner)
version, err := m.runner.Evaluate(m.pomPath, "project.version", m.execRunner)
if err != nil {
return "", errors.Wrap(err, "Maven - getting version failed")
}
@@ -59,19 +58,19 @@ func (m *Maven) GetVersion() (string, error) {
return version, nil
}
// SetVersion ...
// SetVersion updates the version of the artifact
func (m *Maven) SetVersion(version string) error {
m.init()
groupID, err := m.Runner.Evaluate(m.PomPath, "project.groupId", m.ExecRunner)
groupID, err := m.runner.Evaluate(m.pomPath, "project.groupId", m.execRunner)
if err != nil {
return errors.Wrap(err, "Maven - getting groupId failed")
}
opts := maven.ExecuteOptions{
PomPath: m.PomPath,
ProjectSettingsFile: m.ProjectSettingsFile,
GlobalSettingsFile: m.GlobalSettingsFile,
M2Path: m.M2Path,
PomPath: m.pomPath,
ProjectSettingsFile: m.projectSettingsFile,
GlobalSettingsFile: m.globalSettingsFile,
M2Path: m.m2Path,
Goals: []string{"org.codehaus.mojo:versions-maven-plugin:2.7:set"},
Defines: []string{
fmt.Sprintf("-DnewVersion=%v", version),
@@ -81,7 +80,7 @@ func (m *Maven) SetVersion(version string) error {
"-DgenerateBackupPoms=false",
},
}
_, err = m.Runner.Execute(&opts, m.ExecRunner)
_, err = m.runner.Execute(&opts, m.execRunner)
if err != nil {
return errors.Wrapf(err, "Maven - setting version %v failed", version)
}

View File

@@ -43,8 +43,8 @@ func TestMavenGetVersion(t *testing.T) {
stdout: "1.2.3",
}
mvn := &Maven{
Runner: &runner,
PomPath: "path/to/pom.xml",
runner: &runner,
pomPath: "path/to/pom.xml",
}
version, err := mvn.GetVersion()
assert.NoError(t, err)
@@ -59,7 +59,7 @@ func TestMavenGetVersion(t *testing.T) {
evaluateErrorString: "maven eval failed",
}
mvn := &Maven{
Runner: &runner,
runner: &runner,
}
version, err := mvn.GetVersion()
assert.EqualError(t, err, "Maven - getting version failed: maven eval failed")
@@ -74,11 +74,11 @@ func TestMavenSetVersion(t *testing.T) {
stdout: "testGroup",
}
mvn := &Maven{
Runner: &runner,
PomPath: "path/to/pom.xml",
ProjectSettingsFile: "project-settings.xml",
GlobalSettingsFile: "global-settings.xml",
M2Path: "m2/path",
runner: &runner,
pomPath: "path/to/pom.xml",
projectSettingsFile: "project-settings.xml",
globalSettingsFile: "global-settings.xml",
m2Path: "m2/path",
}
expectedOptions := maven.ExecuteOptions{
PomPath: "path/to/pom.xml",
@@ -99,8 +99,8 @@ func TestMavenSetVersion(t *testing.T) {
evaluateErrorString: "maven eval failed",
}
mvn := &Maven{
Runner: &runner,
PomPath: "path/to/pom.xml",
runner: &runner,
pomPath: "path/to/pom.xml",
}
err := mvn.SetVersion("1.2.4")
assert.EqualError(t, err, "Maven - getting groupId failed: maven eval failed")
@@ -112,8 +112,8 @@ func TestMavenSetVersion(t *testing.T) {
executeErrorString: "maven exec failed",
}
mvn := &Maven{
Runner: &runner,
PomPath: "path/to/pom.xml",
runner: &runner,
pomPath: "path/to/pom.xml",
}
err := mvn.SetVersion("1.2.4")
assert.EqualError(t, err, "Maven - setting version 1.2.4 failed: maven exec failed")

View File

@@ -1,78 +0,0 @@
package versioning
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"github.com/pkg/errors"
)
// Npm ...
type Npm struct {
PackageJSONPath string
PackageJSONContent map[string]interface{}
ReadFile func(string) ([]byte, error)
WriteFile func(string, []byte, os.FileMode) error
}
// InitBuildDescriptor ...
func (n *Npm) init() {
if len(n.PackageJSONPath) == 0 {
n.PackageJSONPath = "package.json"
}
if n.ReadFile == nil {
n.ReadFile = ioutil.ReadFile
}
if n.WriteFile == nil {
n.WriteFile = ioutil.WriteFile
}
}
// VersioningScheme ...
func (n *Npm) VersioningScheme() string {
return "semver2"
}
// GetVersion ...
func (n *Npm) GetVersion() (string, error) {
n.init()
content, err := n.ReadFile(n.PackageJSONPath)
if err != nil {
return "", errors.Wrapf(err, "failed to read file '%v'", n.PackageJSONPath)
}
err = json.Unmarshal(content, &n.PackageJSONContent)
if err != nil {
return "", errors.Wrap(err, "failed to read package.json content")
}
return fmt.Sprint(n.PackageJSONContent["version"]), nil
}
// SetVersion ...
func (n *Npm) SetVersion(version string) error {
n.init()
if n.PackageJSONContent == nil {
_, err := n.GetVersion()
if err != nil {
return err
}
}
n.PackageJSONContent["version"] = version
content, err := json.MarshalIndent(n.PackageJSONContent, "", " ")
if err != nil {
return errors.Wrap(err, "failed to create json content for package.json")
}
err = n.WriteFile(n.PackageJSONPath, content, 0700)
if err != nil {
return errors.Wrapf(err, "failed to write file '%v'", n.PackageJSONPath)
}
return nil
}

View File

@@ -1,73 +0,0 @@
package versioning
import (
"fmt"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestInit(t *testing.T) {
t.Run("default", func(t *testing.T) {
npm := Npm{}
npm.init()
assert.Equal(t, "package.json", npm.PackageJSONPath)
})
t.Run("no default", func(t *testing.T) {
npm := Npm{PackageJSONPath: "my/package.json"}
npm.init()
assert.Equal(t, "my/package.json", npm.PackageJSONPath)
})
}
func TestVersioningScheme(t *testing.T) {
npm := Npm{}
assert.Equal(t, "semver2", npm.VersioningScheme())
}
func TestGetVersion(t *testing.T) {
t.Run("success case", func(t *testing.T) {
npm := Npm{
PackageJSONPath: "my/package.json",
ReadFile: func(filename string) ([]byte, error) { return []byte(`{"name": "test","version": "1.2.3"}`), nil },
}
version, err := npm.GetVersion()
assert.NoError(t, err)
assert.Equal(t, "1.2.3", version)
})
t.Run("error case", func(t *testing.T) {
npm := Npm{
PackageJSONPath: "my/package.json",
ReadFile: func(filename string) ([]byte, error) { return []byte{}, fmt.Errorf("read error") },
}
_, err := npm.GetVersion()
assert.EqualError(t, err, "failed to read file 'my/package.json': read error")
})
}
func TestSetVersion(t *testing.T) {
t.Run("success case", func(t *testing.T) {
var content []byte
npm := Npm{
PackageJSONPath: "my/package.json",
ReadFile: func(filename string) ([]byte, error) { return []byte(`{"name": "test","version": "1.2.3"}`), nil },
WriteFile: func(filename string, filecontent []byte, mode os.FileMode) error { content = filecontent; return nil },
}
err := npm.SetVersion("1.2.4")
assert.NoError(t, err)
assert.Contains(t, string(content), "1.2.4")
})
t.Run("error case", func(t *testing.T) {
npm := Npm{
PackageJSONPath: "my/package.json",
ReadFile: func(filename string) ([]byte, error) { return []byte(`{"name": "test","version": "1.2.3"}`), nil },
WriteFile: func(filename string, filecontent []byte, mode os.FileMode) error { return fmt.Errorf("write error") },
}
err := npm.SetVersion("1.2.4")
assert.EqualError(t, err, "failed to write file 'my/package.json': write error")
})
}

View File

@@ -0,0 +1,62 @@
package versioning
import (
"io/ioutil"
"os"
"strings"
"github.com/pkg/errors"
)
// Versionfile defines an artifact containing the version in a file, e.g. VERSION
type Versionfile struct {
path string
readFile func(string) ([]byte, error)
writeFile func(string, []byte, os.FileMode) error
versioningScheme string
}
func (v *Versionfile) init() {
if len(v.path) == 0 {
v.path = "VERSION"
}
if v.readFile == nil {
v.readFile = ioutil.ReadFile
}
if v.writeFile == nil {
v.writeFile = ioutil.WriteFile
}
}
// VersioningScheme returns the relevant versioning scheme
func (v *Versionfile) VersioningScheme() string {
if len(v.versioningScheme) == 0 {
return "semver2"
}
return v.versioningScheme
}
// GetVersion returns the current version of the artifact
func (v *Versionfile) GetVersion() (string, error) {
v.init()
content, err := v.readFile(v.path)
if err != nil {
return "", errors.Wrapf(err, "failed to read file '%v'", v.path)
}
return strings.TrimSpace(string(content)), nil
}
// SetVersion updates the version of the artifact
func (v *Versionfile) SetVersion(version string) error {
v.init()
err := v.writeFile(v.path, []byte(version), 0700)
if err != nil {
return errors.Wrapf(err, "failed to write file '%v'", v.path)
}
return nil
}

View File

@@ -0,0 +1,83 @@
package versioning
import (
"fmt"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestVersionfileInit(t *testing.T) {
t.Run("default", func(t *testing.T) {
versionfile := Versionfile{}
versionfile.init()
assert.Equal(t, "VERSION", versionfile.path)
})
t.Run("no default", func(t *testing.T) {
versionfile := Versionfile{path: "my/VERSION"}
versionfile.init()
assert.Equal(t, "my/VERSION", versionfile.path)
})
}
func TestVersionfileVersioningScheme(t *testing.T) {
versionfile := Versionfile{}
assert.Equal(t, "semver2", versionfile.VersioningScheme())
}
func TestVersionfileGetVersion(t *testing.T) {
t.Run("success case", func(t *testing.T) {
versionfile := Versionfile{
path: "my/VERSION",
readFile: func(filename string) ([]byte, error) { return []byte("1.2.3"), nil },
}
version, err := versionfile.GetVersion()
assert.NoError(t, err)
assert.Equal(t, "1.2.3", version)
})
t.Run("success case - trimming", func(t *testing.T) {
versionfile := Versionfile{
path: "my/VERSION",
readFile: func(filename string) ([]byte, error) { return []byte("1.2.3 \n"), nil },
}
version, err := versionfile.GetVersion()
assert.NoError(t, err)
assert.Equal(t, "1.2.3", version)
})
t.Run("error case", func(t *testing.T) {
versionfile := Versionfile{
path: "my/VERSION",
readFile: func(filename string) ([]byte, error) { return []byte{}, fmt.Errorf("read error") },
}
_, err := versionfile.GetVersion()
assert.EqualError(t, err, "failed to read file 'my/VERSION': read error")
})
}
func TestVersionfileSetVersion(t *testing.T) {
t.Run("success case", func(t *testing.T) {
var content []byte
versionfile := Versionfile{
path: "my/VERSION",
readFile: func(filename string) ([]byte, error) { return []byte("1.2.3"), nil },
writeFile: func(filename string, filecontent []byte, mode os.FileMode) error { content = filecontent; return nil },
}
err := versionfile.SetVersion("1.2.4")
assert.NoError(t, err)
assert.Contains(t, string(content), "1.2.4")
})
t.Run("error case", func(t *testing.T) {
versionfile := Versionfile{
path: "my/VERSION",
readFile: func(filename string) ([]byte, error) { return []byte("1.2.3"), nil },
writeFile: func(filename string, filecontent []byte, mode os.FileMode) error { return fmt.Errorf("write error") },
}
err := versionfile.SetVersion("1.2.4")
assert.EqualError(t, err, "failed to write file 'my/VERSION': write error")
})
}

View File

@@ -2,22 +2,28 @@ package versioning
import (
"fmt"
"path/filepath"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/maven"
)
// Artifact ...
// Artifact defines the versioning operations for various build tools
type Artifact interface {
VersioningScheme() string
GetVersion() (string, error)
SetVersion(string) error
}
// Options ...
// Options define build tool specific settings in order to properly retrieve e.g. the version of an artifact
type Options struct {
ProjectSettingsFile string
GlobalSettingsFile string
M2Path string
VersionSource string
VersionSection string
VersionField string
}
type mvnRunner struct{}
@@ -29,22 +35,94 @@ func (m *mvnRunner) Evaluate(pomFile, expression string, execRunner mavenExecRun
return maven.Evaluate(pomFile, expression, execRunner)
}
// GetArtifact ...
var fileExists func(string) (bool, error)
// GetArtifact returns the build tool specific implementation for retrieving version, etc. of an artifact
func GetArtifact(buildTool, buildDescriptorFilePath string, opts *Options, execRunner mavenExecRunner) (Artifact, error) {
var artifact Artifact
if fileExists == nil {
fileExists = piperutils.FileExists
}
switch buildTool {
case "custom":
var err error
artifact, err = customArtifact(buildDescriptorFilePath, opts.VersionField, opts.VersionSection)
if err != nil {
return artifact, err
}
case "docker":
artifact = &Docker{
execRunner: execRunner,
options: opts,
path: buildDescriptorFilePath,
versionSource: opts.VersionSource,
}
case "dub":
if len(buildDescriptorFilePath) == 0 {
buildDescriptorFilePath = "dub.json"
}
artifact = &JSONfile{
path: buildDescriptorFilePath,
versionField: "version",
}
case "golang":
if len(buildDescriptorFilePath) == 0 {
var err error
buildDescriptorFilePath, err = searchDescriptor([]string{"VERSION", "version.txt"}, fileExists)
if err != nil {
return artifact, err
}
}
artifact = &Versionfile{
path: buildDescriptorFilePath,
}
case "maven":
if len(buildDescriptorFilePath) == 0 {
buildDescriptorFilePath = "pom.xml"
}
artifact = &Maven{
Runner: &mvnRunner{},
ExecRunner: execRunner,
PomPath: buildDescriptorFilePath,
ProjectSettingsFile: opts.ProjectSettingsFile,
GlobalSettingsFile: opts.GlobalSettingsFile,
M2Path: opts.M2Path,
runner: &mvnRunner{},
execRunner: execRunner,
pomPath: buildDescriptorFilePath,
projectSettingsFile: opts.ProjectSettingsFile,
globalSettingsFile: opts.GlobalSettingsFile,
m2Path: opts.M2Path,
}
case "mta":
if len(buildDescriptorFilePath) == 0 {
buildDescriptorFilePath = "mta.yaml"
}
artifact = &YAMLfile{
path: buildDescriptorFilePath,
versionField: "version",
}
case "npm":
artifact = &Npm{
PackageJSONPath: buildDescriptorFilePath,
if len(buildDescriptorFilePath) == 0 {
buildDescriptorFilePath = "package.json"
}
artifact = &JSONfile{
path: buildDescriptorFilePath,
versionField: "version",
}
case "pip":
if len(buildDescriptorFilePath) == 0 {
var err error
buildDescriptorFilePath, err = searchDescriptor([]string{"version.txt", "VERSION"}, fileExists)
if err != nil {
return artifact, err
}
}
artifact = &Versionfile{
path: buildDescriptorFilePath,
versioningScheme: "pep440",
}
case "sbt":
if len(buildDescriptorFilePath) == 0 {
buildDescriptorFilePath = "sbtDescriptor.json"
}
artifact = &JSONfile{
path: buildDescriptorFilePath,
versionField: "version",
}
default:
return artifact, fmt.Errorf("build tool '%v' not supported", buildTool)
@@ -52,3 +130,45 @@ func GetArtifact(buildTool, buildDescriptorFilePath string, opts *Options, execR
return artifact, nil
}
func searchDescriptor(supported []string, existsFunc func(string) (bool, error)) (string, error) {
var descriptor string
for _, f := range supported {
exists, _ := existsFunc(f)
if exists {
descriptor = f
break
}
}
if len(descriptor) == 0 {
return "", fmt.Errorf("no build descriptor available, supported: %v", supported)
}
return descriptor, nil
}
func customArtifact(buildDescriptorFilePath, field, section string) (Artifact, error) {
switch filepath.Ext(buildDescriptorFilePath) {
case ".cfg", ".ini":
return &INIfile{
path: buildDescriptorFilePath,
versionField: field,
versionSection: section,
}, nil
case ".json":
return &JSONfile{
path: buildDescriptorFilePath,
versionField: field,
}, nil
case ".yaml", ".yml":
return &YAMLfile{
path: buildDescriptorFilePath,
versionField: field,
}, nil
case ".txt", "":
return &Versionfile{
path: buildDescriptorFilePath,
}, nil
default:
return nil, fmt.Errorf("file type not supported: '%v'", buildDescriptorFilePath)
}
}

View File

@@ -7,20 +7,168 @@ import (
)
func TestGetArtifact(t *testing.T) {
t.Run("maven", func(t *testing.T) {
maven, err := GetArtifact("maven", "my/pom.xml", &Options{}, nil)
t.Run("custom", func(t *testing.T) {
custom, err := GetArtifact("custom", "test.ini", &Options{VersionField: "theversion", VersionSection: "test"}, nil)
assert.NoError(t, err)
theType, ok := custom.(*INIfile)
assert.True(t, ok)
assert.Equal(t, "test.ini", theType.path)
assert.Equal(t, "theversion", theType.versionField)
assert.Equal(t, "test", theType.versionSection)
assert.Equal(t, "semver2", custom.VersioningScheme())
})
t.Run("docker", func(t *testing.T) {
docker, err := GetArtifact("docker", "test.ini", &Options{VersionSource: "custom", VersionField: "theversion", VersionSection: "test"}, nil)
assert.NoError(t, err)
theType, ok := docker.(*Docker)
assert.True(t, ok)
assert.Equal(t, "test.ini", theType.path)
assert.Equal(t, "theversion", theType.options.VersionField)
assert.Equal(t, "test", theType.options.VersionSection)
assert.Equal(t, "maven", docker.VersioningScheme())
})
t.Run("dub", func(t *testing.T) {
dub, err := GetArtifact("dub", "", &Options{VersionField: "theversion"}, nil)
assert.NoError(t, err)
theType, ok := dub.(*JSONfile)
assert.True(t, ok)
assert.Equal(t, "dub.json", theType.path)
assert.Equal(t, "version", theType.versionField)
assert.Equal(t, "semver2", dub.VersioningScheme())
})
t.Run("golang", func(t *testing.T) {
fileExists = func(string) (bool, error) { return true, nil }
golang, err := GetArtifact("golang", "", &Options{}, nil)
assert.NoError(t, err)
theType, ok := golang.(*Versionfile)
assert.True(t, ok)
assert.Equal(t, "VERSION", theType.path)
assert.Equal(t, "semver2", golang.VersioningScheme())
})
t.Run("golang - error", func(t *testing.T) {
fileExists = func(string) (bool, error) { return false, nil }
_, err := GetArtifact("golang", "", &Options{}, nil)
assert.EqualError(t, err, "no build descriptor available, supported: [VERSION version.txt]")
})
t.Run("maven", func(t *testing.T) {
opts := Options{
ProjectSettingsFile: "projectsettings.xml",
GlobalSettingsFile: "globalsettings.xml",
M2Path: "m2/path",
}
maven, err := GetArtifact("maven", "", &opts, nil)
assert.NoError(t, err)
theType, ok := maven.(*Maven)
assert.True(t, ok)
assert.Equal(t, "pom.xml", theType.pomPath)
assert.Equal(t, opts.ProjectSettingsFile, theType.projectSettingsFile)
assert.Equal(t, opts.GlobalSettingsFile, theType.globalSettingsFile)
assert.Equal(t, opts.M2Path, theType.m2Path)
assert.Equal(t, "maven", maven.VersioningScheme())
})
t.Run("npm", func(t *testing.T) {
npm, err := GetArtifact("npm", "my/package.json", &Options{}, nil)
t.Run("mta", func(t *testing.T) {
mta, err := GetArtifact("mta", "", &Options{VersionField: "theversion"}, nil)
assert.NoError(t, err)
theType, ok := mta.(*YAMLfile)
assert.True(t, ok)
assert.Equal(t, "mta.yaml", theType.path)
assert.Equal(t, "version", theType.versionField)
assert.Equal(t, "semver2", mta.VersioningScheme())
})
t.Run("npm", func(t *testing.T) {
npm, err := GetArtifact("npm", "", &Options{VersionField: "theversion"}, nil)
assert.NoError(t, err)
theType, ok := npm.(*JSONfile)
assert.True(t, ok)
assert.Equal(t, "package.json", theType.path)
assert.Equal(t, "version", theType.versionField)
assert.Equal(t, "semver2", npm.VersioningScheme())
})
t.Run("pip", func(t *testing.T) {
fileExists = func(string) (bool, error) { return true, nil }
pip, err := GetArtifact("pip", "", &Options{}, nil)
assert.NoError(t, err)
theType, ok := pip.(*Versionfile)
assert.True(t, ok)
assert.Equal(t, "version.txt", theType.path)
assert.Equal(t, "pep440", pip.VersioningScheme())
})
t.Run("pip - error", func(t *testing.T) {
fileExists = func(string) (bool, error) { return false, nil }
_, err := GetArtifact("pip", "", &Options{}, nil)
assert.EqualError(t, err, "no build descriptor available, supported: [version.txt VERSION]")
})
t.Run("sbt", func(t *testing.T) {
sbt, err := GetArtifact("sbt", "", &Options{VersionField: "theversion"}, nil)
assert.NoError(t, err)
theType, ok := sbt.(*JSONfile)
assert.True(t, ok)
assert.Equal(t, "sbtDescriptor.json", theType.path)
assert.Equal(t, "version", theType.versionField)
assert.Equal(t, "semver2", sbt.VersioningScheme())
})
t.Run("not supported build tool", func(t *testing.T) {
_, err := GetArtifact("nosupport", "whatever", &Options{}, nil)
assert.EqualError(t, err, "build tool 'nosupport' not supported")
})
}
func TestCustomArtifact(t *testing.T) {
tt := []struct {
file string
field string
section string
expected Artifact
expectedErr string
}{
{file: "not.supported", expectedErr: "file type not supported: 'not.supported'"},
{file: "test.cfg", field: "testField", section: "testSection", expected: &INIfile{path: "test.cfg", versionField: "testField", versionSection: "testSection"}},
{file: "test.ini", field: "testField", section: "testSection", expected: &INIfile{path: "test.ini", versionField: "testField", versionSection: "testSection"}},
{file: "test.json", field: "testField", expected: &JSONfile{path: "test.json", versionField: "testField"}},
{file: "test.yaml", field: "testField", expected: &YAMLfile{path: "test.yaml", versionField: "testField"}},
{file: "test.yml", field: "testField", expected: &YAMLfile{path: "test.yml", versionField: "testField"}},
{file: "test.txt", expected: &Versionfile{path: "test.txt"}},
{file: "test", expected: &Versionfile{path: "test"}},
}
for _, test := range tt {
res, err := customArtifact(test.file, test.field, test.section)
if len(test.expectedErr) == 0 {
assert.NoError(t, err)
assert.Equal(t, test.expected, res)
} else {
assert.EqualError(t, err, test.expectedErr)
}
}
}

View File

@@ -0,0 +1,79 @@
package versioning
import (
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/ghodss/yaml"
"github.com/pkg/errors"
)
// YAMLfile defines an artifact using a yaml file for versioning
type YAMLfile struct {
path string
content map[string]interface{}
versionField string
readFile func(string) ([]byte, error)
writeFile func(string, []byte, os.FileMode) error
}
func (y *YAMLfile) init() {
if len(y.versionField) == 0 {
y.versionField = "version"
}
if y.readFile == nil {
y.readFile = ioutil.ReadFile
}
if y.writeFile == nil {
y.writeFile = ioutil.WriteFile
}
}
// VersioningScheme returns the relevant versioning scheme
func (y *YAMLfile) VersioningScheme() string {
return "semver2"
}
// GetVersion returns the current version of the artifact with a YAML-based build descriptor
func (y *YAMLfile) GetVersion() (string, error) {
y.init()
content, err := y.readFile(y.path)
if err != nil {
return "", errors.Wrapf(err, "failed to read file '%v'", y.path)
}
err = yaml.Unmarshal(content, &y.content)
if err != nil {
return "", errors.Wrapf(err, "failed to read yaml content of file '%v'", y.content)
}
return strings.TrimSpace(fmt.Sprint(y.content[y.versionField])), nil
}
// SetVersion updates the version of the artifact with a YAML-based build descriptor
func (y *YAMLfile) SetVersion(version string) error {
y.init()
if y.content == nil {
_, err := y.GetVersion()
if err != nil {
return err
}
}
y.content[y.versionField] = version
content, err := yaml.Marshal(y.content)
if err != nil {
return errors.Wrapf(err, "failed to create yaml content for '%v'", y.path)
}
err = y.writeFile(y.path, content, 0700)
if err != nil {
return errors.Wrapf(err, "failed to write file '%v'", y.path)
}
return nil
}

View File

@@ -0,0 +1,57 @@
package versioning
import (
"fmt"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestYAMLfileGetVersion(t *testing.T) {
t.Run("success case", func(t *testing.T) {
yamlfile := YAMLfile{
path: "my.yaml",
readFile: func(filename string) ([]byte, error) { return []byte(`version: 1.2.3`), nil },
}
version, err := yamlfile.GetVersion()
assert.NoError(t, err)
assert.Equal(t, "1.2.3", version)
})
t.Run("error case", func(t *testing.T) {
yamlfile := YAMLfile{
path: "my.yaml",
versionField: "theversion",
readFile: func(filename string) ([]byte, error) { return []byte{}, fmt.Errorf("read error") },
}
_, err := yamlfile.GetVersion()
assert.EqualError(t, err, "failed to read file 'my.yaml': read error")
})
}
func TestYAMLfileSetVersion(t *testing.T) {
t.Run("success case", func(t *testing.T) {
var content []byte
yamlfile := YAMLfile{
path: "my.yaml",
versionField: "theversion",
readFile: func(filename string) ([]byte, error) { return []byte(`theversion: 1.2.3`), nil },
writeFile: func(filename string, filecontent []byte, mode os.FileMode) error { content = filecontent; return nil },
}
err := yamlfile.SetVersion("1.2.4")
assert.NoError(t, err)
assert.Contains(t, string(content), "theversion: 1.2.4")
})
t.Run("error case", func(t *testing.T) {
yamlfile := YAMLfile{
path: "my.yaml",
versionField: "theversion",
readFile: func(filename string) ([]byte, error) { return []byte(`theversion: 1.2.3`), nil },
writeFile: func(filename string, filecontent []byte, mode os.FileMode) error { return fmt.Errorf("write error") },
}
err := yamlfile.SetVersion("1.2.4")
assert.EqualError(t, err, "failed to write file 'my.yaml': write error")
})
}

View File

@@ -43,12 +43,38 @@ metadata:
Unlike for the _Continuous Deloyment_ pattern descibed above, in this case there is no dedicated tagging required for the build process since the version is already available in the repository.
Configuration of this pattern is done via `versioningType: library`.
### Support of additional build tools
Besides the `buildTools` provided out of the box (like `maven`, `mta`, `npm`, ...) it is possible to set `buildTool: custom`.
This allows you to provide automatic versioning for tools using a:
#### file with the version as only content:
Define `buildTool: custom` as well as `filePath: <path to your file>`
**Please note:** `<path to your file>` need to point either to a `*.txt` file or to a file without extension.
#### `ini` file containing the version:
Define `buildTool: custom`, `filePath: <path to your ini-file>` as well as parameters `versionSection` and `versionSource` to point to the version location (section & parameter name) within the file.
**Please note:** `<path to your file>` need to point either to a `*.cfg` or a `*.ini` file.
#### `json` file containing the version:
Define `buildTool: custom`, `filePath: <path to your *.json file` as well as parameter `versionSource` to point to the parameter containing the version.
#### `yaml` file containing the version
Define `buildTool: custom`, `filePath: <path to your *.yml/*.yaml file` as well as parameter `versionSource` to point to the parameter containing the version.
spec:
inputs:
params:
- name: buildTool
type: string
description: Defines the tool which is used for building the artifact.
description: Defines the tool which is used for building the artifact. Supports `custom`, `dub`, `golang`, `maven`, `mta`, `npm`, `pip`, `sbt`.
mandatory: true
scope:
- GENERAL
@@ -65,9 +91,23 @@ spec:
- STAGES
- STEPS
default: Project Piper
- name: customversionField
type: string
description: "For `buildTool: custom`: Defines the field which contains the version in the descriptor file."
scope:
- PARAMETERS
- STAGES
- STEPS
- name: customVersionSection
type: string
description: "For `buildTool: custom`: Defines the section for version retrieval in vase a *.ini/*.cfg file is used."
scope:
- PARAMETERS
- STAGES
- STEPS
- name: dockerVersionSource
type: string
description: "For Docker only: Specifies the source to be used for for generating the automatic version. * This can either be the version of the base image - as retrieved from the `FROM` statement within the Dockerfile, e.g. `FROM jenkins:2.46.2` * Alternatively the name of an environment variable defined in the Docker image can be used which contains the version number, e.g. `ENV MY_VERSION 1.2.3`."
description: "For `buildTool: docker`: Defines the source of the version. Can be `FROM`, any supported _buildTool_ or an environment variable name."
scope:
- PARAMETERS
- STAGES