diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 63e792ef4..245d2e366 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -12,6 +12,13 @@ rules: verbs: - list - watch +- apiGroups: + - catalogd.operatorframework.io + resources: + - catalogsources + verbs: + - list + - watch - apiGroups: - catalogd.operatorframework.io resources: diff --git a/controllers/catalog_predicates.go b/controllers/catalog_predicates.go new file mode 100644 index 000000000..b35d10d5b --- /dev/null +++ b/controllers/catalog_predicates.go @@ -0,0 +1,115 @@ +package controllers + +import ( + "context" + "fmt" + + "github.com/go-logr/logr" + catalogd "github.com/operator-framework/catalogd/pkg/apis/core/v1beta1" + operatorv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +// Predicate for reconciling operators when available (i.e. ready) catalogsources on cluster change +type catalogReadyTransitionPredicate struct { + predicate.Funcs + catalogReady map[string]bool +} + +func newCatalogReadyTransitionPredicate() *catalogReadyTransitionPredicate { + return &catalogReadyTransitionPredicate{ + catalogReady: map[string]bool{}, + } +} + +func (c *catalogReadyTransitionPredicate) Create(e event.CreateEvent) bool { + fmt.Println("CreateEvent CatalogSource", e.Object.GetName()) + catalogReady, err := isCatalogReady(e.Object) + if err != nil { + fmt.Println(err) + return false + } + c.catalogReady[e.Object.GetName()] = catalogReady + return catalogReady +} + +func (c *catalogReadyTransitionPredicate) Update(e event.UpdateEvent) bool { + fmt.Println("UpdateEvent CatalogSource", e.ObjectOld.GetName(), e.ObjectNew.GetName()) + oldCatalogReady, err := isCatalogReady(e.ObjectOld) + if err != nil { + fmt.Println(err) + return false + } + + newCatalogReady, err := isCatalogReady(e.ObjectNew) + if err != nil { + fmt.Println(err) + return false + } + + c.catalogReady[e.ObjectNew.GetName()] = newCatalogReady + // TODO: determine if ready -> non-ready transition triggers reconcile with stale catalog contents + return oldCatalogReady != newCatalogReady +} + +func (c *catalogReadyTransitionPredicate) Delete(e event.DeleteEvent) bool { + fmt.Println("DeleteEvent CatalogSource", e.Object.GetName()) + delete(c.catalogReady, e.Object.GetName()) + return true +} + +func (c *catalogReadyTransitionPredicate) Generic(e event.GenericEvent) bool { + fmt.Println("GenericEvent CatalogSource", e.Object.GetName()) + catalogReady, err := isCatalogReady(e.Object) + if err != nil { + fmt.Println(err) + return false + } + predicateState := c.catalogReady[e.Object.GetName()] != catalogReady + c.catalogReady[e.Object.GetName()] = catalogReady + return predicateState +} + +func isCatalogReady(o client.Object) (bool, error) { + catalog, ok := o.(*catalogd.CatalogSource) + if !ok { + return false, fmt.Errorf("wrong object type: not a catalogsource: %+v", o) + } + if len(catalog.Status.Conditions) > 0 { + for _, cond := range catalog.Status.Conditions { + if cond.Type == catalogd.TypeReady && cond.Status == v1.ConditionTrue { + return true, nil + } + } + } + return false, nil +} + +// Generate reconcile requests for all operators affected by a catalog change +func operatorRequestsForCatalog(ctx context.Context, c client.Client, logger logr.Logger) handler.MapFunc { + return func(object client.Object) []reconcile.Request { + // no way of associating an operator to a catalog so create reconcile requests for everything + operators := operatorv1alpha1.OperatorList{} + err := c.List(ctx, &operators) + if err != nil { + logger.Error(err, "unable to enqueue operators for catalog reconcile") + return nil + } + var requests []reconcile.Request + for _, op := range operators.Items { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: op.GetNamespace(), + Name: op.GetName(), + }, + }) + } + return requests + } +} diff --git a/controllers/operator_controller.go b/controllers/operator_controller.go index 25cbddbeb..2b39398e2 100644 --- a/controllers/operator_controller.go +++ b/controllers/operator_controller.go @@ -20,7 +20,9 @@ import ( "context" "fmt" + catalogd "github.com/operator-framework/catalogd/pkg/apis/core/v1beta1" "github.com/operator-framework/deppy/pkg/deppy/solver" + "github.com/operator-framework/operator-controller/controllers/validators" rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1" "k8s.io/apimachinery/pkg/api/equality" apimeta "k8s.io/apimachinery/pkg/api/meta" @@ -31,10 +33,11 @@ import ( utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/utils/pointer" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" - - "github.com/operator-framework/operator-controller/controllers/validators" + "sigs.k8s.io/controller-runtime/pkg/source" operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" "github.com/operator-framework/operator-controller/internal/resolution" @@ -57,6 +60,7 @@ type OperatorReconciler struct { //+kubebuilder:rbac:groups=catalogd.operatorframework.io,resources=bundlemetadata,verbs=list;watch //+kubebuilder:rbac:groups=catalogd.operatorframework.io,resources=packages,verbs=list;watch +//+kubebuilder:rbac:groups=catalogd.operatorframework.io,resources=catalogsources,verbs=list;watch func (r *OperatorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { l := log.FromContext(ctx).WithName("operator-controller") @@ -294,6 +298,9 @@ func (r *OperatorReconciler) generateExpectedBundleDeployment(o operatorsv1alpha func (r *OperatorReconciler) SetupWithManager(mgr ctrl.Manager) error { err := ctrl.NewControllerManagedBy(mgr). For(&operatorsv1alpha1.Operator{}). + Watches(source.NewKindWithCache(&catalogd.CatalogSource{}, mgr.GetCache()), + handler.EnqueueRequestsFromMapFunc(operatorRequestsForCatalog(context.TODO(), mgr.GetClient(), mgr.GetLogger())), + builder.WithPredicates(newCatalogReadyTransitionPredicate())). Owns(&rukpakv1alpha1.BundleDeployment{}). Complete(r) diff --git a/go.mod b/go.mod index c8d28c5aa..1ef55a2ea 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.19 require ( github.com/blang/semver/v4 v4.0.0 + github.com/go-logr/logr v1.2.3 github.com/onsi/ginkgo/v2 v2.8.3 github.com/onsi/gomega v1.27.1 github.com/operator-framework/catalogd v0.1.3 @@ -26,7 +27,6 @@ require ( github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-air/gini v1.0.4 // indirect - github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/zapr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect