11package declcfg
22
33import (
4- "bytes"
54 "encoding/json"
65 "errors"
76 "fmt"
87 "io"
98 "io/fs"
109 "path/filepath"
11- "strings"
1210
1311 "github.com/joelanford/ignore"
1412 "github.com/operator-framework/api/pkg/operators"
@@ -22,13 +20,68 @@ const (
2220 indexIgnoreFilename = ".indexignore"
2321)
2422
23+ type WalkMetasFSFunc func (path string , meta * Meta , err error ) error
24+
25+ func WalkMetasFS (root fs.FS , walkFn WalkMetasFSFunc ) error {
26+ return walkFiles (root , func (root fs.FS , path string , err error ) error {
27+ if err != nil {
28+ return walkFn (path , nil , err )
29+ }
30+
31+ f , err := root .Open (path )
32+ if err != nil {
33+ return walkFn (path , nil , err )
34+ }
35+ defer f .Close ()
36+
37+ return WalkMetasReader (f , func (meta * Meta , err error ) error {
38+ return walkFn (path , meta , err )
39+ })
40+ })
41+ }
42+
43+ type WalkMetasReaderFunc func (meta * Meta , err error ) error
44+
45+ func WalkMetasReader (r io.Reader , walkFn WalkMetasReaderFunc ) error {
46+ dec := yaml .NewYAMLOrJSONDecoder (r , 4096 )
47+ for {
48+ var in Meta
49+ if err := dec .Decode (& in ); err != nil {
50+ if errors .Is (err , io .EOF ) {
51+ break
52+ }
53+ return walkFn (nil , err )
54+ }
55+
56+ if err := walkFn (& in , nil ); err != nil {
57+ return err
58+ }
59+ }
60+ return nil
61+ }
62+
2563type WalkFunc func (path string , cfg * DeclarativeConfig , err error ) error
2664
2765// WalkFS walks root using a gitignore-style filename matcher to skip files
2866// that match patterns found in .indexignore files found throughout the filesystem.
2967// It calls walkFn for each declarative config file it finds. If WalkFS encounters
3068// an error loading or parsing any file, the error will be immediately returned.
3169func WalkFS (root fs.FS , walkFn WalkFunc ) error {
70+ return walkFiles (root , func (root fs.FS , path string , err error ) error {
71+ if err != nil {
72+ return walkFn (path , nil , err )
73+ }
74+
75+ cfg , err := LoadFile (root , path )
76+ if err != nil {
77+ return walkFn (path , cfg , err )
78+ }
79+
80+ return walkFn (path , cfg , nil )
81+ })
82+ }
83+
84+ func walkFiles (root fs.FS , fn func (root fs.FS , path string , err error ) error ) error {
3285 if root == nil {
3386 return fmt .Errorf ("no declarative config filesystem provided" )
3487 }
@@ -40,20 +93,15 @@ func WalkFS(root fs.FS, walkFn WalkFunc) error {
4093
4194 return fs .WalkDir (root , "." , func (path string , info fs.DirEntry , err error ) error {
4295 if err != nil {
43- return walkFn ( path , nil , err )
96+ return fn ( root , path , err )
4497 }
4598 // avoid validating a directory, an .indexignore file, or any file that matches
4699 // an ignore pattern outlined in a .indexignore file.
47100 if info .IsDir () || info .Name () == indexIgnoreFilename || matcher .Match (path , false ) {
48101 return nil
49102 }
50103
51- cfg , err := LoadFile (root , path )
52- if err != nil {
53- return walkFn (path , cfg , err )
54- }
55-
56- return walkFn (path , cfg , err )
104+ return fn (root , path , nil )
57105 })
58106}
59107
@@ -123,46 +171,38 @@ func extractCSV(objs []string) string {
123171// Path references will not be de-referenced so callers are responsible for de-referencing if necessary.
124172func LoadReader (r io.Reader ) (* DeclarativeConfig , error ) {
125173 cfg := & DeclarativeConfig {}
126- dec := yaml .NewYAMLOrJSONDecoder (r , 4096 )
127- for {
128- doc := json.RawMessage {}
129- if err := dec .Decode (& doc ); err != nil {
130- if errors .Is (err , io .EOF ) {
131- break
132- }
133- return nil , err
134- }
135- doc = []byte (strings .NewReplacer (`\u003c` , "<" , `\u003e` , ">" , `\u0026` , "&" ).Replace (string (doc )))
136174
137- var in Meta
138- if err := json . Unmarshal ( doc , & in ); err != nil {
139- return nil , fmt . Errorf ( "unmarshal error: %s" , resolveUnmarshalErr ( doc , err ))
175+ if err := WalkMetasReader ( r , func ( in * Meta , err error ) error {
176+ if err != nil {
177+ return err
140178 }
141-
142179 switch in .Schema {
143180 case SchemaPackage :
144181 var p Package
145- if err := json .Unmarshal (doc , & p ); err != nil {
146- return nil , fmt .Errorf ("parse package: %v" , err )
182+ if err := json .Unmarshal (in . Blob , & p ); err != nil {
183+ return fmt .Errorf ("parse package: %v" , err )
147184 }
148185 cfg .Packages = append (cfg .Packages , p )
149186 case SchemaChannel :
150187 var c Channel
151- if err := json .Unmarshal (doc , & c ); err != nil {
152- return nil , fmt .Errorf ("parse channel: %v" , err )
188+ if err := json .Unmarshal (in . Blob , & c ); err != nil {
189+ return fmt .Errorf ("parse channel: %v" , err )
153190 }
154191 cfg .Channels = append (cfg .Channels , c )
155192 case SchemaBundle :
156193 var b Bundle
157- if err := json .Unmarshal (doc , & b ); err != nil {
158- return nil , fmt .Errorf ("parse bundle: %v" , err )
194+ if err := json .Unmarshal (in . Blob , & b ); err != nil {
195+ return fmt .Errorf ("parse bundle: %v" , err )
159196 }
160197 cfg .Bundles = append (cfg .Bundles , b )
161198 case "" :
162- return nil , fmt .Errorf ("object '%s' is missing root schema field" , string (doc ))
199+ return fmt .Errorf ("object '%s' is missing root schema field" , string (in . Blob ))
163200 default :
164- cfg .Others = append (cfg .Others , in )
201+ cfg .Others = append (cfg .Others , * in )
165202 }
203+ return nil
204+ }); err != nil {
205+ return nil , err
166206 }
167207 return cfg , nil
168208}
@@ -187,47 +227,3 @@ func LoadFile(root fs.FS, path string) (*DeclarativeConfig, error) {
187227
188228 return cfg , nil
189229}
190-
191- func resolveUnmarshalErr (data []byte , err error ) string {
192- var te * json.UnmarshalTypeError
193- if errors .As (err , & te ) {
194- return formatUnmarshallErrorString (data , te .Error (), te .Offset )
195- }
196- var se * json.SyntaxError
197- if errors .As (err , & se ) {
198- return formatUnmarshallErrorString (data , se .Error (), se .Offset )
199- }
200- return err .Error ()
201- }
202-
203- func formatUnmarshallErrorString (data []byte , errmsg string , offset int64 ) string {
204- sb := new (strings.Builder )
205- _ , _ = sb .WriteString (fmt .Sprintf ("%s at offset %d (indicated by <==)\n " , errmsg , offset ))
206- // attempt to present the erroneous JSON in indented, human-readable format
207- // errors result in presenting the original, unformatted output
208- var pretty bytes.Buffer
209- err := json .Indent (& pretty , data , "" , " " )
210- if err == nil {
211- pString := pretty .String ()
212- // calc the prettified string offset which correlates to the original string offset
213- var pOffset , origOffset int64
214- origOffset = 0
215- for origOffset = 0 ; origOffset < offset ; {
216- pOffset ++
217- if pString [pOffset ] != '\n' && pString [pOffset ] != ' ' {
218- origOffset ++
219- }
220- }
221- _ , _ = sb .WriteString (pString [:pOffset ])
222- _ , _ = sb .WriteString (" <== " )
223- _ , _ = sb .WriteString (pString [pOffset :])
224- } else {
225- for i := int64 (0 ); i < offset ; i ++ {
226- _ = sb .WriteByte (data [i ])
227- }
228- _ , _ = sb .WriteString (" <== " )
229- _ , _ = sb .Write (data [offset :])
230- }
231-
232- return sb .String ()
233- }
0 commit comments