diff --git a/pkg/cmd/addon/cmd.go b/pkg/cmd/addon/cmd.go index 2b4e0bd46..827d57093 100644 --- a/pkg/cmd/addon/cmd.go +++ b/pkg/cmd/addon/cmd.go @@ -4,8 +4,8 @@ package addon import ( "github.com/spf13/cobra" "k8s.io/cli-runtime/pkg/genericclioptions" + "open-cluster-management.io/clusteradm/pkg/cmd/addon/disable" "open-cluster-management.io/clusteradm/pkg/cmd/addon/enable" - genericclioptionsclusteradm "open-cluster-management.io/clusteradm/pkg/genericclioptions" ) @@ -17,6 +17,7 @@ func NewCmd(clusteradmFlags *genericclioptionsclusteradm.ClusteradmFlags, stream } cmd.AddCommand(enable.NewCmd(clusteradmFlags, streams)) + cmd.AddCommand(disable.NewCmd(clusteradmFlags, streams)) return cmd } diff --git a/pkg/cmd/addon/disable/cmd.go b/pkg/cmd/addon/disable/cmd.go new file mode 100644 index 000000000..c3c92e844 --- /dev/null +++ b/pkg/cmd/addon/disable/cmd.go @@ -0,0 +1,52 @@ +// Copyright Contributors to the Open Cluster Management project +package disable + +import ( + "fmt" + + genericclioptionsclusteradm "open-cluster-management.io/clusteradm/pkg/genericclioptions" + clusteradmhelpers "open-cluster-management.io/clusteradm/pkg/helpers" + + "github.com/spf13/cobra" + "k8s.io/cli-runtime/pkg/genericclioptions" +) + +var example = ` +# Disanable addon on a cluster in speccified a namespace +%[1]s addon disable --name application-manager --ns namespace --cluster cluster1,cluster2 +` + +// NewCmd... +func NewCmd(clusteradmFlags *genericclioptionsclusteradm.ClusteradmFlags, streams genericclioptions.IOStreams) *cobra.Command { + o := newOptions(clusteradmFlags, streams) + + cmd := &cobra.Command{ + Use: "disable", + Short: "disable specified addon on specified managed cluster", + Example: fmt.Sprintf(example, clusteradmhelpers.GetExampleHeader()), + SilenceUsage: true, + PreRunE: func(c *cobra.Command, args []string) error { + clusteradmhelpers.DryRunMessage(clusteradmFlags.DryRun) + + return nil + }, + RunE: func(c *cobra.Command, args []string) error { + if err := o.complete(c, args); err != nil { + return err + } + if err := o.validate(); err != nil { + return err + } + if err := o.run(); err != nil { + return err + } + + return nil + }, + } + + cmd.Flags().StringSliceVar(&o.names, "name", []string{}, "Names of the add-on to deploy (comma separated)") + cmd.Flags().StringSliceVar(&o.clusters, "cluster", []string{}, "Names of the managed cluster to deploy the add-on to (comma separated)") + + return cmd +} diff --git a/pkg/cmd/addon/disable/exec.go b/pkg/cmd/addon/disable/exec.go new file mode 100644 index 000000000..e222bfd28 --- /dev/null +++ b/pkg/cmd/addon/disable/exec.go @@ -0,0 +1,100 @@ +// Copyright Contributors to the Open Cluster Management project +package disable + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/api/errors" + + "github.com/spf13/cobra" + apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" + addonclient "open-cluster-management.io/api/client/addon/clientset/versioned" + clusterclientset "open-cluster-management.io/api/client/cluster/clientset/versioned" + "open-cluster-management.io/clusteradm/pkg/helpers" +) + +func (o *Options) complete(cmd *cobra.Command, args []string) (err error) { + klog.V(1).InfoS("disable options:", "dry-run", o.ClusteradmFlags.DryRun, "names", o.names, "clusters", o.clusters) + + return nil +} + +func (o *Options) validate() (err error) { + if len(o.names) == 0 { + return fmt.Errorf("names is missing") + } + + if len(o.clusters) == 0 { + return fmt.Errorf("clusters is misisng") + } + return nil +} + +func (o *Options) run() (err error) { + addons := sets.NewString(o.names...) + clusters := sets.NewString(o.clusters...) + + klog.V(3).InfoS("values:", "addon", addons, "clusters", clusters) + + restConfig, err := o.ClusteradmFlags.KubectlFactory.ToRESTConfig() + if err != nil { + return err + } + clusterClient, err := clusterclientset.NewForConfig(restConfig) + if err != nil { + return err + } + + addonClient, err := addonclient.NewForConfig(restConfig) + if err != nil { + return err + } + + kubeClient, apiExtensionsClient, dynamicClient, err := helpers.GetClients(o.ClusteradmFlags.KubectlFactory) + if err != nil { + return err + } + + return o.runWithClient(clusterClient, addonClient, kubeClient, apiExtensionsClient, dynamicClient, o.ClusteradmFlags.DryRun, addons.List(), clusters.List()) +} + +func (o *Options) runWithClient(clusterClient clusterclientset.Interface, + addonClient addonclient.Interface, + kubeClient kubernetes.Interface, + apiExtensionsClient apiextensionsclient.Interface, + dynamicClient dynamic.Interface, + dryRun bool, + addons []string, + clusters []string) error { + + for _, clusterName := range clusters { + _, err := clusterClient.ClusterV1().ManagedClusters().Get(context.TODO(), + clusterName, + metav1.GetOptions{}) + if err != nil { + return err + } + } + + for _, addon := range addons { + for _, clusterName := range clusters { + err := addonClient.AddonV1alpha1().ManagedClusterAddOns(clusterName).Delete(context.TODO(), + addon, + metav1.DeleteOptions{}) + if err != nil { + if !errors.IsNotFound(err) { + return err + } + } + fmt.Fprintf(o.Streams.Out, "Undeploying %s add-on in managed cluster: %s.\n", addon, clusterName) + } + } + + return nil +} diff --git a/pkg/cmd/addon/disable/exec_test.go b/pkg/cmd/addon/disable/exec_test.go new file mode 100644 index 000000000..db26b7d45 --- /dev/null +++ b/pkg/cmd/addon/disable/exec_test.go @@ -0,0 +1,127 @@ +// Copyright Contributors to the Open Cluster Management project +package disable + +import ( + "context" + "fmt" + "os" + + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + "k8s.io/cli-runtime/pkg/genericclioptions" + + clusterapiv1 "open-cluster-management.io/api/cluster/v1" + + "open-cluster-management.io/clusteradm/pkg/cmd/addon/enable" + "open-cluster-management.io/clusteradm/pkg/cmd/addon/enable/scenario" + + "open-cluster-management.io/clusteradm/pkg/helpers/apply" +) + +var _ = ginkgo.Describe("addon disable", func() { + var cluster1Name string + var cluster2Name string + var err error + + appMgrAddonName := "application-manager" + + ginkgo.BeforeEach(func() { + cluster1Name = fmt.Sprintf("cluster-%s", rand.String(5)) + cluster2Name = fmt.Sprintf("cluster-%s", rand.String(5)) + }) + + assertCreatingClusters := func(clusterName string) { + ginkgo.By(fmt.Sprintf("Create %s cluster", clusterName)) + + cluster := &clusterapiv1.ManagedCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterName, + }, + } + + _, err = clusterClient.ClusterV1().ManagedClusters().Create(context.Background(), cluster, metav1.CreateOptions{}) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterName, + }, + } + _, err := kubeClient.CoreV1().Namespaces().Create(context.Background(), ns, metav1.CreateOptions{}) + gomega.Expect(err).ToNot(gomega.HaveOccurred(), "creat cluster error") + } + + streams := genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr} + + assertEnableAddon := func(addons []string, clusters []string, ns string) { + + reader := scenario.GetScenarioResourcesReader() + applierBuilder := &apply.ApplierBuilder{} + applier := applierBuilder.WithClient(kubeClient, apiExtensionsClient, dynamicClient).Build() + + for _, addon := range addons { + for _, clus := range clusters { + ginkgo.By(fmt.Sprintf("Enableing %s addon on %s cluster in %s namespce", addon, clus, ns)) + + cai := enable.NewClusterAddonInfo(clus, ns, addon) + _, err := applier.ApplyCustomResources(reader, cai, false, "", "addons/app/addon.yaml") + gomega.Expect(err).ToNot(gomega.HaveOccurred(), "enable addon error") + fmt.Fprintf(streams.Out, "Deploying %s add-on to namespaces %s of managed cluster: %s.\n", addon, ns, clus) + } + } + } + + ginkgo.Context("runWithClient", func() { + + ginkgo.It("Should disable application-manager ManagedClusterAddOn in ManagedCluster namespace successfully", func() { + assertCreatingClusters(cluster1Name) + + addons := []string{appMgrAddonName} + clusters := []string{cluster1Name} + assertEnableAddon([]string{appMgrAddonName}, []string{cluster1Name}, "default") + + o := Options{ + Streams: streams, + } + + err := o.runWithClient(clusterClient, addonClient, kubeClient, apiExtensionsClient, dynamicClient, false, addons, clusters) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + }) + + ginkgo.It("Should disable application-manager ManagedClusterAddOns in each ManagedCluster namespace successfully", func() { + assertCreatingClusters(cluster1Name) + assertCreatingClusters(cluster2Name) + + addons := []string{appMgrAddonName} + clusters := []string{cluster1Name, cluster2Name} + assertEnableAddon(addons, clusters, "default") + + o := Options{ + Streams: streams, + } + + err := o.runWithClient(clusterClient, addonClient, kubeClient, apiExtensionsClient, dynamicClient, false, addons, clusters) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + }) + + ginkgo.It("Should not disable a ManagedClusterAddOn because ManagedCluster doesn't exist", func() { + assertCreatingClusters(cluster1Name) + + addons := []string{appMgrAddonName} + clusters := []string{cluster1Name} + assertEnableAddon(addons, clusters, "default") + + wrongCluster := "no-such-addon" + wrongClusters := []string{wrongCluster} + o := Options{ + Streams: streams, + } + + err := o.runWithClient(clusterClient, addonClient, kubeClient, apiExtensionsClient, dynamicClient, false, addons, wrongClusters) + gomega.Expect(err).To(gomega.HaveOccurred()) + }) + }) +}) diff --git a/pkg/cmd/addon/disable/options.go b/pkg/cmd/addon/disable/options.go new file mode 100644 index 000000000..709d20bfe --- /dev/null +++ b/pkg/cmd/addon/disable/options.go @@ -0,0 +1,25 @@ +// Copyright Contributors to the Open Cluster Management project +package disable + +import ( + "k8s.io/cli-runtime/pkg/genericclioptions" + genericclioptionsclusteradm "open-cluster-management.io/clusteradm/pkg/genericclioptions" +) + +type Options struct { + //ClusteradmFlags: The generic optiosn from the clusteradm cli-runtime. + ClusteradmFlags *genericclioptionsclusteradm.ClusteradmFlags + //A list of comma separated addon names + names []string + //A list of comma separated cluster names + clusters []string + + Streams genericclioptions.IOStreams +} + +func newOptions(clusteradmFlags *genericclioptionsclusteradm.ClusteradmFlags, streams genericclioptions.IOStreams) *Options { + return &Options{ + ClusteradmFlags: clusteradmFlags, + Streams: streams, + } +} diff --git a/pkg/cmd/addon/disable/suite_test.go b/pkg/cmd/addon/disable/suite_test.go new file mode 100644 index 000000000..cfb9b82e8 --- /dev/null +++ b/pkg/cmd/addon/disable/suite_test.go @@ -0,0 +1,70 @@ +// Copyright Contributors to the Open Cluster Management project +package disable + +import ( + "path/filepath" + "testing" + + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" + + apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/envtest" + + addonv1alpha1client "open-cluster-management.io/api/client/addon/clientset/versioned" + clusterv1client "open-cluster-management.io/api/client/cluster/clientset/versioned" +) + +var testEnv *envtest.Environment +var restConfig *rest.Config +var kubeClient kubernetes.Interface +var apiExtensionsClient apiextensionsclient.Interface +var dynamicClient dynamic.Interface +var clusterClient clusterv1client.Interface +var addonClient addonv1alpha1client.Interface + +func TestIntegrationEnableAddons(t *testing.T) { + gomega.RegisterFailHandler(ginkgo.Fail) + ginkgo.RunSpecs(t, "Integration Disable Addons Suite") +} + +var _ = ginkgo.BeforeSuite(func(done ginkgo.Done) { + ginkgo.By("bootstrapping test environment") + + // start a kube-apiserver + testEnv = &envtest.Environment{ + ErrorIfCRDPathMissing: true, + CRDDirectoryPaths: []string{ + filepath.Join("..", "..", "..", "..", "vendor", "open-cluster-management.io", "api", "cluster", "v1"), + filepath.Join("..", "..", "..", "..", "vendor", "open-cluster-management.io", "api", "addon", "v1alpha1"), + }, + } + + cfg, err := testEnv.Start() + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + gomega.Expect(cfg).ToNot(gomega.BeNil()) + + kubeClient, err = kubernetes.NewForConfig(cfg) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + apiExtensionsClient, err = apiextensionsclient.NewForConfig(cfg) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + dynamicClient, err = dynamic.NewForConfig(cfg) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + clusterClient, err = clusterv1client.NewForConfig(cfg) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + addonClient, err = addonv1alpha1client.NewForConfig(cfg) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + restConfig = cfg + close(done) +}, 60) + +var _ = ginkgo.AfterSuite(func() { + ginkgo.By("tearing down the test environment") + + err := testEnv.Stop() + gomega.Expect(err).ToNot(gomega.HaveOccurred()) +}) diff --git a/test/integration-test.mk b/test/integration-test.mk index 6625a6ea6..985118d24 100644 --- a/test/integration-test.mk +++ b/test/integration-test.mk @@ -30,5 +30,5 @@ clean-integration-test: clean: clean-integration-test test-integration: ensure-kubebuilder-tools - go test -v ./pkg/cmd/addon/enable ./pkg/cmd/install/addons + go test -v ./pkg/cmd/addon/enable ./pkg/cmd/addon/disable ./pkg/cmd/install/addons .PHONY: test-integration