Skip to content

Commit 1c4db83

Browse files
committed
Introduce ResultV2 for update results
ResultV2 update result contains Result (original), which provides information in terms of files, objects and images, and also includes file changes which provides information in terms of files, objects and changes. The changes contain the old value, new value and the setter that was involved in the update. ResultV2 can be used to obtain detailed information about updates in terms of the previous value and the new value, which could be beneficial in commit messages to summarize the granular changes. Signed-off-by: Sunny <[email protected]>
1 parent 1698305 commit 1c4db83

File tree

4 files changed

+207
-12
lines changed

4 files changed

+207
-12
lines changed

pkg/update/result.go

+63
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,66 @@ func (r Result) Objects() map[ObjectIdentifier][]ImageRef {
9696
}
9797
return result
9898
}
99+
100+
// ResultV2 contains Result of update and also the file changes made during the
101+
// update. This extends the Result to include details about the exact changes
102+
// made to the files and the objects in them. It has a nested structure
103+
// file->objects->changes.
104+
type ResultV2 struct {
105+
ImageResult Result
106+
FileChanges map[string]ObjectChanges
107+
}
108+
109+
// ObjectChanges contains all the changes made to objects.
110+
type ObjectChanges map[ObjectIdentifier][]Change
111+
112+
// Change contains the setter that resulted in a Change, the old and the new
113+
// value after the Change.
114+
type Change struct {
115+
OldValue string
116+
NewValue string
117+
Setter string
118+
}
119+
120+
// AddChange adds changes to Resultv2 for a given file, object and changes
121+
// associated with it.
122+
func (r *ResultV2) AddChange(file string, objectID ObjectIdentifier, changes ...Change) {
123+
if r.FileChanges == nil {
124+
r.FileChanges = map[string]ObjectChanges{}
125+
}
126+
// Create an entry for the file if not present.
127+
_, ok := r.FileChanges[file]
128+
if !ok {
129+
r.FileChanges[file] = ObjectChanges{}
130+
}
131+
// Append to the changes for the object.
132+
r.FileChanges[file][objectID] = append(r.FileChanges[file][objectID], changes...)
133+
}
134+
135+
// Changes returns all the changes that were made in at least one update.
136+
func (r ResultV2) Changes() []Change {
137+
seen := make(map[Change]struct{})
138+
var result []Change
139+
for _, objChanges := range r.FileChanges {
140+
for _, changes := range objChanges {
141+
for _, change := range changes {
142+
if _, ok := seen[change]; !ok {
143+
seen[change] = struct{}{}
144+
result = append(result, change)
145+
}
146+
}
147+
}
148+
}
149+
return result
150+
}
151+
152+
// Objects returns ObjectChanges, regardless of which file they appear in.
153+
func (r ResultV2) Objects() ObjectChanges {
154+
result := make(ObjectChanges)
155+
for _, objChanges := range r.FileChanges {
156+
for obj, change := range objChanges {
157+
result[obj] = change
158+
}
159+
}
160+
return result
161+
}

pkg/update/result_test.go

+77
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,80 @@ func TestUpdateResults(t *testing.T) {
9292
},
9393
}))
9494
}
95+
96+
func TestResultV2(t *testing.T) {
97+
g := NewWithT(t)
98+
99+
var result ResultV2
100+
objectNames := []ObjectIdentifier{
101+
{yaml.ResourceIdentifier{
102+
NameMeta: yaml.NameMeta{Namespace: "ns", Name: "foo"},
103+
}},
104+
{yaml.ResourceIdentifier{
105+
NameMeta: yaml.NameMeta{Namespace: "ns", Name: "bar"},
106+
}},
107+
}
108+
109+
result.AddChange("foo.yaml", objectNames[0], Change{
110+
OldValue: "aaa",
111+
NewValue: "bbb",
112+
Setter: "foo-ns:policy:name",
113+
})
114+
result.AddChange("bar.yaml", objectNames[1], Change{
115+
OldValue: "cccc:v1.0",
116+
NewValue: "cccc:v1.2",
117+
Setter: "foo-ns:policy",
118+
})
119+
120+
result = ResultV2{
121+
FileChanges: map[string]ObjectChanges{
122+
"foo.yaml": {
123+
objectNames[0]: []Change{
124+
{
125+
OldValue: "aaa",
126+
NewValue: "bbb",
127+
Setter: "foo-ns:policy:name",
128+
},
129+
},
130+
},
131+
"bar.yaml": {
132+
objectNames[1]: []Change{
133+
{
134+
OldValue: "cccc:v1.0",
135+
NewValue: "cccc:v1.2",
136+
Setter: "foo-ns:policy",
137+
},
138+
},
139+
},
140+
},
141+
}
142+
143+
g.Expect(result.Changes()).To(ContainElements([]Change{
144+
{
145+
OldValue: "aaa",
146+
NewValue: "bbb",
147+
Setter: "foo-ns:policy:name",
148+
},
149+
{
150+
OldValue: "cccc:v1.0",
151+
NewValue: "cccc:v1.2",
152+
Setter: "foo-ns:policy",
153+
},
154+
}))
155+
g.Expect(result.Objects()).To(Equal(ObjectChanges{
156+
objectNames[0]: []Change{
157+
{
158+
OldValue: "aaa",
159+
NewValue: "bbb",
160+
Setter: "foo-ns:policy:name",
161+
},
162+
},
163+
objectNames[1]: []Change{
164+
{
165+
OldValue: "cccc:v1.0",
166+
NewValue: "cccc:v1.2",
167+
Setter: "foo-ns:policy",
168+
},
169+
},
170+
}))
171+
}

pkg/update/setters.go

+29-6
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ func init() {
5656
// that contain an "in scope" image policy marker, and writes files it
5757
// updated (and only those files) back to `outpath`.
5858
func UpdateWithSetters(tracelog logr.Logger, inpath, outpath string, policies []imagev1_reflect.ImagePolicy) (Result, error) {
59+
result, err := UpdateV2WithSetters(tracelog, inpath, outpath, policies)
60+
return result.ImageResult, err
61+
}
62+
63+
// UpdateV2WithSetters takes all YAML files from `inpath`, updates any
64+
// that contain an "in scope" image policy marker, and writes files it
65+
// updated (and only those files) back to `outpath`. It also returns the result
66+
// of the changes it made as ResultV2.
67+
func UpdateV2WithSetters(tracelog logr.Logger, inpath, outpath string, policies []imagev1_reflect.ImagePolicy) (ResultV2, error) {
5968
// the OpenAPI schema is a package variable in kyaml/openapi. In
6069
// lieu of being able to isolate invocations (per
6170
// https://github.com/kubernetes-sigs/kustomize/issues/3058), I
@@ -99,13 +108,15 @@ func UpdateWithSetters(tracelog logr.Logger, inpath, outpath string, policies []
99108
Files: make(map[string]FileResult),
100109
}
101110

111+
var resultV2 ResultV2
112+
102113
// Compilng the result needs the file, the image ref used, and the
103114
// object. Each setter will supply its own name to its callback,
104115
// which can be used to look up the image ref; the file and object
105116
// we will get from `setAll` which keeps track of those as it
106117
// iterates.
107118
imageRefs := make(map[string]imageRef)
108-
setAllCallback := func(file, setterName string, node *yaml.RNode) {
119+
setAllCallback := func(file, setterName string, node *yaml.RNode, old, new string) {
109120
ref, ok := imageRefs[setterName]
110121
if !ok {
111122
return
@@ -117,6 +128,15 @@ func UpdateWithSetters(tracelog logr.Logger, inpath, outpath string, policies []
117128
}
118129
oid := ObjectIdentifier{meta.GetIdentifier()}
119130

131+
// Record the change.
132+
ch := Change{
133+
OldValue: old,
134+
NewValue: new,
135+
Setter: setterName,
136+
}
137+
// Append the change for the file and identifier.
138+
resultV2.AddChange(file, oid, ch)
139+
120140
fileres, ok := result.Files[file]
121141
if !ok {
122142
fileres = FileResult{
@@ -148,7 +168,7 @@ func UpdateWithSetters(tracelog logr.Logger, inpath, outpath string, policies []
148168
image := policy.Status.LatestImage
149169
r, err := name.ParseReference(image, name.WeakValidation)
150170
if err != nil {
151-
return Result{}, fmt.Errorf("encountered invalid image ref %q: %w", policy.Status.LatestImage, err)
171+
return ResultV2{}, fmt.Errorf("encountered invalid image ref %q: %w", policy.Status.LatestImage, err)
152172
}
153173
ref := imageRef{
154174
Reference: r,
@@ -204,9 +224,12 @@ func UpdateWithSetters(tracelog logr.Logger, inpath, outpath string, policies []
204224
// go!
205225
err := pipeline.Execute()
206226
if err != nil {
207-
return Result{}, err
227+
return ResultV2{}, err
208228
}
209-
return result, nil
229+
230+
// Combine the results.
231+
resultV2.ImageResult = result
232+
return resultV2, nil
210233
}
211234

212235
// setAll returns a kio.Filter using the supplied SetAllCallback
@@ -215,7 +238,7 @@ func UpdateWithSetters(tracelog logr.Logger, inpath, outpath string, policies []
215238
// files with changed nodes. This is based on
216239
// [`SetAll`](https://github.com/kubernetes-sigs/kustomize/blob/kyaml/v0.10.16/kyaml/setters2/set.go#L503
217240
// from kyaml/kio.
218-
func setAll(schema *spec.Schema, tracelog logr.Logger, callback func(file, setterName string, node *yaml.RNode)) kio.Filter {
241+
func setAll(schema *spec.Schema, tracelog logr.Logger, callback func(file, setterName string, node *yaml.RNode, old, new string)) kio.Filter {
219242
filter := &SetAllCallback{
220243
SettersSchema: schema,
221244
Trace: tracelog,
@@ -231,7 +254,7 @@ func setAll(schema *spec.Schema, tracelog logr.Logger, callback func(file, sette
231254

232255
filter.Callback = func(setter, oldValue, newValue string) {
233256
if newValue != oldValue {
234-
callback(path, setter, nodes[i])
257+
callback(path, setter, nodes[i], oldValue, newValue)
235258
filesToUpdate.Insert(path)
236259
}
237260
}

pkg/update/update_test.go

+38-6
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ limitations under the License.
1717
package update
1818

1919
import (
20-
"io/ioutil"
21-
"os"
2220
"testing"
2321

2422
"github.com/go-logr/logr"
@@ -56,10 +54,7 @@ func TestUpdateWithSetters(t *testing.T) {
5654
},
5755
}
5856

59-
tmp, err := ioutil.TempDir("", "gotest")
60-
g.Expect(err).ToNot(HaveOccurred())
61-
defer os.RemoveAll(tmp)
62-
57+
tmp := t.TempDir()
6358
result, err := UpdateWithSetters(logr.Discard(), "testdata/setters/original", tmp, policies)
6459
g.Expect(err).ToNot(HaveOccurred())
6560
test.ExpectMatchingDirectories(g, tmp, "testdata/setters/expected")
@@ -106,4 +101,41 @@ func TestUpdateWithSetters(t *testing.T) {
106101
}
107102

108103
g.Expect(result).To(Equal(expectedResult))
104+
105+
// Test ResultV2.
106+
tmp2 := t.TempDir()
107+
resultV2, err := UpdateV2WithSetters(logr.Discard(), "testdata/setters/original", tmp2, policies)
108+
g.Expect(err).ToNot(HaveOccurred())
109+
test.ExpectMatchingDirectories(g, tmp2, "testdata/setters/expected")
110+
111+
expectedResultV2 := ResultV2{
112+
ImageResult: expectedResult,
113+
FileChanges: map[string]ObjectChanges{
114+
"kustomization.yaml": {
115+
kustomizeResourceID: []Change{
116+
{
117+
OldValue: "replaced",
118+
NewValue: "index.repo.fake/updated",
119+
Setter: "automation-ns:policy:name",
120+
},
121+
{
122+
OldValue: "v1",
123+
NewValue: "v1.0.1",
124+
Setter: "automation-ns:policy:tag",
125+
},
126+
},
127+
},
128+
"marked.yaml": {
129+
markedResourceID: []Change{
130+
{
131+
OldValue: "image:v1.0.0",
132+
NewValue: "index.repo.fake/updated:v1.0.1",
133+
Setter: "automation-ns:policy",
134+
},
135+
},
136+
},
137+
},
138+
}
139+
140+
g.Expect(resultV2).To(Equal(expectedResultV2))
109141
}

0 commit comments

Comments
 (0)