Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions pkg/lib/kptops/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
var testFunctions = map[string]framework.ResourceListProcessorFunc{
"ghcr.io/kptdev/krm-functions-catalog/set-labels:latest": setLabels,
"ghcr.io/kptdev/krm-functions-catalog/set-namespace:latest": setNamespace,
"ghcr.io/test/check-fnconfig-labels:latest": checkFnConfigLabels,
}

// runtime is a test-only FunctionRuntime that resolves functions from testFunctions.
Expand Down Expand Up @@ -120,3 +121,17 @@ func validGVK(rn *kyaml.RNode, apiVersion, kind string) bool {
}
return meta.APIVersion == apiVersion && meta.Kind == kind
}

// checkFnConfigLabels is a test-only validator that verifies its functionConfig
// has a specific label. This is used to test that validators receive updated
// functionConfig from in-memory input after mutators have modified it.
func checkFnConfigLabels(rl *framework.ResourceList) error {
if rl.FunctionConfig == nil {
return errors.New("functionConfig is nil")
}
labels := rl.FunctionConfig.GetLabels()
if _, ok := labels["env"]; !ok {
return fmt.Errorf("functionConfig is stale - missing 'env' label that mutator should have added; labels: %v", labels)
}
return nil
}
35 changes: 30 additions & 5 deletions pkg/lib/kptops/render_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -858,11 +858,8 @@ func (pn *pkgNode) runValidators(ctx context.Context, hctx *hydrationContext, in
if err != nil {
return err
}
var validator kio.Filter
displayResourceCount := false
if len(function.Selectors) > 0 || len(function.Exclusions) > 0 {
displayResourceCount = true
}
var validator *fnruntime.FunctionRunner
displayResourceCount := len(function.Selectors) > 0 || len(function.Exclusions) > 0
if function.Exec != "" && !hctx.runnerOptions.AllowExec {
hctx.validationSteps = append(hctx.validationSteps, preExecFailureStep(function, errAllowedExecNotSpecified))
return errAllowedExecNotSpecified
Expand All @@ -875,6 +872,11 @@ func (pn *pkgNode) runValidators(ctx context.Context, hctx *hydrationContext, in
hctx.validationSteps = append(hctx.validationSteps, preExecFailureStep(function, err))
return err
}

if err := pn.refreshFnConfig(validator, input, function.ConfigPath); err != nil {
return err
}

if _, err = validator.Filter(cloneResources(selectedResources)); err != nil {
hctx.validationSteps = append(hctx.validationSteps, captureStepResult(function, hctx.fnResults, resultCountBeforeExec, err))
return err
Expand All @@ -892,6 +894,29 @@ func cloneResources(input []*yaml.RNode) (output []*yaml.RNode) {
return
}

// refreshFnConfig updates the runner's functionConfig from the in-memory input
// to pick up any mutations applied earlier in the pipeline.
func (pn *pkgNode) refreshFnConfig(runner *fnruntime.FunctionRunner, input []*yaml.RNode, configPath string) error {
if configPath == "" {
return nil
}
for _, r := range input {
pkgPath, err := pkg.GetPkgPathAnnotation(r)
if err != nil {
return err
}
currPath, _, err := kioutil.GetFileAnnotations(r)
if err != nil {
return err
}
if pkgPath == pn.pkg.UniquePath.String() && currPath == configPath {
runner.SetFnConfig(r)
break
}
}
return nil
}

// clearAnnotationsOnMutFailure removes annotations that are added during mutation when mutation fails.
func clearAnnotationsOnMutFailure(input []*yaml.RNode) {
annotations := []string{
Expand Down
78 changes: 78 additions & 0 deletions pkg/lib/kptops/render_executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ import (
fnruntime "github.com/kptdev/kpt/pkg/fn/runtime"
"github.com/kptdev/kpt/pkg/kptfile/kptfileutil"
"github.com/kptdev/kpt/pkg/lib/pkg"
"github.com/kptdev/kpt/pkg/lib/runneroptions"
"github.com/kptdev/kpt/pkg/printer"
"github.com/kptdev/kpt/pkg/printer/fake"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/kyaml/filesys"
"sigs.k8s.io/kustomize/kyaml/fn/framework"
Expand Down Expand Up @@ -1050,3 +1052,79 @@ metadata:
})
}
}

func TestRunValidators_RefreshFnConfigFromInMemoryInput(t *testing.T) {
// This test verifies that when a mutator modifies a resource that is used as
// a validator's functionConfig (via configPath), the validator sees the
// updated in-memory version rather than the stale on-disk version.

tests := []struct {
name string
renderBfs bool
}{
{name: "DFS rendering", renderBfs: false},
{name: "BFS rendering", renderBfs: true},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
mockFS := filesys.MakeFsInMemory()
pkgPath := rootString
assert.NoError(t, mockFS.Mkdir(pkgPath))

kptfileContent := fmt.Appendf(nil, `apiVersion: kpt.dev/v1
kind: Kptfile
metadata:
name: test-pkg
annotations:
kpt.dev/bfs-rendering: %q
pipeline:
mutators:
- image: ghcr.io/kptdev/krm-functions-catalog/set-labels:latest
configMap:
env: production
validators:
- image: ghcr.io/test/check-fnconfig-labels:latest
configPath: validator-config.yaml
`, fmt.Sprintf("%t", tc.renderBfs))
assert.NoError(t, mockFS.WriteFile(filepath.Join(pkgPath, "Kptfile"), kptfileContent))

validatorConfig := `apiVersion: v1
kind: ConfigMap
metadata:
name: validator-config
annotations:
config.kubernetes.io/local-config: "true"
data:
check: labels
`
assert.NoError(t, mockFS.WriteFile(filepath.Join(pkgPath, "validator-config.yaml"), []byte(validatorConfig)))

deployment := `apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 1
`
assert.NoError(t, mockFS.WriteFile(filepath.Join(pkgPath, "deployment.yaml"), []byte(deployment)))

r := &Renderer{
PkgPath: pkgPath,
FileSystem: mockFS,
Runtime: &runtime{},
}
r.RunnerOptions.InitDefaults(runneroptions.GHCRImagePrefix)
r.RunnerOptions.ImagePullPolicy = runneroptions.IfNotPresentPull

ctx := fake.CtxWithDefaultPrinter()
_, err := r.Execute(ctx)
assert.NoError(t, err, "validator should pass because it receives the mutated functionConfig with 'env' label")

// Verify the validator config was indeed mutated (written with labels)
res, err := mockFS.ReadFile(filepath.Join(pkgPath, "validator-config.yaml"))
assert.NoError(t, err)
assert.Contains(t, string(res), "env: production", "validator-config.yaml should have the label added by the mutator")
})
}
}