Skip to content

Commit 365ad21

Browse files
committed
Defaulting to the latest tag if no tag was specified.
In the current code, we handle differently build/sign and deploying a pre-built kmod. This is the current behavior: * If a `build`/`sign` section is set in the module, and the image tag isn't specified, then we will return an error. * If there are no `build`/`sign` section, and the image tag isn't specified, then we will default to the `latest` tag. This change is adjusting the behavior of both workflow to always default to the `latest` image tag if it is not specified in the `Module`. This will also add a webhook warning in case we are defaulting to `latest` for visibility. Signed-off-by: Yoni Bettan <[email protected]>
1 parent 3665eea commit 365ad21

File tree

9 files changed

+111
-25
lines changed

9 files changed

+111
-25
lines changed

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ require (
1212
github.com/google/go-cmp v0.6.0
1313
github.com/google/go-containerregistry v0.19.1
1414
github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-20230523181351-c3f8a49229d3
15+
github.com/hashicorp/go-multierror v1.1.1
1516
github.com/mitchellh/hashstructure/v2 v2.0.2
1617
github.com/moby/moby v26.1.3+incompatible
1718
github.com/onsi/ginkgo/v2 v2.19.0
@@ -66,6 +67,7 @@ require (
6667
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect
6768
github.com/google/uuid v1.3.1 // indirect
6869
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect
70+
github.com/hashicorp/errwrap v1.1.0 // indirect
6971
github.com/imdario/mergo v0.3.13 // indirect
7072
github.com/inconshreveable/mousetrap v1.1.0 // indirect
7173
github.com/josharian/intern v1.0.0 // indirect

go.sum

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
9696
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
9797
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
9898
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
99+
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
100+
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
101+
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
102+
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
103+
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
99104
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
100105
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
101106
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=

internal/module/kernelmapper.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"errors"
55
"fmt"
66
"regexp"
7+
"strings"
78

89
kmmv1beta1 "github.com/kubernetes-sigs/kernel-module-management/api/v1beta1"
910
"github.com/kubernetes-sigs/kernel-module-management/internal/api"
@@ -114,6 +115,9 @@ func (kh *kernelMapperHelper) prepareModuleLoaderData(mapping *kmmv1beta1.Kernel
114115
if mapping.ContainerImage == "" {
115116
mld.ContainerImage = mod.Spec.ModuleLoader.Container.ContainerImage
116117
}
118+
if !strings.Contains(mld.ContainerImage, "@") && !strings.Contains(mld.ContainerImage, ":") {
119+
mld.ContainerImage = fmt.Sprintf("%s:latest", mld.ContainerImage)
120+
}
117121

118122
mld.InTreeModulesToRemove = mod.Spec.ModuleLoader.Container.InTreeModulesToRemove
119123
if mapping.InTreeModulesToRemove != nil {

internal/module/kernelmapper_test.go

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package module
33
import (
44
"errors"
55
"fmt"
6+
"strings"
67

78
kmmv1beta1 "github.com/kubernetes-sigs/kernel-module-management/api/v1beta1"
89
"github.com/kubernetes-sigs/kernel-module-management/internal/api"
@@ -170,7 +171,7 @@ var _ = Describe("prepareModuleLoaderData", func() {
170171
signHelper = sign.NewMockHelper(ctrl)
171172
kh = newKernelMapperHelper(buildHelper, signHelper)
172173
mod = kmmv1beta1.Module{}
173-
mod.Spec.ModuleLoader.Container.ContainerImage = "spec container image"
174+
mod.Spec.ModuleLoader.Container.ContainerImage = "registry.io/org/image:spec"
174175
mod.Spec.ModuleLoader.Container.ImagePullPolicy = "Always"
175176
mapping = kmmv1beta1.KernelMapping{}
176177
})
@@ -180,7 +181,7 @@ var _ = Describe("prepareModuleLoaderData", func() {
180181
})
181182

182183
DescribeTable("prepare mapping", func(buildExistsInMapping, buildExistsInModuleSpec, signExistsInMapping, SignExistsInModuleSpec,
183-
registryTLSExistsInMapping, containerImageExistsInMapping, inTreeModulesToRemoveExistsInMapping bool) {
184+
registryTLSExistsInMapping, containerImageExistsInMapping, inTreeModulesToRemoveExistsInMapping, containerImageNotTagged bool) {
184185
build := &kmmv1beta1.Build{
185186
DockerfileConfigMap: &v1.LocalObjectReference{
186187
Name: "some name",
@@ -224,9 +225,14 @@ var _ = Describe("prepareModuleLoaderData", func() {
224225
}
225226
mld.ContainerImage = mod.Spec.ModuleLoader.Container.ContainerImage
226227
if containerImageExistsInMapping {
227-
mapping.ContainerImage = "mapping container image"
228+
mapping.ContainerImage = "registry.io/org/image:mapping"
228229
mld.ContainerImage = mapping.ContainerImage
229230
}
231+
if containerImageNotTagged {
232+
mod.Spec.ModuleLoader.Container.ContainerImage = strings.Split(mod.Spec.ModuleLoader.Container.ContainerImage, ":")[0]
233+
mapping.ContainerImage = strings.Split(mapping.ContainerImage, ":")[0]
234+
mld.ContainerImage = "registry.io/org/image:latest"
235+
}
230236

231237
if buildExistsInMapping || buildExistsInModuleSpec {
232238
mld.Build = build
@@ -245,13 +251,14 @@ var _ = Describe("prepareModuleLoaderData", func() {
245251
Expect(err).NotTo(HaveOccurred())
246252
Expect(*res).To(Equal(mld))
247253
},
248-
Entry("build in mapping only", true, false, false, false, false, false, false),
249-
Entry("build in spec only", false, true, false, false, false, false, false),
250-
Entry("sign in mapping only", false, false, true, false, false, false, false),
251-
Entry("sign in spec only", false, false, false, true, false, false, false),
252-
Entry("registryTLS in mapping", false, false, false, false, true, false, false),
253-
Entry("containerImage in mapping", false, false, false, false, false, true, false),
254-
Entry("inTreeModulesToRemove in mapping", false, false, false, false, false, false, true),
254+
Entry("build in mapping only", true, false, false, false, false, false, false, false),
255+
Entry("build in spec only", false, true, false, false, false, false, false, false),
256+
Entry("sign in mapping only", false, false, true, false, false, false, false, false),
257+
Entry("sign in spec only", false, false, false, true, false, false, false, false),
258+
Entry("registryTLS in mapping", false, false, false, false, true, false, false, false),
259+
Entry("containerImage in mapping", false, false, false, false, false, true, false, false),
260+
Entry("containerImage with no tag or hash set", false, false, false, false, false, true, false, true),
261+
Entry("inTreeModulesToRemove in mapping", false, false, false, false, false, false, true, false),
255262
)
256263

257264
// [TODO] remove this unit test once InTreeModuleToRemove depricated field is removed from CRD

internal/registry/registry.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ import (
2323
)
2424

2525
const (
26-
modulesLocationPath = "lib/modules"
26+
modulesLocationPath = "lib/modules"
27+
latestTagNotSetErrMsg = "`latest` tag should have been set to the image if not specified by the user"
2728
)
2829

2930
type DriverToolkitEntry struct {
@@ -106,15 +107,14 @@ func (r *registry) GetDigest(ctx context.Context, image string, tlsOptions *kmmv
106107
}
107108

108109
func (r *registry) getPullOptions(ctx context.Context, image string, tlsOptions *kmmv1beta1.TLSOptions, registryAuthGetter auth.RegistryAuthGetter) (*RepoPullConfig, error) {
110+
109111
var repo string
110112
if hash := strings.Split(image, "@"); len(hash) > 1 {
111113
repo = hash[0]
112114
} else if tag := strings.Split(image, ":"); len(tag) > 1 {
113115
repo = tag[0]
114-
}
115-
116-
if repo == "" {
117-
return nil, fmt.Errorf("image url %s is not valid, does not contain hash or tag", image)
116+
} else {
117+
return nil, errors.New(latestTagNotSetErrMsg)
118118
}
119119

120120
options := []crane.Option{

internal/registry/registry_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ var _ = Describe("ImageExists", func() {
6262
_, err = reg.ImageExists(ctx, invalidImage, &kmmv1beta1.TLSOptions{}, nil)
6363

6464
Expect(err).To(HaveOccurred())
65-
Expect(err.Error()).To(ContainSubstring("does not contain hash or tag"))
65+
Expect(err.Error()).To(ContainSubstring(latestTagNotSetErrMsg))
6666
})
6767

6868
It("should fail if it cannot get key chain from secret", func() {
@@ -227,7 +227,7 @@ var _ = Describe("GetLayersDigests", func() {
227227
_, err = reg.ImageExists(ctx, invalidImage, &kmmv1beta1.TLSOptions{}, nil)
228228

229229
Expect(err).To(HaveOccurred())
230-
Expect(err.Error()).To(ContainSubstring("does not contain hash or tag"))
230+
Expect(err.Error()).To(ContainSubstring(latestTagNotSetErrMsg))
231231
})
232232

233233
It("should fail if it cannot get key chain from secret", func() {
@@ -397,7 +397,7 @@ var _ = Describe("GetDigest", func() {
397397
_, err = reg.GetDigest(ctx, invalidImage, &kmmv1beta1.TLSOptions{}, nil)
398398

399399
Expect(err).To(HaveOccurred())
400-
Expect(err.Error()).To(ContainSubstring("does not contain hash or tag"))
400+
Expect(err.Error()).To(ContainSubstring(latestTagNotSetErrMsg))
401401
})
402402

403403
It("should fail if it cannot get key chain from secret", func() {

internal/webhook/errors.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ package webhook
33
import "errors"
44

55
var NotImplemented = errors.New("not implemented")
6+
var ImageHashOrTagNotSet = errors.New("the container image does not contain hash or tag")

internal/webhook/module.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ import (
2121
"errors"
2222
"fmt"
2323
"regexp"
24+
"strings"
2425

2526
"github.com/go-logr/logr"
27+
multierror "github.com/hashicorp/go-multierror"
2628
kmmv1beta1 "github.com/kubernetes-sigs/kernel-module-management/api/v1beta1"
2729
"k8s.io/apimachinery/pkg/runtime"
2830
"k8s.io/apimachinery/pkg/util/sets"
@@ -104,18 +106,24 @@ func validateModule(mod *kmmv1beta1.Module) (admission.Warnings, error) {
104106
)
105107
}
106108

109+
var warnings admission.Warnings
107110
if err := validateModuleLoaderContainerSpec(mod.Spec.ModuleLoader.Container); err != nil {
108-
return nil, fmt.Errorf("failed to validate kernel mappings: %v", err)
111+
if !errors.Is(err, ImageHashOrTagNotSet) {
112+
return nil, fmt.Errorf("failed to validate kernel mappings: %v", err)
113+
}
114+
warn := fmt.Sprintf("%s - defaulting to `latest` tag", err.Error())
115+
warnings = append(warnings, warn)
109116
}
110117

111-
return nil, validateModprobe(mod.Spec.ModuleLoader.Container.Modprobe)
118+
return warnings, validateModprobe(mod.Spec.ModuleLoader.Container.Modprobe)
112119
}
113120

114121
func validateModuleLoaderContainerSpec(container kmmv1beta1.ModuleLoaderContainerSpec) error {
115122
if container.InTreeModulesToRemove != nil && container.InTreeModuleToRemove != "" { //nolint:staticcheck
116123
return fmt.Errorf("only one of the Container's fields: InTreeModulesToRemove or InTreeModuleToRemove can be defined")
117124
}
118125

126+
var imagesErrors error
119127
for idx, km := range container.KernelMappings {
120128
if km.Regexp != "" && km.Literal != "" {
121129
return fmt.Errorf("regexp and literal are mutually exclusive properties at kernelMappings[%d]", idx)
@@ -141,6 +149,19 @@ func validateModuleLoaderContainerSpec(container kmmv1beta1.ModuleLoaderContaine
141149
(km.InTreeModuleToRemove != "" && container.InTreeModulesToRemove != nil) { //nolint:staticcheck
142150
return fmt.Errorf("only one type if field (InTreeModuleToRemove or InTreeModulesToRemove) can be defined in KenrelMapping or Container")
143151
}
152+
153+
var imgToCheck string
154+
imgToCheck = container.ContainerImage
155+
if km.ContainerImage != "" {
156+
imgToCheck = km.ContainerImage
157+
}
158+
if !strings.ContainsRune(imgToCheck, '@') && !strings.ContainsRune(imgToCheck, ':') {
159+
imagesErrors = multierror.Append(imagesErrors, fmt.Errorf("image %s is malformed", imgToCheck))
160+
}
161+
}
162+
163+
if imagesErrors != nil {
164+
return multierror.Append(imagesErrors, ImageHashOrTagNotSet)
144165
}
145166

146167
return nil

internal/webhook/module_test.go

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ var _ = Describe("maxCombinedLength", func() {
7676
})
7777

7878
var _ = Describe("validateModuleLoaderContainerSpec", func() {
79+
80+
const containerImage = "registry.io/org/image:tag"
7981
It("should pass when there are not kernel mappings", func() {
8082
Expect(
8183
validateModuleLoaderContainerSpec(kmmv1beta1.ModuleLoaderContainerSpec{}),
@@ -87,15 +89,15 @@ var _ = Describe("validateModuleLoaderContainerSpec", func() {
8789
It("should pass when regexp,literal and containerImage are valid", func() {
8890
containerSpec1 := kmmv1beta1.ModuleLoaderContainerSpec{
8991
KernelMappings: []kmmv1beta1.KernelMapping{
90-
{Regexp: "valid-regexp", ContainerImage: "image-url"},
91-
{Regexp: "^.*$", ContainerImage: "image-url"},
92+
{Regexp: "valid-regexp", ContainerImage: containerImage},
93+
{Regexp: "^.*$", ContainerImage: containerImage},
9294
},
9395
}
9496

9597
Expect(validateModuleLoaderContainerSpec(containerSpec1)).ToNot(HaveOccurred())
9698

9799
containerSpec2 := kmmv1beta1.ModuleLoaderContainerSpec{
98-
ContainerImage: "image-url",
100+
ContainerImage: containerImage,
99101
KernelMappings: []kmmv1beta1.KernelMapping{
100102
{Regexp: "valid-regexp"},
101103
},
@@ -104,7 +106,7 @@ var _ = Describe("validateModuleLoaderContainerSpec", func() {
104106
Expect(validateModuleLoaderContainerSpec(containerSpec2)).ToNot(HaveOccurred())
105107

106108
containerSpec3 := kmmv1beta1.ModuleLoaderContainerSpec{
107-
ContainerImage: "image-url",
109+
ContainerImage: containerImage,
108110
KernelMappings: []kmmv1beta1.KernelMapping{
109111
{Literal: "any-value"},
110112
},
@@ -176,6 +178,25 @@ var _ = Describe("validateModuleLoaderContainerSpec", func() {
176178
)
177179
})
178180

181+
It("should fail when a kernel-mapping has a container image with no sha or tag", func() {
182+
containerSpec := kmmv1beta1.ModuleLoaderContainerSpec{
183+
KernelMappings: []kmmv1beta1.KernelMapping{
184+
{
185+
Regexp: "valid-regexp",
186+
ContainerImage: "registry.io/org/image-with-no-tag",
187+
},
188+
},
189+
}
190+
191+
Expect(
192+
validateModuleLoaderContainerSpec(containerSpec),
193+
).To(
194+
MatchError(
195+
ContainSubstring(ImageHashOrTagNotSet.Error()),
196+
),
197+
)
198+
})
199+
179200
DescribeTable("should fail when InTreeModulesToRemove and InTreeModuleToRemove both defined",
180201
func(inTreeModulesInContainer, inTreeModuleInContainer, inTreeModulesInKM, inTreeModuleInKM bool) {
181202
containerSpec := kmmv1beta1.ModuleLoaderContainerSpec{}
@@ -349,7 +370,7 @@ var _ = Describe("validateModule", func() {
349370
chars21 := strings.Repeat("a", 21)
350371

351372
DescribeTable(
352-
"should work as expected",
373+
"module naming",
353374
func(name, ns string, errExpected bool) {
354375
mod := validModule
355376
mod.Name = name
@@ -367,6 +388,31 @@ var _ = Describe("validateModule", func() {
367388
Entry("not too long", "name", "ns", false),
368389
Entry("too long", chars21, chars21, true),
369390
)
391+
392+
DescribeTable(
393+
"container image with/without tag or has",
394+
func(containerImg string, expectedWarnings bool) {
395+
mod := validModule
396+
mod.Spec.ModuleLoader.Container.ContainerImage = containerImg
397+
mod.Spec.ModuleLoader.Container.KernelMappings = []kmmv1beta1.KernelMapping{
398+
{
399+
Regexp: "valid-regexp",
400+
ContainerImage: containerImg,
401+
},
402+
}
403+
404+
warnings, err := validateModule(&mod)
405+
Expect(err).NotTo(HaveOccurred())
406+
if expectedWarnings {
407+
Expect(warnings).NotTo(BeEmpty())
408+
} else {
409+
Expect(warnings).To(BeEmpty())
410+
}
411+
},
412+
Entry("image has tag", "registry.io/org/image:some-tag", false),
413+
Entry("image has hash", "registry.io/org/image@99999", false),
414+
Entry("image has no tag nor hash", "registry.io/org/image", true),
415+
)
370416
})
371417

372418
var _ = Describe("ValidateCreate", func() {

0 commit comments

Comments
 (0)