From 1244759ba59547820b59e1ad29726eb526057be8 Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Wed, 29 Apr 2026 13:40:04 +0200 Subject: [PATCH 1/5] chore: bump golangci-lint to v2.11.4 and sync CI Go version from go.mod Update golangci-lint from v2.7.2 to v2.11.4 (adds Go 1.26 support). Update CI workflow to read Go version from go.mod instead of hardcoding 1.21. Bump GitHub Actions to current versions and add new linters to disabled list. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Marcin Owsiany --- .github/workflows/lint.yml | 6 +++--- .golangci.yml | 10 +++++++++- Makefile | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 080ba37e..afed6b46 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,10 +14,10 @@ jobs: lint: runs-on: ubuntu-24.04 steps: - - uses: actions/setup-go@v2.1.4 + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 with: - go-version: 1.21 - - uses: actions/checkout@v2.4.0 + go-version-file: go.mod - name: "Lint the code" run: make lint - name: "Verify generate" diff --git a/.golangci.yml b/.golangci.yml index 40b090ce..b2d00a28 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -78,27 +78,34 @@ linters: # These linters are disabled ONLY because fixing/investigating their complaints initially was too overwhelming. # We SHOULD try and evaluate each of them eventually and either fix their reports # or comment here on why we decided to not listen to them. + - arangolint - cyclop - depguard + - embeddedstructfieldcheck - err113 - errorlint - exhaustruct - forbidigo + - funcorder - funlen + - gochecknoglobals - gocognit + - godoclint - godot - godox - gosec - inamedparam - intrange + - iotamixing - ireturn - - gochecknoglobals - maintidx - mnd + - modernize - musttag - nestif - nilnil - nlreturn + - noinlineerr - nonamedreturns - paralleltest - perfsprint @@ -107,6 +114,7 @@ linters: - testifylint - testpackage - thelper + - unqueryvet - varnamelen - wrapcheck settings: diff --git a/Makefile b/Makefile index 2f681f9a..e4d14248 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ BUILD_DATE := $(shell date -u -d "@$SOURCE_DATE_EPOCH" "+${DATE_FMT}" 2>/dev/nul LDFLAGS := -X ${GIT_VERSION_PATH}=${GIT_VERSION} -X ${GIT_COMMIT_PATH}=${GIT_COMMIT} -X ${BUILD_DATE_PATH}=${BUILD_DATE} # Please update the list of linters in .golagci.yml when bumping the version. -GOLANGCI_LINT_VER = 2.7.2 +GOLANGCI_LINT_VER = 2.11.4 # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. ENVTEST_K8S_VERSION ?= 1.25.0 From 09e760a074ecee21aaf9481bb310bbfecce77fef Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Wed, 29 Apr 2026 13:44:10 +0200 Subject: [PATCH 2/5] chore: enable zero-issue linters and fix embeddedstructfieldcheck Enable arangolint, depguard, godoclint, iotamixing, unqueryvet (no issues), and embeddedstructfieldcheck (fixed 4 missing blank lines after embedded fields). Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Marcin Owsiany --- .golangci.yml | 12 +++++------- internal/testcase/case_test.go | 1 + internal/testcase/client.go | 1 + pkg/apis/testharness/v1beta1/test_types.go | 2 ++ 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index b2d00a28..ad488d94 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -2,6 +2,7 @@ version: "2" linters: enable: + - arangolint - asasalint - asciicheck - bidichk @@ -14,6 +15,7 @@ linters: - dogsled - dupl - dupword + - embeddedstructfieldcheck - durationcheck - errcheck - errchkjson @@ -24,6 +26,7 @@ linters: - forcetypeassert - ginkgolinter - gocheckcompilerdirectives + - godoclint - gochecknoinits - gochecksumtype - goconst @@ -40,6 +43,7 @@ linters: - importas - ineffassign - interfacebloat + - iotamixing - lll - loggercheck - makezero @@ -67,6 +71,7 @@ linters: - tparallel - unconvert - unparam + - unqueryvet - unused - usestdlibvars - usetesting @@ -78,10 +83,6 @@ linters: # These linters are disabled ONLY because fixing/investigating their complaints initially was too overwhelming. # We SHOULD try and evaluate each of them eventually and either fix their reports # or comment here on why we decided to not listen to them. - - arangolint - - cyclop - - depguard - - embeddedstructfieldcheck - err113 - errorlint - exhaustruct @@ -90,13 +91,11 @@ linters: - funlen - gochecknoglobals - gocognit - - godoclint - godot - godox - gosec - inamedparam - intrange - - iotamixing - ireturn - maintidx - mnd @@ -114,7 +113,6 @@ linters: - testifylint - testpackage - thelper - - unqueryvet - varnamelen - wrapcheck settings: diff --git a/internal/testcase/case_test.go b/internal/testcase/case_test.go index b38e053a..eeb14a06 100644 --- a/internal/testcase/case_test.go +++ b/internal/testcase/case_test.go @@ -552,6 +552,7 @@ func TestCase_createNamespace(t *testing.T) { // Optionally it also refuses Get operations. type noPermClient struct { client.Client + forbidGet bool t *testing.T } diff --git a/internal/testcase/client.go b/internal/testcase/client.go index f518c508..932a25e9 100644 --- a/internal/testcase/client.go +++ b/internal/testcase/client.go @@ -10,6 +10,7 @@ import ( type clientWithKubeConfig struct { client.Client + kubeConfigPath string logger testutils.Logger } diff --git a/pkg/apis/testharness/v1beta1/test_types.go b/pkg/apis/testharness/v1beta1/test_types.go index 2a246481..96a9f300 100644 --- a/pkg/apis/testharness/v1beta1/test_types.go +++ b/pkg/apis/testharness/v1beta1/test_types.go @@ -158,6 +158,7 @@ type TestAssert struct { metav1.TypeMeta `json:",inline"` // Override the default metadata. Set labels or override the test step name. metav1.ObjectMeta `json:"metadata,omitempty"` + // Override the default timeout of 30 seconds (in seconds). Timeout int `json:"timeout"` // Collectors is a set of pod log collectors fired on an assert failure @@ -189,6 +190,7 @@ type TestAssertCommand struct { // objects by label. type ObjectReference struct { corev1.ObjectReference `json:",inline"` + // Labels to match on. Labels map[string]string `json:"labels"` } From f25552bdb799b708ff7b26ae981dd385b84bdc88 Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Wed, 29 Apr 2026 13:52:49 +0200 Subject: [PATCH 3/5] chore: enable godot and inamedparam linters, annotate remaining disabled linters Enable godot (comments must end in a period) and fix all comments. Enable inamedparam (interface methods must have named parameters) and add parameter names to DockerClient and Logger interfaces. Add cyclop and depguard back to the disabled list. Add issue-count summary comments to all disabled linters. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Marcin Owsiany # Conflicts: # internal/kubernetes/wait.go --- .golangci.yml | 40 ++++++++++++++++++-- internal/assert/assert.go | 4 +- internal/assert/dummy_test.go | 2 +- internal/expressions/dummy_test.go | 2 +- internal/file/files.go | 4 +- internal/file/tar.go | 4 +- internal/harness/harness.go | 8 ++-- internal/harness/harness_integration_test.go | 2 +- internal/http/client.go | 8 ++-- internal/http/http.go | 4 +- internal/kubernetes/fake/dummy_test.go | 2 +- internal/kubernetes/serialization.go | 4 +- internal/kubernetes/unstructured.go | 2 +- internal/kuttlctl/cmd/assert.go | 2 +- internal/kuttlctl/cmd/errors.go | 2 +- internal/kuttlctl/cmd/root.go | 2 +- internal/kuttlctl/cmd/test.go | 2 +- internal/kuttlctl/cmd/version.go | 4 +- internal/report/report.go | 22 +++++------ internal/step/step.go | 4 +- internal/utils/commands.go | 6 +-- internal/utils/docker.go | 4 +- internal/utils/events/dummy_test.go | 2 +- internal/utils/files/files_test.go | 2 +- internal/utils/logger.go | 2 +- internal/utils/subset.go | 2 +- internal/version/base.go | 2 +- internal/version/version.go | 12 +++--- pkg/apis/dummy_test.go | 2 +- pkg/apis/testharness/v1beta1/collector.go | 8 ++-- pkg/apis/testharness/v1beta1/test_types.go | 4 +- 31 files changed, 102 insertions(+), 68 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index ad488d94..9000cebd 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -15,8 +15,8 @@ linters: - dogsled - dupl - dupword - - embeddedstructfieldcheck - durationcheck + - embeddedstructfieldcheck - errcheck - errchkjson - errname @@ -27,6 +27,7 @@ linters: - ginkgolinter - gocheckcompilerdirectives - godoclint + - godot - gochecknoinits - gochecksumtype - goconst @@ -40,6 +41,7 @@ linters: - govet - grouper - iface + - inamedparam - importas - ineffassign - interfacebloat @@ -83,37 +85,69 @@ linters: # These linters are disabled ONLY because fixing/investigating their complaints initially was too overwhelming. # We SHOULD try and evaluate each of them eventually and either fix their reports # or comment here on why we decided to not listen to them. + # 15 issues: many functions exceed default cyclomatic complexity threshold + - cyclop + # 50 issues: needs allowlist configuration for all imports + - depguard + # 50 issues: requires wrapping all errors with fmt.Errorf - err113 + # 6 issues: requires error type assertions to use errors.As/errors.Is - errorlint + # 50 issues: requires initializing every struct field explicitly - exhaustruct + # 6 issues: forbids fmt.Print*/os.Stdout usage - forbidigo + # 18 issues: enforces function declaration ordering within files - funcorder + # 28 issues: many functions exceed default length limit - funlen + # 15 issues: flags all package-level variables - gochecknoglobals + # 4 issues: similar to cyclop, flags high cognitive complexity - gocognit - - godot + # 9 issues: flags TODO/FIXME comments - godox + # 11 issues: flags potential security issues (hardcoded credentials, weak crypto, etc.) - gosec - - inamedparam + # 4 issues: requires integer range loops (Go 1.22+) - intrange + # 28 issues: forbids returning interfaces - ireturn + # 2 issues: flags functions with low maintainability index - maintidx + # 20 issues: flags magic numbers throughout the code - mnd + # 7 issues: suggests using modern Go idioms (e.g. slices.Contains) - modernize + # 4 issues: requires struct tags on marshaled types - musttag + # 7 issues: flags deeply nested if statements - nestif + # 3 issues: flags functions returning both nil error and nil value - nilnil + # 7 issues: requires blank lines before return/branch statements - nlreturn + # 3 issues: forbids inline error handling (if err := ...; err != nil) - noinlineerr + # 13 issues: forbids named return values - nonamedreturns + # 50 issues: requires t.Parallel() in every test - paralleltest + # 8 issues: suggests replacing fmt.Sprintf with faster alternatives - perfsprint + # 4 issues: suggests preallocating slices - prealloc + # 27 issues: enforces struct tag alignment - tagalign + # 27 issues: enforces testify best practices - testifylint + # 31 issues: requires using separate _test packages - testpackage + # 6 issues: requires t.Helper() in test helper functions - thelper + # 50 issues: enforces minimum variable name length - varnamelen + # 50 issues: requires wrapping errors from external packages - wrapcheck settings: errcheck: diff --git a/internal/assert/assert.go b/internal/assert/assert.go index f2e705b8..46704896 100644 --- a/internal/assert/assert.go +++ b/internal/assert/assert.go @@ -16,7 +16,7 @@ import ( "github.com/kudobuilder/kuttl/internal/step" ) -// Assert checks all provided assert files against a namespace. Upon assert failure, it prints the failures and returns an error +// Assert checks all provided assert files against a namespace. Upon assert failure, it prints the failures and returns an error. func Assert(namespace string, timeout int, assertFiles ...string) error { var objects []client.Object @@ -56,7 +56,7 @@ func Assert(namespace string, timeout int, assertFiles ...string) error { return errors.New("asserts not valid") } -// Errors checks all provided errors files against a namespace. Upon assert failure, it prints the failures and returns an error +// Errors checks all provided errors files against a namespace. Upon assert failure, it prints the failures and returns an error. func Errors(namespace string, timeout int, errorFiles ...string) error { var objects []client.Object diff --git a/internal/assert/dummy_test.go b/internal/assert/dummy_test.go index 1f4ff6b7..714fd109 100644 --- a/internal/assert/dummy_test.go +++ b/internal/assert/dummy_test.go @@ -2,7 +2,7 @@ package assert import "testing" -// TestDummy is a no-op test to satisfy coverage tools +// TestDummy is a no-op test to satisfy coverage tools. func TestDummy(_ *testing.T) { // This test exists only to ensure the package is recognized by go test // and to avoid "go: no such tool 'covdata'" warnings diff --git a/internal/expressions/dummy_test.go b/internal/expressions/dummy_test.go index 51c32083..e99a1d0c 100644 --- a/internal/expressions/dummy_test.go +++ b/internal/expressions/dummy_test.go @@ -2,7 +2,7 @@ package expressions import "testing" -// TestDummy is a no-op test to satisfy coverage tools +// TestDummy is a no-op test to satisfy coverage tools. func TestDummy(_ *testing.T) { // This test exists only to ensure the package is recognized by go test // and to avoid "go: no such tool 'covdata'" warnings diff --git a/internal/file/files.go b/internal/file/files.go index 625f0d6f..379c9c7a 100644 --- a/internal/file/files.go +++ b/internal/file/files.go @@ -28,7 +28,7 @@ func ToObjects(paths []string) ([]client.Object, error) { } // FromPath from a file or dir path returns an array of flat file paths. -// pattern is a filepath.Match pattern to limit files to a pattern +// pattern is a filepath.Match pattern to limit files to a pattern. func FromPath(path, pattern string) ([]string, error) { files := []string{} @@ -61,7 +61,7 @@ func FromPath(path, pattern string) ([]string, error) { return files, nil } -// TrimExt removes the ext of a file path, foo.tar == foo +// TrimExt removes the ext of a file path, foo.tar == foo. func TrimExt(path string) string { return strings.TrimSuffix(path, filepath.Ext(path)) } diff --git a/internal/file/tar.go b/internal/file/tar.go index fb3ee3f5..f8e33751 100644 --- a/internal/file/tar.go +++ b/internal/file/tar.go @@ -10,7 +10,7 @@ import ( // UntarInPlace untars a .tar file using the same name as the file as the sub-folder name in the folder it was referenced in. // i.e., /opt/foo.tar will land in /opt/foo -// supports tar and tgz file formats +// supports tar and tgz file formats. func UntarInPlace(path string) error { folder := TrimExt(path) file, err := os.Open(path) @@ -26,7 +26,7 @@ func UntarInPlace(path string) error { // UnTar untars a tar or tgz file into the dest folder. // dest is the folder location // io.Reader is a reader that is tar (or compressed tar) format -// compressed is true if reader is a compressed format +// compressed is true if reader is a compressed format. func UnTar(dest string, r io.Reader, compressed bool) (err error) { if compressed { gzr, err := gzip.NewReader(r) diff --git a/internal/harness/harness.go b/internal/harness/harness.go index 446189f6..053bf080 100644 --- a/internal/harness/harness.go +++ b/internal/harness/harness.go @@ -172,7 +172,7 @@ func (h *Harness) RunKIND() (*rest.Config, error) { } // initTempPath creates the temp folder if needed. -// various parts of system may need it, starting with kind, or working with tar test suites +// various parts of system may need it, starting with kind, or working with tar test suites. func (h *Harness) initTempPath() (err error) { if h.tempPath == "" { h.tempPath, err = os.MkdirTemp("", "kuttl") @@ -430,7 +430,7 @@ func (h *Harness) RunTests() { h.T.Log("run tests finished") } -// testPreProcessing provides preprocessing bring all tests suites local if there are any refers to URLs +// testPreProcessing provides preprocessing bring all tests suites local if there are any refers to URLs. func (h *Harness) testPreProcessing() []string { testDirs := []string{} // preprocessing step @@ -617,7 +617,7 @@ func (h *Harness) Stop() { } // wraps Test.Fatal in order to clean up harness -// fatal should NOT be used with a go routine, it is not thread safe +// fatal should NOT be used with a go routine, it is not thread safe. func (h *Harness) fatal(err error) { // clean up on fatal in setup if !h.stopping { @@ -644,7 +644,7 @@ func (h *Harness) Report() { } } -// NewSuiteReport creates and assigns a TestSuite to the TestSuites (then returns the suite), +// NewSuiteReport creates and assigns a TestSuite to the TestSuites (then returns the suite),. func (h *Harness) NewSuiteReport(name string) *report.Testsuite { suite := report.NewSuite(name, h.TestSuite.ReportGranularity) h.report.AddTestSuite(suite) diff --git a/internal/harness/harness_integration_test.go b/internal/harness/harness_integration_test.go index 2b3aa228..6d9a661d 100644 --- a/internal/harness/harness_integration_test.go +++ b/internal/harness/harness_integration_test.go @@ -52,7 +52,7 @@ func TestHarnessRunIntegrationWithConfig(t *testing.T) { } } -// This test requires external KinD support to run thus is an integration test +// This test requires external KinD support to run thus is an integration test. func TestRunBackgroundCommands(t *testing.T) { h := Harness{ T: t, diff --git a/internal/http/client.go b/internal/http/client.go index cc77a679..2fce4e8c 100644 --- a/internal/http/client.go +++ b/internal/http/client.go @@ -16,7 +16,7 @@ import ( "github.com/kudobuilder/kuttl/internal/version" ) -// Client is client used to simplified http requests for tarballs +// Client is client used to simplified http requests for tarballs. type Client struct { client *http.Client UserAgent string @@ -35,7 +35,7 @@ func (c *Client) Get(url string) (*http.Response, error) { } // GetByteBuffer performs HTTP get, retrieves the contents and returns a bytes.Buffer of the entire contents -// this could be dangerous against an extremely large file +// this could be dangerous against an extremely large file. func (c *Client) GetByteBuffer(url string) (*bytes.Buffer, error) { buf := bytes.NewBuffer(nil) @@ -139,7 +139,7 @@ func (wc *writeCounter) Write(p []byte) (int, error) { return n, nil } -// PrintProgress prints the progress of a file write +// PrintProgress prints the progress of a file write. func (wc *writeCounter) PrintProgress() { // Clear the line by using a character return to go back to the start and remove // the remaining characters by filling it with spaces @@ -150,7 +150,7 @@ func (wc *writeCounter) PrintProgress() { fmt.Printf("\rDownloading (%s) %s complete", wc.Name, humanize.Bytes(wc.Total)) } -// NewClient creates HTTP client +// NewClient creates HTTP client. func NewClient() *Client { var client Client tr := &http.Transport{ diff --git a/internal/http/http.go b/internal/http/http.go index 2fe26736..d70c5ada 100644 --- a/internal/http/http.go +++ b/internal/http/http.go @@ -10,7 +10,7 @@ import ( "github.com/kudobuilder/kuttl/internal/kubernetes" ) -// IsURL returns true if string is an URL +// IsURL returns true if string is an URL. func IsURL(str string) bool { u, err := url.Parse(str) return err == nil && u.Scheme != "" && u.Host != "" @@ -35,7 +35,7 @@ func ToObjects(urlPath string) ([]client.Object, error) { return apply, nil } -// Read returns a buffer for the file at the url +// Read returns a buffer for the file at the url. func Read(urlPath string) (*bytes.Buffer, error) { c := NewClient() return c.GetByteBuffer(urlPath) diff --git a/internal/kubernetes/fake/dummy_test.go b/internal/kubernetes/fake/dummy_test.go index a7903dfa..54baefbe 100644 --- a/internal/kubernetes/fake/dummy_test.go +++ b/internal/kubernetes/fake/dummy_test.go @@ -3,7 +3,7 @@ package fake import "testing" -// TestDummy is a no-op test to satisfy coverage tools +// TestDummy is a no-op test to satisfy coverage tools. func TestDummy(_ *testing.T) { // This test exists only to ensure the package is recognized by go test // and to avoid "go: no such tool 'covdata'" warnings diff --git a/internal/kubernetes/serialization.go b/internal/kubernetes/serialization.go index e3b96dac..09146222 100644 --- a/internal/kubernetes/serialization.go +++ b/internal/kubernetes/serialization.go @@ -77,7 +77,7 @@ func countLines(k string, v interface{}) (int, error) { return strings.Count(buf.String(), "\n"), nil } -// PrettyDiff creates a unified diff highlighting the differences between two Kubernetes resources +// PrettyDiff creates a unified diff highlighting the differences between two Kubernetes resources. func PrettyDiff(expected *unstructured.Unstructured, actual *unstructured.Unstructured) (string, error) { actualPruned := pruneLargeAdditions(expected, actual) @@ -199,7 +199,7 @@ func LoadYAMLFromFile(path string) ([]client.Object, error) { return LoadYAML(path, opened) } -// LoadYAML loads all objects from a reader +// LoadYAML loads all objects from a reader. func LoadYAML(path string, r io.Reader) ([]client.Object, error) { yamlReader := yaml.NewYAMLReader(bufio.NewReader(r)) diff --git a/internal/kubernetes/unstructured.go b/internal/kubernetes/unstructured.go index 6adbd6ad..69324671 100644 --- a/internal/kubernetes/unstructured.go +++ b/internal/kubernetes/unstructured.go @@ -31,7 +31,7 @@ func NewResource(apiVersion, kind, name, namespace string) *unstructured.Unstruc } } -// NewClusterRoleBinding Create a clusterrolebinding for the serviceAccount passed +// NewClusterRoleBinding Create a clusterrolebinding for the serviceAccount passed. func NewClusterRoleBinding(apiVersion, kind, name, namespace string, serviceAccount string, roleName string) runtime.Object { sa := &rbacv1.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ diff --git a/internal/kuttlctl/cmd/assert.go b/internal/kuttlctl/cmd/assert.go index 0eea79d2..32609ac0 100644 --- a/internal/kuttlctl/cmd/assert.go +++ b/internal/kuttlctl/cmd/assert.go @@ -14,7 +14,7 @@ var ( kubectl kuttl assert ` ) -// newAssertCmd returns a new initialized instance of the assert sub command +// newAssertCmd returns a new initialized instance of the assert sub command. func newAssertCmd() *cobra.Command { timeout := 5 namespace := "default" diff --git a/internal/kuttlctl/cmd/errors.go b/internal/kuttlctl/cmd/errors.go index dda35722..27ee9872 100644 --- a/internal/kuttlctl/cmd/errors.go +++ b/internal/kuttlctl/cmd/errors.go @@ -13,7 +13,7 @@ var ( kubectl kuttl errors ...` ) -// newErrorsCmd returns a new initialized instance of the errors sub command +// newErrorsCmd returns a new initialized instance of the errors sub command. func newErrorsCmd() *cobra.Command { timeout := 5 namespace := "default" diff --git a/internal/kuttlctl/cmd/root.go b/internal/kuttlctl/cmd/root.go index 53e51e8a..ec4e3c75 100644 --- a/internal/kuttlctl/cmd/root.go +++ b/internal/kuttlctl/cmd/root.go @@ -7,7 +7,7 @@ import ( "github.com/kudobuilder/kuttl/internal/version" ) -// NewKuttlCmd creates a new root command for kuttlctl +// NewKuttlCmd creates a new root command for kuttlctl. func NewKuttlCmd() *cobra.Command { cmd := &cobra.Command{ Use: "kubectl-kuttl", diff --git a/internal/kuttlctl/cmd/test.go b/internal/kuttlctl/cmd/test.go index 75ab539f..e2d6d5cd 100644 --- a/internal/kuttlctl/cmd/test.go +++ b/internal/kuttlctl/cmd/test.go @@ -43,7 +43,7 @@ var ( ` ) -// newTestCmd creates the test command for the CLI +// newTestCmd creates the test command for the CLI. func newTestCmd() *cobra.Command { //nolint:gocyclo configPath := "" crdDir := "" diff --git a/internal/kuttlctl/cmd/version.go b/internal/kuttlctl/cmd/version.go index 28d03b36..30fd9cbe 100644 --- a/internal/kuttlctl/cmd/version.go +++ b/internal/kuttlctl/cmd/version.go @@ -13,7 +13,7 @@ var ( kubectl kuttl version` ) -// newVersionCmd returns a new initialized instance of the version sub command +// newVersionCmd returns a new initialized instance of the version sub command. func newVersionCmd() *cobra.Command { versionCmd := &cobra.Command{ Use: "version", @@ -26,7 +26,7 @@ func newVersionCmd() *cobra.Command { return versionCmd } -// VersionCmd performs the version sub command +// VersionCmd performs the version sub command. func VersionCmd(_ *cobra.Command, _ []string) error { kuttlVersion := version.Get() fmt.Printf("KUTTL Version: %s\n", fmt.Sprintf("%#v", kuttlVersion)) diff --git a/internal/report/report.go b/internal/report/report.go index 19373286..b1789a3d 100644 --- a/internal/report/report.go +++ b/internal/report/report.go @@ -130,25 +130,25 @@ type TestReporter interface { Done() } -// NewSuiteCollection returns the address of a newly created TestSuites +// NewSuiteCollection returns the address of a newly created TestSuites. func NewSuiteCollection(name string) *Testsuites { start := time.Now() return &Testsuites{XMLName: xml.Name{Local: "testsuites"}, Name: name, start: start} } -// NewSuite returns the address of a newly created TestSuite +// NewSuite returns the address of a newly created TestSuite. func NewSuite(name string, reportGranularity string) *Testsuite { start := time.Now() return &Testsuite{Name: name, Timestamp: start, reportGranularity: reportGranularity} } -// NewCase returns the address of a newly create Testcase +// NewCase returns the address of a newly create Testcase. func NewCase(name string) *Testcase { start := time.Now() return &Testcase{Name: name, Timestamp: start} } -// NewFailure returns the address of a newly created Failure +// NewFailure returns the address of a newly created Failure. func NewFailure(msg string, errs []error) *Failure { f := &Failure{Message: msg} @@ -162,7 +162,7 @@ func NewFailure(msg string, errs []error) *Failure { return f } -// AddTestcase adds a testcase to a suite, providing stats and calculations to both +// AddTestcase adds a testcase to a suite, providing stats and calculations to both. func (ts *Testsuite) AddTestcase(testcase *Testcase) { // this is needed to calc elapse time of testsuite in a async work testcase.end = time.Now() @@ -182,7 +182,7 @@ func (ts *Testsuite) AddTestcase(testcase *Testcase) { } } -// AddProperty adds a property to a testsuite +// AddProperty adds a property to a testsuite. func (ts *Testsuite) AddProperty(property Property) { if ts.Properties == nil { ts.Properties = &Properties{Property: []Property{property}} @@ -299,7 +299,7 @@ func (r *testReporter) Done() { var _ TestReporter = (*testReporter)(nil) var _ StepReporter = (*stepReport)(nil) -// AddTestSuite is a convenience method to add a testsuite to the collection in testsuites +// AddTestSuite is a convenience method to add a testsuite to the collection in testsuites. func (ts *Testsuites) AddTestSuite(testsuite *Testsuite) { // testsuite is added prior to stat availability, stat management in the close of the testsuites ts.lock.Lock() @@ -311,7 +311,7 @@ func (ts *Testsuites) AddTestSuite(testsuite *Testsuite) { }) } -// AddProperty adds a property to a testsuites +// AddProperty adds a property to a testsuites. func (ts *Testsuites) AddProperty(property Property) { if ts.Properties == nil { ts.Properties = &Properties{Property: []Property{property}} @@ -324,7 +324,7 @@ func (ts *Testsuites) AddProperty(property Property) { ts.Properties.Property = append(ts.Properties.Property, property) } -// Close closes the report and does all end stat calculations +// Close closes the report and does all end stat calculations. func (ts *Testsuites) Close() { elapsed := time.Since(ts.start) ts.Time = fmt.Sprintf("%.3f", elapsed.Seconds()) @@ -340,7 +340,7 @@ func (ts *Testsuites) Close() { // latestEnd provides the time of the latest end out of the collection of testcases -// Report prints a report for TestSuites to the directory. ftype == json | xml +// Report prints a report for TestSuites to the directory. ftype == json | xml. func (ts *Testsuites) Report(dir, name string, ftype Type) error { ts.Close() @@ -374,7 +374,7 @@ func ensureDir(dir string) error { return err } -// SetFailure adds a failure to the TestSuites collection for startup failures in the test harness +// SetFailure adds a failure to the TestSuites collection for startup failures in the test harness. func (ts *Testsuites) SetFailure(message string) { ts.Failure = &Failure{ Message: message, diff --git a/internal/step/step.go b/internal/step/step.go index bdb03fb5..2ecac7c4 100644 --- a/internal/step/step.go +++ b/internal/step/step.go @@ -704,7 +704,7 @@ func (s *Step) Setup(caseLogger testutils.Logger, defaultClientFunc func(forceNe } } -// ObjectsFromPath returns an array of runtime.Objects for files / urls provided +// ObjectsFromPath returns an array of runtime.Objects for files / urls provided. func ObjectsFromPath(path, dir string) ([]client.Object, error) { if http.IsURL(path) { apply, err := http.ToObjects(path) @@ -727,7 +727,7 @@ func ObjectsFromPath(path, dir string) ([]client.Object, error) { return apply, nil } -// cleanPath returns either the abs path or the joined path +// cleanPath returns either the abs path or the joined path. func cleanPath(path, dir string) string { if filepath.IsAbs(path) { return path diff --git a/internal/utils/commands.go b/internal/utils/commands.go index d6420921..3001c6cf 100644 --- a/internal/utils/commands.go +++ b/internal/utils/commands.go @@ -71,7 +71,7 @@ func GetArgs(ctx context.Context, cmd harness.Command, namespace string, envMap // RunCommand runs a command with args. // args gets split on spaces (respecting quoted strings). -// if the command is run in the background a reference to the process is returned for later cleanup +// if the command is run in the background a reference to the process is returned for later cleanup. func RunCommand(ctx context.Context, namespace string, cmd harness.Command, cwd string, stdout io.Writer, stderr io.Writer, logger Logger, timeout int, kubeconfigOverride string) (*exec.Cmd, error) { actualDir, err := os.Getwd() if err != nil { @@ -180,14 +180,14 @@ func convertAssertCommand(assertCommands []harness.TestAssertCommand, timeout in return commands } -// RunAssertCommands runs a set of commands specified as TestAssertCommand +// RunAssertCommands runs a set of commands specified as TestAssertCommand. func RunAssertCommands(ctx context.Context, logger Logger, namespace string, commands []harness.TestAssertCommand, workdir string, timeout int, kubeconfigOverride string) ([]*exec.Cmd, error) { return RunCommands(ctx, logger, namespace, convertAssertCommand(commands, timeout), workdir, timeout, kubeconfigOverride) } // RunCommands runs a set of commands, returning any errors. // If any (non-background) command fails, the following commands are skipped -// commands running in the background are returned +// commands running in the background are returned. func RunCommands(ctx context.Context, logger Logger, namespace string, commands []harness.Command, workdir string, timeout int, kubeconfigOverride string) ([]*exec.Cmd, error) { bgs := []*exec.Cmd{} diff --git a/internal/utils/docker.go b/internal/utils/docker.go index 40e30f40..cafaac37 100644 --- a/internal/utils/docker.go +++ b/internal/utils/docker.go @@ -8,6 +8,6 @@ import ( // DockerClient is a wrapper interface for the Docker library to support unit testing. type DockerClient interface { - VolumeCreate(context.Context, client.VolumeCreateOptions) (client.VolumeCreateResult, error) - ImageSave(context.Context, []string, ...client.ImageSaveOption) (client.ImageSaveResult, error) + VolumeCreate(ctx context.Context, options client.VolumeCreateOptions) (client.VolumeCreateResult, error) + ImageSave(ctx context.Context, imageIDs []string, opts ...client.ImageSaveOption) (client.ImageSaveResult, error) } diff --git a/internal/utils/events/dummy_test.go b/internal/utils/events/dummy_test.go index 9f62b9fb..d34c00f1 100644 --- a/internal/utils/events/dummy_test.go +++ b/internal/utils/events/dummy_test.go @@ -3,7 +3,7 @@ package events import "testing" -// TestDummy is a no-op test to satisfy coverage tools +// TestDummy is a no-op test to satisfy coverage tools. func TestDummy(_ *testing.T) { // This test exists only to ensure the package is recognized by go test // and to avoid "go: no such tool 'covdata'" warnings diff --git a/internal/utils/files/files_test.go b/internal/utils/files/files_test.go index d69d72ae..1cc31e8c 100644 --- a/internal/utils/files/files_test.go +++ b/internal/utils/files/files_test.go @@ -11,7 +11,7 @@ import ( testutils "github.com/kudobuilder/kuttl/internal/utils" ) -// mockLogger is a simple logger that captures log messages for testing +// mockLogger is a simple logger that captures log messages for testing. type mockLogger struct { messages []string } diff --git a/internal/utils/logger.go b/internal/utils/logger.go index 78f7f1f1..11719db0 100644 --- a/internal/utils/logger.go +++ b/internal/utils/logger.go @@ -11,7 +11,7 @@ import ( type Logger interface { Log(args ...interface{}) Logf(format string, args ...interface{}) - WithPrefix(string) Logger + WithPrefix(prefix string) Logger Write(p []byte) (n int, err error) Flush() } diff --git a/internal/utils/subset.go b/internal/utils/subset.go index 7968aa5e..90b76fad 100644 --- a/internal/utils/subset.go +++ b/internal/utils/subset.go @@ -11,7 +11,7 @@ type SubsetError struct { message string } -// AppendPath appends key to the existing struct path. For example, in struct member `a.Key1.Key2`, the path would be ["Key1", "Key2"] +// AppendPath appends key to the existing struct path. For example, in struct member `a.Key1.Key2`, the path would be ["Key1", "Key2"]. func (e *SubsetError) AppendPath(key string) { if e.path == nil { e.path = []string{} diff --git a/internal/version/base.go b/internal/version/base.go index 3a5ec763..5075d8b0 100644 --- a/internal/version/base.go +++ b/internal/version/base.go @@ -16,7 +16,7 @@ package version var ( // semantic version, derived by build scripts (see // https://github.com/kubernetes/community/blob/master/contributors/design-proposals/release/versioning.md - // for a detailed discussion of this field) + // for a detailed discussion of this field). gitVersion = "v0.0.0-main+$Format:%h$" gitCommit = "$Format:%H$" // sha1 from git, output of $(git rev-parse HEAD) diff --git a/internal/version/version.go b/internal/version/version.go index bb0ae630..dae9ab02 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -52,7 +52,7 @@ func Get() Info { } } -// Version is an extension of semver.Version +// Version is an extension of semver.Version. type Version struct { *semver.Version } @@ -70,7 +70,7 @@ func (v *Version) CompareMajorMinor(o *Version) int { return 0 } -// compares v1 against v2 resulting in -1, 0, 1 for less than, equal, greater than +// compares v1 against v2 resulting in -1, 0, 1 for less than, equal, greater than. func compareSegment(v1, v2 uint64) int { if v1 < v2 { return -1 @@ -82,7 +82,7 @@ func compareSegment(v1, v2 uint64) int { return 0 } -// New provides an instance of Version from a semver string +// New provides an instance of Version from a semver string. func New(v string) (*Version, error) { ver, err := semver.NewVersion(v) if err != nil { @@ -92,12 +92,12 @@ func New(v string) (*Version, error) { } // FromGithubVersion provides a version parsed from github semver which starts with "v". -// v1.5.2 provides a sem version of 1.5.2 +// v1.5.2 provides a sem version of 1.5.2. func FromGithubVersion(v string) (*Version, error) { return New(Clean(v)) } -// FromSemVer converts a semver.Version to our Version +// FromSemVer converts a semver.Version to our Version. func FromSemVer(v *semver.Version) *Version { return &Version{v} } @@ -107,7 +107,7 @@ func MustParse(v string) *Version { return FromSemVer(semver.MustParse(v)) } -// Clean returns version without a prefixed v if it exists +// Clean returns version without a prefixed v if it exists. func Clean(ver string) string { if strings.HasPrefix(ver, "v") { return ver[1:] diff --git a/pkg/apis/dummy_test.go b/pkg/apis/dummy_test.go index 181c297f..054ca882 100644 --- a/pkg/apis/dummy_test.go +++ b/pkg/apis/dummy_test.go @@ -2,7 +2,7 @@ package apis import "testing" -// TestDummy is a no-op test to satisfy coverage tools +// TestDummy is a no-op test to satisfy coverage tools. func TestDummy(_ *testing.T) { // This test exists only to ensure the package is recognized by go test // and to avoid "go: no such tool 'covdata'" warnings diff --git a/pkg/apis/testharness/v1beta1/collector.go b/pkg/apis/testharness/v1beta1/collector.go index 69d8954a..87c6d047 100644 --- a/pkg/apis/testharness/v1beta1/collector.go +++ b/pkg/apis/testharness/v1beta1/collector.go @@ -13,7 +13,7 @@ const ( ) // validate checks user input and updates type if not provided. -// It is expected to be called prior to any other call +// It is expected to be called prior to any other call. func (tc *TestCollector) validate() error { cleanType(tc) switch tc.Type { @@ -55,7 +55,7 @@ func validateCmd(tc *TestCollector) error { return nil } -// determines and cleans collector type +// determines and cleans collector type. func cleanType(tc *TestCollector) { // intuit pod or command or invalid if tc.Type == "" { @@ -69,7 +69,7 @@ func cleanType(tc *TestCollector) { tc.Type = strings.ToLower(tc.Type) } -// Command provides the command to exec to perform the collection +// Command provides the command to exec to perform the collection. func (tc *TestCollector) Command() *Command { err := tc.validate() if err != nil { @@ -139,7 +139,7 @@ func podCommand(tc *TestCollector) *Command { } } -// String provides defaults of the type of collector +// String provides defaults of the type of collector. func (tc *TestCollector) String() string { err := tc.validate() if err != nil { diff --git a/pkg/apis/testharness/v1beta1/test_types.go b/pkg/apis/testharness/v1beta1/test_types.go index 96a9f300..7dad1710 100644 --- a/pkg/apis/testharness/v1beta1/test_types.go +++ b/pkg/apis/testharness/v1beta1/test_types.go @@ -109,7 +109,7 @@ type TestSuite struct { // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// TestStep settings to apply to a test step.go +// TestStep settings to apply to a test step.go. type TestStep struct { // The type meta object, should always be a GVK of kuttl.dev/v1beta1/TestStep or kuttl.dev/v1beta1/TestStep. metav1.TypeMeta `json:",inline"` @@ -172,7 +172,7 @@ type TestAssert struct { AssertAll []*Assertion `json:"assertAll,omitempty"` } -// TestAssertCommand an assertion based on the result of the execution of a command +// TestAssertCommand an assertion based on the result of the execution of a command. type TestAssertCommand struct { // The command and argument to run as a string. Command string `json:"command"` From ca30cdcf73fe74922652041efd37316a0c76cb3c Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Wed, 29 Apr 2026 13:54:41 +0200 Subject: [PATCH 4/5] chore: enable intrange linter and convert for loops to integer ranges Replace traditional C-style for loops with Go 1.22+ integer range syntax. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Marcin Owsiany --- .golangci.yml | 3 +-- internal/assert/assert.go | 4 ++-- internal/kubernetes/retry_client_integration_test.go | 2 +- internal/utils/subset.go | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 9000cebd..25464247 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -45,6 +45,7 @@ linters: - importas - ineffassign - interfacebloat + - intrange - iotamixing - lll - loggercheck @@ -109,8 +110,6 @@ linters: - godox # 11 issues: flags potential security issues (hardcoded credentials, weak crypto, etc.) - gosec - # 4 issues: requires integer range loops (Go 1.22+) - - intrange # 28 issues: forbids returning interfaces - ireturn # 2 issues: flags functions with low maintainability index diff --git a/internal/assert/assert.go b/internal/assert/assert.go index 46704896..277296c3 100644 --- a/internal/assert/assert.go +++ b/internal/assert/assert.go @@ -31,7 +31,7 @@ func Assert(namespace string, timeout int, assertFiles ...string) error { s := setupStep() var testErrors []error - for i := 0; i < timeout; i++ { + for range timeout { // start fresh testErrors = []error{} for _, expected := range objects { @@ -71,7 +71,7 @@ func Errors(namespace string, timeout int, errorFiles ...string) error { s := setupStep() var testErrors []error - for i := 0; i < timeout; i++ { + for range timeout { // start fresh testErrors = []error{} for _, expected := range objects { diff --git a/internal/kubernetes/retry_client_integration_test.go b/internal/kubernetes/retry_client_integration_test.go index 0e069ef1..c9b131e8 100644 --- a/internal/kubernetes/retry_client_integration_test.go +++ b/internal/kubernetes/retry_client_integration_test.go @@ -36,7 +36,7 @@ func TestMain(m *testing.M) { func TestCreateOrUpdate(t *testing.T) { // Run the test a bunch of times to try to trigger a conflict and ensure that it handles conflicts properly. - for i := 0; i < 10; i++ { + for i := range 10 { namespaceName := fmt.Sprintf("default-%d", i) namespaceObj := NewResource("v1", "Namespace", namespaceName, "default") diff --git a/internal/utils/subset.go b/internal/utils/subset.go index 90b76fad..476684f1 100644 --- a/internal/utils/subset.go +++ b/internal/utils/subset.go @@ -55,7 +55,7 @@ func IsSubset(expected, actual interface{}) error { } } - for i := 0; i < reflect.ValueOf(expected).Len(); i++ { + for i := range reflect.ValueOf(expected).Len() { if err := IsSubset(reflect.ValueOf(expected).Index(i).Interface(), reflect.ValueOf(actual).Index(i).Interface()); err != nil { return err } From 3577d7bbbf7c9d697debed8ef2f03a9f2fb9fee6 Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Thu, 7 May 2026 08:28:48 +0200 Subject: [PATCH 5/5] review comments Signed-off-by: Marcin Owsiany --- internal/harness/harness.go | 2 +- pkg/apis/testharness/v1beta1/test_types.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/harness/harness.go b/internal/harness/harness.go index 053bf080..61439168 100644 --- a/internal/harness/harness.go +++ b/internal/harness/harness.go @@ -644,7 +644,7 @@ func (h *Harness) Report() { } } -// NewSuiteReport creates and assigns a TestSuite to the TestSuites (then returns the suite),. +// NewSuiteReport creates and assigns a TestSuite to the TestSuites (then returns the suite). func (h *Harness) NewSuiteReport(name string) *report.Testsuite { suite := report.NewSuite(name, h.TestSuite.ReportGranularity) h.report.AddTestSuite(suite) diff --git a/pkg/apis/testharness/v1beta1/test_types.go b/pkg/apis/testharness/v1beta1/test_types.go index 7dad1710..cd092394 100644 --- a/pkg/apis/testharness/v1beta1/test_types.go +++ b/pkg/apis/testharness/v1beta1/test_types.go @@ -109,7 +109,7 @@ type TestSuite struct { // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// TestStep settings to apply to a test step.go. +// TestStep contains settings to apply to a test step. type TestStep struct { // The type meta object, should always be a GVK of kuttl.dev/v1beta1/TestStep or kuttl.dev/v1beta1/TestStep. metav1.TypeMeta `json:",inline"`