diff --git a/internal/controller/component/component_controller.go b/internal/controller/component/component_controller.go index 735d6368..0510e2d6 100644 --- a/internal/controller/component/component_controller.go +++ b/internal/controller/component/component_controller.go @@ -26,6 +26,7 @@ import ( "github.com/fluxcd/pkg/runtime/patch" "github.com/mandelsoft/goutils/sliceutils" "github.com/opencontainers/go-digest" + k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/types" "ocm.software/ocm/api/datacontext" @@ -49,6 +50,8 @@ import ( "github.com/open-component-model/ocm-k8s-toolkit/pkg/status" ) +const SnapshotFinalizer = "snapshot-finalizer" + // Reconciler reconciles a Component object. type Reconciler struct { *ocm.BaseReconciler @@ -120,6 +123,7 @@ func (r *Reconciler) findOCMRepositories(key string) handler.MapFunc { // Reconcile the component object. func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, retErr error) { + log.FromContext(ctx).Info("reconciling component", "name", req.Name) component := &v1alpha1.Component{} if err := r.Get(ctx, req.NamespacedName, component); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) @@ -144,9 +148,36 @@ func (r *Reconciler) reconcileWithStatusUpdate(ctx context.Context, component *v func (r *Reconciler) reconcileExists(ctx context.Context, component *v1alpha1.Component) (_ ctrl.Result, retErr error) { logger := log.FromContext(ctx) - if component.GetDeletionTimestamp() != nil { + if !component.GetDeletionTimestamp().IsZero() { logger.Info("component is being deleted and cannot be used", "name", component.Name) + if component.Status.SnapshotRef.Name != "" { + snap := &v1alpha1.Snapshot{} + snap.SetNamespace(component.GetNamespace()) + snap.SetName(component.Status.SnapshotRef.Name) + err := r.Get(ctx, client.ObjectKeyFromObject(snap), snap) + if err == nil { + err = r.Delete(ctx, snap) + } + if err != nil && !k8serrors.IsNotFound(err) { + return ctrl.Result{}, fmt.Errorf("failed to delete snapshot: %w", err) + } + logger.Info("referenced snapshot deleted", "name", snap.GetName()) + } + + if updated := controllerutil.RemoveFinalizer(component, SnapshotFinalizer); updated { + if err := r.Update(ctx, component); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to remove finalizer: %w", err) + } + } + + return ctrl.Result{Requeue: true}, nil + } + + if updated := controllerutil.AddFinalizer(component, SnapshotFinalizer); updated { + if err := r.Update(ctx, component); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to add finalizer: %w", err) + } return ctrl.Result{Requeue: true}, nil } @@ -172,9 +203,9 @@ func (r *Reconciler) reconcile(ctx context.Context, component *v1alpha1.Componen return ctrl.Result{}, fmt.Errorf("failed to get repository: %w", err) } - if repo.DeletionTimestamp != nil { + if !repo.DeletionTimestamp.IsZero() { err := errors.New("repository is being deleted, please do not use it") - logger.Error(err, "repository is being deleted, please do not use it", "name", component.Spec.RepositoryRef.Name) + logger.Error(err, "waiting for deletion", "name", component.Spec.RepositoryRef.Name) // Triggered through cache return ctrl.Result{}, nil @@ -330,22 +361,27 @@ func (r *Reconciler) reconcileComponent(ctx context.Context, octx ocmctx.Context Size: int64(len(descriptorsBytes)), }, ) + snapshotCopy := snapshotCR.DeepCopy() - if _, err = controllerutil.CreateOrUpdate(ctx, r.GetClient(), snapshotCR, func() error { + result, err := controllerutil.CreateOrUpdate(ctx, r.GetClient(), snapshotCR, func() error { if err := controllerutil.SetControllerReference(component, snapshotCR, r.GetScheme()); err != nil { return fmt.Errorf("failed to set controller reference: %w", err) } + snapshotCR.Spec = snapshotCopy.Spec + component.Status.SnapshotRef = corev1.LocalObjectReference{ Name: snapshotCR.GetName(), } return nil - }); err != nil { + }) + if err != nil { status.MarkNotReady(r.EventRecorder, component, v1alpha1.CreateSnapshotFailedReason, err.Error()) return ctrl.Result{}, err } + logger.Info(fmt.Sprintf("snapshot %s", result), "operation", result) logger.Info("updating status") component.Status.Component = v1alpha1.ComponentInfo{ diff --git a/internal/controller/component/component_controller_test.go b/internal/controller/component/component_controller_test.go index a3ab847c..9ce49f40 100644 --- a/internal/controller/component/component_controller_test.go +++ b/internal/controller/component/component_controller_test.go @@ -18,8 +18,8 @@ package component import ( "context" - "fmt" "os" + "strings" "time" "github.com/fluxcd/pkg/apis/meta" @@ -49,40 +49,32 @@ import ( ) const ( - CTFPath = "ocm-k8s-ctfstore--*" - Namespace = "test-namespace" - RepositoryObj = "test-repository" - Component = "ocm.software/test-component" - ComponentObj = "test-component" - Version1 = "1.0.0" - Version2 = "1.0.1" + CTFPath = "ocm-k8s-ctfstore--*" + Component = "ocm.software/test-component" + ComponentObj = "test-component" + Version1 = "1.0.0" + Version2 = "1.0.1" ) var _ = Describe("Component Controller", func() { var ( - ctx context.Context - cancel context.CancelFunc env *Builder ctfpath string - - repositoryName string - testNumber int - repositoryObj *v1alpha1.OCMRepository ) BeforeEach(func() { ctfpath = Must(os.MkdirTemp("", CTFPath)) env = NewBuilder(environment.FileSystem(osfs.OsFs)) - ctx = context.Background() - ctx, cancel = context.WithCancel(context.Background()) }) AfterEach(func() { Expect(os.RemoveAll(ctfpath)).To(Succeed()) Expect(env.Cleanup()).To(Succeed()) - cancel() }) Context("component controller", func() { - BeforeEach(func() { + var repositoryObj *v1alpha1.OCMRepository + var namespace *corev1.Namespace + + BeforeEach(func(ctx SpecContext) { By("creating a repository with name") env.OCMCommonTransport(ctfpath, accessio.FormatDirectory, func() { env.Component(Component, func() { @@ -93,10 +85,18 @@ var _ = Describe("Component Controller", func() { spec := Must(ctf.NewRepositorySpec(ctf.ACC_READONLY, ctfpath)) specdata := Must(spec.MarshalJSON()) - repositoryName = fmt.Sprintf("%s-%d", RepositoryObj, testNumber) + namespaceName := generateNamespace(ctx.SpecReport().LeafNodeText) + namespace = &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespaceName, + }, + } + Expect(k8sClient.Create(ctx, namespace)).To(Succeed()) + + repositoryName := "repository" repositoryObj = &v1alpha1.OCMRepository{ ObjectMeta: metav1.ObjectMeta{ - Namespace: Namespace, + Namespace: namespaceName, Name: repositoryName, }, Spec: v1alpha1.OCMRepositorySpec{ @@ -110,27 +110,41 @@ var _ = Describe("Component Controller", func() { conditions.MarkTrue(repositoryObj, "Ready", "ready", "message") Expect(k8sClient.Status().Update(ctx, repositoryObj)).To(Succeed()) - - testNumber++ }) - AfterEach(func() { + AfterEach(func(ctx SpecContext) { // make sure the repo is still ready conditions.MarkTrue(repositoryObj, "Ready", "ready", "message") Expect(k8sClient.Status().Update(ctx, repositoryObj)).To(Succeed()) + + By("deleting the repository") + Expect(k8sClient.Delete(ctx, repositoryObj)).To(Succeed()) + Eventually(func(ctx context.Context) bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(repositoryObj), repositoryObj) + return errors.IsNotFound(err) + }).WithContext(ctx).Should(BeTrue()) + + components := &v1alpha1.ComponentList{} + + Expect(k8sClient.List(ctx, components, client.InNamespace(namespace.GetName()))).To(Succeed()) + Expect(components.Items).To(HaveLen(0)) + + snapshots := &v1alpha1.SnapshotList{} + Expect(k8sClient.List(ctx, snapshots, client.InNamespace(namespace.GetName()))).To(Succeed()) + Expect(snapshots.Items).To(HaveLen(0)) }) - It("reconcileComponent a component", func() { + It("reconcileComponent a component", func(ctx SpecContext) { By("creating a component") component := &v1alpha1.Component{ ObjectMeta: metav1.ObjectMeta{ - Namespace: Namespace, - Name: fmt.Sprintf("%s-%d", ComponentObj, testNumber), + Namespace: namespace.GetName(), + Name: ComponentObj, }, Spec: v1alpha1.ComponentSpec{ RepositoryRef: v1alpha1.ObjectKey{ - Namespace: Namespace, - Name: repositoryName, + Namespace: namespace.GetName(), + Name: repositoryObj.GetName(), }, Component: Component, Semver: "1.0.0", @@ -152,17 +166,17 @@ var _ = Describe("Component Controller", func() { By("checking that the snapshot has been created successfully") Eventually(komega.Object(component), "15s").Should( HaveField("Status.SnapshotRef.Name", Not(BeEmpty()))) - snapshotComponent := &v1alpha1.Snapshot{} - Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: component.GetNamespace(), Name: component.GetSnapshotName()}, snapshotComponent)).To(Succeed()) + snapshot := &v1alpha1.Snapshot{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: component.GetNamespace(), Name: component.GetSnapshotName()}, snapshot)).To(Succeed()) By("validating the snapshot") - ownersReference := snapshotComponent.GetOwnerReferences() + ownersReference := snapshot.GetOwnerReferences() Expect(len(ownersReference)).To(Equal(1), "expected only one ownersReference") Expect(ownersReference[0].Name).To(Equal(component.GetName()), "expected to be a ownersReference of the component") By("checking that the snapshot contains the correct content") - snapshotRepository := Must(registry.NewRepository(ctx, snapshotComponent.Spec.Repository)) - snapshotComponentContent := Must(snapshotRepository.FetchSnapshot(ctx, snapshotComponent.GetDigest())) + snapshotRepository := Must(registry.NewRepository(ctx, snapshot.Spec.Repository)) + snapshotComponentContent := Must(snapshotRepository.FetchSnapshot(ctx, snapshot.GetDigest())) snapshotDescriptors := &ocm.Descriptors{} MustBeSuccessful(yaml.Unmarshal(snapshotComponentContent, snapshotDescriptors)) @@ -173,14 +187,19 @@ var _ = Describe("Component Controller", func() { By("delete resources manually") Expect(k8sClient.Delete(ctx, component)).To(Succeed()) - Expect(k8sClient.Delete(ctx, snapshotComponent)).To(Succeed()) + By("checking that the component has been deleted successfully") Eventually(func(ctx context.Context) bool { err := k8sClient.Get(ctx, client.ObjectKeyFromObject(component), component) return errors.IsNotFound(err) }, "15s").WithContext(ctx).Should(BeTrue()) + By("checking that the snapshot has been deleted successfully") + Eventually(func(ctx context.Context) bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(snapshot), snapshot) + return errors.IsNotFound(err) + }, "15s").WithContext(ctx).Should(BeTrue()) }) - It("does not reconcile when the repository is not ready", func() { + It("does not reconcile when the repository is not ready", func(ctx SpecContext) { By("marking the repository as not ready") conditions.MarkFalse(repositoryObj, "Ready", "notReady", "reason") Expect(k8sClient.Status().Update(ctx, repositoryObj)).To(Succeed()) @@ -188,13 +207,13 @@ var _ = Describe("Component Controller", func() { By("creating a component object") component := &v1alpha1.Component{ ObjectMeta: metav1.ObjectMeta{ - Namespace: Namespace, - Name: fmt.Sprintf("%s-%d", ComponentObj, testNumber), + Namespace: namespace.GetName(), + Name: ComponentObj, }, Spec: v1alpha1.ComponentSpec{ RepositoryRef: v1alpha1.ObjectKey{ - Namespace: Namespace, - Name: repositoryName, + Namespace: namespace.GetName(), + Name: repositoryObj.GetName(), }, Component: Component, Semver: "1.0.0", @@ -226,17 +245,17 @@ var _ = Describe("Component Controller", func() { }, "15s").WithContext(ctx).Should(BeTrue()) }) - It("grabs the new version when it becomes available", func() { + It("grabs the new version when it becomes available", func(ctx SpecContext) { By("creating a component") component := &v1alpha1.Component{ ObjectMeta: metav1.ObjectMeta{ - Namespace: Namespace, - Name: fmt.Sprintf("%s-%d", ComponentObj, testNumber), + Namespace: namespace.GetName(), + Name: ComponentObj, }, Spec: v1alpha1.ComponentSpec{ RepositoryRef: v1alpha1.ObjectKey{ - Namespace: Namespace, - Name: repositoryName, + Namespace: namespace.GetName(), + Name: repositoryObj.GetName(), }, Component: Component, Semver: ">=1.0.0", @@ -258,8 +277,8 @@ var _ = Describe("Component Controller", func() { By("checking that the snapshot has been created successfully") Eventually(komega.Object(component), "15s").Should( HaveField("Status.SnapshotRef.Name", Not(BeEmpty()))) - snapshotComponent := &v1alpha1.Snapshot{} - Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: component.GetNamespace(), Name: component.GetSnapshotName()}, snapshotComponent)).To(Succeed()) + snapshot := &v1alpha1.Snapshot{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: component.GetNamespace(), Name: component.GetSnapshotName()}, snapshot)).To(Succeed()) Expect(k8sClient.Get(ctx, types.NamespacedName{Name: component.Name, Namespace: component.Namespace}, component)).To(Succeed()) Expect(component.Status.Component.Version).To(Equal(Version1)) @@ -281,14 +300,17 @@ var _ = Describe("Component Controller", func() { By("delete resources manually") Expect(k8sClient.Delete(ctx, component)).To(Succeed()) - Expect(k8sClient.Delete(ctx, snapshotComponent)).To(Succeed()) Eventually(func(ctx context.Context) bool { err := k8sClient.Get(ctx, client.ObjectKeyFromObject(component), component) return errors.IsNotFound(err) }, "15s").WithContext(ctx).Should(BeTrue()) + Eventually(func(ctx context.Context) bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(snapshot), snapshot) + return errors.IsNotFound(err) + }, "15s").WithContext(ctx).Should(BeTrue()) }) - It("grabs lower version if downgrade is allowed", func() { + It("grabs lower version if downgrade is allowed", func(ctx SpecContext) { componentName := Component + "-downgrade" env.OCMCommonTransport(ctfpath, accessio.FormatDirectory, func() { env.Component(componentName, func() { @@ -304,13 +326,13 @@ var _ = Describe("Component Controller", func() { By("creating a component") component := &v1alpha1.Component{ ObjectMeta: metav1.ObjectMeta{ - Namespace: Namespace, - Name: fmt.Sprintf("%s-%d", ComponentObj, testNumber), + Namespace: namespace.GetName(), + Name: ComponentObj, }, Spec: v1alpha1.ComponentSpec{ RepositoryRef: v1alpha1.ObjectKey{ - Namespace: Namespace, - Name: repositoryName, + Namespace: namespace.GetName(), + Name: repositoryObj.GetName(), }, Component: componentName, DowngradePolicy: v1alpha1.DowngradePolicyAllow, @@ -333,8 +355,8 @@ var _ = Describe("Component Controller", func() { By("checking that the snapshot has been created successfully") Eventually(komega.Object(component), "15s").Should( HaveField("Status.SnapshotRef.Name", Not(BeEmpty()))) - snapshotComponent := &v1alpha1.Snapshot{} - Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: component.GetNamespace(), Name: component.GetSnapshotName()}, snapshotComponent)).To(Succeed()) + snapshot := &v1alpha1.Snapshot{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: component.GetNamespace(), Name: component.GetSnapshotName()}, snapshot)).To(Succeed()) Expect(k8sClient.Get(ctx, types.NamespacedName{Name: component.Name, Namespace: component.Namespace}, component)).To(Succeed()) Expect(component.Status.Component.Version).To(Equal("0.0.3")) @@ -350,14 +372,19 @@ var _ = Describe("Component Controller", func() { By("delete resources manually") Expect(k8sClient.Delete(ctx, component)).To(Succeed()) - Expect(k8sClient.Delete(ctx, snapshotComponent)).To(Succeed()) + By("verifying component is deleted") Eventually(func(ctx context.Context) bool { err := k8sClient.Get(ctx, client.ObjectKeyFromObject(component), component) return errors.IsNotFound(err) }, "15s").WithContext(ctx).Should(BeTrue()) + By("verifying snapshot is deleted") + Eventually(func(ctx context.Context) bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(snapshot), snapshot) + return errors.IsNotFound(err) + }, "15s").WithContext(ctx).Should(BeTrue()) }) - It("does not grab lower version if downgrade is denied", func() { + It("does not grab lower version if downgrade is denied", func(ctx SpecContext) { componentName := Component + "-downgrade-2" env.OCMCommonTransport(ctfpath, accessio.FormatDirectory, func() { env.Component(componentName, func() { @@ -373,13 +400,13 @@ var _ = Describe("Component Controller", func() { By("creating a component") component := &v1alpha1.Component{ ObjectMeta: metav1.ObjectMeta{ - Namespace: Namespace, - Name: fmt.Sprintf("%s-%d", ComponentObj, testNumber), + Namespace: namespace.GetName(), + Name: ComponentObj, }, Spec: v1alpha1.ComponentSpec{ RepositoryRef: v1alpha1.ObjectKey{ - Namespace: Namespace, - Name: repositoryName, + Namespace: namespace.GetName(), + Name: repositoryObj.GetName(), }, Component: componentName, DowngradePolicy: v1alpha1.DowngradePolicyDeny, @@ -419,14 +446,13 @@ var _ = Describe("Component Controller", func() { By("delete resources manually") Expect(k8sClient.Delete(ctx, component)).To(Succeed()) - Expect(k8sClient.Delete(ctx, snapshotComponent)).To(Succeed()) Eventually(func(ctx context.Context) bool { err := k8sClient.Get(ctx, client.ObjectKeyFromObject(component), component) return errors.IsNotFound(err) }, "15s").WithContext(ctx).Should(BeTrue()) }) - It("can force downgrade even if not allowed by the component", func() { + It("can force downgrade even if not allowed by the component", func(ctx SpecContext) { componentName := Component + "-downgrade-3" env.OCMCommonTransport(ctfpath, accessio.FormatDirectory, func() { env.Component(componentName, func() { @@ -438,13 +464,13 @@ var _ = Describe("Component Controller", func() { By("creating a component") component := &v1alpha1.Component{ ObjectMeta: metav1.ObjectMeta{ - Namespace: Namespace, - Name: fmt.Sprintf("%s-%d", ComponentObj, testNumber), + Namespace: namespace.GetName(), + Name: ComponentObj, }, Spec: v1alpha1.ComponentSpec{ RepositoryRef: v1alpha1.ObjectKey{ - Namespace: Namespace, - Name: repositoryName, + Namespace: namespace.GetName(), + Name: repositoryObj.GetName(), }, Component: componentName, DowngradePolicy: v1alpha1.DowngradePolicyEnforce, @@ -467,8 +493,8 @@ var _ = Describe("Component Controller", func() { By("checking that the snapshot has been created successfully") Eventually(komega.Object(component), "15s").Should( HaveField("Status.SnapshotRef.Name", Not(BeEmpty()))) - snapshotComponent := &v1alpha1.Snapshot{} - Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: component.GetNamespace(), Name: component.GetSnapshotName()}, snapshotComponent)).To(Succeed()) + snapshot := &v1alpha1.Snapshot{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: component.GetNamespace(), Name: component.GetSnapshotName()}, snapshot)).To(Succeed()) Expect(k8sClient.Get(ctx, types.NamespacedName{Name: component.Name, Namespace: component.Namespace}, component)).To(Succeed()) Expect(component.Status.Component.Version).To(Equal("0.0.3")) @@ -484,7 +510,10 @@ var _ = Describe("Component Controller", func() { By("delete resources manually") Expect(k8sClient.Delete(ctx, component)).To(Succeed()) - Expect(k8sClient.Delete(ctx, snapshotComponent)).To(Succeed()) + Eventually(func(ctx context.Context) bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(snapshot), snapshot) + return errors.IsNotFound(err) + }, "15s").WithContext(ctx).Should(BeTrue()) Eventually(func(ctx context.Context) bool { err := k8sClient.Get(ctx, client.ObjectKeyFromObject(component), component) return errors.IsNotFound(err) @@ -493,16 +522,14 @@ var _ = Describe("Component Controller", func() { }) Context("ocm config handling", func() { - const ( - Namespace = "test-namespace" - ) - var ( - configs []*corev1.ConfigMap - secrets []*corev1.Secret + configs []*corev1.ConfigMap + secrets []*corev1.Secret + namespace *corev1.Namespace + repositoryObj *v1alpha1.OCMRepository ) - BeforeEach(func() { + BeforeEach(func(ctx SpecContext) { By("creating a repository with name") env.OCMCommonTransport(ctfpath, accessio.FormatDirectory, func() { env.Component(Component, func() { @@ -513,12 +540,20 @@ var _ = Describe("Component Controller", func() { spec := Must(ctf.NewRepositorySpec(ctf.ACC_READONLY, ctfpath)) specdata := Must(spec.MarshalJSON()) - configs, secrets = createTestConfigsAndSecrets(ctx) + namespaceName := generateNamespace(ctx.SpecReport().LeafNodeText) + namespace = &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespaceName, + }, + } + Expect(k8sClient.Create(ctx, namespace)).To(Succeed()) - repositoryName = fmt.Sprintf("%s-%d", RepositoryObj, testNumber) + configs, secrets = createTestConfigsAndSecrets(ctx, namespace.GetName()) + + repositoryName := "repository" repositoryObj = &v1alpha1.OCMRepository{ ObjectMeta: metav1.ObjectMeta{ - Namespace: Namespace, + Namespace: namespace.GetName(), Name: repositoryName, }, Spec: v1alpha1.OCMRepositorySpec{ @@ -640,28 +675,46 @@ var _ = Describe("Component Controller", func() { conditions.MarkTrue(repositoryObj, "Ready", "ready", "message") Expect(k8sClient.Status().Update(ctx, repositoryObj)).To(Succeed()) - - testNumber++ }) - AfterEach(func() { - // make sure the repo is still ready + AfterEach(func(ctx SpecContext) { + By("make sure the repo is still ready") conditions.MarkTrue(repositoryObj, "Ready", "ready", "message") Expect(k8sClient.Status().Update(ctx, repositoryObj)).To(Succeed()) cleanupTestConfigsAndSecrets(ctx, configs, secrets) + + By("delete repository") + Expect(k8sClient.Delete(ctx, repositoryObj)).To(Succeed()) + Eventually(func(ctx context.Context) bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(repositoryObj), repositoryObj) + return errors.IsNotFound(err) + }, "15s").WithContext(ctx).Should(BeTrue()) + + By("ensuring no components are left") + Eventually(func(g Gomega, ctx SpecContext) { + components := &v1alpha1.ComponentList{} + g.Expect(k8sClient.List(ctx, components, client.InNamespace(namespace.GetName()))).To(Succeed()) + g.Expect(components.Items).To(HaveLen(0)) + }, "15s").WithContext(ctx).Should(Succeed()) + By("ensuring no snapshots are left") + Eventually(func(g Gomega, ctx SpecContext) { + snapshots := &v1alpha1.SnapshotList{} + g.Expect(k8sClient.List(ctx, snapshots, client.InNamespace(namespace.GetName()))).To(Succeed()) + g.Expect(snapshots.Items).To(HaveLen(0)) + }, "15s").WithContext(ctx).Should(Succeed()) }) - It("component resolves and propagates config from repository", func() { + It("component resolves and propagates config from repository", func(ctx SpecContext) { By("creating a component") component := &v1alpha1.Component{ ObjectMeta: metav1.ObjectMeta{ - Namespace: Namespace, - Name: fmt.Sprintf("%s-%d", ComponentObj, testNumber), + Namespace: namespace.GetName(), + Name: ComponentObj, }, Spec: v1alpha1.ComponentSpec{ RepositoryRef: v1alpha1.ObjectKey{ - Namespace: Namespace, - Name: repositoryName, + Namespace: namespace.GetName(), + Name: repositoryObj.GetName(), }, Component: Component, Semver: "1.0.0", @@ -670,8 +723,8 @@ var _ = Describe("Component Controller", func() { NamespacedObjectKindReference: meta.NamespacedObjectKindReference{ APIVersion: v1alpha1.GroupVersion.String(), Kind: v1alpha1.KindOCMRepository, - Name: repositoryName, - Namespace: Namespace, + Namespace: namespace.GetName(), + Name: repositoryObj.GetName(), }, Policy: v1alpha1.ConfigurationPolicyDoNotPropagate, }, @@ -694,8 +747,8 @@ var _ = Describe("Component Controller", func() { By("checking that the snapshot has been created successfully") Eventually(komega.Object(component), "15s").Should( HaveField("Status.SnapshotRef.Name", Not(BeEmpty()))) - snapshotComponent := &v1alpha1.Snapshot{} - Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: component.GetNamespace(), Name: component.GetSnapshotName()}, snapshotComponent)).To(Succeed()) + snapshot := &v1alpha1.Snapshot{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: component.GetNamespace(), Name: component.GetSnapshotName()}, snapshot)).To(Succeed()) Eventually(komega.Object(component), "15s").Should( HaveField("Status.EffectiveOCMConfig", ConsistOf( @@ -722,7 +775,10 @@ var _ = Describe("Component Controller", func() { By("delete resources manually") Expect(k8sClient.Delete(ctx, component)).To(Succeed()) - Expect(k8sClient.Delete(ctx, snapshotComponent)).To(Succeed()) + Eventually(func(ctx context.Context) bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(snapshot), snapshot) + return errors.IsNotFound(err) + }, "15s").WithContext(ctx).Should(BeTrue()) Eventually(func(ctx context.Context) bool { err := k8sClient.Get(ctx, client.ObjectKeyFromObject(component), component) return errors.IsNotFound(err) @@ -731,7 +787,7 @@ var _ = Describe("Component Controller", func() { }) }) -func createTestConfigsAndSecrets(ctx context.Context) (configs []*corev1.ConfigMap, secrets []*corev1.Secret) { +func createTestConfigsAndSecrets(ctx context.Context, namespace string) (configs []*corev1.ConfigMap, secrets []*corev1.Secret) { const ( Config1 = "config1" Config2 = "config2" @@ -745,7 +801,7 @@ func createTestConfigsAndSecrets(ctx context.Context) (configs []*corev1.ConfigM By("setup configs") config1 := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Namespace: Namespace, + Namespace: namespace, Name: Config1, }, Data: map[string]string{ @@ -774,7 +830,7 @@ sets: config2 := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Namespace: Namespace, + Namespace: namespace, Name: Config2, }, Data: map[string]string{ @@ -803,7 +859,7 @@ sets: config3 := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Namespace: Namespace, + Namespace: namespace, Name: Config3, }, Data: map[string]string{ @@ -833,7 +889,7 @@ sets: By("setup secrets") secret1 := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Namespace: Namespace, + Namespace: namespace, Name: Secret1, }, Data: map[string][]byte{ @@ -857,7 +913,7 @@ consumers: secret2 := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Namespace: Namespace, + Namespace: namespace, Name: Secret2, }, Data: map[string][]byte{ @@ -881,7 +937,7 @@ consumers: secret3 := corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Namespace: Namespace, + Namespace: namespace, Name: Secret3, }, Data: map[string][]byte{ @@ -914,3 +970,11 @@ func cleanupTestConfigsAndSecrets(ctx context.Context, configs []*corev1.ConfigM Expect(k8sClient.Delete(ctx, secret)).To(Succeed()) } } + +func generateNamespace(testName string) string { + replaced := strings.ToLower(strings.Replace(testName, " ", "-", -1)) + if len(replaced) > 63 { + return replaced[:63] + } + return replaced +} diff --git a/internal/controller/component/suite_test.go b/internal/controller/component/suite_test.go index a3e5e645..748f7458 100644 --- a/internal/controller/component/suite_test.go +++ b/internal/controller/component/suite_test.go @@ -25,10 +25,10 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/clientcmd/api" "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -56,6 +56,7 @@ var testEnv *envtest.Environment var zotCmd *exec.Cmd var registry *snapshotPkg.Registry var zotRootDir string +var recorder record.EventRecorder func TestControllers(t *testing.T) { RegisterFailHandler(Fail) @@ -115,28 +116,34 @@ var _ = BeforeSuite(func() { zotCmd, registry = test.SetupRegistry(filepath.Join("..", "..", "..", "bin", "zot-registry"), zotRootDir, "0.0.0.0", "8080") + events := make(chan string) + recorder = &record.FakeRecorder{ + Events: events, + IncludeObject: true, + } + ctx, cancel := context.WithCancel(context.Background()) + DeferCleanup(cancel) + + go func() { + for { + select { + case event := <-events: + GinkgoLogr.Info("Event received", "event", event) + case <-ctx.Done(): + return + } + } + }() + Expect((&Reconciler{ BaseReconciler: &ocm.BaseReconciler{ - Client: k8sClient, - Scheme: testEnv.Scheme, - EventRecorder: &record.FakeRecorder{ - Events: make(chan string, 32), - IncludeObject: true, - }, + Client: k8sClient, + Scheme: testEnv.Scheme, + EventRecorder: recorder, }, Registry: registry, }).SetupWithManager(k8sManager)).To(Succeed()) - ctx, cancel := context.WithCancel(context.Background()) - DeferCleanup(cancel) - - namespace := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: Namespace, - }, - } - Expect(k8sClient.Create(ctx, namespace)).To(Succeed()) - go func() { defer GinkgoRecover() Expect(k8sManager.Start(ctx)).To(Succeed())