Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package kuttl for openSUSE:Factory checked in at 2025-12-05 16:54:19 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/kuttl (Old) and /work/SRC/openSUSE:Factory/.kuttl.new.1939 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kuttl" Fri Dec 5 16:54:19 2025 rev:6 rq:1321082 version:0.24.0 Changes: -------- --- /work/SRC/openSUSE:Factory/kuttl/kuttl.changes 2025-11-20 14:50:48.051446772 +0100 +++ /work/SRC/openSUSE:Factory/.kuttl.new.1939/kuttl.changes 2025-12-05 16:55:09.968696186 +0100 @@ -1,0 +2,20 @@ +Thu Dec 04 07:00:44 UTC 2025 - Johannes Kastl <[email protected]> + +- Update to version 0.24.0: + * Highlights + - feat: add ignoreFiles option to suppress file warnings in + test directories (#651) + * Bug Fixes + - fix: existing user-supplied ns when lacking permissions + (#657) (bug was introduced in v0.23.0) + * Dependency bumps + - chore(deps): bump github.com/spf13/cobra from 1.9.1 to 1.10.1 + (#655) + - chore(deps): bump github.com/spf13/pflag from 1.0.7 to 1.0.10 + (#654) + - chore(deps): bump github.com/Masterminds/semver/v3 from 3.3.1 + to 3.4.0 (#653) + - chore(deps): bump github.com/stretchr/testify from 1.10.0 to + 1.11.1 (#652) + +------------------------------------------------------------------- Old: ---- kuttl-0.23.0.obscpio New: ---- kuttl-0.24.0.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ kuttl.spec ++++++ --- /var/tmp/diff_new_pack.WSRHH6/_old 2025-12-05 16:55:11.204747864 +0100 +++ /var/tmp/diff_new_pack.WSRHH6/_new 2025-12-05 16:55:11.208748030 +0100 @@ -19,7 +19,7 @@ %define executable_name kubectl-kuttl Name: kuttl -Version: 0.23.0 +Version: 0.24.0 Release: 0 Summary: KUbernetes Test TooL License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.WSRHH6/_old 2025-12-05 16:55:11.248749703 +0100 +++ /var/tmp/diff_new_pack.WSRHH6/_new 2025-12-05 16:55:11.252749870 +0100 @@ -3,7 +3,7 @@ <param name="url">https://github.com/kudobuilder/kuttl</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v0.23.0</param> + <param name="revision">v0.24.0</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.WSRHH6/_old 2025-12-05 16:55:11.280751041 +0100 +++ /var/tmp/diff_new_pack.WSRHH6/_new 2025-12-05 16:55:11.284751208 +0100 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/kudobuilder/kuttl</param> - <param name="changesrevision">9ef9f000371ebdd88fb16747ee5ac49f88021ade</param></service></servicedata> + <param name="changesrevision">eb4852937e499abb3db28505638210b04b8f5984</param></service></servicedata> (No newline at EOF) ++++++ kuttl-0.23.0.obscpio -> kuttl-0.24.0.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kuttl-0.23.0/docs/testing/reference.md new/kuttl-0.24.0/docs/testing/reference.md --- old/kuttl-0.23.0/docs/testing/reference.md 2025-11-19 11:01:38.000000000 +0100 +++ new/kuttl-0.24.0/docs/testing/reference.md 2025-12-03 08:15:11.000000000 +0100 @@ -13,6 +13,10 @@ testDirs: - tests/e2e/ timeout: 120 +ignoreFiles: +- "*.md" +- "README*" +- ".gitignore" ``` Supported settings: @@ -39,6 +43,7 @@ reportName | string | The name of report to create. This field is not used unless reportFormat is set. | "kuttl-test" namespace | string | The namespace to use for tests. This namespace will be created if it does not exist and removed if it was created (unless `skipDelete` is set). If no namespace is set, one will be auto-generated. | suppress | list of strings | Suppresses log collection of the specified types. Currently only `events` is supported. | +ignoreFiles | list of strings | File patterns (e.g., `*.md`, `README*`) to ignore when collecting test steps. Files matching these patterns will not generate warnings about not matching the expected test file pattern. Setting this field (even to an empty list) overrides the defaults. | `["README*"]` ## TestStep diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kuttl-0.23.0/go.mod new/kuttl-0.24.0/go.mod --- old/kuttl-0.23.0/go.mod 2025-11-19 11:01:38.000000000 +0100 +++ new/kuttl-0.24.0/go.mod 2025-12-03 08:15:11.000000000 +0100 @@ -3,7 +3,7 @@ go 1.24.0 require ( - github.com/Masterminds/semver/v3 v3.3.1 + github.com/Masterminds/semver/v3 v3.4.0 github.com/docker/docker v28.5.2+incompatible github.com/dustin/go-humanize v1.0.1 github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0 @@ -11,9 +11,9 @@ github.com/google/cel-go v0.26.1 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 - github.com/spf13/cobra v1.9.1 - github.com/spf13/pflag v1.0.7 - github.com/stretchr/testify v1.10.0 + github.com/spf13/cobra v1.10.1 + github.com/spf13/pflag v1.0.10 + github.com/stretchr/testify v1.11.1 github.com/thoas/go-funk v0.9.3 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kuttl-0.23.0/go.sum new/kuttl-0.24.0/go.sum --- old/kuttl-0.23.0/go.sum 2025-11-19 11:01:38.000000000 +0100 +++ new/kuttl-0.24.0/go.sum 2025-12-03 08:15:11.000000000 +0100 @@ -6,8 +6,8 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= -github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= @@ -176,12 +176,12 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= -github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= -github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -195,8 +195,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/thoas/go-funk v0.9.3 h1:7+nAEx3kn5ZJcnDm2Bh23N2yOtweO14bi//dvRtgLpw= github.com/thoas/go-funk v0.9.3/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kuttl-0.23.0/internal/harness/harness.go new/kuttl-0.24.0/internal/harness/harness.go --- old/kuttl-0.23.0/internal/harness/harness.go 2025-11-19 11:01:38.000000000 +0100 +++ new/kuttl-0.24.0/internal/harness/harness.go 2025-12-03 08:15:11.000000000 +0100 @@ -88,6 +88,7 @@ testcase.WithNamespace(h.TestSuite.Namespace), testcase.WithTimeout(timeout), testcase.WithLogSuppressions(h.TestSuite.Suppress), + testcase.WithIgnoreFiles(h.TestSuite.IgnoreFiles), testcase.WithRunLabels(h.RunLabels), testcase.WithClients(h.Client, h.DiscoveryClient), testcase.WithTemplateVars(h.TemplateVars))) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kuttl-0.23.0/internal/testcase/case.go new/kuttl-0.24.0/internal/testcase/case.go --- old/kuttl-0.23.0/internal/testcase/case.go 2025-11-19 11:01:38.000000000 +0100 +++ new/kuttl-0.24.0/internal/testcase/case.go 2025-12-03 08:15:11.000000000 +0100 @@ -71,6 +71,13 @@ } } +// WithIgnoreFiles sets the list of file patterns to ignore. +func WithIgnoreFiles(patterns []string) CaseOption { + return func(c *Case) { + c.ignoreFiles = patterns + } +} + // WithRunLabels sets the run labels. func WithRunLabels(runLabels labels.Set) CaseOption { return func(c *Case) { @@ -118,6 +125,8 @@ logger testutils.Logger // List of log types which should be suppressed. suppressions []string + // List of file patterns to ignore when collecting test steps. + ignoreFiles []string // Caution: the Vars element of this struct may be shared with other Case objects. templateEnv template.Env } @@ -192,7 +201,13 @@ return cl.Wrapf(err, "waiting for namespace %q to be deleted timed out", c.ns.name) } -func (c *Case) createNamespace(test *testing.T, cl clientWithKubeConfig) error { +type T interface { + Context() context.Context + Cleanup(f func()) + Error(args ...any) +} + +func (c *Case) createNamespace(test T, cl clientWithKubeConfig) error { cl.Logf("Creating namespace %q", c.ns.name) ctx := test.Context() @@ -210,11 +225,15 @@ Kind: "Namespace", }, }) + nsExisted, err := c.didNsExist(ctx, err, cl) + if err != nil { + return fmt.Errorf("failed to create test namespace %q: %w", c.ns.name, err) + } if !c.skipDelete { test.Cleanup(func() { // Namespace cleanup is tracked per-client for multi-cluster tests. // See KEP-0008 for details on backward compatibility decisions. - if c.ns.userSupplied && k8serrors.IsAlreadyExists(err) { + if c.ns.userSupplied && nsExisted { cl.Logf("Skipping deletion of pre-existing user supplied namespace %s", c.ns.name) } else { if err := c.deleteNamespace(cl); err != nil { @@ -224,14 +243,35 @@ }) } + return nil +} + +func (c *Case) didNsExist(ctx context.Context, err error, cl clientWithKubeConfig) (bool, error) { if k8serrors.IsAlreadyExists(err) { cl.Logf("Namespace %q already exists", c.ns.name) - return nil + return true, nil } - if err != nil { - return fmt.Errorf("failed to create test namespace %q: %w", c.ns.name, err) + if k8serrors.IsForbidden(err) { + // If we got a permission error, check separately if the namespace exists + var existingNs corev1.Namespace + getErr := cl.Get(ctx, client.ObjectKey{Name: c.ns.name}, &existingNs) + if getErr == nil { + cl.Logf("Namespace %q already exists", c.ns.name) + return true, nil + } + if k8serrors.IsNotFound(getErr) { + return false, fmt.Errorf("cannot create namespace %q: %w", c.ns.name, err) + } + cl.Logf("Namespace %q creation forbidden (%v) and cannot check its existence: %v", c.ns.name, err, getErr) + // If the user supplied the namespace, it might be due to restrictive RBAC, so let's trust the user. + // If this is an ephemeral namespace, then: + // a) either our permissions are very wrong and the test is unlikely to pass anyway, or + // b) it's a subtle multi-kubeconfig setup where this client is not meant to manage the namespace, but another is. + // Either way, we log the above for visibility and carry on as if a pre-existing namespace is usable. + // The worst that could happen is that the test will fail slightly later than during setup. + return true, nil } - return nil + return false, err } func (c *Case) maybeReportEvents() { @@ -347,7 +387,7 @@ // LoadTestSteps loads all the test steps for a test case. func (c *Case) LoadTestSteps() error { - testStepFiles, err := files.CollectTestStepFiles(c.dir, c.logger) + testStepFiles, err := files.CollectTestStepFiles(c.dir, c.logger, c.ignoreFiles) if err != nil { return err } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kuttl-0.23.0/internal/testcase/case_test.go new/kuttl-0.24.0/internal/testcase/case_test.go --- old/kuttl-0.23.0/internal/testcase/case_test.go 2025-11-19 11:01:38.000000000 +0100 +++ new/kuttl-0.24.0/internal/testcase/case_test.go 2025-12-03 08:15:11.000000000 +0100 @@ -1,16 +1,21 @@ package testcase import ( + "context" "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/kubernetes/scheme" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" "github.com/kudobuilder/kuttl/internal/kubernetes" "github.com/kudobuilder/kuttl/internal/step" @@ -353,3 +358,262 @@ }) } } + +// testMock is an object useful for unit-testing Case.createNamespace(). +type testMock struct { + cleanup func() + testErrors [][]any +} + +func (t *testMock) Context() context.Context { + return context.Background() +} + +func (t *testMock) Cleanup(f func()) { + t.cleanup = f +} + +func (t *testMock) Error(args ...any) { + t.testErrors = append(t.testErrors, args) +} + +func TestCase_createNamespace(t *testing.T) { + tests := map[string]struct { + options []CaseOption + cl func(*testing.T, string) client.Client + wantErr error + expectedCleanupError func(err error) bool + getNsBeforeCleanup func(*testing.T, error) + getNsAfterCleanup func(*testing.T, error) + }{ + "user-supplied exists": { + options: []CaseOption{WithNamespace("foo")}, + cl: newClientWithExistingNs, + getNsBeforeCleanup: func(t *testing.T, err error) { + require.NoError(t, err) + }, + getNsAfterCleanup: func(t *testing.T, err error) { + require.NoError(t, err) + }, + }, + "user-supplied absent": { + options: []CaseOption{WithNamespace("foo")}, + cl: newClientWithAbsentNs, + getNsBeforeCleanup: func(t *testing.T, err error) { + require.NoError(t, err) + }, + getNsAfterCleanup: func(t *testing.T, err error) { + assert.True(t, k8serrors.IsNotFound(err), "expected namespace to be deleted after cleanup, but client returned %v", err) + }, + }, + "user-supplied absent and no write permission": { + options: []CaseOption{WithNamespace("foo")}, + cl: newClientWithAbsentNsNoWritePerm, + wantErr: errCreationForbidden, + getNsBeforeCleanup: func(t *testing.T, err error) { + assert.True(t, k8serrors.IsNotFound(err), "expected namespace to be missing before cleanup, but client returned %v", err) + }, + getNsAfterCleanup: func(t *testing.T, err error) { + assert.True(t, k8serrors.IsNotFound(err), "expected namespace to be missing after cleanup, but client returned %v", err) + }, + }, + "user-supplied exists and no write permission": { + options: []CaseOption{WithNamespace("foo")}, + cl: newClientWithExistingNsNoWritePerm, + getNsBeforeCleanup: func(t *testing.T, err error) { + require.NoError(t, err) + }, + getNsAfterCleanup: func(t *testing.T, err error) { + require.NoError(t, err) + }, + }, + "user-supplied exists and no permissions at all": { + options: []CaseOption{WithNamespace("foo")}, + cl: newClientWithExistingNsNoPerms, + getNsBeforeCleanup: func(t *testing.T, err error) { + require.NoError(t, err) + }, + getNsAfterCleanup: func(t *testing.T, err error) { + require.NoError(t, err) + }, + }, + "ephemeral exists": { + cl: newClientWithExistingNs, + getNsBeforeCleanup: func(t *testing.T, err error) { + require.NoError(t, err) + }, + getNsAfterCleanup: func(t *testing.T, err error) { + assert.True(t, k8serrors.IsNotFound(err), "expected namespace to be deleted after cleanup, but client returned %v", err) + }, + }, + "ephemeral absent": { + cl: newClientWithAbsentNs, + getNsBeforeCleanup: func(t *testing.T, err error) { + require.NoError(t, err) + }, + getNsAfterCleanup: func(t *testing.T, err error) { + assert.True(t, k8serrors.IsNotFound(err), "expected namespace to be deleted after cleanup, but client returned %v", err) + }, + }, + "ephemeral absent and no write permission": { + cl: newClientWithAbsentNsNoWritePerm, + wantErr: errCreationForbidden, + getNsBeforeCleanup: func(t *testing.T, err error) { + assert.True(t, k8serrors.IsNotFound(err), "expected namespace to be missing before cleanup, but client returned %v", err) + }, + getNsAfterCleanup: func(t *testing.T, err error) { + assert.True(t, k8serrors.IsNotFound(err), "expected namespace to be missing after cleanup, but client returned %v", err) + }, + }, + "ephemeral exists and no write permission": { + cl: newClientWithExistingNsNoWritePerm, + expectedCleanupError: k8serrors.IsForbidden, + getNsBeforeCleanup: func(t *testing.T, err error) { + require.NoError(t, err) + }, + getNsAfterCleanup: func(t *testing.T, err error) { + require.NoError(t, err) + }, + }, + "ephemeral exists and no permissions at all": { + cl: newClientWithExistingNsNoPerms, + expectedCleanupError: k8serrors.IsForbidden, + getNsBeforeCleanup: func(t *testing.T, err error) { + require.NoError(t, err) + }, + getNsAfterCleanup: func(t *testing.T, err error) { + require.NoError(t, err) + }, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + c := NewCase(name, "", tt.options...) + tm := &testMock{} + cl := tt.cl(t, c.ns.name) + if npc, ok := cl.(*noPermClient); ok { + npc.t = t + } + clk := clientWithKubeConfig{ + Client: cl, + kubeConfigPath: "kubeconfig/path", + logger: testutils.NewTestLogger(t, ""), + } + + gotErr := c.createNamespace(tm, clk) + if tt.wantErr == nil { + assert.NoError(t, gotErr) + } else { + assert.ErrorIs(t, gotErr, tt.wantErr) + } + + baseClient := cl + if npc, ok := cl.(*noPermClient); ok { + baseClient = npc.Client + } + + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: c.ns.name, + }, + } + err := baseClient.Get(t.Context(), kubernetes.ObjectKey(ns), ns) + tt.getNsBeforeCleanup(t, err) + + if tm.cleanup != nil { + tm.cleanup() + } + + if tt.expectedCleanupError != nil { + // Error should have been called once... + require.Len(t, tm.testErrors, 1) + // ...with one parameter... + require.Len(t, tm.testErrors[0], 1) + // ...which is an error. + err, ok := tm.testErrors[0][0].(error) + require.True(t, ok) + assert.True(t, tt.expectedCleanupError(err)) + } else { + assert.Empty(t, tm.testErrors) + } + ns = &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: c.ns.name, + }, + } + err = baseClient.Get(t.Context(), kubernetes.ObjectKey(ns), ns) + + tt.getNsAfterCleanup(t, err) + }) + } +} + +// noPermClient wraps a client and returns forbidden errors for Create/Delete operations. +// Optionally it also refuses Get operations. +type noPermClient struct { + client.Client + forbidGet bool + t *testing.T +} + +var errCreationForbidden = k8serrors.NewForbidden(schema.GroupResource{Group: "", Resource: "namespaces"}, "foo", fmt.Errorf("forbidden: User cannot create resource \"namespaces\"")) + +func (c *noPermClient) Create(_ context.Context, obj client.Object, _ ...client.CreateOption) error { + c.t.Logf("Create object %v refused", obj.GetObjectKind().GroupVersionKind()) + return errCreationForbidden +} + +func (c *noPermClient) Delete(_ context.Context, obj client.Object, _ ...client.DeleteOption) error { + c.t.Logf("Delete object %v refused", obj.GetObjectKind().GroupVersionKind()) + return k8serrors.NewForbidden(schema.GroupResource{Group: "", Resource: "namespaces"}, obj.GetName(), fmt.Errorf("forbidden: User cannot delete resource \"namespaces\"")) +} + +func (c *noPermClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + if c.forbidGet { + c.t.Logf("Get object %v refused", key) + return k8serrors.NewForbidden(schema.GroupResource{Group: "", Resource: "namespaces"}, obj.GetName(), fmt.Errorf("forbidden: User cannot get resource \"namespaces\"")) + } + return c.Client.Get(ctx, key, obj, opts...) +} + +func newClientWithExistingNsNoWritePerm(t *testing.T, nsName string) client.Client { + return &noPermClient{ + Client: fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(&corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: nsName, + }, + }).Build(), + forbidGet: false, + t: t, + } +} +func newClientWithAbsentNsNoWritePerm(t *testing.T, _ string) client.Client { + return &noPermClient{ + Client: fake.NewClientBuilder().WithScheme(scheme.Scheme).Build(), + forbidGet: false, + t: t, + } +} +func newClientWithExistingNsNoPerms(t *testing.T, nsName string) client.Client { + return &noPermClient{ + Client: fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(&corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: nsName, + }, + }).Build(), + forbidGet: true, + t: t, + } +} + +func newClientWithAbsentNs(*testing.T, string) client.Client { + return fake.NewClientBuilder().WithScheme(scheme.Scheme).Build() +} + +func newClientWithExistingNs(_ *testing.T, nsName string) client.Client { + return fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(&corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: nsName, + }, + }).Build() +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kuttl-0.23.0/internal/utils/commands.go new/kuttl-0.24.0/internal/utils/commands.go --- old/kuttl-0.23.0/internal/utils/commands.go 2025-11-19 11:01:38.000000000 +0100 +++ new/kuttl-0.24.0/internal/utils/commands.go 2025-12-03 08:15:11.000000000 +0100 @@ -50,7 +50,7 @@ if cmd.Namespaced { fs := pflag.NewFlagSet("", pflag.ContinueOnError) - fs.ParseErrorsWhitelist.UnknownFlags = true + fs.ParseErrorsAllowlist.UnknownFlags = true namespaceParsed := fs.StringP("namespace", "n", "", "") if err := fs.Parse(argSplit); err != nil { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kuttl-0.23.0/internal/utils/files/files.go new/kuttl-0.24.0/internal/utils/files/files.go --- old/kuttl-0.23.0/internal/utils/files/files.go 2025-11-19 11:01:38.000000000 +0100 +++ new/kuttl-0.24.0/internal/utils/files/files.go 2025-12-03 08:15:11.000000000 +0100 @@ -8,17 +8,44 @@ testutils "github.com/kudobuilder/kuttl/internal/utils" ) +var defaultIgnorePatterns = []string{"README*"} + +// matchesAnyPattern checks if a filename matches any of the given patterns. +// Returns true if a match is found, false otherwise. +func matchesAnyPattern(filename string, patterns []string, logger testutils.Logger) bool { + for _, pattern := range patterns { + matched, err := filepath.Match(pattern, filename) + if err != nil { + logger.Logf("Invalid ignore pattern %q: %v", pattern, err) + continue + } + if matched { + return true + } + } + return false +} + // CollectTestStepFiles collects a map of test steps and their associated files // from a directory. -func CollectTestStepFiles(dir string, logger testutils.Logger) (map[int64][]string, error) { +func CollectTestStepFiles(dir string, logger testutils.Logger, ignorePatterns []string) (map[int64][]string, error) { testStepFiles := map[int64][]string{} + patterns := ignorePatterns + if patterns == nil { + patterns = defaultIgnorePatterns + } + files, err := os.ReadDir(dir) if err != nil { return nil, err } for _, file := range files { + if matchesAnyPattern(file.Name(), patterns, logger) { + continue + } + f := kfile.Parse(file.Name()) if f.Type == kfile.TypeUnknown { logger.Logf("Ignoring %q: %v.", file.Name(), f.Error) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kuttl-0.23.0/internal/utils/files/files_test.go new/kuttl-0.24.0/internal/utils/files/files_test.go --- old/kuttl-0.23.0/internal/utils/files/files_test.go 2025-11-19 11:01:38.000000000 +0100 +++ new/kuttl-0.24.0/internal/utils/files/files_test.go 2025-12-03 08:15:11.000000000 +0100 @@ -1,6 +1,8 @@ package files import ( + "fmt" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -9,6 +11,38 @@ testutils "github.com/kudobuilder/kuttl/internal/utils" ) +// mockLogger is a simple logger that captures log messages for testing +type mockLogger struct { + messages []string +} + +func (m *mockLogger) Log(args ...interface{}) { + m.messages = append(m.messages, fmt.Sprint(args...)) +} + +func (m *mockLogger) Logf(format string, args ...interface{}) { + m.messages = append(m.messages, fmt.Sprintf(format, args...)) +} + +func (m *mockLogger) WithPrefix(_ string) testutils.Logger { + return m +} + +func (m *mockLogger) Write(p []byte) (n int, err error) { + return len(p), nil +} + +func (m *mockLogger) Flush() {} + +func (m *mockLogger) hasMessageContaining(substring string) bool { + for _, msg := range m.messages { + if strings.Contains(msg, substring) { + return true + } + } + return false +} + func TestCollectTestStepFiles(t *testing.T) { for _, tt := range []struct { path string @@ -48,9 +82,38 @@ }, } { t.Run(tt.path, func(t *testing.T) { - testStepFiles, err := CollectTestStepFiles(tt.path, testutils.NewTestLogger(t, tt.path)) + testStepFiles, err := CollectTestStepFiles(tt.path, testutils.NewTestLogger(t, tt.path), nil) require.NoError(t, err) assert.Equal(t, tt.expected, testStepFiles) }) } } + +func TestCollectTestStepFilesWithIgnorePatterns(t *testing.T) { + t.Run("default patterns ignore README files", func(t *testing.T) { + logger := &mockLogger{} + _, err := CollectTestStepFiles("test_data/with-overrides", logger, nil) + require.NoError(t, err) + + assert.False(t, logger.hasMessageContaining("Ignoring \"README.md\""), + "README.md should be silently ignored with default patterns") + }) + + t.Run("explicit patterns override defaults", func(t *testing.T) { + logger := &mockLogger{} + _, err := CollectTestStepFiles("test_data/with-overrides", logger, []string{}) + require.NoError(t, err) + + assert.True(t, logger.hasMessageContaining("Ignoring \"README.md\""), + "README.md should generate warning when default patterns are overridden with empty list") + }) + + t.Run("custom patterns silently ignore matching files", func(t *testing.T) { + logger := &mockLogger{} + _, err := CollectTestStepFiles("test_data/with-overrides", logger, []string{"*.txt", "*.md"}) + require.NoError(t, err) + + assert.False(t, logger.hasMessageContaining("Ignoring \"README.md\""), + "README.md should be silently ignored with matching custom pattern") + }) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kuttl-0.23.0/pkg/apis/testharness/v1beta1/test_types.go new/kuttl-0.24.0/pkg/apis/testharness/v1beta1/test_types.go --- old/kuttl-0.23.0/pkg/apis/testharness/v1beta1/test_types.go 2025-11-19 11:01:38.000000000 +0100 +++ new/kuttl-0.24.0/pkg/apis/testharness/v1beta1/test_types.go 2025-12-03 08:15:11.000000000 +0100 @@ -97,6 +97,10 @@ // Suppress is used to suppress logs Suppress []string `json:"suppress"` + // IgnoreFiles is a list of file patterns (e.g., "*.md", "README*") to ignore when collecting test steps. + // Files matching these patterns will not generate warnings about not matching the expected test file pattern. + IgnoreFiles []string `json:"ignoreFiles"` + Config *RestConfig `json:"config,omitempty"` } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kuttl-0.23.0/pkg/apis/testharness/v1beta1/zz_generated.deepcopy.go new/kuttl-0.24.0/pkg/apis/testharness/v1beta1/zz_generated.deepcopy.go --- old/kuttl-0.23.0/pkg/apis/testharness/v1beta1/zz_generated.deepcopy.go 2025-11-19 11:01:38.000000000 +0100 +++ new/kuttl-0.24.0/pkg/apis/testharness/v1beta1/zz_generated.deepcopy.go 2025-12-03 08:15:11.000000000 +0100 @@ -326,6 +326,11 @@ *out = make([]string, len(*in)) copy(*out, *in) } + if in.IgnoreFiles != nil { + in, out := &in.IgnoreFiles, &out.IgnoreFiles + *out = make([]string, len(*in)) + copy(*out, *in) + } if in.Config != nil { in, out := &in.Config, &out.Config *out = (*in).DeepCopy() ++++++ kuttl.obsinfo ++++++ --- /var/tmp/diff_new_pack.WSRHH6/_old 2025-12-05 16:55:11.836774287 +0100 +++ /var/tmp/diff_new_pack.WSRHH6/_new 2025-12-05 16:55:11.848774789 +0100 @@ -1,5 +1,5 @@ name: kuttl -version: 0.23.0 -mtime: 1763546498 -commit: 9ef9f000371ebdd88fb16747ee5ac49f88021ade +version: 0.24.0 +mtime: 1764746111 +commit: eb4852937e499abb3db28505638210b04b8f5984 ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/kuttl/vendor.tar.gz /work/SRC/openSUSE:Factory/.kuttl.new.1939/vendor.tar.gz differ: char 131, line 2
