/* Copyright © 2022 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" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" elementalv1 "github.com/rancher/elemental-operator/api/v1beta1" "github.com/rancher/elemental-operator/pkg/test" fleetv1 "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1" upgradev1 "github.com/rancher/system-upgrade-controller/pkg/apis/upgrade.cattle.io/v1" "github.com/rancher/wrangler/pkg/genericcondition" "github.com/rancher/wrangler/pkg/name" "gopkg.in/yaml.v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) var _ = Describe("reconcile managed os image", func() { var r *ManagedOSImageReconciler var managedOSImage *elementalv1.ManagedOSImage var bundle *fleetv1.Bundle BeforeEach(func() { r = &ManagedOSImageReconciler{ Client: cl, Scheme: cl.Scheme(), } managedOSImage = &elementalv1.ManagedOSImage{ ObjectMeta: metav1.ObjectMeta{ Name: "test-name", Namespace: "default", }, } bundle = &fleetv1.Bundle{ ObjectMeta: metav1.ObjectMeta{ Name: name.SafeConcatName("mos", managedOSImage.Name), Namespace: managedOSImage.Namespace, }, } }) AfterEach(func() { Expect(test.CleanupAndWait(ctx, cl, managedOSImage, bundle)).To(Succeed()) }) It("should reconcile managed os image object", func() { Expect(cl.Create(ctx, managedOSImage)).To(Succeed()) _, err := r.Reconcile(ctx, reconcile.Request{ NamespacedName: types.NamespacedName{ Namespace: managedOSImage.Namespace, Name: managedOSImage.Name, }, }) Expect(err).ToNot(HaveOccurred()) Expect(cl.Get(ctx, client.ObjectKey{ Name: managedOSImage.Name, Namespace: managedOSImage.Namespace, }, managedOSImage)).To(Succeed()) bundle := &fleetv1.Bundle{} Expect(cl.Get(ctx, client.ObjectKey{ Name: name.SafeConcatName("mos", managedOSImage.Name), Namespace: managedOSImage.Namespace, }, bundle)).To(Succeed()) }) }) var _ = Describe("newFleetBundleResources", func() { var r *ManagedOSImageReconciler var managedOSImage *elementalv1.ManagedOSImage var managedOSVersion *elementalv1.ManagedOSVersion BeforeEach(func() { r = &ManagedOSImageReconciler{ Client: cl, Scheme: cl.Scheme(), } managedOSImage = &elementalv1.ManagedOSImage{ ObjectMeta: metav1.ObjectMeta{ Name: "test-name", Namespace: "default", }, } managedOSVersion = &elementalv1.ManagedOSVersion{ ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "default", }, Spec: elementalv1.ManagedOSVersionSpec{ UpgradeContainer: &upgradev1.ContainerSpec{ Image: "foo/bar:image", }, }, } }) AfterEach(func() { Expect(test.CleanupAndWait(ctx, cl, managedOSImage, managedOSVersion)).To(Succeed()) }) It("should create fleet bundle resources", func() { bundleResources, err := r.newFleetBundleResources(ctx, managedOSImage) Expect(err).ToNot(HaveOccurred()) Expect(bundleResources).To(HaveLen(5)) Expect(bundleResources[0].Name).To(Equal("ClusterRole--os-upgrader-test-name-87f8d805a5c0.yaml")) Expect(bundleResources[1].Name).To(Equal("ClusterRoleBinding--os-upgrader-test-name-cc7ce4275b54.yaml")) Expect(bundleResources[2].Name).To(Equal("ServiceAccount-cattle-system-os-upgrader-test-name-08929531f5c0.yaml")) Expect(bundleResources[3].Name).To(Equal("Secret-cattle-system-os-upgrader-test-name-52e9d8e041f4.yaml")) Expect(bundleResources[4].Name).To(Equal("Plan-cattle-system-os-upgrader-test-name-24d63a562894.yaml")) }) It("should create fleet bundle when managedOSVersion exists", func() { Expect(r.Create(ctx, managedOSVersion)).To(Succeed()) managedOSImage.Spec.ManagedOSVersionName = "test" bundleResources, err := r.newFleetBundleResources(ctx, managedOSImage) Expect(err).ToNot(HaveOccurred()) Expect(bundleResources).To(HaveLen(5)) plan := &upgradev1.Plan{} Expect(yaml.Unmarshal([]byte(bundleResources[4].Content), plan)).To(Succeed()) Expect(plan.Spec.Upgrade.Image).To(Equal("foo/bar:image")) }) It("return error of managedOSVersion doesn't exist", func() { managedOSImage.Spec.ManagedOSVersionName = "test" bundleResources, err := r.newFleetBundleResources(ctx, managedOSImage) Expect(err).To(HaveOccurred()) Expect(bundleResources).To(BeNil()) }) It("return early if fleet bundle already exists", func() { meta.SetStatusCondition(&managedOSImage.Status.Conditions, metav1.Condition{ Type: elementalv1.FleetBundleCreation, Reason: elementalv1.FleetBundleCreateSuccessReason, Status: metav1.ConditionTrue, }) bundleResources, err := r.newFleetBundleResources(ctx, managedOSImage) Expect(err).ToNot(HaveOccurred()) Expect(bundleResources).To(BeNil()) }) }) var _ = Describe("createFleetBundle", func() { var r *ManagedOSImageReconciler var managedOSImage *elementalv1.ManagedOSImage var bundle *fleetv1.Bundle BeforeEach(func() { r = &ManagedOSImageReconciler{ Client: cl, Scheme: cl.Scheme(), } managedOSImage = &elementalv1.ManagedOSImage{ ObjectMeta: metav1.ObjectMeta{ Name: "test-name", Namespace: "default", UID: "test", }, Spec: elementalv1.ManagedOSImageSpec{ ClusterRolloutStrategy: &fleetv1.RolloutStrategy{ MaxUnavailable: &intstr.IntOrString{ IntVal: 3, }, }, Targets: []elementalv1.BundleTarget{ { Name: "test", }, }, }, } bundle = &fleetv1.Bundle{ ObjectMeta: metav1.ObjectMeta{ Name: name.SafeConcatName("mos", managedOSImage.Name), Namespace: managedOSImage.Namespace, }, } }) AfterEach(func() { Expect(test.CleanupAndWait(ctx, cl, managedOSImage, bundle)).To(Succeed()) }) It("should create fleet bundle resources", func() { err := r.createFleetBundle(ctx, managedOSImage, []fleetv1.BundleResource{ { Name: "test", }, }) Expect(err).ToNot(HaveOccurred()) Expect(cl.Get(ctx, client.ObjectKey{ Name: name.SafeConcatName("mos", managedOSImage.Name), Namespace: managedOSImage.Namespace, }, bundle)).To(Succeed()) Expect(bundle.Spec.Resources).To(HaveLen(1)) Expect(bundle.Spec.Resources[0].Name).To(Equal("test")) Expect(bundle.Spec.RolloutStrategy).ToNot(BeNil()) Expect(bundle.Spec.RolloutStrategy.MaxUnavailable.IntVal).To(Equal(int32(3))) Expect(bundle.Spec.Targets).To(HaveLen(1)) Expect(bundle.Spec.Targets[0].Name).To(Equal("test")) }) It("should change target when namespace is fleet-local", func() { managedOSImage.Namespace = "fleet-local" err := r.createFleetBundle(ctx, managedOSImage, []fleetv1.BundleResource{ { Name: "test", }, }) Expect(err).ToNot(HaveOccurred()) Expect(cl.Get(ctx, client.ObjectKey{ Name: name.SafeConcatName("mos", managedOSImage.Name), Namespace: managedOSImage.Namespace, }, bundle)).To(Succeed()) Expect(bundle.Spec.Resources).To(HaveLen(1)) Expect(bundle.Spec.Resources[0].Name).To(Equal("test")) Expect(bundle.Spec.RolloutStrategy).ToNot(BeNil()) Expect(bundle.Spec.RolloutStrategy.MaxUnavailable.IntVal).To(Equal(int32(3))) Expect(bundle.Spec.Targets).To(HaveLen(1)) Expect(bundle.Spec.Targets[0].ClusterName).To(Equal("local")) }) It("return early if fleet bundle already exists", func() { meta.SetStatusCondition(&managedOSImage.Status.Conditions, metav1.Condition{ Type: elementalv1.FleetBundleCreation, Reason: elementalv1.FleetBundleCreateSuccessReason, Status: metav1.ConditionTrue, }) err := r.createFleetBundle(ctx, managedOSImage, []fleetv1.BundleResource{}) Expect(err).ToNot(HaveOccurred()) }) }) var _ = Describe("updateManagedOSImageStatus", func() { var r *ManagedOSImageReconciler var managedOSImage *elementalv1.ManagedOSImage var bundle *fleetv1.Bundle BeforeEach(func() { r = &ManagedOSImageReconciler{ Client: cl, Scheme: cl.Scheme(), } managedOSImage = &elementalv1.ManagedOSImage{ ObjectMeta: metav1.ObjectMeta{ Name: "test-name", Namespace: "default", }, Spec: elementalv1.ManagedOSImageSpec{ ClusterRolloutStrategy: &fleetv1.RolloutStrategy{ MaxUnavailable: &intstr.IntOrString{ IntVal: 3, }, }, Targets: []elementalv1.BundleTarget{ { Name: "test", }, }, }, } bundle = &fleetv1.Bundle{ ObjectMeta: metav1.ObjectMeta{ Name: name.SafeConcatName("mos", managedOSImage.Name), Namespace: managedOSImage.Namespace, }, } }) AfterEach(func() { Expect(test.CleanupAndWait(ctx, cl, managedOSImage, bundle)).To(Succeed()) }) It("should update manage os image status", func() { Expect(r.Create(ctx, bundle)).To(Succeed()) patchBase := client.MergeFrom(bundle.DeepCopy()) bundle.Status = fleetv1.BundleStatus{ Conditions: []genericcondition.GenericCondition{ { Type: "test", Status: corev1.ConditionTrue, Reason: "test", Message: "test", }, }, } Expect(r.Status().Patch(ctx, bundle, patchBase)).To(Succeed()) err := r.updateManagedOSImageStatus(ctx, managedOSImage) Expect(err).ToNot(HaveOccurred()) Expect(managedOSImage.Status.Conditions).To(HaveLen(1)) Expect(managedOSImage.Status.Conditions[0].Type).To(Equal("test")) Expect(managedOSImage.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) Expect(managedOSImage.Status.Conditions[0].Reason).To(Equal("test")) }) It("should update manage os image status when condition is missing fields", func() { Expect(r.Create(ctx, bundle)).To(Succeed()) patchBase := client.MergeFrom(bundle.DeepCopy()) bundle.Status = fleetv1.BundleStatus{ Conditions: []genericcondition.GenericCondition{ { Message: "test", }, }, } Expect(r.Status().Patch(ctx, bundle, patchBase)).To(Succeed()) err := r.updateManagedOSImageStatus(ctx, managedOSImage) Expect(err).ToNot(HaveOccurred()) Expect(managedOSImage.Status.Conditions).To(HaveLen(1)) Expect(managedOSImage.Status.Conditions[0].Type).To(Equal("UnknownType")) Expect(managedOSImage.Status.Conditions[0].Status).To(Equal(metav1.ConditionUnknown)) Expect(managedOSImage.Status.Conditions[0].Reason).To(Equal("UnknownReason")) }) }) var _ = Describe("metadataEnv", func() { It("should container env variables when passed as strings", func() { envMap := map[string]runtime.RawExtension{ "upgradeImage": {Raw: []byte(`"registry.com/repository/image:v1.0"`)}, "robin": {Raw: []byte(`"batman"`)}, } env := metadataEnv(envMap) Expect(env).To(HaveLen(2)) Expect(env[0].Name).To(Equal("METADATA_UPGRADEIMAGE")) Expect(env[0].Value).To(Equal("registry.com/repository/image:v1.0")) Expect(env[1].Name).To(Equal("METADATA_ROBIN")) Expect(env[1].Value).To(Equal("batman")) }) It("should container env variables when passed as jsondata", func() { jsonStruct := struct{ Foo string }{Foo: "foostruct"} jsonData, err := json.Marshal(jsonStruct) Expect(err).ToNot(HaveOccurred()) envMap := map[string]runtime.RawExtension{ "jsondata": {Raw: jsonData}, } env := metadataEnv(envMap) Expect(env).To(HaveLen(1)) Expect(env[0].Name).To(Equal("METADATA_JSONDATA")) Expect(env[0].Value).To(Equal("{\"Foo\":\"foostruct\"}")) }) })