Skip to content

Commit 42c31cc

Browse files
authored
Merge pull request #4567 from felixtran39/tagTemplate-tagger
Tag template tagger
2 parents aa85937 + 2dc9e8c commit 42c31cc

File tree

4 files changed

+238
-0
lines changed

4 files changed

+238
-0
lines changed

pkg/skaffold/build/tag/env_template.go

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ func NewEnvTemplateTagger(t string) (Tagger, error) {
4444

4545
// GenerateTag generates a tag from a template referencing environment variables.
4646
func (t *envTemplateTagger) GenerateTag(_, imageName string) (string, error) {
47+
// missingkey=error throws error when map is indexed with an undefined key
4748
tag, err := util.ExecuteEnvTemplate(t.Template.Option("missingkey=error"), map[string]string{
4849
"IMAGE_NAME": imageName,
4950
"DIGEST": "_DEPRECATED_DIGEST_",

pkg/skaffold/build/tag/git_commit_test.go

+53
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,59 @@ func TestGitCommit_GenerateFullyQualifiedImageName(t *testing.T) {
399399
}
400400
}
401401

402+
func TestGitCommit_TagTemplate(t *testing.T) {
403+
gitCommitExample, _ := NewGitCommit("", "CommitSha")
404+
tests := []struct {
405+
description string
406+
template string
407+
customMap map[string]Tagger
408+
expected string
409+
createGitRepo func(string)
410+
subDir string
411+
}{
412+
{
413+
description: "gitCommit component",
414+
template: "{{.FOO}}",
415+
customMap: map[string]Tagger{"FOO": gitCommitExample},
416+
expected: "eefe1b9c44eb0aa87199c9a079f2d48d8eb8baed",
417+
createGitRepo: func(dir string) {
418+
gitInit(t, dir).
419+
write("source.go", "code").
420+
add("source.go").
421+
commit("initial")
422+
},
423+
},
424+
{
425+
description: "gitCommit default component",
426+
template: "{{.GIT}}",
427+
expected: "eefe1b9",
428+
createGitRepo: func(dir string) {
429+
gitInit(t, dir).
430+
write("source.go", "code").
431+
add("source.go").
432+
commit("initial")
433+
},
434+
},
435+
}
436+
for _, test := range tests {
437+
test := test
438+
testutil.Run(t, test.description, func(t *testutil.T) {
439+
tmpDir := t.NewTempDir()
440+
test.createGitRepo(tmpDir.Root())
441+
workspace := tmpDir.Path(test.subDir)
442+
443+
c, err := NewTemplateTagger(test.template, test.customMap)
444+
445+
t.CheckNoError(err)
446+
447+
tag, err := c.GenerateTag(workspace, "test")
448+
449+
t.CheckNoError(err)
450+
t.CheckDeepEqual(test.expected, tag)
451+
})
452+
}
453+
}
454+
402455
func TestGitCommitSubDirectory(t *testing.T) {
403456
testutil.Run(t, "", func(t *testutil.T) {
404457
tmpDir := t.NewTempDir()

pkg/skaffold/build/tag/tag_template.go

+60
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,66 @@ import (
2424
"github.com/sirupsen/logrus"
2525
)
2626

27+
// templateTagger implements Tagger
28+
type templateTagger struct {
29+
Template *template.Template
30+
Components map[string]Tagger
31+
}
32+
33+
// NewTemplateTagger creates a new TemplateTagger
34+
func NewTemplateTagger(t string, components map[string]Tagger) (Tagger, error) {
35+
tmpl, err := ParseTagTemplate(t)
36+
if err != nil {
37+
return nil, fmt.Errorf("parsing template: %w", err)
38+
}
39+
40+
return &templateTagger{
41+
Template: tmpl,
42+
Components: components,
43+
}, nil
44+
}
45+
46+
// GenerateTag generates a tag from a template referencing tagging strategies.
47+
func (t *templateTagger) GenerateTag(workingDir, imageName string) (string, error) {
48+
customMap, err := t.EvaluateComponents(workingDir, imageName)
49+
if err != nil {
50+
return "", err
51+
}
52+
53+
// missingkey=error throws error when map is indexed with an undefined key
54+
tag, err := ExecuteTagTemplate(t.Template.Option("missingkey=error"), customMap)
55+
if err != nil {
56+
return "", err
57+
}
58+
59+
return tag, nil
60+
}
61+
62+
// EvaluateComponents creates a custom mapping of component names to their tagger string representation.
63+
func (t *templateTagger) EvaluateComponents(workingDir, imageName string) (map[string]string, error) {
64+
customMap := map[string]string{}
65+
66+
gitTagger, _ := NewGitCommit("", "")
67+
dateTimeTagger := NewDateTimeTagger("", "")
68+
69+
for k, v := range map[string]Tagger{"GIT": gitTagger, "DATE": dateTimeTagger, "SHA": &ChecksumTagger{}} {
70+
tag, _ := v.GenerateTag(workingDir, imageName)
71+
customMap[k] = tag
72+
}
73+
74+
for k, v := range t.Components {
75+
if _, ok := v.(*templateTagger); ok {
76+
return nil, fmt.Errorf("invalid component specified in tag template: %v", v)
77+
}
78+
tag, err := v.GenerateTag(workingDir, imageName)
79+
if err != nil {
80+
return nil, fmt.Errorf("evaluating tag template component: %w", err)
81+
}
82+
customMap[k] = tag
83+
}
84+
return customMap, nil
85+
}
86+
2787
// ParseTagTemplate is a simple wrapper to parse an tag template.
2888
func ParseTagTemplate(t string) (*template.Template, error) {
2989
return template.New("tagTemplate").Parse(t)

pkg/skaffold/build/tag/tag_template_test.go

+124
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,134 @@ package tag
1818

1919
import (
2020
"testing"
21+
"time"
2122

23+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
2224
"github.com/GoogleContainerTools/skaffold/testutil"
2325
)
2426

27+
func TestTagTemplate_GenerateTag(t *testing.T) {
28+
aLocalTimeStamp := time.Date(2015, 03, 07, 11, 06, 39, 123456789, time.Local)
29+
30+
dateTimeExample := &dateTimeTagger{
31+
Format: "2006-01-02",
32+
TimeZone: "UTC",
33+
timeFn: func() time.Time { return aLocalTimeStamp },
34+
}
35+
36+
envTemplateExample, _ := NewEnvTemplateTagger("{{.FOO}}")
37+
invalidEnvTemplate, _ := NewEnvTemplateTagger("{{.BAR}}")
38+
env := []string{"FOO=BAR"}
39+
40+
tagTemplateExample, _ := NewTemplateTagger("", nil)
41+
42+
tests := []struct {
43+
description string
44+
template string
45+
customMap map[string]Tagger
46+
expected string
47+
shouldErr bool
48+
}{
49+
{
50+
description: "empty template",
51+
},
52+
{
53+
description: "only text",
54+
template: "foo-bar",
55+
expected: "foo-bar",
56+
},
57+
{
58+
description: "only component (dateTime) in template, providing more components than necessary",
59+
template: "{{.FOO}}",
60+
customMap: map[string]Tagger{"FOO": dateTimeExample, "BAR": envTemplateExample},
61+
expected: "2015-03-07",
62+
},
63+
{
64+
description: "envTemplate and sha256 as components",
65+
template: "foo-{{.FOO}}-{{.BAR}}",
66+
customMap: map[string]Tagger{"FOO": envTemplateExample, "BAR": &ChecksumTagger{}},
67+
expected: "foo-BAR-latest",
68+
},
69+
{
70+
description: "using tagTemplate as a component",
71+
template: "{{.FOO}}",
72+
customMap: map[string]Tagger{"FOO": tagTemplateExample},
73+
shouldErr: true,
74+
},
75+
{
76+
description: "faulty component, envTemplate has undefined references",
77+
template: "{{.FOO}}",
78+
customMap: map[string]Tagger{"FOO": invalidEnvTemplate},
79+
shouldErr: true,
80+
},
81+
{
82+
description: "missing required components",
83+
template: "{{.FOO}}",
84+
shouldErr: true,
85+
},
86+
{
87+
description: "default component name SHA",
88+
template: "{{.SHA}}",
89+
expected: "latest",
90+
},
91+
{
92+
description: "override default components",
93+
template: "{{.GIT}}-{{.DATE}}-{{.SHA}}",
94+
customMap: map[string]Tagger{"GIT": dateTimeExample, "DATE": envTemplateExample, "SHA": dateTimeExample},
95+
expected: "2015-03-07-BAR-2015-03-07",
96+
},
97+
}
98+
for _, test := range tests {
99+
testutil.Run(t, test.description, func(t *testutil.T) {
100+
t.Override(&util.OSEnviron, func() []string { return env })
101+
102+
c, err := NewTemplateTagger(test.template, test.customMap)
103+
104+
t.CheckNoError(err)
105+
106+
tag, err := c.GenerateTag(".", "test")
107+
108+
t.CheckErrorAndDeepEqual(test.shouldErr, err, test.expected, tag)
109+
})
110+
}
111+
}
112+
113+
func TestTagTemplate_NewTemplateTagger(t *testing.T) {
114+
tests := []struct {
115+
description string
116+
template string
117+
customMap map[string]Tagger
118+
shouldErr bool
119+
}{
120+
{
121+
description: "valid template with nil map",
122+
template: "{{.FOO}}",
123+
},
124+
{
125+
description: "valid template with atleast one mapping",
126+
template: "{{.FOO}}",
127+
customMap: map[string]Tagger{"FOO": &ChecksumTagger{}},
128+
},
129+
{
130+
description: "invalid template with nil mapping",
131+
template: "{{.FOO",
132+
shouldErr: true,
133+
},
134+
{
135+
description: "invalid template with atleast one mapping",
136+
template: "{{.FOO",
137+
customMap: map[string]Tagger{"FOO": &ChecksumTagger{}},
138+
shouldErr: true,
139+
},
140+
}
141+
for _, test := range tests {
142+
testutil.Run(t, test.description, func(t *testutil.T) {
143+
_, err := NewTemplateTagger(test.template, test.customMap)
144+
t.CheckError(test.shouldErr, err)
145+
})
146+
}
147+
}
148+
25149
func TestTagTemplate_ExecuteTagTemplate(t *testing.T) {
26150
tests := []struct {
27151
description string

0 commit comments

Comments
 (0)