Skip to content

Commit 20bb4f8

Browse files
authored
kpt fn render ensures validators don't mutate resources (#1896)
* kpt fn render ensures validators don't mutate resources
1 parent 47bbdb3 commit 20bb4f8

File tree

5 files changed

+116
-17
lines changed

5 files changed

+116
-17
lines changed

e2e/testdata/fn-render/validate-generated-resource/Kptfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ metadata:
44
name: db
55
pipeline:
66
mutators:
7-
- image: gcr.io/kpt-fn/starlark:unstable # generates httpbin deployment
8-
configPath: starlark-httpbin-gen.yaml
7+
- image: gcr.io/kpt-fn/starlark:unstable # generates httpbin deployment
8+
configPath: starlark-httpbin-gen.yaml
99
validators:
1010
- image: gcr.io/kpt-fn/starlark:unstable # validates httpbin deployment exists
1111
configPath: starlark-httpbin-val.yaml
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.expected
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
apiVersion: kpt.dev/v1alpha2
2+
kind: Kptfile
3+
metadata:
4+
name: app
5+
pipeline:
6+
validators:
7+
# mutating function is being used as validator
8+
- image: gcr.io/kpt-fn/set-label:unstable
9+
configMap:
10+
tier: backend
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Copyright 2021 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
apiVersion: apps/v1
15+
kind: Deployment
16+
metadata:
17+
name: nginx-deployment
18+
spec:
19+
replicas: 3
20+
---
21+
apiVersion: custom.io/v1
22+
kind: Custom
23+
metadata:
24+
name: custom
25+
spec:
26+
image: nginx:1.2.3

internal/cmdrender/executor.go

Lines changed: 77 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -286,30 +286,97 @@ func (pn *pkgNode) runPipeline(ctx context.Context, hctx *hydrationContext, inpu
286286
return input, nil
287287
}
288288

289-
fnChain, err := fnChain(ctx, pl, pn.pkg.UniquePath)
289+
mutatedResources, err := pn.runMutators(ctx, hctx, input)
290290
if err != nil {
291291
return nil, errors.E(op, pn.pkg.UniquePath, err)
292292
}
293293

294+
if err = pn.runValidators(ctx, hctx, mutatedResources); err != nil {
295+
return nil, errors.E(op, pn.pkg.UniquePath, err)
296+
}
297+
// print a new line after a pipeline running
298+
pr.Printf("\n")
299+
return mutatedResources, nil
300+
}
301+
302+
// runMutators runs a set of mutators functions on given input resources.
303+
func (pn *pkgNode) runMutators(ctx context.Context, hctx *hydrationContext, input []*yaml.RNode) ([]*yaml.RNode, error) {
304+
if len(input) == 0 {
305+
return input, nil
306+
}
307+
308+
pl, err := pn.pkg.Pipeline()
309+
if err != nil {
310+
return nil, err
311+
}
312+
313+
if len(pl.Mutators) == 0 {
314+
return input, nil
315+
}
316+
317+
mutators, err := fnChain(ctx, pn.pkg.UniquePath, pl.Mutators)
318+
if err != nil {
319+
return nil, err
320+
}
321+
294322
output := &kio.PackageBuffer{}
295323
// create a kio pipeline from kyaml library to execute the function chains
296-
kioPipeline := kio.Pipeline{
324+
mutation := kio.Pipeline{
297325
Inputs: []kio.Reader{
298326
&kio.PackageBuffer{Nodes: input},
299327
},
300-
Filters: fnChain,
328+
Filters: mutators,
301329
Outputs: []kio.Writer{output},
302330
}
303-
err = kioPipeline.Execute()
331+
err = mutation.Execute()
304332
if err != nil {
305-
return nil, errors.E(op, pn.pkg.UniquePath, err)
333+
return nil, err
306334
}
307-
hctx.executedFunctionCnt += len(fnChain)
308-
// print a new line after a pipeline running
309-
pr.Printf("\n")
335+
hctx.executedFunctionCnt += len(mutators)
310336
return output.Nodes, nil
311337
}
312338

339+
// runValidators runs a set of validator functions on input resources.
340+
// We bail out on first validation failure today, but the logic can be
341+
// improved to report multiple failures. Reporting multiple failures
342+
// will require changes to the way we print errors
343+
func (pn *pkgNode) runValidators(ctx context.Context, hctx *hydrationContext, input []*yaml.RNode) error {
344+
if len(input) == 0 {
345+
return nil
346+
}
347+
348+
pl, err := pn.pkg.Pipeline()
349+
if err != nil {
350+
return err
351+
}
352+
353+
if len(pl.Validators) == 0 {
354+
return nil
355+
}
356+
357+
for i := range pl.Validators {
358+
fn := pl.Validators[i]
359+
validator, err := newFnRunner(ctx, &fn, pn.pkg.UniquePath)
360+
if err != nil {
361+
return err
362+
}
363+
// validators are run on a copy of mutated resources to ensure
364+
// resources are not mutated.
365+
if _, err = validator.Filter(cloneResources(input)); err != nil {
366+
return err
367+
}
368+
hctx.executedFunctionCnt++
369+
}
370+
return nil
371+
}
372+
373+
func cloneResources(input []*yaml.RNode) (output []*yaml.RNode) {
374+
for _, resource := range input {
375+
output = append(output, resource.Copy())
376+
}
377+
return
378+
}
379+
313380
// path (location) of a KRM resources is tracked in a special key in
314381
// metadata.annotation field. adjustRelPath updates that path annotation by prepending
315382
// the given relPath to the current path annotation if it doesn't exist already.
@@ -338,13 +405,8 @@ func adjustRelPath(resources []*yaml.RNode, relPath string) ([]*yaml.RNode, erro
338405
return resources, nil
339406
}
340407

341-
// fnChain returns a slice of function runners from the
342-
// functions and configs defined in pipeline.
343-
func fnChain(ctx context.Context, pl *kptfilev1alpha2.Pipeline, pkgPath types.UniquePath) ([]kio.Filter, error) {
344-
fns := []kptfilev1alpha2.Function{}
345-
fns = append(fns, pl.Mutators...)
346-
// TODO: Validators cannot modify resources.
347-
fns = append(fns, pl.Validators...)
408+
// fnChain returns a slice of function runners given a list of functions defined in pipeline.
409+
func fnChain(ctx context.Context, pkgPath types.UniquePath, fns []kptfilev1alpha2.Function) ([]kio.Filter, error) {
348410
var runners []kio.Filter
349411
for i := range fns {
350412
fn := fns[i]

0 commit comments

Comments
 (0)