Skip to content
Merged
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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ require (
k8s.io/api v0.34.11
k8s.io/apiextensions-apiserver v0.34.11
k8s.io/apimachinery v0.34.11
k8s.io/client-go v0.34.11
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
sigs.k8s.io/yaml v1.6.0
)
Expand Down Expand Up @@ -267,7 +268,6 @@ require (
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/apiserver v0.34.11 // indirect
k8s.io/cli-runtime v0.34.11 // indirect
k8s.io/client-go v0.34.11 // indirect
k8s.io/component-base v0.34.11 // indirect
k8s.io/klog v1.0.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
Expand Down
10 changes: 10 additions & 0 deletions internal/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,12 @@ var (
ShowIgnored bool
ShowDocumentation bool
Fix bool
Matrix bool
MatrixLimit int
)

const defaultMatrixLimit = 100

var (
BootstrapRepositoryType string
BootstrapRepositoryURL string
Expand Down Expand Up @@ -77,6 +81,12 @@ func InitLintFlagSet() *pflag.FlagSet {
// automatically fix findings that support autofix
lint.BoolVarP(&Fix, "fix", "", false, "automatically fix findings that support autofix")

// render every combination of template variants (across openapi examples,
// enums and booleans) and lint them all, to reach conditionally-rendered
// resources a single default render never produces.
lint.BoolVarP(&Matrix, "matrix", "", false, "render and lint all template variants (all openapi value combinations)")
lint.IntVarP(&MatrixLimit, "matrix-limit", "", defaultMatrixLimit, "maximum number of value combinations to render per module in --matrix mode")

// hide warnings in output
lint.BoolVarP(&HideWarnings, "hide-warnings", "", false, "hide warnings")

Expand Down
10 changes: 9 additions & 1 deletion internal/fsutils/getfiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,15 @@ func GetFiles(rootPath string, skipSymlink bool, filters ...filterFn) []string {
return result
}

_ = filepath.Walk(rootPath, func(path string, info os.FileInfo, _ error) error {
_ = filepath.Walk(rootPath, func(path string, info os.FileInfo, err error) error {
// Walk reports a path it could not stat with a nil info: a file removed
// between the directory listing and the stat, a directory it may not read.
// Every branch below dereferences info, so the entry has to be skipped here
// rather than crash the whole run over one unreadable path.
if err != nil || info == nil {
return nil
}

if skipSymlink && info.Mode()&os.ModeSymlink != 0 {
// Correct symlink handling: skip symlink directory, just skip symlink file
if info.IsDir() {
Expand Down
45 changes: 45 additions & 0 deletions internal/fsutils/getfiles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package fsutils
import (
"os"
"path/filepath"
"slices"
"testing"
)

Expand Down Expand Up @@ -84,3 +85,47 @@ func assertEqualFiles(t *testing.T, actual, expected []string) {
t.Errorf("expected %d files, but got %d", len(expected), len(actual))
}
}

// TestGetFilesSurvivesUnstatablePath is the regression guard for a crash that took
// down whole lint runs: filepath.Walk hands the callback a nil FileInfo for a path
// it could not stat, and the callback used to dereference it. dmt itself creates
// such paths — a render injects a helper template into the module's templates/ and
// removes it again — so a linter walking that directory could hit an entry that had
// just vanished and panic the process.
//
// A directory that is readable but not traversable reproduces it deterministically:
// its children are listed, and the lstat of each one then fails.
func TestGetFilesSurvivesUnstatablePath(t *testing.T) {
if os.Geteuid() == 0 {
t.Skip("root ignores the permission bits this test relies on")
}

root := t.TempDir()

readable := filepath.Join(root, "readable.yaml")
if err := os.WriteFile(readable, []byte("x"), 0o644); err != nil {
t.Fatal(err)
}

blocked := filepath.Join(root, "blocked")
if err := os.Mkdir(blocked, 0o755); err != nil {
t.Fatal(err)
}

if err := os.WriteFile(filepath.Join(blocked, "hidden.yaml"), []byte("x"), 0o644); err != nil {
t.Fatal(err)
}

// Readable (r) but not traversable (no x): the entry is listed, its lstat fails.
if err := os.Chmod(blocked, 0o600); err != nil {
t.Fatal(err)
}

t.Cleanup(func() { _ = os.Chmod(blocked, 0o755) })

files := GetFiles(root, false)

if !slices.Contains(files, readable) {
t.Errorf("GetFiles dropped the readable file: %v", files)
}
}
Loading
Loading