Skip to content

Feature: krmignore refactor - #4829

Draft
Catalin-Stratulat-Ericsson wants to merge 20 commits into
kptdev:mainfrom
Nordix:feature-krmignore-refactor
Draft

Catalin-Stratulat-Ericsson wants to merge 20 commits into
kptdev:mainfrom
Nordix:feature-krmignore-refactor

Conversation

@Catalin-Stratulat-Ericsson

@Catalin-Stratulat-Ericsson Catalin-Stratulat-Ericsson commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Description

This PR comprised of 3 main changes but see #4824 for the full write-up of the problem

  • Introduces one shared, correct definition of "is this file KRM" (pkg/lib/krmvalidate), and uses it to give every affected command the same behavior for non-KRM files:
  • A whole-package-tree pre-scan that fails once, up front, listing every non-KRM file found, instead of crashing on (or silently mishandling) the first one.
  • A new opt-in kpt.dev/auto-ignore-non-krm Kptfile annotation that skips non-KRM files automatically, in memory only (nothing written to disk, no .krmignore generated), for every affected command except live migrate.

The PR is split up as follows

  1. Utility functions for new logic ~25%
  2. Rewiring of old functions to use new logic ~10%
  3. Testing ~65%

Commands changed:

  • fn render, fn eval, fn source, pkg update, live apply, live status, live destroy, live init, live migrate, alpha live plan.
  • pkg tree/pkg cat are intentionally unchanged (read-only display tools, already tolerant by design).
  • live migrate gets the pre-scan but not the annotation: its legacy ConfigMap-based inventory reader has no way to skip a file in memory, so .krmignore is the only way to exclude a non-KRM file for that command.

Documentation is updated to match: a new annotation reference page, and a short note on each affected command's reference page and in the packages guide's .krmignore section.

Motivation

Fixes #4790: along with other issues raised of .krmignore regarding, consistency throughout the code, bugs related to its performance and of course ease of use.

AI Disclosure

  • I have used AI in the creation of this PR.
  • Kiro/Claude to trace through the code for the different commands and identify where krmignore is used/how its used etc.
  • Kiro/Claude to write unit tests for all the different .krmignore situations not covered previously

Fixes

Fixes #4790

Testing

  • Unit tests added/updated for every changed package (krmvalidate, kptops/render executor, runfn, cmdsource, pkg/lib/update, pkg/live (shared by apply/status/destroy/plan), live/init, live/migrate).
  • go build ./... and go vet ./... pass with no errors.
  • go test ./... passes with no failures.
  • Each affected live command was manually verified end-to-end against a real cluster (kind), confirming: without the annotation, a non-KRM file blocks the command before any cluster mutation; with the annotation, the file is skipped and left untouched on disk with no .krmignore written; live migrate continues to block regardless of the annotation, as designed.
  • fn render/fn eval/fn source were manually verified against the compiled binary for the same before/after behavior.
  • Documentation changes verified with a full hugo build (no broken relrefs) and the repo's misspell check (make verify in documentation/).

Signed-off-by: Catalin-Stratulat-Ericsson <catalin.stratulat@ericsson.com>
… in Kptfile status

Signed-off-by: Catalin-Stratulat-Ericsson <catalin.stratulat@ericsson.com>
Signed-off-by: Catalin-Stratulat-Ericsson <catalin.stratulat@ericsson.com>
Signed-off-by: Catalin-Stratulat-Ericsson <catalin.stratulat@ericsson.com>
Signed-off-by: Catalin-Stratulat-Ericsson <catalin.stratulat@ericsson.com>
Signed-off-by: Catalin-Stratulat-Ericsson <catalin.stratulat@ericsson.com>
Signed-off-by: Catalin-Stratulat-Ericsson <catalin.stratulat@ericsson.com>
Signed-off-by: Catalin-Stratulat-Ericsson <catalin.stratulat@ericsson.com>
Signed-off-by: Catalin-Stratulat-Ericsson <catalin.stratulat@ericsson.com>
@netlify

netlify Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for kptdocs ready!

Name Link
🔨 Latest commit eaab59e
🔍 Latest deploy log https://app.netlify.com/projects/kptdocs/deploys/6ada49b17f040914f3339ab6
😎 Deploy Preview https://deploy-preview-4829--kptdocs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved file-content loss paths and inconsistent exclusion and validation behavior prevent safe approval.

9 open issues
What changed in this PR

This PR centralizes non-KRM validation across kpt commands to improve diagnostics for packages containing files such as Helm values, addressing #4790 and the proposal in #4824.

Changes:

  • Adds shared validation and pre-scanning, plus an opt-in annotation for automatic exclusion.
  • Integrates scanning into function and live commands, records render skips, and classifies update inputs by content.
  • Adds regression tests and documents command behavior and annotation scope.
File Description
thirdparty/​kyaml/​runfn/​runfn.go Adds eval pre-scanning and file skipping.
thirdparty/​kyaml/​runfn/​runfn_test.go Makes the function fixture valid KRM.
thirdparty/​cmdconfig/​commands/​cmdsource/​cmdsource.go Adds source pre-scanning and exclusions.
thirdparty/​cmdconfig/​commands/​cmdsource/​cmdsource_test.go Updates fixtures and tests non-KRM handling.
pkg/​live/​rgpath.go Passes file exclusions to manifest reading.
pkg/​live/​load.go Adds context-aware live pre-scanning.
pkg/​live/​load_test.go Tests live loading and exclusion behavior.
pkg/​lib/​update/​update_test.go Updates non-KRM raw-copy expectations.
pkg/​lib/​update/​resource-merge.go Classifies merge inputs by content.
pkg/​lib/​update/​resource-merge_test.go Tests non-KRM values-file updates.
pkg/​lib/​update/​pruninglocalpackagereader.go Filters non-KRM merge inputs.
pkg/​lib/​krmvalidate/​prescan.go Implements scanning, decisions, and skip helpers.
pkg/​lib/​krmvalidate/​prescan_test.go Tests partitioning and consolidated diagnostics.
pkg/​lib/​krmvalidate/​iskrm.go Defines shared KRM validation and exceptions.
pkg/​lib/​krmvalidate/​iskrm_test.go Tests validation rules and exceptions.
pkg/​lib/​kptops/​render_executor.go Integrates scanning and records ignored files.
pkg/​lib/​kptops/​render_executor_test.go Tests skipped-file status messages.
pkg/​lib/​kptops/​autoignore.go Exposes render-facing scan helpers.
pkg/​lib/​kptops/​autoignore_test.go Tests render blocking, exclusions, and scope.
documentation/​content/​en/​reference/​cli/​pkg/​update/​_index.md Describes non-KRM update handling.
documentation/​content/​en/​reference/​cli/​live/​status/​_index.md Documents validation and auto-ignore.
documentation/​content/​en/​reference/​cli/​live/​migrate/​_index.md Documents scanning without annotation support.
documentation/​content/​en/​reference/​cli/​live/​init/​_index.md Documents validation and auto-ignore.
documentation/​content/​en/​reference/​cli/​live/​destroy/​_index.md Documents validation before cluster changes.
documentation/​content/​en/​reference/​cli/​live/​apply/​_index.md Documents validation before cluster changes.
documentation/​content/​en/​reference/​cli/​fn/​source/​_index.md Documents source validation and exclusions.
documentation/​content/​en/​reference/​cli/​fn/​render/​_index.md Documents scanning and skipped-file status.
documentation/​content/​en/​reference/​cli/​fn/​eval/​_index.md Documents validation before function execution.
documentation/​content/​en/​reference/​cli/​alpha/​live/​plan/​_index.md Documents validation and auto-ignore.
documentation/​content/​en/​reference/​annotations/​auto-ignore-non-krm/​_index.md Adds annotation behavior, scope, and examples.
documentation/​content/​en/​reference/​annotations/​_index.md Links the new annotation reference.
documentation/​content/​en/​book/​03-packages/​_index.md Explains manual and automatic exclusions.
commands/​live/​status/​fake-loader.go Passes context to live loading.
commands/​live/​status/​cmdstatus.go Passes context to live loading.
commands/​live/​migrate/​migratecmd.go Adds pre-scanning before CRD installation.
commands/​live/​migrate/​migratecmd_test.go Tests blocking and manual exclusions.
commands/​live/​init/​cmdliveinit.go Adds scanning before namespace discovery.
commands/​live/​init/​cmdliveinit_test.go Adds non-KRM initialization cases.
commands/​live/​destroy/​cmddestroy.go Passes context to live loading.
commands/​live/​apply/​cmdapply.go Passes context to live loading.
commands/​alpha/​live/​plan/​command.go Passes context to live loading.

Review effort: Balanced

Comment thread commands/live/init/cmdliveinit.go Outdated
Comment thread pkg/lib/krmvalidate/prescan.go Outdated
Comment thread pkg/lib/krmvalidate/prescan.go Outdated
Comment thread pkg/lib/update/pruninglocalpackagereader.go Outdated
Comment thread pkg/lib/update/resource-merge.go
Comment thread pkg/lib/update/resource-merge.go
Comment thread pkg/lib/kptops/render_executor.go Outdated
Comment thread pkg/lib/krmvalidate/iskrm.go
Comment thread pkg/lib/krmvalidate/prescan.go
@Catalin-Stratulat-Ericsson
Catalin-Stratulat-Ericsson marked this pull request as draft October 22, 2026 14:54
…les containing offending documents'

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Catalin-Stratulat-Ericsson <catalin.stratulat@ericsson.com>
… the root package)

Signed-off-by: Catalin-Stratulat-Ericsson <catalin.stratulat@ericsson.com>
…ring out-of-place renders)

Signed-off-by: Catalin-Stratulat-Ericsson <catalin.stratulat@ericsson.com>
…en classifying parsed files)

Signed-off-by: Catalin-Stratulat-Ericsson <catalin.stratulat@ericsson.com>
…consistent across merge paths)

Signed-off-by: Catalin-Stratulat-Ericsson <catalin.stratulat@ericsson.com>
… without inheritance)

Signed-off-by: Catalin-Stratulat-Ericsson <catalin.stratulat@ericsson.com>
… discovery and attribution)

Signed-off-by: Catalin-Stratulat-Ericsson <catalin.stratulat@ericsson.com>
… during destination writes)

Signed-off-by: Catalin-Stratulat-Ericsson <catalin.stratulat@ericsson.com>
…on input and output checks)

Signed-off-by: Catalin-Stratulat-Ericsson <catalin.stratulat@ericsson.com>
Signed-off-by: Catalin-Stratulat-Ericsson <catalin.stratulat@ericsson.com>
@Catalin-Stratulat-Ericsson
Catalin-Stratulat-Ericsson marked this pull request as ready for review October 22, 2026 17:01
Signed-off-by: Catalin-Stratulat-Ericsson <catalin.stratulat@ericsson.com>
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
53.8% Coverage on New Code (required ≥ 80%)
8.6% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@Catalin-Stratulat-Ericsson
Catalin-Stratulat-Ericsson marked this pull request as draft October 23, 2026 04:26
@Catalin-Stratulat-Ericsson

Copy link
Copy Markdown
Contributor Author

will address sonarqube report on return

@Nikshay1 Nikshay1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey @Catalin-Stratulat-Ericsson, I went through this and tried a bunch of small packages on the merge base and on this branch, and for some of them also on this branch with main merged in. left a few things inline, each with what I ran. the first few change things for people who don't set the annotation, so those are probably worth looking at first. happy to share the scripts too!

if err != nil {
return false, errors.E(op, errors.IO, err)
}
nodes, err := kio.FromBytes(content)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this one can quietly delete a file during pkg update. I had a kind: List file with no top-level apiVersion, just items: with one ConfigMap. it was the same in upstream and local, and only another file changed upstream:

kind: List
items:
- apiVersion: v1
  kind: ConfigMap
  metadata:
    name: a
base: Updated 1 package(s).   list.yaml is still there
PR:   Updated 1 package(s).   list.yaml is gone, no message

I think the two readers disagree about this file. kio.FromBytes here unwraps the List, so the file counts as KRM. the merge reader keeps the wrapper, so PruningLocalPackageReader drops it for having no apiVersion. the destination writer still tracks the file, and since nothing in the merged output comes from it, the writer deletes it. the non-KRM copy pass doesn't bring it back because this check still says it's KRM. happens with main merged in too.

}
return false, nil
for _, n := range nodes {
if krmvalidate.IsKRM(n) != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsKRM needs metadata.name, so a Job that uses generateName now goes down the non-KRM path and stops getting the 3-way merge. upstream changed backoffLimit from 1 to 5, and local changed parallelism from 1 to 3:

base: backoffLimit: 5, parallelism: 3   both changes kept
PR:   backoffLimit: 1, parallelism: 3   upstream change lost, no warning

it seems the file is treated as locally modified, so it's kept as is and the upstream side never comes in. on base the same Job goes through the 3-way merge fine. maybe pkg update could use a looser check here than the one for function input?


namespace, err := config.FindNamespace(c.Factory.ToRawKubeConfigLoader(), c.Pkg.UniquePath.String())
// Pre-scan for non-KRM files before config.FindNamespace() reads them.
scanResult, err := krmvalidate.ScanFlatAndDecide(filesys.FileSystemOrOnDisk{}, c.Pkg.UniquePath.String(), pr.Printf)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this runs before the namespace step, so --namespace doesn't get you past non-KRM files anymore. before, FindNamespace returned early when the namespace was given, without reading the package, and the old error message told people to use that flag:

$ kpt live init --namespace bar pkg      # pkg has a JSON6902 bare-list patch.yaml
base: initializing "resourcegroup.yaml" data (namespace: bar)...success
PR:   Error: input resource list must contain only KRM resources (1 file(s)):
        patch.yaml: resource must have `apiVersion`

a malformed yaml file fails the same way, with non-krm pre-scan: ... MalformedYAMLError. this is also the bare-list case from #4795, so it hits that path too. maybe skip the pre-scan when the namespace is set, or only warn there?

}

// Non-KRM pre-scan, before any pipeline starts. See autoignore.go.
preScanResult, preScanErr := PreScanNonKRM(e.FileSystem, e.PkgPath, pr.Printf)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the scan here covers the whole package, so files a function would never see now block it. before, SelectInput ran first and the function's input check only looked at the selected resources:

# Kptfile mutator with selectors: [{kind: ConfigMap}], plus a values.yaml
base: [PASS] "..." / Successfully executed 1 function(s) in 1 package(s).
PR:   Error: input resource list must contain only KRM resources (1 file(s)):
        values.yaml: resource must have `apiVersion`

fn eval --match-kind ConfigMap does the same thing through thirdparty/kyaml/runfn/runfn.go:130. maybe the check could run after SelectInput?

Comment thread pkg/live/load.go
return nil, kptfilev1.Inventory{}, err
}
pr := printer.FromContextOrDie(ctx)
scanResult, err := krmvalidate.ScanFlatAndDecide(filesys.FileSystemOrOnDisk{}, absPkgPath, pr.Printf)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

two cases where the scan here rejects things the live reader below never complained about:

  1. the scan reads *.json because it uses MatchAllKRM, but ResourceGroupPathManifestReader only reads *.yaml and *.yml. so an unrelated package.json now breaks the live commands:
base: live init succeeds, Load returns 1 object
PR:   package.json: resource must have `apiVersion`
  1. filterLocalConfig used to drop a config.kubernetes.io/local-config: "true" object without a name, and now the scan rejects it first:
base: live init succeeds, Load returns 1 object
PR:   local.yaml: resource must have `metadata.name`

maybe the scan could use the same glob as the real reader and skip local-config objects?

}
found = append(found, nonKRM...)

subpkgs, subErr := p.DirectSubpackages()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the scan and the live reader seem to see .krmignore differently. the live reader reads the whole tree from the root, so the root .krmignore covers subpackage files too. here each subpackage gets read on its own, and DirectSubpackages doesn't look at .krmignore, so it also walks into ignored dirs. I ran three cases through live.Load:

root .krmignore has values.yaml, file is at sub/values.yaml
  base: OK, 2 objects
  PR:   blocked on values.yaml

root .krmignore has testdata/, which holds a fixture Kptfile and a values.yaml
  base: OK, and live init, fn eval and fn source all work
  PR:   all four blocked on values.yaml

sub/.krmignore has values.yaml
  base: unknown resource types: //
  PR:   unknown resource types: //

the first two used to work. the last one still ends in the same unknown resource types: // as base, because the scan honours the subpackage .krmignore but the flat live reader doesn't. maybe the flat callers could scan with the same reader setup, and only use the per-package walk to find out whose Kptfile opted in?

var b strings.Builder
fmt.Fprintf(&b, "input resource list must contain only KRM resources (%d file(s)):", len(blocking))
for _, f := range blocking {
fmt.Fprintf(&b, "\n %s: %s", f.RelPath, f.Reason)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small one. f.RelPath is relative to the owning subpackage, so the message gets confusing when two subpackages have the same file name:

# bare dir with two subpackages (each has a Kptfile): apps/app1/values.yaml, apps/app2/values.yaml
base: apps/app1/values.yaml: resource must have `apiVersion`
PR:     values.yaml: resource must have `apiVersion`
        values.yaml: resource must have `apiVersion`

the hint also says "this package's Kptfile" without saying which one. printing the path relative to the scan root would probably fix both.

// FindNamespace re-reads the package itself with no skip hook,
// so it would hit the files we just excused. Use the loader's
// namespace directly instead, same as FindNamespace's own fallback.
namespace, _, err = c.Factory.ToRawKubeConfigLoader().Namespace()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when the annotation skips at least one file, this skips namespace detection and uses the kubeconfig namespace, so you get a different result than with .krmignore. all resources were in foo, plus a values.yaml:

values.yaml listed in .krmignore   namespace: foo
annotation instead                 namespace: default

could FindNamespace, or a kpt copy of it, take the skip func instead?


The non-KRM file is skipped in memory for that invocation only.
Nothing is written to disk because of this annotation: the file
itself is left completely untouched, and no `.krmignore` file is ever

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think live init still writes to files the annotation skipped. it happens in the attribution pass at the end of Run, at cmdliveinit.go:247. that line isn't part of the diff, so I'm commenting here. my package was annotated and had one file with a StorageBucket and a non-KRM doc, so the scan skipped the whole file:

[info] ignoring 1 non-KRM file(s) ... : mixed.yaml
diff after live init:
>   annotations:
>     cnrm.cloud.google.com/blueprint: 'krm-live'

this line says skipped files are left completely untouched. it still happens with main merged in. #4796 only stops writing files whose attribution didn't change, and here adding the CNRM annotation does change it. passing the skip func to the Attributor would probably do it.

} else {
resources, resErr = p.LocalResources()
}
if resErr != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when a file doesn't parse, the reader errors out here before Split sees it. so the annotation can't skip it, and the error stops at the first bad file:

# annotated package, b1.yaml is "not: [valid"
PR: Error: non-krm pre-scan: pkg.readResources: ... b1.yaml: MalformedYAMLError ...

# not annotated, b1.yaml and b2.yaml both broken, plus a values.yaml
PR: only b1.yaml is reported

base fails on these files too, except for live init --namespace from the other comment, so this part isn't a regression. but the docs say the annotation skips "any non-KRM files", so maybe count parse errors as non-KRM, or say in the docs that the file has to parse as YAML first?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hard to diagnose error in kpt live init findNamespace (in presence of non krm yaml)

3 participants