diff --git a/cmd/image-builder/config.go b/cmd/image-builder/config.go index 49debaebdbf1..bc96c9894552 100644 --- a/cmd/image-builder/config.go +++ b/cmd/image-builder/config.go @@ -33,9 +33,17 @@ type Config struct { DevRegistry Registry `yaml:"dev-registry" json:"dev-registry"` // Cache options that are directly related to kaniko flags Cache CacheConfig `yaml:"cache" json:"cache"` - // TagTemplate is go-template field that defines the format of the $_TAG substitution. - // See tags.Tag struct for more information and available fields + // TagTemplate is used to generate default tag for push events for current image-builder version. + // This will be removed after migration. TagTemplate tags.Tag `yaml:"tag-template" json:"tag-template"` + // Default Tag template used for images build on commit. + // The value can be a go-template string or literal tag value string. + // See tags.Tag struct for more information and available fields + DefaultCommitTag tags.Tag `yaml:"default-commit-tag" json:"default-commit-tag"` + // Default Tag template used for images build on pull request. + // The value can be a go-template string or literal tag value string. + // See tags.Tag struct for more information and available fields + DefaultPRTag tags.Tag `yaml:"default-pr-tag" json:"default-pr-tag"` // LogFormat defines the format kaniko logs are projected. // Supported formats are 'color', 'text' and 'json'. Default: 'color' LogFormat string `yaml:"log-format" json:"log-format"` diff --git a/cmd/image-builder/config_test.go b/cmd/image-builder/config_test.go index 74ffb47449d3..8fdbf4384ec7 100644 --- a/cmd/image-builder/config_test.go +++ b/cmd/image-builder/config_test.go @@ -19,11 +19,19 @@ func Test_ParseConfig(t *testing.T) { name: "parsed full config one repo", config: `registry: kyma-project.io/prod-registry dev-registry: dev.kyma-project.io/dev-registry -tag-template: v{{ .Date }}-{{ .ShortSHA }}`, +default-commit-tag: + name: default_tag + value: v{{ .Date }}-{{ .ShortSHA }} + validation: ^(v[0-9]{8}-[0-9a-f]{8})$ +default-pr-tag: + name: default_tag + value: pr-{{ .PRNumber }} + validation: ^(PR-[0-9]+)$`, expectedConfig: Config{ - Registry: []string{"kyma-project.io/prod-registry"}, - DevRegistry: []string{"dev.kyma-project.io/dev-registry"}, - TagTemplate: tags.Tag{Name: "default_tag", Value: `v{{ .Date }}-{{ .ShortSHA }}`}, + Registry: []string{"kyma-project.io/prod-registry"}, + DevRegistry: []string{"dev.kyma-project.io/dev-registry"}, + DefaultCommitTag: tags.Tag{Name: "default_tag", Value: `v{{ .Date }}-{{ .ShortSHA }}`, Validation: `^(v[0-9]{8}-[0-9a-f]{8})$`}, + DefaultPRTag: tags.Tag{Name: "default_tag", Value: `pr-{{ .PRNumber }}`, Validation: `^(PR-[0-9]+)$`}, }, }, { @@ -34,11 +42,19 @@ tag-template: v{{ .Date }}-{{ .ShortSHA }}`, dev-registry: - dev.kyma-project.io/dev-registry - dev.kyma-project.io/second-registry -tag-template: v{{ .Date }}-{{ .ShortSHA }}`, +default-commit-tag: + name: default_tag + value: v{{ .Date }}-{{ .ShortSHA }} + validation: ^(v[0-9]{8}-[0-9a-f]{8})$ +default-pr-tag: + name: default_tag + value: pr-{{ .PRNumber }} + validation: ^(PR-[0-9]+)$`, expectedConfig: Config{ - Registry: []string{"kyma-project.io/prod-registry", "kyma-project.io/second-registry"}, - DevRegistry: []string{"dev.kyma-project.io/dev-registry", "dev.kyma-project.io/second-registry"}, - TagTemplate: tags.Tag{Name: "default_tag", Value: `v{{ .Date }}-{{ .ShortSHA }}`}, + Registry: []string{"kyma-project.io/prod-registry", "kyma-project.io/second-registry"}, + DevRegistry: []string{"dev.kyma-project.io/dev-registry", "dev.kyma-project.io/second-registry"}, + DefaultCommitTag: tags.Tag{Name: "default_tag", Value: `v{{ .Date }}-{{ .ShortSHA }}`, Validation: `^(v[0-9]{8}-[0-9a-f]{8})$`}, + DefaultPRTag: tags.Tag{Name: "default_tag", Value: `pr-{{ .PRNumber }}`, Validation: `^(PR-[0-9]+)$`}, }, }, { diff --git a/cmd/image-builder/main.go b/cmd/image-builder/main.go index 413b790c1212..e9b156f4e918 100644 --- a/cmd/image-builder/main.go +++ b/cmd/image-builder/main.go @@ -471,8 +471,13 @@ func buildLocally(o options) error { return fmt.Errorf("'sha' could not be determined") } + defaultTag, err := getDefaultTag(o) + if err != nil { + return err + } + // Get the tags for the image. - parsedTags, err := getTags(pr, sha, append(o.tags, o.TagTemplate)) + parsedTags, err := getTags(pr, sha, append(o.tags, defaultTag)) if err != nil { return err } @@ -638,13 +643,16 @@ func (l *StrList) List() []string { } func getTags(pr, sha string, templates []tags.Tag) ([]tags.Tag, error) { - // (Ressetkk): PR tag should not be hardcoded, in the future we have to find a way to parametrize it - if pr != "" { - // assume we are using PR number, build default tag as 'PR-XXXX' - return []tags.Tag{{Name: "default_tag", Value: "PR-" + pr}}, nil + var taggerOptions []tags.TagOption + if len(pr) > 0 { + taggerOptions = append(taggerOptions, tags.PRNumber(pr)) + } + if len(sha) > 0 { + taggerOptions = append(taggerOptions, tags.CommitSHA(sha)) } + // build a tag from commit SHA - tagger, err := tags.NewTagger(templates, tags.CommitSHA(sha)) + tagger, err := tags.NewTagger(templates, taggerOptions...) if err != nil { return nil, fmt.Errorf("get tagger: %w", err) } @@ -914,34 +922,39 @@ func getEnvs(o options, dockerfilePath string) (map[string]string, error) { } func parseTags(o options) ([]tags.Tag, error) { - var pr string - sha := o.gitState.BaseCommitSHA - if o.gitState.isPullRequest { - pr = fmt.Sprint(o.gitState.PullRequestNumber) + var ( + pr string + sha string + ) + if !o.gitState.isPullRequest && o.gitState.BaseCommitSHA != "" { + sha = o.gitState.BaseCommitSHA } - - if sha == "" { - return nil, fmt.Errorf("sha still empty") + if o.gitState.isPullRequest && o.gitState.PullRequestNumber > 0 { + pr = fmt.Sprint(o.gitState.PullRequestNumber) } + // TODO (dekiel): Tags provided as base64 encoded string should be parsed and added to the tags list when parsing flags. + // This way all tags are available in the tags list from thr very beginning of execution and can be used in any process. // read tags from base64 encoded string if provided if o.tagsBase64 != "" { decoded, err := base64.StdEncoding.DecodeString(o.tagsBase64) if err != nil { - fmt.Printf("Failed to decode tags, error: %s", err) - os.Exit(1) + return nil, fmt.Errorf("failed to decode tags, error: %w", err) } splitedTags := strings.Split(string(decoded), ",") for _, tag := range splitedTags { err = o.tags.Set(tag) if err != nil { - fmt.Printf("Failed to set tag, tag: %s, error: %s", tag, err) - os.Exit(1) + return nil, fmt.Errorf("failed to set tag, tag: %s, error: %w", tag, err) } } } - parsedTags, err := getTags(pr, sha, append(o.tags, o.TagTemplate)) + defaultTag, err := getDefaultTag(o) + if err != nil { + return nil, err + } + parsedTags, err := getTags(pr, sha, append(o.tags, defaultTag)) if err != nil { return nil, err } @@ -949,6 +962,19 @@ func parseTags(o options) ([]tags.Tag, error) { return parsedTags, nil } +// getDefaultTag returns the default tag based on the read git state. +// The function provid default tag for pull request or commit. +// The default tag is read from the provided options struct. +func getDefaultTag(o options) (tags.Tag, error) { + if o.gitState.isPullRequest && o.gitState.PullRequestNumber > 0 { + return o.DefaultPRTag, nil + } + if len(o.gitState.BaseCommitSHA) > 0 { + return o.DefaultCommitTag, nil + } + return tags.Tag{}, fmt.Errorf("could not determine default tag, no pr number or commit sha provided") +} + func getDockerfileDirPath(o options) (string, error) { // Get the absolute path to the build context directory. context, err := filepath.Abs(o.context) diff --git a/cmd/image-builder/main_test.go b/cmd/image-builder/main_test.go index 91e907709c38..221c08033cbd 100644 --- a/cmd/image-builder/main_test.go +++ b/cmd/image-builder/main_test.go @@ -1,17 +1,47 @@ package main import ( + "encoding/base64" "flag" + "fmt" "os" "reflect" + "strconv" "strings" "testing" "testing/fstest" + "time" "github.com/kyma-project/test-infra/pkg/azuredevops/pipelines" "github.com/kyma-project/test-infra/pkg/sets" "github.com/kyma-project/test-infra/pkg/sign" "github.com/kyma-project/test-infra/pkg/tags" + + . "github.com/onsi/gomega" +) + +var ( + defaultPRTag = tags.Tag{Name: "default_tag", Value: `PR-{{ .PRNumber }}`, Validation: "^(PR-[0-9]+)$"} + defaultCommitTag = tags.Tag{Name: "default_tag", Value: `v{{ .Date }}-{{ .ShortSHA }}`, Validation: "^(v[0-9]{8}-[0-9a-f]{8})$"} + expectedDefaultPRTag = func(prNumber int) tags.Tag { + return tags.Tag{Name: "default_tag", Value: "PR-" + strconv.Itoa(prNumber), Validation: "^(PR-[0-9]+)$"} + } + expectedDefaultCommitTag = func(baseSHA string) tags.Tag { + return tags.Tag{Name: "default_tag", Value: "v" + time.Now().Format("20060102") + "-" + fmt.Sprintf("%.8s", baseSHA), Validation: "^(v[0-9]{8}-[0-9a-f]{8})$"} + } + buildConfig = Config{ + DefaultPRTag: defaultPRTag, + DefaultCommitTag: defaultCommitTag, + } + prGitState = GitStateConfig{ + BaseCommitSHA: "abcdef123456", + PullRequestNumber: 5, + isPullRequest: true, + } + commitGitState = GitStateConfig{ + BaseCommitSHA: "abcdef123456", + isPullRequest: false, + } ) func Test_gatherDestinations(t *testing.T) { @@ -297,7 +327,7 @@ func TestFlags(t *testing.T) { } } -func Test_gatTags(t *testing.T) { +func Test_getTags(t *testing.T) { tc := []struct { name string pr string @@ -309,18 +339,49 @@ func Test_gatTags(t *testing.T) { expectResult []tags.Tag }{ { - name: "pr variable is present", + name: "generate default pr tag, when no pr number and commit sha provided", + tagTemplate: defaultPRTag, + expectErr: true, + }, + { + name: "generate default commit tag, when no pr number and commit sha provided", + tagTemplate: defaultCommitTag, + expectErr: true, + }, + { + name: "generate default pr tag, when pr number provided", pr: "1234", - expectResult: []tags.Tag{{Name: "default_tag", Value: "PR-1234"}}, + tagTemplate: defaultPRTag, + expectResult: []tags.Tag{expectedDefaultPRTag(1234)}, }, { - name: "sha is empty", + name: "generate default commit tag, when commit sha provided", + sha: "1a2b3c4d5e6f78", + tagTemplate: defaultCommitTag, + expectResult: []tags.Tag{expectedDefaultCommitTag("1a2b3c4d5e6f78")}, + }, + { + name: "generate default pr tag and additional tags", + pr: "1234", + tagTemplate: defaultPRTag, + additionalTags: []tags.Tag{{Name: "additional_tag", Value: "additional"}}, + expectResult: []tags.Tag{{Name: "additional_tag", Value: "additional"}, expectedDefaultPRTag(1234)}, + }, + { + name: "generate default commit tag and additional tags", + sha: "1a2b3c4d5e6f78", + tagTemplate: defaultCommitTag, + additionalTags: []tags.Tag{{Name: "additional_tag", Value: "additional"}}, + expectResult: []tags.Tag{{Name: "additional_tag", Value: "additional"}, expectedDefaultCommitTag("1a2b3c4d5e6f78")}, + }, + { + name: "no pr, sha and default tag provided", expectErr: true, }, { name: "bad tagTemplate", expectErr: true, - sha: "abcd1234", + sha: "1a2b3c4d5e6f78", tagTemplate: tags.Tag{Name: "TagTemplate", Value: `v{{ .ASD }}`}, }, { @@ -565,45 +626,77 @@ func Test_appendMissing(t *testing.T) { } func Test_parseTags(t *testing.T) { + tagsFlag := sets.Tags{{Name: "base64testtag", Value: "testtag"}, {Name: "base64testtemplate", Value: "test-{{ .PRNumber }}"}} + base64Tags := base64.StdEncoding.EncodeToString([]byte(tagsFlag.String())) tc := []struct { - name string - options options - tags []tags.Tag - expectErr bool + name string + options options + expectedTags []tags.Tag + expectErr bool }{ { - name: "PR tag parse", + name: "pares only PR default tag", options: options{ - gitState: GitStateConfig{ - BaseCommitSHA: "some-sha", - PullRequestNumber: 5, - isPullRequest: true, - }, + gitState: prGitState, + Config: buildConfig, + }, + expectedTags: []tags.Tag{expectedDefaultPRTag(prGitState.PullRequestNumber)}, + }, + { + name: "parse only commit default tag", + options: options{ + gitState: commitGitState, + Config: buildConfig, + }, + expectedTags: []tags.Tag{expectedDefaultCommitTag(commitGitState.BaseCommitSHA)}, + }, + { + name: "parse PR default and additional tags", + options: options{ + gitState: prGitState, + Config: buildConfig, tags: sets.Tags{ - {Name: "AnotherTest", Value: `{{ .CommitSHA }}`}, + {Name: "AnotherTest", Value: `Another-{{ .PRNumber }}`}, + {Name: "Test", Value: "tag-value"}, }, }, - tags: []tags.Tag{{Name: "default_tag", Value: "PR-5"}}, + expectedTags: []tags.Tag{{Name: "AnotherTest", Value: "Another-" + strconv.Itoa(prGitState.PullRequestNumber)}, {Name: "Test", Value: "tag-value"}, expectedDefaultPRTag(prGitState.PullRequestNumber)}, }, { - name: "Tags from commit sha", + name: "parse commit default and additional tags", options: options{ - gitState: GitStateConfig{ - BaseCommitSHA: "some-sha", - }, - Config: Config{ - TagTemplate: tags.Tag{Name: "AnotherTest", Value: `{{ .CommitSHA }}`}, + gitState: commitGitState, + Config: buildConfig, + tags: sets.Tags{ + {Name: "AnotherTest", Value: `Another-{{ .CommitSHA }}`}, + {Name: "Test", Value: "tag-value"}, }, }, - tags: []tags.Tag{{Name: "AnotherTest", Value: "some-sha"}}, + expectedTags: []tags.Tag{{Name: "AnotherTest", Value: "Another-" + commitGitState.BaseCommitSHA}, {Name: "Test", Value: "tag-value"}, expectedDefaultCommitTag(commitGitState.BaseCommitSHA)}, }, { - name: "empty commit sha", + name: "parse bad tag template", options: options{ - gitState: GitStateConfig{}, + gitState: prGitState, + Config: buildConfig, + tags: sets.Tags{ + {Name: "BadTagTemplate", Value: `{{ .ASD }}`}, + }, }, expectErr: true, }, + { + name: "parse tags from base64 encoded flag", + options: options{ + gitState: prGitState, + Config: buildConfig, + tagsBase64: base64Tags, + }, + expectedTags: []tags.Tag{ + {Name: "base64testtag", Value: "testtag"}, + {Name: "base64testtemplate", Value: "test-5"}, + expectedDefaultPRTag(prGitState.PullRequestNumber)}, + }, } for _, c := range tc { @@ -616,8 +709,58 @@ func Test_parseTags(t *testing.T) { t.Error("Expected error, but no one occured") } - if !reflect.DeepEqual(tags, c.tags) { - t.Errorf("Got %v, but expected %v", tags, c.tags) + if !reflect.DeepEqual(tags, c.expectedTags) { + t.Errorf("Got %v, but expected %v", tags, c.expectedTags) + } + }) + } +} + +func Test_getDefaultTag(t *testing.T) { + g := NewGomegaWithT(t) + + tests := []struct { + name string + options options + want tags.Tag + wantErr bool + }{ + { + name: "Success - Pull Request", + options: options{ + gitState: prGitState, + Config: buildConfig, + }, + want: defaultPRTag, + wantErr: false, + }, + { + name: "Success - Commit SHA", + options: options{ + gitState: commitGitState, + Config: buildConfig, + }, + want: defaultCommitTag, + wantErr: false, + }, + { + name: "Failure - No PR number or commit SHA", + options: options{ + gitState: GitStateConfig{}, + }, + want: tags.Tag{}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := getDefaultTag(tt.options) + if tt.wantErr { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(got).To(Equal(tt.want)) } }) } diff --git a/configs/kaniko-build-config.yaml b/configs/kaniko-build-config.yaml index 1c490b3bc127..2b894cc314ab 100644 --- a/configs/kaniko-build-config.yaml +++ b/configs/kaniko-build-config.yaml @@ -34,4 +34,4 @@ sign-config: retry-timeout: 10s secret: path: /secret-prod/secret.yaml - type: signify + type: signify \ No newline at end of file diff --git a/pkg/tags/options.go b/pkg/tags/options.go index c8c451cf78fd..580440b9b184 100644 --- a/pkg/tags/options.go +++ b/pkg/tags/options.go @@ -1,15 +1,45 @@ package tags -type TagOption func(o *Tagger) +import ( + "fmt" +) +type TagOption func(o *Tagger) error + +// DateFormat sets Tagger Date field to the given value. +// It returns an error if the given value is empty. func DateFormat(format string) TagOption { - return func(t *Tagger) { + return func(t *Tagger) error { + if len(format) == 0 { + return fmt.Errorf("date format cannot be empty") + } t.Date = t.Time.Format(format) + return nil } } +// CommitSHA sets Tagger CommitSHA field to the given value. +// It also sets the Tagger ShortSHA field to the first 8 characters of the given value. +// It returns an error if the given value is empty. func CommitSHA(sha string) TagOption { - return func(t *Tagger) { + return func(t *Tagger) error { + if len(sha) == 0 { + return fmt.Errorf("sha cannot be empty") + } t.CommitSHA = sha + t.ShortSHA = fmt.Sprintf("%.8s", t.CommitSHA) + return nil + } +} + +// PRNumber sets Tagger PRNumber field to given value. +// It returns error if given value is empty. +func PRNumber(pr string) TagOption { + return func(t *Tagger) error { + if len(pr) == 0 { + return fmt.Errorf("pr number cannot be empty") + } + t.PRNumber = pr + return nil } } diff --git a/pkg/tags/options_test.go b/pkg/tags/options_test.go index 2c0063efaf21..faaa675c66f7 100644 --- a/pkg/tags/options_test.go +++ b/pkg/tags/options_test.go @@ -3,9 +3,47 @@ package tags import ( "testing" "time" + + . "github.com/onsi/gomega" ) -func TestOption_CommitSHA(t *testing.T) { +func TestOption_PRNumber_success(t *testing.T) { + g := NewGomegaWithT(t) + + tc := struct { + pr string + expected string + }{ + pr: "123", + expected: "123", + } + tag := Tagger{} + f := PRNumber(tc.pr) + err := f(&tag) + + g.Expect(err).To(BeNil()) + g.Expect(tag.PRNumber).To(Equal(tc.expected)) +} + +func TestOption_PRNumber_return_error_when_empty_pr(t *testing.T) { + g := NewGomegaWithT(t) + + tc := struct { + pr string + }{ + pr: "", + } + tag := Tagger{} + f := PRNumber(tc.pr) + err := f(&tag) + + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(Equal("pr number cannot be empty")) +} + +func TestOption_CommitSHA_success(t *testing.T) { + g := NewGomegaWithT(t) + tc := struct { sha string expected string @@ -19,12 +57,29 @@ func TestOption_CommitSHA(t *testing.T) { } f := CommitSHA(tc.sha) f(&tag) - if tag.CommitSHA != tc.expected { - t.Errorf("%s != %s", tag.CommitSHA, tc.expected) + + g.Expect(tag.CommitSHA).To(Equal(tc.expected)) +} + +func TestOption_CommitSHA_return_error_when_empty_sha(t *testing.T) { + g := NewGomegaWithT(t) + + tc := struct { + sha string + }{ + sha: "", } + tag := Tagger{} + f := CommitSHA(tc.sha) + err := f(&tag) + + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(Equal("sha cannot be empty")) } -func TestOption_DateFormat(t *testing.T) { +func TestOption_DateFormat_success(t *testing.T) { + g := NewGomegaWithT(t) + now := time.Now() tc := struct { dateFormat string @@ -38,7 +93,24 @@ func TestOption_DateFormat(t *testing.T) { } f := DateFormat(tc.dateFormat) f(&tag) - if tag.Date != tc.expectedDate { - t.Errorf("%s != %s", tag.Date, tc.expectedDate) + + g.Expect(tag.Date).To(Equal(tc.expectedDate)) +} + +func TestOption_DateFormat_return_error_when_empty_date_format(t *testing.T) { + g := NewGomegaWithT(t) + + tc := struct { + dateFormat string + }{ + dateFormat: "", } + tag := Tagger{ + Time: time.Now(), + } + f := DateFormat(tc.dateFormat) + err := f(&tag) + + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(Equal("date format cannot be empty")) } diff --git a/pkg/tags/tag.go b/pkg/tags/tag.go index e42c6ce1307c..97e5c36d8d19 100644 --- a/pkg/tags/tag.go +++ b/pkg/tags/tag.go @@ -4,8 +4,6 @@ import ( "fmt" "regexp" "strings" - - "gopkg.in/yaml.v3" ) // Tag store informations about single Tag @@ -14,6 +12,8 @@ type Tag struct { Name string `yaml:"name" json:"name"` // Value of the tag or template of it Value string `yaml:"value" json:"value"` + // Validation is a regex pattern to validate the tag value after it has been parsed + Validation string `yaml:"validation" json:"validation,omitempty"` } // NewTagFromString creates new Tag from env var style string @@ -36,29 +36,4 @@ func NewTagFromString(val string) (Tag, error) { } return t, nil -} - -// UnmarshalYAML provides custom logic for unmarshalling tag into struct -// If not name is given it will be replaced by default_tag. -// It ensures that both use cases are supported -func (t *Tag) UnmarshalYAML(value *yaml.Node) error { - var tagTemplate string - - if err := value.Decode(&tagTemplate); err == nil { - t.Name = "default_tag" - t.Value = tagTemplate - return nil - } - - var tag map[string]string - - err := value.Decode(&tag) - if err != nil { - return err - } - - t.Name = tag["name"] - t.Value = tag["value"] - - return nil -} +} \ No newline at end of file diff --git a/pkg/tags/tag_test.go b/pkg/tags/tag_test.go index 6defbf1223ac..1a581989d07e 100644 --- a/pkg/tags/tag_test.go +++ b/pkg/tags/tag_test.go @@ -3,8 +3,6 @@ package tags import ( "reflect" "testing" - - "gopkg.in/yaml.v3" ) func TestNewTagFromString(t *testing.T) { @@ -72,51 +70,3 @@ func TestNewTagFromString(t *testing.T) { }) } } - -func TestUnmarshallYAML(t *testing.T) { - tc := []struct { - Name string - Yaml string - ExpectedTag Tag - ExpectErr bool - }{ - { - Name: "parse single value, pass", - Yaml: `tag-template: v{{ .Test }}`, - ExpectedTag: Tag{Name: "default_tag", Value: "v{{ .Test }}"}, - ExpectErr: false, - }, - { - Name: "parse single value, pass", - Yaml: `tag-template: - name: Test - value: v{{ .Test }}`, - ExpectedTag: Tag{Name: "Test", Value: "v{{ .Test }}"}, - ExpectErr: false, - }, - { - Name: "malformed tag, fail", - Yaml: `tag-template:`, - ExpectedTag: Tag{}, - ExpectErr: true, - }, - } - - for _, c := range tc { - t.Run(c.Name, func(t *testing.T) { - var tagStruct struct { - TagTemplate Tag `yaml:"tag-template" json:"tagTemplate"` - } - - err := yaml.Unmarshal([]byte(c.Yaml), &tagStruct) - - if err != nil && !c.ExpectErr { - t.Errorf("got unexpected error, %v", err) - } - - if !reflect.DeepEqual(tagStruct.TagTemplate, c.ExpectedTag) { - t.Errorf("expected %v got %v", c.ExpectedTag, tagStruct.TagTemplate) - } - }) - } -} diff --git a/pkg/tags/tags.go b/pkg/tags/tags.go index 8c627e088ca0..284663776d8b 100644 --- a/pkg/tags/tags.go +++ b/pkg/tags/tags.go @@ -2,33 +2,33 @@ package tags import ( "bytes" - "errors" "fmt" "os" + "regexp" "text/template" "time" ) type Tagger struct { - tags []Tag - CommitSHA, ShortSHA string - Time time.Time - Date string + tags []Tag + CommitSHA string + ShortSHA string + PRNumber string + Time time.Time + Date string } -// TODO (@Ressetkk): Evaluate if we really need to implement it in central way -//func (tg *Tagger) AddFlags(fs *flag.FlagSet) { -// fs.Var(&tg.tags, "tag", "Go-template based tag") -//} - func (tg *Tagger) Env(key string) string { return os.Getenv(key) } func (tg *Tagger) ParseTags() ([]Tag, error) { var parsed []Tag - for _, t := range tg.tags { - tmpl, err := template.New("tag").Parse(t.Value) + for _, tag := range tg.tags { + if len(tag.Name) == 0 || len(tag.Value) == 0 { + return nil, fmt.Errorf("tag name or value is empty, tag name: %s, tag value: %s", tag.Name, tag.Value) + } + tmpl, err := template.New("tag").Parse(tag.Value) if err != nil { return nil, err } @@ -37,9 +37,10 @@ func (tg *Tagger) ParseTags() ([]Tag, error) { if err != nil { return nil, err } - tag := Tag{ - Name: t.Name, - Value: buf.String(), + tag.Value = buf.String() + err = tg.validateTag(tag) + if err != nil { + return nil, err } parsed = append(parsed, tag) } @@ -47,6 +48,21 @@ func (tg *Tagger) ParseTags() ([]Tag, error) { return parsed, nil } +func (tg *Tagger) validateTag(tag Tag) error { + if tag.Name == "default_tag" && len(tag.Validation) == 0 { + return fmt.Errorf("default_tag validation is empty, tag: %s", tag.Value) + } + if tag.Validation != "" { + // Verify PR default tag. Check if value starts with PR- and is followed by a number + re := regexp.MustCompile(tag.Validation) + match := re.FindAllString(tag.Value, -1) + if match == nil { + return fmt.Errorf("tag validation failed, tag: %s, validation: %s", tag.Value, tag.Validation) + } + } + return nil +} + func NewTagger(tags []Tag, opts ...TagOption) (*Tagger, error) { now := time.Now() t := Tagger{ @@ -55,11 +71,10 @@ func NewTagger(tags []Tag, opts ...TagOption) (*Tagger, error) { Date: now.Format("20060102"), } for _, o := range opts { - o(&t) - } - if t.CommitSHA == "" { - return nil, errors.New("variable CommitSHA is empty") + err := o(&t) + if err != nil { + return nil, fmt.Errorf("error applying tag option: %w", err) + } } - t.ShortSHA = fmt.Sprintf("%.8s", t.CommitSHA) return &t, nil }