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 <michael@weave.works>
This commit is contained in:
parent
908f8b775c
commit
df7d570ae5
|
@ -69,7 +69,9 @@ const defaultMessageTemplate = `Update from image update automation`
|
|||
const repoRefKey = ".spec.gitRepository"
|
||||
const imagePolicyKey = ".spec.update.imagePolicy"
|
||||
|
||||
type TemplateValues struct {
|
||||
// TemplateData is the type of the value given to the commit message
|
||||
// template.
|
||||
type TemplateData struct {
|
||||
AutomationObject types.NamespacedName
|
||||
Updated update.Result
|
||||
}
|
||||
|
@ -90,7 +92,7 @@ type ImageUpdateAutomationReconciler struct {
|
|||
func (r *ImageUpdateAutomationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
log := logr.FromContext(ctx)
|
||||
now := time.Now()
|
||||
var templateValues TemplateValues
|
||||
var templateValues TemplateData
|
||||
|
||||
var auto imagev1.ImageUpdateAutomation
|
||||
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
|
|||
|
||||
var errNoChanges error = errors.New("no changes made to working directory")
|
||||
|
||||
func commitAll(ctx context.Context, repo *gogit.Repository, commit *imagev1.CommitSpec, values TemplateValues) (string, error) {
|
||||
func commitAll(ctx context.Context, repo *gogit.Repository, commit *imagev1.CommitSpec, values TemplateData) (string, error) {
|
||||
working, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
|
|
@ -156,6 +156,104 @@ spec:
|
|||
[ci skip]
|
||||
```
|
||||
|
||||
### Commit template data
|
||||
|
||||
The data available to the commit message template have this structure (not reproduced verbatim):
|
||||
|
||||
```go
|
||||
// controllers/imageupdateautomation_controller.go
|
||||
|
||||
// TemplateData is the type of the value given to the commit message
|
||||
// template.
|
||||
type TemplateData struct {
|
||||
AutomationObject struct {
|
||||
Name, Namespace string
|
||||
}
|
||||
Updated update.Result
|
||||
}
|
||||
|
||||
// pkg/update/result.go
|
||||
|
||||
// ImageRef represents the image reference used to replace a field
|
||||
// value in an update.
|
||||
type ImageRef interface {
|
||||
// String returns a string representation of the image ref as it
|
||||
// is used in the update; e.g., "helloworld:v1.0.1"
|
||||
String() string
|
||||
// Identifier returns the tag or digest; e.g., "v1.0.1"
|
||||
Identifier() string
|
||||
// Repository returns the repository component of the ImageRef,
|
||||
// with an implied defaults, e.g., "library/helloworld"
|
||||
Repository() string
|
||||
// Registry returns the registry component of the ImageRef, e.g.,
|
||||
// "index.docker.io"
|
||||
Registry() string
|
||||
// Name gives the fully-qualified reference name, e.g.,
|
||||
// "index.docker.io/library/helloworld:v1.0.1"
|
||||
Name() string
|
||||
}
|
||||
|
||||
// ObjectIdentifier holds the identifying data for a particular
|
||||
// object. This won't always have a name (e.g., a kustomization.yaml).
|
||||
type ObjectIdentifier struct {
|
||||
Name, Namespace, APIVersion, Kind string
|
||||
}
|
||||
|
||||
// Result reports the outcome of an automated update. It has a nested
|
||||
// structure file->objects->images. Different projections (e.g., all
|
||||
// the images, regardless of object) are available via methods.
|
||||
type Result struct {
|
||||
Files map[string]FileResult
|
||||
}
|
||||
|
||||
// FileResult gives the updates in a particular file.
|
||||
type FileResult struct {
|
||||
Objects map[ObjectIdentifier][]ImageRef
|
||||
}
|
||||
```
|
||||
|
||||
These methods are defined on `update.Result`:
|
||||
|
||||
```go
|
||||
// Images returns all the images that were involved in at least one
|
||||
// update.
|
||||
func (r Result) Images() []ImageRef {
|
||||
// ...
|
||||
}
|
||||
|
||||
// Objects returns a map of all the objects against the images updated
|
||||
// within, regardless of which file they appear in.
|
||||
func (r Result) Objects() map[ObjectIdentifier][]ImageRef {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The methods let you range over the objects and images without descending the data structure. Here's
|
||||
an example of using the fields and methods in a template:
|
||||
|
||||
```go
|
||||
commitTemplate := `
|
||||
`Automated image update
|
||||
|
||||
Automation name: {{ .AutomationObject }}
|
||||
|
||||
Files:
|
||||
{{ range $filename, $_ := .Updated.Files -}}
|
||||
- {{ $filename }}
|
||||
{{ end -}}
|
||||
|
||||
Objects:
|
||||
{{ range $resource, $_ := .Updated.Objects -}}
|
||||
- {{ $resource.Kind }} {{ $resource.Name }}
|
||||
{{ end -}}
|
||||
|
||||
Images:
|
||||
{{ range .Updated.Images -}}
|
||||
- {{.}}
|
||||
{{ end -}}
|
||||
`
|
||||
```
|
||||
|
||||
## Status
|
||||
|
||||
The status of an `ImageUpdateAutomation` object records the result of the last automation run.
|
||||
|
|
|
@ -5,23 +5,62 @@ import (
|
|||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// Result reports the outcome of an update. It has a
|
||||
// file->objects->images structure, i.e., from the top level down to the
|
||||
// most detail. Different projections (e.g., all the images) are
|
||||
// available via the methods.
|
||||
// ImageRef represents the image reference used to replace a field
|
||||
// value in an update.
|
||||
type ImageRef interface {
|
||||
// String returns a string representation of the image ref as it
|
||||
// is used in the update; e.g., "helloworld:v1.0.1"
|
||||
String() string
|
||||
// Identifier returns the tag or digest; e.g., "v1.0.1"
|
||||
Identifier() string
|
||||
// Repository returns the repository component of the ImageRef,
|
||||
// with an implied defaults, e.g., "library/helloworld"
|
||||
Repository() string
|
||||
// Registry returns the registry component of the ImageRef, e.g.,
|
||||
// "index.docker.io"
|
||||
Registry() string
|
||||
// Name gives the fully-qualified reference name, e.g.,
|
||||
// "index.docker.io/library/helloworld:v1.0.1"
|
||||
Name() string
|
||||
}
|
||||
|
||||
type imageRef struct {
|
||||
name.Reference
|
||||
}
|
||||
|
||||
// Repository gives the repository component of the image ref.
|
||||
func (i imageRef) Repository() string {
|
||||
return i.Context().RepositoryStr()
|
||||
}
|
||||
|
||||
// Registry gives the registry component of the image ref.
|
||||
func (i imageRef) Registry() string {
|
||||
return i.Context().Registry.String()
|
||||
}
|
||||
|
||||
// ObjectIdentifier holds the identifying data for a particular
|
||||
// object. This won't always have a name (e.g., a kustomization.yaml).
|
||||
type ObjectIdentifier struct {
|
||||
yaml.ResourceIdentifier
|
||||
}
|
||||
|
||||
// Result reports the outcome of an automated update. It has a nested
|
||||
// structure file->objects->images. Different projections (e.g., all
|
||||
// the images, regardless of object) are available via methods.
|
||||
type Result struct {
|
||||
Files map[string]FileResult
|
||||
}
|
||||
|
||||
// FileResult gives the updates in a particular file.
|
||||
type FileResult struct {
|
||||
Objects map[yaml.ResourceIdentifier][]name.Reference
|
||||
Objects map[ObjectIdentifier][]ImageRef
|
||||
}
|
||||
|
||||
// Images returns all the images that were involved in at least one
|
||||
// update.
|
||||
func (r Result) Images() []name.Reference {
|
||||
seen := make(map[name.Reference]struct{})
|
||||
var result []name.Reference
|
||||
func (r Result) Images() []ImageRef {
|
||||
seen := make(map[ImageRef]struct{})
|
||||
var result []ImageRef
|
||||
for _, file := range r.Files {
|
||||
for _, images := range file.Objects {
|
||||
for _, ref := range images {
|
||||
|
@ -37,8 +76,8 @@ func (r Result) Images() []name.Reference {
|
|||
|
||||
// Objects returns a map of all the objects against the images updated
|
||||
// within, regardless of which file they appear in.
|
||||
func (r Result) Objects() map[yaml.ResourceIdentifier][]name.Reference {
|
||||
result := make(map[yaml.ResourceIdentifier][]name.Reference)
|
||||
func (r Result) Objects() map[ObjectIdentifier][]ImageRef {
|
||||
result := make(map[ObjectIdentifier][]ImageRef)
|
||||
for _, file := range r.Files {
|
||||
for res, refs := range file.Objects {
|
||||
result[res] = append(result[res], refs...)
|
||||
|
|
|
@ -7,31 +7,52 @@ import (
|
|||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func mustRef(ref string) name.Reference {
|
||||
func mustRef(ref string) imageRef {
|
||||
r, err := name.ParseReference(ref)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return r
|
||||
return imageRef{r}
|
||||
}
|
||||
|
||||
var _ = Describe("image ref", func() {
|
||||
It("gives each component of an image ref", func() {
|
||||
ref := mustRef("helloworld:v1.0.1")
|
||||
Expect(ref.String()).To(Equal("helloworld:v1.0.1"))
|
||||
Expect(ref.Identifier()).To(Equal("v1.0.1"))
|
||||
Expect(ref.Repository()).To(Equal("library/helloworld"))
|
||||
Expect(ref.Registry()).To(Equal("index.docker.io"))
|
||||
Expect(ref.Name()).To(Equal("index.docker.io/library/helloworld:v1.0.1"))
|
||||
})
|
||||
|
||||
It("deals with hostnames and digests", func() {
|
||||
image := "localhost:5000/org/helloworld@sha256:6745aaad46d795c9836632e1fb62f24b7e7f4c843144da8e47a5465c411a14be"
|
||||
ref := mustRef(image)
|
||||
Expect(ref.String()).To(Equal(image))
|
||||
Expect(ref.Identifier()).To(Equal("sha256:6745aaad46d795c9836632e1fb62f24b7e7f4c843144da8e47a5465c411a14be"))
|
||||
Expect(ref.Repository()).To(Equal("org/helloworld"))
|
||||
Expect(ref.Registry()).To(Equal("localhost:5000"))
|
||||
Expect(ref.Name()).To(Equal(image))
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("update results", func() {
|
||||
|
||||
var result Result
|
||||
objectNames := []yaml.ResourceIdentifier{
|
||||
yaml.ResourceIdentifier{
|
||||
objectNames := []ObjectIdentifier{
|
||||
ObjectIdentifier{yaml.ResourceIdentifier{
|
||||
NameMeta: yaml.NameMeta{Namespace: "ns", Name: "foo"},
|
||||
},
|
||||
yaml.ResourceIdentifier{
|
||||
}},
|
||||
ObjectIdentifier{yaml.ResourceIdentifier{
|
||||
NameMeta: yaml.NameMeta{Namespace: "ns", Name: "bar"},
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
BeforeEach(func() {
|
||||
result = Result{
|
||||
Files: map[string]FileResult{
|
||||
"foo.yaml": {
|
||||
Objects: map[yaml.ResourceIdentifier][]name.Reference{
|
||||
Objects: map[ObjectIdentifier][]ImageRef{
|
||||
objectNames[0]: {
|
||||
mustRef("image:v1.0"),
|
||||
mustRef("other:v2.0"),
|
||||
|
@ -39,7 +60,7 @@ var _ = Describe("update results", func() {
|
|||
},
|
||||
},
|
||||
"bar.yaml": {
|
||||
Objects: map[yaml.ResourceIdentifier][]name.Reference{
|
||||
Objects: map[ObjectIdentifier][]ImageRef{
|
||||
objectNames[1]: {
|
||||
mustRef("image:v1.0"),
|
||||
mustRef("other:v2.0"),
|
||||
|
@ -51,14 +72,14 @@ var _ = Describe("update results", func() {
|
|||
})
|
||||
|
||||
It("deduplicates images", func() {
|
||||
Expect(result.Images()).To(Equal([]name.Reference{
|
||||
Expect(result.Images()).To(Equal([]ImageRef{
|
||||
mustRef("image:v1.0"),
|
||||
mustRef("other:v2.0"),
|
||||
}))
|
||||
})
|
||||
|
||||
It("collects images by object", func() {
|
||||
Expect(result.Objects()).To(Equal(map[yaml.ResourceIdentifier][]name.Reference{
|
||||
Expect(result.Objects()).To(Equal(map[ObjectIdentifier][]ImageRef{
|
||||
objectNames[0]: {
|
||||
mustRef("image:v1.0"),
|
||||
mustRef("other:v2.0"),
|
||||
|
|
|
@ -190,7 +190,7 @@ updates:
|
|||
file, ok := result.Files[update.file]
|
||||
if !ok {
|
||||
file = FileResult{
|
||||
Objects: make(map[yaml.ResourceIdentifier][]name.Reference),
|
||||
Objects: make(map[ObjectIdentifier][]ImageRef),
|
||||
}
|
||||
result.Files[update.file] = file
|
||||
}
|
||||
|
@ -200,18 +200,20 @@ updates:
|
|||
if err != nil {
|
||||
continue updates
|
||||
}
|
||||
id := meta.GetIdentifier()
|
||||
id := ObjectIdentifier{meta.GetIdentifier()}
|
||||
|
||||
name, ok := nameToImage[update.name]
|
||||
if !ok { // this means an update was made that wasn't recorded as being an image
|
||||
continue updates
|
||||
}
|
||||
// if the name and tag of an image are both used, we don't need to record it twice
|
||||
ref := imageRef{name}
|
||||
for _, n := range objects[id] {
|
||||
if n == name {
|
||||
if n == ref {
|
||||
continue updates
|
||||
}
|
||||
}
|
||||
objects[id] = append(objects[id], name)
|
||||
objects[id] = append(objects[id], ref)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -79,13 +79,13 @@ var _ = Describe("Update image via kyaml setters2", func() {
|
|||
result, err := UpdateWithSetters("testdata/setters/original", tmp, policies)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
kustomizeResourceID := yaml.ResourceIdentifier{
|
||||
kustomizeResourceID := ObjectIdentifier{yaml.ResourceIdentifier{
|
||||
TypeMeta: yaml.TypeMeta{
|
||||
APIVersion: "kustomize.config.k8s.io/v1beta1",
|
||||
Kind: "Kustomization",
|
||||
},
|
||||
}
|
||||
markedResourceID := yaml.ResourceIdentifier{
|
||||
}}
|
||||
markedResourceID := ObjectIdentifier{yaml.ResourceIdentifier{
|
||||
TypeMeta: yaml.TypeMeta{
|
||||
APIVersion: "batch/v1beta1",
|
||||
Kind: "CronJob",
|
||||
|
@ -94,20 +94,21 @@ var _ = Describe("Update image via kyaml setters2", func() {
|
|||
Namespace: "bar",
|
||||
Name: "foo",
|
||||
},
|
||||
}
|
||||
expectedImageRef, _ := name.ParseReference("updated:v1.0.1")
|
||||
}}
|
||||
r, _ := name.ParseReference("updated:v1.0.1")
|
||||
expectedImageRef := imageRef{r}
|
||||
|
||||
expectedResult := Result{
|
||||
Files: map[string]FileResult{
|
||||
"kustomization.yaml": {
|
||||
Objects: map[yaml.ResourceIdentifier][]name.Reference{
|
||||
Objects: map[ObjectIdentifier][]ImageRef{
|
||||
kustomizeResourceID: {
|
||||
expectedImageRef,
|
||||
},
|
||||
},
|
||||
},
|
||||
"marked.yaml": {
|
||||
Objects: map[yaml.ResourceIdentifier][]name.Reference{
|
||||
Objects: map[ObjectIdentifier][]ImageRef{
|
||||
markedResourceID: {
|
||||
expectedImageRef,
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue