diff --git a/e2e/test_support.go b/e2e/test_support.go index ff29976d5c..5c33ab13b0 100644 --- a/e2e/test_support.go +++ b/e2e/test_support.go @@ -26,6 +26,7 @@ import ( "errors" "fmt" "io/ioutil" + rbacv1 "k8s.io/api/rbac/v1" "os" "os/exec" "strings" @@ -378,7 +379,7 @@ func configmap(ns string, name string) func() *corev1.ConfigMap { cm := corev1.ConfigMap{ TypeMeta: metav1.TypeMeta{ Kind: "ConfigMap", - APIVersion: metav1.SchemeGroupVersion.String(), + APIVersion: corev1.SchemeGroupVersion.String(), }, ObjectMeta: metav1.ObjectMeta{ Namespace: ns, @@ -556,6 +557,98 @@ func scaleOperator(ns string, replicas int32) func() error { } } +func role(ns string) func() *rbacv1.Role { + return func() *rbacv1.Role { + lst := rbacv1.RoleList{ + TypeMeta: metav1.TypeMeta{ + Kind: "Role", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + } + err := testClient.List(testContext, &lst, + k8sclient.InNamespace(ns), + k8sclient.MatchingLabels{ + "app": "camel-k", + }) + if err != nil { + panic(err) + } + if len(lst.Items) == 0 { + return nil + } + return &lst.Items[0] + } +} + +func rolebinding(ns string) func() *rbacv1.RoleBinding { + return func() *rbacv1.RoleBinding { + lst := rbacv1.RoleBindingList{ + TypeMeta: metav1.TypeMeta{ + Kind: "RoleBinding", + APIVersion: metav1.SchemeGroupVersion.String(), + }, + } + err := testClient.List(testContext, &lst, + k8sclient.InNamespace(ns), + k8sclient.MatchingLabels{ + "app": "camel-k", + }) + if err != nil { + panic(err) + } + if len(lst.Items) == 0 { + return nil + } + return &lst.Items[0] + } +} + +func clusterrole(ns string) func() *rbacv1.ClusterRole { + return func() *rbacv1.ClusterRole { + lst := rbacv1.ClusterRoleList{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterRole", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + } + err := testClient.List(testContext, &lst, + k8sclient.InNamespace(ns), + k8sclient.MatchingLabels{ + "app": "camel-k", + }) + if err != nil { + panic(err) + } + if len(lst.Items) == 0 { + return nil + } + return &lst.Items[0] + } +} + +func serviceaccount(ns, name string) func() *corev1.ServiceAccount { + return func() *corev1.ServiceAccount { + lst := corev1.ServiceAccountList{ + TypeMeta: metav1.TypeMeta{ + Kind: "ServiceAccount", + APIVersion: corev1.SchemeGroupVersion.String(), + }, + } + err := testClient.List(testContext, &lst, + k8sclient.InNamespace(ns), + k8sclient.MatchingLabels{ + "app": "camel-k", + }) + if err != nil { + panic(err) + } + if len(lst.Items) == 0 { + return nil + } + return &lst.Items[0] + } +} + /* Tekton */ diff --git a/e2e/uninstall_test.go b/e2e/uninstall_test.go new file mode 100644 index 0000000000..cbe33f0749 --- /dev/null +++ b/e2e/uninstall_test.go @@ -0,0 +1,112 @@ +// +build integration + +// To enable compilation of this file in Goland, go to "Settings -> Go -> Vendoring & Build Tags -> Custom Tags" and add "integration" + +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You 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 e2e + +import ( + "testing" + + . "github.com/onsi/gomega" +) + +func TestBasicUninstall(t *testing.T) { + withNewTestNamespace(t, func(ns string) { + // a successful new installation + Expect(kamel("install", "-n", ns).Execute()).Should(BeNil()) + Eventually(operatorPod(ns)).ShouldNot(BeNil()) + // should be completely removed on uninstall + Expect(kamel("uninstall", "-n", ns).Execute()).Should(BeNil()) + Eventually(role(ns)).Should(BeNil()) + Eventually(rolebinding(ns)).Should(BeNil()) + Eventually(configmap(ns,"camel-k-maven-settings")).Should(BeNil()) + Eventually(clusterrole(ns)).Should(BeNil()) + Eventually(serviceaccount(ns,"camel-k-maven-settings")).Should(BeNil()) + Eventually(operatorPod(ns)).Should(BeNil()) + }) +} + +func TestUninstallSkipOperator(t *testing.T) { + withNewTestNamespace(t, func(ns string) { + // a successful new installation + Expect(kamel("install", "-n", ns).Execute()).Should(BeNil()) + Eventually(operatorPod(ns)).ShouldNot(BeNil()) + // on uninstall it should remove everything except operator + Expect(kamel("uninstall", "-n", ns,"--skip-operator").Execute()).Should(BeNil()) + Eventually(operatorPod(ns)).ShouldNot(BeNil()) + }) +} + +func TestUninstallSkipRole(t *testing.T) { + withNewTestNamespace(t, func(ns string) { + // a successful new installation + Expect(kamel("install", "-n", ns).Execute()).Should(BeNil()) + Eventually(operatorPod(ns)).ShouldNot(BeNil()) + // on uninstall it should remove everything except roles + Expect(kamel("uninstall", "-n", ns,"--skip-roles").Execute()).Should(BeNil()) + Eventually(role(ns)).ShouldNot(BeNil()) + }) +} + +func TestUninstallSkipRoleBinding(t *testing.T) { + withNewTestNamespace(t, func(ns string) { + // a successful new installation + Expect(kamel("install", "-n", ns).Execute()).Should(BeNil()) + Eventually(operatorPod(ns)).ShouldNot(BeNil()) + // on uninstall it should remove everything except role-bindings + Expect(kamel("uninstall", "-n", ns,"--skip-role-bindings").Execute()).Should(BeNil()) + Eventually(rolebinding(ns)).ShouldNot(BeNil()) + }) +} + +func TestUninstallSkipClusterRoles(t *testing.T) { + withNewTestNamespace(t, func(ns string) { + // a successful new installation + Expect(kamel("install", "-n", ns).Execute()).Should(BeNil()) + Eventually(operatorPod(ns)).ShouldNot(BeNil()) + // on uninstall it should remove everything except cluster-roles + Expect(kamel("uninstall", "-n", ns,"--skip-cluster-roles").Execute()).Should(BeNil()) + Eventually(clusterrole(ns)).ShouldNot(BeNil()) + }) +} + +func TestUninstallSkipServiceAccounts(t *testing.T) { + //t.Skip("inconsistent test results ") + withNewTestNamespace(t, func(ns string) { + // a successful new installation + Expect(kamel("install", "-n", ns).Execute()).Should(BeNil()) + Eventually(operatorPod(ns)).ShouldNot(BeNil()) + // on uninstall it should remove everything except cluster-roles + Expect(kamel("uninstall", "-n", ns,"--skip-service-accounts").Execute()).Should(BeNil()) + Eventually(serviceaccount(ns, "camel-k-operator")).ShouldNot(BeNil()) + }) +} + +func TestUninstallSkipIntegrationPlatform(t *testing.T) { + withNewTestNamespace(t, func(ns string) { + // a successful new installation + Expect(kamel("install", "-n", ns).Execute()).Should(BeNil()) + Eventually(operatorPod(ns)).ShouldNot(BeNil()) + // on uninstall it should remove everything except cluster-roles + // NOTE: skip CRDs is also required in addition to skip integration platform + Expect(kamel("uninstall", "-n", ns,"--skip-crd","--skip-integration-platform").Execute()).Should(BeNil()) + Eventually(platform(ns)).ShouldNot(BeNil()) + }) +} \ No newline at end of file diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index a232039e54..97bd7cef57 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -108,6 +108,7 @@ func addKamelSubcommands(cmd *cobra.Command, options *RootCmdOptions) { cmd.AddCommand(cmdOnly(newCmdGet(options))) cmd.AddCommand(cmdOnly(newCmdDelete(options))) cmd.AddCommand(cmdOnly(newCmdInstall(options))) + cmd.AddCommand(cmdOnly(newCmdUninstall(options))) cmd.AddCommand(cmdOnly(newCmdLog(options))) cmd.AddCommand(newCmdKit(options)) cmd.AddCommand(cmdOnly(newCmdReset(options))) diff --git a/pkg/cmd/uninstall.go b/pkg/cmd/uninstall.go new file mode 100644 index 0000000000..3971f3fe6b --- /dev/null +++ b/pkg/cmd/uninstall.go @@ -0,0 +1,303 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You 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 cmd + +import ( + "fmt" + + "k8s.io/client-go/kubernetes" + + "github.com/apache/camel-k/pkg/client" + "github.com/apache/camel-k/pkg/util/kubernetes/customclient" + "github.com/spf13/cobra" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func newCmdUninstall(rootCmdOptions *RootCmdOptions) (*cobra.Command, *uninstallCmdOptions) { + options := uninstallCmdOptions{ + RootCmdOptions: rootCmdOptions, + } + + cmd := cobra.Command{ + Use: "uninstall", + Short: "Uninstall Camel K from a Kubernetes cluster", + Long: `Uninstalls Camel K from a Kubernetes or OpenShift cluster.`, + Run: options.uninstall, + } + + cmd.Flags().BoolVar(&options.skipOperator, "skip-operator", false, "Do not uninstall the Camel-K Operator in the current namespace") + cmd.Flags().BoolVar(&options.skipCrd, "skip-crd", false, "Do not uninstall the Camel-k Custom Resource Definitions (CRD) in the current namespace") + cmd.Flags().BoolVar(&options.skipRoleBindings, "skip-role-bindings", false, "Do not uninstall the Camel-K Role Bindings in the current namespace") + cmd.Flags().BoolVar(&options.skipRoles, "skip-roles", false, "Do not uninstall the Camel-K Roles in the current namespace") + cmd.Flags().BoolVar(&options.skipClusterRoles, "skip-cluster-roles", false, "Do not uninstall the Camel-K Cluster Roles in the current namespace") + cmd.Flags().BoolVar(&options.skipIntegrationPlatform, "skip-integration-platform", false, "Do not uninstall the Camel-K Integration Platform in the current namespace") + cmd.Flags().BoolVar(&options.skipServiceAccounts, "skip-service-accounts", false, "Do not uninstall the Camel-K Service Accounts in the current namespace") + cmd.Flags().BoolVar(&options.skipConfigMaps, "skip-config-maps", false, "Do not uninstall the Camel-K Config Maps in the current namespace") + + // completion support + configureBashAnnotationForFlag( + &cmd, + "context", + map[string][]string{ + cobra.BashCompCustom: {"kamel_kubectl_get_known_integrationcontexts"}, + }, + ) + + return &cmd, &options +} + +type uninstallCmdOptions struct { + *RootCmdOptions + skipOperator bool + skipCrd bool + skipRoleBindings bool + skipRoles bool + skipClusterRoles bool + skipIntegrationPlatform bool + skipServiceAccounts bool + skipConfigMaps bool +} + +var defaultListOptions = metav1.ListOptions{ + LabelSelector: "app=camel-k", +} + +// nolint: gocyclo +func (o *uninstallCmdOptions) uninstall(_ *cobra.Command, _ []string) { + c, err := o.GetCmdClient() + if err != nil { + return + } + + if !o.skipIntegrationPlatform { + if err = o.uninstallIntegrationPlatform(); err != nil { + fmt.Print(err) + return + } + fmt.Printf("Camel-K Integration Platform removed from namespace %s\n", o.Namespace) + } + + if err = o.uninstallClusterWideResources(c); err != nil { + fmt.Print(err) + return + } + fmt.Printf("Camel-K Cluster Wide Resources removed from namespace %s\n", o.Namespace) + + if !o.skipOperator { + if err = o.uninstallOperator(c); err != nil { + fmt.Print(err) + return + } + fmt.Printf("Camel-K Operator removed from namespace %s\n", o.Namespace) + } +} + +func (o *uninstallCmdOptions) uninstallOperator(c client.Client) error { + api := c.AppsV1() + + deployments, err := api.Deployments(o.Namespace).List(defaultListOptions) + if err != nil { + return err + } + + for _, deployment := range deployments.Items { + err := api.Deployments(o.Namespace).Delete(deployment.Name, &metav1.DeleteOptions{}) + if err != nil { + return err + } + } + + return nil +} + +func (o *uninstallCmdOptions) uninstallClusterWideResources(c client.Client) error { + if !o.skipCrd { + if err := o.uninstallCrd(c); err != nil { + return err + } + fmt.Printf("Camel-K Custom Resource Definitions removed from namespace %s\n", o.Namespace) + } + + if !o.skipRoleBindings { + if err := o.uninstallRoleBindings(c); err != nil { + return err + } + fmt.Printf("Camel-K Role Bindings removed from namespace %s\n", o.Namespace) + } + + if !o.skipRoles { + if err := o.uninstallRoles(c); err != nil { + return err + } + fmt.Printf("Camel-K Roles removed from namespace %s\n", o.Namespace) + } + + if !o.skipClusterRoles { + if err := o.uninstallClusterRoles(c); err != nil { + return err + } + fmt.Printf("Camel-K Cluster Roles removed from namespace %s\n", o.Namespace) + } + + if !o.skipServiceAccounts { + if err := o.uninstallServiceAccounts(c); err != nil { + return err + } + fmt.Printf("Camel-K Service Accounts removed from namespace %s\n", o.Namespace) + } + + if !o.skipConfigMaps { + if err := o.uninstallConfigMaps(c); err != nil { + return err + } + fmt.Printf("Camel-K Config Maps removed from namespace %s\n", o.Namespace) + } + + return nil +} + +func (o *uninstallCmdOptions) uninstallCrd(c kubernetes.Interface) error { + restClient, err := customclient.GetClientFor(c, "apiextensions.k8s.io", "v1beta1") + if err != nil { + return err + } + + result := restClient. + Delete(). + Param("labelSelector", "app=camel-k"). + Resource("customresourcedefinitions"). + Do() + + if result.Error() != nil && !k8serrors.IsAlreadyExists(result.Error()) { + return result.Error() + } + + return nil +} + +func (o *uninstallCmdOptions) uninstallRoles(c client.Client) error { + api := c.RbacV1() + + roleBindings, err := api.Roles(o.Namespace).List(defaultListOptions) + if err != nil { + return err + } + + for _, roleBinding := range roleBindings.Items { + err := api.Roles(o.Namespace).Delete(roleBinding.Name, &metav1.DeleteOptions{}) + if err != nil { + return err + } + } + + return nil +} + +func (o *uninstallCmdOptions) uninstallRoleBindings(c client.Client) error { + api := c.RbacV1() + + roleBindings, err := api.RoleBindings(o.Namespace).List(defaultListOptions) + if err != nil { + return err + } + + for _, roleBinding := range roleBindings.Items { + err := api.RoleBindings(o.Namespace).Delete(roleBinding.Name, &metav1.DeleteOptions{}) + if err != nil { + return err + } + } + + return nil +} + +func (o *uninstallCmdOptions) uninstallClusterRoles(c client.Client) error { + api := c.RbacV1() + + clusterRoles, err := api.ClusterRoles().List(defaultListOptions) + if err != nil { + return err + } + + for _, clusterRole := range clusterRoles.Items { + err := api.ClusterRoles().Delete(clusterRole.Name, &metav1.DeleteOptions{}) + if err != nil { + return err + } + } + + return nil +} + +func (o *uninstallCmdOptions) uninstallServiceAccounts(c client.Client) error { + api := c.CoreV1() + + serviceAccountList, err := api.ServiceAccounts(o.Namespace).List(defaultListOptions) + if err != nil { + return err + } + + for _, serviceAccount := range serviceAccountList.Items { + err := api.ServiceAccounts(o.Namespace).Delete(serviceAccount.Name, &metav1.DeleteOptions{}) + if err != nil { + return err + } + } + + return nil +} + +func (o *uninstallCmdOptions) uninstallIntegrationPlatform() error { + api, err := customclient.GetDefaultDynamicClientFor("integrationplatforms", o.Namespace) + if err != nil { + return err + } + + integrationPlatforms, err := api.List(defaultListOptions) + if err != nil { + return err + } + + for _, integrationPlatform := range integrationPlatforms.Items { + err := api.Delete(integrationPlatform.GetName(), &metav1.DeleteOptions{}) + if err != nil { + return err + } + } + + return nil +} + +func (o *uninstallCmdOptions) uninstallConfigMaps(c client.Client) error { + api := c.CoreV1() + + configMapsList, err := api.ConfigMaps(o.Namespace).List(defaultListOptions) + if err != nil { + return err + } + + for _, configMap := range configMapsList.Items { + err := api.ConfigMaps(o.Namespace).Delete(configMap.Name, &metav1.DeleteOptions{}) + if err != nil { + return err + } + } + + return nil +}