From 63a73c8c7a5bb2cbec5be9319621823fa073e9cd Mon Sep 17 00:00:00 2001 From: Joaquim Moreno Prusi Date: Mon, 15 May 2023 16:56:43 +0200 Subject: [PATCH 1/2] Extends status conditions and fields This commits adds a new status conditions: "Resolved" that represent if the Operator-Controller was able to resolv the desired operator image or not. Also, extends the status with a new field: "resolvedBundleResource" that contains the resolved operator image. Signed-off-by: Joaquim Moreno Prusi --- api/v1alpha1/operator_types.go | 15 ++++++++++++--- .../operators.operatorframework.io_operators.yaml | 2 ++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/api/v1alpha1/operator_types.go b/api/v1alpha1/operator_types.go index 409abc5bd0..6c66a481f1 100644 --- a/api/v1alpha1/operator_types.go +++ b/api/v1alpha1/operator_types.go @@ -46,34 +46,43 @@ type OperatorSpec struct { const ( // TODO(user): add more Types, here and into init() - TypeReady = "Ready" + TypeReady = "Ready" + TypeResolved = "Resolved" - ReasonInstallationSucceeded = "InstallationSucceeded" - ReasonResolutionFailed = "ResolutionFailed" ReasonBundleLookupFailed = "BundleLookupFailed" ReasonInstallationFailed = "InstallationFailed" ReasonInstallationStatusUnknown = "InstallationStatusUnknown" + ReasonInstallationSucceeded = "InstallationSucceeded" ReasonInvalidSpec = "InvalidSpec" + ReasonResolutionFailed = "ResolutionFailed" + ReasonResolutionUnknown = "ResolutionUnknown" + ReasonSuccess = "Success" ) func init() { // TODO(user): add Types from above operatorutil.ConditionTypes = append(operatorutil.ConditionTypes, TypeReady, + TypeResolved, ) // TODO(user): add Reasons from above operatorutil.ConditionReasons = append(operatorutil.ConditionReasons, ReasonInstallationSucceeded, ReasonResolutionFailed, + ReasonResolutionUnknown, ReasonBundleLookupFailed, ReasonInstallationFailed, ReasonInstallationStatusUnknown, ReasonInvalidSpec, + ReasonSuccess, ) } // OperatorStatus defines the observed state of Operator type OperatorStatus struct { + // +optional + ResolvedBundleResource string `json:"resolvedBundleResource,omitempty"` + // +patchMergeKey=type // +patchStrategy=merge // +listType=map diff --git a/config/crd/bases/operators.operatorframework.io_operators.yaml b/config/crd/bases/operators.operatorframework.io_operators.yaml index 6cce301f70..1a43f50144 100644 --- a/config/crd/bases/operators.operatorframework.io_operators.yaml +++ b/config/crd/bases/operators.operatorframework.io_operators.yaml @@ -131,6 +131,8 @@ spec: x-kubernetes-list-map-keys: - type x-kubernetes-list-type: map + resolvedBundleResource: + type: string type: object type: object served: true From c6a638a8e22c8d3393c6ea80d58e64e9102ea595 Mon Sep 17 00:00:00 2001 From: Joaquim Moreno Prusi Date: Mon, 15 May 2023 17:05:16 +0200 Subject: [PATCH 2/2] Implements logic for new status cond. and fields Signed-off-by: Joaquim Moreno Prusi --- controllers/operator_controller.go | 44 +++++++ controllers/operator_controller_test.go | 152 ++++++++++++++++++++++++ test/e2e/install_test.go | 22 +++- 3 files changed, 212 insertions(+), 6 deletions(-) diff --git a/controllers/operator_controller.go b/controllers/operator_controller.go index 59e531e3e7..25cbddbebb 100644 --- a/controllers/operator_controller.go +++ b/controllers/operator_controller.go @@ -113,6 +113,16 @@ func (r *OperatorReconciler) reconcile(ctx context.Context, op *operatorsv1alpha Message: err.Error(), ObservedGeneration: op.GetGeneration(), }) + // Set the TypeResolved condition to Unknown to indicate that the resolution + // hasn't been attempted yet, due to the spec being invalid. + op.Status.ResolvedBundleResource = "" + apimeta.SetStatusCondition(&op.Status.Conditions, metav1.Condition{ + Type: operatorsv1alpha1.TypeResolved, + Status: metav1.ConditionUnknown, + Reason: operatorsv1alpha1.ReasonResolutionUnknown, + Message: "validation has not been attempted as spec is invalid", + ObservedGeneration: op.GetGeneration(), + }) return ctrl.Result{}, nil } // run resolution @@ -125,6 +135,14 @@ func (r *OperatorReconciler) reconcile(ctx context.Context, op *operatorsv1alpha Message: err.Error(), ObservedGeneration: op.GetGeneration(), }) + op.Status.ResolvedBundleResource = "" + apimeta.SetStatusCondition(&op.Status.Conditions, metav1.Condition{ + Type: operatorsv1alpha1.TypeResolved, + Status: metav1.ConditionFalse, + Reason: operatorsv1alpha1.ReasonResolutionFailed, + Message: err.Error(), + ObservedGeneration: op.GetGeneration(), + }) return ctrl.Result{}, err } @@ -139,6 +157,14 @@ func (r *OperatorReconciler) reconcile(ctx context.Context, op *operatorsv1alpha Message: err.Error(), ObservedGeneration: op.GetGeneration(), }) + op.Status.ResolvedBundleResource = "" + apimeta.SetStatusCondition(&op.Status.Conditions, metav1.Condition{ + Type: operatorsv1alpha1.TypeResolved, + Status: metav1.ConditionFalse, + Reason: operatorsv1alpha1.ReasonResolutionFailed, + Message: err.Error(), + ObservedGeneration: op.GetGeneration(), + }) return ctrl.Result{}, err } @@ -152,9 +178,27 @@ func (r *OperatorReconciler) reconcile(ctx context.Context, op *operatorsv1alpha Message: err.Error(), ObservedGeneration: op.GetGeneration(), }) + op.Status.ResolvedBundleResource = "" + apimeta.SetStatusCondition(&op.Status.Conditions, metav1.Condition{ + Type: operatorsv1alpha1.TypeResolved, + Status: metav1.ConditionFalse, + Reason: operatorsv1alpha1.ReasonResolutionFailed, + Message: err.Error(), + ObservedGeneration: op.GetGeneration(), + }) return ctrl.Result{}, err } + // Now we can set the Resolved Condition, and the resolvedBundleSource field to the bundleImage value. + op.Status.ResolvedBundleResource = bundleImage + apimeta.SetStatusCondition(&op.Status.Conditions, metav1.Condition{ + Type: operatorsv1alpha1.TypeResolved, + Status: metav1.ConditionTrue, + Reason: operatorsv1alpha1.ReasonSuccess, + Message: fmt.Sprintf("resolved to %q", bundleImage), + ObservedGeneration: op.GetGeneration(), + }) + // Ensure a BundleDeployment exists with its bundle source from the bundle // image we just looked up in the solution. dep := r.generateExpectedBundleDeployment(*op, bundleImage) diff --git a/controllers/operator_controller_test.go b/controllers/operator_controller_test.go index c3f4c9c3d8..9cabea9e25 100644 --- a/controllers/operator_controller_test.go +++ b/controllers/operator_controller_test.go @@ -73,12 +73,20 @@ var _ = Describe("Operator Controller Test", func() { By("fetching updated operator after reconcile") Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) + By("Checking the status fields") + Expect(operator.Status.ResolvedBundleResource).To(Equal("")) + By("checking the expected conditions") cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady) Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(metav1.ConditionFalse)) Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionFailed)) Expect(cond.Message).To(Equal(fmt.Sprintf("package '%s' not found", pkgName))) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionFalse)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionFailed)) + Expect(cond.Message).To(Equal(fmt.Sprintf("package '%s' not found", pkgName))) }) }) When("the operator specifies a version that does not exist", func() { @@ -105,12 +113,20 @@ var _ = Describe("Operator Controller Test", func() { By("fetching updated operator after reconcile") Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) + By("Checking the status fields") + Expect(operator.Status.ResolvedBundleResource).To(Equal("")) + By("checking the expected conditions") cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady) Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(metav1.ConditionFalse)) Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionFailed)) Expect(cond.Message).To(Equal(fmt.Sprintf("package '%s' at version '0.50.0' not found", pkgName))) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionFalse)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionFailed)) + Expect(cond.Message).To(Equal(fmt.Sprintf("package '%s' at version '0.50.0' not found", pkgName))) }) }) When("the operator specifies a valid available package", func() { @@ -145,12 +161,20 @@ var _ = Describe("Operator Controller Test", func() { Expect(bd.Spec.Template.Spec.Source.Image).NotTo(BeNil()) Expect(bd.Spec.Template.Spec.Source.Image.Ref).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) }) + It("sets the resolvedBundleResource status field", func() { + Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + }) It("sets the status on operator", func() { cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady) Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) Expect(cond.Message).To(ContainSubstring("waiting for BundleDeployment")) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionTrue)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) + Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed\"")) }) }) When("the expected BundleDeployment already exists", func() { @@ -214,12 +238,20 @@ var _ = Describe("Operator Controller Test", func() { Expect(bd.Spec.Template.Spec.Source.Image).NotTo(BeNil()) Expect(bd.Spec.Template.Spec.Source.Image.Ref).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + By("Checking the status fields") + Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + By("checking the expected status conditions") cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady) Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) Expect(cond.Message).To(Equal(fmt.Sprintf("waiting for BundleDeployment %q status to be updated. BundleDeployment conditions out of date.", bd.Name))) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionTrue)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) + Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed\"")) }) }) @@ -253,12 +285,20 @@ var _ = Describe("Operator Controller Test", func() { err = cl.Get(ctx, opKey, op) Expect(err).NotTo(HaveOccurred()) + By("Checking the status fields") + Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + By("checking the expected conditions") cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeReady) Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) Expect(cond.Message).To(Equal(fmt.Sprintf("could not determine the state of BundleDeployment %s", bd.Name))) + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionTrue)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) + Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed\"")) }) It("verify operator status when `HasValidBundle` condition of rukpak is false", func() { @@ -283,12 +323,20 @@ var _ = Describe("Operator Controller Test", func() { err = cl.Get(ctx, opKey, op) Expect(err).NotTo(HaveOccurred()) + By("Checking the status fields") + Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + By("checking the expected conditions") cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeReady) Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(metav1.ConditionFalse)) Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationFailed)) Expect(cond.Message).To(ContainSubstring(`failed to unpack`)) + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionTrue)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) + Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed\"")) }) It("verify operator status when `InstallReady` condition of rukpak is false", func() { @@ -313,12 +361,20 @@ var _ = Describe("Operator Controller Test", func() { err = cl.Get(ctx, opKey, op) Expect(err).NotTo(HaveOccurred()) + By("Checking the status fields") + Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + By("checking the expected conditions") cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeReady) Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(metav1.ConditionFalse)) Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationFailed)) Expect(cond.Message).To(ContainSubstring(`failed to install`)) + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionTrue)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) + Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed\"")) }) It("verify operator status when `InstallReady` condition of rukpak is true", func() { @@ -343,12 +399,20 @@ var _ = Describe("Operator Controller Test", func() { err = cl.Get(ctx, opKey, op) Expect(err).NotTo(HaveOccurred()) + By("Checking the status fields") + Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + By("checking the expected conditions") cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeReady) Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(metav1.ConditionTrue)) Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationSucceeded)) Expect(cond.Message).To(ContainSubstring(`install was successful`)) + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionTrue)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) + Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed\"")) }) It("verify any other unknown status of bundledeployment", func() { @@ -380,12 +444,20 @@ var _ = Describe("Operator Controller Test", func() { err = cl.Get(ctx, opKey, op) Expect(err).NotTo(HaveOccurred()) + By("Checking the status fields") + Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + By("checking the expected conditions") cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeReady) Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) Expect(cond.Message).To(Equal(fmt.Sprintf("could not determine the state of BundleDeployment %s", bd.Name))) + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionTrue)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) + Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed\"")) }) It("verify operator status when bundleDeployment installation status is unknown", func() { @@ -410,12 +482,20 @@ var _ = Describe("Operator Controller Test", func() { err = cl.Get(ctx, opKey, op) Expect(err).NotTo(HaveOccurred()) + By("Checking the status fields") + Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + By("checking the expected conditions") cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeReady) Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) Expect(cond.Message).To(Equal(fmt.Sprintf("could not determine the state of BundleDeployment %s", bd.Name))) + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionTrue)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) + Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed\"")) }) }) @@ -468,12 +548,20 @@ var _ = Describe("Operator Controller Test", func() { Expect(bd.Spec.Template.Spec.Source.Image).NotTo(BeNil()) Expect(bd.Spec.Template.Spec.Source.Image.Ref).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) }) + It("sets the resolvedBundleResource status field", func() { + Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + }) It("sets resolution to unknown status", func() { cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady) Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) Expect(cond.Message).To(ContainSubstring("waiting for BundleDeployment")) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionTrue)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) + Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed\"")) }) }) }) @@ -497,12 +585,20 @@ var _ = Describe("Operator Controller Test", func() { By("fetching updated operator after reconcile") Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) + By("Checking the status fields") + Expect(operator.Status.ResolvedBundleResource).To(Equal("")) + By("checking the expected conditions") cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady) Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(metav1.ConditionFalse)) Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonBundleLookupFailed)) Expect(cond.Message).To(ContainSubstring(`error determining bundle path for entity`)) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionFalse)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionFailed)) + Expect(cond.Message).To(ContainSubstring(`error determining bundle path for entity`)) }) }) When("the operator specifies a duplicate package", func() { @@ -541,12 +637,20 @@ var _ = Describe("Operator Controller Test", func() { By("fetching updated operator after reconcile") Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) + By("Checking the status fields") + Expect(operator.Status.ResolvedBundleResource).To(Equal("")) + By("checking the expected conditions") cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady) Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(metav1.ConditionFalse)) Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionFailed)) Expect(cond.Message).To(Equal(`duplicate identifier "required package prometheus" in input`)) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionFalse)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionFailed)) + Expect(cond.Message).To(Equal(`duplicate identifier "required package prometheus" in input`)) }) }) When("the existing operator status is based on bundleDeployment", func() { @@ -623,12 +727,20 @@ var _ = Describe("Operator Controller Test", func() { By("fetching updated operator after reconcile") Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) + By("Checking the status fields") + Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + By("checking the expected conditions") cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady) Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) Expect(cond.Message).To(ContainSubstring("waiting for BundleDeployment")) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionTrue)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) + Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed\"")) By("fetching the bundled deployment") bd := &rukpakv1alpha1.BundleDeployment{} @@ -668,12 +780,20 @@ var _ = Describe("Operator Controller Test", func() { By("fetching updated operator after reconcile") Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) + By("Checking the status fields") + Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + By("checking the expected conditions") cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady) Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) Expect(cond.Message).To(ContainSubstring("waiting for BundleDeployment")) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionTrue)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) + Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed\"")) By("fetching the bundled deployment") bd := &rukpakv1alpha1.BundleDeployment{} @@ -714,12 +834,20 @@ var _ = Describe("Operator Controller Test", func() { By("fetching updated operator after reconcile") Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) + By("Checking the status fields") + Expect(operator.Status.ResolvedBundleResource).To(Equal("")) + By("checking the expected conditions") cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady) Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(metav1.ConditionFalse)) Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionFailed)) Expect(cond.Message).To(Equal(fmt.Sprintf("package '%s' at version '%s' in channel '%s' not found", pkgName, pkgVer, pkgChan))) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionFalse)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionFailed)) + Expect(cond.Message).To(Equal(fmt.Sprintf("package '%s' at version '%s' in channel '%s' not found", pkgName, pkgVer, pkgChan))) }) }) When("the operator specifies a package in a channel that does not exist", func() { @@ -748,12 +876,20 @@ var _ = Describe("Operator Controller Test", func() { By("fetching updated operator after reconcile") Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) + By("Checking the status fields") + Expect(operator.Status.ResolvedBundleResource).To(Equal("")) + By("checking the expected conditions") cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady) Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(metav1.ConditionFalse)) Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionFailed)) Expect(cond.Message).To(Equal(fmt.Sprintf("package '%s' in channel '%s' not found", pkgName, pkgChan))) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionFalse)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionFailed)) + Expect(cond.Message).To(Equal(fmt.Sprintf("package '%s' in channel '%s' not found", pkgName, pkgChan))) }) }) When("the operator specifies a package version that does not exist in the channel", func() { @@ -785,12 +921,20 @@ var _ = Describe("Operator Controller Test", func() { By("fetching updated operator after reconcile") Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) + By("Checking the status fields") + Expect(operator.Status.ResolvedBundleResource).To(Equal("")) + By("checking the expected conditions") cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady) Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(metav1.ConditionFalse)) Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionFailed)) Expect(cond.Message).To(Equal(fmt.Sprintf("package '%s' at version '%s' in channel '%s' not found", pkgName, pkgVer, pkgChan))) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionFalse)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionFailed)) + Expect(cond.Message).To(Equal(fmt.Sprintf("package '%s' at version '%s' in channel '%s' not found", pkgName, pkgVer, pkgChan))) }) }) AfterEach(func() { @@ -840,12 +984,20 @@ var _ = Describe("Operator Controller Test", func() { By("fetching updated operator after reconcile") Expect(fakeClient.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) + By("Checking the status fields") + Expect(operator.Status.ResolvedBundleResource).To(Equal("")) + By("checking the expected conditions") cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady) Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(metav1.ConditionFalse)) Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInvalidSpec)) Expect(cond.Message).To(Equal("invalid .spec.version: Invalid character(s) found in prerelease \"123abc_def\"")) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionUnknown)) + Expect(cond.Message).To(Equal("validation has not been attempted as spec is invalid")) }) }) }) diff --git a/test/e2e/install_test.go b/test/e2e/install_test.go index a6a12ed4d6..1d7673dd97 100644 --- a/test/e2e/install_test.go +++ b/test/e2e/install_test.go @@ -5,15 +5,15 @@ import ( "fmt" "time" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/rand" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" catalogd "github.com/operator-framework/catalogd/pkg/apis/core/v1beta1" operatorv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1" + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/rand" ) const ( @@ -70,8 +70,18 @@ var _ = Describe("Operator Install", func() { Eventually(func(g Gomega) { err = c.Get(ctx, types.NamespacedName{Name: operator.Name}, operator) g.Expect(err).ToNot(HaveOccurred()) - g.Expect(len(operator.Status.Conditions)).To(Equal(1)) - g.Expect(operator.Status.Conditions[0].Message).To(Equal("install was successful")) + g.Expect(len(operator.Status.Conditions)).To(Equal(2)) + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorv1alpha1.TypeReady) + g.Expect(cond).ToNot(BeNil()) + g.Expect(cond.Status).To(Equal(metav1.ConditionTrue)) + g.Expect(cond.Reason).To(Equal(operatorv1alpha1.ReasonInstallationSucceeded)) + g.Expect(cond.Message).To(Equal("install was successful")) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorv1alpha1.TypeResolved) + g.Expect(cond).ToNot(BeNil()) + g.Expect(cond.Status).To(Equal(metav1.ConditionTrue)) + g.Expect(cond.Reason).To(Equal(operatorv1alpha1.ReasonSuccess)) + g.Expect(cond.Message).To(ContainSubstring("resolved to")) + g.Expect(operator.Status.ResolvedBundleResource).ToNot(BeEmpty()) }).WithTimeout(defaultTimeout).WithPolling(defaultPoll).Should(Succeed()) By("eventually installing the package successfully")