/* Copyright © 2022 - 2025 SUSE LLC 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 controllers import ( "encoding/json" "fmt" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" managementv3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" elementalv1 "github.com/rancher/elemental-operator/api/v1beta1" "github.com/rancher/elemental-operator/pkg/test" ) var _ = Describe("reconcile seed image", func() { var r *SeedImageReconciler var mRegistration *elementalv1.MachineRegistration var seedImg *elementalv1.SeedImage var setting *managementv3.Setting var pod *corev1.Pod var service *corev1.Service BeforeEach(func() { r = &SeedImageReconciler{ Client: cl, SeedImageImage: "registry.suse.com/rancher/seedimage-builder:latest", SeedImageImagePullPolicy: corev1.PullIfNotPresent, } mRegistration = &elementalv1.MachineRegistration{ ObjectMeta: metav1.ObjectMeta{ Name: "test-name", Namespace: "default", }, Status: elementalv1.MachineRegistrationStatus{ RegistrationURL: "https://example.com/token", RegistrationToken: "token", Conditions: []metav1.Condition{ { Type: elementalv1.ReadyCondition, Reason: elementalv1.SuccessfullyCreatedReason, Status: metav1.ConditionTrue, LastTransitionTime: metav1.Now(), }, }, }, Spec: elementalv1.MachineRegistrationSpec{ Config: elementalv1.Config{ Elemental: elementalv1.Elemental{ Install: elementalv1.Install{}, }, }, }, } seedImg = &elementalv1.SeedImage{ ObjectMeta: metav1.ObjectMeta{ Name: "test-name", Namespace: "default", }, Spec: elementalv1.SeedImageSpec{ BaseImage: "missing-image", MachineRegistrationRef: &corev1.ObjectReference{ Name: mRegistration.Name, Namespace: mRegistration.Namespace, }, Type: elementalv1.TypeIso, }, } setting = &managementv3.Setting{ ObjectMeta: metav1.ObjectMeta{ Name: "server-url", }, Value: "https://example.com", } pod = &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Namespace: seedImg.Namespace, Name: seedImg.Name, }, } service = &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Namespace: seedImg.Namespace, Name: seedImg.Name, }, } statusCopy := mRegistration.Status.DeepCopy() Expect(cl.Create(ctx, mRegistration)).To(Succeed()) Expect(cl.Create(ctx, seedImg)).To(Succeed()) Expect(cl.Create(ctx, setting)).To(Succeed()) patchBase := client.MergeFrom(mRegistration.DeepCopy()) mRegistration.Status = *statusCopy Expect(cl.Status().Patch(ctx, mRegistration, patchBase)).To(Succeed()) }) AfterEach(func() { Expect(test.CleanupAndWait(ctx, cl, mRegistration, seedImg, setting, pod, service)).To(Succeed()) }) It("should reconcile seed image object", func() { _, err := r.Reconcile(ctx, reconcile.Request{ NamespacedName: types.NamespacedName{ Namespace: seedImg.Namespace, Name: seedImg.Name, }, }) Expect(err).ToNot(HaveOccurred()) Expect(cl.Get(ctx, client.ObjectKey{ Name: seedImg.Name, Namespace: seedImg.Namespace, }, seedImg)).To(Succeed()) Expect(seedImg.Status.Conditions).To(HaveLen(2)) Expect(seedImg.Status.Conditions[0].Type).To(Equal(elementalv1.ReadyCondition)) Expect(seedImg.Status.Conditions[0].Reason).To(Equal(elementalv1.ResourcesSuccessfullyCreatedReason)) Expect(seedImg.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) Expect(seedImg.Status.Conditions[1].Type).To(Equal(elementalv1.SeedImageConditionReady)) Expect(seedImg.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) Expect(seedImg.OwnerReferences[0].UID).To(Equal(mRegistration.UID)) objKey := types.NamespacedName{Namespace: seedImg.Namespace, Name: seedImg.Name} Expect(r.Get(ctx, objKey, &corev1.Pod{})).To(Succeed()) Expect(r.Get(ctx, objKey, &corev1.Service{})).To(Succeed()) }) It("should create the pod pulling the ISO from a container", func() { seedImg.Spec.BaseImage = "domain.org/some/repo:and-a-tag" _, err := r.Reconcile(ctx, reconcile.Request{ NamespacedName: types.NamespacedName{ Namespace: seedImg.Namespace, Name: seedImg.Name, }, }) Expect(err).ToNot(HaveOccurred()) Expect(cl.Get(ctx, client.ObjectKey{ Name: seedImg.Name, Namespace: seedImg.Namespace, }, seedImg)).To(Succeed()) foundPod := &corev1.Pod{} Expect(cl.Get(ctx, client.ObjectKey{ Name: seedImg.Name, Namespace: seedImg.Namespace, }, foundPod)).To(Succeed()) Expect(foundPod.Annotations["elemental.cattle.io/base-image"]).To(Equal(seedImg.Spec.BaseImage)) Expect(len(foundPod.Spec.InitContainers)).To(Equal(2)) Expect(foundPod.Spec.InitContainers[0].Image).To(Equal(seedImg.Spec.BaseImage)) }) }) var _ = Describe("validate seed image", func() { var mRegistration *elementalv1.MachineRegistration var seedImg *elementalv1.SeedImage BeforeEach(func() { mRegistration = &elementalv1.MachineRegistration{ ObjectMeta: metav1.ObjectMeta{ Name: "test-name2", Namespace: "default", }, Status: elementalv1.MachineRegistrationStatus{ RegistrationURL: "https://example.com/token", RegistrationToken: "token", Conditions: []metav1.Condition{ { Type: elementalv1.ReadyCondition, Reason: elementalv1.SuccessfullyCreatedReason, Status: metav1.ConditionTrue, LastTransitionTime: metav1.Now(), }, }, }, Spec: elementalv1.MachineRegistrationSpec{ Config: elementalv1.Config{ Elemental: elementalv1.Elemental{ Install: elementalv1.Install{}, }, }, }, } }) It("should fail validation on unknown platform format", func() { seedImg = &elementalv1.SeedImage{ ObjectMeta: metav1.ObjectMeta{ Name: "test-name", Namespace: "default", }, Spec: elementalv1.SeedImageSpec{ TargetPlatform: "test-platform", BaseImage: "missing-image", MachineRegistrationRef: &corev1.ObjectReference{ Name: mRegistration.Name, Namespace: mRegistration.Namespace, }, Type: elementalv1.TypeIso, }, } Expect(cl.Create(ctx, mRegistration)).To(Succeed()) err := cl.Create(ctx, seedImg) Expect(err).ToNot(Succeed()) Expect(err.Error()).To(ContainSubstring("Invalid value: \"test-platform\": spec.targetPlatform in body should match '^$|^\\S+\\/\\S+$'")) }) It("should pass validation on linux/amd64", func() { seedImg = &elementalv1.SeedImage{ ObjectMeta: metav1.ObjectMeta{ Name: "test-name", Namespace: "default", }, Spec: elementalv1.SeedImageSpec{ TargetPlatform: "linux/amd64", BaseImage: "missing-image", MachineRegistrationRef: &corev1.ObjectReference{ Name: mRegistration.Name, Namespace: mRegistration.Namespace, }, Type: elementalv1.TypeIso, }, } Expect(cl.Create(ctx, mRegistration)).To(Succeed()) Expect(cl.Create(ctx, seedImg)).To(Succeed()) }) AfterEach(func() { Expect(test.CleanupAndWait(ctx, cl, mRegistration, seedImg)).To(Succeed()) }) }) var _ = Describe("reconcile seed image build container", func() { var r *SeedImageReconciler var mRegistration *elementalv1.MachineRegistration var seedImg *elementalv1.SeedImage var setting *managementv3.Setting var pod *corev1.Pod var service *corev1.Service BeforeEach(func() { r = &SeedImageReconciler{ Client: cl, SeedImageImage: "registry.suse.com/rancher/seedimage-builder:latest", SeedImageImagePullPolicy: corev1.PullIfNotPresent, } mRegistration = &elementalv1.MachineRegistration{ ObjectMeta: metav1.ObjectMeta{ Name: "test-name", Namespace: "default", }, Status: elementalv1.MachineRegistrationStatus{ RegistrationURL: "https://example.com/token", RegistrationToken: "token", Conditions: []metav1.Condition{ { Type: elementalv1.ReadyCondition, Reason: elementalv1.SuccessfullyCreatedReason, Status: metav1.ConditionTrue, LastTransitionTime: metav1.Now(), }, }, }, Spec: elementalv1.MachineRegistrationSpec{ Config: elementalv1.Config{ Elemental: elementalv1.Elemental{ Install: elementalv1.Install{}, }, }, }, } seedImg = &elementalv1.SeedImage{ ObjectMeta: metav1.ObjectMeta{ Name: "test-name", Namespace: "default", }, Spec: elementalv1.SeedImageSpec{ TargetPlatform: "linux/amd64", BaseImage: "seed-image:latest", MachineRegistrationRef: &corev1.ObjectReference{ Name: mRegistration.Name, Namespace: mRegistration.Namespace, }, BuildContainer: &elementalv1.BuildContainer{ Image: "test-seedimage-builder:latest", ImagePullPolicy: corev1.PullNever, }, Type: elementalv1.TypeIso, }, } setting = &managementv3.Setting{ ObjectMeta: metav1.ObjectMeta{ Name: "server-url", }, Value: "https://example.com", } pod = &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Namespace: seedImg.Namespace, Name: seedImg.Name, }, } service = &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Namespace: seedImg.Namespace, Name: seedImg.Name, }, } statusCopy := mRegistration.Status.DeepCopy() Expect(cl.Create(ctx, mRegistration)).To(Succeed()) Expect(cl.Create(ctx, seedImg)).To(Succeed()) Expect(cl.Create(ctx, setting)).To(Succeed()) patchBase := client.MergeFrom(mRegistration.DeepCopy()) mRegistration.Status = *statusCopy Expect(cl.Status().Patch(ctx, mRegistration, patchBase)).To(Succeed()) }) AfterEach(func() { Expect(test.CleanupAndWait(ctx, cl, mRegistration, seedImg, setting, pod, service)).To(Succeed()) }) It("should create the initContainer from a user-specified BuildContainer", func() { _, err := r.Reconcile(ctx, reconcile.Request{ NamespacedName: types.NamespacedName{ Namespace: seedImg.Namespace, Name: seedImg.Name, }, }) Expect(err).ToNot(HaveOccurred()) Expect(cl.Get(ctx, client.ObjectKey{ Name: seedImg.Name, Namespace: seedImg.Namespace, }, seedImg)).To(Succeed()) foundPod := &corev1.Pod{} Expect(cl.Get(ctx, client.ObjectKey{ Name: seedImg.Name, Namespace: seedImg.Namespace, }, foundPod)).To(Succeed()) Expect(foundPod.Annotations["elemental.cattle.io/base-image"]).To(Equal(seedImg.Spec.BaseImage)) Expect(len(foundPod.Spec.InitContainers)).To(Equal(1)) Expect(foundPod.Spec.InitContainers[0].Image).To(Equal(seedImg.Spec.BuildContainer.Image)) Expect(foundPod.Spec.InitContainers[0].ImagePullPolicy).To(Equal(seedImg.Spec.BuildContainer.ImagePullPolicy)) Expect(len(foundPod.Spec.InitContainers[0].Env)).To(Equal(4)) }) }) var _ = Describe("reconcileBuildImagePod", func() { var r *SeedImageReconciler var setting *managementv3.Setting var mRegistration *elementalv1.MachineRegistration var seedImg *elementalv1.SeedImage var pod *corev1.Pod var svc *corev1.Service BeforeEach(func() { r = &SeedImageReconciler{ Client: cl, SeedImageImage: "registry.suse.com/rancher/seedimage-builder:latest", SeedImageImagePullPolicy: corev1.PullIfNotPresent, } mRegistration = &elementalv1.MachineRegistration{ ObjectMeta: metav1.ObjectMeta{ Name: "test-name", Namespace: "default", }, Status: elementalv1.MachineRegistrationStatus{ RegistrationURL: "https://example.com/token", RegistrationToken: "token", Conditions: []metav1.Condition{ { Type: elementalv1.ReadyCondition, Reason: elementalv1.SuccessfullyCreatedReason, Status: metav1.ConditionTrue, LastTransitionTime: metav1.Now(), }, }, }, Spec: elementalv1.MachineRegistrationSpec{ Config: elementalv1.Config{ Elemental: elementalv1.Elemental{ Install: elementalv1.Install{}, }, }, }, } seedImg = &elementalv1.SeedImage{ ObjectMeta: metav1.ObjectMeta{ Name: "test-name", Namespace: "default", }, Spec: elementalv1.SeedImageSpec{ BaseImage: "https://example.com/base.iso", MachineRegistrationRef: &corev1.ObjectReference{ Name: mRegistration.Name, Namespace: mRegistration.Namespace, }, Type: elementalv1.TypeIso, }, } pod = &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: seedImg.Name, Namespace: seedImg.Namespace, }, } svc = &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Namespace: seedImg.Namespace, Name: seedImg.Name, }, } setting = &managementv3.Setting{ ObjectMeta: metav1.ObjectMeta{ Name: "server-url", }, Value: "https://example.com", } statusCopy := mRegistration.Status.DeepCopy() Expect(cl.Create(ctx, mRegistration)).To(Succeed()) Expect(cl.Create(ctx, seedImg)).To(Succeed()) Expect(cl.Create(ctx, setting)).To(Succeed()) patchBase := client.MergeFrom(mRegistration.DeepCopy()) mRegistration.Status = *statusCopy Expect(cl.Status().Patch(ctx, mRegistration, patchBase)).To(Succeed()) }) AfterEach(func() { Expect(test.CleanupAndWait(ctx, cl, mRegistration, setting, seedImg, pod, svc)).To(Succeed()) }) It("should return error when a pod with the same name but different owner is there", func() { pod.Spec.Containers = []corev1.Container{ { Name: "nginx", Image: "nginx:latest", }, } Expect(cl.Create(ctx, pod)).To(Succeed()) err := r.reconcileBuildImagePod(ctx, seedImg, mRegistration) // Pod already there and not owned by the SeedImage obj Expect(err).To(HaveOccurred()) }) It("should skip the reconcile loop when the SeedImageReady condition is met and the Image lifetime has expired", func() { seedImg.Status.Conditions = []metav1.Condition{ { Type: elementalv1.SeedImageConditionReady, Status: metav1.ConditionTrue, Reason: elementalv1.SeedImageBuildDeadline, }, } err := r.reconcileBuildImagePod(ctx, seedImg, mRegistration) Expect(err).ToNot(HaveOccurred()) foundPod := &corev1.Pod{} err = cl.Get(ctx, types.NamespacedName{ Namespace: seedImg.Namespace, Name: seedImg.Name, }, foundPod) Expect(apierrors.IsNotFound(err)).To(BeTrue()) }) It("should recreate the pod if the pod is owned but the base iso is different", func() { _, err := r.Reconcile(ctx, reconcile.Request{ NamespacedName: types.NamespacedName{ Namespace: seedImg.Namespace, Name: seedImg.Name, }, }) Expect(err).ToNot(HaveOccurred()) Expect(cl.Get(ctx, client.ObjectKey{ Name: seedImg.Name, Namespace: seedImg.Namespace, }, seedImg)).To(Succeed()) foundPod := &corev1.Pod{} Expect(cl.Get(ctx, client.ObjectKey{ Name: seedImg.Name, Namespace: seedImg.Namespace, }, foundPod)).To(Succeed()) Expect(foundPod.Annotations["elemental.cattle.io/base-image"]).To(Equal(seedImg.Spec.BaseImage)) seedImg.Spec.BaseImage = "https://example.com/new-base.iso" err = r.reconcileBuildImagePod(ctx, seedImg, mRegistration) Expect(err).ToNot(HaveOccurred()) Expect(cl.Get(ctx, client.ObjectKey{ Name: seedImg.Name, Namespace: seedImg.Namespace, }, foundPod)).To(Succeed()) Expect(foundPod.Annotations["elemental.cattle.io/base-image"]).To(Equal(seedImg.Spec.BaseImage)) }) It("should recreate the pod if the pod is owned but the cloud-config is different", func() { _, err := r.Reconcile(ctx, reconcile.Request{ NamespacedName: types.NamespacedName{ Namespace: seedImg.Namespace, Name: seedImg.Name, }, }) Expect(err).ToNot(HaveOccurred()) Expect(cl.Get(ctx, client.ObjectKey{ Name: seedImg.Name, Namespace: seedImg.Namespace, }, seedImg)).To(Succeed()) foundPod := &corev1.Pod{} Expect(cl.Get(ctx, client.ObjectKey{ Name: seedImg.Name, Namespace: seedImg.Namespace, }, foundPod)).To(Succeed()) seedImg.Spec.CloudConfig = map[string]runtime.RawExtension{ "write_files": {}, } err = r.reconcileBuildImagePod(ctx, seedImg, mRegistration) Expect(err).ToNot(HaveOccurred()) Expect(cl.Get(ctx, client.ObjectKey{ Name: seedImg.Name, Namespace: seedImg.Namespace, }, foundPod)).To(Succeed()) }) }) var _ = Describe("createConfigMapObject", func() { var r *SeedImageReconciler var setting *managementv3.Setting var mRegistration *elementalv1.MachineRegistration var seedImg *elementalv1.SeedImage var configMap *corev1.ConfigMap var pod *corev1.Pod var svc *corev1.Service BeforeEach(func() { r = &SeedImageReconciler{ Client: cl, SeedImageImage: "registry.suse.com/rancher/seedimage-builder:latest", SeedImageImagePullPolicy: corev1.PullIfNotPresent, } mRegistration = &elementalv1.MachineRegistration{ ObjectMeta: metav1.ObjectMeta{ Name: "test-name", Namespace: "default", }, Status: elementalv1.MachineRegistrationStatus{ RegistrationURL: "https://example.com/token", RegistrationToken: "token", Conditions: []metav1.Condition{ { Type: elementalv1.ReadyCondition, Reason: elementalv1.SuccessfullyCreatedReason, Status: metav1.ConditionTrue, LastTransitionTime: metav1.Now(), }, }, }, Spec: elementalv1.MachineRegistrationSpec{ Config: elementalv1.Config{ Elemental: elementalv1.Elemental{ Install: elementalv1.Install{}, }, }, }, } seedImg = &elementalv1.SeedImage{ ObjectMeta: metav1.ObjectMeta{ Name: "test-name", Namespace: "default", }, Spec: elementalv1.SeedImageSpec{ BaseImage: "https://example.com/base.iso", MachineRegistrationRef: &corev1.ObjectReference{ Name: mRegistration.Name, Namespace: mRegistration.Namespace, }, Type: elementalv1.TypeIso, }, } pod = &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: seedImg.Name, Namespace: seedImg.Namespace, }, } svc = &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Namespace: seedImg.Namespace, Name: seedImg.Name, }, } configMap = &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Namespace: seedImg.Namespace, Name: seedImg.Name, }, } setting = &managementv3.Setting{ ObjectMeta: metav1.ObjectMeta{ Name: "server-url", }, Value: "https://example.com", } statusCopy := mRegistration.Status.DeepCopy() Expect(cl.Create(ctx, mRegistration)).To(Succeed()) Expect(cl.Create(ctx, seedImg)).To(Succeed()) Expect(cl.Create(ctx, setting)).To(Succeed()) patchBase := client.MergeFrom(mRegistration.DeepCopy()) mRegistration.Status = *statusCopy Expect(cl.Status().Patch(ctx, mRegistration, patchBase)).To(Succeed()) }) AfterEach(func() { Expect(test.CleanupAndWait(ctx, cl, mRegistration, seedImg, setting, configMap, pod, svc)).To(Succeed()) }) It("should create a configmap with empty cloud-config data", func() { Expect(r.reconcileConfigMapObject(ctx, seedImg, mRegistration)).To(Succeed()) Expect(cl.Get(ctx, client.ObjectKey{ Name: seedImg.Name, Namespace: seedImg.Namespace, }, configMap)).To(Succeed()) Expect(len(configMap.BinaryData["cloud-config"])).To(Equal(0)) }) It("should create a configmap with non-empty cloud-config data", func() { config := []map[string]string{{ "path": "/some/path", "content": "file contents", }} rawConf, err := json.Marshal(config) Expect(err).ToNot(HaveOccurred()) seedImg.Spec.CloudConfig = map[string]runtime.RawExtension{} seedImg.Spec.CloudConfig["write_files"] = runtime.RawExtension{Raw: rawConf} Expect(r.reconcileConfigMapObject(ctx, seedImg, mRegistration)).To(Succeed()) Expect(cl.Get(ctx, client.ObjectKey{ Name: seedImg.Name, Namespace: seedImg.Namespace, }, configMap)).To(Succeed()) Expect(string(configMap.BinaryData["cloud-config"])). To(ContainSubstring("#cloud-config\nwrite_files:")) }) It("should fail if cloud-config can't be marshalled", func() { seedImg.Spec.CloudConfig = map[string]runtime.RawExtension{} seedImg.Spec.CloudConfig["write_files"] = runtime.RawExtension{Raw: []byte("invalid data")} Expect(r.reconcileConfigMapObject(ctx, seedImg, mRegistration)).ToNot(Succeed()) }) }) var _ = Describe("updateStatusFromPod", func() { var r *SeedImageReconciler var seedImg *elementalv1.SeedImage var pod *corev1.Pod BeforeEach(func() { r = &SeedImageReconciler{ Client: cl, SeedImageImage: "registry.suse.com/rancher/seedimage-builder:latest", } seedImg = &elementalv1.SeedImage{ Status: elementalv1.SeedImageStatus{ Conditions: []metav1.Condition{ { Type: elementalv1.SeedImageConditionReady, Status: metav1.ConditionUnknown, }, }, }, } pod = &corev1.Pod{} }) AfterEach(func() { Expect(test.CleanupAndWait(ctx, cl, seedImg, pod)).To(Succeed()) }) It("should sync SeedImageReady condition to Pod phase", func() { expectedStates := []struct { podPhase corev1.PodPhase errorExpected bool imgConditionState metav1.ConditionStatus imgConditionReason string }{ {corev1.PodPending, false, metav1.ConditionFalse, elementalv1.SeedImageBuildOngoingReason}, {corev1.PodRunning, true, metav1.ConditionFalse, elementalv1.SeedImageExposeFailureReason}, {corev1.PodFailed, false, metav1.ConditionFalse, elementalv1.SeedImageBuildFailureReason}, {corev1.PodSucceeded, true, metav1.ConditionFalse, elementalv1.SeedImageBuildDeadline}, {corev1.PodUnknown, false, metav1.ConditionUnknown, elementalv1.SeedImageBuildUnknown}, } for _, expectedState := range expectedStates { pod.Status.Phase = expectedState.podPhase err := r.updateStatusFromPod(ctx, seedImg, pod) if expectedState.errorExpected { Expect(err).To(HaveOccurred()) } else { Expect(err).ToNot(HaveOccurred()) } annotation := fmt.Sprintf("pod phase: %s", expectedState.podPhase) Expect(seedImg.Status.Conditions[0].Status).To(Equal(expectedState.imgConditionState), annotation) Expect(seedImg.Status.Conditions[0].Reason).To(Equal(expectedState.imgConditionReason), annotation) } }) It("should skip syncing when SeedImageReady condition is True", func() { seedImg.Status.Conditions = []metav1.Condition{ { Type: elementalv1.SeedImageConditionReady, Status: metav1.ConditionTrue, Reason: "should stay untouched", }, } err := r.updateStatusFromPod(ctx, seedImg, pod) Expect(err).ToNot(HaveOccurred()) Expect(seedImg.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) Expect(seedImg.Status.Conditions[0].Reason).To(Equal("should stay untouched")) }) }) var _ = Describe("fillBuildImagePod", func() { It("should provide default build container", func() { defaultBuildImg := "default-builder:latest" seedImg := &elementalv1.SeedImage{ Spec: elementalv1.SeedImageSpec{ BaseImage: "http://localhost/default-image.iso", }, } pod := fillBuildImagePod(seedImg, defaultBuildImg, corev1.PullNever) Expect(len(pod.Spec.InitContainers)).To(Equal(1)) Expect(pod.Spec.InitContainers[0].Image).To(Equal(defaultBuildImg)) Expect(pod.Spec.InitContainers[0].ImagePullPolicy).To(Equal(corev1.PullNever)) }) It("should use user-provided build container", func() { buildImg := "custom-image:latest" quantity10M := *resource.NewQuantity(10*1024*1024, resource.BinarySI) seedImg := &elementalv1.SeedImage{ Spec: elementalv1.SeedImageSpec{ BuildContainer: &elementalv1.BuildContainer{ Image: buildImg, ImagePullPolicy: corev1.PullNever, }, Size: quantity10M, }, } pod := fillBuildImagePod(seedImg, "", corev1.PullNever) Expect(len(pod.Spec.InitContainers)).To(Equal(1)) Expect(pod.Spec.InitContainers[0].Image).To(Equal(buildImg)) Expect(pod.Spec.InitContainers[0].ImagePullPolicy).To(Equal(corev1.PullNever)) }) It("should use targetPlatform when provided", func() { defaultBuildImg := "default-builder:latest" seedImg := &elementalv1.SeedImage{ Spec: elementalv1.SeedImageSpec{ BaseImage: "elemental/default-iso:latest", TargetPlatform: "linux/riscv64", }, } pod := fillBuildImagePod(seedImg, defaultBuildImg, corev1.PullNever) Expect(len(pod.Spec.InitContainers)).To(Equal(2)) Expect(pod.Spec.InitContainers[0].Image).To(Equal(defaultBuildImg)) Expect(pod.Spec.InitContainers[0].Args[0]).To(ContainSubstring("elemental pull-image --platform=linux/riscv64")) }) })