Skip to content

Commit df7d570

Browse files
committed
Give details of template data in spec docs
This explains the data available to the commit message template in the API guide. While writing it, I realised it could be made more convenient, so: - mask external types by embedding them - make the most useful parts of an image ref available using a wrapper struct and interface Signed-off-by: Michael Bridgen <[email protected]>
1 parent 908f8b7 commit df7d570

File tree

6 files changed

+198
-35
lines changed

6 files changed

+198
-35
lines changed

controllers/imageupdateautomation_controller.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ const defaultMessageTemplate = `Update from image update automation`
6969
const repoRefKey = ".spec.gitRepository"
7070
const imagePolicyKey = ".spec.update.imagePolicy"
7171

72-
type TemplateValues struct {
72+
// TemplateData is the type of the value given to the commit message
73+
// template.
74+
type TemplateData struct {
7375
AutomationObject types.NamespacedName
7476
Updated update.Result
7577
}
@@ -90,7 +92,7 @@ type ImageUpdateAutomationReconciler struct {
9092
func (r *ImageUpdateAutomationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
9193
log := logr.FromContext(ctx)
9294
now := time.Now()
93-
var templateValues TemplateValues
95+
var templateValues TemplateData
9496

9597
var auto imagev1.ImageUpdateAutomation
9698
if err := r.Get(ctx, req.NamespacedName, &auto); err != nil {
@@ -358,7 +360,7 @@ func cloneInto(ctx context.Context, access repoAccess, branch, path, impl string
358360

359361
var errNoChanges error = errors.New("no changes made to working directory")
360362

361-
func commitAll(ctx context.Context, repo *gogit.Repository, commit *imagev1.CommitSpec, values TemplateValues) (string, error) {
363+
func commitAll(ctx context.Context, repo *gogit.Repository, commit *imagev1.CommitSpec, values TemplateData) (string, error) {
362364
working, err := repo.Worktree()
363365
if err != nil {
364366
return "", err

docs/spec/v1alpha1/imageupdateautomations.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,104 @@ spec:
156156
[ci skip]
157157
```
158158

159+
### Commit template data
160+
161+
The data available to the commit message template have this structure (not reproduced verbatim):
162+
163+
```go
164+
// controllers/imageupdateautomation_controller.go
165+
166+
// TemplateData is the type of the value given to the commit message
167+
// template.
168+
type TemplateData struct {
169+
AutomationObject struct {
170+
Name, Namespace string
171+
}
172+
Updated update.Result
173+
}
174+
175+
// pkg/update/result.go
176+
177+
// ImageRef represents the image reference used to replace a field
178+
// value in an update.
179+
type ImageRef interface {
180+
// String returns a string representation of the image ref as it
181+
// is used in the update; e.g., "helloworld:v1.0.1"
182+
String() string
183+
// Identifier returns the tag or digest; e.g., "v1.0.1"
184+
Identifier() string
185+
// Repository returns the repository component of the ImageRef,
186+
// with an implied defaults, e.g., "library/helloworld"
187+
Repository() string
188+
// Registry returns the registry component of the ImageRef, e.g.,
189+
// "index.docker.io"
190+
Registry() string
191+
// Name gives the fully-qualified reference name, e.g.,
192+
// "index.docker.io/library/helloworld:v1.0.1"
193+
Name() string
194+
}
195+
196+
// ObjectIdentifier holds the identifying data for a particular
197+
// object. This won't always have a name (e.g., a kustomization.yaml).
198+
type ObjectIdentifier struct {
199+
Name, Namespace, APIVersion, Kind string
200+
}
201+
202+
// Result reports the outcome of an automated update. It has a nested
203+
// structure file->objects->images. Different projections (e.g., all
204+
// the images, regardless of object) are available via methods.
205+
type Result struct {
206+
Files map[string]FileResult
207+
}
208+
209+
// FileResult gives the updates in a particular file.
210+
type FileResult struct {
211+
Objects map[ObjectIdentifier][]ImageRef
212+
}
213+
```
214+
215+
These methods are defined on `update.Result`:
216+
217+
```go
218+
// Images returns all the images that were involved in at least one
219+
// update.
220+
func (r Result) Images() []ImageRef {
221+
// ...
222+
}
223+
224+
// Objects returns a map of all the objects against the images updated
225+
// within, regardless of which file they appear in.
226+
func (r Result) Objects() map[ObjectIdentifier][]ImageRef {
227+
// ...
228+
}
229+
```
230+
231+
The methods let you range over the objects and images without descending the data structure. Here's
232+
an example of using the fields and methods in a template:
233+
234+
```go
235+
commitTemplate := `
236+
`Automated image update
237+
238+
Automation name: {{ .AutomationObject }}
239+
240+
Files:
241+
{{ range $filename, $_ := .Updated.Files -}}
242+
- {{ $filename }}
243+
{{ end -}}
244+
245+
Objects:
246+
{{ range $resource, $_ := .Updated.Objects -}}
247+
- {{ $resource.Kind }} {{ $resource.Name }}
248+
{{ end -}}
249+
250+
Images:
251+
{{ range .Updated.Images -}}
252+
- {{.}}
253+
{{ end -}}
254+
`
255+
```
256+
159257
## Status
160258

161259
The status of an `ImageUpdateAutomation` object records the result of the last automation run.

pkg/update/result.go

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,62 @@ import (
55
"sigs.k8s.io/kustomize/kyaml/yaml"
66
)
77

8-
// Result reports the outcome of an update. It has a
9-
// file->objects->images structure, i.e., from the top level down to the
10-
// most detail. Different projections (e.g., all the images) are
11-
// available via the methods.
8+
// ImageRef represents the image reference used to replace a field
9+
// value in an update.
10+
type ImageRef interface {
11+
// String returns a string representation of the image ref as it
12+
// is used in the update; e.g., "helloworld:v1.0.1"
13+
String() string
14+
// Identifier returns the tag or digest; e.g., "v1.0.1"
15+
Identifier() string
16+
// Repository returns the repository component of the ImageRef,
17+
// with an implied defaults, e.g., "library/helloworld"
18+
Repository() string
19+
// Registry returns the registry component of the ImageRef, e.g.,
20+
// "index.docker.io"
21+
Registry() string
22+
// Name gives the fully-qualified reference name, e.g.,
23+
// "index.docker.io/library/helloworld:v1.0.1"
24+
Name() string
25+
}
26+
27+
type imageRef struct {
28+
name.Reference
29+
}
30+
31+
// Repository gives the repository component of the image ref.
32+
func (i imageRef) Repository() string {
33+
return i.Context().RepositoryStr()
34+
}
35+
36+
// Registry gives the registry component of the image ref.
37+
func (i imageRef) Registry() string {
38+
return i.Context().Registry.String()
39+
}
40+
41+
// ObjectIdentifier holds the identifying data for a particular
42+
// object. This won't always have a name (e.g., a kustomization.yaml).
43+
type ObjectIdentifier struct {
44+
yaml.ResourceIdentifier
45+
}
46+
47+
// Result reports the outcome of an automated update. It has a nested
48+
// structure file->objects->images. Different projections (e.g., all
49+
// the images, regardless of object) are available via methods.
1250
type Result struct {
1351
Files map[string]FileResult
1452
}
1553

54+
// FileResult gives the updates in a particular file.
1655
type FileResult struct {
17-
Objects map[yaml.ResourceIdentifier][]name.Reference
56+
Objects map[ObjectIdentifier][]ImageRef
1857
}
1958

2059
// Images returns all the images that were involved in at least one
2160
// update.
22-
func (r Result) Images() []name.Reference {
23-
seen := make(map[name.Reference]struct{})
24-
var result []name.Reference
61+
func (r Result) Images() []ImageRef {
62+
seen := make(map[ImageRef]struct{})
63+
var result []ImageRef
2564
for _, file := range r.Files {
2665
for _, images := range file.Objects {
2766
for _, ref := range images {
@@ -37,8 +76,8 @@ func (r Result) Images() []name.Reference {
3776

3877
// Objects returns a map of all the objects against the images updated
3978
// within, regardless of which file they appear in.
40-
func (r Result) Objects() map[yaml.ResourceIdentifier][]name.Reference {
41-
result := make(map[yaml.ResourceIdentifier][]name.Reference)
79+
func (r Result) Objects() map[ObjectIdentifier][]ImageRef {
80+
result := make(map[ObjectIdentifier][]ImageRef)
4281
for _, file := range r.Files {
4382
for res, refs := range file.Objects {
4483
result[res] = append(result[res], refs...)

pkg/update/result_test.go

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,60 @@ import (
77
"sigs.k8s.io/kustomize/kyaml/yaml"
88
)
99

10-
func mustRef(ref string) name.Reference {
10+
func mustRef(ref string) imageRef {
1111
r, err := name.ParseReference(ref)
1212
if err != nil {
1313
panic(err)
1414
}
15-
return r
15+
return imageRef{r}
1616
}
1717

18+
var _ = Describe("image ref", func() {
19+
It("gives each component of an image ref", func() {
20+
ref := mustRef("helloworld:v1.0.1")
21+
Expect(ref.String()).To(Equal("helloworld:v1.0.1"))
22+
Expect(ref.Identifier()).To(Equal("v1.0.1"))
23+
Expect(ref.Repository()).To(Equal("library/helloworld"))
24+
Expect(ref.Registry()).To(Equal("index.docker.io"))
25+
Expect(ref.Name()).To(Equal("index.docker.io/library/helloworld:v1.0.1"))
26+
})
27+
28+
It("deals with hostnames and digests", func() {
29+
image := "localhost:5000/org/helloworld@sha256:6745aaad46d795c9836632e1fb62f24b7e7f4c843144da8e47a5465c411a14be"
30+
ref := mustRef(image)
31+
Expect(ref.String()).To(Equal(image))
32+
Expect(ref.Identifier()).To(Equal("sha256:6745aaad46d795c9836632e1fb62f24b7e7f4c843144da8e47a5465c411a14be"))
33+
Expect(ref.Repository()).To(Equal("org/helloworld"))
34+
Expect(ref.Registry()).To(Equal("localhost:5000"))
35+
Expect(ref.Name()).To(Equal(image))
36+
})
37+
})
38+
1839
var _ = Describe("update results", func() {
1940

2041
var result Result
21-
objectNames := []yaml.ResourceIdentifier{
22-
yaml.ResourceIdentifier{
42+
objectNames := []ObjectIdentifier{
43+
ObjectIdentifier{yaml.ResourceIdentifier{
2344
NameMeta: yaml.NameMeta{Namespace: "ns", Name: "foo"},
24-
},
25-
yaml.ResourceIdentifier{
45+
}},
46+
ObjectIdentifier{yaml.ResourceIdentifier{
2647
NameMeta: yaml.NameMeta{Namespace: "ns", Name: "bar"},
27-
},
48+
}},
2849
}
2950

3051
BeforeEach(func() {
3152
result = Result{
3253
Files: map[string]FileResult{
3354
"foo.yaml": {
34-
Objects: map[yaml.ResourceIdentifier][]name.Reference{
55+
Objects: map[ObjectIdentifier][]ImageRef{
3556
objectNames[0]: {
3657
mustRef("image:v1.0"),
3758
mustRef("other:v2.0"),
3859
},
3960
},
4061
},
4162
"bar.yaml": {
42-
Objects: map[yaml.ResourceIdentifier][]name.Reference{
63+
Objects: map[ObjectIdentifier][]ImageRef{
4364
objectNames[1]: {
4465
mustRef("image:v1.0"),
4566
mustRef("other:v2.0"),
@@ -51,14 +72,14 @@ var _ = Describe("update results", func() {
5172
})
5273

5374
It("deduplicates images", func() {
54-
Expect(result.Images()).To(Equal([]name.Reference{
75+
Expect(result.Images()).To(Equal([]ImageRef{
5576
mustRef("image:v1.0"),
5677
mustRef("other:v2.0"),
5778
}))
5879
})
5980

6081
It("collects images by object", func() {
61-
Expect(result.Objects()).To(Equal(map[yaml.ResourceIdentifier][]name.Reference{
82+
Expect(result.Objects()).To(Equal(map[ObjectIdentifier][]ImageRef{
6283
objectNames[0]: {
6384
mustRef("image:v1.0"),
6485
mustRef("other:v2.0"),

pkg/update/setters.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ updates:
190190
file, ok := result.Files[update.file]
191191
if !ok {
192192
file = FileResult{
193-
Objects: make(map[yaml.ResourceIdentifier][]name.Reference),
193+
Objects: make(map[ObjectIdentifier][]ImageRef),
194194
}
195195
result.Files[update.file] = file
196196
}
@@ -200,18 +200,20 @@ updates:
200200
if err != nil {
201201
continue updates
202202
}
203-
id := meta.GetIdentifier()
203+
id := ObjectIdentifier{meta.GetIdentifier()}
204+
204205
name, ok := nameToImage[update.name]
205206
if !ok { // this means an update was made that wasn't recorded as being an image
206207
continue updates
207208
}
208209
// if the name and tag of an image are both used, we don't need to record it twice
210+
ref := imageRef{name}
209211
for _, n := range objects[id] {
210-
if n == name {
212+
if n == ref {
211213
continue updates
212214
}
213215
}
214-
objects[id] = append(objects[id], name)
216+
objects[id] = append(objects[id], ref)
215217
}
216218
return result
217219
}

pkg/update/update_test.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,13 @@ var _ = Describe("Update image via kyaml setters2", func() {
7979
result, err := UpdateWithSetters("testdata/setters/original", tmp, policies)
8080
Expect(err).ToNot(HaveOccurred())
8181

82-
kustomizeResourceID := yaml.ResourceIdentifier{
82+
kustomizeResourceID := ObjectIdentifier{yaml.ResourceIdentifier{
8383
TypeMeta: yaml.TypeMeta{
8484
APIVersion: "kustomize.config.k8s.io/v1beta1",
8585
Kind: "Kustomization",
8686
},
87-
}
88-
markedResourceID := yaml.ResourceIdentifier{
87+
}}
88+
markedResourceID := ObjectIdentifier{yaml.ResourceIdentifier{
8989
TypeMeta: yaml.TypeMeta{
9090
APIVersion: "batch/v1beta1",
9191
Kind: "CronJob",
@@ -94,20 +94,21 @@ var _ = Describe("Update image via kyaml setters2", func() {
9494
Namespace: "bar",
9595
Name: "foo",
9696
},
97-
}
98-
expectedImageRef, _ := name.ParseReference("updated:v1.0.1")
97+
}}
98+
r, _ := name.ParseReference("updated:v1.0.1")
99+
expectedImageRef := imageRef{r}
99100

100101
expectedResult := Result{
101102
Files: map[string]FileResult{
102103
"kustomization.yaml": {
103-
Objects: map[yaml.ResourceIdentifier][]name.Reference{
104+
Objects: map[ObjectIdentifier][]ImageRef{
104105
kustomizeResourceID: {
105106
expectedImageRef,
106107
},
107108
},
108109
},
109110
"marked.yaml": {
110-
Objects: map[yaml.ResourceIdentifier][]name.Reference{
111+
Objects: map[ObjectIdentifier][]ImageRef{
111112
markedResourceID: {
112113
expectedImageRef,
113114
},

0 commit comments

Comments
 (0)