diff --git a/internal/controllers/operator_controller.go b/internal/controllers/operator_controller.go index 5d626d54c..c35013118 100644 --- a/internal/controllers/operator_controller.go +++ b/internal/controllers/operator_controller.go @@ -52,6 +52,13 @@ type OperatorReconciler struct { Resolver *solver.DeppySolver } +// bundleDeploymentMetadata serves as an extensible struct holding +// any metadata that is to be added to bundleDeployments. +type bundleDeploymentMetadata struct { + channel string + version string +} + //+kubebuilder:rbac:groups=operators.operatorframework.io,resources=operators,verbs=get;list;watch //+kubebuilder:rbac:groups=operators.operatorframework.io,resources=operators/status,verbs=get;update;patch //+kubebuilder:rbac:groups=operators.operatorframework.io,resources=operators/finalizers,verbs=update @@ -73,7 +80,7 @@ func (r *OperatorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c } reconciledOp := existingOp.DeepCopy() - res, reconcileErr := r.reconcile(ctx, reconciledOp) + res, reconcileErr := r.reconcile(ctx, reconciledOp, l) // Do checks before any Update()s, as Update() may modify the resource structure! updateStatus := !equality.Semantic.DeepEqual(existingOp.Status, reconciledOp.Status) @@ -113,7 +120,7 @@ func checkForUnexpectedFieldChange(a, b operatorsv1alpha1.Operator) bool { // to return different results (e.g. requeue). // //nolint:unparam -func (r *OperatorReconciler) reconcile(ctx context.Context, op *operatorsv1alpha1.Operator) (ctrl.Result, error) { +func (r *OperatorReconciler) reconcile(ctx context.Context, op *operatorsv1alpha1.Operator, log logr.Logger) (ctrl.Result, error) { // validate spec if err := validators.ValidateOperatorSpec(op); err != nil { // Set the TypeInstalled condition to Unknown to indicate that the resolution @@ -172,9 +179,17 @@ func (r *OperatorReconciler) reconcile(ctx context.Context, op *operatorsv1alpha setInstalledStatusConditionFailed(&op.Status.Conditions, err.Error(), op.GetGeneration()) return ctrl.Result{}, err } + + // Get annotations which needs to be added to the bundleDeployment. + // An error here need not re-trigger a reconcile. + bundleDeploymentAnnotations := &bundleDeploymentMetadata{} + if err = bundleDeploymentAnnotations.CompleteBundleDeploymentMetadata(bundleEntity); err != nil { + log.Error(err, "unable to fetch annotations from the resolved bundleEntity") + } + // Ensure a BundleDeployment exists with its bundle source from the bundle // image we just looked up in the solution. - dep := r.generateExpectedBundleDeployment(*op, bundleImage, bundleProvisioner) + dep := r.generateExpectedBundleDeployment(*op, bundleImage, bundleProvisioner, bundleDeploymentAnnotations) if err := r.ensureBundleDeployment(ctx, dep); err != nil { // originally Reason: operatorsv1alpha1.ReasonInstallationFailed op.Status.InstalledBundleResource = "" @@ -260,7 +275,7 @@ func (r *OperatorReconciler) getBundleEntityFromSolution(solution *solver.Soluti return nil, fmt.Errorf("entity for package %q not found in solution", packageName) } -func (r *OperatorReconciler) generateExpectedBundleDeployment(o operatorsv1alpha1.Operator, bundlePath string, bundleProvisioner string) *unstructured.Unstructured { +func (r *OperatorReconciler) generateExpectedBundleDeployment(o operatorsv1alpha1.Operator, bundlePath string, bundleProvisioner string, annotations *bundleDeploymentMetadata) *unstructured.Unstructured { // We use unstructured here to avoid problems of serializing default values when sending patches to the apiserver. // If you use a typed object, any default values from that struct get serialized into the JSON patch, which could // cause unrelated fields to be patched back to the default value even though that isn't the intention. Using an @@ -272,6 +287,10 @@ func (r *OperatorReconciler) generateExpectedBundleDeployment(o operatorsv1alpha "kind": rukpakv1alpha1.BundleDeploymentKind, "metadata": map[string]interface{}{ "name": o.GetName(), + "annotations": map[string]string{ + "operator_version": annotations.version, + "operator_channel": annotations.channel, + }, }, "spec": map[string]interface{}{ // TODO: Don't assume plain provisioner @@ -459,3 +478,27 @@ func operatorRequestsForCatalog(ctx context.Context, c client.Reader, logger log return requests } } + +// Complete fills in the annotations from the information received from +// bundleEntities. +func (bdm *bundleDeploymentMetadata) CompleteBundleDeploymentMetadata(entity *entity.BundleEntity) error { + var errs []error + + channel, err := entity.ChannelName() + if err != nil || channel == "" { + errs = append(errs, fmt.Errorf("unable to find the channel name from resolved entity: %v", err)) + channel = "unknown" + } + bdm.channel = channel + + var version string + semverVer, err := entity.Version() + version = semverVer.String() + if err != nil || version == "" { + errs = append(errs, fmt.Errorf("unable to find the version of operator to be installed from resolved entity: %v", err)) + version = "unknown" + } + bdm.version = version + + return utilerrors.NewAggregate(errs) +} diff --git a/internal/controllers/operator_controller_test.go b/internal/controllers/operator_controller_test.go index 8cf7b1d5f..00ec2ec0d 100644 --- a/internal/controllers/operator_controller_test.go +++ b/internal/controllers/operator_controller_test.go @@ -162,6 +162,9 @@ var _ = Describe("Operator Controller Test", func() { Expect(bd.Spec.Template.Spec.Source.Type).To(Equal(rukpakv1alpha1.SourceTypeImage)) 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("verifying annotations") + verifyBundleDeploymentAnnotations(bd) }) It("sets the resolvedBundleResource status field", func() { Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) @@ -243,6 +246,9 @@ 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("verifying annotations") + verifyBundleDeploymentAnnotations(bd) + By("Checking the status fields") Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) Expect(operator.Status.InstalledBundleResource).To(Equal("")) @@ -555,6 +561,9 @@ var _ = Describe("Operator Controller Test", func() { Expect(bd.Spec.Template.Spec.Source.Type).To(Equal(rukpakv1alpha1.SourceTypeImage)) 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("verifying annotations") + verifyBundleDeploymentAnnotations(bd) }) It("sets the resolvedBundleResource status field", func() { Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) @@ -1081,6 +1090,12 @@ func verifyConditionsInvariants(op *operatorsv1alpha1.Operator) { } } +func verifyBundleDeploymentAnnotations(bd *rukpakv1alpha1.BundleDeployment) { + annotations := bd.GetAnnotations() + Expect(annotations["operator_channel"]).To(BeEquivalentTo("beta")) + Expect(annotations["operator_version"]).To(BeEquivalentTo("0.47.0")) +} + var testEntitySource = input.NewCacheQuerier(map[deppy.Identifier]input.Entity{ "operatorhub/prometheus/0.37.0": *input.NewEntity("operatorhub/prometheus/0.37.0", map[string]string{ "olm.bundle.path": `"quay.io/operatorhubio/prometheus@sha256:3e281e587de3d03011440685fc4fb782672beab044c1ebadc42788ce05a21c35"`,