Files
Jian Qiu c367622060 🌱 Move addon api to beta in registration (#1438)
* Move addon api to beta in registration

Signed-off-by: Jian Qiu <jqiu@redhat.com>

* Update conversion e2e tests

Signed-off-by: Jian Qiu <jqiu@redhat.com>

* Add addon v1beta1 to supported type in grpc

Signed-off-by: Jian Qiu <jqiu@redhat.com>

* Fix flaky e2e in addon conversion

Signed-off-by: Jian Qiu <jqiu@redhat.com>

* Set subject for registration configuration when it is not set

Signed-off-by: Jian Qiu <jqiu@redhat.com>

---------

Signed-off-by: Jian Qiu <jqiu@redhat.com>
2026-03-23 13:31:55 +00:00

155 lines
5.0 KiB
Go

package addon
import (
"context"
"fmt"
"strings"
"github.com/openshift/library-go/pkg/operator/resource/resourcemerge"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
addonv1beta1 "open-cluster-management.io/api/addon/v1beta1"
addoninformerv1beta1 "open-cluster-management.io/api/client/addon/informers/externalversions/addon/v1beta1"
addonlisterv1beta1 "open-cluster-management.io/api/client/addon/listers/addon/v1beta1"
clientset "open-cluster-management.io/api/client/cluster/clientset/versioned"
clusterv1informer "open-cluster-management.io/api/client/cluster/informers/externalversions/cluster/v1"
clusterv1listers "open-cluster-management.io/api/client/cluster/listers/cluster/v1"
clusterv1 "open-cluster-management.io/api/cluster/v1"
"open-cluster-management.io/sdk-go/pkg/basecontroller/factory"
"open-cluster-management.io/sdk-go/pkg/patcher"
"open-cluster-management.io/ocm/pkg/common/queue"
)
const (
addOnFeaturePrefix = "feature.open-cluster-management.io/addon-"
addOnStatusAvailable = "available"
addOnStatusUnhealthy = "unhealthy"
addOnStatusUnreachable = "unreachable"
)
// addOnFeatureDiscoveryController monitors ManagedCluster and its ManagedClusterAddOns on hub and
// create/update/delete labels of the ManagedCluster to reflect the status of addons.
type addOnFeatureDiscoveryController struct {
patcher patcher.Patcher[*clusterv1.ManagedCluster, clusterv1.ManagedClusterSpec, clusterv1.ManagedClusterStatus]
clusterLister clusterv1listers.ManagedClusterLister
addOnLister addonlisterv1beta1.ManagedClusterAddOnLister
}
// NewAddOnFeatureDiscoveryController returns an instance of addOnFeatureDiscoveryController
func NewAddOnFeatureDiscoveryController(
clusterClient clientset.Interface,
clusterInformer clusterv1informer.ManagedClusterInformer,
addOnInformers addoninformerv1beta1.ManagedClusterAddOnInformer,
) factory.Controller {
c := &addOnFeatureDiscoveryController{
patcher: patcher.NewPatcher[
*clusterv1.ManagedCluster, clusterv1.ManagedClusterSpec, clusterv1.ManagedClusterStatus](
clusterClient.ClusterV1().ManagedClusters()),
clusterLister: clusterInformer.Lister(),
addOnLister: addOnInformers.Lister(),
}
return factory.New().
WithInformersQueueKeysFunc(
queue.QueueKeyByMetaName,
clusterInformer.Informer()).
WithInformersQueueKeysFunc(
queue.QueueKeyByMetaNamespace,
addOnInformers.Informer()).
WithSync(c.sync).
ToController("AddOnFeatureDiscoveryController")
}
func (c *addOnFeatureDiscoveryController) sync(ctx context.Context, syncCtx factory.SyncContext, queueKey string) error {
switch {
case queueKey == factory.DefaultQueueKey:
// no need to resync
return nil
default:
// sync the cluster
return c.syncCluster(ctx, queueKey)
}
}
func (c *addOnFeatureDiscoveryController) syncCluster(ctx context.Context, clusterName string) error {
// sync all addon labels on the managed cluster
cluster, err := c.clusterLister.Get(clusterName)
if errors.IsNotFound(err) {
// cluster is deleted
return nil
}
if err != nil {
return err
}
// Do not update addon label if cluster is deleting
if !cluster.DeletionTimestamp.IsZero() {
return nil
}
// Do not update if cluster has no available condition yet.
if meta.FindStatusCondition(cluster.Status.Conditions, clusterv1.ManagedClusterConditionAvailable) == nil {
return nil
}
// build labels for existing addons
addOnLabels := map[string]string{}
addOns, err := c.addOnLister.ManagedClusterAddOns(clusterName).List(labels.Everything())
if err != nil {
return fmt.Errorf("unable to list addOns of cluster %q: %w", clusterName, err)
}
for _, addOn := range addOns {
// addon is deleting
if !addOn.DeletionTimestamp.IsZero() {
continue
}
key := fmt.Sprintf("%s%s", addOnFeaturePrefix, addOn.Name)
addOnLabels[key] = getAddOnLabelValue(addOn)
}
// remove addon lable if its corresponding addon no longer exists
for key := range cluster.Labels {
if !strings.HasPrefix(key, addOnFeaturePrefix) {
continue
}
if _, ok := addOnLabels[key]; !ok {
addOnLabels[fmt.Sprintf("%s-", key)] = ""
}
}
// merge labels
modified := false
newCluster := cluster.DeepCopy()
resourcemerge.MergeMap(&modified, &newCluster.Labels, addOnLabels)
// no work if the cluster labels have no change
if !modified {
return nil
}
// otherwise, update cluster
_, err = c.patcher.PatchLabelAnnotations(ctx, newCluster, newCluster.ObjectMeta, cluster.ObjectMeta)
return err
}
func getAddOnLabelValue(addOn *addonv1beta1.ManagedClusterAddOn) string {
availableCondition := meta.FindStatusCondition(addOn.Status.Conditions, addonv1beta1.ManagedClusterAddOnConditionAvailable)
if availableCondition == nil {
return addOnStatusUnreachable
}
switch availableCondition.Status {
case metav1.ConditionTrue:
return addOnStatusAvailable
case metav1.ConditionFalse:
return addOnStatusUnhealthy
default:
return addOnStatusUnreachable
}
}