diff --git a/api/v1beta1/module_types.go b/api/v1beta1/module_types.go index 683653082..d9265dd44 100644 --- a/api/v1beta1/module_types.go +++ b/api/v1beta1/module_types.go @@ -46,6 +46,7 @@ type KanikoParams struct { type Build struct { // +optional + // +nullable // BuildArgs is an array of build variables that are provided to the image building backend. BuildArgs []BuildArg `json:"buildArgs"` @@ -125,6 +126,7 @@ type KernelMapping struct { InTreeModuleToRemove string `json:"inTreeModuleToRemove"` // +optional + // +nullable // InTreeModulesToRemove specifies any number of in-tree kernel modules that should be removed (if present) // before loading the kernel module from the ContainerImage InTreeModulesToRemove []string `json:"inTreeModulesToRemove"` @@ -315,6 +317,7 @@ type ModuleSpec struct { // DevicePlugin allows overriding some properties of the container that deploys the device plugin on the node. // Name is ignored and is set automatically by the KMM Operator. // +optional + // +nullable DevicePlugin *DevicePluginSpec `json:"devicePlugin"` // ModuleLoader allows overriding some properties of the container that loads the kernel module on the node. diff --git a/cmd/webhook-server/main.go b/cmd/webhook-server/main.go index e7f9b995c..cb7ba2930 100644 --- a/cmd/webhook-server/main.go +++ b/cmd/webhook-server/main.go @@ -82,6 +82,10 @@ func main() { if err = webhook.NewModuleValidator(logger).SetupWebhookWithManager(mgr); err != nil { cmd.FatalError(setupLogger, err, "unable to create webhook", "webhook", "ModuleValidator") } + + if err = webhook.NewModuleDefaulter(logger).SetupWebhookWithManager(mgr); err != nil { + cmd.FatalError(setupLogger, err, "unable to create mutating webhook", "webhook", "ModuleDefaulter") + } } if enableManagedClusterModule { @@ -90,6 +94,10 @@ func main() { if err = hub.NewManagedClusterModuleValidator(logger).SetupWebhookWithManager(mgr); err != nil { cmd.FatalError(setupLogger, err, "unable to create webhook", "webhook", "ManagedClusterModuleValidator") } + + if err = hub.NewManagedClusterModuleDefaulter(logger).SetupWebhookWithManager(mgr); err != nil { + cmd.FatalError(setupLogger, err, "unable to create mutating webhook", "webhook", "ManagedClusterModuleDefaulter") + } } if enableNamespaceDeletion { diff --git a/config/crd-hub/bases/hub.kmm.sigs.x-k8s.io_managedclustermodules.yaml b/config/crd-hub/bases/hub.kmm.sigs.x-k8s.io_managedclustermodules.yaml index 15ca77fcc..5ae2fa2b6 100644 --- a/config/crd-hub/bases/hub.kmm.sigs.x-k8s.io_managedclustermodules.yaml +++ b/config/crd-hub/bases/hub.kmm.sigs.x-k8s.io_managedclustermodules.yaml @@ -48,6 +48,7 @@ spec: description: |- DevicePlugin allows overriding some properties of the container that deploys the device plugin on the node. Name is ignored and is set automatically by the KMM Operator. + nullable: true properties: container: properties: @@ -2188,6 +2189,7 @@ spec: - name - value type: object + nullable: true type: array dockerfileConfigMap: description: ConfigMap that holds Dockerfile contents @@ -2318,6 +2320,7 @@ spec: - name - value type: object + nullable: true type: array dockerfileConfigMap: description: ConfigMap that holds Dockerfile @@ -2394,6 +2397,7 @@ spec: before loading the kernel module from the ContainerImage items: type: string + nullable: true type: array literal: description: Literal defines a literal target kernel diff --git a/config/crd/bases/kmm.sigs.x-k8s.io_modules.yaml b/config/crd/bases/kmm.sigs.x-k8s.io_modules.yaml index 8efe72b8f..511d58c57 100644 --- a/config/crd/bases/kmm.sigs.x-k8s.io_modules.yaml +++ b/config/crd/bases/kmm.sigs.x-k8s.io_modules.yaml @@ -44,6 +44,7 @@ spec: description: |- DevicePlugin allows overriding some properties of the container that deploys the device plugin on the node. Name is ignored and is set automatically by the KMM Operator. + nullable: true properties: container: properties: @@ -2171,6 +2172,7 @@ spec: - name - value type: object + nullable: true type: array dockerfileConfigMap: description: ConfigMap that holds Dockerfile contents @@ -2299,6 +2301,7 @@ spec: - name - value type: object + nullable: true type: array dockerfileConfigMap: description: ConfigMap that holds Dockerfile contents @@ -2374,6 +2377,7 @@ spec: before loading the kernel module from the ContainerImage items: type: string + nullable: true type: array literal: description: Literal defines a literal target kernel diff --git a/config/default-hub/kustomization.yaml b/config/default-hub/kustomization.yaml index 253b64bec..825fd2f9c 100644 --- a/config/default-hub/kustomization.yaml +++ b/config/default-hub/kustomization.yaml @@ -38,6 +38,13 @@ replacements: options: create: true delimiter: / + - select: + kind: MutatingWebhookConfiguration + fieldPaths: + - metadata.annotations.[cert-manager.io/inject-ca-from] + options: + create: true + delimiter: / - source: kind: Certificate fieldPath: metadata.name @@ -58,6 +65,14 @@ replacements: create: true delimiter: / index: 1 + - select: + kind: MutatingWebhookConfiguration + fieldPaths: + - metadata.annotations.[cert-manager.io/inject-ca-from] + options: + create: true + delimiter: / + index: 1 # Replacements below adjust the DNS names of the webhook certificate based on the service name. - source: kind: Service diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index c6eaf1bc2..80177fa77 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -39,6 +39,13 @@ replacements: options: create: true delimiter: / + - select: + kind: MutatingWebhookConfiguration + fieldPaths: + - metadata.annotations.[cert-manager.io/inject-ca-from] + options: + create: true + delimiter: / - source: kind: Certificate fieldPath: metadata.name @@ -60,6 +67,14 @@ replacements: create: true delimiter: / index: 1 + - select: + kind: MutatingWebhookConfiguration + fieldPaths: + - metadata.annotations.[cert-manager.io/inject-ca-from] + options: + create: true + delimiter: / + index: 1 # Replacements below adjust the DNS names of the webhook certificate based on the service name. - source: kind: Service diff --git a/config/webhook-hub/manifests.yaml b/config/webhook-hub/manifests.yaml index 570e7f37a..9c5c39b74 100644 --- a/config/webhook-hub/manifests.yaml +++ b/config/webhook-hub/manifests.yaml @@ -1,5 +1,31 @@ --- apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: mutating-webhook-configuration +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-hub-kmm-sigs-x-k8s-io-v1beta1-managedclustermodule + failurePolicy: Fail + name: vmanageclustermodule.kb.io + rules: + - apiGroups: + - hub.kmm.sigs.x-k8s.io + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - managedclustermodules + sideEffects: None +--- +apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: name: validating-webhook-configuration diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index cda309ed5..20c58f482 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -1,5 +1,31 @@ --- apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: mutating-webhook-configuration +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-kmm-sigs-x-k8s-io-v1beta1-module + failurePolicy: Fail + name: vmodule.kb.io + rules: + - apiGroups: + - kmm.sigs.x-k8s.io + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - modules + sideEffects: None +--- +apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: name: validating-webhook-configuration diff --git a/internal/registry/registry.go b/internal/registry/registry.go index 338185402..07b8ff398 100644 --- a/internal/registry/registry.go +++ b/internal/registry/registry.go @@ -113,10 +113,6 @@ func (r *registry) getPullOptions(ctx context.Context, image string, tlsOptions repo = tag[0] } - if repo == "" { - return nil, fmt.Errorf("image url %s is not valid, does not contain hash or tag", image) - } - options := []crane.Option{ crane.WithContext(ctx), } diff --git a/internal/registry/registry_test.go b/internal/registry/registry_test.go index db7f55e34..5ac03f507 100644 --- a/internal/registry/registry_test.go +++ b/internal/registry/registry_test.go @@ -57,14 +57,6 @@ var _ = Describe("ImageExists", func() { Expect(err.Error()).To(ContainSubstring("failed to get pull options for image")) }) - It("should fail if the image name isn't valid", func() { - - _, err = reg.ImageExists(ctx, invalidImage, &kmmv1beta1.TLSOptions{}, nil) - - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("does not contain hash or tag")) - }) - It("should fail if it cannot get key chain from secret", func() { mockRegistryAuthGetter.EXPECT().GetKeyChain(ctx).Return(nil, errors.New("some error")) @@ -222,14 +214,6 @@ var _ = Describe("GetLayersDigests", func() { Expect(err.Error()).To(ContainSubstring("failed to get pull options for image")) }) - It("should fail if the image name isn't valid", func() { - - _, err = reg.ImageExists(ctx, invalidImage, &kmmv1beta1.TLSOptions{}, nil) - - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("does not contain hash or tag")) - }) - It("should fail if it cannot get key chain from secret", func() { mockRegistryAuthGetter.EXPECT().GetKeyChain(ctx).Return(nil, errors.New("some error")) @@ -393,13 +377,6 @@ var _ = Describe("GetDigest", func() { Expect(err.Error()).To(ContainSubstring("failed to get pull options for image")) }) - It("should fail if the image name isn't valid", func() { - _, err = reg.GetDigest(ctx, invalidImage, &kmmv1beta1.TLSOptions{}, nil) - - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("does not contain hash or tag")) - }) - It("should fail if it cannot get key chain from secret", func() { mockRegistryAuthGetter.EXPECT().GetKeyChain(ctx).Return(nil, errors.New("some error")) diff --git a/internal/webhook/hub/managedclustermodule_webhook.go b/internal/webhook/hub/managedclustermodule.go similarity index 100% rename from internal/webhook/hub/managedclustermodule_webhook.go rename to internal/webhook/hub/managedclustermodule.go diff --git a/internal/webhook/hub/managedclustermodule_mutator.go b/internal/webhook/hub/managedclustermodule_mutator.go new file mode 100644 index 000000000..936745dd8 --- /dev/null +++ b/internal/webhook/hub/managedclustermodule_mutator.go @@ -0,0 +1,68 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package hub + +import ( + "context" + "fmt" + + "github.com/go-logr/logr" + "github.com/kubernetes-sigs/kernel-module-management/api-hub/v1beta1" + "github.com/kubernetes-sigs/kernel-module-management/internal/webhook" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" +) + +// FIXME: remove the code that returned an error until now +type ManagedClusterModuleDefaulter struct { + logger logr.Logger + moduleDefaulter *webhook.ModuleDefaulter +} + +func NewManagedClusterModuleDefaulter(logger logr.Logger) *ManagedClusterModuleDefaulter { + return &ManagedClusterModuleDefaulter{ + logger: logger, + moduleDefaulter: webhook.NewModuleDefaulter(logger), + } +} + +func (mcmd *ManagedClusterModuleDefaulter) SetupWebhookWithManager(mgr ctrl.Manager) error { + // controller-runtime will set the path to `mutate--- so we + // need to make sure it is set correctly in the +kubebuilder annotation below. + return ctrl.NewWebhookManagedBy(mgr). + For(&v1beta1.ManagedClusterModule{}). + WithDefaulter(mcmd). + Complete() +} + +//+kubebuilder:webhook:path=/mutate-hub-kmm-sigs-x-k8s-io-v1beta1-managedclustermodule,mutating=true,failurePolicy=fail,sideEffects=None,groups=hub.kmm.sigs.x-k8s.io,resources=managedclustermodules,verbs=create;update,versions=v1beta1,name=vmanageclustermodule.kb.io,admissionReviewVersions=v1 + +// Default implements webhook.Default so a webhook will be registered for the type +func (mcmd *ManagedClusterModuleDefaulter) Default(ctx context.Context, obj runtime.Object) error { + + mcm, ok := obj.(*v1beta1.ManagedClusterModule) + if !ok { + return fmt.Errorf("bad type for the object; expected %T, got %T", mcm, obj) + } + + mcmd.logger.Info("Mutating ManagedClusterModule creation", "name", mcm.Name, "namespace", mcm.Namespace) + + webhook.SetDefaultContainerImageTagIfNeeded(&mcm.Spec.ModuleSpec) + webhook.SetDefaultKernelMappingTagsIfNeeded(&mcm.Spec.ModuleSpec) + + return nil +} diff --git a/internal/webhook/hub/managedclustermodule_mutator_test.go b/internal/webhook/hub/managedclustermodule_mutator_test.go new file mode 100644 index 000000000..cdefb6e4a --- /dev/null +++ b/internal/webhook/hub/managedclustermodule_mutator_test.go @@ -0,0 +1,109 @@ +package hub + +import ( + "context" + + kmmhub "github.com/kubernetes-sigs/kernel-module-management/api-hub/v1beta1" + kmm "github.com/kubernetes-sigs/kernel-module-management/api/v1beta1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Default", func() { + + var ( + ctx context.Context + mcmd *ManagedClusterModuleDefaulter + ) + + BeforeEach(func() { + + mcmd = NewManagedClusterModuleDefaulter(GinkgoLogr) + }) + + It("should fail if it got the wrong type", func() { + + err := mcmd.Default(ctx, nil) + Expect(err).To(HaveOccurred()) + }) + + It("should default the container image tag to `latest`", func() { + + mcm := kmmhub.ManagedClusterModule{ + Spec: kmmhub.ManagedClusterModuleSpec{ + ModuleSpec: kmm.ModuleSpec{ + ModuleLoader: kmm.ModuleLoaderSpec{ + Container: kmm.ModuleLoaderContainerSpec{ + ContainerImage: "myregistry.com/org/image", + }, + }, + }, + }, + } + + err := mcmd.Default(ctx, &mcm) + Expect(err).NotTo(HaveOccurred()) + Expect(mcm.Spec.ModuleSpec.ModuleLoader.Container.ContainerImage).To(Equal("myregistry.com/org/image:latest")) + }) + + It("should not default the container image tag to `latest` if a sha is speciied", func() { + + mcm := kmmhub.ManagedClusterModule{ + Spec: kmmhub.ManagedClusterModuleSpec{ + ModuleSpec: kmm.ModuleSpec{ + ModuleLoader: kmm.ModuleLoaderSpec{ + Container: kmm.ModuleLoaderContainerSpec{ + ContainerImage: "myregistry.com/org/image@99999", + }, + }, + }, + }, + } + + err := mcmd.Default(ctx, &mcm) + Expect(err).NotTo(HaveOccurred()) + Expect(mcm.Spec.ModuleSpec.ModuleLoader.Container.ContainerImage).To(Equal("myregistry.com/org/image@99999")) + }) + + It("should not default the container image tag to `latest` if a tag is speciied", func() { + + mcm := kmmhub.ManagedClusterModule{ + Spec: kmmhub.ManagedClusterModuleSpec{ + ModuleSpec: kmm.ModuleSpec{ + ModuleLoader: kmm.ModuleLoaderSpec{ + Container: kmm.ModuleLoaderContainerSpec{ + ContainerImage: "myregistry.com/org/image:mytag", + }, + }, + }, + }, + } + + err := mcmd.Default(ctx, &mcm) + Expect(err).NotTo(HaveOccurred()) + Expect(mcm.Spec.ModuleSpec.ModuleLoader.Container.ContainerImage).To(Equal("myregistry.com/org/image:mytag")) + }) + + It("should default the kernel-mappings image tags to `latest`", func() { + + mcm := kmmhub.ManagedClusterModule{ + Spec: kmmhub.ManagedClusterModuleSpec{ + ModuleSpec: kmm.ModuleSpec{ + ModuleLoader: kmm.ModuleLoaderSpec{ + Container: kmm.ModuleLoaderContainerSpec{ + KernelMappings: []kmm.KernelMapping{ + { + ContainerImage: "myregistry.com/org/image", + }, + }, + }, + }, + }, + }, + } + + err := mcmd.Default(ctx, &mcm) + Expect(err).NotTo(HaveOccurred()) + Expect(mcm.Spec.ModuleSpec.ModuleLoader.Container.KernelMappings[0].ContainerImage).To(Equal("myregistry.com/org/image:latest")) + }) +}) diff --git a/internal/webhook/hub/suite_test.go b/internal/webhook/hub/suite_test.go new file mode 100644 index 000000000..fe1ab7538 --- /dev/null +++ b/internal/webhook/hub/suite_test.go @@ -0,0 +1,13 @@ +package hub + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestSuite(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Webhook-Hub Suite") +} diff --git a/internal/webhook/module_mutator.go b/internal/webhook/module_mutator.go new file mode 100644 index 000000000..b74ad31f6 --- /dev/null +++ b/internal/webhook/module_mutator.go @@ -0,0 +1,88 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import ( + "context" + "fmt" + "strings" + + "github.com/go-logr/logr" + kmmv1beta1 "github.com/kubernetes-sigs/kernel-module-management/api/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" +) + +const defaultTag = "latest" + +type ModuleDefaulter struct { + logger logr.Logger +} + +func NewModuleDefaulter(logger logr.Logger) *ModuleDefaulter { + return &ModuleDefaulter{logger: logger} +} + +func (md *ModuleDefaulter) SetupWebhookWithManager(mgr ctrl.Manager) error { + // controller-runtime will set the path to `mutate--- so we + // need to make sure it is set correctly in the +kubebuilder annotation below. + return ctrl.NewWebhookManagedBy(mgr). + For(&kmmv1beta1.Module{}). + WithDefaulter(md). + Complete() +} + +//+kubebuilder:webhook:path=/mutate-kmm-sigs-x-k8s-io-v1beta1-module,mutating=true,failurePolicy=fail,sideEffects=None,groups=kmm.sigs.x-k8s.io,resources=modules,verbs=create;update,versions=v1beta1,name=vmodule.kb.io,admissionReviewVersions=v1 + +// Default implements webhook.Default so a webhook will be registered for the type +func (md *ModuleDefaulter) Default(ctx context.Context, obj runtime.Object) error { + + mod, ok := obj.(*kmmv1beta1.Module) + if !ok { + return fmt.Errorf("bad type for the object; expected %T, got %T", mod, obj) + } + + md.logger.Info("Mutating Module creation", "name", mod.Name, "namespace", mod.Namespace) + + SetDefaultContainerImageTagIfNeeded(&mod.Spec) + SetDefaultKernelMappingTagsIfNeeded(&mod.Spec) + + return nil +} + +func SetDefaultContainerImageTagIfNeeded(modSpec *kmmv1beta1.ModuleSpec) { + + setDefaultTagIfNeeded(&modSpec.ModuleLoader.Container.ContainerImage) +} + +func SetDefaultKernelMappingTagsIfNeeded(modSpec *kmmv1beta1.ModuleSpec) { + + for i := range modSpec.ModuleLoader.Container.KernelMappings { + setDefaultTagIfNeeded(&modSpec.ModuleLoader.Container.KernelMappings[i].ContainerImage) + } +} + +func setDefaultTagIfNeeded(image *string) { + + if *image == "" { + return + } + + if !strings.Contains(*image, ":") && !strings.Contains(*image, "@") { + *image = fmt.Sprintf("%s:%s", *image, defaultTag) + } +} diff --git a/internal/webhook/module_mutator_test.go b/internal/webhook/module_mutator_test.go new file mode 100644 index 000000000..9b4bc5d31 --- /dev/null +++ b/internal/webhook/module_mutator_test.go @@ -0,0 +1,117 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import ( + "context" + + kmmv1beta1 "github.com/kubernetes-sigs/kernel-module-management/api/v1beta1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Default", func() { + + var ( + md *ModuleDefaulter + ctx context.Context + ) + + BeforeEach(func() { + + md = NewModuleDefaulter(GinkgoLogr) + ctx = context.TODO() + }) + + It("should fail if it got the wrong type", func() { + + err := md.Default(ctx, nil) + Expect(err).To(HaveOccurred()) + }) + + It("should default the container image tag to `latest`", func() { + + mod := kmmv1beta1.Module{ + Spec: kmmv1beta1.ModuleSpec{ + ModuleLoader: kmmv1beta1.ModuleLoaderSpec{ + Container: kmmv1beta1.ModuleLoaderContainerSpec{ + ContainerImage: "myregistry.com/org/image", + }, + }, + }, + } + + err := md.Default(ctx, &mod) + Expect(err).NotTo(HaveOccurred()) + Expect(mod.Spec.ModuleLoader.Container.ContainerImage).To(Equal("myregistry.com/org/image:latest")) + }) + + It("should not default the container image tag to `latest` if a sha is speciied", func() { + + mod := kmmv1beta1.Module{ + Spec: kmmv1beta1.ModuleSpec{ + ModuleLoader: kmmv1beta1.ModuleLoaderSpec{ + Container: kmmv1beta1.ModuleLoaderContainerSpec{ + ContainerImage: "myregistry.com/org/image@99999", + }, + }, + }, + } + + err := md.Default(ctx, &mod) + Expect(err).NotTo(HaveOccurred()) + Expect(mod.Spec.ModuleLoader.Container.ContainerImage).To(Equal("myregistry.com/org/image@99999")) + }) + + It("should not default the container image tag to `latest` if a tag is speciied", func() { + + mod := kmmv1beta1.Module{ + Spec: kmmv1beta1.ModuleSpec{ + ModuleLoader: kmmv1beta1.ModuleLoaderSpec{ + Container: kmmv1beta1.ModuleLoaderContainerSpec{ + ContainerImage: "myregistry.com/org/image:mytag", + }, + }, + }, + } + + err := md.Default(ctx, &mod) + Expect(err).NotTo(HaveOccurred()) + Expect(mod.Spec.ModuleLoader.Container.ContainerImage).To(Equal("myregistry.com/org/image:mytag")) + }) + + It("should default the kernel-mappings image tags to `latest`", func() { + + mod := kmmv1beta1.Module{ + Spec: kmmv1beta1.ModuleSpec{ + ModuleLoader: kmmv1beta1.ModuleLoaderSpec{ + Container: kmmv1beta1.ModuleLoaderContainerSpec{ + KernelMappings: []kmmv1beta1.KernelMapping{ + { + ContainerImage: "myregistry.com/org/image", + }, + }, + }, + }, + }, + } + + err := md.Default(ctx, &mod) + Expect(err).NotTo(HaveOccurred()) + Expect(mod.Spec.ModuleLoader.Container.KernelMappings[0].ContainerImage).To(Equal("myregistry.com/org/image:latest")) + }) +})