Skip to content

Commit bf3cf4b

Browse files
authored
Merge pull request #642 from fluxcd/resultv2
Introduce ResultV2 for update results
2 parents 1698305 + 1c4db83 commit bf3cf4b

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)