Repository: spegel-org/spegel (go)

Issue:
Spegel don't start in version >=0.0.25
### Spegel version

`>=0.0.25`

### Kubernetes distribution

kubeadm

### Kubernetes version

1.30.8

### CNI

flannel

### Describe the bug

Hi, I've had Spegel running for few months without any issues on version 0.0.23. Today I decided to upgrade it, I upgraded Helm Chart to 0.0.24 and everything was fine. So then I tried update to 0.0.25 (or any hgher) and pods are crashing with very weird message I wasn't even able to find in Spegel's source code. No version since 0.0.25 works up until latest.

# Update method:

```
$ helm upgrade --create-namespace -n spegel -i --version v0.0.25 spegel oci://ghcr.io/spegel-org/helm-charts/spegel -f spegel/spegel.values.yaml
```

just in case, this is what I have in my valuess:
```yaml
resources:
  requests:
    cpu: 10m
    memory: 24Mi
  limits:
    cpu: 1
    memory: 64Mi
```

And pod logs:

```
$ k -n spegel logs -f pod/spegel-wj7d8
Defaulted container "registry" out of: registry, configuration (init)
{"time":"2025-02-07T22:32:06.08283206Z","level":"ERROR","source":{"function":"main.main","file":"/build/main.go","line":89},"msg":"run exit with error","err":"Invalid Semantic Version"}
```

My Helm version is:
```
version.BuildInfo{Version:"v3.16.4", GitCommit:"7877b45b63f95635153b29a42c0c2f4273ec45ca", GitTreeState:"dirty", GoVersion:"go1.23.4"}
```

Pull request description:
Fix verification of Containerd configuration with suffixes
This changes the how we parse Containerd versions when attempting to check Containerd configuration. Some Linux distributions will add  a suffix to the version of Containerd as part of the release process when the dependency is built. So in Debian for example the version would be `1.6.20~ds1 ` instead of `1.6.20`. Before this change we would error because the reported version is not Semver compliant. Kubernetes already deals with this in their method of parsing Semver, most likely for the same issues that we are facing. So switching to their dependency will make sure that at least we are able to parse the same versions that Kubernetes can.

Fixes #729 

Patch:
diff --git a/.golangci.yaml b/.golangci.yaml
index 15eaf1e..aa16b93 100644
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -66,6 +66,8 @@ linters:
           alias: digest
         - pkg: github.com/opencontainers/image-spec/specs-go/v1
           alias: ocispec
+        - pkg: k8s.io/apimachinery/pkg/util/version
+          alias: utilversion
       no-extra-aliases: true
     nolintlint:
       require-explanation: true
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7a96c35..5171f4b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 - [#807](https://github.com/spegel-org/spegel/pull/807) Update golangci lint and fix new issues.
 - [#810](https://github.com/spegel-org/spegel/pull/810) Increase timeout to avoid flakiness in conformance tests.
+- [#806](https://github.com/spegel-org/spegel/pull/806) Fix verification of Containerd configuration with suffixes.
 
 ### Security
 
diff --git a/go.mod b/go.mod
index 5a926ac..5b96083 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,6 @@ go 1.23.6
 toolchain go1.24.1
 
 require (
-	github.com/Masterminds/semver/v3 v3.3.1
 	github.com/alexflint/go-arg v1.5.1
 	github.com/containerd/containerd/api v1.8.0
 	github.com/containerd/containerd/v2 v2.0.4
@@ -29,6 +28,7 @@ require (
 	go.etcd.io/bbolt v1.4.0
 	golang.org/x/sync v0.12.0
 	google.golang.org/grpc v1.71.0
+	k8s.io/apimachinery v0.32.3
 	k8s.io/cri-api v0.32.3
 	k8s.io/klog/v2 v2.130.1
 )
@@ -37,6 +37,7 @@ require (
 	github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect
 	github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2 // indirect
 	github.com/Masterminds/goutils v1.1.1 // indirect
+	github.com/Masterminds/semver/v3 v3.3.1 // indirect
 	github.com/Masterminds/sprig/v3 v3.2.3 // indirect
 	github.com/Microsoft/go-winio v0.6.2 // indirect
 	github.com/Microsoft/hcsshim v0.12.9 // indirect
diff --git a/go.sum b/go.sum
index d364a4c..0ffb250 100644
--- a/go.sum
+++ b/go.sum
@@ -793,6 +793,8 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
+k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
 k8s.io/cri-api v0.32.3 h1:E8VXbXNn4yAgmuKTeNzg0C1MFSxzTdlHSwUvjuYlPTY=
 k8s.io/cri-api v0.32.3/go.mod h1:DCzMuTh2padoinefWME0G678Mc3QFbLMF2vEweGzBAI=
 k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
diff --git a/pkg/oci/containerd.go b/pkg/oci/containerd.go
index 26f66e0..83dc024 100644
--- a/pkg/oci/containerd.go
+++ b/pkg/oci/containerd.go
@@ -15,7 +15,6 @@ import (
 	"strings"
 	"text/template"
 
-	"github.com/Masterminds/semver/v3"
 	eventtypes "github.com/containerd/containerd/api/events"
 	"github.com/containerd/containerd/v2/client"
 	"github.com/containerd/containerd/v2/core/content"
@@ -28,6 +27,7 @@ import (
 	tomlu "github.com/pelletier/go-toml/v2/unstable"
 	"github.com/spf13/afero"
 	"google.golang.org/grpc"
+	utilversion "k8s.io/apimachinery/pkg/util/version"
 	runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
 )
 
@@ -105,16 +105,12 @@ func (c *Containerd) Verify(ctx context.Context) error {
 	if err != nil {
 		return err
 	}
-	version, err := semver.NewVersion(versionResp.GetRuntimeVersion())
+	ok, err = canVerifyContainerdConfiguration(versionResp.RuntimeVersion)
 	if err != nil {
-		return err
+		return fmt.Errorf("could not check Containerd version %s: %w", versionResp.RuntimeVersion, err)
 	}
-	constraint, err := semver.NewConstraint(">1-0")
-	if err != nil {
-		return err
-	}
-	if constraint.Check(version) {
-		log.Info("unable to verify status response", "runtime_version", version.String())
+	if !ok {
+		log.Info("skipping verification of Containerd configuration", "version", versionResp.RuntimeVersion)
 		return nil
 	}
 
@@ -129,6 +125,14 @@ func (c *Containerd) Verify(ctx context.Context) error {
 	return nil
 }
 
+func canVerifyContainerdConfiguration(version string) (bool, error) {
+	v, err := utilversion.Parse(version)
+	if err != nil {
+		return false, err
+	}
+	return v.LessThan(utilversion.MustParse("2.0")), nil
+}
+
 func verifyStatusResponse(resp *runtimeapi.StatusResponse, configPath string) error {
 	str, ok := resp.Info["config"]
 	if !ok {


Test patch:
diff --git a/pkg/oci/containerd_test.go b/pkg/oci/containerd_test.go
index f94d2b8..591f397 100644
--- a/pkg/oci/containerd_test.go
+++ b/pkg/oci/containerd_test.go
@@ -31,6 +31,47 @@ func TestNewContainerd(t *testing.T) {
 	require.Equal(t, "local", c.contentPath)
 }
 
+func TestCanVerifyContainerdConfiguration(t *testing.T) {
+	t.Parallel()
+
+	tests := []struct {
+		version  string
+		expected bool
+	}{
+		{
+			version:  "v2.0.2",
+			expected: false,
+		},
+		{
+			version:  "2.1.4",
+			expected: false,
+		},
+		{
+			version:  "v1.7.27",
+			expected: true,
+		},
+		{
+			version:  "1.6.0",
+			expected: true,
+		},
+	}
+	for _, tt := range tests {
+		// Testing with a suffix is important as some Linux distributions will modify the version
+		// with a non Semver compliant modification. Even if the version is supposed to comply with
+		// semver that may not always be the case.
+		for _, suffix := range []string{"", "~ds1"} {
+			version := tt.version + suffix
+			t.Run(version, func(t *testing.T) {
+				t.Parallel()
+
+				ok, err := canVerifyContainerdConfiguration(tt.version)
+				require.NoError(t, err)
+				require.Equal(t, tt.expected, ok)
+			})
+		}
+	}
+}
+
 func TestVerifyStatusResponse(t *testing.T) {
 	t.Parallel()