1
1
package declcfg
2
2
3
3
import (
4
- "bytes"
5
4
"encoding/json"
6
5
"errors"
7
6
"fmt"
8
7
"io"
9
8
"io/fs"
10
9
"path/filepath"
11
- "strings"
12
10
13
11
"github.com/joelanford/ignore"
14
12
"github.com/operator-framework/api/pkg/operators"
@@ -22,13 +20,68 @@ const (
22
20
indexIgnoreFilename = ".indexignore"
23
21
)
24
22
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
+
25
63
type WalkFunc func (path string , cfg * DeclarativeConfig , err error ) error
26
64
27
65
// WalkFS walks root using a gitignore-style filename matcher to skip files
28
66
// that match patterns found in .indexignore files found throughout the filesystem.
29
67
// It calls walkFn for each declarative config file it finds. If WalkFS encounters
30
68
// an error loading or parsing any file, the error will be immediately returned.
31
69
func 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 {
32
85
if root == nil {
33
86
return fmt .Errorf ("no declarative config filesystem provided" )
34
87
}
@@ -40,20 +93,15 @@ func WalkFS(root fs.FS, walkFn WalkFunc) error {
40
93
41
94
return fs .WalkDir (root , "." , func (path string , info fs.DirEntry , err error ) error {
42
95
if err != nil {
43
- return walkFn ( path , nil , err )
96
+ return fn ( root , path , err )
44
97
}
45
98
// avoid validating a directory, an .indexignore file, or any file that matches
46
99
// an ignore pattern outlined in a .indexignore file.
47
100
if info .IsDir () || info .Name () == indexIgnoreFilename || matcher .Match (path , false ) {
48
101
return nil
49
102
}
50
103
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 )
57
105
})
58
106
}
59
107
@@ -123,46 +171,38 @@ func extractCSV(objs []string) string {
123
171
// Path references will not be de-referenced so callers are responsible for de-referencing if necessary.
124
172
func LoadReader (r io.Reader ) (* DeclarativeConfig , error ) {
125
173
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 )))
136
174
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
140
178
}
141
-
142
179
switch in .Schema {
143
180
case SchemaPackage :
144
181
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 )
147
184
}
148
185
cfg .Packages = append (cfg .Packages , p )
149
186
case SchemaChannel :
150
187
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 )
153
190
}
154
191
cfg .Channels = append (cfg .Channels , c )
155
192
case SchemaBundle :
156
193
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 )
159
196
}
160
197
cfg .Bundles = append (cfg .Bundles , b )
161
198
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 ))
163
200
default :
164
- cfg .Others = append (cfg .Others , in )
201
+ cfg .Others = append (cfg .Others , * in )
165
202
}
203
+ return nil
204
+ }); err != nil {
205
+ return nil , err
166
206
}
167
207
return cfg , nil
168
208
}
@@ -187,47 +227,3 @@ func LoadFile(root fs.FS, path string) (*DeclarativeConfig, error) {
187
227
188
228
return cfg , nil
189
229
}
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