Repository: containers/common
Pull request #2389: Switch to golangci-lint v2, cleanup config, fix new linter warnings
Author: login: kolyshkin
Labels: name: lgtm
name: approved
Created: 2025-03-26T00:40:05Z | Merged: 2025-03-26T18:08:46Z

This can be logically split into 3 parts:

1. Configuration cleanup (first 2 commits)

* .golangci.yml: switch to list of enabled linters
    
    Instead of enabling all linters, and when disabling some of them because
    we don't like them, switch to list of explicitly enabled linters. The
    upside of this is we can easily upgrade golangci-lint (i.e. any new
    linters won't break our CI). The downside is, we need to explicitly
    enable extra linters.
    
    To me, this is better from the maintainability perspective.
    
    NOTE this commit does not change the configuration in any way, in other
    words, the list of linters being run is the same as before. The next
    commit will address this.

    PS this is my second attempt of doing this. The first one was in
    commit 3bb7e636 ("Bump golangci-lint to 1.45.2", PR #997),
    which was reverted in b6cd1365 ("golangci.yml: enable all,
    disable failing linters"). I still think that enable-all is not sane.

* .golangci.yml: remove some linters

    This carefully remove linters that makes no sense but were previuously
    enabled because of enable-all logic.

2. Fix or silence some of the new warnings (all the intermediary commits)

*  pkg/retry: rm unused break
*  pkg: do not capitalize error strings
*  libimage: silence a staticcheck warning
*  pkg/cgroups: apply De Morgan's law
*  libimage: apply De Morgan's law
*  pkg: apply De Morgan's law
*  pkg/timetype: fix linter warning, improve comment
*  libnetwork/netavark: simplify isMacVlan init
*  libnetwork/types: rename RegexError to ErrInvalidName
*  pkg/timezone: simplify switch
*  libnetwork: simplify write
*  pkg/cgroups: simplify write
*  pkg/secrets: use time.Equal

3. Switch to golangci-lint v2.0 (the last commit)

### Questions / discussion

1. Whether "apply De Morgan's law" check should be enabled?

    Personally, I am not convinced it always makes the code more readable. If others agree, I will remove the corresponding commits and instead just disable [staticcheck's QF1001](https://staticcheck.dev/docs/checks/#QF1001);

2. Can `pkg: do not capitalize error strings` result in any regressions or test failures?

    Probably. If that's the case, I think we should introduce more typed errors.
    
    The alternative, as in the previous case, is to disable staticcheck's ST1005.

3. Should the .z version of golangci-lint be specified?

    Pros of having .z: more stability (no random CI breaks caused by unintended upgrades).
    Cons: more upgrade "noisy" PRs.


Diff:
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index 14eb0ce22..28b2413c1 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -11,7 +11,7 @@ on:
 permissions: read-all
 
 env:
-  LINT_VERSION: v1.64.8
+  LINT_VERSION: v2.0
 
 jobs:
   codespell:
@@ -37,13 +37,13 @@ jobs:
         sudo apt-get -qq update
         sudo apt-get -qq install libseccomp-dev
     - name: lint
-      uses: golangci/golangci-lint-action@v6
+      uses: golangci/golangci-lint-action@v7
       with:
         version: "${{ env.LINT_VERSION }}"
         args: --verbose
     # Extra linters, only checking new code from a pull request.
     - name: lint-extra
-      uses: golangci/golangci-lint-action@v6
+      uses: golangci/golangci-lint-action@v7
       with:
         args: --config=.golangci-extra.yml
         version: "${{ env.LINT_VERSION }}"
diff --git a/.golangci-extra.yml b/.golangci-extra.yml
index 849b7fcb4..f77d1811f 100644
--- a/.golangci-extra.yml
+++ b/.golangci-extra.yml
@@ -6,7 +6,10 @@
 # The idea is to impose additional rules for newly added code only
 # (rules we can not realistically satisfy for existing code).
 
+version: "2"
+
 run:
+  timeout: 5m
   build-tags:
     - apparmor
     - seccomp
@@ -14,9 +17,13 @@ run:
     - systemd
     - exclude_graphdriver_btrfs
     - containers_image_openpgp
-  timeout: 5m
 
 linters:
-  disable-all: true
+  default: none
   enable:
     - godot
+    - staticcheck
+  settings:
+    staticcheck:
+      checks:
+        - all
diff --git a/.golangci.yml b/.golangci.yml
index d71b6b310..c5e98a990 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,5 +1,7 @@
----
+version: "2"
+
 run:
+  timeout: 5m
   build-tags:
     - apparmor
     - seccomp
@@ -8,81 +10,80 @@ run:
     - exclude_graphdriver_btrfs
     - containers_image_openpgp
     - cni
-  timeout: 5m
+
+formatters:
+  enable:
+    - gofumpt
+
 linters:
-  enable-all: true
-  disable:
-    # linters explicitly disabled for the below mentioned reasons ...
-    - funlen # too aggressive/wishful size/statement limit
-    - gochecknoinits # too many hard to fix init() funcs across the code
-    - gocognit # too aggressive default
-    - wsl # useful but too opinionated
-    # others to be re-enabled one-by-one ...
-    - goconst
-    - godox
-    - lll
-    - nestif
-    - cyclop
-    - depguard
-    - errchkjson
-    - errname
-    - errorlint
-    - exhaustive
-    - gochecknoglobals
-    - err113
-    - nolintlint
-    - wrapcheck
-    - varnamelen
-    - testpackage
-    - tenv
-    - tagliatelle
-    - stylecheck
-    - paralleltest
-    - nonamedreturns
-    - nlreturn
-    - nakedret
-    - musttag
-    - maintidx
-    - ireturn
-    - exhaustruct
-    - gosec
-    - godot
-    - gocyclo
-    - dogsled
-    - tparallel
-    - thelper
-    - mnd #way to many false positives
-    - nilnil
-    - nilerr
-    - interfacebloat
-    - forcetypeassert
-    - gomoddirectives
-    - testifylint # way to many issues to fix right now, however it is a great linter for better test errors
-    - inamedparam # opinionated style
-linters-settings:
-  errcheck:
-    check-type-assertions: true
-  gocyclo:
-    min-complexity: 35
-  gofmt:
-    rewrite-rules:
-      - pattern: 'interface{}'
-        replacement: 'any'
-  revive:
+  enable:
+    - asasalint
+    - asciicheck
+    - bidichk
+    - bodyclose
+    - canonicalheader
+    - containedctx
+    - contextcheck
+    - copyloopvar
+    - decorder
+    - dupl
+    - dupword
+    - durationcheck
+    - exptostd
+    - fatcontext
+    - forbidigo
+    - ginkgolinter
+    - gocheckcompilerdirectives
+    - gochecksumtype
+    - gocritic
+    - goprintffuncname
+    - gosmopolitan
+    - iface
+    - intrange
+    - makezero
+    - mirror
+    - misspell
+    - nilnesserr
+    - noctx
+    - nosprintfhostport
+    - perfsprint
+    - prealloc
+    - predeclared
+    - reassign
+    - recvcheck
+    - revive
+    - unconvert
+    - unparam
+    - usestdlibvars
+    - usetesting
+    - wastedassign
+    - whitespace
+  settings:
+    errcheck:
+      check-type-assertions: true
+    gocyclo:
+      min-complexity: 35
+    revive:
+      rules:
+        - name: dot-imports
+          disabled: true
+    staticcheck:
+      checks:
+        - all
+        - -ST1003 # https://staticcheck.dev/docs/checks/#ST1003 Poorly chosen identifier.
+        - -QF1008 # https://staticcheck.dev/docs/checks/#QF1008 Omit embedded fields from selector expression.
+  exclusions:
+    generated: strict
+    presets:
+      - comments
+      - common-false-positives
+      - std-error-handling
     rules:
-      - name: dot-imports
-        disabled: true
-
-issues:
-    # Excluding configuration per-path, per-linter, per-text and per-source
-  exclude-rules:
-    # Exclude some linters from running on tests files.
-    - path: _test\.go
-      linters:
-        - dupl
-        - perfsprint
-    # Exclude "should pass the context parameter" for libimage.LookupImage because of backward compatibility.
-    - path: "libimage"
-      text: "LookupImage"
-      linters:
-        - contextcheck
+      - linters:
+          - dupl
+          - perfsprint
+        path: _test\.go
+      - linters:
+          - contextcheck
+        path: libimage
+        text: LookupImage
diff --git a/libimage/image.go b/libimage/image.go
index 91e694c16..8f300e839 100644
--- a/libimage/image.go
+++ b/libimage/image.go
@@ -463,13 +463,13 @@ func (i *Image) removeRecursive(ctx context.Context, rmMap map[string]*RemoveIma
 	skipRemove := false
 	numNames := len(i.Names())
 
-	// NOTE: the `numNames == 1` check is not only a performance
+	// NOTE: the `numNames != 1` check is not only a performance
 	// optimization but also preserves existing Podman/Docker behaviour.
 	// If image "foo" is used by a container and has only this tag/name,
 	// an `rmi foo` will not untag "foo" but instead attempt to remove the
 	// entire image.  If there's a container using "foo", we should get an
 	// error.
-	if !(referencedBy == "" || numNames == 1) {
+	if referencedBy != "" && numNames != 1 {
 		byID := strings.HasPrefix(i.ID(), referencedBy)
 		byDigest := strings.HasPrefix(referencedBy, "sha256:")
 		if !options.Force {
diff --git a/libimage/load.go b/libimage/load.go
index 48f255e8b..2f69fd639 100644
--- a/libimage/load.go
+++ b/libimage/load.go
@@ -115,7 +115,7 @@ func (r *Runtime) Load(ctx context.Context, path string, options *LoadOptions) (
 
 	// Give a decent error message if nothing above worked.
 	// we want the colon here for the multiline error
-	//nolint:revive
+	//nolint:revive,staticcheck
 	loadError := errors.New("payload does not match any of the supported image formats:")
 	for _, err := range loadErrors {
 		loadError = fmt.Errorf("%v\n * %v", loadError, err)
diff --git a/libimage/normalize.go b/libimage/normalize.go
index b00af66a0..1dab82fe2 100644
--- a/libimage/normalize.go
+++ b/libimage/normalize.go
@@ -30,7 +30,7 @@ func NormalizeName(name string) (reference.Named, error) {
 
 	// Enforce "localhost" if needed.
 	registry := reference.Domain(named)
-	if !(strings.ContainsAny(registry, ".:") || registry == "localhost") {
+	if !strings.ContainsAny(registry, ".:") && registry != "localhost" {
 		name = toLocalImageName(ref.String())
 	}
 
diff --git a/libimage/search.go b/libimage/search.go
index 6ab016b3b..2745b52bb 100644
--- a/libimage/search.go
+++ b/libimage/search.go
@@ -217,7 +217,7 @@ func (r *Runtime) searchImageInRegistry(ctx context.Context, term, registry stri
 	paramsArr := []SearchResult{}
 	for i := range limit {
 		// Check whether query matches filters
-		if !(filterMatchesAutomatedFilter(&options.Filter, results[i]) && filterMatchesOfficialFilter(&options.Filter, results[i]) && filterMatchesStarFilter(&options.Filter, results[i])) {
+		if !filterMatchesAutomatedFilter(&options.Filter, results[i]) || !filterMatchesOfficialFilter(&options.Filter, results[i]) || !filterMatchesStarFilter(&options.Filter, results[i]) {
 			continue
 		}
 		official := ""
diff --git a/libnetwork/cni/network.go b/libnetwork/cni/network.go
index 7e001fab0..ab3a05b55 100644
--- a/libnetwork/cni/network.go
+++ b/libnetwork/cni/network.go
@@ -191,7 +191,7 @@ func (n *cniNetwork) loadNetworks() error {
 		}
 
 		if !types.NameRegex.MatchString(conf.Name) {
-			logrus.Warnf("CNI config list %s has invalid name, skipping: %v", file, types.RegexError)
+			logrus.Warnf("CNI config list %s has invalid name, skipping: %v", file, types.ErrInvalidName)
 			continue
 		}
 
diff --git a/libnetwork/internal/util/bridge.go b/libnetwork/internal/util/bridge.go
index 44cd555c1..e69c38829 100644
--- a/libnetwork/internal/util/bridge.go
+++ b/libnetwork/internal/util/bridge.go
@@ -19,7 +19,7 @@ func CreateBridge(n NetUtil, network *types.Network, usedNetworks []*net.IPNet,
 			}
 		}
 		if !types.NameRegex.MatchString(network.NetworkInterface) {
-			return fmt.Errorf("bridge name %s invalid: %w", network.NetworkInterface, types.RegexError)
+			return fmt.Errorf("bridge name %s invalid: %w", network.NetworkInterface, types.ErrInvalidName)
 		}
 	} else {
 		var err error
diff --git a/libnetwork/internal/util/create.go b/libnetwork/internal/util/create.go
index 376a57315..42609d1ba 100644
--- a/libnetwork/internal/util/create.go
+++ b/libnetwork/internal/util/create.go
@@ -23,7 +23,7 @@ func CommonNetworkCreate(n NetUtil, network *types.Network) error {
 	// validate the name when given
 	if network.Name != "" {
 		if !types.NameRegex.MatchString(network.Name) {
-			return fmt.Errorf("network name %s invalid: %w", network.Name, types.RegexError)
+			return fmt.Errorf("network name %s invalid: %w", network.Name, types.ErrInvalidName)
 		}
 		if _, err := n.Network(network.Name); err == nil {
 			return fmt.Errorf("network name %s already used: %w", network.Name, types.ErrNetworkExists)
diff --git a/libnetwork/netavark/config.go b/libnetwork/netavark/config.go
index 4916f725e..6c17bd0a1 100644
--- a/libnetwork/netavark/config.go
+++ b/libnetwork/netavark/config.go
@@ -294,10 +294,7 @@ func createIpvlanOrMacvlan(network *types.Network) error {
 	}
 
 	driver := network.Driver
-	isMacVlan := true
-	if driver == types.IPVLANNetworkDriver {
-		isMacVlan = false
-	}
+	isMacVlan := driver != types.IPVLANNetworkDriver
 
 	// always turn dns off with macvlan, it is not implemented in netavark
 	// and makes little sense to support with macvlan
diff --git a/libnetwork/netavark/network.go b/libnetwork/netavark/network.go
index 985d0db2d..cf8b0d490 100644
--- a/libnetwork/netavark/network.go
+++ b/libnetwork/netavark/network.go
@@ -248,7 +248,7 @@ func (n *netavarkNetwork) loadNetworks() error {
 		}
 
 		if !types.NameRegex.MatchString(network.Name) {
-			logrus.Warnf("Network config %q has invalid name: %q, skipping: %v", path, network.Name, types.RegexError)
+			logrus.Warnf("Network config %q has invalid name: %q, skipping: %v", path, network.Name, types.ErrInvalidName)
 			continue
 		}
 
diff --git a/libnetwork/slirp4netns/slirp4netns.go b/libnetwork/slirp4netns/slirp4netns.go
index 524cabc42..afb54f60d 100644
--- a/libnetwork/slirp4netns/slirp4netns.go
+++ b/libnetwork/slirp4netns/slirp4netns.go
@@ -682,7 +682,7 @@ func openSlirp4netnsPort(apiSocket, proto, hostip string, hostport, guestport ui
 	if err != nil {
 		return fmt.Errorf("cannot marshal JSON for slirp4netns: %w", err)
 	}
-	if _, err := conn.Write([]byte(fmt.Sprintf("%s\n", data))); err != nil {
+	if _, err := fmt.Fprintf(conn, "%s\n", data); err != nil {
 		return fmt.Errorf("cannot write to control socket %s: %w", apiSocket, err)
 	}
 	//nolint:errcheck // This cast should never fail, if it does we get a interface
diff --git a/libnetwork/types/define.go b/libnetwork/types/define.go
index 0bc688103..e5929c3e8 100644
--- a/libnetwork/types/define.go
+++ b/libnetwork/types/define.go
@@ -24,8 +24,10 @@ var (
 	// NameRegex is a regular expression to validate names.
 	// This must NOT be changed.
 	NameRegex = regexp.Delayed("^[a-zA-Z0-9][a-zA-Z0-9_.-]*$")
-	// RegexError is thrown in presence of an invalid name.
-	RegexError = fmt.Errorf("names must match [a-zA-Z0-9][a-zA-Z0-9_.-]*: %w", ErrInvalidArg) // nolint:revive // This lint is new and we do not want to break the API.
+	// ErrInvalidName is thrown in presence of an invalid name.
+	ErrInvalidName = fmt.Errorf("names must match [a-zA-Z0-9][a-zA-Z0-9_.-]*: %w", ErrInvalidArg)
+	// Deprecated: use [ErrInvalidName] instead.
+	RegexError = ErrInvalidName
 
 	// NotHexRegex is a regular expression to check if a string is
 	// a hexadecimal string.
diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go
index a3d333a99..4bcd490db 100644
--- a/pkg/auth/auth.go
+++ b/pkg/auth/auth.go
@@ -173,10 +173,10 @@ func Login(ctx context.Context, systemContext *types.SystemContext, opts *LoginO
 	if opts.StdinPassword {
 		var stdinPasswordStrBuilder strings.Builder
 		if opts.Password != "" {
-			return errors.New("Can't specify both --password-stdin and --password")
+			return errors.New("can't specify both --password-stdin and --password")
 		}
 		if opts.Username == "" {
-			return errors.New("Must provide --username with --password-stdin")
+			return errors.New("must provide --username with --password-stdin")
 		}
 		scanner := bufio.NewScanner(opts.Stdin)
 		for scanner.Scan() {
diff --git a/pkg/cgroups/utils_linux.go b/pkg/cgroups/utils_linux.go
index 848a59d23..505ac1b29 100644
--- a/pkg/cgroups/utils_linux.go
+++ b/pkg/cgroups/utils_linux.go
@@ -221,7 +221,7 @@ func MoveUnderCgroup(cgroup, subtree string, processes []uint32) error {
 		}
 
 		// root cgroup, skip it
-		if parts[2] == "/" && !(unifiedMode && parts[1] == "") {
+		if parts[2] == "/" && (!unifiedMode || parts[1] != "") {
 			continue
 		}
 
@@ -261,7 +261,7 @@ func MoveUnderCgroup(cgroup, subtree string, processes []uint32) error {
 
 		if len(processes) > 0 {
 			for _, pid := range processes {
-				if _, err := f.WriteString(fmt.Sprintf("%d\n", pid)); err != nil {
+				if _, err := fmt.Fprintf(f, "%d\n", pid); err != nil {
 					logrus.Debugf("Cannot move process %d to cgroup %q: %v", pid, newCgroup, err)
 				}
 			}
diff --git a/pkg/configmaps/configmaps.go b/pkg/configmaps/configmaps.go
index 092cb0153..8fc831c8e 100644
--- a/pkg/configmaps/configmaps.go
+++ b/pkg/configmaps/configmaps.go
@@ -132,7 +132,7 @@ func (s *ConfigMapManager) Store(name string, data []byte, driverType string, dr
 		return "", err
 	}
 
-	if !(len(data) > 0 && len(data) < maxConfigMapSize) {
+	if len(data) == 0 || len(data) >= maxConfigMapSize {
 		return "", errDataSize
 	}
 
diff --git a/pkg/parse/parse_unix.go b/pkg/parse/parse_unix.go
index 86563b3eb..3e4e1d140 100644
--- a/pkg/parse/parse_unix.go
+++ b/pkg/parse/parse_unix.go
@@ -17,7 +17,7 @@ func DeviceFromPath(device string) ([]devices.Device, error) {
 		return nil, err
 	}
 	if unshare.IsRootless() && src != dst {
-		return nil, fmt.Errorf("Renaming device %s to %s is not supported in rootless containers", src, dst)
+		return nil, fmt.Errorf("renaming device %s to %s is not supported in rootless containers", src, dst)
 	}
 	srcInfo, err := os.Stat(src)
 	if err != nil {
diff --git a/pkg/retry/retry.go b/pkg/retry/retry.go
index d47e47e10..adc42c3b3 100644
--- a/pkg/retry/retry.go
+++ b/pkg/retry/retry.go
@@ -47,7 +47,7 @@ func IfNecessary(ctx context.Context, operation func() error, options *Options)
 		logrus.Warnf("Failed, retrying in %s ... (%d/%d). Error: %v", delay, attempt+1, options.MaxRetry, err)
 		select {
 		case <-time.After(delay):
-			break
+			// Do nothing.
 		case <-ctx.Done():
 			return err
 		}
diff --git a/pkg/secrets/secrets.go b/pkg/secrets/secrets.go
index 113c6c43b..7d8d33a11 100644
--- a/pkg/secrets/secrets.go
+++ b/pkg/secrets/secrets.go
@@ -166,7 +166,7 @@ func (s *SecretsManager) Store(name string, data []byte, driverType string, opti
 		return "", err
 	}
 
-	if !(len(data) > 0 && len(data) < maxSecretSize) {
+	if len(data) == 0 || len(data) >= maxSecretSize {
 		return "", errDataSize
 	}
 	var secr *Secret
diff --git a/pkg/secrets/secrets_test.go b/pkg/secrets/secrets_test.go
index 7677d7191..550bf5ee8 100644
--- a/pkg/secrets/secrets_test.go
+++ b/pkg/secrets/secrets_test.go
@@ -64,7 +64,7 @@ func TestAddSecretAndLookupData(t *testing.T) {
 
 	s, _, err = manager.LookupSecretData("mysecret")
 	require.NoError(t, err)
-	if s.CreatedAt == s.UpdatedAt {
+	if s.CreatedAt.Equal(s.UpdatedAt) {
 		t.Errorf("error: secret CreatedAt should not equal UpdatedAt after a Replace")
 	}
 
diff --git a/pkg/subscriptions/subscriptions.go b/pkg/subscriptions/subscriptions.go
index 2c78cf1e6..202eb6d3c 100644
--- a/pkg/subscriptions/subscriptions.go
+++ b/pkg/subscriptions/subscriptions.go
@@ -322,7 +322,7 @@ func addSubscriptionsFromMountsFile(filePath, mountLabel, containerRunDir string
 func containerHasEtcSystemFips(subscriptionsDir, mountPoint string) (bool, error) {
 	containerEtc, err := securejoin.SecureJoin(mountPoint, "etc")
 	if err != nil {
-		return false, fmt.Errorf("Container /etc resolution error: %w", err)
+		return false, fmt.Errorf("container /etc resolution error: %w", err)
 	}
 	if fileutils.Lexists(filepath.Join(containerEtc, "system-fips")) != nil {
 		logrus.Debug("/etc/system-fips does not exist in the container, not creating /run/secrets/system-fips")
@@ -331,7 +331,7 @@ func containerHasEtcSystemFips(subscriptionsDir, mountPoint string) (bool, error
 
 	fipsFileTarget, err := securejoin.SecureJoin(mountPoint, "etc/system-fips")
 	if err != nil {
-		return false, fmt.Errorf("Container /etc/system-fips resolution error: %w", err)
+		return false, fmt.Errorf("container /etc/system-fips resolution error: %w", err)
 	}
 	if fipsFileTarget != filepath.Join(mountPoint, subscriptionsDir, "system-fips") {
 		logrus.Warnf("/etc/system-fips exists in the container, but is not a symlink to %[1]v/system-fips; not creating %[1]v/system-fips", subscriptionsDir)
@@ -448,24 +448,24 @@ func addFIPSMounts(mounts *[]rspec.Mount, containerRunDir, mountPoint, mountLabe
 	destPolicyConfig := "/etc/crypto-policies/config"
 	srcPolicyConfigOnHost, err := securejoin.SecureJoin(mountPoint, srcPolicyConfig)
 	if err != nil {
-		return fmt.Errorf("Could not expand %q in container: %w", srcPolicyConfig, err)
+		return fmt.Errorf("could not expand %q in container: %w", srcPolicyConfig, err)
 	}
 
 	if err = fileutils.Exists(srcPolicyConfigOnHost); err != nil {
 		if !errors.Is(err, os.ErrNotExist) {
-			return fmt.Errorf("Could not check whether %q exists in container: %w", srcPolicyConfig, err)
+			return fmt.Errorf("could not check whether %q exists in container: %w", srcPolicyConfig, err)
 		}
 
 		// /usr/share/crypto-policies/default-fips-config does not exist, let's create it ourselves
 		cryptoPoliciesConfigFile := filepath.Join(containerRunDir, "fips-config")
 		if err := os.WriteFile(cryptoPoliciesConfigFile, []byte("FIPS\n"), 0o644); err != nil {
-			return fmt.Errorf("Failed to write fips config file in container for FIPS mode: %w", err)
+			return fmt.Errorf("failed to write fips config file in container for FIPS mode: %w", err)
 		}
 		if err = label.Relabel(cryptoPoliciesConfigFile, mountLabel, false); err != nil {
-			return fmt.Errorf("Failed to apply correct labels on fips config file: %w", err)
+			return fmt.Errorf("failed to apply correct labels on fips config file: %w", err)
 		}
 		if err := os.Chown(cryptoPoliciesConfigFile, uid, gid); err != nil {
-			return fmt.Errorf("Failed to chown fips config file: %w", err)
+			return fmt.Errorf("failed to chown fips config file: %w", err)
 		}
 
 		srcPolicyConfigOnHost = cryptoPoliciesConfigFile
diff --git a/pkg/timetype/timestamp.go b/pkg/timetype/timestamp.go
index 519884c55..ce03c784c 100644
--- a/pkg/timetype/timestamp.go
+++ b/pkg/timetype/timestamp.go
@@ -31,8 +31,9 @@ func GetTimestamp(value string, reference time.Time) (string, error) {
 	}
 
 	var format string
-	// if the string has a Z or a + or three dashes use parse otherwise use parseinlocation
-	parseInLocation := !(strings.ContainsAny(value, "zZ+") || strings.Count(value, "-") == 3)
+	// If the string has a Z, or a +, or three dashes,
+	// then use time.Parse, otherwise use time.ParseInLocation.
+	parseInLocation := !strings.ContainsAny(value, "zZ+") && strings.Count(value, "-") != 3
 
 	switch {
 	case strings.Contains(value, "."):
diff --git a/pkg/timezone/timezone.go b/pkg/timezone/timezone.go
index b82dc309d..e60661457 100644
--- a/pkg/timezone/timezone.go
+++ b/pkg/timezone/timezone.go
@@ -20,10 +20,10 @@ import (
 // It returns the path of the created /etc/localtime file if needed.
 func ConfigureContainerTimeZone(timezone, containerRunDir, mountPoint, etcPath, containerID string) (localTimePath string, err error) {
 	var timezonePath string
-	switch {
-	case timezone == "":
+	switch timezone {
+	case "":
 		return "", nil
-	case timezone == "local":
+	case "local":
 		timezonePath, err = filepath.EvalSymlinks("/etc/localtime")
 		if err != nil {
 			return "", fmt.Errorf("finding local timezone for container %s: %w", containerID, err)


Reviews:
user: login: Luap99
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T12:50:40Z
user: login: kolyshkin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T16:38:25Z
user: login: Luap99
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T16:43:11Z
user: login: Luap99
body: LGTM
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-26T16:43:23Z
user: login: kolyshkin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T21:25:03Z

Review comments:
diff_hunk: @@ -11,7 +11,7 @@ on:
 permissions: read-all
 
 env:
-  LINT_VERSION: v1.64.8
+  LINT_VERSION: v2.0
path: .github/workflows/validate.yml
user: login: Luap99
body: shouldn't we pin x.y.**z** here. Otherwise the linter might can get a ptach update that randomly breaks on a PR, no?
created_at: 2025-03-26T12:50:39Z
author_association: MEMBER
diff_hunk: @@ -11,7 +11,7 @@ on:
 permissions: read-all
 
 env:
-  LINT_VERSION: v1.64.8
+  LINT_VERSION: v2.0
path: .github/workflows/validate.yml
user: login: kolyshkin
body: My thinking was by omitting the patch version we have less noise PRs to upgrade, and from my experience with other repos I don't remember that patch bump results in any new warnings.

Of course YMMV, so let me know which way you like it.
created_at: 2025-03-26T16:38:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,7 +11,7 @@ on:
 permissions: read-all
 
 env:
-  LINT_VERSION: v1.64.8
+  LINT_VERSION: v2.0
path: .github/workflows/validate.yml
user: login: Luap99
body: personally I hate breakages on random PRs, in case of community contributors they might get confused and you then get to tell them to rebase, etc...

That said I agree if you have experience that it is rather stable I am fine to give that a go, my (bad) experiences about linter updates are from the fact that we had enable-all which of course causes new warnings very often but you already fixed that problem here.

created_at: 2025-03-26T16:43:11Z
author_association: MEMBER
diff_hunk: @@ -11,7 +11,7 @@ on:
 permissions: read-all
 
 env:
-  LINT_VERSION: v1.64.8
+  LINT_VERSION: v2.0
path: .github/workflows/validate.yml
user: login: kolyshkin
body: I agree that enable-all is a PITA, and this is actually my second attempt at killing it (🤞🏻 it will stay this time).
created_at: 2025-03-26T21:25:03Z
author_association: CONTRIBUTOR

PR comments:
user: login: openshift-ci[bot]
created_at: 2025-03-26T16:43:29Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/containers/common/pull/2389#" title="Author self-approved">kolyshkin</a>*, *<a href="https://github.com/containers/common/pull/2389#pullrequestreview-2717940815" title="Approved">Luap99</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=containers%2Fcommon).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/containers/common/blob/main/OWNERS)~~ [Luap99,kolyshkin]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: CONTRIBUTOR
user: login: mheon
created_at: 2025-03-26T18:04:42Z
body: /lgtm
author_association: MEMBER