diff --git a/.github/workflows/bootstrap.yaml b/.github/workflows/bootstrap.yaml index b460cd2f..a2572ac4 100644 --- a/.github/workflows/bootstrap.yaml +++ b/.github/workflows/bootstrap.yaml @@ -49,7 +49,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }} - name: uninstall run: | - ./bin/flux uninstall --resources --crds -s --timeout=10m + ./bin/flux uninstall -s --keep-namespace + kubectl delete ns flux-system --timeout=10m --wait=true - name: bootstrap reinstall run: | ./bin/flux bootstrap github --manifests ./manifests/install/ \ diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index de51ddc1..f1f5b2b4 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -195,7 +195,7 @@ jobs: ./bin/flux check - name: flux uninstall run: | - ./bin/flux uninstall --crds --silent --timeout=10m + ./bin/flux uninstall --silent - name: Debug failure if: failure() run: | diff --git a/cmd/flux/main.go b/cmd/flux/main.go index 215a3525..d4410338 100644 --- a/cmd/flux/main.go +++ b/cmd/flux/main.go @@ -41,7 +41,7 @@ var rootCmd = &cobra.Command{ Example: ` # Check prerequisites flux check --pre - # Install the latest version of the toolkit + # Install the latest version of Flux flux install --version=master # Create a source from a public Git repository @@ -88,8 +88,8 @@ var rootCmd = &cobra.Command{ # Delete a GitRepository source flux delete source git webapp-latest - # Uninstall the toolkit and delete CRDs - flux uninstall --crds + # Uninstall Flux and delete CRDs + flux uninstall `, } diff --git a/cmd/flux/uninstall.go b/cmd/flux/uninstall.go index 8d6027f6..0505d55a 100644 --- a/cmd/flux/uninstall.go +++ b/cmd/flux/uninstall.go @@ -22,8 +22,11 @@ import ( "github.com/manifoldco/promptui" "github.com/spf13/cobra" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/fluxcd/flux2/internal/utils" @@ -34,33 +37,27 @@ import ( var uninstallCmd = &cobra.Command{ Use: "uninstall", - Short: "Uninstall the toolkit components", - Long: "The uninstall command removes the namespace, cluster roles, cluster role bindings and CRDs from the cluster.", - Example: ` # Dry-run uninstall of all components - flux uninstall --dry-run --namespace=flux-system + Short: "Uninstall Flux and its custom resource definitions", + Long: "The uninstall command removes the Flux components and the toolkit.fluxcd.io resources from the cluster.", + Example: ` # Uninstall Flux components, its custom resources and namespace + flux uninstall --namespace=flux-system - # Uninstall all components and delete custom resource definitions - flux uninstall --resources --crds --namespace=flux-system + # Uninstall Flux but keep the namespace + flux uninstall --namespace=infra --keep-namespace=true `, RunE: uninstallCmdRun, } type uninstallFlags struct { - crds bool - resources bool - dryRun bool - silent bool + keepNamespace bool + silent bool } var uninstallArgs uninstallFlags func init() { - uninstallCmd.Flags().BoolVar(&uninstallArgs.resources, "resources", true, - "removes custom resources such as Kustomizations, GitRepositories and HelmRepositories") - uninstallCmd.Flags().BoolVar(&uninstallArgs.crds, "crds", false, - "removes all CRDs previously installed") - uninstallCmd.Flags().BoolVar(&uninstallArgs.dryRun, "dry-run", false, - "only print the object that would be deleted") + uninstallCmd.Flags().BoolVar(&uninstallArgs.keepNamespace, "keep-namespace", false, + "skip namespace deletion") uninstallCmd.Flags().BoolVarP(&uninstallArgs.silent, "silent", "s", false, "delete components without asking for confirmation") @@ -68,6 +65,16 @@ func init() { } func uninstallCmdRun(cmd *cobra.Command, args []string) error { + if !uninstallArgs.silent { + prompt := promptui.Prompt{ + Label: "Are you sure you want to delete Flux and its custom resource definitions", + IsConfirm: true, + } + if _, err := prompt.Run(); err != nil { + return fmt.Errorf("aborting") + } + } + ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) defer cancel() @@ -76,96 +83,189 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error { return err } - if !uninstallArgs.dryRun && !uninstallArgs.silent { - prompt := promptui.Prompt{ - Label: fmt.Sprintf("Are you sure you want to delete the %s namespace", rootArgs.namespace), - IsConfirm: true, - } - if _, err := prompt.Run(); err != nil { - return fmt.Errorf("aborting") - } - } + logger.Actionf("deleting components in %s namespace", rootArgs.namespace) + uninstallComponents(ctx, kubeClient, rootArgs.namespace) + + logger.Actionf("deleting toolkit.fluxcd.io finalizers in all namespaces") + uninstallFinalizers(ctx, kubeClient) - dryRun := "--dry-run=server" - deleteResources := uninstallArgs.resources || uninstallArgs.crds + logger.Actionf("deleting toolkit.fluxcd.io custom resource definitions") + uninstallCustomResourceDefinitions(ctx, kubeClient, rootArgs.namespace) - // known kinds with finalizers - namespacedKinds := []string{ - sourcev1.GitRepositoryKind, - sourcev1.HelmRepositoryKind, - sourcev1.BucketKind, + if !uninstallArgs.keepNamespace { + uninstallNamespace(ctx, kubeClient, rootArgs.namespace) } - // suspend bootstrap kustomization to avoid finalizers deadlock - kustomizationName := types.NamespacedName{ - Namespace: rootArgs.namespace, - Name: rootArgs.namespace, + logger.Successf("uninstall finished") + return nil +} + +func uninstallComponents(ctx context.Context, kubeClient client.Client, namespace string) { + selector := client.MatchingLabels{"app.kubernetes.io/instance": namespace} + { + var list appsv1.DeploymentList + if err := kubeClient.List(ctx, &list, client.InNamespace(namespace), selector); err == nil { + for _, r := range list.Items { + if err := kubeClient.Delete(ctx, &r); err != nil { + logger.Failuref("Deployment/%s/%s deletion failed: %s", r.Namespace, r.Name, err.Error()) + } else { + logger.Successf("Deployment/%s/%s deleted", r.Namespace, r.Name) + } + } + } } - var kustomization kustomizev1.Kustomization - err = kubeClient.Get(ctx, kustomizationName, &kustomization) - if err == nil { - kustomization.Spec.Suspend = true - if err := kubeClient.Update(ctx, &kustomization); err != nil { - return fmt.Errorf("unable to suspend kustomization '%s': %w", kustomizationName.String(), err) + { + var list corev1.ServiceList + if err := kubeClient.List(ctx, &list, client.InNamespace(namespace), selector); err == nil { + for _, r := range list.Items { + if err := kubeClient.Delete(ctx, &r); err != nil { + logger.Failuref("Service/%s/%s deletion failed: %s", r.Namespace, r.Name, err.Error()) + } else { + logger.Successf("Service/%s/%s deleted", r.Namespace, r.Name) + } + } } } - if err == nil || apierrors.IsNotFound(err) { - namespacedKinds = append(namespacedKinds, kustomizev1.KustomizationKind) + { + var list corev1.ServiceAccountList + if err := kubeClient.List(ctx, &list, client.InNamespace(namespace), selector); err == nil { + for _, r := range list.Items { + if err := kubeClient.Delete(ctx, &r); err != nil { + logger.Failuref("ServiceAccount/%s/%s deletion failed: %s", r.Namespace, r.Name, err.Error()) + } else { + logger.Successf("ServiceAccount/%s/%s deleted", r.Namespace, r.Name) + } + } + } } - - // add HelmRelease kind to deletion list if exists - var list helmv2.HelmReleaseList - if err := kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace)); err == nil { - namespacedKinds = append(namespacedKinds, helmv2.HelmReleaseKind) + { + var list rbacv1.ClusterRoleList + if err := kubeClient.List(ctx, &list, selector); err == nil { + for _, r := range list.Items { + if err := kubeClient.Delete(ctx, &r); err != nil { + logger.Failuref("ClusterRole/%s deletion failed: %s", r.Name, err.Error()) + } else { + logger.Successf("ClusterRole/%s deleted", r.Name) + } + } + } } - - if deleteResources { - logger.Actionf("uninstalling custom resources") - for _, kind := range namespacedKinds { - if err := deleteAll(ctx, kind, uninstallArgs.dryRun); err != nil { - logger.Failuref("kubectl: %s", err.Error()) + { + var list rbacv1.ClusterRoleBindingList + if err := kubeClient.List(ctx, &list, selector); err == nil { + for _, r := range list.Items { + if err := kubeClient.Delete(ctx, &r); err != nil { + logger.Failuref("ClusterRoleBinding/%s deletion failed: %s", r.Name, err.Error()) + } else { + logger.Successf("ClusterRoleBinding/%s deleted", r.Name) + } } } } +} - var kinds []string - if uninstallArgs.crds { - kinds = append(kinds, "crds") +func uninstallFinalizers(ctx context.Context, kubeClient client.Client) { + { + var list sourcev1.GitRepositoryList + if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil { + for _, r := range list.Items { + r.Finalizers = []string{} + if err := kubeClient.Update(ctx, &r); err != nil { + logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error()) + } else { + logger.Successf("%s/%s/%s finalizers deleted", r.Kind, r.Namespace, r.Name) + } + } + } } - - kinds = append(kinds, "clusterroles,clusterrolebindings", "namespace") - - logger.Actionf("uninstalling components") - - for _, kind := range kinds { - kubectlArgs := []string{ - "delete", kind, - "-l", fmt.Sprintf("app.kubernetes.io/instance=%s", rootArgs.namespace), - "--ignore-not-found", "--timeout", rootArgs.timeout.String(), + { + var list sourcev1.HelmRepositoryList + if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil { + for _, r := range list.Items { + r.Finalizers = []string{} + if err := kubeClient.Update(ctx, &r); err != nil { + logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error()) + } else { + logger.Successf("%s/%s/%s finalizers deleted", r.Kind, r.Namespace, r.Name) + } + } + } + } + { + var list sourcev1.HelmChartList + if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil { + for _, r := range list.Items { + r.Finalizers = []string{} + if err := kubeClient.Update(ctx, &r); err != nil { + logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error()) + } else { + logger.Successf("%s/%s/%s finalizers deleted", r.Kind, r.Namespace, r.Name) + } + } + } + } + { + var list sourcev1.BucketList + if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil { + for _, r := range list.Items { + r.Finalizers = []string{} + if err := kubeClient.Update(ctx, &r); err != nil { + logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error()) + } else { + logger.Successf("%s/%s/%s finalizers deleted", r.Kind, r.Namespace, r.Name) + } + } } - if uninstallArgs.dryRun { - kubectlArgs = append(kubectlArgs, dryRun) + } + { + var list kustomizev1.KustomizationList + if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil { + for _, r := range list.Items { + r.Finalizers = []string{} + if err := kubeClient.Update(ctx, &r); err != nil { + logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error()) + } else { + logger.Successf("%s/%s/%s finalizers deleted", r.Kind, r.Namespace, r.Name) + } + } } - if _, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...); err != nil { - return fmt.Errorf("uninstall failed: %w", err) + } + { + var list helmv2.HelmReleaseList + if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil { + for _, r := range list.Items { + r.Finalizers = []string{} + if err := kubeClient.Update(ctx, &r); err != nil { + logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error()) + } else { + logger.Successf("%s/%s/%s finalizers deleted", r.Kind, r.Namespace, r.Name) + } + } } } - - logger.Successf("uninstall finished") - return nil } -func deleteAll(ctx context.Context, kind string, dryRun bool) error { - kubectlArgs := []string{ - "delete", kind, "--ignore-not-found", - "--all", "--all-namespaces", - "--timeout", rootArgs.timeout.String(), +func uninstallCustomResourceDefinitions(ctx context.Context, kubeClient client.Client, namespace string) { + selector := client.MatchingLabels{"app.kubernetes.io/instance": namespace} + { + var list apiextensionsv1.CustomResourceDefinitionList + if err := kubeClient.List(ctx, &list, selector); err == nil { + for _, r := range list.Items { + if err := kubeClient.Delete(ctx, &r); err != nil { + logger.Failuref("CustomResourceDefinition/%s deletion failed: %s", r.Name, err.Error()) + } else { + logger.Successf("CustomResourceDefinition/%s deleted", r.Name) + } + } + } } +} - if dryRun { - kubectlArgs = append(kubectlArgs, "--dry-run=server") +func uninstallNamespace(ctx context.Context, kubeClient client.Client, namespace string) { + ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} + if err := kubeClient.Delete(ctx, &ns); err != nil { + logger.Failuref("Namespace/%s deletion failed: %s", namespace, err.Error()) + } else { + logger.Successf("Namespace/%s deleted", namespace) } - - _, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...) - return err } diff --git a/docs/cmd/flux.md b/docs/cmd/flux.md index 6686573d..e8642943 100644 --- a/docs/cmd/flux.md +++ b/docs/cmd/flux.md @@ -12,7 +12,7 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way. # Check prerequisites flux check --pre - # Install the latest version of the toolkit + # Install the latest version of Flux flux install --version=master # Create a source from a public Git repository @@ -59,8 +59,8 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way. # Delete a GitRepository source flux delete source git webapp-latest - # Uninstall the toolkit and delete CRDs - flux uninstall --crds + # Uninstall Flux and delete CRDs + flux uninstall ``` @@ -88,5 +88,5 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way. * [flux reconcile](flux_reconcile.md) - Reconcile sources and resources * [flux resume](flux_resume.md) - Resume suspended resources * [flux suspend](flux_suspend.md) - Suspend resources -* [flux uninstall](flux_uninstall.md) - Uninstall the toolkit components +* [flux uninstall](flux_uninstall.md) - Uninstall Flux and its custom resource definitions diff --git a/docs/cmd/flux_uninstall.md b/docs/cmd/flux_uninstall.md index e0c72bc9..89a383ea 100644 --- a/docs/cmd/flux_uninstall.md +++ b/docs/cmd/flux_uninstall.md @@ -1,10 +1,10 @@ ## flux uninstall -Uninstall the toolkit components +Uninstall Flux and its custom resource definitions ### Synopsis -The uninstall command removes the namespace, cluster roles, cluster role bindings and CRDs from the cluster. +The uninstall command removes the Flux components and the toolkit.fluxcd.io resources from the cluster. ``` flux uninstall [flags] @@ -13,22 +13,20 @@ flux uninstall [flags] ### Examples ``` - # Dry-run uninstall of all components - flux uninstall --dry-run --namespace=flux-system + # Uninstall Flux components, its custom resources and namespace + flux uninstall --namespace=flux-system - # Uninstall all components and delete custom resource definitions - flux uninstall --resources --crds --namespace=flux-system + # Uninstall Flux but keep the namespace + flux uninstall --namespace=infra --keep-namespace=true ``` ### Options ``` - --crds removes all CRDs previously installed - --dry-run only print the object that would be deleted - -h, --help help for uninstall - --resources removes custom resources such as Kustomizations, GitRepositories and HelmRepositories (default true) - -s, --silent delete components without asking for confirmation + -h, --help help for uninstall + --keep-namespace skip namespace deletion + -s, --silent delete components without asking for confirmation ``` ### Options inherited from parent commands diff --git a/docs/guides/installation.md b/docs/guides/installation.md index 152335f2..5608f19a 100644 --- a/docs/guides/installation.md +++ b/docs/guides/installation.md @@ -608,11 +608,27 @@ kustomize build https://github.com/fluxcd/flux2/manifests/install?ref=main | kub ## Uninstall -You can uninstall the Flux components with: +You can uninstall Flux with: ```sh -flux uninstall --crds +flux uninstall --namespace=flux-system ``` -The above command will delete the custom resources definitions, the -controllers, and the namespace where they were installed. +The above command performs the following operations: + +- deletes Flux components (deployments and services) +- deletes Flux RBAC (service accounts, cluster roles and cluster role bindings) +- removes the Kubernetes finalizers from Flux custom resources +- deletes Flux custom resource definitions and custom resources +- deletes the namespace where Flux was installed + +If you've installed Flux in a namespace that you wish to preserve, you +can skip the namespace deletion with: + +```sh +flux uninstall --namespace=infra --keep-namespace +``` + +!!! hint + Note that the `uninstall` command will not remove any Kubernetes objects + or Helm releases that were reconciled on the cluster by Flux. diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 1113ddda..0acddf35 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -21,7 +21,6 @@ import ( "bytes" "context" "fmt" - "github.com/fluxcd/flux2/pkg/manifestgen/install" "io" "io/ioutil" "os" @@ -32,8 +31,10 @@ import ( "text/template" "github.com/olekukonko/tablewriter" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiruntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" @@ -44,6 +45,7 @@ import ( kustypes "sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/yaml" + "github.com/fluxcd/flux2/pkg/manifestgen/install" helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" imageautov1 "github.com/fluxcd/image-automation-controller/api/v1alpha1" imagereflectv1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1" @@ -163,8 +165,10 @@ func KubeClient(kubeConfigPath string, kubeContext string) (client.Client, error } scheme := apiruntime.NewScheme() + _ = apiextensionsv1.AddToScheme(scheme) _ = corev1.AddToScheme(scheme) _ = rbacv1.AddToScheme(scheme) + _ = appsv1.AddToScheme(scheme) _ = sourcev1.AddToScheme(scheme) _ = kustomizev1.AddToScheme(scheme) _ = helmv2.AddToScheme(scheme)