Repository: falcosecurity/driverkit
Pull request #217 (2022-09-29 13:23:12)

chore: use semver package for gcc versions and kernel versions.
**What type of PR is this?**

/kind cleanup

**Any specific area of the project related to this PR?**

/area cmd

/area pkg

/area tests


**What this PR does / why we need it**:

This PR enforces the use of a semver library to manage:
* gcc versions
* kernel versions

Moreover, it updates and fixes an ubuntu test (as always, the ubuntu header for the old test-case was no more there; just updated the test version)

**Which issue(s) this PR fixes**:

Fixes #

**Special notes for your reviewer**:

**Does this PR introduce a user-facing change?**:

<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below.
If the PR requires additional action from users switching to the new release, prepend the string "action required:".
For example, `action required: change the API interface of the rule engine`.
-->

```release-note
NONE
```


poiana: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/falcosecurity/driverkit/pull/217#" title="Author self-approved">FedeDP</a>*

The full list of commands accepted by this bot can be found [here](https://prow.falco.org/command-help?repo=falcosecurity%2Fdriverkit).

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/falcosecurity/driverkit/blob/master/OWNERS)~~ [FedeDP]

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":[]} -->
FedeDP: NOTE: this is far less elegant than what i hoped :/ 
I am sorry for the `builders.go` part, but we needed a way to map a semver to a proper string (that is, the gcc string actually installed in the builder image); basically, we couldn't use `semver.Version` for builder images spec, because then we would have lost the mapping between 
```
v := semver.Version { Major: 5, Minor: 3 }
v.String() -> "5.3.0"
```
and real gcc name in the system (ie: `gcc-5.3`).
While we could possibly just say "skip 0es" writing an ad hoc function, i think that was not a very future proof way (what if a certain point we would have needed like "5.3.0" for real?).

In the end, i chose to keep builder images mapping using string for the provided gcc versions, and parse them automatically to `semver.Version` in the gcc selection algorithm.
FedeDP: /unhold
dwindsor: > I am sorry for the builders.go part, but we needed a way to map a semver to a proper string (that is, the gcc string actually installed in the builder image); 

Not a huge deal, the switch to semver is worth it imo
poiana: LGTM label has been added.  <details>Git tree hash: d27acd615e4551791931ce544ad9b4818b2551c9</details>
poiana: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/falcosecurity/driverkit/pull/217#pullrequestreview-1136270502" title="Approved">dwindsor</a>*, *<a href="https://github.com/falcosecurity/driverkit/pull/217#" title="Author self-approved">FedeDP</a>*

The full list of commands accepted by this bot can be found [here](https://prow.falco.org/command-help?repo=falcosecurity%2Fdriverkit).

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/falcosecurity/driverkit/blob/master/OWNERS)~~ [FedeDP,dwindsor]

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":[]} -->

Patch:
diff --git a/cmd/root.go b/cmd/root.go
index 11fddda..cea4e16 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -144,7 +144,7 @@ func NewRootCmd() *RootCmd {
 	flags.StringVar(&rootOpts.ModuleDeviceName, "moduledevicename", rootOpts.ModuleDeviceName, "kernel module device name (the default is falco, so the device will be under /dev/falco*)")
 	flags.StringVar(&rootOpts.ModuleDriverName, "moduledrivername", rootOpts.ModuleDriverName, "kernel module driver name, i.e. the name you see when you check installed modules via lsmod")
 	flags.StringVar(&rootOpts.BuilderImage, "builderimage", rootOpts.BuilderImage, "docker image to be used to build the kernel module and eBPF probe. If not provided, an automatically selected image will be used.")
-	flags.Float64Var(&rootOpts.GCCVersion, "gccversion", rootOpts.GCCVersion, "enforce a specific gcc version for the build")
+	flags.StringVar(&rootOpts.GCCVersion, "gccversion", rootOpts.GCCVersion, "enforce a specific gcc version for the build")
 
 	flags.StringSliceVar(&rootOpts.KernelUrls, "kernelurls", nil, "list of kernel header urls (e.g. --kernelurls <URL1> --kernelurls <URL2> --kernelurls \"<URL3>,<URL4>\")")
 
diff --git a/cmd/root_options.go b/cmd/root_options.go
index 714f4e5..4b0873b 100644
--- a/cmd/root_options.go
+++ b/cmd/root_options.go
@@ -30,8 +30,8 @@ type RootOptions struct {
 	KernelRelease    string   `validate:"required,ascii" name:"kernel release"`
 	Target           string   `validate:"required,target" name:"target"`
 	KernelConfigData string   `validate:"omitempty,base64" name:"kernel config data"` // fixme > tag "name" does not seem to work when used at struct level, but works when used at inner level
-	BuilderImage     string   `validate:"imagename" name:"builder image"`
-	GCCVersion       float64  `name:"gcc version"`
+	BuilderImage     string   `validate:"omitempty,imagename" name:"builder image"`
+	GCCVersion       string   `validate:"omitempty,semvertolerant" name:"gcc version"`
 	KernelUrls       []string `name:"kernel header urls"`
 	Repo             RepoOptions
 	Output           OutputOptions
diff --git a/go.mod b/go.mod
index 9a788ed..dbd71e5 100644
--- a/go.mod
+++ b/go.mod
@@ -3,8 +3,8 @@ module github.com/falcosecurity/driverkit
 go 1.16
 
 require (
-	github.com/Masterminds/semver/v3 v3.1.1
 	github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
+	github.com/blang/semver v3.5.1+incompatible
 	github.com/containerd/containerd v1.6.6 // indirect
 	github.com/creasty/defaults v1.6.0
 	github.com/docker/distribution v2.8.1+incompatible // indirect
diff --git a/go.sum b/go.sum
index 475fd6b..7114b57 100644
--- a/go.sum
+++ b/go.sum
@@ -79,8 +79,6 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
 github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
 github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU=
 github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
-github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
-github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
 github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
 github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
 github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
@@ -146,6 +144,7 @@ github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edY
 github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
 github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
 github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
+github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
 github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
 github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
 github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
diff --git a/pkg/driverbuilder/builder/build.go b/pkg/driverbuilder/builder/build.go
index 80ab5d4..c78474c 100644
--- a/pkg/driverbuilder/builder/build.go
+++ b/pkg/driverbuilder/builder/build.go
@@ -19,7 +19,7 @@ type Build struct {
 	ModuleDeviceName   string
 	CustomBuilderImage string
 	KernelUrls         []string
-	GCCVersion         float64
+	GCCVersion         string
 	RepoOrg            string
 	RepoName           string
 }
diff --git a/pkg/driverbuilder/builder/builders.go b/pkg/driverbuilder/builder/builders.go
index 04435a3..20db978 100644
--- a/pkg/driverbuilder/builder/builders.go
+++ b/pkg/driverbuilder/builder/builders.go
@@ -4,9 +4,9 @@ import (
 	"bytes"
 	"errors"
 	"fmt"
+	"github.com/blang/semver"
 	"github.com/falcosecurity/driverkit/pkg/kernelrelease"
 	"log"
-	"math"
 	"net/http"
 	"net/url"
 	"path"
@@ -50,7 +50,7 @@ type commonTemplateData struct {
 	ModuleFullPath    string
 	BuildModule       bool
 	BuildProbe        bool
-	GCCVersion        float64
+	GCCVersion        string
 }
 
 // Builder represents a builder capable of generating a script for a driverkit target.
@@ -116,97 +116,145 @@ func Script(b Builder, c Config, kr kernelrelease.KernelRelease) (string, error)
 }
 
 type GCCVersionRequestor interface {
-	GCCVersion(kr kernelrelease.KernelRelease) float64
+	GCCVersion(kr kernelrelease.KernelRelease) semver.Version
 }
 
 type Image struct {
-	GCCVersion map[kernelrelease.Architecture][]float64
+	GCCVersion map[kernelrelease.Architecture][]string
 }
 
+// We cannot use semver.Version here, because we need to know the
+// actual gcc-$version string that will be used by template scripts,
+// like: make CC=/usr/bin/gcc-{{ .GCCVersion }}
+// When using semver.Version, gcc like semver.Version { Major: 5 }.String() -> "5.0.0"
+// and that would break our scripts, because the real name is just "5"
 var images = map[string]Image{
 	"buster": {
-		GCCVersion: map[kernelrelease.Architecture][]float64{
-			kernelrelease.ArchitectureAmd64: {4.8, 4.9, 5, 6, 8},
-			kernelrelease.ArchitectureArm64: {4.8, 5, 6, 8}, // 4.9 is not present on arm64
+		GCCVersion: map[kernelrelease.Architecture][]string{
+			kernelrelease.ArchitectureAmd64: {"4.8", "4.9", "5", "6", "8"},
+			kernelrelease.ArchitectureArm64: {"4.8", "5", "6", "8"}, // 4.9 is not present on arm64
 		},
 	},
 	"bullseye": {
-		GCCVersion: map[kernelrelease.Architecture][]float64{
-			kernelrelease.ArchitectureAmd64: {9, 10},
-			kernelrelease.ArchitectureArm64: {9, 10},
+		GCCVersion: map[kernelrelease.Architecture][]string{
+			kernelrelease.ArchitectureAmd64: {"9", "10"},
+			kernelrelease.ArchitectureArm64: {"9", "10"},
 		},
 	},
 	"bookworm": {
-		GCCVersion: map[kernelrelease.Architecture][]float64{
-			kernelrelease.ArchitectureAmd64: {11, 12},
-			kernelrelease.ArchitectureArm64: {11, 12},
+		GCCVersion: map[kernelrelease.Architecture][]string{
+			kernelrelease.ArchitectureAmd64: {"11", "12"},
+			kernelrelease.ArchitectureArm64: {"11", "12"},
 		},
 	},
 }
 
-func defaultGCC(kr kernelrelease.KernelRelease) float64 {
-	switch kr.Version {
+func defaultGCC(kr kernelrelease.KernelRelease) semver.Version {
+	switch kr.Major {
 	case 5:
-		if kr.PatchLevel >= 18 {
-			return 12
+		if kr.Minor >= 18 {
+			return semver.Version{Major: 12}
 		}
-		return 11
+		return semver.Version{Major: 11}
 	case 4:
-		return 8
+		return semver.Version{Major: 8}
 	case 3:
-		if kr.PatchLevel >= 18 {
-			return 5
+		if kr.Minor >= 18 {
+			return semver.Version{Major: 5}
 		}
-		return 4.9
+		return semver.Version{Major: 4, Minor: 9}
 	case 2:
-		return 4.8
+		return semver.Version{Major: 4, Minor: 8}
 	default:
-		return 12
+		return semver.Version{Major: 12}
 	}
 }
 
+func mustParseTolerant(gccStr string) semver.Version {
+	g, err := semver.ParseTolerant(gccStr)
+	if err != nil {
+		panic(err)
+	}
+	return g
+}
+
+// Simple algorithm:
+// sort versions passed as first params,
+// then, find the higher gcc version which is also lower than target.
+// Moreover, try harder to keep same major gcc version.
+func findNearestGCCVersion(gccs []semver.Version, target semver.Version) semver.Version {
+	semver.Sort(gccs)
+	foundGCC := gccs[0]
+	for _, gcc := range gccs {
+		if gcc.GT(target) {
+			// Be smarter trying to find the proper gcc version
+			if gcc.Major == target.Major && foundGCC.Major < target.Major {
+				foundGCC = gcc
+			}
+			break
+		}
+		foundGCC = gcc
+	}
+	return foundGCC
+}
+
+// Given an image, returns the list of semvers for its supported gccs
+func buildGCCSemvers(img Image, arch kernelrelease.Architecture) []semver.Version {
+	gccs := make([]semver.Version, 0)
+	for _, gccStr := range img.GCCVersion[arch] {
+		gccs = append(gccs, mustParseTolerant(gccStr))
+	}
+	return gccs
+}
+
 func (b *Build) setGCCVersion(builder Builder, kr kernelrelease.KernelRelease) {
-	if b.GCCVersion != 0 {
+	if len(b.GCCVersion) > 0 {
 		// If set from user, go on
 		return
 	}
 
-	b.GCCVersion = 8 // default value
-
-	distance := math.MaxFloat64
+	b.GCCVersion = "8" // default value
 
 	// if builder implements "GCCVersionRequestor" interface -> use it
 	// Else, fetch the best builder available from the kernelrelease version
 	// using the deadly simple defaultGCC() algorithm
 	// Always returns the nearest one
-	var targetGCC float64
+	var targetGCC semver.Version
 	if bb, ok := builder.(GCCVersionRequestor); ok {
 		targetGCC = bb.GCCVersion(kr)
 	} else {
 		targetGCC = defaultGCC(kr)
 	}
 
+	// Build the list of "proposed" GCC versions,
+	// that is, the nearest-to-target GCC version
+	// for each builder image.
+	proposedGCCs := make([]semver.Version, 0)
 	for name, img := range images {
-		var foundGCC float64
-		d := math.MaxFloat64
-		for _, gcc := range img.GCCVersion[kr.Architecture] {
-			if math.Abs(gcc-targetGCC) < d {
-				// Find the nearest to targetGCC
-				// for this image
-				d = math.Abs(gcc - targetGCC)
-				foundGCC = gcc
-			}
-		}
+		gccs := buildGCCSemvers(img, kr.Architecture)
+		foundGCC := findNearestGCCVersion(gccs, targetGCC)
+		proposedGCCs = append(proposedGCCs, foundGCC)
 		logger.WithField("image", name).
-			WithField("targetGCC", targetGCC).
-			Debug("foundGCC=", foundGCC)
+			WithField("targetGCC", targetGCC.String()).
+			Debug("proposedGCC=", foundGCC.String())
+	}
 
-		if d < distance {
-			b.GCCVersion = foundGCC
-			distance = d
+	// Now, find the nearest-to-target GCC version
+	// from the proposed GCCs from the builder images.
+	foundGCC := findNearestGCCVersion(proposedGCCs, targetGCC)
+
+	// Finally, discover the right gcc version string
+	// for the final GCC that will be used by scripts.
+	for _, img := range images {
+		for _, gccStr := range img.GCCVersion[kr.Architecture] {
+			gccSemVer := mustParseTolerant(gccStr)
+			if gccSemVer.EQ(foundGCC) {
+				b.GCCVersion = gccStr
+			}
 		}
 	}
-	logger.WithField("targetGCC", targetGCC).
+
+	logger.WithField("targetGCC", targetGCC.String()).
 		Debug("foundGCC=", b.GCCVersion)
 }
 
@@ -244,8 +292,8 @@ func (b *Build) GetBuilderImage() string {
 	}
 
 	for name, img := range images {
-		for _, gcc := range img.GCCVersion[kernelrelease.Architecture(b.Architecture)] {
-			if gcc == b.GCCVersion {
+		for _, gccStr := range img.GCCVersion[kernelrelease.Architecture(b.Architecture)] {
+			if gccStr == b.GCCVersion {
 				return names[0] + "_" + name + ":" + imageTag
 			}
 		}
diff --git a/pkg/driverbuilder/builder/debian.go b/pkg/driverbuilder/builder/debian.go
index c85eede..6808886 100644
--- a/pkg/driverbuilder/builder/debian.go
+++ b/pkg/driverbuilder/builder/debian.go
@@ -125,12 +125,12 @@ func fetchDebianHeadersURLFromRelease(baseURL string, kr kernelrelease.KernelRel
 	bodyStr := string(body)
 
 	// look for kernel headers
-	fullregex := fmt.Sprintf(rmatch, kr.Version, kr.PatchLevel, kr.Sublevel,
+	fullregex := fmt.Sprintf(rmatch, kr.Major, kr.Minor, kr.Patch,
 		extraVersionPartial, matchExtraGroup, kr.Architecture.String())
 	pattern := regexp.MustCompile(fullregex)
 	matches := pattern.FindStringSubmatch(bodyStr)
 	if len(matches) < 1 {
-		fullregex = fmt.Sprintf(rmatchNew, matchExtraGroup, kr.Version, kr.PatchLevel, kr.Sublevel,
+		fullregex = fmt.Sprintf(rmatchNew, matchExtraGroup, kr.Major, kr.Minor, kr.Patch,
 			extraVersionPartial, kr.Architecture.String())
 		pattern = regexp.MustCompile(fullregex)
 		matches = pattern.FindStringSubmatch(bodyStr)
@@ -140,12 +140,12 @@ func fetchDebianHeadersURLFromRelease(baseURL string, kr kernelrelease.KernelRel
 	}
 
 	// look for kernel headers common
-	fullregexCommon := fmt.Sprintf(rmatch, kr.Version, kr.PatchLevel, kr.Sublevel,
+	fullregexCommon := fmt.Sprintf(rmatch, kr.Major, kr.Minor, kr.Patch,
 		extraVersionPartial, matchExtraGroupCommon, kr.Architecture.String())
 	patternCommon := regexp.MustCompile(fullregexCommon)
 	matchesCommon := patternCommon.FindStringSubmatch(bodyStr)
 	if len(matchesCommon) < 1 {
-		fullregexCommon = fmt.Sprintf(rmatchNew, matchExtraGroupCommon, kr.Version, kr.PatchLevel, kr.Sublevel,
+		fullregexCommon = fmt.Sprintf(rmatchNew, matchExtraGroupCommon, kr.Major, kr.Minor, kr.Patch,
 			extraVersionPartial, kr.Architecture.String())
 		patternCommon = regexp.MustCompile(fullregexCommon)
 		matchesCommon = patternCommon.FindStringSubmatch(bodyStr)
@@ -163,9 +163,9 @@ func fetchDebianHeadersURLFromRelease(baseURL string, kr kernelrelease.KernelRel
 func debianKbuildURLFromRelease(kr kernelrelease.KernelRelease) (string, error) {
 	rmatch := `href="(linux-kbuild-%d\.%d.*%s\.deb)"`
 
-	kbuildPattern := regexp.MustCompile(fmt.Sprintf(rmatch, kr.Version, kr.PatchLevel, kr.Architecture.String()))
+	kbuildPattern := regexp.MustCompile(fmt.Sprintf(rmatch, kr.Major, kr.Minor, kr.Architecture.String()))
 	baseURL := "http://mirrors.kernel.org/debian/pool/main/l/linux/"
-	if kr.Version == 3 {
+	if kr.Major == 3 {
 		baseURL = "http://mirrors.kernel.org/debian/pool/main/l/linux-tools/"
 	}
 
diff --git a/pkg/driverbuilder/builder/flatcar.go b/pkg/driverbuilder/builder/flatcar.go
index b8a22c9..e9eb59d 100644
--- a/pkg/driverbuilder/builder/flatcar.go
+++ b/pkg/driverbuilder/builder/flatcar.go
@@ -3,6 +3,7 @@ package builder
 import (
 	_ "embed"
 	"fmt"
+	"github.com/blang/semver"
 	"github.com/falcosecurity/driverkit/pkg/kernelrelease"
 	"io/ioutil"
 	"net/http"
@@ -59,11 +60,8 @@ func (f *flatcar) TemplateData(c Config, kr kernelrelease.KernelRelease, urls []
 	}
 }
 
-func (f *flatcar) GCCVersion(_ kernelrelease.KernelRelease) float64 {
-	// Parse maj,min,patch
-	gccV := kernelrelease.FromString(f.info.GCCVersion)
-	targetV := float64(gccV.Version) + float64(gccV.PatchLevel)/10
-	return targetV
+func (f *flatcar) GCCVersion(_ kernelrelease.KernelRelease) semver.Version {
+	return f.info.GCCVersion
 }
 
 func (f *flatcar) fillFlatcarInfos(kr kernelrelease.KernelRelease) error {
@@ -72,8 +70,8 @@ func (f *flatcar) fillFlatcarInfos(kr kernelrelease.KernelRelease) error {
 	}
 
 	// convert string to int
-	if kr.Version < 1500 {
-		return fmt.Errorf("not a valid flatcar release version: %d", kr.Version)
+	if kr.Major < 1500 {
+		return fmt.Errorf("not a valid flatcar release version: %d", kr.Major)
 	}
 
 	var err error
@@ -124,7 +122,10 @@ func fetchFlatcarMetadata(kr kernelrelease.KernelRelease) (*flatcarReleaseInfo,
 			kernelVersion = strings.Split(kernelVersion, "-")[0]
 		}
 	}
-	flatcarInfo.GCCVersion = gccVersion
+	flatcarInfo.GCCVersion, err = semver.ParseTolerant(gccVersion)
+	if err != nil {
+		return nil, err
+	}
 	flatcarInfo.KernelVersion = kernelVersion
 
 	return &flatcarInfo, nil
@@ -146,6 +147,6 @@ func fetchFlatcarPackageListURL(architecture kernelrelease.Architecture, flatcar
 
 type flatcarReleaseInfo struct {
 	Channel       string
-	GCCVersion    string
+	GCCVersion    semver.Version
 	KernelVersion string
 }
diff --git a/pkg/driverbuilder/builder/ubuntu.go b/pkg/driverbuilder/builder/ubuntu.go
index bdeadd2..f6f126c 100644
--- a/pkg/driverbuilder/builder/ubuntu.go
+++ b/pkg/driverbuilder/builder/ubuntu.go
@@ -3,6 +3,7 @@ package builder
 import (
 	_ "embed"
 	"fmt"
+	"github.com/blang/semver"
 	"github.com/falcosecurity/driverkit/pkg/kernelrelease"
 	"regexp"
 	"strings"
@@ -69,24 +70,24 @@ func (v *ubuntu) TemplateData(c Config, kr kernelrelease.KernelRelease, urls []s
 	}
 }
 
-func (v *ubuntu) GCCVersion(kr kernelrelease.KernelRelease) float64 {
-	switch kr.Version {
+func (v *ubuntu) GCCVersion(kr kernelrelease.KernelRelease) semver.Version {
+	switch kr.Major {
 	case 3:
 		switch {
-		case kr.PatchLevel == 13 || kr.PatchLevel == 2:
-			return 4.8
+		case kr.Minor == 13 || kr.Minor == 2:
+			return semver.Version{Major: 4, Minor: 8}
 		default:
-			return 6
+			return semver.Version{Major: 6}
 		}
 	case 5:
 		switch {
-		case kr.PatchLevel >= 18:
-			return 12
-		case kr.PatchLevel >= 11:
-			return 11
+		case kr.Minor >= 18:
+			return semver.Version{Major: 12}
+		case kr.Minor >= 11:
+			return semver.Version{Major: 11}
 		}
 	}
-	return 8
+	return semver.Version{Major: 8}
 }
 
 func ubuntuHeadersURLFromRelease(kr kernelrelease.KernelRelease, kv string) ([]string, error) {
@@ -136,7 +137,7 @@ func fetchUbuntuKernelURL(baseURL string, kr kernelrelease.KernelRelease, kernel
 	possibleSubDirs := []string{
 		"linux",                               // default subdir, where generic etc. are stored
 		fmt.Sprintf("linux-%s", ubuntuFlavor), // ex: linux-aws
-		fmt.Sprintf("linux-%s-%d.%d", ubuntuFlavor, kr.Version, kr.PatchLevel), // ex: linux-azure-5.15
+		fmt.Sprintf("linux-%s-%d.%d", ubuntuFlavor, kr.Major, kr.Minor), // ex: linux-azure-5.15
 	}
 
 	// build all possible full URLs with the flavor subdirs
diff --git a/pkg/driverbuilder/builder/vanilla.go b/pkg/driverbuilder/builder/vanilla.go
index 69f5f00..3112fbe 100644
--- a/pkg/driverbuilder/builder/vanilla.go
+++ b/pkg/driverbuilder/builder/vanilla.go
@@ -47,5 +47,5 @@ func (v *vanilla) TemplateData(c Config, kr kernelrelease.KernelRelease, urls []
 }
 
 func fetchVanillaKernelURLFromKernelVersion(kv kernelrelease.KernelRelease) string {
-	return fmt.Sprintf("https://cdn.kernel.org/pub/linux/kernel/v%d.x/linux-%s.tar.xz", kv.Version, kv.Fullversion)
+	return fmt.Sprintf("https://cdn.kernel.org/pub/linux/kernel/v%d.x/linux-%s.tar.xz", kv.Major, kv.Fullversion)
 }
diff --git a/pkg/kernelrelease/kernelrelease.go b/pkg/kernelrelease/kernelrelease.go
index fbf3a9c..472edcc 100644
--- a/pkg/kernelrelease/kernelrelease.go
+++ b/pkg/kernelrelease/kernelrelease.go
@@ -2,6 +2,7 @@ package kernelrelease
 
 import (
 	"fmt"
+	"github.com/blang/semver"
 	"log"
 	"regexp"
 	"strconv"
@@ -65,13 +66,11 @@ func (a Architecture) String() string {
 // Instead, rely on the global option
 // (it it set for builders in kernelReleaseFromBuildConfig())
 type KernelRelease struct {
-	Fullversion      string       `json:"full_version"`
-	Version          int          `json:"version"`
-	PatchLevel       int          `json:"patch_level"`
-	Sublevel         int          `json:"sublevel"`
-	Extraversion     string       `json:"extra_version"`
-	FullExtraversion string       `json:"full_extra_version"`
-	Architecture     Architecture `json:"architecture"`
+	Fullversion string
+	semver.Version
+	Extraversion     string
+	FullExtraversion string
+	Architecture     Architecture
 }
 
 // FromString extracts a KernelRelease object from string.
@@ -87,11 +86,11 @@ func FromString(kernelVersionStr string) KernelRelease {
 			case "fullversion":
 				kv.Fullversion = match[i]
 			case "version":
-				kv.Version, err = strconv.Atoi(match[i])
+				kv.Major, err = strconv.ParseUint(match[i], 10, 64)
 			case "patchlevel":
-				kv.PatchLevel, err = strconv.Atoi(match[i])
+				kv.Minor, err = strconv.ParseUint(match[i], 10, 64)
 			case "sublevel":
-				kv.Sublevel, err = strconv.Atoi(match[i])
+				kv.Patch, err = strconv.ParseUint(match[i], 10, 64)
 			case "extraversion":
 				kv.Extraversion = match[i]
 			case "fullextraversion":
@@ -103,6 +102,5 @@ func FromString(kernelVersionStr string) KernelRelease {
 			}
 		}
 	}
-
 	return kv
 }
diff --git a/validate/isimagename.go b/validate/isimagename.go
index 8d27f3b..e2a45c3 100644
--- a/validate/isimagename.go
+++ b/validate/isimagename.go
@@ -13,11 +13,6 @@ const alphabet = letters + digits + separators
 func isImageName(fl validator.FieldLevel) bool {
 	name := fl.Field().String()
 
-	// default value
-	if name == "" {
-		return true
-	}
-
 	for _, c := range name {
 		if !strings.ContainsRune(alphabet, c) {
 			return false
diff --git a/validate/semver.go b/validate/semver.go
index 3d1d995..73446bf 100644
--- a/validate/semver.go
+++ b/validate/semver.go
@@ -2,20 +2,32 @@ package validate
 
 import (
 	"fmt"
+	"github.com/blang/semver"
 	"reflect"
 
-	"github.com/Masterminds/semver/v3"
 	"github.com/go-playground/validator/v10"
 )
 
-func isSemVer(fl validator.FieldLevel) bool {
-	field := fl.Field()
-
+func checkSemver(field reflect.Value, tolerant bool) bool {
 	switch field.Kind() {
 	case reflect.String:
-		_, err := semver.NewVersion(field.String())
+		var err error
+		if tolerant {
+			// Be tolerant (ie: you can pass eg: "5.2" instead of "5.2.0")
+			_, err = semver.ParseTolerant(field.String())
+		} else {
+			_, err = semver.Parse(field.String())
+		}
 		return err == nil
 	}
 
 	panic(fmt.Sprintf("Bad field type %T", field.Interface()))
 }
+
+func isSemVer(fl validator.FieldLevel) bool {
+	return checkSemver(fl.Field(), false)
+}
+
+func isSemVerTolerant(fl validator.FieldLevel) bool {
+	return checkSemver(fl.Field(), true)
+}
diff --git a/validate/validate.go b/validate/validate.go
index 8cbb6dc..b1f9d38 100644
--- a/validate/validate.go
+++ b/validate/validate.go
@@ -39,6 +39,7 @@ func init() {
 	V.RegisterValidation("target", isTargetSupported)
 	V.RegisterValidation("architecture", isArchitectureSupported)
 	V.RegisterValidation("semver", isSemVer)
+	V.RegisterValidation("semvertolerant", isSemVerTolerant)
 	V.RegisterValidation("proxy", isProxy)
 	V.RegisterValidation("imagename", isImageName)
 


Test patch:
diff --git a/cmd/testdata/templates/flags.txt b/cmd/testdata/templates/flags.txt
index fac5883..415c9cb 100644
--- a/cmd/testdata/templates/flags.txt
+++ b/cmd/testdata/templates/flags.txt
@@ -4,7 +4,7 @@ Flags:
   -c, --config string             config file path (default $HOME/.driverkit.yaml if exists)
       --driverversion string      driver version as a git commit hash or as a git tag (default "master")
       --dryrun                    do not actually perform the action
-      --gccversion float          enforce a specific gcc version for the build
+      --gccversion string         enforce a specific gcc version for the build
   -h, --help                      help for {{ .Cmd }}
       --kernelconfigdata string   base64 encoded kernel config data: in some systems it can be found under the /boot directory, in other it is gzip compressed under /proc
       --kernelrelease string      kernel release to build the module for, it can be found by executing 'uname -v'
diff --git a/pkg/driverbuilder/builder/builders_test.go b/pkg/driverbuilder/builder/builders_test.go
index 8d215e1..c7ec882 100644
--- a/pkg/driverbuilder/builder/builders_test.go
+++ b/pkg/driverbuilder/builder/builders_test.go
@@ -1,61 +1,79 @@
 package builder
 
 import (
+	"github.com/blang/semver"
 	"github.com/falcosecurity/driverkit/pkg/kernelrelease"
 	"testing"
 )
 
 var gccTests = []struct {
 	config      kernelrelease.KernelRelease
-	expectedGCC float64
+	expectedGCC semver.Version
 }{
 	{
 		config: kernelrelease.KernelRelease{
-			Fullversion:      "4.15.0",
-			Version:          4,
-			PatchLevel:       15,
-			Sublevel:         0,
+			Fullversion: "4.15.0",
+			Version: semver.Version{
+				Major: 4,
+				Minor: 15,
+				Patch: 0,
+			},
 			Extraversion:     "188",
 			FullExtraversion: "-188",
 			Architecture:     kernelrelease.ArchitectureAmd64,
 		},
-		expectedGCC: 8,
+		expectedGCC: semver.Version{
+			Major: 8,
+		},
 	},
 	{
 		config: kernelrelease.KernelRelease{
-			Fullversion:      "5.15.0",
-			Version:          5,
-			PatchLevel:       15,
-			Sublevel:         0,
+			Fullversion: "5.15.0",
+			Version: semver.Version{
+				Major: 5,
+				Minor: 15,
+				Patch: 0,
+			},
 			Extraversion:     "1004-intel-iotg",
 			FullExtraversion: "-1004-intel-iotg",
 			Architecture:     kernelrelease.ArchitectureAmd64,
 		},
-		expectedGCC: 11,
+		expectedGCC: semver.Version{
+			Major: 11,
+		},
 	},
 	{
 		config: kernelrelease.KernelRelease{
-			Fullversion:      "3.13.0",
-			Version:          3,
-			PatchLevel:       13,
-			Sublevel:         0,
+			Fullversion: "3.13.0",
+			Version: semver.Version{
+				Major: 3,
+				Minor: 13,
+				Patch: 0,
+			},
 			Extraversion:     "100",
 			FullExtraversion: "-100",
 			Architecture:     kernelrelease.ArchitectureAmd64,
 		},
-		expectedGCC: 4.9,
+		expectedGCC: semver.Version{
+			Major: 4,
+			Minor: 9,
+		},
 	},
 	{
 		config: kernelrelease.KernelRelease{
-			Fullversion:      "5.18.0",
-			Version:          5,
-			PatchLevel:       18,
-			Sublevel:         0,
+			Fullversion: "5.18.0",
+			Version: semver.Version{
+				Major: 5,
+				Minor: 18,
+				Patch: 0,
+			},
 			Extraversion:     "1001-kvm",
 			FullExtraversion: "-1001-kvm",
 			Architecture:     kernelrelease.ArchitectureAmd64,
 		},
-		expectedGCC: 12,
+		expectedGCC: semver.Version{
+			Major: 12,
+		},
 	},
 }
 
@@ -67,8 +85,8 @@ func TestDefaultGCC(t *testing.T) {
 		// compare errors
 		// there are no official errors, so comparing fmt.Errorf() doesn't really work
 		// compare error message text instead
-		if test.expectedGCC != selectedGCC {
-			t.Fatalf("SelectedGCC (%f) != expectedGCC (%f) with kernelrelease: '%v'", selectedGCC, test.expectedGCC, test.config)
+		if test.expectedGCC.NE(selectedGCC) {
+			t.Fatalf("SelectedGCC (%s) != expectedGCC (%s) with kernelrelease: '%v'", selectedGCC, test.expectedGCC, test.config)
 		}
 	}
 }
diff --git a/pkg/driverbuilder/builder/ubuntu_test.go b/pkg/driverbuilder/builder/ubuntu_test.go
index 5b86dbb..0eb5b75 100644
--- a/pkg/driverbuilder/builder/ubuntu_test.go
+++ b/pkg/driverbuilder/builder/ubuntu_test.go
@@ -2,6 +2,7 @@ package builder
 
 import (
 	"fmt"
+	"github.com/blang/semver"
 	"testing"
 
 	"github.com/falcosecurity/driverkit/pkg/kernelrelease"
@@ -13,7 +14,7 @@ var tests = []struct {
 	expected      struct {
 		headersURLs []string
 		urls        []string
-		gccVersion  float64
+		gccVersion  semver.Version
 		firstExtra  string
 		flavor      string
 		err         error
@@ -21,10 +22,12 @@ var tests = []struct {
 }{
 	{
 		config: kernelrelease.KernelRelease{
-			Fullversion:      "4.15.0",
-			Version:          4,
-			PatchLevel:       15,
-			Sublevel:         0,
+			Fullversion: "4.15.0",
+			Version: semver.Version{
+				Major: 4,
+				Minor: 15,
+				Patch: 0,
+			},
 			Extraversion:     "188",
 			FullExtraversion: "-188",
 			Architecture:     kernelrelease.ArchitectureAmd64,
@@ -33,25 +36,29 @@ var tests = []struct {
 		expected: struct {
 			headersURLs []string
 			urls        []string
-			gccVersion  float64
+			gccVersion  semver.Version
 			firstExtra  string
 			flavor      string
 			err         error
 		}{
 			headersURLs: []string{"https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-headers-4.15.0-188-generic_4.15.0-188.199_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-headers-4.15.0-188_4.15.0-188.199_all.deb"},
 			urls:        []string{"https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-headers-4.15.0-188_4.15.0-188.199_amd64_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-headers-4.15.0-188-generic_4.15.0-188.199_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-generic-headers-4.15.0-188_4.15.0-188.199_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-headers-4.15.0-188_4.15.0-188.199_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-headers-4.15.0-188_4.15.0-188.199_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-generic/linux-headers-4.15.0-188_4.15.0-188.199_amd64_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-generic/linux-headers-4.15.0-188-generic_4.15.0-188.199_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-generic/linux-generic-headers-4.15.0-188_4.15.0-188.199_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-generic/linux-headers-4.15.0-188_4.15.0-188.199_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-generic/linux-headers-4.15.0-188_4.15.0-188.199_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-generic-4.15/linux-headers-4.15.0-188_4.15.0-188.199_amd64_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-generic-4.15/linux-headers-4.15.0-188-generic_4.15.0-188.199_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-generic-4.15/linux-generic-headers-4.15.0-188_4.15.0-188.199_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-generic-4.15/linux-headers-4.15.0-188_4.15.0-188.199_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-generic-4.15/linux-headers-4.15.0-188_4.15.0-188.199_all.deb"},
-			gccVersion:  8,
-			firstExtra:  "188",
-			flavor:      "generic",
-			err:         fmt.Errorf("kernel headers not found"),
+			gccVersion: semver.Version{
+				Major: 8,
+			},
+			firstExtra: "188",
+			flavor:     "generic",
+			err:        fmt.Errorf("kernel headers not found"),
 		},
 	},
 	{
 		config: kernelrelease.KernelRelease{
-			Fullversion:      "4.15.0",
-			Version:          4,
-			PatchLevel:       15,
-			Sublevel:         0,
+			Fullversion: "4.15.0",
+			Version: semver.Version{
+				Major: 4,
+				Minor: 15,
+				Patch: 0,
+			},
 			Extraversion:     "1140-aws",
 			FullExtraversion: "-1140-aws",
 			Architecture:     kernelrelease.ArchitectureArm64,
@@ -60,25 +67,29 @@ var tests = []struct {
 		expected: struct {
 			headersURLs []string
 			urls        []string
-			gccVersion  float64
+			gccVersion  semver.Version
 			firstExtra  string
 			flavor      string
 			err         error
 		}{
 			headersURLs: []string{"http://ports.ubuntu.com/ubuntu-ports/pool/main/l/linux-aws/linux-headers-4.15.0-1140-aws_4.15.0-1140.151_arm64.deb", "http://ports.ubuntu.com/ubuntu-ports/pool/main/l/linux-aws/linux-aws-headers-4.15.0-1140_4.15.0-1140.151_all.deb"},
 			urls:        []string{"http://ports.ubuntu.com/ubuntu-ports/pool/main/l/linux/linux-headers-4.15.0-1140-aws_4.15.0-1140.151_arm64_all.deb", "http://ports.ubuntu.com/ubuntu-ports/pool/main/l/linux/linux-headers-4.15.0-1140-aws_4.15.0-1140.151_arm64.deb", "http://ports.ubuntu.com/ubuntu-ports/pool/main/l/linux/linux-aws-headers-4.15.0-1140_4.15.0-1140.151_all.deb", "http://ports.ubuntu.com/ubuntu-ports/pool/main/l/linux-aws/linux-headers-4.15.0-1140-aws_4.15.0-1140.151_arm64_all.deb", "http://ports.ubuntu.com/ubuntu-ports/pool/main/l/linux-aws/linux-headers-4.15.0-1140-aws_4.15.0-1140.151_arm64.deb", "http://ports.ubuntu.com/ubuntu-ports/pool/main/l/linux-aws/linux-aws-headers-4.15.0-1140_4.15.0-1140.151_all.deb", "http://ports.ubuntu.com/ubuntu-ports/pool/main/l/linux-aws-4.15/linux-headers-4.15.0-1140-aws_4.15.0-1140.151_arm64_all.deb", "http://ports.ubuntu.com/ubuntu-ports/pool/main/l/linux-aws-4.15/linux-headers-4.15.0-1140-aws_4.15.0-1140.151_arm64.deb", "http://ports.ubuntu.com/ubuntu-ports/pool/main/l/linux-aws-4.15/linux-aws-headers-4.15.0-1140_4.15.0-1140.151_all.deb"},
-			gccVersion:  8,
-			firstExtra:  "1140",
-			flavor:      "aws",
-			err:         nil,
+			gccVersion: semver.Version{
+				Major: 8,
+			},
+			firstExtra: "1140",
+			flavor:     "aws",
+			err:        nil,
 		},
 	},
 	{
 		config: kernelrelease.KernelRelease{
-			Fullversion:      "5.15.0",
-			Version:          5,
-			PatchLevel:       15,
-			Sublevel:         0,
+			Fullversion: "5.15.0",
+			Version: semver.Version{
+				Major: 5,
+				Minor: 15,
+				Patch: 0,
+			},
 			Extraversion:     "1004-intel-iotg",
 			FullExtraversion: "-1004-intel-iotg",
 			Architecture:     kernelrelease.ArchitectureAmd64,
@@ -87,25 +98,29 @@ var tests = []struct {
 		expected: struct {
 			headersURLs []string
 			urls        []string
-			gccVersion  float64
+			gccVersion  semver.Version
 			firstExtra  string
 			flavor      string
 			err         error
 		}{
 			headersURLs: []string{"https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-intel-iotg/linux-headers-5.15.0-1004-intel-iotg_5.15.0-1004.6_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-intel-iotg/linux-intel-iotg-headers-5.15.0-1004_5.15.0-1004.6_all.deb"},
 			urls:        []string{"https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-headers-5.15.0-1004-intel-iotg_5.15.0-1004.6_amd64_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-headers-5.15.0-1004-intel-iotg_5.15.0-1004.6_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-intel-iotg-headers-5.15.0-1004_5.15.0-1004.6_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-intel-iotg/linux-headers-5.15.0-1004-intel-iotg_5.15.0-1004.6_amd64_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-intel-iotg/linux-headers-5.15.0-1004-intel-iotg_5.15.0-1004.6_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-intel-iotg/linux-intel-iotg-headers-5.15.0-1004_5.15.0-1004.6_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-intel-iotg-5.15/linux-headers-5.15.0-1004-intel-iotg_5.15.0-1004.6_amd64_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-intel-iotg-5.15/linux-headers-5.15.0-1004-intel-iotg_5.15.0-1004.6_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-intel-iotg-5.15/linux-intel-iotg-headers-5.15.0-1004_5.15.0-1004.6_all.deb"},
-			gccVersion:  11,
-			firstExtra:  "1004",
-			flavor:      "intel-iotg",
-			err:         nil,
+			gccVersion: semver.Version{
+				Major: 11,
+			},
+			firstExtra: "1004",
+			flavor:     "intel-iotg",
+			err:        nil,
 		},
 	},
 	{
 		config: kernelrelease.KernelRelease{
-			Fullversion:      "5.15.0",
-			Version:          5,
-			PatchLevel:       15,
-			Sublevel:         0,
+			Fullversion: "5.15.0",
+			Version: semver.Version{
+				Major: 5,
+				Minor: 15,
+				Patch: 0,
+			},
 			Extraversion:     "24-lowlatency-hwe-5.15",
 			FullExtraversion: "-24-lowlatency-hwe-5.15",
 			Architecture:     kernelrelease.ArchitectureAmd64,
@@ -114,25 +129,29 @@ var tests = []struct {
 		expected: struct {
 			headersURLs []string
 			urls        []string
-			gccVersion  float64
+			gccVersion  semver.Version
 			firstExtra  string
 			flavor      string
 			err         error
 		}{
 			headersURLs: []string{},
 			urls:        []string{"https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-headers-5.15.0-24-lowlatency-hwe-5.15_5.15.0-24.24~20.04.3_amd64_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-headers-5.15.0-24-lowlatency-hwe_5.15.0-24.24~20.04.3_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-lowlatency-hwe-headers-5.15.0-24_5.15.0-24.24~20.04.3_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-headers-5.15.0-24-lowlatency-hwe-5.15_5.15.0-24.24~20.04.3_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-lowlatency-hwe/linux-headers-5.15.0-24-lowlatency-hwe-5.15_5.15.0-24.24~20.04.3_amd64_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-lowlatency-hwe/linux-headers-5.15.0-24-lowlatency-hwe_5.15.0-24.24~20.04.3_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-lowlatency-hwe/linux-lowlatency-hwe-headers-5.15.0-24_5.15.0-24.24~20.04.3_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-lowlatency-hwe/linux-headers-5.15.0-24-lowlatency-hwe-5.15_5.15.0-24.24~20.04.3_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-lowlatency-hwe-5.15/linux-headers-5.15.0-24-lowlatency-hwe-5.15_5.15.0-24.24~20.04.3_amd64_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-lowlatency-hwe-5.15/linux-headers-5.15.0-24-lowlatency-hwe_5.15.0-24.24~20.04.3_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-lowlatency-hwe-5.15/linux-lowlatency-hwe-headers-5.15.0-24_5.15.0-24.24~20.04.3_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-lowlatency-hwe-5.15/linux-headers-5.15.0-24-lowlatency-hwe-5.15_5.15.0-24.24~20.04.3_amd64.deb"},
-			gccVersion:  11,
-			firstExtra:  "24",
-			flavor:      "lowlatency-hwe",
-			err:         fmt.Errorf("kernel headers not found"),
+			gccVersion: semver.Version{
+				Major: 11,
+			},
+			firstExtra: "24",
+			flavor:     "lowlatency-hwe",
+			err:        fmt.Errorf("kernel headers not found"),
 		},
 	},
 	{
 		config: kernelrelease.KernelRelease{
-			Fullversion:      "3.13.0",
-			Version:          3,
-			PatchLevel:       13,
-			Sublevel:         0,
+			Fullversion: "3.13.0",
+			Version: semver.Version{
+				Major: 3,
+				Minor: 13,
+				Patch: 0,
+			},
 			Extraversion:     "100",
 			FullExtraversion: "-100",
 			Architecture:     kernelrelease.ArchitectureAmd64,
@@ -141,25 +160,30 @@ var tests = []struct {
 		expected: struct {
 			headersURLs []string
 			urls        []string
-			gccVersion  float64
+			gccVersion  semver.Version
 			firstExtra  string
 			flavor      string
 			err         error
 		}{
 			headersURLs: []string{"https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-headers-3.13.0-100-generic_3.13.0-100.147_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-headers-3.13.0-100_3.13.0-100.147_all.deb"},
 			urls:        []string{"https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-headers-3.13.0-100_3.13.0-100.147_amd64_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-headers-3.13.0-100-generic_3.13.0-100.147_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-generic-headers-3.13.0-100_3.13.0-100.147_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-headers-3.13.0-100_3.13.0-100.147_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-headers-3.13.0-100_3.13.0-100.147_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-generic/linux-headers-3.13.0-100_3.13.0-100.147_amd64_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-generic/linux-headers-3.13.0-100-generic_3.13.0-100.147_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-generic/linux-generic-headers-3.13.0-100_3.13.0-100.147_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-generic/linux-headers-3.13.0-100_3.13.0-100.147_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-generic/linux-headers-3.13.0-100_3.13.0-100.147_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-generic-3.13/linux-headers-3.13.0-100_3.13.0-100.147_amd64_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-generic-3.13/linux-headers-3.13.0-100-generic_3.13.0-100.147_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-generic-3.13/linux-generic-headers-3.13.0-100_3.13.0-100.147_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-generic-3.13/linux-headers-3.13.0-100_3.13.0-100.147_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-generic-3.13/linux-headers-3.13.0-100_3.13.0-100.147_all.deb"},
-			gccVersion:  4.8,
-			firstExtra:  "100",
-			flavor:      "generic",
-			err:         nil,
+			gccVersion: semver.Version{
+				Major: 4,
+				Minor: 8,
+			},
+			firstExtra: "100",
+			flavor:     "generic",
+			err:        nil,
 		},
 	},
 	{
 		config: kernelrelease.KernelRelease{
-			Fullversion:      "3.16.0",
-			Version:          3,
-			PatchLevel:       16,
-			Sublevel:         0,
+			Fullversion: "3.16.0",
+			Version: semver.Version{
+				Major: 3,
+				Minor: 16,
+				Patch: 0,
+			},
 			Extraversion:     "38-lts-utopic",
 			FullExtraversion: "-38-lts-utopic",
 			Architecture:     kernelrelease.ArchitectureAmd64,
@@ -168,44 +192,50 @@ var tests = []struct {
 		expected: struct {
 			headersURLs []string
 			urls        []string
-			gccVersion  float64
+			gccVersion  semver.Version
 			firstExtra  string
 			flavor      string
 			err         error
 		}{
 			headersURLs: []string{},
 			urls:        []string{"https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-headers-3.16.0-38-lts-utopic_3.16.0-38.52~14.04.1_amd64_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-headers-3.16.0-38-lts-utopic_3.16.0-38.52~14.04.1_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-lts-utopic-headers-3.16.0-38_3.16.0-38.52~14.04.1_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-lts-utopic/linux-headers-3.16.0-38-lts-utopic_3.16.0-38.52~14.04.1_amd64_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-lts-utopic/linux-headers-3.16.0-38-lts-utopic_3.16.0-38.52~14.04.1_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-lts-utopic/linux-lts-utopic-headers-3.16.0-38_3.16.0-38.52~14.04.1_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-lts-utopic-3.16/linux-headers-3.16.0-38-lts-utopic_3.16.0-38.52~14.04.1_amd64_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-lts-utopic-3.16/linux-headers-3.16.0-38-lts-utopic_3.16.0-38.52~14.04.1_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-lts-utopic-3.16/linux-lts-utopic-headers-3.16.0-38_3.16.0-38.52~14.04.1_all.deb"},
-			gccVersion:  6,
-			firstExtra:  "38",
-			flavor:      "lts-utopic",
-			err:         nil,
+			gccVersion: semver.Version{
+				Major: 6,
+			},
+			firstExtra: "38",
+			flavor:     "lts-utopic",
+			err:        nil,
 		},
 	},
 	{
 		config: kernelrelease.KernelRelease{
-			Fullversion:      "5.19.0",
-			Version:          5,
-			PatchLevel:       19,
-			Sublevel:         0,
-			Extraversion:     "1004-kvm",
-			FullExtraversion: "-1004-kvm",
+			Fullversion: "5.19.0",
+			Version: semver.Version{
+				Major: 5,
+				Minor: 19,
+				Patch: 0,
+			},
+			Extraversion:     "1006-kvm",
+			FullExtraversion: "-1006-kvm",
 			Architecture:     kernelrelease.ArchitectureAmd64,
 		},
-		kernelversion: "4",
+		kernelversion: "6",
 		expected: struct {
 			headersURLs []string
 			urls        []string
-			gccVersion  float64
+			gccVersion  semver.Version
 			firstExtra  string
 			flavor      string
 			err         error
 		}{
-			headersURLs: []string{"https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-kvm/linux-headers-5.19.0-1004-kvm_5.19.0-1004.4_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-kvm/linux-kvm-headers-5.19.0-1004_5.19.0-1004.4_all.deb"},
-			urls:        []string{"https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-headers-5.19.0-1004-kvm_5.19.0-1004.4_amd64_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-headers-5.19.0-1004-kvm_5.19.0-1004.4_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-kvm-headers-5.19.0-1004_5.19.0-1004.4_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-kvm/linux-headers-5.19.0-1004-kvm_5.19.0-1004.4_amd64_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-kvm/linux-headers-5.19.0-1004-kvm_5.19.0-1004.4_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-kvm/linux-kvm-headers-5.19.0-1004_5.19.0-1004.4_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-kvm-5.19/linux-headers-5.19.0-1004-kvm_5.19.0-1004.4_amd64_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-kvm-5.19/linux-headers-5.19.0-1004-kvm_5.19.0-1004.4_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-kvm-5.19/linux-kvm-headers-5.19.0-1004_5.19.0-1004.4_all.deb"},
-			gccVersion:  12,
-			firstExtra:  "1004",
-			flavor:      "kvm",
-			err:         nil,
+			headersURLs: []string{"https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-kvm/linux-headers-5.19.0-1006-kvm_5.19.0-1006.6_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-kvm/linux-kvm-headers-5.19.0-1006_5.19.0-1006.6_all.deb"},
+			urls:        []string{"https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-headers-5.19.0-1006-kvm_5.19.0-1006.6_amd64_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-headers-5.19.0-1006-kvm_5.19.0-1006.6_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux/linux-kvm-headers-5.19.0-1006_5.19.0-1006.6_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-kvm/linux-headers-5.19.0-1006-kvm_5.19.0-1006.6_amd64_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-kvm/linux-headers-5.19.0-1006-kvm_5.19.0-1006.6_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-kvm/linux-kvm-headers-5.19.0-1006_5.19.0-1006.6_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-kvm-5.19/linux-headers-5.19.0-1006-kvm_5.19.0-1006.6_amd64_all.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-kvm-5.19/linux-headers-5.19.0-1006-kvm_5.19.0-1006.6_amd64.deb", "https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-kvm-5.19/linux-kvm-headers-5.19.0-1006_5.19.0-1006.6_all.deb"},
+			gccVersion: semver.Version{
+				Major: 12,
+			},
+			firstExtra: "1006",
+			flavor:     "kvm",
+			err:        nil,
 		},
 	},
 }
@@ -301,9 +331,9 @@ func TestUbuntuGCCVersionFromKernelRelease(t *testing.T) {
 	for _, test := range tests {
 		input := test.config
 		gotGCCVersion := b.GCCVersion(input)
-		if gotGCCVersion != test.expected.gccVersion {
+		if gotGCCVersion.NE(test.expected.gccVersion) {
 			t.Errorf(
-				"Test Input: [ '%v' ] | Got: [ '%f' ] / Want: [ '%f' ]",
+				"Test Input: [ '%v' ] | Got: [ '%s' ] / Want: [ '%s' ]",
 				input,
 				gotGCCVersion,
 				test.expected.gccVersion,
diff --git a/pkg/kernelrelease/kernelrelease_test.go b/pkg/kernelrelease/kernelrelease_test.go
index a324f5a..86adb81 100644
--- a/pkg/kernelrelease/kernelrelease_test.go
+++ b/pkg/kernelrelease/kernelrelease_test.go
@@ -1,34 +1,12 @@
 package kernelrelease
 
 import (
-	"encoding/json"
+	"github.com/blang/semver"
 	"testing"
 
 	"gotest.tools/assert"
 )
 
-func TestFromKrToJson(t *testing.T) {
-	test := struct {
-		kernelRelease KernelRelease
-		want          string
-	}{
-		kernelRelease: KernelRelease{
-			Fullversion:      "5.16.5",
-			Version:          5,
-			PatchLevel:       16,
-			Sublevel:         5,
-			Extraversion:     "arch1-1",
-			FullExtraversion: "-arch1-1",
-			Architecture:     ArchitectureAmd64,
-		},
-		want: `{"full_version":"5.16.5","version":5,"patch_level":16,"sublevel":5,"extra_version":"arch1-1","full_extra_version":"-arch1-1","architecture":"amd64"}`,
-	}
-	t.Run("version with local version", func(t *testing.T) {
-		got, _ := json.Marshal(test.kernelRelease)
-		assert.Equal(t, test.want, string(got))
-	})
-}
-
 func TestFromString(t *testing.T) {
 	tests := map[string]struct {
 		kernelVersionStr string
@@ -37,10 +15,12 @@ func TestFromString(t *testing.T) {
 		"version with local version": {
 			kernelVersionStr: "5.5.2-arch1-1",
 			want: KernelRelease{
-				Fullversion:      "5.5.2",
-				Version:          5,
-				PatchLevel:       5,
-				Sublevel:         2,
+				Fullversion: "5.5.2",
+				Version: semver.Version{
+					Major: 5,
+					Minor: 5,
+					Patch: 2,
+				},
 				Extraversion:     "arch1-1",
 				FullExtraversion: "-arch1-1",
 			},
@@ -48,10 +28,12 @@ func TestFromString(t *testing.T) {
 		"just kernel version": {
 			kernelVersionStr: "5.5.2",
 			want: KernelRelease{
-				Fullversion:      "5.5.2",
-				Version:          5,
-				PatchLevel:       5,
-				Sublevel:         2,
+				Fullversion: "5.5.2",
+				Version: semver.Version{
+					Major: 5,
+					Minor: 5,
+					Patch: 2,
+				},
 				Extraversion:     "",
 				FullExtraversion: "",
 			},
@@ -59,10 +41,12 @@ func TestFromString(t *testing.T) {
 		"an empty string": {
 			kernelVersionStr: "",
 			want: KernelRelease{
-				Fullversion:      "",
-				Version:          0,
-				PatchLevel:       0,
-				Sublevel:         0,
+				Fullversion: "",
+				Version: semver.Version{
+					Major: 0,
+					Minor: 0,
+					Patch: 0,
+				},
 				Extraversion:     "",
 				FullExtraversion: "",
 			},
@@ -70,10 +54,12 @@ func TestFromString(t *testing.T) {
 		"version with aws local version": {
 			kernelVersionStr: "4.15.0-1057-aws",
 			want: KernelRelease{
-				Fullversion:      "4.15.0",
-				Version:          4,
-				PatchLevel:       15,
-				Sublevel:         0,
+				Fullversion: "4.15.0",
+				Version: semver.Version{
+					Major: 4,
+					Minor: 15,
+					Patch: 0,
+				},
 				Extraversion:     "1057-aws",
 				FullExtraversion: "-1057-aws",
 			},
@@ -81,10 +67,12 @@ func TestFromString(t *testing.T) {
 		"centos version updates": {
 			kernelVersionStr: "3.10.0-957.12.2.el7.aarch64",
 			want: KernelRelease{
-				Fullversion:      "3.10.0",
-				Version:          3,
-				PatchLevel:       10,
-				Sublevel:         0,
+				Fullversion: "3.10.0",
+				Version: semver.Version{
+					Major: 3,
+					Minor: 10,
+					Patch: 0,
+				},
 				Extraversion:     "957",
 				FullExtraversion: "-957.12.2.el7.aarch64",
 			},
@@ -92,10 +80,12 @@ func TestFromString(t *testing.T) {
 		"centos version os": {
 			kernelVersionStr: "2.6.32-754.el6.x86_64",
 			want: KernelRelease{
-				Fullversion:      "2.6.32",
-				Version:          2,
-				PatchLevel:       6,
-				Sublevel:         32,
+				Fullversion: "2.6.32",
+				Version: semver.Version{
+					Major: 2,
+					Minor: 6,
+					Patch: 32,
+				},
 				Extraversion:     "754",
 				FullExtraversion: "-754.el6.x86_64",
 			},
@@ -103,10 +93,12 @@ func TestFromString(t *testing.T) {
 		"debian jessie version": {
 			kernelVersionStr: "3.16.0-10-amd64",
 			want: KernelRelease{
-				Fullversion:      "3.16.0",
-				Version:          3,
-				PatchLevel:       16,
-				Sublevel:         0,
+				Fullversion: "3.16.0",
+				Version: semver.Version{
+					Major: 3,
+					Minor: 16,
+					Patch: 0,
+				},
 				Extraversion:     "10-amd64",
 				FullExtraversion: "-10-amd64",
 			},
@@ -114,10 +106,12 @@ func TestFromString(t *testing.T) {
 		"debian buster version": {
 			kernelVersionStr: "4.19.0-6-amd64",
 			want: KernelRelease{
-				Fullversion:      "4.19.0",
-				Version:          4,
-				PatchLevel:       19,
-				Sublevel:         0,
+				Fullversion: "4.19.0",
+				Version: semver.Version{
+					Major: 4,
+					Minor: 19,
+					Patch: 0,
+				},
 				Extraversion:     "6-amd64",
 				FullExtraversion: "-6-amd64",
 			},
@@ -125,10 +119,12 @@ func TestFromString(t *testing.T) {
 		"amazon linux 2 version": {
 			kernelVersionStr: "4.14.171-136.231.amzn2.x86_64",
 			want: KernelRelease{
-				Fullversion:      "4.14.171",
-				Version:          4,
-				PatchLevel:       14,
-				Sublevel:         171,
+				Fullversion: "4.14.171",
+				Version: semver.Version{
+					Major: 4,
+					Minor: 14,
+					Patch: 171,
+				},
 				Extraversion:     "136",
 				FullExtraversion: "-136.231.amzn2.x86_64",
 			},
@@ -136,10 +132,12 @@ func TestFromString(t *testing.T) {
 		"gke version": {
 			kernelVersionStr: "4.15.0-1044-gke",
 			want: KernelRelease{
-				Fullversion:      "4.15.0",
-				Version:          4,
-				PatchLevel:       15,
-				Sublevel:         0,
+				Fullversion: "4.15.0",
+				Version: semver.Version{
+					Major: 4,
+					Minor: 15,
+					Patch: 0,
+				},
 				Extraversion:     "1044-gke",
 				FullExtraversion: "-1044-gke",
 			},
@@ -147,10 +145,12 @@ func TestFromString(t *testing.T) {
 		"arch version": {
 			kernelVersionStr: "5.19.3.arch1-1",
 			want: KernelRelease{
-				Fullversion:      "5.19.3",
-				Version:          5,
-				PatchLevel:       19,
-				Sublevel:         3,
+				Fullversion: "5.19.3",
+				Version: semver.Version{
+					Major: 5,
+					Minor: 19,
+					Patch: 3,
+				},
 				Extraversion:     "arch1-1",
 				FullExtraversion: ".arch1-1",
 			},
@@ -159,7 +159,7 @@ func TestFromString(t *testing.T) {
 	for name, tt := range tests {
 		t.Run(name, func(t *testing.T) {
 			got := FromString(tt.kernelVersionStr)
-			assert.Equal(t, tt.want, got)
+			assert.DeepEqual(t, tt.want, got)
 		})
 	}
 }