-
Notifications
You must be signed in to change notification settings - Fork 2.2k
/
processors.go
347 lines (307 loc) · 12.9 KB
/
processors.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package framework
import (
goerrors "errors"
"strings"
"k8s.io/kube-openapi/pkg/validation/spec"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/kio/filters"
"sigs.k8s.io/kustomize/kyaml/openapi"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// SimpleProcessor processes a ResourceList by loading the FunctionConfig into
// the given Config type and then running the provided Filter on the ResourceList.
// The provided Config MAY implement Defaulter and Validator to have Default and Validate
// respectively called between unmarshalling and filter execution.
//
// Typical uses include functions that do not actually require config, and simple functions built
// with a filter that closes over the Config instance to access ResourceList.functionConfig values.
type SimpleProcessor struct {
// Filter is the kio.Filter that will be used to process the ResourceList's items.
// Note that kio.FilterFunc is available to transform a compatible func into a kio.Filter.
Filter kio.Filter
// Config must be a struct capable of receiving the data from ResourceList.functionConfig.
// Filter functions may close over this struct to access its data.
Config interface{}
}
// Process makes SimpleProcessor implement the ResourceListProcessor interface.
// It loads the ResourceList.functionConfig into the provided Config type, applying
// defaulting and validation if supported by Config. It then executes the processor's filter.
func (p SimpleProcessor) Process(rl *ResourceList) error {
if err := LoadFunctionConfig(rl.FunctionConfig, p.Config); err != nil {
return errors.Wrap(err)
}
return errors.Wrap(rl.Filter(p.Filter))
}
// GVKFilterMap is a FilterProvider that resolves Filters through a simple lookup in a map.
// It is intended for use in VersionedAPIProcessor.
type GVKFilterMap map[string]map[string]kio.Filter
// ProviderFor makes GVKFilterMap implement the FilterProvider interface.
// It uses the given apiVersion and kind to do a simple lookup in the map and
// returns an error if no exact match is found.
func (m GVKFilterMap) ProviderFor(apiVersion, kind string) (kio.Filter, error) {
if kind == "" {
return nil, errors.Errorf("kind is required")
}
if apiVersion == "" {
return nil, errors.Errorf("apiVersion is required")
}
var ok bool
var versionMap map[string]kio.Filter
if versionMap, ok = m[kind]; !ok {
return nil, errors.Errorf("kind %q is not supported", kind)
}
var p kio.Filter
if p, ok = versionMap[apiVersion]; !ok {
return nil, errors.Errorf("apiVersion %q is not supported for kind %q", apiVersion, kind)
}
return p, nil
}
// FilterProvider is implemented by types that provide a way to look up which Filter
// should be used to process a ResourceList based on the ApiVersion and Kind of the
// ResourceList.functionConfig in the input. FilterProviders are intended to be used
// as part of VersionedAPIProcessor.
type FilterProvider interface {
// ProviderFor returns the appropriate filter for the given APIVersion and Kind.
ProviderFor(apiVersion, kind string) (kio.Filter, error)
}
// FilterProviderFunc converts a compatible function to a FilterProvider.
type FilterProviderFunc func(apiVersion, kind string) (kio.Filter, error)
// ProviderFor makes FilterProviderFunc implement FilterProvider.
func (f FilterProviderFunc) ProviderFor(apiVersion, kind string) (kio.Filter, error) {
return f(apiVersion, kind)
}
// VersionedAPIProcessor selects the appropriate kio.Filter based on the ApiVersion
// and Kind of the ResourceList.functionConfig in the input.
// It can be used to implement configuration function APIs that evolve over time,
// or create processors that support multiple configuration APIs with a single entrypoint.
// All provided Filters MUST be structs capable of receiving ResourceList.functionConfig data.
// Provided Filters MAY implement Defaulter and Validator to have Default and Validate
// respectively called between unmarshalling and filter execution.
type VersionedAPIProcessor struct {
// FilterProvider resolves a kio.Filter for each supported API, based on its APIVersion and Kind.
// GVKFilterMap is a simple FilterProvider implementation for use here.
FilterProvider FilterProvider
}
// Process makes VersionedAPIProcessor implement the ResourceListProcessor interface.
// It looks up the configuration object to use based on the ApiVersion and Kind of the
// input ResourceList.functionConfig, loads ResourceList.functionConfig into that object,
// invokes Validate and Default if supported, and finally invokes Filter.
func (p *VersionedAPIProcessor) Process(rl *ResourceList) error {
api, err := p.FilterProvider.ProviderFor(extractGVK(rl.FunctionConfig))
if err != nil {
return errors.WrapPrefixf(err, "unable to identify provider for resource")
}
if err := LoadFunctionConfig(rl.FunctionConfig, api); err != nil {
return errors.Wrap(err)
}
return errors.Wrap(rl.Filter(api))
}
// extractGVK returns the apiVersion and kind fields from the given RNodes if it contains
// valid TypeMeta. It returns an empty string if a value is not found.
func extractGVK(src *yaml.RNode) (apiVersion, kind string) {
if src == nil {
return "", ""
}
if versionNode := src.Field("apiVersion"); versionNode != nil {
if a, err := versionNode.Value.String(); err == nil {
apiVersion = strings.TrimSpace(a)
}
}
if kindNode := src.Field("kind"); kindNode != nil {
if k, err := kindNode.Value.String(); err == nil {
kind = strings.TrimSpace(k)
}
}
return apiVersion, kind
}
// LoadFunctionConfig reads a configuration resource from YAML into the provided data structure
// and then prepares it for use by running defaulting and validation on it, if supported.
// ResourceListProcessors should use this function to load ResourceList.functionConfig.
func LoadFunctionConfig(src *yaml.RNode, api interface{}) error {
if api == nil {
return nil
}
if err := yaml.Unmarshal([]byte(src.MustString()), api); err != nil {
return errors.Wrap(err)
}
if d, ok := api.(Defaulter); ok {
if err := d.Default(); err != nil {
return err
}
}
if v, ok := api.(Validator); ok {
return v.Validate()
}
return nil
}
// TemplateProcessor is a ResourceListProcessor based on rendering templates with the data in
// ResourceList.functionConfig. It works as follows:
// - loads ResourceList.functionConfig into TemplateData
// - runs PreProcessFilters
// - renders ResourceTemplates and adds them to ResourceList.items
// - renders PatchTemplates and applies them to ResourceList.items
// - executes a merge on ResourceList.items if configured to
// - runs PostProcessFilters
// The TemplateData struct MAY implement Defaulter and Validator to have Default and Validate
// respectively called between unmarshalling and filter execution.
//
// TemplateProcessor also implements kio.Filter directly and can be used in the construction of
// higher-level processors. For example, you might use TemplateProcessors as the filters for each
// API supported by a VersionedAPIProcessor (see VersionedAPIProcessor examples).
type TemplateProcessor struct {
// TemplateData will will be exposed to all the templates in the processor (unless explicitly
// overridden for a template).
// If TemplateProcessor is used directly as a ResourceListProcessor, TemplateData will contain the
// value of ResourceList.functionConfig.
TemplateData interface{}
// ResourceTemplates returns a list of templates to render into resources.
// If MergeResources is set, any matching resources in ResourceList.items will be used as patches
// modifying the rendered templates. Otherwise, the rendered resources will be appended to
// the input resources as-is.
ResourceTemplates []ResourceTemplate
// PatchTemplates is a list of templates to render into patches that apply to ResourceList.items.
// ResourcePatchTemplate can be used here to patch entire resources.
// ContainerPatchTemplate can be used here to patch specific containers within resources.
PatchTemplates []PatchTemplate
// MergeResources, if set to true, will cause the resources in ResourceList.items to be
// will be applied as patches on any matching resources generated by ResourceTemplates.
MergeResources bool
// PreProcessFilters provides a hook to manipulate the ResourceList's items or config after
// TemplateData has been populated but before template-based filters are applied.
PreProcessFilters []kio.Filter
// PostProcessFilters provides a hook to manipulate the ResourceList's items after template
// filters are applied.
PostProcessFilters []kio.Filter
// AdditionalSchemas is a function that returns a list of schema definitions to add to openapi.
// This enables correct merging of custom resource fields.
AdditionalSchemas SchemaParser
}
type SchemaParser interface {
Parse() ([]*spec.Definitions, error)
}
type SchemaParserFunc func() ([]*spec.Definitions, error)
func (s SchemaParserFunc) Parse() ([]*spec.Definitions, error) {
return s()
}
// Filter implements the kio.Filter interface, enabling you to use TemplateProcessor
// as part of a higher-level ResourceListProcessor like VersionedAPIProcessor.
// It sets up all the features of TemplateProcessors as a pipeline of filters and executes them.
func (tp TemplateProcessor) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) {
if tp.AdditionalSchemas != nil {
defs, err := tp.AdditionalSchemas.Parse()
if err != nil {
return nil, errors.WrapPrefixf(err, "parsing AdditionalSchemas")
}
defer openapi.ResetOpenAPI()
for i := range defs {
openapi.AddDefinitions(*defs[i])
}
}
buf := &kio.PackageBuffer{Nodes: items}
pipeline := kio.Pipeline{
Inputs: []kio.Reader{buf},
Filters: []kio.Filter{
kio.FilterFunc(tp.doPreProcess),
kio.FilterFunc(tp.doResourceTemplates),
kio.FilterFunc(tp.doPatchTemplates),
kio.FilterFunc(tp.doMerge),
kio.FilterFunc(tp.doPostProcess),
},
Outputs: []kio.Writer{buf},
ContinueOnEmptyResult: true,
}
if err := pipeline.Execute(); err != nil {
return nil, err
}
return buf.Nodes, nil
}
// Process implements the ResourceListProcessor interface, enabling you to use TemplateProcessor
// directly as a processor. As a Processor, it loads the ResourceList.functionConfig into the
// TemplateData field, exposing it to all templates by default.
func (tp TemplateProcessor) Process(rl *ResourceList) error {
if err := LoadFunctionConfig(rl.FunctionConfig, tp.TemplateData); err != nil {
return errors.Wrap(err)
}
return errors.Wrap(rl.Filter(tp))
}
// PatchTemplate is implemented by kio.Filters that work by rendering patches and applying them to
// the given resource nodes.
type PatchTemplate interface {
// Filter is a kio.Filter-compliant function that applies PatchTemplate's templates as patches
// on the given resource nodes.
Filter(items []*yaml.RNode) ([]*yaml.RNode, error)
// DefaultTemplateData accepts default data to be used in template rendering when no template
// data was explicitly provided to the PatchTemplate.
DefaultTemplateData(interface{})
}
func (tp *TemplateProcessor) doPreProcess(items []*yaml.RNode) ([]*yaml.RNode, error) {
if tp.PreProcessFilters == nil {
return items, nil
}
for i := range tp.PreProcessFilters {
filter := tp.PreProcessFilters[i]
var err error
items, err = filter.Filter(items)
if err != nil && !goerrors.Is(err, &Results{}) {
return nil, err
}
}
return items, nil
}
func (tp *TemplateProcessor) doMerge(items []*yaml.RNode) ([]*yaml.RNode, error) {
if tp.MergeResources {
return filters.MergeFilter{}.Filter(items)
}
return items, nil
}
func (tp *TemplateProcessor) doPostProcess(items []*yaml.RNode) ([]*yaml.RNode, error) {
if tp.PostProcessFilters == nil {
return items, nil
}
for i := range tp.PostProcessFilters {
filter := tp.PostProcessFilters[i]
var err error
items, err = filter.Filter(items)
if err != nil && !goerrors.Is(err, &Results{}) {
return nil, err
}
}
return items, nil
}
func (tp *TemplateProcessor) doResourceTemplates(items []*yaml.RNode) ([]*yaml.RNode, error) {
if tp.ResourceTemplates == nil {
return items, nil
}
for i := range tp.ResourceTemplates {
tp.ResourceTemplates[i].DefaultTemplateData(tp.TemplateData)
newItems, err := tp.ResourceTemplates[i].Render()
if err != nil {
return nil, err
}
if tp.MergeResources {
// apply inputs as patches -- add the new items to the front of the list
items = append(newItems, items...)
} else {
// assume these are new unique resources--append to the list
items = append(items, newItems...)
}
}
return items, nil
}
func (tp *TemplateProcessor) doPatchTemplates(items []*yaml.RNode) ([]*yaml.RNode, error) {
if tp.PatchTemplates == nil {
return items, nil
}
for i := range tp.PatchTemplates {
// Default the template data for the patch to the processor's data
tp.PatchTemplates[i].DefaultTemplateData(tp.TemplateData)
var err error
if items, err = tp.PatchTemplates[i].Filter(items); err != nil {
return nil, err
}
}
return items, nil
}