elemental-operator/controllers/seedimage_controller_test.go

851 lines
25 KiB
Go

/*
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"))
})
})