Skip to content

Commit 8eec8c0

Browse files
committed
Screen files, and output only those updated
This is intended to address two problems: - LocalPackage{Reader,Writer} like to reformat the YAML that passes through them; mostly this is harmless, but occasionally it will end up fighting format tooling, e.g., prettier. - It's possible that things like Helm chart templates are lying around in the git repository to which automation is applied. Those templates have extensions of ".yaml" but are not usually parseable as YAML, so would result in errors from the file reader. This commit changes how updates are run -- firstly, it screens files by checking for a token (`"$imagepolicy"`) that will be present in files that might need updating. This cheaply removes some nodes -- likely including Helm chart templates -- from consideration. Secondly, it now only writes files that were actually updated by an imagepolicy setter, rather than writing everything that was an input. This means it's less likely to reformat something that doesn't need to be touched at all. Signed-off-by: Michael Bridgen <[email protected]>
1 parent 8894a7e commit 8eec8c0

File tree

5 files changed

+160
-26
lines changed

5 files changed

+160
-26
lines changed

pkg/update/filereader.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package update
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"io/ioutil"
7+
"os"
8+
"path/filepath"
9+
10+
"sigs.k8s.io/kustomize/kyaml/kio"
11+
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
12+
"sigs.k8s.io/kustomize/kyaml/yaml"
13+
)
14+
15+
// ScreeningReader is a kio.Reader that includes only files that are
16+
// pertinent to automation. In practice this means looking for a
17+
// particular token in each file, and ignoring those files without the
18+
// token. This avoids most problematic cases -- e.g., templates in a
19+
// Helm chart, which won't parse as YAML -- and cheaply filters for
20+
// only those files that need processing.
21+
type ScreeningLocalReader struct {
22+
Token string
23+
Path string
24+
25+
// This records the relative path of each file that passed
26+
// screening (i.e., contained the token), but couldn't be parsed.
27+
ProblemFiles []string
28+
}
29+
30+
// Read scans the .Path recursively for files that contain .Token, and
31+
// parses any that do. It applies the filename annotation used by
32+
// [`kio.LocalPackageWriter`](https://godoc.org/sigs.k8s.io/kustomize/kyaml/kio#LocalPackageWriter)
33+
// so that the same will write files back to their original
34+
// location. The implementation follows that of
35+
// [LocalPackageReader.Read](https://godoc.org/sigs.k8s.io/kustomize/kyaml/kio#LocalPackageReader.Read),
36+
// adapting lightly (mainly to leave features out).
37+
func (r *ScreeningLocalReader) Read() ([]*yaml.RNode, error) {
38+
if r.Path == "" {
39+
return nil, fmt.Errorf("must supply path to scan for files")
40+
}
41+
42+
root, err := filepath.Abs(r.Path)
43+
if err != nil {
44+
return nil, fmt.Errorf("path field cannot be made absolute: %w", err)
45+
}
46+
47+
// For the filename annotation, I want a directory for filenames
48+
// to be relative to; but I don't know whether path is a directory
49+
// or file yetm so this must wait until the body of the filepath.Walk.
50+
var relativePath string
51+
52+
tokenbytes := []byte(r.Token)
53+
54+
var result []*yaml.RNode
55+
err = filepath.Walk(root, func(p string, info os.FileInfo, err error) error {
56+
if err != nil {
57+
return fmt.Errorf("walking path for files: %w", err)
58+
}
59+
60+
if p == root {
61+
if info.IsDir() {
62+
relativePath = p
63+
return nil // keep walking
64+
}
65+
relativePath = filepath.Dir(p)
66+
}
67+
68+
if info.IsDir() {
69+
return nil
70+
}
71+
72+
if ext := filepath.Ext(p); ext != ".yaml" && ext != ".yml" {
73+
return nil
74+
}
75+
76+
// To check for the token, I need the file contents. This
77+
// assumes the file is encoded as UTF8.
78+
filebytes, err := ioutil.ReadFile(p)
79+
if err != nil {
80+
return fmt.Errorf("reading YAML file: %w", err)
81+
}
82+
83+
if !bytes.Contains(filebytes, tokenbytes) {
84+
return nil
85+
}
86+
87+
path, err := filepath.Rel(relativePath, p)
88+
if err != nil {
89+
return fmt.Errorf("relativising path: %w", err)
90+
}
91+
annotations := map[string]string{
92+
kioutil.PathAnnotation: path,
93+
}
94+
95+
rdr := &kio.ByteReader{
96+
Reader: bytes.NewBuffer(filebytes),
97+
SetAnnotations: annotations,
98+
}
99+
100+
nodes, err := rdr.Read()
101+
// Having screened the file and decided it's worth examining,
102+
// an error at this point is most unfortunate. However, it
103+
// doesn't need to be the end of the matter; we can record
104+
// this file as problematic, and continue.
105+
if err != nil {
106+
r.ProblemFiles = append(r.ProblemFiles, path)
107+
return nil
108+
}
109+
result = append(result, nodes...)
110+
return nil
111+
})
112+
113+
return result, err
114+
}

pkg/update/filereader_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package update
2+
3+
import (
4+
. "github.com/onsi/ginkgo"
5+
. "github.com/onsi/gomega"
6+
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
7+
)
8+
9+
var _ = Describe("load YAMLs with ScreeningLocalReader", func() {
10+
It("loads only the YAMLs containing the token", func() {
11+
r := ScreeningLocalReader{
12+
Path: "testdata/setters/original",
13+
Token: "$imagepolicy",
14+
}
15+
nodes, err := r.Read()
16+
Expect(err).ToNot(HaveOccurred())
17+
// the test fixture has three files that contain the marker:
18+
// - otherns.yaml
19+
// - marked.yaml
20+
// - kustomization.yaml
21+
Expect(len(nodes)).To(Equal(3))
22+
filesSeen := map[string]struct{}{}
23+
for i := range nodes {
24+
path, _, err := kioutil.GetFileAnnotations(nodes[i])
25+
Expect(err).ToNot(HaveOccurred())
26+
filesSeen[path] = struct{}{}
27+
}
28+
Expect(filesSeen).To(Equal(map[string]struct{}{
29+
"marked.yaml": struct{}{},
30+
"kustomization.yaml": struct{}{},
31+
"otherns.yaml": struct{}{},
32+
}))
33+
})
34+
})

pkg/update/setters.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ func resetSchema() {
3636
openapi.SuppressBuiltInSchemaUse()
3737
}
3838

39+
// UpdateWithSetters takes all YAML files from `inpath`, updates any
40+
// that contain an "in scope" image policy marker, and writes files it
41+
// updated (and only those files) back to `outpath`.
3942
func UpdateWithSetters(inpath, outpath string, policies []imagev1alpha1_reflect.ImagePolicy) error {
4043
// the OpenAPI schema is a package variable in kyaml/openapi. In
4144
// lieu of being able to isolate invocations (per
@@ -104,9 +107,9 @@ func UpdateWithSetters(inpath, outpath string, policies []imagev1alpha1_reflect.
104107
}
105108

106109
// get ready with the reader and writer
107-
reader := &kio.LocalPackageReader{
108-
PackagePath: inpath,
109-
IncludeSubpackages: true,
110+
reader := &ScreeningLocalReader{
111+
Path: inpath,
112+
Token: fmt.Sprintf("%q", SetterShortHand),
110113
}
111114
writer := &kio.LocalPackageWriter{
112115
PackagePath: outpath,
@@ -115,7 +118,12 @@ func UpdateWithSetters(inpath, outpath string, policies []imagev1alpha1_reflect.
115118
pipeline := kio.Pipeline{
116119
Inputs: []kio.Reader{reader},
117120
Outputs: []kio.Writer{writer},
118-
Filters: []kio.Filter{kio.FilterAll(&setters2.Set{SetAll: true})},
121+
Filters: []kio.Filter{
122+
setters2.SetAll( // run the enclosed single-node setters2.Filter on all nodes,
123+
// and only include those in files that changed in the output
124+
&setters2.Set{SetAll: true}, // set all images that are in the constructed schema
125+
),
126+
},
119127
}
120128

121129
// go!

pkg/update/testdata/setters/expected/otherns.yaml

Lines changed: 0 additions & 11 deletions
This file was deleted.

pkg/update/testdata/setters/expected/unmarked.yaml

Lines changed: 0 additions & 11 deletions
This file was deleted.

0 commit comments

Comments
 (0)