diff --git a/agent/Makefile b/agent/Makefile index 67da5d96a..a9883cc4b 100644 --- a/agent/Makefile +++ b/agent/Makefile @@ -24,7 +24,7 @@ CRD_OPTIONS ?= "crd:trivialVersions=true,preserveUnknownFields=false" .PHONY: controller-gen ##downloads controller-gen locally if necessary. controller-gen: - go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.6.1 + go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.14.0 .PHONY: generate ##generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. generate: controller-gen diff --git a/go.mod b/go.mod index 114c4faab..93bda23ed 100644 --- a/go.mod +++ b/go.mod @@ -31,8 +31,8 @@ require ( github.com/operator-framework/operator-lifecycle-manager v0.22.0 github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.63.0 github.com/spf13/pflag v1.0.5 - github.com/stolostron/cluster-lifecycle-api v0.0.0-20230222063645-5b18b26381ff - github.com/stolostron/klusterlet-addon-controller v0.0.0-20230528112800-a466a2368df4 + github.com/stolostron/cluster-lifecycle-api v0.0.0-20230810064008-81160dedc4f8 + github.com/stolostron/klusterlet-addon-controller v0.0.0-20240626080538-fb87041882e2 github.com/stolostron/multiclusterhub-operator v0.0.0-20230829141355-4ad378ab367f github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.26.0 @@ -47,9 +47,9 @@ require ( k8s.io/client-go v0.30.1 k8s.io/klog v1.0.0 k8s.io/kube-aggregator v0.30.1 - k8s.io/utils v0.0.0-20240102154912-e7106e64919e - open-cluster-management.io/addon-framework v0.9.0 - open-cluster-management.io/api v0.13.0 + k8s.io/utils v0.0.0-20240310230437-4693a0247e57 + open-cluster-management.io/addon-framework v0.10.0 + open-cluster-management.io/api v0.14.0 open-cluster-management.io/governance-policy-propagator v0.11.1-0.20230815182526-b4ee1b24b1d0 open-cluster-management.io/multicloud-operators-channel v0.11.0 open-cluster-management.io/multicloud-operators-subscription v0.11.0 @@ -211,7 +211,7 @@ require ( k8s.io/component-base v0.30.1 // indirect k8s.io/klog/v2 v2.120.1 k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect - open-cluster-management.io/sdk-go v0.13.0 // indirect + open-cluster-management.io/sdk-go v0.13.1-0.20240416062924-20307e6fe090 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kube-storage-version-migrator v0.0.6-0.20230721195810-5c8923c5ff96 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect diff --git a/go.sum b/go.sum index 4c09daca4..b04544b9b 100644 --- a/go.sum +++ b/go.sum @@ -627,8 +627,8 @@ github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/z github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -1176,10 +1176,10 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= -github.com/stolostron/cluster-lifecycle-api v0.0.0-20230222063645-5b18b26381ff h1:psHMqHMefkFtr7y4JT5tz8oKwmChvLJe/cHUUM9tcMI= -github.com/stolostron/cluster-lifecycle-api v0.0.0-20230222063645-5b18b26381ff/go.mod h1:pNeVzujoHsTHDloNHVfp1QPYlQy8MkXMuuZme96/x8M= -github.com/stolostron/klusterlet-addon-controller v0.0.0-20230528112800-a466a2368df4 h1:uK6AKdfrhCKkrEllKDF8W1QtDoWN7HI8qoxVnn6kpIM= -github.com/stolostron/klusterlet-addon-controller v0.0.0-20230528112800-a466a2368df4/go.mod h1:WyEG4DcVm+zrFgoOFejnMB1Ua8qsX3YwKF2/i8U+HIw= +github.com/stolostron/cluster-lifecycle-api v0.0.0-20230810064008-81160dedc4f8 h1:HfoSdzarXIABlLNihSRY1Gl+G+L/0Qv9NiqHa3yOWOs= +github.com/stolostron/cluster-lifecycle-api v0.0.0-20230810064008-81160dedc4f8/go.mod h1:ZNQ3Rttgk4HEreCHfocrhXavLDaUgHbZaUqk5dP8/As= +github.com/stolostron/klusterlet-addon-controller v0.0.0-20240626080538-fb87041882e2 h1:wcFk/+Oyyg8fcGnFspPDm72e8f/b6GguwpVxVdbtZv4= +github.com/stolostron/klusterlet-addon-controller v0.0.0-20240626080538-fb87041882e2/go.mod h1:ptR774KOKeg3AW4G4jkf0d+Hn5iTyLMLWc6n5UcP+zw= github.com/stolostron/multiclusterhub-operator v0.0.0-20230829141355-4ad378ab367f h1:vKFrnwEqWoR1jJeioWzQhqSzCqBSN31M4w4HOxBfIxc= github.com/stolostron/multiclusterhub-operator v0.0.0-20230829141355-4ad378ab367f/go.mod h1:qYKONCSLWFOny+poZRx/6EDKZXZlIBDdeDukYr4+B6c= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -1965,25 +1965,25 @@ k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ= -k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20240310230437-4693a0247e57 h1:gbqbevonBh57eILzModw6mrkbwM0gQBEuevE/AaBsHY= +k8s.io/utils v0.0.0-20240310230437-4693a0247e57/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= -open-cluster-management.io/addon-framework v0.9.0 h1:7QKLgfRns2BRLFigjIaWVTPCwG9feM+CNtZ22Yf2I20= -open-cluster-management.io/addon-framework v0.9.0/go.mod h1:OEIFCEXhZKO/Grv08CB0T+TGzS0bLshw4G9u7Vw8dw0= -open-cluster-management.io/api v0.13.0 h1:dlcJEZlNlE0DmSDctK2s7iWKg9l+Tgb0V78Z040nMuk= -open-cluster-management.io/api v0.13.0/go.mod h1:CuCPEzXDvOyxBB0H1d1eSeajbHqaeGEKq9c63vQc63w= +open-cluster-management.io/addon-framework v0.10.0 h1:bwI1XujcbkDoqlCFG1mKuwXNzoED4im/9/9BVu4xpRo= +open-cluster-management.io/addon-framework v0.10.0/go.mod h1:HayKCznnlyW+0dUJQGj5sNR6i3tvylSySD3YnvZkBtY= +open-cluster-management.io/api v0.14.0 h1:yjhnNeO/QudiIoEi0i/yUYmP3iElAfUgtj4pHMV+4uM= +open-cluster-management.io/api v0.14.0/go.mod h1:ltijKJhDifrPH0csvCUmFt5lzaERv+BBfh6X3l83rT0= open-cluster-management.io/governance-policy-propagator v0.11.1-0.20230815182526-b4ee1b24b1d0 h1:r20lNdYf/dZll6d9MRlWv9CAMcW+YO5PGG25odV+FgY= open-cluster-management.io/governance-policy-propagator v0.11.1-0.20230815182526-b4ee1b24b1d0/go.mod h1:ZRc+w6JPLUXUGcfR6cYBL+2yufaNWsn7Vi/JugStDuw= open-cluster-management.io/multicloud-operators-channel v0.11.0 h1:Uprx8ShWY2P/ng4UduYg6kgZZyF+732uJ4z0ZZWLi04= open-cluster-management.io/multicloud-operators-channel v0.11.0/go.mod h1:XpOQgTYxprwTGQBKXwI+6TaJKdOjltO5+jHkTU+LfVA= open-cluster-management.io/multicloud-operators-subscription v0.11.0 h1:OLpohu92lMEmWk4LriTyxD1SIJrzHOvuhS3UsnEOElk= open-cluster-management.io/multicloud-operators-subscription v0.11.0/go.mod h1:0YDADTwQiNoLc7ihyHhTaCNAxx9VSVvrTUQf3W+AyGk= -open-cluster-management.io/sdk-go v0.13.0 h1:ddMGsPUekQr9z03tVN6vF39Uf+WEKMtGU/xSd81HdoA= -open-cluster-management.io/sdk-go v0.13.0/go.mod h1:UnsjzYOrDTF9a8rHEXksoIAtAdO1o5CD5Jtaw6T5B9w= +open-cluster-management.io/sdk-go v0.13.1-0.20240416062924-20307e6fe090 h1:zFmHuW+ztdfUUNslqNW+H1WEcfdEUQHoRDbmdajX340= +open-cluster-management.io/sdk-go v0.13.1-0.20240416062924-20307e6fe090/go.mod h1:w2OaxtCyegxeyFLU42UQ3oxUz01QdsBQkcHI17T/l48= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/letsencrypt v0.0.3 h1:H7xDfhkaFFSYEJlKeq38RwX2jYcnTeHuDQyT+mMNMwM= rsc.io/letsencrypt v0.0.3/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY= diff --git a/manager/Makefile b/manager/Makefile index 0bf4ef25c..d1809e067 100644 --- a/manager/Makefile +++ b/manager/Makefile @@ -24,7 +24,7 @@ CRD_OPTIONS ?= "crd:trivialVersions=true,preserveUnknownFields=false" .PHONY: controller-gen ##downloads controller-gen locally if necessary. controller-gen: - go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.6.1 + go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.14.0 .PHONY: generate ##generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. generate: controller-gen diff --git a/manager/cmd/manager/main.go b/manager/cmd/manager/main.go index 78014eaac..b69fc7251 100644 --- a/manager/cmd/manager/main.go +++ b/manager/cmd/manager/main.go @@ -26,6 +26,7 @@ import ( metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" + "github.com/stolostron/multicluster-global-hub/manager/pkg/addons" "github.com/stolostron/multicluster-global-hub/manager/pkg/backup" managerconfig "github.com/stolostron/multicluster-global-hub/manager/pkg/config" "github.com/stolostron/multicluster-global-hub/manager/pkg/cronjob" @@ -154,6 +155,8 @@ func parseFlags() *managerconfig.ManagerConfig { "enable the global resource feature") pflag.BoolVar(&managerConfig.WithACM, "with-acm", false, "run on Red Hat Advanced Cluster Management") + pflag.BoolVar(&managerConfig.ImportClusterInHosted, "import-in-hosted", false, + "Import the managedhub cluster in hosted mode") pflag.BoolVar(&managerConfig.EnablePprof, "enable-pprof", false, "enable the pprof tool") pflag.Parse() // set zap logger @@ -207,7 +210,7 @@ func createManager(ctx context.Context, NewCache: initCache, } - if managerConfig.EnableGlobalResource { + if managerConfig.EnableGlobalResource || managerConfig.ImportClusterInHosted { options.WebhookServer = &webhook.DefaultServer{ Options: webhook.Options{ Port: webhookPort, @@ -275,6 +278,15 @@ func createManager(ctx context.Context, return nil, fmt.Errorf("failed to add hubmanagement to manager - %w", err) } + // Change addons namespaces for hosted mode + if managerConfig.ImportClusterInHosted { + addons := addons.NewAddonsReconciler(mgr) + err = addons.SetupWithManager(mgr) + if err != nil { + return nil, err + } + } + // need lock DB for backup backupPVC := backup.NewBackupPVCReconciler(mgr, sqlConn) err = backupPVC.SetupWithManager(mgr) @@ -335,7 +347,7 @@ func doMain(ctx context.Context, restConfig *rest.Config) int { return 1 } - if managerConfig.EnableGlobalResource { + if managerConfig.EnableGlobalResource || managerConfig.ImportClusterInHosted { hookServer := mgr.GetWebhookServer() setupLog.Info("registering webhooks to the webhook server") hookServer.Register("/mutating", &webhook.Admission{ diff --git a/manager/pkg/addons/addons_controller.go b/manager/pkg/addons/addons_controller.go new file mode 100644 index 000000000..b76c0108e --- /dev/null +++ b/manager/pkg/addons/addons_controller.go @@ -0,0 +1,138 @@ +/* +Copyright 2023. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package addons + +import ( + "context" + "reflect" + "time" + + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/klog" + "open-cluster-management.io/api/addon/v1alpha1" + 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/event" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + "github.com/stolostron/multicluster-global-hub/pkg/constants" +) + +var addonList = sets.NewString( + "work-manager", + "cluster-proxy", + "managed-serviceaccount", +) + +// BackupReconciler reconciles a MulticlusterGlobalHub object +type AddonsReconciler struct { + manager.Manager + client.Client +} + +func NewAddonsReconciler(mgr manager.Manager) *AddonsReconciler { + return &AddonsReconciler{ + Manager: mgr, + Client: mgr.GetClient(), + } +} + +// SetupWithManager sets up the controller with the Manager. +func (r *AddonsReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr).Named("AddonsController"). + For(&v1alpha1.ClusterManagementAddOn{}, + builder.WithPredicates(addonPred)). + Complete(r) +} + +var addonPred = predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + return addonList.Has(e.Object.GetName()) + }, + UpdateFunc: func(e event.UpdateEvent) bool { + return addonList.Has(e.ObjectNew.GetName()) + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return false + }, +} + +func (r *AddonsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + klog.V(2).Infof("Reconcile ClusterManagementAddOn: %v", req.NamespacedName) + + cma := &v1alpha1.ClusterManagementAddOn{} + err := r.Client.Get(ctx, req.NamespacedName, cma) + if err != nil { + return ctrl.Result{}, err + } + needUpdate := addAddonConfig(cma) + + if !needUpdate { + return ctrl.Result{}, nil + } + err = r.Client.Update(ctx, cma) + + if err != nil { + klog.Errorf("Failed to update cma, err:%v", err) + return ctrl.Result{}, err + } + time.Sleep(1 * time.Second) + cma1 := &v1alpha1.ClusterManagementAddOn{} + + err = r.Client.Get(ctx, req.NamespacedName, cma1) + return ctrl.Result{}, nil +} + +// addAddonConfig return true if the cma updated +func addAddonConfig(cma *v1alpha1.ClusterManagementAddOn) bool { + newNamespaceConfig := v1alpha1.PlacementStrategy{ + PlacementRef: v1alpha1.PlacementRef{ + Namespace: "open-cluster-management-global-set", + Name: "global", + }, + Configs: []v1alpha1.AddOnConfig{ + { + ConfigReferent: v1alpha1.ConfigReferent{ + Name: "global-hub", + Namespace: constants.GHDefaultNamespace, + }, + ConfigGroupResource: v1alpha1.ConfigGroupResource{ + Group: "addon.open-cluster-management.io", + Resource: "addondeploymentconfigs", + }, + }, + }, + } + if len(cma.Spec.InstallStrategy.Placements) == 0 { + cma.Spec.InstallStrategy.Placements = append(cma.Spec.InstallStrategy.Placements, newNamespaceConfig) + return true + } + for _, pl := range cma.Spec.InstallStrategy.Placements { + if !reflect.DeepEqual(pl.PlacementRef, newNamespaceConfig.PlacementRef) { + continue + } + if reflect.DeepEqual(pl.Configs, newNamespaceConfig.Configs) { + return false + } + pl.Configs = append(pl.Configs, newNamespaceConfig.Configs...) + return true + } + cma.Spec.InstallStrategy.Placements = append(cma.Spec.InstallStrategy.Placements, newNamespaceConfig) + return true +} diff --git a/manager/pkg/addons/addons_controller_test.go b/manager/pkg/addons/addons_controller_test.go new file mode 100644 index 000000000..da5527f6b --- /dev/null +++ b/manager/pkg/addons/addons_controller_test.go @@ -0,0 +1,110 @@ +/* +Copyright 2023. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package addons + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "open-cluster-management.io/api/addon/v1alpha1" + + "github.com/stolostron/multicluster-global-hub/pkg/constants" +) + +func Test_addAddonConfig(t *testing.T) { + tests := []struct { + name string + cma *v1alpha1.ClusterManagementAddOn + want bool + }{ + { + name: "empty spec", + cma: &v1alpha1.ClusterManagementAddOn{ + ObjectMeta: metav1.ObjectMeta{ + Name: "work-manager", + Namespace: "c1", + }, + }, + want: true, + }, + { + name: "has config in spec", + cma: &v1alpha1.ClusterManagementAddOn{ + ObjectMeta: metav1.ObjectMeta{ + Name: "work-manager", + Namespace: "c1", + }, + Spec: v1alpha1.ClusterManagementAddOnSpec{ + InstallStrategy: v1alpha1.InstallStrategy{ + Type: "Manual", + Placements: []v1alpha1.PlacementStrategy{ + { + PlacementRef: v1alpha1.PlacementRef{ + Namespace: "ns", + Name: "pl", + }, + }, + }, + }, + }, + }, + want: true, + }, + { + name: "has needed config in spec", + cma: &v1alpha1.ClusterManagementAddOn{ + ObjectMeta: metav1.ObjectMeta{ + Name: "work-manager", + Namespace: "c1", + }, + Spec: v1alpha1.ClusterManagementAddOnSpec{ + InstallStrategy: v1alpha1.InstallStrategy{ + Type: "Manual", + Placements: []v1alpha1.PlacementStrategy{ + { + PlacementRef: v1alpha1.PlacementRef{ + Namespace: "open-cluster-management-global-set", + Name: "global", + }, + Configs: []v1alpha1.AddOnConfig{ + { + ConfigReferent: v1alpha1.ConfigReferent{ + Name: "global-hub", + Namespace: constants.GHDefaultNamespace, + }, + ConfigGroupResource: v1alpha1.ConfigGroupResource{ + Group: "addon.open-cluster-management.io", + Resource: "addondeploymentconfigs", + }, + }, + }, + }, + }, + }, + }, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := addAddonConfig(tt.cma); got != tt.want { + t.Errorf("addAddonConfig() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/manager/pkg/config/manager_config.go b/manager/pkg/config/manager_config.go index 06d228b11..cb248da36 100644 --- a/manager/pkg/config/manager_config.go +++ b/manager/pkg/config/manager_config.go @@ -26,6 +26,7 @@ type ManagerConfig struct { WithACM bool LaunchJobNames string EnablePprof bool + ImportClusterInHosted bool } type SyncerConfig struct { diff --git a/manager/pkg/config/manager_scheme.go b/manager/pkg/config/manager_scheme.go index 423d1b5d0..8925964b8 100644 --- a/manager/pkg/config/manager_scheme.go +++ b/manager/pkg/config/manager_scheme.go @@ -4,10 +4,12 @@ package config import ( + addonapi "github.com/stolostron/klusterlet-addon-controller/pkg/apis" mchv1 "github.com/stolostron/multiclusterhub-operator/api/v1" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" + addonv1alpha1 "open-cluster-management.io/api/addon/v1alpha1" clusterv1 "open-cluster-management.io/api/cluster/v1" clusterv1alpha1 "open-cluster-management.io/api/cluster/v1alpha1" clusterv1beta1 "open-cluster-management.io/api/cluster/v1beta1" @@ -34,5 +36,7 @@ func GetRuntimeScheme() *runtime.Scheme { utilruntime.Must(channelv1.AddToScheme(scheme)) utilruntime.Must(applicationv1beta1.AddToScheme(scheme)) utilruntime.Must(mchv1.AddToScheme(scheme)) + utilruntime.Must(addonapi.AddToScheme(scheme)) + utilruntime.Must(addonv1alpha1.AddToScheme(scheme)) return scheme } diff --git a/manager/pkg/webhook/admission_handler.go b/manager/pkg/webhook/admission_handler.go index afd45a9f8..27ba1b129 100644 --- a/manager/pkg/webhook/admission_handler.go +++ b/manager/pkg/webhook/admission_handler.go @@ -8,11 +8,13 @@ import ( "encoding/json" "net/http" + addonv1 "github.com/stolostron/klusterlet-addon-controller/pkg/apis/agent/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/klog" + clusterv1 "open-cluster-management.io/api/cluster/v1" clusterv1beta1 "open-cluster-management.io/api/cluster/v1beta1" placementrulesv1 "open-cluster-management.io/multicloud-operators-subscription/pkg/apis/apps/placementrule/v1" "sigs.k8s.io/controller-runtime/pkg/client" - logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" "github.com/stolostron/multicluster-global-hub/pkg/constants" @@ -26,18 +28,16 @@ func NewAdmissionHandler(c client.Client, s *runtime.Scheme) admission.Handler { } } -var log = logf.Log.WithName("admission-handler") - type admissionHandler struct { client client.Client decoder admission.Decoder } func (a *admissionHandler) Handle(ctx context.Context, req admission.Request) admission.Response { - log.V(2).Info("admission webhook is called", "name", req.Name, "namespace", - req.Namespace, "kind", req.Kind.Kind, "operation", req.Operation) - - if req.Kind.Kind == "Placement" { + klog.V(2).Infof("admission webhook is called, name:%v, namespace:%v, kind:%v, operation:%v", req.Name, + req.Namespace, req.Kind.Kind, req.Operation) + switch req.Kind.Kind { + case "Placement": placement := &clusterv1beta1.Placement{} err := a.decoder.Decode(req, placement) if err != nil { @@ -57,7 +57,8 @@ func (a *admissionHandler) Handle(ctx context.Context, req admission.Request) ad } return admission.PatchResponseFromRaw(req.Object.Raw, marshaledPlacement) } - } else if req.Kind.Kind == "PlacementRule" { + return admission.Allowed("") + case "PlacementRule": placementrule := &placementrulesv1.PlacementRule{} err := a.decoder.Decode(req, placementrule) if err != nil { @@ -73,9 +74,81 @@ func (a *admissionHandler) Handle(ctx context.Context, req admission.Request) ad } return admission.PatchResponseFromRaw(req.Object.Raw, marshaledPlacementRule) } + return admission.Allowed("") + case "ManagedCluster": + cluster := &clusterv1.ManagedCluster{} + err := a.decoder.Decode(req, cluster) + if err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + if cluster.Name == constants.LocalClusterName { + return admission.Allowed("") + } + klog.Infof("Add hosted annotation for managedcluster: %v", cluster.Name) + + changed := setHostedAnnotations(cluster) + if !changed { + return admission.Allowed("") + } + + marshaledCluster, err := json.Marshal(cluster) + if err != nil { + return admission.Errored(http.StatusInternalServerError, err) + } + return admission.PatchResponseFromRaw(req.Object.Raw, marshaledCluster) + + case "KlusterletAddonConfig": + klusterletaddonconfig := &addonv1.KlusterletAddonConfig{} + err := a.decoder.Decode(req, klusterletaddonconfig) + if err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + klog.Infof("Disable addons in cluster :%v", klusterletaddonconfig.Namespace) + changed := disableAddons(klusterletaddonconfig) + if !changed { + return admission.Allowed("") + } + + marshaledKlusterletAddon, err := json.Marshal(klusterletaddonconfig) + if err != nil { + return admission.Errored(http.StatusInternalServerError, err) + } + return admission.PatchResponseFromRaw(req.Object.Raw, marshaledKlusterletAddon) + default: + return admission.Allowed("") + } +} + +func disableAddons(klusterletaddonconfig *addonv1.KlusterletAddonConfig) bool { + changed := false + if klusterletaddonconfig.Spec.ApplicationManagerConfig.Enabled { + klusterletaddonconfig.Spec.ApplicationManagerConfig.Enabled = false + changed = true + } + if klusterletaddonconfig.Spec.PolicyController.Enabled { + klusterletaddonconfig.Spec.PolicyController.Enabled = false + changed = true + } + if klusterletaddonconfig.Spec.CertPolicyControllerConfig.Enabled { + klusterletaddonconfig.Spec.CertPolicyControllerConfig.Enabled = false + changed = true } - return admission.Allowed("") + return changed +} + +// setHostedAnnotations set hosted annotation for cluster, and return true if changed +func setHostedAnnotations(cluster *clusterv1.ManagedCluster) bool { + if (cluster.Annotations[constants.AnnotationClusterDeployMode] == constants.ClusterDeployModeHosted) && + (cluster.Annotations[constants.AnnotationClusterHostingClusterName] == constants.LocalClusterName) { + return false + } + if cluster.Annotations == nil { + cluster.Annotations = map[string]string{} + } + cluster.Annotations[constants.AnnotationClusterDeployMode] = constants.ClusterDeployModeHosted + cluster.Annotations[constants.AnnotationClusterHostingClusterName] = constants.LocalClusterName + return true } // AdmissionHandler implements admission.DecoderInjector. diff --git a/manager/pkg/webhook/admission_handler_test.go b/manager/pkg/webhook/admission_handler_test.go new file mode 100644 index 000000000..bfe499d5d --- /dev/null +++ b/manager/pkg/webhook/admission_handler_test.go @@ -0,0 +1,160 @@ +// Copyright (c) 2022 Red Hat, Inc. +// Copyright Contributors to the Open Cluster Management project + +package webhook + +import ( + "testing" + + addonv1 "github.com/stolostron/klusterlet-addon-controller/pkg/apis/agent/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + clusterv1 "open-cluster-management.io/api/cluster/v1" + + "github.com/stolostron/multicluster-global-hub/pkg/constants" +) + +func Test_setHostedAnnotations(t *testing.T) { + tests := []struct { + name string + cluster *clusterv1.ManagedCluster + want bool + }{ + { + name: "no annotation", + cluster: &clusterv1.ManagedCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + }, + want: true, + }, + { + name: "has other annotation", + cluster: &clusterv1.ManagedCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + Annotations: map[string]string{ + "a": "b", + }, + }, + }, + want: true, + }, + { + name: "has false annotation", + cluster: &clusterv1.ManagedCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + Annotations: map[string]string{ + constants.AnnotationClusterDeployMode: "b", + constants.AnnotationClusterHostingClusterName: constants.LocalClusterName, + }, + }, + }, + want: true, + }, + { + name: "has hosted annotation", + cluster: &clusterv1.ManagedCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + Annotations: map[string]string{ + constants.AnnotationClusterDeployMode: constants.ClusterDeployModeHosted, + constants.AnnotationClusterHostingClusterName: constants.LocalClusterName, + }, + }, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := setHostedAnnotations(tt.cluster); got != tt.want { + t.Errorf("name:%v, setHostedAnnotations() = %v, want %v", tt.name, got, tt.want) + } + }) + } +} + +func Test_disableAddons(t *testing.T) { + tests := []struct { + name string + klusterletaddonconfig *addonv1.KlusterletAddonConfig + want bool + }{ + { + name: "enable all addons", + klusterletaddonconfig: &addonv1.KlusterletAddonConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + Spec: addonv1.KlusterletAddonConfigSpec{ + ApplicationManagerConfig: addonv1.KlusterletAddonAgentConfigSpec{ + Enabled: true, + }, + PolicyController: addonv1.KlusterletAddonAgentConfigSpec{ + Enabled: true, + }, + CertPolicyControllerConfig: addonv1.KlusterletAddonAgentConfigSpec{ + Enabled: true, + }, + }, + }, + want: true, + }, + { + name: "enable some addons", + klusterletaddonconfig: &addonv1.KlusterletAddonConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + Spec: addonv1.KlusterletAddonConfigSpec{ + ApplicationManagerConfig: addonv1.KlusterletAddonAgentConfigSpec{ + Enabled: false, + }, + PolicyController: addonv1.KlusterletAddonAgentConfigSpec{ + Enabled: false, + }, + CertPolicyControllerConfig: addonv1.KlusterletAddonAgentConfigSpec{ + Enabled: true, + }, + }, + }, + want: true, + }, + { + name: "disable all addons", + klusterletaddonconfig: &addonv1.KlusterletAddonConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + Spec: addonv1.KlusterletAddonConfigSpec{ + ApplicationManagerConfig: addonv1.KlusterletAddonAgentConfigSpec{ + Enabled: false, + }, + PolicyController: addonv1.KlusterletAddonAgentConfigSpec{ + Enabled: false, + }, + CertPolicyControllerConfig: addonv1.KlusterletAddonAgentConfigSpec{ + Enabled: false, + }, + }, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := disableAddons(tt.klusterletaddonconfig); got != tt.want { + t.Errorf("disableAddons() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/operator/Makefile b/operator/Makefile index f37439f2b..6ad36f0ac 100644 --- a/operator/Makefile +++ b/operator/Makefile @@ -183,7 +183,7 @@ ENVTEST ?= $(LOCALBIN)/setup-envtest ## Tool Versions KUSTOMIZE_VERSION ?= v5.2.1 -CONTROLLER_TOOLS_VERSION ?= v0.13.0 +CONTROLLER_TOOLS_VERSION ?= v0.14.0 .PHONY: kustomize kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. diff --git a/operator/bundle/manifests/multicluster-global-hub-operator.clusterserviceversion.yaml b/operator/bundle/manifests/multicluster-global-hub-operator.clusterserviceversion.yaml index 0e5357d48..986cd569c 100644 --- a/operator/bundle/manifests/multicluster-global-hub-operator.clusterserviceversion.yaml +++ b/operator/bundle/manifests/multicluster-global-hub-operator.clusterserviceversion.yaml @@ -23,7 +23,7 @@ metadata: categories: Integration & Delivery,OpenShift Optional certified: "false" containerImage: quay.io/stolostron/multicluster-global-hub-operator:latest - createdAt: "2024-08-08T01:35:13Z" + createdAt: "2024-08-09T06:22:23Z" description: Manages the installation and upgrade of the Multicluster Global Hub. olm.skipRange: '>=1.2.0 <1.3.0' operatorframework.io/initialization-resource: '{"apiVersion":"operator.open-cluster-management.io/v1alpha4", @@ -341,8 +341,11 @@ spec: resources: - addondeploymentconfigs verbs: + - create + - delete - get - list + - update - watch - apiGroups: - addon.open-cluster-management.io diff --git a/operator/bundle/manifests/operator.open-cluster-management.io_multiclusterglobalhubs.yaml b/operator/bundle/manifests/operator.open-cluster-management.io_multiclusterglobalhubs.yaml index d901b8f5e..dd28b3f86 100644 --- a/operator/bundle/manifests/operator.open-cluster-management.io_multiclusterglobalhubs.yaml +++ b/operator/bundle/manifests/operator.open-cluster-management.io_multiclusterglobalhubs.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 creationTimestamp: null name: multiclusterglobalhubs.operator.open-cluster-management.io spec: @@ -24,14 +24,19 @@ spec: of the multiCluster global hub properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -60,8 +65,9 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Limits describes the maximum amount of compute resources allowed. + For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -70,12 +76,11 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of - compute resources required. If requests are omitted - for a container, it defaults to the specified limits. - If there are no specified limits, it defaults to an - implementation-defined value. For more information, - see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Requests describes the minimum amount of compute resources required. + If requests are omitted for a container, it defaults to the specified limits. + If there are no specified limits, it defaults to an implementation-defined value. + For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object type: object @@ -92,8 +97,9 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Limits describes the maximum amount of compute resources allowed. + For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -102,12 +108,11 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of - compute resources required. If requests are omitted - for a container, it defaults to the specified limits. - If there are no specified limits, it defaults to an - implementation-defined value. For more information, - see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Requests describes the minimum amount of compute resources required. + If requests are omitted for a container, it defaults to the specified limits. + If there are no specified limits, it defaults to an implementation-defined value. + For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object type: object @@ -124,8 +129,9 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Limits describes the maximum amount of compute resources allowed. + For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -134,12 +140,11 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of - compute resources required. If requests are omitted - for a container, it defaults to the specified limits. - If there are no specified limits, it defaults to an - implementation-defined value. For more information, - see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Requests describes the minimum amount of compute resources required. + If requests are omitted for a container, it defaults to the specified limits. + If there are no specified limits, it defaults to an implementation-defined value. + For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object type: object @@ -157,8 +162,9 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Limits describes the maximum amount of compute resources allowed. + For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -167,12 +173,11 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of - compute resources required. If requests are omitted - for a container, it defaults to the specified limits. - If there are no specified limits, it defaults to an - implementation-defined value. For more information, - see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Requests describes the minimum amount of compute resources required. + If requests are omitted for a container, it defaults to the specified limits. + If there are no specified limits, it defaults to an implementation-defined value. + For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object type: object @@ -189,8 +194,9 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Limits describes the maximum amount of compute resources allowed. + For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -199,12 +205,11 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of - compute resources required. If requests are omitted - for a container, it defaults to the specified limits. - If there are no specified limits, it defaults to an - implementation-defined value. For more information, - see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Requests describes the minimum amount of compute resources required. + If requests are omitted for a container, it defaults to the specified limits. + If there are no specified limits, it defaults to an implementation-defined value. + For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object type: object @@ -221,8 +226,9 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Limits describes the maximum amount of compute resources allowed. + For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -231,12 +237,11 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of - compute resources required. If requests are omitted - for a container, it defaults to the specified limits. - If there are no specified limits, it defaults to an - implementation-defined value. For more information, - see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Requests describes the minimum amount of compute resources required. + If requests are omitted for a container, it defaults to the specified limits. + If there are no specified limits, it defaults to an implementation-defined value. + For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object type: object @@ -276,15 +281,12 @@ spec: type: string statusTopic: default: gh-event.* - description: 'StatusTopic specifies the topic where an - agent reports events and status updates to a manager. - Specifically, the topic can end up with an asterisk - (*), indicating topics for individual managed hubs. - For example: the default value is "gh-event.*" for the - global hub built-in kafka. Therefore, the topic for - the hub cluster named "hub1" would be "gh-event.hub1"; - In the BYO case, the default value for all managed hubs - is "gh-event"' + description: |- + StatusTopic specifies the topic where an agent reports events and status updates to a manager. + Specifically, the topic can end up with an asterisk (*), indicating topics for individual managed hubs. + For example: the default value is "gh-event.*" for the global hub built-in kafka. Therefore, the topic + for the hub cluster named "hub1" would be "gh-event.hub1"; In the BYO case, the default value for all + managed hubs is "gh-event" type: string type: object type: object @@ -295,12 +297,12 @@ spec: properties: retention: default: 18m - description: Retention is a duration string, defining how - long to keep the data in the database. The recommended minimum - value is 1 month, and the default value is 18 months. A - duration string is a signed sequence of decimal numbers, - each with an optional fraction and a unit suffix, such as - "1y6m". Valid time units are "m" and "y" + description: |- + Retention is a duration string, defining how long to keep the data in the database. + The recommended minimum value is 1 month, and the default value is 18 months. + A duration string is a signed sequence of decimal numbers, + each with an optional fraction and a unit suffix, such as "1y6m". + Valid time units are "m" and "y" type: string storageSize: description: StorageSize specifies the size for storage @@ -312,9 +314,9 @@ spec: type: object enableMetrics: default: true - description: EnableMetrics enables the metrics for the global hub - created kafka and postgres components. If the user provides the - kafka and postgres, then the enablemetrics variable is useless. + description: |- + EnableMetrics enables the metrics for the global hub created kafka and postgres components. + If the user provides the kafka and postgres, then the enablemetrics variable is useless. type: boolean imagePullPolicy: description: ImagePullPolicy specifies the pull policy of the multicluster @@ -332,40 +334,39 @@ spec: tolerations: description: Tolerations causes all components to tolerate any taints items: - description: The pod this Toleration is attached to tolerates any - taint that matches the triple using the matching - operator . + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . properties: effect: - description: Effect indicates the taint effect to match. Empty - means match all taint effects. When specified, allowed values - are NoSchedule, PreferNoSchedule and NoExecute. + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. type: string key: - description: Key is the taint key that the toleration applies - to. Empty means match all taint keys. If the key is empty, - operator must be Exists; this combination means to match all - values and all keys. + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. type: string operator: - description: Operator represents a key's relationship to the - value. Valid operators are Exists and Equal. Defaults to Equal. - Exists is equivalent to wildcard for value, so that a pod - can tolerate all taints of a particular category. + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. type: string tolerationSeconds: - description: TolerationSeconds represents the period of time - the toleration (which must be of effect NoExecute, otherwise - this field is ignored) tolerates the taint. By default, it - is not set, which means tolerate the taint forever (do not - evict). Zero and negative values will be treated as 0 (evict - immediately) by the system. + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. format: int64 type: integer value: - description: Value is the taint value the toleration matches - to. If the operator is Exists, the value should be empty, - otherwise just a regular string. + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. type: string type: object type: array @@ -381,42 +382,42 @@ spec: of the current state items: description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge - // +listType=map // +listMapKey=type Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" properties: lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. format: date-time type: string message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. maxLength: 32768 type: string observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. format: int64 minimum: 0 type: integer reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. This field may not be empty. maxLength: 1024 minLength: 1 @@ -430,11 +431,12 @@ spec: - Unknown type: string type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/operator/config/crd/bases/operator.open-cluster-management.io_multiclusterglobalhubs.yaml b/operator/config/crd/bases/operator.open-cluster-management.io_multiclusterglobalhubs.yaml index ad4ac74b6..5a0ed6351 100644 --- a/operator/config/crd/bases/operator.open-cluster-management.io_multiclusterglobalhubs.yaml +++ b/operator/config/crd/bases/operator.open-cluster-management.io_multiclusterglobalhubs.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: multiclusterglobalhubs.operator.open-cluster-management.io spec: group: operator.open-cluster-management.io @@ -24,14 +24,19 @@ spec: of the multiCluster global hub properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -60,8 +65,9 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Limits describes the maximum amount of compute resources allowed. + For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -70,12 +76,11 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of - compute resources required. If requests are omitted - for a container, it defaults to the specified limits. - If there are no specified limits, it defaults to an - implementation-defined value. For more information, - see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Requests describes the minimum amount of compute resources required. + If requests are omitted for a container, it defaults to the specified limits. + If there are no specified limits, it defaults to an implementation-defined value. + For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object type: object @@ -92,8 +97,9 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Limits describes the maximum amount of compute resources allowed. + For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -102,12 +108,11 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of - compute resources required. If requests are omitted - for a container, it defaults to the specified limits. - If there are no specified limits, it defaults to an - implementation-defined value. For more information, - see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Requests describes the minimum amount of compute resources required. + If requests are omitted for a container, it defaults to the specified limits. + If there are no specified limits, it defaults to an implementation-defined value. + For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object type: object @@ -124,8 +129,9 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Limits describes the maximum amount of compute resources allowed. + For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -134,12 +140,11 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of - compute resources required. If requests are omitted - for a container, it defaults to the specified limits. - If there are no specified limits, it defaults to an - implementation-defined value. For more information, - see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Requests describes the minimum amount of compute resources required. + If requests are omitted for a container, it defaults to the specified limits. + If there are no specified limits, it defaults to an implementation-defined value. + For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object type: object @@ -157,8 +162,9 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Limits describes the maximum amount of compute resources allowed. + For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -167,12 +173,11 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of - compute resources required. If requests are omitted - for a container, it defaults to the specified limits. - If there are no specified limits, it defaults to an - implementation-defined value. For more information, - see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Requests describes the minimum amount of compute resources required. + If requests are omitted for a container, it defaults to the specified limits. + If there are no specified limits, it defaults to an implementation-defined value. + For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object type: object @@ -189,8 +194,9 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Limits describes the maximum amount of compute resources allowed. + For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -199,12 +205,11 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of - compute resources required. If requests are omitted - for a container, it defaults to the specified limits. - If there are no specified limits, it defaults to an - implementation-defined value. For more information, - see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Requests describes the minimum amount of compute resources required. + If requests are omitted for a container, it defaults to the specified limits. + If there are no specified limits, it defaults to an implementation-defined value. + For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object type: object @@ -221,8 +226,9 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Limits describes the maximum amount of compute resources allowed. + For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -231,12 +237,11 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of - compute resources required. If requests are omitted - for a container, it defaults to the specified limits. - If there are no specified limits, it defaults to an - implementation-defined value. For more information, - see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + description: |- + Requests describes the minimum amount of compute resources required. + If requests are omitted for a container, it defaults to the specified limits. + If there are no specified limits, it defaults to an implementation-defined value. + For more information, see: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object type: object @@ -276,15 +281,12 @@ spec: type: string statusTopic: default: gh-event.* - description: 'StatusTopic specifies the topic where an - agent reports events and status updates to a manager. - Specifically, the topic can end up with an asterisk - (*), indicating topics for individual managed hubs. - For example: the default value is "gh-event.*" for the - global hub built-in kafka. Therefore, the topic for - the hub cluster named "hub1" would be "gh-event.hub1"; - In the BYO case, the default value for all managed hubs - is "gh-event"' + description: |- + StatusTopic specifies the topic where an agent reports events and status updates to a manager. + Specifically, the topic can end up with an asterisk (*), indicating topics for individual managed hubs. + For example: the default value is "gh-event.*" for the global hub built-in kafka. Therefore, the topic + for the hub cluster named "hub1" would be "gh-event.hub1"; In the BYO case, the default value for all + managed hubs is "gh-event" type: string type: object type: object @@ -295,12 +297,12 @@ spec: properties: retention: default: 18m - description: Retention is a duration string, defining how - long to keep the data in the database. The recommended minimum - value is 1 month, and the default value is 18 months. A - duration string is a signed sequence of decimal numbers, - each with an optional fraction and a unit suffix, such as - "1y6m". Valid time units are "m" and "y" + description: |- + Retention is a duration string, defining how long to keep the data in the database. + The recommended minimum value is 1 month, and the default value is 18 months. + A duration string is a signed sequence of decimal numbers, + each with an optional fraction and a unit suffix, such as "1y6m". + Valid time units are "m" and "y" type: string storageSize: description: StorageSize specifies the size for storage @@ -312,9 +314,9 @@ spec: type: object enableMetrics: default: true - description: EnableMetrics enables the metrics for the global hub - created kafka and postgres components. If the user provides the - kafka and postgres, then the enablemetrics variable is useless. + description: |- + EnableMetrics enables the metrics for the global hub created kafka and postgres components. + If the user provides the kafka and postgres, then the enablemetrics variable is useless. type: boolean imagePullPolicy: description: ImagePullPolicy specifies the pull policy of the multicluster @@ -332,40 +334,39 @@ spec: tolerations: description: Tolerations causes all components to tolerate any taints items: - description: The pod this Toleration is attached to tolerates any - taint that matches the triple using the matching - operator . + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . properties: effect: - description: Effect indicates the taint effect to match. Empty - means match all taint effects. When specified, allowed values - are NoSchedule, PreferNoSchedule and NoExecute. + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. type: string key: - description: Key is the taint key that the toleration applies - to. Empty means match all taint keys. If the key is empty, - operator must be Exists; this combination means to match all - values and all keys. + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. type: string operator: - description: Operator represents a key's relationship to the - value. Valid operators are Exists and Equal. Defaults to Equal. - Exists is equivalent to wildcard for value, so that a pod - can tolerate all taints of a particular category. + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. type: string tolerationSeconds: - description: TolerationSeconds represents the period of time - the toleration (which must be of effect NoExecute, otherwise - this field is ignored) tolerates the taint. By default, it - is not set, which means tolerate the taint forever (do not - evict). Zero and negative values will be treated as 0 (evict - immediately) by the system. + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. format: int64 type: integer value: - description: Value is the taint value the toleration matches - to. If the operator is Exists, the value should be empty, - otherwise just a regular string. + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. type: string type: object type: array @@ -381,42 +382,42 @@ spec: of the current state items: description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge - // +listType=map // +listMapKey=type Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" properties: lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. format: date-time type: string message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. maxLength: 32768 type: string observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. format: int64 minimum: 0 type: integer reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. This field may not be empty. maxLength: 1024 minLength: 1 @@ -430,11 +431,12 @@ spec: - Unknown type: string type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/operator/config/rbac/role.yaml b/operator/config/rbac/role.yaml index aedfebf78..9b1a99b8f 100644 --- a/operator/config/rbac/role.yaml +++ b/operator/config/rbac/role.yaml @@ -91,8 +91,11 @@ rules: resources: - addondeploymentconfigs verbs: + - create + - delete - get - list + - update - watch - apiGroups: - addon.open-cluster-management.io diff --git a/operator/pkg/config/multiclusterglobalhub_config.go b/operator/pkg/config/multiclusterglobalhub_config.go index 3847833b6..bab4adf94 100644 --- a/operator/pkg/config/multiclusterglobalhub_config.go +++ b/operator/pkg/config/multiclusterglobalhub_config.go @@ -82,6 +82,7 @@ var ( metricsScrapeInterval = "1m" imagePullSecretName = "" addonMgr addonmanager.AddonManager + importClusterInHosted = false ) func SetAddonManager(addonManager addonmanager.AddonManager) { @@ -188,6 +189,20 @@ func SetImageOverrides(mgh *v1alpha4.MulticlusterGlobalHub) error { return nil } +func SetImportClusterInHosted(mgh *v1alpha4.MulticlusterGlobalHub) { + importClusterInHostedValue := getAnnotation(mgh, operatorconstants.AnnotationImportClusterInHosted) + + if importClusterInHostedValue == "true" || importClusterInHostedValue == "True" { + importClusterInHosted = true + return + } + importClusterInHosted = false +} + +func GetImportClusterInHosted() bool { + return importClusterInHosted +} + func SetOauthProxyImage(image string) { imageOverrides[OauthProxyImageKey] = image } diff --git a/operator/pkg/constants/constants.go b/operator/pkg/constants/constants.go index a9810735c..21baa883f 100644 --- a/operator/pkg/constants/constants.go +++ b/operator/pkg/constants/constants.go @@ -56,6 +56,9 @@ const ( AnnotationMGHSchedulerInterval = "mgh-scheduler-interval" // MGHOperandImagePrefix ... MGHOperandImagePrefix = "RELATED_IMAGE_" + // AnnotationImportClusterInHosted will import a managedhub cluster in hosted mode, + // will disable application and policy related addons + AnnotationImportClusterInHosted = "import-cluster-in-hosted" // AnnotationStatisticInterval to log the interval of statistic log AnnotationStatisticInterval = "mgh-statistic-interval" // AnnotationMetricsScrapeInterval to set the scrape interval for metrics @@ -68,8 +71,6 @@ const ( // hub installation constants const ( - LocalClusterName = "local-cluster" - OpenshiftMarketPlaceNamespace = "openshift-marketplace" ACMSubscriptionPublicSource = "redhat-operators" ACMSubscriptionPrivateSource = "acm-custom-registry" @@ -90,14 +91,6 @@ const ( ) const ( - // AnnotationAddonHostingClusterName is the annotation for indicating the hosting cluster name in the addon - AnnotationAddonHostingClusterName = "addon.open-cluster-management.io/hosting-cluster-name" - // AnnotationClusterHostingClusterName is the annotation for indicating the hosting cluster name in the cluster - AnnotationClusterHostingClusterName = "import.open-cluster-management.io/hosting-cluster-name" - AnnotationClusterDeployMode = "import.open-cluster-management.io/klusterlet-deploy-mode" - AnnotationClusterKlusterletDeployNamespace = "import.open-cluster-management.io/klusterlet-namespace" - ClusterDeployModeHosted = "Hosted" - ClusterDeployModeDefault = "Default" // GHAgentDeployModeLabelKey is to indicate which deploy mode the agent is installed. GHAgentDeployModeLabelKey = "global-hub.open-cluster-management.io/agent-deploy-mode" diff --git a/operator/pkg/controllers/addon/addon_controller.go b/operator/pkg/controllers/addon/addon_controller.go index c2a0a94b5..2aa2edeb8 100644 --- a/operator/pkg/controllers/addon/addon_controller.go +++ b/operator/pkg/controllers/addon/addon_controller.go @@ -116,9 +116,9 @@ func (a *AddonController) Start(ctx context.Context) error { WithAgentHostedModeEnabledOption(). WithGetValuesFuncs(hohAgentAddon.GetValues, addonfactory.GetValuesFromAddonAnnotation, - addonfactory.GetAddOnDeloymentConfigValues( - addonfactory.NewAddOnDeloymentConfigGetter(addonClient), - addonfactory.ToAddOnDeloymentConfigValues, + addonfactory.GetAddOnDeploymentConfigValues( + addonfactory.NewAddOnDeploymentConfigGetter(addonClient), + addonfactory.ToAddOnDeploymentConfigValues, addonfactory.ToAddOnCustomizedVariableValues, )). WithAgentRegistrationOption(newRegistrationOption(operatorconstants.GHManagedClusterAddonName)). diff --git a/operator/pkg/controllers/addon/addon_controller_manifests.go b/operator/pkg/controllers/addon/addon_controller_manifests.go index 4db16f693..e3cbf3a4f 100644 --- a/operator/pkg/controllers/addon/addon_controller_manifests.go +++ b/operator/pkg/controllers/addon/addon_controller_manifests.go @@ -127,8 +127,8 @@ func (a *HohAgentAddon) setInstallHostedMode(cluster *clusterv1.ManagedCluster, ) { annotations := cluster.GetAnnotations() labels := cluster.GetLabels() - if annotations[operatorconstants.AnnotationClusterDeployMode] != - operatorconstants.ClusterDeployModeHosted { + if annotations[constants.AnnotationClusterDeployMode] != + constants.ClusterDeployModeHosted { return } if labels[operatorconstants.GHAgentDeployModeLabelKey] != @@ -137,8 +137,8 @@ func (a *HohAgentAddon) setInstallHostedMode(cluster *clusterv1.ManagedCluster, } manifestsConfig.InstallHostedMode = true - if annotations[operatorconstants.AnnotationClusterKlusterletDeployNamespace] != "" { - manifestsConfig.KlusterletNamespace = annotations[operatorconstants.AnnotationClusterKlusterletDeployNamespace] + if annotations[constants.AnnotationClusterKlusterletDeployNamespace] != "" { + manifestsConfig.KlusterletNamespace = annotations[constants.AnnotationClusterKlusterletDeployNamespace] } manifestsConfig.KlusterletWorkSA = fmt.Sprintf("klusterlet-%s-work-sa", cluster.GetName()) } diff --git a/operator/pkg/controllers/addon/addon_installer.go b/operator/pkg/controllers/addon/addon_installer.go index 3a6cf4e6f..0065de90f 100644 --- a/operator/pkg/controllers/addon/addon_installer.go +++ b/operator/pkg/controllers/addon/addon_installer.go @@ -198,12 +198,12 @@ func expectedManagedClusterAddon(cluster *clusterv1.ManagedCluster) (*v1alpha1.M deployMode := cluster.GetLabels()[operatorconstants.GHAgentDeployModeLabelKey] if deployMode == operatorconstants.GHAgentDeployModeHosted { annotations := cluster.GetAnnotations() - if hostingCluster := annotations[operatorconstants.AnnotationClusterHostingClusterName]; hostingCluster != "" { - expectedAddonAnnotations[operatorconstants.AnnotationAddonHostingClusterName] = hostingCluster + if hostingCluster := annotations[constants.AnnotationClusterHostingClusterName]; hostingCluster != "" { + expectedAddonAnnotations[constants.AnnotationAddonHostingClusterName] = hostingCluster expectedAddon.Spec.InstallNamespace = fmt.Sprintf("klusterlet-%s", cluster.Name) } else { return nil, fmt.Errorf("failed to get %s when addon in %s is installed in hosted mode", - operatorconstants.AnnotationClusterHostingClusterName, cluster.Name) + constants.AnnotationClusterHostingClusterName, cluster.Name) } } @@ -368,5 +368,5 @@ func GetAllManagedHubNames(ctx context.Context, c client.Client) ([]string, erro func filterManagedCluster(obj client.Object) bool { return obj.GetLabels()["vendor"] != "OpenShift" || obj.GetLabels()["openshiftVersion"] == "3" || - obj.GetName() == operatorconstants.LocalClusterName + obj.GetName() == constants.LocalClusterName } diff --git a/operator/pkg/controllers/addon/addon_installer_test.go b/operator/pkg/controllers/addon/addon_installer_test.go index dcb60f6bb..36b71899e 100644 --- a/operator/pkg/controllers/addon/addon_installer_test.go +++ b/operator/pkg/controllers/addon/addon_installer_test.go @@ -39,8 +39,8 @@ func fakeCluster(name, hostingCluster, addonDeployMode string) *v1.ManagedCluste if hostingCluster != "" { annotations := map[string]string{ - operatorconstants.AnnotationClusterDeployMode: operatorconstants.ClusterDeployModeHosted, - operatorconstants.AnnotationClusterHostingClusterName: hostingCluster, + constants.AnnotationClusterDeployMode: constants.ClusterDeployModeHosted, + constants.AnnotationClusterHostingClusterName: hostingCluster, } cluster.SetAnnotations(annotations) } @@ -87,8 +87,8 @@ func fakeHoHAddon(cluster, installNamespace, addonDeployMode string) *v1alpha1.M }, } - if addonDeployMode == operatorconstants.ClusterDeployModeHosted { - addon.SetAnnotations(map[string]string{operatorconstants.AnnotationAddonHostingClusterName: "hostingcluster"}) + if addonDeployMode == constants.ClusterDeployModeHosted { + addon.SetAnnotations(map[string]string{constants.AnnotationAddonHostingClusterName: "hostingcluster"}) } return addon @@ -186,9 +186,9 @@ func TestAddonInstaller(t *testing.T) { if addon.Spec.InstallNamespace != "klusterlet-cluster1" { t.Errorf("expected installname klusterlet-cluster1, but got %s", addon.Spec.InstallNamespace) } - if addon.Annotations[operatorconstants.AnnotationAddonHostingClusterName] != "cluster2" { + if addon.Annotations[constants.AnnotationAddonHostingClusterName] != "cluster2" { t.Errorf("expected hosting cluster cluster2, but got %s", - addon.Annotations[operatorconstants.AnnotationAddonHostingClusterName]) + addon.Annotations[constants.AnnotationAddonHostingClusterName]) } }, }, @@ -207,9 +207,9 @@ func TestAddonInstaller(t *testing.T) { if addon.Spec.InstallNamespace != "klusterlet-cluster1" { t.Errorf("expected installname klusterlet-cluster1, but got %s", addon.Spec.InstallNamespace) } - if addon.Annotations[operatorconstants.AnnotationAddonHostingClusterName] != "cluster2" { + if addon.Annotations[constants.AnnotationAddonHostingClusterName] != "cluster2" { t.Errorf("expected hosting cluster cluster2, but got %s", - addon.Annotations[operatorconstants.AnnotationAddonHostingClusterName]) + addon.Annotations[constants.AnnotationAddonHostingClusterName]) } }, }, diff --git a/operator/pkg/controllers/hubofhubs/controller.go b/operator/pkg/controllers/hubofhubs/controller.go index 6353ec9c2..f7ed7fdd0 100644 --- a/operator/pkg/controllers/hubofhubs/controller.go +++ b/operator/pkg/controllers/hubofhubs/controller.go @@ -95,7 +95,7 @@ func NewGlobalHubReconciler(mgr ctrl.Manager, kubeClient kubernetes.Interface, scheme: mgr.GetScheme(), recorder: mgr.GetEventRecorderFor(operatorconstants.GlobalHubControllerName), operatorConfig: operatorConfig, - pruneReconciler: prune.NewPruneReconciler(mgr.GetClient()), + pruneReconciler: prune.NewPruneReconciler(mgr.GetClient(), operatorConfig), metricsReconciler: metrics.NewMetricsReconciler(mgr.GetClient()), storageReconciler: storage.NewStorageReconciler(mgr, operatorConfig.GlobalResourceEnabled), transportReconciler: transporter.NewTransportReconciler(mgr), @@ -559,6 +559,7 @@ func watchMutatingWebhookConfigurationPredicate() predicate.TypedPredicate[*admi // +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=clusterrolebindings,verbs=get;list;watch;create;update;delete // +kubebuilder:rbac:groups="admissionregistration.k8s.io",resources=mutatingwebhookconfigurations,verbs=get;list;watch;create;update;delete // +kubebuilder:rbac:groups=addon.open-cluster-management.io,resources=clustermanagementaddons,verbs=create;delete;get;list;update;watch +// +kubebuilder:rbac:groups=addon.open-cluster-management.io,resources=addondeploymentconfigs,verbs=create;delete;get;list;update;watch // +kubebuilder:rbac:groups=addon.open-cluster-management.io,resources=clustermanagementaddons/finalizers,verbs=update // +kubebuilder:rbac:groups=operator.open-cluster-management.io,resources=multiclusterhubs,verbs=get;list;patch;update;watch // +kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors;prometheusrules;podmonitors,verbs=get;create;delete;update;list;watch @@ -611,6 +612,8 @@ func (r *GlobalHubReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( } } + config.SetImportClusterInHosted(mgh) + // update status condition defer func() { err := r.statusReconciler.Reconcile(ctx, mgh, err) diff --git a/operator/pkg/controllers/hubofhubs/manager/manager_reconciler.go b/operator/pkg/controllers/hubofhubs/manager/manager_reconciler.go index f99df1d6f..e26f487e5 100644 --- a/operator/pkg/controllers/hubofhubs/manager/manager_reconciler.go +++ b/operator/pkg/controllers/hubofhubs/manager/manager_reconciler.go @@ -142,6 +142,7 @@ func (r *ManagerReconciler) Reconcile(ctx context.Context, RetentionMonth: months, StatisticLogInterval: config.GetStatisticLogInterval(), EnableGlobalResource: r.operatorConfig.GlobalResourceEnabled, + ImportClusterInHosted: config.GetImportClusterInHosted(), EnablePprof: r.operatorConfig.EnablePprof, LogLevel: r.operatorConfig.LogLevel, Resources: utils.GetResources(operatorconstants.Manager, mgh.Spec.AdvancedConfig), @@ -214,6 +215,7 @@ type ManagerVariables struct { RetentionMonth int StatisticLogInterval string EnableGlobalResource bool + ImportClusterInHosted bool EnablePprof bool LogLevel string Resources *corev1.ResourceRequirements diff --git a/operator/pkg/controllers/hubofhubs/manager/manifests/addondeploymentconfig.yaml b/operator/pkg/controllers/hubofhubs/manager/manifests/addondeploymentconfig.yaml new file mode 100644 index 000000000..9033cc195 --- /dev/null +++ b/operator/pkg/controllers/hubofhubs/manager/manifests/addondeploymentconfig.yaml @@ -0,0 +1,11 @@ +{{- if .ImportClusterInHosted}} +apiVersion: addon.open-cluster-management.io/v1alpha1 +kind: AddOnDeploymentConfig +metadata: + name: global-hub + namespace: multicluster-global-hub + labels: + cluster.open-cluster-management.io/backup: globalhub +spec: + agentInstallNamespace: open-cluster-management-global-hub-agent-addon +{{ end }} diff --git a/operator/pkg/controllers/hubofhubs/manager/manifests/clusterrole.yaml b/operator/pkg/controllers/hubofhubs/manager/manifests/clusterrole.yaml index e38ab40fc..7bcf11043 100644 --- a/operator/pkg/controllers/hubofhubs/manager/manifests/clusterrole.yaml +++ b/operator/pkg/controllers/hubofhubs/manager/manifests/clusterrole.yaml @@ -53,6 +53,16 @@ rules: - update - patch - delete +- apiGroups: + - "addon.open-cluster-management.io" + resources: + - clustermanagementaddons + verbs: + - get + - list + - watch + - update + - patch - apiGroups: - "cluster.open-cluster-management.io" resources: diff --git a/operator/pkg/controllers/hubofhubs/manager/manifests/deployment.yaml b/operator/pkg/controllers/hubofhubs/manager/manifests/deployment.yaml index 8c3db3365..e345194cd 100644 --- a/operator/pkg/controllers/hubofhubs/manager/manifests/deployment.yaml +++ b/operator/pkg/controllers/hubofhubs/manager/manifests/deployment.yaml @@ -53,6 +53,7 @@ spec: - --renew-deadline={{.RenewDeadline}} - --retry-period={{.RetryPeriod}} - --enable-global-resource={{.EnableGlobalResource}} + - --import-in-hosted={{.ImportClusterInHosted}} - --with-acm={{.WithACM}} {{- if .SchedulerInterval}} - --scheduler-interval={{.SchedulerInterval}} @@ -90,7 +91,7 @@ spec: name: metrics protocol: TCP volumeMounts: - {{- if .EnableGlobalResource }} + {{- if or .EnableGlobalResource .ImportClusterInHosted}} - mountPath: /webhook-certs name: webhook-certs readOnly: true @@ -101,7 +102,7 @@ spec: - mountPath: /postgres-credential name: postgres-credential readOnly: true - {{- if .EnableGlobalResource }} + {{- if .EnableGlobalResource}} - name: oauth-proxy image: {{.ProxyImage}} imagePullPolicy: {{.ImagePullPolicy}} @@ -179,6 +180,8 @@ spec: - name: cookie-secret secret: secretName: nonk8s-apiserver-cookie-secret + {{- end }} + {{- if or .EnableGlobalResource .ImportClusterInHosted}} - name: webhook-certs secret: secretName: multicluster-global-hub-webhook-certs diff --git a/operator/pkg/controllers/hubofhubs/manager/manifests/mutatingwebhookconfiguration.yaml b/operator/pkg/controllers/hubofhubs/manager/manifests/mutatingwebhookconfiguration.yaml index 0472d7771..b12760a49 100644 --- a/operator/pkg/controllers/hubofhubs/manager/manifests/mutatingwebhookconfiguration.yaml +++ b/operator/pkg/controllers/hubofhubs/manager/manifests/mutatingwebhookconfiguration.yaml @@ -1,4 +1,4 @@ -{{ if .EnableGlobalResource }} +{{- if or .EnableGlobalResource .ImportClusterInHosted}} apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration metadata: @@ -21,6 +21,7 @@ webhooks: matchPolicy: Equivalent sideEffects: None rules: +{{- if .EnableGlobalResource}} - apiGroups: - apps.open-cluster-management.io apiVersions: @@ -40,3 +41,24 @@ webhooks: resources: - placements {{ end }} +{{- if .ImportClusterInHosted}} + - apiGroups: + - cluster.open-cluster-management.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - managedclusters + - apiGroups: + - agent.open-cluster-management.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - klusterletaddonconfigs +{{ end }} +{{ end }} diff --git a/operator/pkg/controllers/hubofhubs/manager/manifests/service.yaml b/operator/pkg/controllers/hubofhubs/manager/manifests/service.yaml index 9051267c0..3f5b20b97 100644 --- a/operator/pkg/controllers/hubofhubs/manager/manifests/service.yaml +++ b/operator/pkg/controllers/hubofhubs/manager/manifests/service.yaml @@ -25,7 +25,7 @@ spec: selector: name: multicluster-global-hub-manager --- -{{ if .EnableGlobalResource }} +{{- if or .EnableGlobalResource .ImportClusterInHosted}} apiVersion: v1 kind: Service metadata: diff --git a/operator/pkg/controllers/hubofhubs/prune/prune_reconciler.go b/operator/pkg/controllers/hubofhubs/prune/prune_reconciler.go index 9ffb41b2d..44ed587b2 100644 --- a/operator/pkg/controllers/hubofhubs/prune/prune_reconciler.go +++ b/operator/pkg/controllers/hubofhubs/prune/prune_reconciler.go @@ -36,13 +36,15 @@ import ( type PruneReconciler struct { client.Client - log logr.Logger + log logr.Logger + operatorConfig *config.OperatorConfig } -func NewPruneReconciler(c client.Client) *PruneReconciler { +func NewPruneReconciler(c client.Client, operatorConfig *config.OperatorConfig) *PruneReconciler { return &PruneReconciler{ - log: ctrl.Log.WithName("global-hub-prune"), - Client: c, + log: ctrl.Log.WithName("global-hub-prune"), + Client: c, + operatorConfig: operatorConfig, } } @@ -61,6 +63,13 @@ func (r *PruneReconciler) Reconcile(ctx context.Context, return nil } + // If webhook is no need to enable, should remove the related resources + if !config.GetImportClusterInHosted() && !r.operatorConfig.GlobalResourceEnabled { + if err := r.pruneWebhookResources(ctx); err != nil { + return err + } + } + // reconcile metrics if config.IsBYOKafka() && config.IsBYOPostgres() { mgh.Spec.EnableMetrics = false @@ -72,6 +81,40 @@ func (r *PruneReconciler) Reconcile(ctx context.Context, return nil } +func (r *PruneReconciler) pruneWebhookResources(ctx context.Context) error { + listOpts := []client.ListOption{ + client.MatchingLabels(map[string]string{ + constants.GlobalHubOwnerLabelKey: constants.GHOperatorOwnerLabelVal, + }), + } + webhookList := &admissionregistrationv1.MutatingWebhookConfigurationList{} + if err := r.Client.List(ctx, webhookList, listOpts...); err != nil { + return err + } + for idx := range webhookList.Items { + if err := r.Client.Delete(ctx, &webhookList.Items[idx]); err != nil && !errors.IsNotFound(err) { + return err + } + } + + webhookServiceListOpts := []client.ListOption{ + client.MatchingLabels(map[string]string{ + constants.GlobalHubOwnerLabelKey: constants.GHOperatorOwnerLabelVal, + "service": "multicluster-global-hub-webhook", + }), + } + webhookServiceList := &corev1.ServiceList{} + if err := r.Client.List(ctx, webhookServiceList, webhookServiceListOpts...); err != nil { + return err + } + for idx := range webhookServiceList.Items { + if err := r.Client.Delete(ctx, &webhookServiceList.Items[idx]); err != nil && !errors.IsNotFound(err) { + return err + } + } + return nil +} + func (r *PruneReconciler) pruneACMResources(ctx context.Context) error { // delete addon.open-cluster-management.io/on-multicluster-hub annotation if err := r.pruneManagedHubs(ctx); err != nil { @@ -293,7 +336,7 @@ func (r *PruneReconciler) pruneManagedHubs(ctx context.Context) error { } for idx, managedHub := range clusters.Items { - if managedHub.Name == operatorconstants.LocalClusterName { + if managedHub.Name == constants.LocalClusterName { continue } orgAnnotations := managedHub.GetAnnotations() diff --git a/operator/pkg/controllers/hubofhubs/prune/prune_reconciler_test.go b/operator/pkg/controllers/hubofhubs/prune/prune_reconciler_test.go index 9fc9fb213..823203731 100644 --- a/operator/pkg/controllers/hubofhubs/prune/prune_reconciler_test.go +++ b/operator/pkg/controllers/hubofhubs/prune/prune_reconciler_test.go @@ -7,14 +7,19 @@ import ( kafkav1beta2 "github.com/RedHatInsights/strimzi-client-go/apis/kafka.strimzi.io/v1beta2" subv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" promv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" + globalhubv1alpha4 "github.com/stolostron/multicluster-global-hub/operator/apis/v1alpha4" + "github.com/stolostron/multicluster-global-hub/operator/pkg/config" "github.com/stolostron/multicluster-global-hub/operator/pkg/controllers/hubofhubs/grafana" "github.com/stolostron/multicluster-global-hub/operator/pkg/controllers/hubofhubs/transporter/protocol" + "github.com/stolostron/multicluster-global-hub/pkg/constants" "github.com/stolostron/multicluster-global-hub/pkg/utils" ) @@ -91,7 +96,8 @@ func TestPruneMetricsResources(t *testing.T) { promv1.AddToScheme(scheme.Scheme) fakeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(tt.initObjects...).Build() - r := NewPruneReconciler(fakeClient) + var operatorConfig *config.OperatorConfig + r := NewPruneReconciler(fakeClient, operatorConfig) if err := r.MetricsResources(ctx); (err != nil) != tt.wantErr { t.Errorf("pruneMetricsResources() error = %v, wantErr %v", err, tt.wantErr) } @@ -154,12 +160,204 @@ func TestMulticlusterGlobalHubReconcilerStrimziResources(t *testing.T) { ctx := context.Background() kafkav1beta2.AddToScheme(scheme.Scheme) subv1alpha1.AddToScheme(scheme.Scheme) - + var operatorConfig *config.OperatorConfig fakeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(tt.initObjects...).Build() - r := NewPruneReconciler(fakeClient) + r := NewPruneReconciler(fakeClient, operatorConfig) if err := r.pruneStrimziResources(ctx); (err != nil) != tt.wantErr { t.Errorf("MulticlusterGlobalHubReconciler.pruneStrimziResources() error = %v, wantErr %v", err, tt.wantErr) } }) } } + +func TestWebhookResources(t *testing.T) { + tests := []struct { + name string + initObjects []runtime.Object + mgh *globalhubv1alpha4.MulticlusterGlobalHub + enableGlobalResource bool + webhookItem int + }{ + { + name: "remove webhook resources", + mgh: &globalhubv1alpha4.MulticlusterGlobalHub{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: utils.GetDefaultNamespace(), + Name: "mgh", + Annotations: map[string]string{ + "import-cluster-in-hosted": "false", + }, + }, + Spec: globalhubv1alpha4.MulticlusterGlobalHubSpec{}, + }, + enableGlobalResource: false, + webhookItem: 0, + initObjects: []runtime.Object{ + &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "multicluster-global-hub-webhook", + Namespace: utils.GetDefaultNamespace(), + Labels: map[string]string{ + "global-hub.open-cluster-management.io/managed-by": "global-hub-operator", + "service": "multicluster-global-hub-webhook", + }, + }, + }, + &admissionregistrationv1.MutatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "multicluster-global-hub-mutator", + Labels: map[string]string{ + "global-hub.open-cluster-management.io/managed-by": "global-hub-operator", + }, + }, + }, + }, + }, + + { + name: "do not remove webhook resources because webhook needed for hosted cluster", + mgh: &globalhubv1alpha4.MulticlusterGlobalHub{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: utils.GetDefaultNamespace(), + Name: "mgh", + Annotations: map[string]string{ + "import-cluster-in-hosted": "true", + }, + }, + Spec: globalhubv1alpha4.MulticlusterGlobalHubSpec{}, + }, + enableGlobalResource: false, + webhookItem: 1, + initObjects: []runtime.Object{ + &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "multicluster-global-hub-webhook", + Namespace: utils.GetDefaultNamespace(), + Labels: map[string]string{ + "global-hub.open-cluster-management.io/managed-by": "global-hub-operator", + "service": "multicluster-global-hub-webhook", + }, + }, + }, + &admissionregistrationv1.MutatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "multicluster-global-hub-mutator", + Labels: map[string]string{ + "global-hub.open-cluster-management.io/managed-by": "global-hub-operator", + }, + }, + }, + }, + }, + { + name: "do not remove webhook resources because webhook needed for global resource", + mgh: &globalhubv1alpha4.MulticlusterGlobalHub{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: utils.GetDefaultNamespace(), + Name: "mgh", + }, + Spec: globalhubv1alpha4.MulticlusterGlobalHubSpec{}, + }, + enableGlobalResource: true, + webhookItem: 1, + initObjects: []runtime.Object{ + &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "multicluster-global-hub-webhook", + Namespace: utils.GetDefaultNamespace(), + Labels: map[string]string{ + "global-hub.open-cluster-management.io/managed-by": "global-hub-operator", + "service": "multicluster-global-hub-webhook", + }, + }, + }, + &admissionregistrationv1.MutatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "multicluster-global-hub-mutator", + Labels: map[string]string{ + "global-hub.open-cluster-management.io/managed-by": "global-hub-operator", + }, + }, + }, + }, + }, + { + name: "do not remove webhook resources because webhook is needed", + mgh: &globalhubv1alpha4.MulticlusterGlobalHub{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: utils.GetDefaultNamespace(), + Name: "mgh", + Annotations: map[string]string{ + "import-cluster-in-hosted": "true", + }, + }, + Spec: globalhubv1alpha4.MulticlusterGlobalHubSpec{}, + }, + enableGlobalResource: true, + webhookItem: 1, + initObjects: []runtime.Object{ + &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "multicluster-global-hub-webhook", + Namespace: utils.GetDefaultNamespace(), + Labels: map[string]string{ + "global-hub.open-cluster-management.io/managed-by": "global-hub-operator", + "service": "multicluster-global-hub-webhook", + }, + }, + }, + &admissionregistrationv1.MutatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "multicluster-global-hub-mutator", + Labels: map[string]string{ + "global-hub.open-cluster-management.io/managed-by": "global-hub-operator", + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + kafkav1beta2.AddToScheme(scheme.Scheme) + subv1alpha1.AddToScheme(scheme.Scheme) + operatorConfig := &config.OperatorConfig{ + GlobalResourceEnabled: tt.enableGlobalResource, + } + config.SetImportClusterInHosted(tt.mgh) + + fakeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(tt.initObjects...).Build() + r := NewPruneReconciler(fakeClient, operatorConfig) + if err := r.Reconcile(ctx, tt.mgh); err != nil { + t.Errorf("MulticlusterGlobalHubReconciler.pruneStrimziResources() error = %v", err) + } + listOpts := []client.ListOption{ + client.MatchingLabels(map[string]string{ + constants.GlobalHubOwnerLabelKey: constants.GHOperatorOwnerLabelVal, + }), + } + webhookList := &admissionregistrationv1.MutatingWebhookConfigurationList{} + if err := fakeClient.List(ctx, webhookList, listOpts...); err != nil { + t.Errorf("Failed to list webhook config") + } + if len(webhookList.Items) != tt.webhookItem { + t.Errorf("Name:%v, Existing webhookItems:%v, want webhook items:%v", tt.name, len(webhookList.Items), tt.webhookItem) + } + + webhookServiceListOpts := []client.ListOption{ + client.MatchingLabels(map[string]string{ + constants.GlobalHubOwnerLabelKey: constants.GHOperatorOwnerLabelVal, + "service": "multicluster-global-hub-webhook", + }), + } + webhookServiceList := &corev1.ServiceList{} + if err := fakeClient.List(ctx, webhookServiceList, webhookServiceListOpts...); err != nil { + t.Errorf("Failed to list webhook service") + } + if len(webhookServiceList.Items) != tt.webhookItem { + t.Errorf("Name:%v,Existing webhookServiceList:%v, want webhook items:%v", tt.name, len(webhookServiceList.Items), tt.webhookItem) + } + }) + } +} diff --git a/operator/pkg/utils/utils.go b/operator/pkg/utils/utils.go index b3ca6e8d9..32b084589 100644 --- a/operator/pkg/utils/utils.go +++ b/operator/pkg/utils/utils.go @@ -47,9 +47,9 @@ import ( "github.com/stolostron/multicluster-global-hub/operator/apis/v1alpha4" "github.com/stolostron/multicluster-global-hub/operator/pkg/config" - "github.com/stolostron/multicluster-global-hub/operator/pkg/constants" + operatorconstants "github.com/stolostron/multicluster-global-hub/operator/pkg/constants" "github.com/stolostron/multicluster-global-hub/operator/pkg/deployer" - commonconstants "github.com/stolostron/multicluster-global-hub/pkg/constants" + "github.com/stolostron/multicluster-global-hub/pkg/constants" "github.com/stolostron/multicluster-global-hub/pkg/utils" ) @@ -304,48 +304,48 @@ func GetResources(component string, advanced *v1alpha4.AdvancedConfig) *corev1.R limits := corev1.ResourceList{} switch component { - case constants.Grafana: - requests[corev1.ResourceName(corev1.ResourceMemory)] = resource.MustParse(constants.GrafanaMemoryRequest) - requests[corev1.ResourceName(corev1.ResourceCPU)] = resource.MustParse(constants.GrafanaCPURequest) - limits[corev1.ResourceName(corev1.ResourceMemory)] = resource.MustParse(constants.GrafanaMemoryLimit) - limits[corev1.ResourceName(corev1.ResourceCPU)] = resource.MustParse(constants.GrafanaCPULimit) + case operatorconstants.Grafana: + requests[corev1.ResourceName(corev1.ResourceMemory)] = resource.MustParse(operatorconstants.GrafanaMemoryRequest) + requests[corev1.ResourceName(corev1.ResourceCPU)] = resource.MustParse(operatorconstants.GrafanaCPURequest) + limits[corev1.ResourceName(corev1.ResourceMemory)] = resource.MustParse(operatorconstants.GrafanaMemoryLimit) + limits[corev1.ResourceName(corev1.ResourceCPU)] = resource.MustParse(operatorconstants.GrafanaCPULimit) if advanced != nil && advanced.Grafana != nil { setResourcesFromCR(advanced.Grafana.Resources, requests, limits) } - case constants.Postgres: - requests[corev1.ResourceName(corev1.ResourceMemory)] = resource.MustParse(constants.PostgresMemoryRequest) - requests[corev1.ResourceName(corev1.ResourceCPU)] = resource.MustParse(constants.PostgresCPURequest) - limits[corev1.ResourceName(corev1.ResourceMemory)] = resource.MustParse(constants.PostgresMemoryLimit) + case operatorconstants.Postgres: + requests[corev1.ResourceName(corev1.ResourceMemory)] = resource.MustParse(operatorconstants.PostgresMemoryRequest) + requests[corev1.ResourceName(corev1.ResourceCPU)] = resource.MustParse(operatorconstants.PostgresCPURequest) + limits[corev1.ResourceName(corev1.ResourceMemory)] = resource.MustParse(operatorconstants.PostgresMemoryLimit) if advanced != nil && advanced.Postgres != nil { setResourcesFromCR(advanced.Postgres.Resources, requests, limits) } - case constants.Manager: - requests[corev1.ResourceName(corev1.ResourceMemory)] = resource.MustParse(constants.ManagerMemoryRequest) - requests[corev1.ResourceName(corev1.ResourceCPU)] = resource.MustParse(constants.ManagerCPURequest) - limits[corev1.ResourceName(corev1.ResourceMemory)] = resource.MustParse(constants.ManagerMemoryLimit) + case operatorconstants.Manager: + requests[corev1.ResourceName(corev1.ResourceMemory)] = resource.MustParse(operatorconstants.ManagerMemoryRequest) + requests[corev1.ResourceName(corev1.ResourceCPU)] = resource.MustParse(operatorconstants.ManagerCPURequest) + limits[corev1.ResourceName(corev1.ResourceMemory)] = resource.MustParse(operatorconstants.ManagerMemoryLimit) if advanced != nil && advanced.Manager != nil { setResourcesFromCR(advanced.Manager.Resources, requests, limits) } - case constants.Agent: - requests[corev1.ResourceName(corev1.ResourceMemory)] = resource.MustParse(constants.AgentMemoryRequest) - requests[corev1.ResourceName(corev1.ResourceCPU)] = resource.MustParse(constants.AgentCPURequest) - limits[corev1.ResourceName(corev1.ResourceMemory)] = resource.MustParse(constants.AgentMemoryLimit) + case operatorconstants.Agent: + requests[corev1.ResourceName(corev1.ResourceMemory)] = resource.MustParse(operatorconstants.AgentMemoryRequest) + requests[corev1.ResourceName(corev1.ResourceCPU)] = resource.MustParse(operatorconstants.AgentCPURequest) + limits[corev1.ResourceName(corev1.ResourceMemory)] = resource.MustParse(operatorconstants.AgentMemoryLimit) if advanced != nil && advanced.Agent != nil { setResourcesFromCR(advanced.Agent.Resources, requests, limits) } - case constants.Kafka: - requests[corev1.ResourceName(corev1.ResourceMemory)] = resource.MustParse(constants.KafkaMemoryRequest) - requests[corev1.ResourceName(corev1.ResourceCPU)] = resource.MustParse(constants.KafkaCPURequest) - limits[corev1.ResourceName(corev1.ResourceMemory)] = resource.MustParse(constants.KafkaMemoryLimit) + case operatorconstants.Kafka: + requests[corev1.ResourceName(corev1.ResourceMemory)] = resource.MustParse(operatorconstants.KafkaMemoryRequest) + requests[corev1.ResourceName(corev1.ResourceCPU)] = resource.MustParse(operatorconstants.KafkaCPURequest) + limits[corev1.ResourceName(corev1.ResourceMemory)] = resource.MustParse(operatorconstants.KafkaMemoryLimit) if advanced != nil && advanced.Kafka != nil { setResourcesFromCR(advanced.Kafka.Resources, requests, limits) } - case constants.Zookeeper: - requests[corev1.ResourceName(corev1.ResourceMemory)] = resource.MustParse(constants.ZookeeperMemoryRequest) - requests[corev1.ResourceName(corev1.ResourceCPU)] = resource.MustParse(constants.ZookeeperCPURequest) - limits[corev1.ResourceName(corev1.ResourceMemory)] = resource.MustParse(constants.ZookeeperMemoryLimit) + case operatorconstants.Zookeeper: + requests[corev1.ResourceName(corev1.ResourceMemory)] = resource.MustParse(operatorconstants.ZookeeperMemoryRequest) + requests[corev1.ResourceName(corev1.ResourceCPU)] = resource.MustParse(operatorconstants.ZookeeperCPURequest) + limits[corev1.ResourceName(corev1.ResourceMemory)] = resource.MustParse(operatorconstants.ZookeeperMemoryLimit) if advanced != nil && advanced.Zookeeper != nil { setResourcesFromCR(advanced.Zookeeper.Resources, requests, limits) } @@ -397,7 +397,7 @@ func RemoveManagedHubClusterFinalizer(ctx context.Context, c client.Client) erro continue } - if ok := controllerutil.RemoveFinalizer(managedHub, commonconstants.GlobalHubCleanupFinalizer); ok { + if ok := controllerutil.RemoveFinalizer(managedHub, constants.GlobalHubCleanupFinalizer); ok { if err := c.Update(ctx, managedHub, &client.UpdateOptions{}); err != nil { return err } @@ -426,8 +426,8 @@ func AnnotateManagedHubCluster(ctx context.Context, c client.Client) error { CopyMap(annotations, managedHub.GetAnnotations()) // set the annotations for the managed hub - orgAnnotations[constants.AnnotationONMulticlusterHub] = "true" - orgAnnotations[constants.AnnotationPolicyONMulticlusterHub] = "true" + orgAnnotations[operatorconstants.AnnotationONMulticlusterHub] = "true" + orgAnnotations[operatorconstants.AnnotationPolicyONMulticlusterHub] = "true" if !equality.Semantic.DeepEqual(annotations, orgAnnotations) { if err := c.Update(ctx, &clusters.Items[idx], &client.UpdateOptions{}); err != nil { return err @@ -446,7 +446,7 @@ func TriggerManagedHubAddons(ctx context.Context, c client.Client, addonManager for i := range clusters.Items { cluster := clusters.Items[i] if !FilterManagedCluster(&cluster) { - addonManager.Trigger(cluster.Name, constants.GHClusterManagementAddonName) + addonManager.Trigger(cluster.Name, operatorconstants.GHClusterManagementAddonName) } } return nil @@ -482,7 +482,7 @@ func ManipulateGlobalHubObjects(objects []*unstructured.Unstructured, if labels == nil { labels = make(map[string]string) } - labels[commonconstants.GlobalHubOwnerLabelKey] = commonconstants.GHOperatorOwnerLabelVal + labels[constants.GlobalHubOwnerLabelKey] = constants.GHOperatorOwnerLabelVal obj.SetLabels(labels) if err := hohDeployer.Deploy(obj); err != nil { diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go index 660b290aa..4ecf75c67 100644 --- a/pkg/constants/constants.go +++ b/pkg/constants/constants.go @@ -49,6 +49,18 @@ const ( ) const ( + // AnnotationAddonHostingClusterName is the annotation for indicating the hosting cluster name in the addon + AnnotationAddonHostingClusterName = "addon.open-cluster-management.io/hosting-cluster-name" + // AnnotationClusterHostingClusterName is the annotation for indicating the hosting cluster name in the cluster + AnnotationClusterHostingClusterName = "import.open-cluster-management.io/hosting-cluster-name" + AnnotationClusterDeployMode = "import.open-cluster-management.io/klusterlet-deploy-mode" + AnnotationClusterKlusterletDeployNamespace = "import.open-cluster-management.io/klusterlet-namespace" + ClusterDeployModeHosted = "Hosted" + ClusterDeployModeDefault = "Default" +) + +const ( + LocalClusterName = "local-cluster" // lock the database LockId = "1" ) diff --git a/test/integration/manager/controller/addons_test.go b/test/integration/manager/controller/addons_test.go new file mode 100644 index 000000000..beca4be3a --- /dev/null +++ b/test/integration/manager/controller/addons_test.go @@ -0,0 +1,122 @@ +package controller + +import ( + "reflect" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/klog" + "open-cluster-management.io/api/addon/v1alpha1" + + "github.com/stolostron/multicluster-global-hub/manager/pkg/addons" + "github.com/stolostron/multicluster-global-hub/pkg/constants" +) + +var addonList = sets.NewString( + "work-manager", + "cluster-proxy", + "managed-serviceaccount", +) + +var newNamespaceConfig = v1alpha1.PlacementStrategy{ + PlacementRef: v1alpha1.PlacementRef{ + Namespace: "open-cluster-management-global-set", + Name: "global", + }, + Configs: []v1alpha1.AddOnConfig{ + { + ConfigReferent: v1alpha1.ConfigReferent{ + Name: "global-hub", + Namespace: constants.GHDefaultNamespace, + }, + ConfigGroupResource: v1alpha1.ConfigGroupResource{ + Group: "addon.open-cluster-management.io", + Resource: "addondeploymentconfigs", + }, + }, + }, +} + +var _ = Describe("addons test", Ordered, func() { + var addonsReconciler *addons.AddonsReconciler + BeforeAll(func() { + addonsReconciler = addons.NewAddonsReconciler(mgr) + err := addonsReconciler.SetupWithManager(mgr) + Expect(err).NotTo(HaveOccurred()) + workManager := &v1alpha1.ClusterManagementAddOn{ + ObjectMeta: metav1.ObjectMeta{ + Name: "work-manager", + Namespace: "default", + }, + Spec: v1alpha1.ClusterManagementAddOnSpec{ + InstallStrategy: v1alpha1.InstallStrategy{ + Type: "Manual", + }, + }, + } + err = mgr.GetClient().Create(ctx, workManager) + Expect(err).NotTo(HaveOccurred()) + + proxy := &v1alpha1.ClusterManagementAddOn{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster-proxy", + Namespace: "default", + }, + Spec: v1alpha1.ClusterManagementAddOnSpec{ + InstallStrategy: v1alpha1.InstallStrategy{ + Type: "Manual", + }, + }, + } + err = mgr.GetClient().Create(ctx, proxy) + Expect(err).NotTo(HaveOccurred()) + + msa := &v1alpha1.ClusterManagementAddOn{ + ObjectMeta: metav1.ObjectMeta{ + Name: "managed-serviceaccount", + Namespace: "default", + }, + Spec: v1alpha1.ClusterManagementAddOnSpec{ + InstallStrategy: v1alpha1.InstallStrategy{ + Type: "Manual", + }, + }, + } + err = mgr.GetClient().Create(ctx, msa) + Expect(err).NotTo(HaveOccurred()) + time.Sleep(5 * time.Second) + }) + + It("addons should be add the new config", func() { + Eventually(func() bool { + cma := &v1alpha1.ClusterManagementAddOn{} + for addonName := range addonList { + err := mgr.GetClient().Get(ctx, types.NamespacedName{ + Namespace: "default", + Name: addonName, + }, cma) + if err != nil { + klog.Errorf("Failed to list ClusterManagementAddOn") + return false + } + + found := false + for _, ps := range cma.Spec.InstallStrategy.Placements { + if reflect.DeepEqual(ps.PlacementRef, newNamespaceConfig.PlacementRef) && + reflect.DeepEqual(ps.Configs, newNamespaceConfig.Configs) { + found = true + } + } + if !found { + klog.Errorf("Can not found expected config in %v", cma.Spec.InstallStrategy) + return false + } + } + return true + }, timeout, interval).Should(BeTrue()) + }) +}) diff --git a/test/integration/manager/webhook/admission_handler_test.go b/test/integration/manager/webhook/admission_handler_test.go index 47e38dfd4..b477a6b81 100644 --- a/test/integration/manager/webhook/admission_handler_test.go +++ b/test/integration/manager/webhook/admission_handler_test.go @@ -4,63 +4,24 @@ package webhook_test import ( - "context" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + addonv1 "github.com/stolostron/klusterlet-addon-controller/pkg/apis/agent/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes/scheme" + "k8s.io/klog" + clusterv1 "open-cluster-management.io/api/cluster/v1" clusterv1beta1 "open-cluster-management.io/api/cluster/v1beta1" placementrulesv1 "open-cluster-management.io/multicloud-operators-subscription/pkg/apis/apps/placementrule/v1" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/manager" - metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" - "sigs.k8s.io/controller-runtime/pkg/webhook" - mgrwebhook "github.com/stolostron/multicluster-global-hub/manager/pkg/webhook" "github.com/stolostron/multicluster-global-hub/pkg/constants" "github.com/stolostron/multicluster-global-hub/pkg/utils" ) var _ = Describe("Multicluster hub manager webhook", func() { - var cancel context.CancelFunc - var c client.Client - Context("Test Placement and placementrule are handled by the global hub manager webhook", Ordered, func() { - BeforeAll(func() { - // add scheme - err := placementrulesv1.AddToScheme(scheme.Scheme) - Expect(err).NotTo(HaveOccurred()) - err = clusterv1beta1.AddToScheme(scheme.Scheme) - Expect(err).NotTo(HaveOccurred()) - - m, err := manager.New(testEnv.Config, manager.Options{ - WebhookServer: webhook.NewServer(webhook.Options{ - Host: testEnv.WebhookInstallOptions.LocalServingHost, - Port: testEnv.WebhookInstallOptions.LocalServingPort, - CertDir: testEnv.WebhookInstallOptions.LocalServingCertDir, - }), - Scheme: scheme.Scheme, - Metrics: metricsserver.Options{ - BindAddress: "0", // disable the metrics serving - }, - }) // we need manager here just to leverage manager.SetFields - Expect(err).NotTo(HaveOccurred()) - - c, err = client.New(testEnv.Config, client.Options{Scheme: scheme.Scheme}) - Expect(err).NotTo(HaveOccurred()) - - server := m.GetWebhookServer() - server.Register("/mutating", &webhook.Admission{ - Handler: mgrwebhook.NewAdmissionHandler(m.GetClient(), m.GetScheme()), - }) - - ctx, cancel = context.WithCancel(context.Background()) - go func() { - _ = m.Start(ctx) - }() - }) It("Should add cluster.open-cluster-management.io/experimental-scheduling-disable annotation to placement", func() { testPlacement := &clusterv1beta1.Placement{ ObjectMeta: metav1.ObjectMeta{ @@ -147,9 +108,79 @@ var _ = Describe("Multicluster hub manager webhook", func() { return placementrule.Spec.SchedulerName != constants.GlobalHubSchedulerName }, 1*time.Second, 5*time.Second).Should(BeTrue()) }) + }) + + Context("Test managedclusters are handled by the global hub manager webhook", Ordered, func() { + It("managedcluster should be added the hosted annotations", func() { + testmanagedcluster := &clusterv1.ManagedCluster{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-mc-", + Namespace: utils.GetDefaultNamespace(), + Labels: map[string]string{}, + Annotations: map[string]string{}, + }, + } - AfterAll(func() { - cancel() + Eventually(func() bool { + if err := c.Create(ctx, testmanagedcluster, &client.CreateOptions{}); err != nil { + return false + } + mc := &clusterv1.ManagedCluster{} + if err := c.Get(ctx, client.ObjectKeyFromObject(testmanagedcluster), mc); err != nil { + return false + } + if mc.Annotations[constants.AnnotationClusterDeployMode] != constants.ClusterDeployModeHosted { + return false + } + if mc.Annotations[constants.AnnotationClusterHostingClusterName] != constants.LocalClusterName { + return false + } + return true + }, 1*time.Second, 5*time.Second).Should(BeTrue()) + }) + }) + + Context("Test klusterletaddonconfig are handled by the global hub manager webhook", Ordered, func() { + It("klusterletaddonconfig should be added the hosted annotations", func() { + klusterletConfig := &addonv1.KlusterletAddonConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + Spec: addonv1.KlusterletAddonConfigSpec{ + ApplicationManagerConfig: addonv1.KlusterletAddonAgentConfigSpec{ + Enabled: true, + }, + PolicyController: addonv1.KlusterletAddonAgentConfigSpec{ + Enabled: true, + }, + CertPolicyControllerConfig: addonv1.KlusterletAddonAgentConfigSpec{ + Enabled: true, + }, + }, + } + + Eventually(func() bool { + if err := c.Create(ctx, klusterletConfig, &client.CreateOptions{}); err != nil { + klog.Errorf("Failed to create klusterletAddonConfig, err:%v", err) + return false + } + kac := &addonv1.KlusterletAddonConfig{} + if err := c.Get(ctx, client.ObjectKeyFromObject(klusterletConfig), kac); err != nil { + klog.Errorf("Failed to get klusterletAddonConfig, err:%v", err) + return false + } + if kac.Spec.PolicyController.Enabled == true { + return false + } + if kac.Spec.ApplicationManagerConfig.Enabled == true { + return false + } + if kac.Spec.CertPolicyControllerConfig.Enabled == true { + return false + } + return true + }, 1*time.Second, 5*time.Second).Should(BeTrue()) }) }) }) diff --git a/test/integration/manager/webhook/admission_suite_test.go b/test/integration/manager/webhook/admission_suite_test.go index 11bdb8da3..0d1a23f76 100644 --- a/test/integration/manager/webhook/admission_suite_test.go +++ b/test/integration/manager/webhook/admission_suite_test.go @@ -21,13 +21,23 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + addonapi "github.com/stolostron/klusterlet-addon-controller/pkg/apis" admissionv1 "k8s.io/api/admissionregistration/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" + clusterv1 "open-cluster-management.io/api/cluster/v1" + clusterv1beta1 "open-cluster-management.io/api/cluster/v1beta1" + placementrulesv1 "open-cluster-management.io/multicloud-operators-subscription/pkg/apis/apps/placementrule/v1" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/manager" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + "sigs.k8s.io/controller-runtime/pkg/webhook" + mgrwebhook "github.com/stolostron/multicluster-global-hub/manager/pkg/webhook" "github.com/stolostron/multicluster-global-hub/pkg/utils" ) @@ -39,6 +49,7 @@ var ( testEnv *envtest.Environment ctx context.Context cancel context.CancelFunc + c client.Client ) func TestControllers(t *testing.T) { @@ -68,14 +79,51 @@ var _ = BeforeSuite(func() { cfg, err = testEnv.Start() Expect(err).NotTo(HaveOccurred()) Expect(cfg).NotTo(BeNil()) + + // add scheme + err = placementrulesv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = clusterv1beta1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = addonapi.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + clusterv1.AddToScheme(scheme.Scheme) + + m, err := manager.New(testEnv.Config, manager.Options{ + WebhookServer: webhook.NewServer(webhook.Options{ + Host: testEnv.WebhookInstallOptions.LocalServingHost, + Port: testEnv.WebhookInstallOptions.LocalServingPort, + CertDir: testEnv.WebhookInstallOptions.LocalServingCertDir, + }), + Scheme: scheme.Scheme, + Metrics: metricsserver.Options{ + BindAddress: "0", // disable the metrics serving + }, + }) // we need manager here just to leverage manager.SetFields + Expect(err).NotTo(HaveOccurred()) + + c, err = client.New(testEnv.Config, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + + server := m.GetWebhookServer() + server.Register("/mutating", &webhook.Admission{ + Handler: mgrwebhook.NewAdmissionHandler(m.GetClient(), m.GetScheme()), + }) + + ctx, cancel = context.WithCancel(context.Background()) + go func() { + _ = m.Start(ctx) + }() }) var _ = AfterSuite(func() { + cancel() Expect(testEnv.Stop()).NotTo(HaveOccurred()) }) func initializeWebhookInEnvironment() { namespacedScopeV1 := admissionv1.NamespacedScope + clusterScope := admissionv1.ClusterScope failedTypeV1 := admissionv1.Fail equivalentTypeV1 := admissionv1.Equivalent noSideEffectsV1 := admissionv1.SideEffectClassNone @@ -108,6 +156,24 @@ func initializeWebhookInEnvironment() { Scope: &namespacedScopeV1, }, }, + { + Operations: []admissionv1.OperationType{"CREATE", "UPDATE"}, + Rule: admissionv1.Rule{ + APIGroups: []string{"agent.open-cluster-management.io"}, + APIVersions: []string{"v1"}, + Resources: []string{"klusterletaddonconfigs"}, + Scope: &namespacedScopeV1, + }, + }, + { + Operations: []admissionv1.OperationType{"CREATE", "UPDATE"}, + Rule: admissionv1.Rule{ + APIGroups: []string{"cluster.open-cluster-management.io"}, + APIVersions: []string{"v1"}, + Resources: []string{"managedclusters"}, + Scope: &clusterScope, + }, + }, }, FailurePolicy: &failedTypeV1, MatchPolicy: &equivalentTypeV1, diff --git a/test/integration/operator/addon/addon_deploy_test.go b/test/integration/operator/addon/addon_deploy_test.go index ddd73f6d0..2f6e7d39b 100644 --- a/test/integration/operator/addon/addon_deploy_test.go +++ b/test/integration/operator/addon/addon_deploy_test.go @@ -163,7 +163,7 @@ var _ = Describe("addon deploy", func() { operatorconstants.GHAgentDeployModeLabelKey: operatorconstants.GHAgentDeployModeDefault, }, map[string]string{ - operatorconstants.AnnotationClusterHostingClusterName: hostingClusterName, + constants.AnnotationClusterHostingClusterName: hostingClusterName, }, []clusterv1.ManagedClusterClaim{}, clusterAvailableCondition) @@ -209,9 +209,9 @@ var _ = Describe("addon deploy", func() { operatorconstants.GHAgentDeployModeLabelKey: operatorconstants.GHAgentDeployModeHosted, }, map[string]string{ - operatorconstants.AnnotationClusterDeployMode: operatorconstants.ClusterDeployModeHosted, - operatorconstants.AnnotationClusterKlusterletDeployNamespace: "open-cluster-management-hub1", - operatorconstants.AnnotationClusterHostingClusterName: hostingClusterName, + constants.AnnotationClusterDeployMode: constants.ClusterDeployModeHosted, + constants.AnnotationClusterKlusterletDeployNamespace: "open-cluster-management-hub1", + constants.AnnotationClusterHostingClusterName: hostingClusterName, }, []clusterv1.ManagedClusterClaim{}, clusterAvailableCondition) @@ -232,7 +232,7 @@ var _ = Describe("addon deploy", func() { }, addon) }, timeout, interval).ShouldNot(HaveOccurred()) - Expect(addon.GetAnnotations()[operatorconstants.AnnotationAddonHostingClusterName]).Should(Equal(hostingClusterName)) + Expect(addon.GetAnnotations()[constants.AnnotationAddonHostingClusterName]).Should(Equal(hostingClusterName)) By("By checking the agent manifestworks are created for the newly created managed cluster") work := &workv1.ManifestWork{} @@ -270,9 +270,9 @@ var _ = Describe("addon deploy", func() { operatorconstants.GHAgentACMHubInstallLabelKey: "", }, map[string]string{ - operatorconstants.AnnotationClusterDeployMode: operatorconstants.ClusterDeployModeHosted, - operatorconstants.AnnotationClusterKlusterletDeployNamespace: "open-cluster-management-hub1", - operatorconstants.AnnotationClusterHostingClusterName: hostingClusterName, + constants.AnnotationClusterDeployMode: constants.ClusterDeployModeHosted, + constants.AnnotationClusterKlusterletDeployNamespace: "open-cluster-management-hub1", + constants.AnnotationClusterHostingClusterName: hostingClusterName, }, []clusterv1.ManagedClusterClaim{ { @@ -298,7 +298,7 @@ var _ = Describe("addon deploy", func() { }, addon) }, timeout, interval).ShouldNot(HaveOccurred()) - Expect(addon.GetAnnotations()[operatorconstants.AnnotationAddonHostingClusterName]).Should(Equal(hostingClusterName)) + Expect(addon.GetAnnotations()[constants.AnnotationAddonHostingClusterName]).Should(Equal(hostingClusterName)) By("By checking the agent manifestworks are created for the newly created managed cluster") work := &workv1.ManifestWork{} @@ -351,7 +351,7 @@ var _ = Describe("addon deploy", func() { []clusterv1.ManagedClusterClaim{}, ) By("By preparing a local cluster") - clusterName4 := operatorconstants.LocalClusterName + clusterName4 := constants.LocalClusterName prepareCluster(clusterName4, map[string]string{ "vendor": "OpenShift", operatorconstants.GHAgentDeployModeLabelKey: operatorconstants.GHAgentDeployModeDefault, diff --git a/test/manifest/crd/0000_00_addon.open-cluster-management.io_clustermanagementaddons.crd.yaml b/test/manifest/crd/0000_00_addon.open-cluster-management.io_clustermanagementaddons.crd.yaml index 6326a7b48..66ca8e609 100644 --- a/test/manifest/crd/0000_00_addon.open-cluster-management.io_clustermanagementaddons.crd.yaml +++ b/test/manifest/crd/0000_00_addon.open-cluster-management.io_clustermanagementaddons.crd.yaml @@ -8,66 +8,667 @@ spec: kind: ClusterManagementAddOn listKind: ClusterManagementAddOnList plural: clustermanagementaddons + shortNames: + - cma + - cmas singular: clustermanagementaddon - scope: Cluster preserveUnknownFields: false + scope: Cluster versions: - - additionalPrinterColumns: - - jsonPath: .spec.addOnMeta.displayName - name: DISPLAY NAME - type: string - - jsonPath: .spec.addOnConfiguration.crdName - name: CRD NAME - type: string - name: v1alpha1 - schema: - openAPIV3Schema: - description: ClusterManagementAddOn represents the registration of an add-on to the cluster manager. This resource allows the user to discover which add-on is available for the cluster manager and also provides metadata information about the add-on. This resource also provides a linkage to ManagedClusterAddOn, the name of the ClusterManagementAddOn resource will be used for the namespace-scoped ManagedClusterAddOn resource. ClusterManagementAddOn is a cluster-scoped resource. - type: object - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: spec represents a desired configuration for the agent on the cluster management add-on. - type: object - properties: - addOnConfiguration: - description: addOnConfiguration is a reference to configuration information for the add-on. In scenario where a multiple add-ons share the same add-on CRD, multiple ClusterManagementAddOn resources need to be created and reference the same AddOnConfiguration. + - additionalPrinterColumns: + - jsonPath: .spec.addOnMeta.displayName + name: DISPLAY NAME + type: string + - jsonPath: .spec.addOnConfiguration.crdName + name: CRD NAME + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: ClusterManagementAddOn represents the registration of an add-on + to the cluster manager. This resource allows you to discover which add-ons + are available for the cluster manager and provides metadata information + about the add-ons. The ClusterManagementAddOn name is used for the namespace-scoped + ManagedClusterAddOn resource. ClusterManagementAddOn is a cluster-scoped + resource. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: spec represents a desired configuration for the agent on + the cluster management add-on. + properties: + addOnConfiguration: + description: 'Deprecated: Use supportedConfigs filed instead addOnConfiguration + is a reference to configuration information for the add-on. In scenario + where a multiple add-ons share the same add-on CRD, multiple ClusterManagementAddOn + resources need to be created and reference the same AddOnConfiguration.' + properties: + crName: + description: crName is the name of the CR used to configure instances + of the managed add-on. This field should be configured if add-on + CR have a consistent name across the all of the ManagedCluster + instaces. + type: string + crdName: + description: crdName is the name of the CRD used to configure + instances of the managed add-on. This field should be configured + if the add-on have a CRD that controls the configuration of + the add-on. + type: string + lastObservedGeneration: + description: lastObservedGeneration is the observed generation + of the custom resource for the configuration of the addon. + format: int64 + type: integer + type: object + addOnMeta: + description: addOnMeta is a reference to the metadata information + for the add-on. + properties: + description: + description: description represents the detailed description of + the add-on. + type: string + displayName: + description: displayName represents the name of add-on that will + be displayed. + type: string + type: object + installStrategy: + default: + type: Manual + description: InstallStrategy represents that related ManagedClusterAddOns + should be installed on certain clusters. + properties: + placements: + description: Placements is a list of placement references honored + when install strategy type is Placements. All clusters selected + by these placements will install the addon If one cluster belongs + to multiple placements, it will only apply the strategy defined + later in the order. That is to say, The latter strategy overrides + the previous one. + items: + properties: + configs: + description: Configs is the configuration of managedClusterAddon + during installation. User can override the configuration + by updating the managedClusterAddon directly. + items: + properties: + group: + default: "" + description: group of the add-on configuration. + type: string + name: + description: name of the add-on configuration. + minLength: 1 + type: string + namespace: + description: namespace of the add-on configuration. + If this field is not set, the configuration is in + the cluster scope. + type: string + resource: + description: resource of the add-on configuration. + minLength: 1 + type: string + required: + - name + - resource + type: object + type: array + name: + description: Name is the name of the placement + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the placement + minLength: 1 + type: string + rolloutStrategy: + default: + type: All + description: The rollout strategy to apply addon configurations + change. The rollout strategy only watches the addon configurations + defined in ClusterManagementAddOn. + properties: + all: + description: All defines required fields for RolloutStrategy + type All + properties: + maxFailures: + anyOf: + - type: integer + - type: string + default: 0 + description: MaxFailures is a percentage or number + of clusters in the current rollout that can fail + before proceeding to the next rollout. Fail means + the cluster has a failed status or timeout status + (does not reach successful status after ProgressDeadline). + Once the MaxFailures is breached, the rollout + will stop. MaxFailures is only considered for + rollout types Progressive and ProgressivePerGroup. + For Progressive, this is considered over the total + number of clusters. For ProgressivePerGroup, this + is considered according to the size of the current + group. For both Progressive and ProgressivePerGroup, + the MaxFailures does not apply for MandatoryDecisionGroups, + which tolerate no failures. Default is that no + failures are tolerated. + pattern: ^((100|[0-9]{1,2})%|[0-9]+)$ + x-kubernetes-int-or-string: true + minSuccessTime: + default: "0" + description: MinSuccessTime is a "soak" time. In + other words, the minimum amount of time the workload + applier controller will wait from the start of + each rollout before proceeding (assuming a successful + state has been reached and MaxFailures wasn't + breached). MinSuccessTime is only considered for + rollout types Progressive and ProgressivePerGroup. + The default value is 0 meaning the workload applier + proceeds immediately after a successful state + is reached. MinSuccessTime must be defined in + [0-9h]|[0-9m]|[0-9s] format examples; 2h , 90m + , 360s + type: string + progressDeadline: + default: None + description: ProgressDeadline defines how long workload + applier controller will wait for the workload + to reach a successful state in the cluster. If + the workload does not reach a successful state + after ProgressDeadline, will stop waiting and + workload will be treated as "timeout" and be counted + into MaxFailures. Once the MaxFailures is breached, + the rollout will stop. ProgressDeadline default + value is "None", meaning the workload applier + will wait for a successful state indefinitely. + ProgressDeadline must be defined in [0-9h]|[0-9m]|[0-9s] + format examples; 2h , 90m , 360s + pattern: ^(([0-9])+[h|m|s])|None$ + type: string + type: object + progressive: + description: Progressive defines required fields for + RolloutStrategy type Progressive + properties: + mandatoryDecisionGroups: + description: List of the decision groups names or + indexes to apply the workload first and fail if + workload did not reach successful state. GroupName + or GroupIndex must match with the decisionGroups + defined in the placement's decisionStrategy + items: + description: MandatoryDecisionGroup set the decision + group name or group index. GroupName is considered + first to select the decisionGroups then GroupIndex. + properties: + groupIndex: + description: GroupIndex of the decision group + should match the placementDecisions label + value with label key cluster.open-cluster-management.io/decision-group-index + format: int32 + type: integer + groupName: + description: GroupName of the decision group + should match the placementDecisions label + value with label key cluster.open-cluster-management.io/decision-group-name + type: string + type: object + type: array + maxConcurrency: + anyOf: + - type: integer + - type: string + description: MaxConcurrency is the max number of + clusters to deploy workload concurrently. The + default value for MaxConcurrency is determined + from the clustersPerDecisionGroup defined in the + placement->DecisionStrategy. + pattern: ^((100|[0-9]{1,2})%|[0-9]+)$ + x-kubernetes-int-or-string: true + maxFailures: + anyOf: + - type: integer + - type: string + default: 0 + description: MaxFailures is a percentage or number + of clusters in the current rollout that can fail + before proceeding to the next rollout. Fail means + the cluster has a failed status or timeout status + (does not reach successful status after ProgressDeadline). + Once the MaxFailures is breached, the rollout + will stop. MaxFailures is only considered for + rollout types Progressive and ProgressivePerGroup. + For Progressive, this is considered over the total + number of clusters. For ProgressivePerGroup, this + is considered according to the size of the current + group. For both Progressive and ProgressivePerGroup, + the MaxFailures does not apply for MandatoryDecisionGroups, + which tolerate no failures. Default is that no + failures are tolerated. + pattern: ^((100|[0-9]{1,2})%|[0-9]+)$ + x-kubernetes-int-or-string: true + minSuccessTime: + default: "0" + description: MinSuccessTime is a "soak" time. In + other words, the minimum amount of time the workload + applier controller will wait from the start of + each rollout before proceeding (assuming a successful + state has been reached and MaxFailures wasn't + breached). MinSuccessTime is only considered for + rollout types Progressive and ProgressivePerGroup. + The default value is 0 meaning the workload applier + proceeds immediately after a successful state + is reached. MinSuccessTime must be defined in + [0-9h]|[0-9m]|[0-9s] format examples; 2h , 90m + , 360s + type: string + progressDeadline: + default: None + description: ProgressDeadline defines how long workload + applier controller will wait for the workload + to reach a successful state in the cluster. If + the workload does not reach a successful state + after ProgressDeadline, will stop waiting and + workload will be treated as "timeout" and be counted + into MaxFailures. Once the MaxFailures is breached, + the rollout will stop. ProgressDeadline default + value is "None", meaning the workload applier + will wait for a successful state indefinitely. + ProgressDeadline must be defined in [0-9h]|[0-9m]|[0-9s] + format examples; 2h , 90m , 360s + pattern: ^(([0-9])+[h|m|s])|None$ + type: string + type: object + progressivePerGroup: + description: ProgressivePerGroup defines required fields + for RolloutStrategy type ProgressivePerGroup + properties: + mandatoryDecisionGroups: + description: List of the decision groups names or + indexes to apply the workload first and fail if + workload did not reach successful state. GroupName + or GroupIndex must match with the decisionGroups + defined in the placement's decisionStrategy + items: + description: MandatoryDecisionGroup set the decision + group name or group index. GroupName is considered + first to select the decisionGroups then GroupIndex. + properties: + groupIndex: + description: GroupIndex of the decision group + should match the placementDecisions label + value with label key cluster.open-cluster-management.io/decision-group-index + format: int32 + type: integer + groupName: + description: GroupName of the decision group + should match the placementDecisions label + value with label key cluster.open-cluster-management.io/decision-group-name + type: string + type: object + type: array + maxFailures: + anyOf: + - type: integer + - type: string + default: 0 + description: MaxFailures is a percentage or number + of clusters in the current rollout that can fail + before proceeding to the next rollout. Fail means + the cluster has a failed status or timeout status + (does not reach successful status after ProgressDeadline). + Once the MaxFailures is breached, the rollout + will stop. MaxFailures is only considered for + rollout types Progressive and ProgressivePerGroup. + For Progressive, this is considered over the total + number of clusters. For ProgressivePerGroup, this + is considered according to the size of the current + group. For both Progressive and ProgressivePerGroup, + the MaxFailures does not apply for MandatoryDecisionGroups, + which tolerate no failures. Default is that no + failures are tolerated. + pattern: ^((100|[0-9]{1,2})%|[0-9]+)$ + x-kubernetes-int-or-string: true + minSuccessTime: + default: "0" + description: MinSuccessTime is a "soak" time. In + other words, the minimum amount of time the workload + applier controller will wait from the start of + each rollout before proceeding (assuming a successful + state has been reached and MaxFailures wasn't + breached). MinSuccessTime is only considered for + rollout types Progressive and ProgressivePerGroup. + The default value is 0 meaning the workload applier + proceeds immediately after a successful state + is reached. MinSuccessTime must be defined in + [0-9h]|[0-9m]|[0-9s] format examples; 2h , 90m + , 360s + type: string + progressDeadline: + default: None + description: ProgressDeadline defines how long workload + applier controller will wait for the workload + to reach a successful state in the cluster. If + the workload does not reach a successful state + after ProgressDeadline, will stop waiting and + workload will be treated as "timeout" and be counted + into MaxFailures. Once the MaxFailures is breached, + the rollout will stop. ProgressDeadline default + value is "None", meaning the workload applier + will wait for a successful state indefinitely. + ProgressDeadline must be defined in [0-9h]|[0-9m]|[0-9s] + format examples; 2h , 90m , 360s + pattern: ^(([0-9])+[h|m|s])|None$ + type: string + type: object + type: + default: All + enum: + - All + - Progressive + - ProgressivePerGroup + type: string + type: object + required: + - name + - namespace + type: object + type: array + x-kubernetes-list-map-keys: + - namespace + - name + x-kubernetes-list-type: map + type: + default: Manual + description: 'Type is the type of the install strategy, it can + be: - Manual: no automatic install - Placements: install to + clusters selected by placements.' + enum: + - Manual + - Placements + type: string + type: object + supportedConfigs: + description: supportedConfigs is a list of configuration types supported + by add-on. An empty list means the add-on does not require configurations. + The default is an empty list + items: + description: ConfigMeta represents a collection of metadata information + for add-on configuration. + properties: + defaultConfig: + description: defaultConfig represents the namespace and name + of the default add-on configuration. In scenario where all + add-ons have a same configuration. + properties: + name: + description: name of the add-on configuration. + minLength: 1 + type: string + namespace: + description: namespace of the add-on configuration. If this + field is not set, the configuration is in the cluster + scope. + type: string + required: + - name + type: object + group: + default: "" + description: group of the add-on configuration. + type: string + resource: + description: resource of the add-on configuration. + minLength: 1 + type: string + required: + - resource type: object + type: array + x-kubernetes-list-map-keys: + - group + - resource + x-kubernetes-list-type: map + type: object + status: + description: status represents the current status of cluster management + add-on. + properties: + defaultconfigReferences: + description: defaultconfigReferences is a list of current add-on default + configuration references. + items: + description: DefaultConfigReference is a reference to the current + add-on configuration. This resource is used to record the configuration + resource for the current add-on. properties: - crName: - description: crName is the name of the CR used to configure instances of the managed add-on. This field should be configured if add-on CR have a consistent name across the all of the ManagedCluster instaces. + desiredConfig: + description: desiredConfig record the desired config spec hash. + properties: + name: + description: name of the add-on configuration. + minLength: 1 + type: string + namespace: + description: namespace of the add-on configuration. If this + field is not set, the configuration is in the cluster + scope. + type: string + specHash: + description: spec hash for an add-on configuration. + type: string + required: + - name + type: object + group: + default: "" + description: group of the add-on configuration. type: string - crdName: - description: crdName is the name of the CRD used to configure instances of the managed add-on. This field should be configured if the add-on have a CRD that controls the configuration of the add-on. + resource: + description: resource of the add-on configuration. + minLength: 1 type: string - lastObservedGeneration: - description: lastObservedGeneration is the observed generation of the custom resource for the configuration of the addon. - type: integer - format: int64 - addOnMeta: - description: addOnMeta is a reference to the metadata information for the add-on. + required: + - resource type: object + type: array + installProgressions: + description: installProgression is a list of current add-on configuration + references per placement. + items: properties: - description: - description: description represents the detailed description of the add-on. + conditions: + description: conditions describe the state of the managed and + monitored components for the operator. + items: + description: "Condition contains details for one aspect of + the current state of this API Resource. --- This struct + is intended for direct use as an array at the field path + .status.conditions. For example, \n type FooStatus struct{ + // Represents the observations of a foo's current state. + // Known .status.conditions.type are: \"Available\", \"Progressing\", + and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields + }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should + be when the underlying condition changed. If that is + not known, then using the time when the API field changed + is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, + if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the + current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier + indicating the reason for the condition's last transition. + Producers of specific condition types may define expected + values and meanings for this field, and whether the + values are considered a guaranteed API. The value should + be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across + resources like Available, but because arbitrary conditions + can be useful (see .node.status.conditions), the ability + to deconflict is important. The regex it matches is + (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + configReferences: + description: configReferences is a list of current add-on configuration + references. + items: + description: InstallConfigReference is a reference to the + current add-on configuration. This resource is used to record + the configuration resource for the current add-on. + properties: + desiredConfig: + description: desiredConfig record the desired config name + and spec hash. + properties: + name: + description: name of the add-on configuration. + minLength: 1 + type: string + namespace: + description: namespace of the add-on configuration. + If this field is not set, the configuration is in + the cluster scope. + type: string + specHash: + description: spec hash for an add-on configuration. + type: string + required: + - name + type: object + group: + default: "" + description: group of the add-on configuration. + type: string + lastAppliedConfig: + description: lastAppliedConfig records the config spec + hash when the all the corresponding ManagedClusterAddOn + are applied successfully. + properties: + name: + description: name of the add-on configuration. + minLength: 1 + type: string + namespace: + description: namespace of the add-on configuration. + If this field is not set, the configuration is in + the cluster scope. + type: string + specHash: + description: spec hash for an add-on configuration. + type: string + required: + - name + type: object + lastKnownGoodConfig: + description: lastKnownGoodConfig records the last known + good config spec hash. For fresh install or rollout + with type UpdateAll or RollingUpdate, the lastKnownGoodConfig + is the same as lastAppliedConfig. For rollout with type + RollingUpdateWithCanary, the lastKnownGoodConfig is + the last successfully applied config spec hash of the + canary placement. + properties: + name: + description: name of the add-on configuration. + minLength: 1 + type: string + namespace: + description: namespace of the add-on configuration. + If this field is not set, the configuration is in + the cluster scope. + type: string + specHash: + description: spec hash for an add-on configuration. + type: string + required: + - name + type: object + resource: + description: resource of the add-on configuration. + minLength: 1 + type: string + required: + - resource + type: object + type: array + name: + description: Name is the name of the placement + minLength: 1 type: string - displayName: - description: displayName represents the name of add-on that will be displayed. + namespace: + description: Namespace is the namespace of the placement + minLength: 1 type: string - status: - description: status represents the current status of cluster management add-on. - type: object - served: true - storage: true - subresources: - status: {} + required: + - name + - namespace + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} status: acceptedNames: kind: "" diff --git a/test/manifest/crd/0000_00_agent.open-cluster-management.io_klusterletaddonconfigs_crd.yaml b/test/manifest/crd/0000_00_agent.open-cluster-management.io_klusterletaddonconfigs_crd.yaml new file mode 100644 index 000000000..e273af468 --- /dev/null +++ b/test/manifest/crd/0000_00_agent.open-cluster-management.io_klusterletaddonconfigs_crd.yaml @@ -0,0 +1,289 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: klusterletaddonconfigs.agent.open-cluster-management.io +spec: + group: agent.open-cluster-management.io + names: + kind: KlusterletAddonConfig + listKind: KlusterletAddonConfigList + plural: klusterletaddonconfigs + singular: klusterletaddonconfig + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: KlusterletAddonConfig is the Schema for the klusterletaddonconfigs + API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: KlusterletAddonConfigSpec defines the desired state of KlusterletAddonConfig + properties: + applicationManager: + description: ApplicationManagerConfig defines the configurations of + ApplicationManager addon agent. + properties: + enabled: + description: Enabled is the flag to enable/disable the addon. + default is false. + type: boolean + proxyPolicy: + description: |- + ProxyPolicy defines the policy to set proxy for each addon agent. default is Disabled. + Disabled means that the addon agent pods do not configure the proxy env variables. + OCPGlobalProxy means that the addon agent pods use the cluster-wide proxy config of OCP cluster provisioned by ACM. + CustomProxy means that the addon agent pods use the ProxyConfig specified in KlusterletAddonConfig. + enum: + - Disabled + - OCPGlobalProxy + - CustomProxy + type: string + type: object + certPolicyController: + description: CertPolicyControllerConfig defines the configurations + of CertPolicyController addon agent. + properties: + enabled: + description: Enabled is the flag to enable/disable the addon. + default is false. + type: boolean + proxyPolicy: + description: |- + ProxyPolicy defines the policy to set proxy for each addon agent. default is Disabled. + Disabled means that the addon agent pods do not configure the proxy env variables. + OCPGlobalProxy means that the addon agent pods use the cluster-wide proxy config of OCP cluster provisioned by ACM. + CustomProxy means that the addon agent pods use the ProxyConfig specified in KlusterletAddonConfig. + enum: + - Disabled + - OCPGlobalProxy + - CustomProxy + type: string + type: object + clusterLabels: + additionalProperties: + type: string + description: DEPRECATED in release 2.4 and will be removed in the + future since not used anymore. + type: object + clusterName: + description: DEPRECATED in release 2.4 and will be removed in the + future since not used anymore. + minLength: 1 + type: string + clusterNamespace: + description: DEPRECATED in release 2.4 and will be removed in the + future since not used anymore. + minLength: 1 + type: string + iamPolicyController: + description: DEPRECATED in release 2.11 and will be removed in the + future since not used anymore. + properties: + enabled: + description: Enabled is the flag to enable/disable the addon. + default is false. + type: boolean + proxyPolicy: + description: |- + ProxyPolicy defines the policy to set proxy for each addon agent. default is Disabled. + Disabled means that the addon agent pods do not configure the proxy env variables. + OCPGlobalProxy means that the addon agent pods use the cluster-wide proxy config of OCP cluster provisioned by ACM. + CustomProxy means that the addon agent pods use the ProxyConfig specified in KlusterletAddonConfig. + enum: + - Disabled + - OCPGlobalProxy + - CustomProxy + type: string + type: object + policyController: + description: PolicyController defines the configurations of PolicyController + addon agent. + properties: + enabled: + description: Enabled is the flag to enable/disable the addon. + default is false. + type: boolean + proxyPolicy: + description: |- + ProxyPolicy defines the policy to set proxy for each addon agent. default is Disabled. + Disabled means that the addon agent pods do not configure the proxy env variables. + OCPGlobalProxy means that the addon agent pods use the cluster-wide proxy config of OCP cluster provisioned by ACM. + CustomProxy means that the addon agent pods use the ProxyConfig specified in KlusterletAddonConfig. + enum: + - Disabled + - OCPGlobalProxy + - CustomProxy + type: string + type: object + proxyConfig: + description: ProxyConfig defines the cluster-wide proxy configuration + of the OCP managed cluster. + properties: + httpProxy: + description: HTTPProxy is the URL of the proxy for HTTP requests. Empty + means unset and will not result in an env var. + type: string + httpsProxy: + description: HTTPSProxy is the URL of the proxy for HTTPS requests. Empty + means unset and will not result in an env var. + type: string + noProxy: + description: |- + NoProxy is a comma-separated list of hostnames and/or CIDRs for which the proxy should not be used. + Empty means unset and will not result in an env var. + The API Server of Hub cluster should be added here. + And If you scale up workers that are not included in the network defined by the networking.machineNetwork[].cidr + field from the installation configuration, you must add them to this list to prevent connection issues. + type: string + type: object + searchCollector: + description: SearchCollectorConfig defines the configurations of SearchCollector + addon agent. + properties: + enabled: + description: Enabled is the flag to enable/disable the addon. + default is false. + type: boolean + proxyPolicy: + description: |- + ProxyPolicy defines the policy to set proxy for each addon agent. default is Disabled. + Disabled means that the addon agent pods do not configure the proxy env variables. + OCPGlobalProxy means that the addon agent pods use the cluster-wide proxy config of OCP cluster provisioned by ACM. + CustomProxy means that the addon agent pods use the ProxyConfig specified in KlusterletAddonConfig. + enum: + - Disabled + - OCPGlobalProxy + - CustomProxy + type: string + type: object + version: + description: DEPRECATED in release 2.4 and will be removed in the + future since not used anymore. + type: string + required: + - applicationManager + - certPolicyController + - policyController + - searchCollector + type: object + status: + description: KlusterletAddonConfigStatus defines the observed state of + KlusterletAddonConfig + properties: + conditions: + description: Conditions contains condition information for the klusterletAddonConfig + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + ocpGlobalProxy: + description: OCPGlobalProxy is the cluster-wide proxy config of the + OCP cluster provisioned by ACM + properties: + httpProxy: + description: HTTPProxy is the URL of the proxy for HTTP requests. Empty + means unset and will not result in an env var. + type: string + httpsProxy: + description: HTTPSProxy is the URL of the proxy for HTTPS requests. Empty + means unset and will not result in an env var. + type: string + noProxy: + description: |- + NoProxy is a comma-separated list of hostnames and/or CIDRs for which the proxy should not be used. + Empty means unset and will not result in an env var. + The API Server of Hub cluster should be added here. + And If you scale up workers that are not included in the network defined by the networking.machineNetwork[].cidr + field from the installation configuration, you must add them to this list to prevent connection issues. + type: string + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {}