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

Reply via email to