diff --git a/.github/workflows/bootstrap.yaml b/.github/workflows/bootstrap.yaml index b460cd2f006ec32d247bc467434ae2645b684e08..a2572ac48c56b81f9d4a9f4f8d19618d9bbc0d86 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 de51ddc1243752cb0d587b7b8a101df2ea33808f..f1f5b2b4b3dc90f4d59809e34abb668ffcc0eaf9 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 215a3525f8027cd01a5d3b00d3cbd3e7956de8db..d44103386e6c2d635da324f32b1af82ad63bceed 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 8d6027f620717157c66893411e369b5d634e9c50..f6826b269a4fc41b9c7dea8a6726415a46fc4a30 100644 --- a/cmd/flux/uninstall.go +++ b/cmd/flux/uninstall.go @@ -22,8 +22,12 @@ 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" + networkingv1 "k8s.io/api/networking/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 +38,30 @@ 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 + dryRun 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.keepNamespace, "keep-namespace", false, + "skip namespace deletion") uninstallCmd.Flags().BoolVar(&uninstallArgs.dryRun, "dry-run", false, - "only print the object that would be deleted") + "only print the objects that would be deleted") uninstallCmd.Flags().BoolVarP(&uninstallArgs.silent, "silent", "s", false, "delete components without asking for confirmation") @@ -68,17 +69,9 @@ func init() { } func uninstallCmdRun(cmd *cobra.Command, args []string) error { - ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) - defer cancel() - - kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext) - if err != nil { - 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), + Label: "Are you sure you want to delete Flux and its custom resource definitions", IsConfirm: true, } if _, err := prompt.Run(); err != nil { @@ -86,86 +79,235 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error { } } - dryRun := "--dry-run=server" - deleteResources := uninstallArgs.resources || uninstallArgs.crds + ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) + defer cancel() - // known kinds with finalizers - namespacedKinds := []string{ - sourcev1.GitRepositoryKind, - sourcev1.HelmRepositoryKind, - sourcev1.BucketKind, + kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext) + if err != nil { + return err } - // suspend bootstrap kustomization to avoid finalizers deadlock - kustomizationName := types.NamespacedName{ - Namespace: rootArgs.namespace, - Name: rootArgs.namespace, + logger.Actionf("deleting components in %s namespace", rootArgs.namespace) + uninstallComponents(ctx, kubeClient, rootArgs.namespace, uninstallArgs.dryRun) + + logger.Actionf("deleting toolkit.fluxcd.io finalizers in all namespaces") + uninstallFinalizers(ctx, kubeClient, uninstallArgs.dryRun) + + logger.Actionf("deleting toolkit.fluxcd.io custom resource definitions") + uninstallCustomResourceDefinitions(ctx, kubeClient, rootArgs.namespace, uninstallArgs.dryRun) + + if !uninstallArgs.keepNamespace { + uninstallNamespace(ctx, kubeClient, rootArgs.namespace, uninstallArgs.dryRun) } - 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) + + logger.Successf("uninstall finished") + return nil +} + +func uninstallComponents(ctx context.Context, kubeClient client.Client, namespace string, dryRun bool) { + opts, dryRunStr := getDeleteOptions(dryRun) + 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, opts); err != nil { + logger.Failuref("Deployment/%s/%s deletion failed: %s", r.Namespace, r.Name, err.Error()) + } else { + logger.Successf("Deployment/%s/%s deleted %s", r.Namespace, r.Name, dryRunStr) + } + } } } - if err == nil || apierrors.IsNotFound(err) { - namespacedKinds = append(namespacedKinds, kustomizev1.KustomizationKind) + { + 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, opts); err != nil { + logger.Failuref("Service/%s/%s deletion failed: %s", r.Namespace, r.Name, err.Error()) + } else { + logger.Successf("Service/%s/%s deleted %s", r.Namespace, r.Name, dryRunStr) + } + } + } } - - // 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 networkingv1.NetworkPolicyList + if err := kubeClient.List(ctx, &list, client.InNamespace(namespace), selector); err == nil { + for _, r := range list.Items { + if err := kubeClient.Delete(ctx, &r, opts); err != nil { + logger.Failuref("NetworkPolicy/%s/%s deletion failed: %s", r.Namespace, r.Name, err.Error()) + } else { + logger.Successf("NetworkPolicy/%s/%s deleted %s", r.Namespace, r.Name, dryRunStr) + } + } + } } - - 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 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, opts); err != nil { + logger.Failuref("ServiceAccount/%s/%s deletion failed: %s", r.Namespace, r.Name, err.Error()) + } else { + logger.Successf("ServiceAccount/%s/%s deleted %s", r.Namespace, r.Name, dryRunStr) + } } } } - - var kinds []string - if uninstallArgs.crds { - kinds = append(kinds, "crds") + { + var list rbacv1.ClusterRoleList + if err := kubeClient.List(ctx, &list, selector); err == nil { + for _, r := range list.Items { + if err := kubeClient.Delete(ctx, &r, opts); err != nil { + logger.Failuref("ClusterRole/%s deletion failed: %s", r.Name, err.Error()) + } else { + logger.Successf("ClusterRole/%s deleted %s", r.Name, dryRunStr) + } + } + } } + { + var list rbacv1.ClusterRoleBindingList + if err := kubeClient.List(ctx, &list, selector); err == nil { + for _, r := range list.Items { + if err := kubeClient.Delete(ctx, &r, opts); err != nil { + logger.Failuref("ClusterRoleBinding/%s deletion failed: %s", r.Name, err.Error()) + } else { + logger.Successf("ClusterRoleBinding/%s deleted %s", r.Name, dryRunStr) + } + } + } + } +} - 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(), +func uninstallFinalizers(ctx context.Context, kubeClient client.Client, dryRun bool) { + opts, dryRunStr := getUpdateOptions(dryRun) + { + 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, opts); 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 %s", r.Kind, r.Namespace, r.Name, dryRunStr) + } + } + } + } + { + 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, opts); 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 %s", r.Kind, r.Namespace, r.Name, dryRunStr) + } + } + } + } + { + 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, opts); 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 %s", r.Kind, r.Namespace, r.Name, dryRunStr) + } + } } - if uninstallArgs.dryRun { - kubectlArgs = append(kubectlArgs, dryRun) + } + { + 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, opts); 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 %s", r.Kind, r.Namespace, r.Name, dryRunStr) + } + } } - if _, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...); err != nil { - return fmt.Errorf("uninstall failed: %w", err) + } + { + 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, opts); 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 %s", r.Kind, r.Namespace, r.Name, dryRunStr) + } + } } } + { + 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, opts); 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 %s", r.Kind, r.Namespace, r.Name, dryRunStr) + } + } + } + } +} - logger.Successf("uninstall finished") - return nil +func uninstallCustomResourceDefinitions(ctx context.Context, kubeClient client.Client, namespace string, dryRun bool) { + opts, dryRunStr := getDeleteOptions(dryRun) + 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, opts); err != nil { + logger.Failuref("CustomResourceDefinition/%s deletion failed: %s", r.Name, err.Error()) + } else { + logger.Successf("CustomResourceDefinition/%s deleted %s", r.Name, dryRunStr) + } + } + } + } } -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 uninstallNamespace(ctx context.Context, kubeClient client.Client, namespace string, dryRun bool) { + opts, dryRunStr := getDeleteOptions(dryRun) + ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} + if err := kubeClient.Delete(ctx, &ns, opts); err != nil { + logger.Failuref("Namespace/%s deletion failed: %s", namespace, err.Error()) + } else { + logger.Successf("Namespace/%s deleted %s", namespace, dryRunStr) } +} + +func getDeleteOptions(dryRun bool) (*client.DeleteOptions, string) { + opts := &client.DeleteOptions{} + var dryRunStr string + if dryRun { + client.DryRunAll.ApplyToDelete(opts) + dryRunStr = "(dry run)" + } + + return opts, dryRunStr +} +func getUpdateOptions(dryRun bool) (*client.UpdateOptions, string) { + opts := &client.UpdateOptions{} + var dryRunStr string if dryRun { - kubectlArgs = append(kubectlArgs, "--dry-run=server") + client.DryRunAll.ApplyToUpdate(opts) + dryRunStr = "(dry run)" } - _, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...) - return err + return opts, dryRunStr } diff --git a/docs/cmd/flux.md b/docs/cmd/flux.md index 6686573d4c80ec94246f407734fa25c222da09a4..e8642943ff5270cf14233410277cb151a16c831a 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 e0c72bc9b266045fcaf63d2cbcddd000e0e896fb..73e243c83bdc39d80fcd234487b1762bab176752 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,21 @@ 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 + --dry-run only print the objects that would be deleted + -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 152335f2d0d69dad08e4ae5c585eff08963956b6..fc303e199f525adb24948c6f0aaf1af69a7fff5f 100644 --- a/docs/guides/installation.md +++ b/docs/guides/installation.md @@ -608,11 +608,28 @@ 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 network policies +- 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 1113ddda969c01be330aaf47a773bd4426f06845..5e8f20d5128d22f5579299c3272de932d28d6716 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,11 @@ import ( "text/template" "github.com/olekukonko/tablewriter" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/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 +46,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 +166,11 @@ func KubeClient(kubeConfigPath string, kubeContext string) (client.Client, error } scheme := apiruntime.NewScheme() + _ = apiextensionsv1.AddToScheme(scheme) _ = corev1.AddToScheme(scheme) _ = rbacv1.AddToScheme(scheme) + _ = appsv1.AddToScheme(scheme) + _ = networkingv1.AddToScheme(scheme) _ = sourcev1.AddToScheme(scheme) _ = kustomizev1.AddToScheme(scheme) _ = helmv2.AddToScheme(scheme)