Skip to content

Commit

Permalink
fixup! ✨ Plumb the Extension API
Browse files Browse the repository at this point in the history
Signed-off-by: Todd Short <tshort@redhat.com>
  • Loading branch information
tmshort committed Feb 7, 2024
1 parent b7da141 commit 4324b88
Show file tree
Hide file tree
Showing 17 changed files with 652 additions and 3,087 deletions.
12 changes: 0 additions & 12 deletions api/v1alpha1/clusterextension_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,18 +170,6 @@ func (r *ClusterExtension) GetPackageSpec() *ExtensionSourcePackage {
return p
}

func (r *ClusterExtension) GetGeneration() int64 {
return r.ObjectMeta.GetGeneration()
}

func (r *ClusterExtension) GetConditions() *[]metav1.Condition {
return &r.Status.Conditions
}

func (r *ClusterExtension) SetInstalledBundleResource(s string) {
r.Status.InstalledBundleResource = s
}

func (r *ClusterExtension) GetUID() types.UID {
return r.ObjectMeta.GetUID()
}
Expand Down
12 changes: 0 additions & 12 deletions api/v1alpha1/extension_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,18 +132,6 @@ func (r *Extension) GetPackageSpec() *ExtensionSourcePackage {
return r.Spec.Source.Package.DeepCopy()
}

func (r *Extension) GetGeneration() int64 {
return r.ObjectMeta.GetGeneration()
}

func (r *Extension) GetConditions() *[]metav1.Condition {
return &r.Status.Conditions
}

func (r *Extension) SetInstalledBundleResource(s string) {
r.Status.InstalledBundleResource = s
}

func (r *Extension) GetUID() types.UID {
return r.ObjectMeta.GetUID()
}
Expand Down
29 changes: 0 additions & 29 deletions internal/controllers/bundle_provider.go

This file was deleted.

200 changes: 192 additions & 8 deletions internal/controllers/clusterextension_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ package controllers
import (
"context"
"fmt"
"strings"

"github.com/go-logr/logr"
catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1"
"github.com/operator-framework/deppy/pkg/deppy"
"github.com/operator-framework/deppy/pkg/deppy/solver"
"github.com/operator-framework/operator-registry/alpha/declcfg"
rukpakv1alpha2 "github.com/operator-framework/rukpak/api/v1alpha2"
"k8s.io/apimachinery/pkg/api/equality"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -63,12 +66,12 @@ type ClusterExtensionReconciler struct {
//+kubebuilder:rbac:groups=catalogd.operatorframework.io,resources=catalogmetadata,verbs=list;watch

func (r *ClusterExtensionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
l := log.FromContext(ctx).WithName("clusterextension-controller")
l := log.FromContext(ctx).WithName("operator-controller")
l.V(1).Info("starting")
defer l.V(1).Info("ending")

var existingExt = &ocv1alpha1.ClusterExtension{}
if err := r.Client.Get(ctx, req.NamespacedName, existingExt); err != nil {
if err := r.Get(ctx, req.NamespacedName, existingExt); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}

Expand All @@ -78,10 +81,10 @@ func (r *ClusterExtensionReconciler) Reconcile(ctx context.Context, req ctrl.Req
// Do checks before any Update()s, as Update() may modify the resource structure!
updateStatus := !equality.Semantic.DeepEqual(existingExt.Status, reconciledExt.Status)
updateFinalizers := !equality.Semantic.DeepEqual(existingExt.Finalizers, reconciledExt.Finalizers)
unexpectedFieldsChanged := r.checkForUnexpectedFieldChange(*existingExt, *reconciledExt)
unexpectedFieldsChanged := checkForUnexpectedFieldChange(*existingExt, *reconciledExt)

if updateStatus {
if updateErr := r.Client.Status().Update(ctx, reconciledExt); updateErr != nil {
if updateErr := r.Status().Update(ctx, reconciledExt); updateErr != nil {
return res, utilerrors.NewAggregate([]error{reconcileErr, updateErr})
}
}
Expand All @@ -91,7 +94,7 @@ func (r *ClusterExtensionReconciler) Reconcile(ctx context.Context, req ctrl.Req
}

if updateFinalizers {
if updateErr := r.Client.Update(ctx, reconciledExt); updateErr != nil {
if updateErr := r.Update(ctx, reconciledExt); updateErr != nil {
return res, utilerrors.NewAggregate([]error{reconcileErr, updateErr})
}
}
Expand All @@ -100,7 +103,7 @@ func (r *ClusterExtensionReconciler) Reconcile(ctx context.Context, req ctrl.Req
}

// Compare resources - ignoring status & metadata.finalizers
func (*ClusterExtensionReconciler) checkForUnexpectedFieldChange(a, b ocv1alpha1.ClusterExtension) bool {
func checkForUnexpectedFieldChange(a, b ocv1alpha1.ClusterExtension) bool {
a.Status, b.Status = ocv1alpha1.ClusterExtensionStatus{}, ocv1alpha1.ClusterExtensionStatus{}
a.Finalizers, b.Finalizers = []string{}, []string{}
return !equality.Semantic.DeepEqual(a, b)
Expand All @@ -115,7 +118,7 @@ func (*ClusterExtensionReconciler) checkForUnexpectedFieldChange(a, b ocv1alpha1
//nolint:unparam
func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alpha1.ClusterExtension) (ctrl.Result, error) {
// validate spec
if err := validators.ValidateSpec(ext); err != nil {
if err := validators.ValidateClusterExtensionSpec(ext); err != nil {
// Set the TypeInstalled condition to Unknown to indicate that the resolution
// hasn't been attempted yet, due to the spec being invalid.
ext.Status.InstalledBundleResource = ""
Expand Down Expand Up @@ -187,7 +190,7 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alp
// Ensure a BundleDeployment exists with its bundle source from the bundle
// image we just looked up in the solution.
dep := r.GenerateExpectedBundleDeployment(*ext, bundle.Image, bundleProvisioner)
if err := ensureBundleDeployment(ctx, r.Client, dep); err != nil {
if err := r.ensureBundleDeployment(ctx, dep); err != nil {
// originally Reason: ocv1alpha1.ReasonInstallationFailed
ext.Status.InstalledBundleResource = ""
setInstalledStatusConditionFailed(&ext.Status.Conditions, err.Error(), ext.GetGeneration())
Expand Down Expand Up @@ -232,6 +235,133 @@ func (r *ClusterExtensionReconciler) variables(ctx context.Context) ([]deppy.Var
return GenerateVariables(allBundles, internal.ClusterExtensionArrayToInterface(clusterExtensionList.Items), bundleDeploymentList.Items)
}

func mapBDStatusToInstalledCondition(existingTypedBundleDeployment *rukpakv1alpha2.BundleDeployment, ext *ocv1alpha1.ClusterExtension) {
bundleDeploymentReady := apimeta.FindStatusCondition(existingTypedBundleDeployment.Status.Conditions, rukpakv1alpha2.TypeInstalled)
if bundleDeploymentReady == nil {
ext.Status.InstalledBundleResource = ""
setInstalledStatusConditionUnknown(&ext.Status.Conditions, "bundledeployment status is unknown", ext.GetGeneration())
return
}

if bundleDeploymentReady.Status != metav1.ConditionTrue {
ext.Status.InstalledBundleResource = ""
setInstalledStatusConditionFailed(
&ext.Status.Conditions,
fmt.Sprintf("bundledeployment not ready: %s", bundleDeploymentReady.Message),
ext.GetGeneration(),
)
return
}

bundleDeploymentSource := existingTypedBundleDeployment.Spec.Source
switch bundleDeploymentSource.Type {
case rukpakv1alpha2.SourceTypeImage:
ext.Status.InstalledBundleResource = bundleDeploymentSource.Image.Ref
setInstalledStatusConditionSuccess(
&ext.Status.Conditions,
fmt.Sprintf("installed from %q", bundleDeploymentSource.Image.Ref),
ext.GetGeneration(),
)
case rukpakv1alpha2.SourceTypeGit:
resource := bundleDeploymentSource.Git.Repository + "@" + bundleDeploymentSource.Git.Ref.Commit
ext.Status.InstalledBundleResource = resource
setInstalledStatusConditionSuccess(
&ext.Status.Conditions,
fmt.Sprintf("installed from %q", resource),
ext.GetGeneration(),
)
default:
ext.Status.InstalledBundleResource = ""
setInstalledStatusConditionUnknown(
&ext.Status.Conditions,
fmt.Sprintf("unknown bundledeployment source type %q", bundleDeploymentSource.Type),
ext.GetGeneration(),
)
}
}

// setDeprecationStatus will set the appropriate deprecation statuses for a ClusterExtension
// based on the provided bundle
func SetDeprecationStatus(ext *ocv1alpha1.ClusterExtension, bundle *catalogmetadata.Bundle) {
// reset conditions to false
conditionTypes := []string{
ocv1alpha1.TypeDeprecated,
ocv1alpha1.TypePackageDeprecated,
ocv1alpha1.TypeChannelDeprecated,
ocv1alpha1.TypeBundleDeprecated,
}

for _, conditionType := range conditionTypes {
apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{
Type: conditionType,
Reason: ocv1alpha1.ReasonDeprecated,
Status: metav1.ConditionFalse,
Message: "",
ObservedGeneration: ext.Generation,
})
}

// There are two early return scenarios here:
// 1) The bundle is not deprecated (i.e bundle deprecations)
// AND there are no other deprecations associated with the bundle
// 2) The bundle is not deprecated, there are deprecations associated
// with the bundle (i.e at least one channel the bundle is present in is deprecated OR whole package is deprecated),
// and the ClusterExtension does not specify a channel. This is because the channel deprecations
// are a loose deprecation coupling on the bundle. A ClusterExtension installation is only
// considered deprecated by a channel deprecation when a deprecated channel is specified via
// the spec.channel field.
if (!bundle.IsDeprecated() && !bundle.HasDeprecation()) || (!bundle.IsDeprecated() && ext.Spec.Channel == "") {
return
}

deprecationMessages := []string{}

for _, deprecation := range bundle.Deprecations {
switch deprecation.Reference.Schema {
case declcfg.SchemaPackage:
apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{
Type: ocv1alpha1.TypePackageDeprecated,
Reason: ocv1alpha1.ReasonDeprecated,
Status: metav1.ConditionTrue,
Message: deprecation.Message,
ObservedGeneration: ext.Generation,
})
case declcfg.SchemaChannel:
if ext.Spec.Channel != deprecation.Reference.Name {
continue
}

apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{
Type: ocv1alpha1.TypeChannelDeprecated,
Reason: ocv1alpha1.ReasonDeprecated,
Status: metav1.ConditionTrue,
Message: deprecation.Message,
ObservedGeneration: ext.Generation,
})
case declcfg.SchemaBundle:
apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{
Type: ocv1alpha1.TypeBundleDeprecated,
Reason: ocv1alpha1.ReasonDeprecated,
Status: metav1.ConditionTrue,
Message: deprecation.Message,
ObservedGeneration: ext.Generation,
})
}

deprecationMessages = append(deprecationMessages, deprecation.Message)
}

if len(deprecationMessages) > 0 {
apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{
Type: ocv1alpha1.TypeDeprecated,
Reason: ocv1alpha1.ReasonDeprecated,
Status: metav1.ConditionTrue,
Message: strings.Join(deprecationMessages, ";"),
ObservedGeneration: ext.Generation,
})
}
}

func (r *ClusterExtensionReconciler) bundleFromSolution(selection []deppy.Variable, packageName string) (*catalogmetadata.Bundle, error) {
for _, variable := range selection {
switch v := variable.(type) {
Expand Down Expand Up @@ -289,6 +419,7 @@ func (r *ClusterExtensionReconciler) GenerateExpectedBundleDeployment(o ocv1alph
return bd
}

// SetupWithManager sets up the controller with the Manager.
func (r *ClusterExtensionReconciler) SetupWithManager(mgr ctrl.Manager) error {
err := ctrl.NewControllerManagedBy(mgr).
For(&ocv1alpha1.ClusterExtension{}).
Expand All @@ -303,6 +434,59 @@ func (r *ClusterExtensionReconciler) SetupWithManager(mgr ctrl.Manager) error {
return nil
}

func (r *ClusterExtensionReconciler) ensureBundleDeployment(ctx context.Context, desiredBundleDeployment *unstructured.Unstructured) error {
// TODO: what if there happens to be an unrelated BD with the same name as the ClusterExtension?
// we should probably also check to see if there's an owner reference and/or a label set
// that we expect only to ever be used by the operator-controller. That way, we don't
// automatically and silently adopt and change a BD that the user doens't intend to be
// owned by the ClusterExtension.
existingBundleDeployment, err := r.existingBundleDeploymentUnstructured(ctx, desiredBundleDeployment.GetName())
if client.IgnoreNotFound(err) != nil {
return err
}

// If the existing BD already has everything that the desired BD has, no need to contact the API server.
// Make sure the status of the existingBD from the server is as expected.
if equality.Semantic.DeepDerivative(desiredBundleDeployment, existingBundleDeployment) {
*desiredBundleDeployment = *existingBundleDeployment
return nil
}

return r.Client.Patch(ctx, desiredBundleDeployment, client.Apply, client.ForceOwnership, client.FieldOwner("operator-controller"))
}

func (r *ClusterExtensionReconciler) existingBundleDeploymentUnstructured(ctx context.Context, name string) (*unstructured.Unstructured, error) {
existingBundleDeployment := &rukpakv1alpha2.BundleDeployment{}
err := r.Client.Get(ctx, types.NamespacedName{Name: name}, existingBundleDeployment)
if err != nil {
return nil, err
}
existingBundleDeployment.APIVersion = rukpakv1alpha2.GroupVersion.String()
existingBundleDeployment.Kind = rukpakv1alpha2.BundleDeploymentKind
unstrExistingBundleDeploymentObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(existingBundleDeployment)
if err != nil {
return nil, err
}
return &unstructured.Unstructured{Object: unstrExistingBundleDeploymentObj}, nil
}

// mapBundleMediaTypeToBundleProvisioner maps an olm.bundle.mediatype property to a
// rukpak bundle provisioner class name that is capable of unpacking the bundle type
func mapBundleMediaTypeToBundleProvisioner(mediaType string) (string, error) {
switch mediaType {
case catalogmetadata.MediaTypePlain:
return "core-rukpak-io-plain", nil
// To ensure compatibility with bundles created with OLMv0 where the
// olm.bundle.mediatype property doesn't exist, we assume that if the
// property is empty (i.e doesn't exist) that the bundle is one created
// with OLMv0 and therefore should use the registry provisioner
case catalogmetadata.MediaTypeRegistry, "":
return "core-rukpak-io-registry", nil
default:
return "", fmt.Errorf("unknown bundle mediatype: %s", mediaType)
}
}

// Generate reconcile requests for all cluster extensions affected by a catalog change
func clusterExtensionRequestsForCatalog(c client.Reader, logger logr.Logger) handler.MapFunc {
return func(ctx context.Context, _ client.Object) []reconcile.Request {
Expand Down
Loading

0 comments on commit 4324b88

Please sign in to comment.