diff --git a/pkg/addonmanager/controllers/addoninstall/controller.go b/pkg/addonmanager/controllers/addoninstall/controller.go index 56209a614..ebac29bec 100644 --- a/pkg/addonmanager/controllers/addoninstall/controller.go +++ b/pkg/addonmanager/controllers/addoninstall/controller.go @@ -2,13 +2,13 @@ package addoninstall import ( "context" - "fmt" "github.com/openshift/library-go/pkg/controller/factory" "github.com/openshift/library-go/pkg/operator/events" "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" "k8s.io/apimachinery/pkg/runtime" "k8s.io/klog/v2" "open-cluster-management.io/addon-framework/pkg/agent" @@ -41,7 +41,7 @@ func NewAddonInstallController( managedClusterLister: clusterInformers.Lister(), managedClusterAddonLister: addonInformers.Lister(), agentAddons: agentAddons, - eventRecorder: recorder.WithComponentSuffix(fmt.Sprintf("addon-install-controller")), + eventRecorder: recorder.WithComponentSuffix("addon-install-controller"), } return factory.New().WithFilteredEventsInformersQueueKeyFunc( @@ -65,14 +65,14 @@ func NewAddonInstallController( }, clusterInformers.Informer(), ). - WithSync(c.sync).ToController(fmt.Sprintf("addon-install-controller"), recorder) + WithSync(c.sync).ToController("addon-install-controller", recorder) } func (c *addonInstallController) sync(ctx context.Context, syncCtx factory.SyncContext) error { clusterName := syncCtx.QueueKey() klog.V(4).Infof("Reconciling addon deploy on cluster %q", clusterName) - _, err := c.managedClusterLister.Get(clusterName) + cluster, err := c.managedClusterLister.Get(clusterName) if errors.IsNotFound(err) { return nil } @@ -87,6 +87,24 @@ func (c *addonInstallController) sync(ctx context.Context, syncCtx factory.SyncC switch addon.GetAgentAddonOptions().InstallStrategy.Type { case agent.InstallAll: + return c.applyAddon(ctx, addonName, clusterName, addon.GetAgentAddonOptions().InstallStrategy.InstallNamespace) + case agent.InstallByLabel: + labelSelector := addon.GetAgentAddonOptions().InstallStrategy.LabelSelector + if labelSelector == nil { + klog.Warningf("installByLabel strategy is set, but label selector is not set") + return nil + } + + selector, err := metav1.LabelSelectorAsSelector(labelSelector) + if err != nil { + klog.Warningf("labels selector is not correct: %v", err) + return nil + } + + if !selector.Matches(labels.Set(cluster.Labels)) { + return nil + } + return c.applyAddon(ctx, addonName, clusterName, addon.GetAgentAddonOptions().InstallStrategy.InstallNamespace) } } diff --git a/pkg/addonmanager/controllers/addoninstall/controller_test.go b/pkg/addonmanager/controllers/addoninstall/controller_test.go index ebc4f92fd..5f276fb53 100644 --- a/pkg/addonmanager/controllers/addoninstall/controller_test.go +++ b/pkg/addonmanager/controllers/addoninstall/controller_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/openshift/library-go/pkg/operator/events/eventstesting" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" clienttesting "k8s.io/client-go/testing" "open-cluster-management.io/addon-framework/pkg/addonmanager/addontesting" @@ -34,6 +35,13 @@ func (t *testAgent) GetAgentAddonOptions() agent.AgentAddonOptions { } } +func newManagedClusterWithLabel(name, key, value string) *clusterv1.ManagedCluster { + cluster := addontesting.NewManagedCluster(name) + cluster.Labels = map[string]string{key: value} + + return cluster +} + func TestReconcile(t *testing.T) { cases := []struct { name string @@ -77,6 +85,38 @@ func TestReconcile(t *testing.T) { }, testaddon: &testAgent{name: "test", strategy: agent.InstallAllStrategy("test")}, }, + { + name: "selector install strategy with unmatched cluster", + addon: []runtime.Object{}, + cluster: []runtime.Object{addontesting.NewManagedCluster("cluster1")}, + validateAddonActions: addontesting.AssertNoActions, + testaddon: &testAgent{name: "test", strategy: agent.InstallByLabelStrategy("test", metav1.LabelSelector{ + MatchLabels: map[string]string{"mode": "dev"}, + })}, + }, + { + name: "selector install strategy with nil label selector", + addon: []runtime.Object{}, + cluster: []runtime.Object{addontesting.NewManagedCluster("cluster1")}, + validateAddonActions: addontesting.AssertNoActions, + testaddon: &testAgent{name: "test", strategy: &agent.InstallStrategy{Type: agent.InstallByLabel}}, + }, + { + name: "selector install strategy with matched cluster", + addon: []runtime.Object{}, + cluster: []runtime.Object{newManagedClusterWithLabel("cluster1", "mode", "dev")}, + validateAddonActions: func(t *testing.T, actions []clienttesting.Action) { + addontesting.AssertActions(t, actions, "create") + actual := actions[0].(clienttesting.CreateActionImpl).Object + addOn := actual.(*addonapiv1alpha1.ManagedClusterAddOn) + if addOn.Spec.InstallNamespace != "test" { + t.Errorf("Install namespace is not correct, expected test but got %s", addOn.Spec.InstallNamespace) + } + }, + testaddon: &testAgent{name: "test", strategy: agent.InstallByLabelStrategy("test", metav1.LabelSelector{ + MatchLabels: map[string]string{"mode": "dev"}, + })}, + }, } for _, c := range cases { diff --git a/pkg/agent/inteface.go b/pkg/agent/inteface.go index 1eac62c5c..5d211c6bc 100644 --- a/pkg/agent/inteface.go +++ b/pkg/agent/inteface.go @@ -4,6 +4,7 @@ import ( "fmt" certificatesv1 "k8s.io/api/certificates/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" addonapiv1alpha1 "open-cluster-management.io/api/addon/v1alpha1" clusterv1 "open-cluster-management.io/api/cluster/v1" @@ -104,7 +105,13 @@ type RegistrationOption struct { type StrategyType string -const InstallAll StrategyType = "*" +const ( + // InstallAll indicate to install addon to all clusters + InstallAll StrategyType = "*" + + // InstallByLabel indicate to install addon based on clusters' label + InstallByLabel StrategyType = "LabelSelector" +) // InstallStrategy is the installation strategy of the manifests prescribed by Manifests(..). type InstallStrategy struct { @@ -113,6 +120,9 @@ type InstallStrategy struct { Type StrategyType // InstallNamespace is target deploying namespace in the managed cluster upon automatic addon installation. InstallNamespace string + + // LabelSelector is used to filter clusters based on label. It is only used when strategyType is InstallByLabel + LabelSelector *metav1.LabelSelector } type HealthProber struct { @@ -173,6 +183,14 @@ func InstallAllStrategy(installNamespace string) *InstallStrategy { } } +func InstallByLabelStrategy(installNamespace string, selector metav1.LabelSelector) *InstallStrategy { + return &InstallStrategy{ + Type: InstallByLabel, + InstallNamespace: installNamespace, + LabelSelector: &selector, + } +} + // ApprovalAllCSRs returns true for all csrs. func ApprovalAllCSRs(cluster *clusterv1.ManagedCluster, addon *addonapiv1alpha1.ManagedClusterAddOn, csr *certificatesv1.CertificateSigningRequest) bool { return true