Refactor flux uninstall command

- 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
- preserves the Kubernetes objects and Helm releases that were reconciled on the cluster by Flux

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
pull/891/head
Stefan Prodan 4 years ago
parent 7f98cfd506
commit f5ae8f44b4
No known key found for this signature in database
GPG Key ID: 3299AEB0E4085BAF

@ -49,7 +49,8 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
- name: uninstall - name: uninstall
run: | 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 - name: bootstrap reinstall
run: | run: |
./bin/flux bootstrap github --manifests ./manifests/install/ \ ./bin/flux bootstrap github --manifests ./manifests/install/ \

@ -195,7 +195,7 @@ jobs:
./bin/flux check ./bin/flux check
- name: flux uninstall - name: flux uninstall
run: | run: |
./bin/flux uninstall --crds --silent --timeout=10m ./bin/flux uninstall --silent
- name: Debug failure - name: Debug failure
if: failure() if: failure()
run: | run: |

@ -41,7 +41,7 @@ var rootCmd = &cobra.Command{
Example: ` # Check prerequisites Example: ` # Check prerequisites
flux check --pre flux check --pre
# Install the latest version of the toolkit # Install the latest version of Flux
flux install --version=master flux install --version=master
# Create a source from a public Git repository # Create a source from a public Git repository
@ -88,8 +88,8 @@ var rootCmd = &cobra.Command{
# Delete a GitRepository source # Delete a GitRepository source
flux delete source git webapp-latest flux delete source git webapp-latest
# Uninstall the toolkit and delete CRDs # Uninstall Flux and delete CRDs
flux uninstall --crds flux uninstall
`, `,
} }

@ -22,8 +22,11 @@ import (
"github.com/manifoldco/promptui" "github.com/manifoldco/promptui"
"github.com/spf13/cobra" "github.com/spf13/cobra"
apierrors "k8s.io/apimachinery/pkg/api/errors" appsv1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/types" 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" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/flux2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
@ -34,33 +37,27 @@ import (
var uninstallCmd = &cobra.Command{ var uninstallCmd = &cobra.Command{
Use: "uninstall", Use: "uninstall",
Short: "Uninstall the toolkit components", Short: "Uninstall Flux and its custom resource definitions",
Long: "The uninstall command removes the namespace, cluster roles, cluster role bindings and CRDs from the cluster.", Long: "The uninstall command removes the Flux components and the toolkit.fluxcd.io resources from the cluster.",
Example: ` # Dry-run uninstall of all components Example: ` # Uninstall Flux components, its custom resources and namespace
flux uninstall --dry-run --namespace=flux-system flux uninstall --namespace=flux-system
# Uninstall all components and delete custom resource definitions # Uninstall Flux but keep the namespace
flux uninstall --resources --crds --namespace=flux-system flux uninstall --namespace=infra --keep-namespace=true
`, `,
RunE: uninstallCmdRun, RunE: uninstallCmdRun,
} }
type uninstallFlags struct { type uninstallFlags struct {
crds bool keepNamespace bool
resources bool silent bool
dryRun bool
silent bool
} }
var uninstallArgs uninstallFlags var uninstallArgs uninstallFlags
func init() { func init() {
uninstallCmd.Flags().BoolVar(&uninstallArgs.resources, "resources", true, uninstallCmd.Flags().BoolVar(&uninstallArgs.keepNamespace, "keep-namespace", false,
"removes custom resources such as Kustomizations, GitRepositories and HelmRepositories") "skip namespace deletion")
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().BoolVarP(&uninstallArgs.silent, "silent", "s", false, uninstallCmd.Flags().BoolVarP(&uninstallArgs.silent, "silent", "s", false,
"delete components without asking for confirmation") "delete components without asking for confirmation")
@ -68,6 +65,16 @@ func init() {
} }
func uninstallCmdRun(cmd *cobra.Command, args []string) error { 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) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
@ -76,96 +83,189 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
if !uninstallArgs.dryRun && !uninstallArgs.silent { logger.Actionf("deleting components in %s namespace", rootArgs.namespace)
prompt := promptui.Prompt{ uninstallComponents(ctx, kubeClient, rootArgs.namespace)
Label: fmt.Sprintf("Are you sure you want to delete the %s namespace", rootArgs.namespace),
IsConfirm: true, logger.Actionf("deleting toolkit.fluxcd.io finalizers in all namespaces")
} uninstallFinalizers(ctx, kubeClient)
if _, err := prompt.Run(); err != nil {
return fmt.Errorf("aborting")
}
}
dryRun := "--dry-run=server" logger.Actionf("deleting toolkit.fluxcd.io custom resource definitions")
deleteResources := uninstallArgs.resources || uninstallArgs.crds uninstallCustomResourceDefinitions(ctx, kubeClient, rootArgs.namespace)
// known kinds with finalizers if !uninstallArgs.keepNamespace {
namespacedKinds := []string{ uninstallNamespace(ctx, kubeClient, rootArgs.namespace)
sourcev1.GitRepositoryKind,
sourcev1.HelmRepositoryKind,
sourcev1.BucketKind,
} }
// suspend bootstrap kustomization to avoid finalizers deadlock logger.Successf("uninstall finished")
kustomizationName := types.NamespacedName{ return nil
Namespace: rootArgs.namespace, }
Name: rootArgs.namespace,
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) var list corev1.ServiceList
if err == nil { if err := kubeClient.List(ctx, &list, client.InNamespace(namespace), selector); err == nil {
kustomization.Spec.Suspend = true for _, r := range list.Items {
if err := kubeClient.Update(ctx, &kustomization); err != nil { if err := kubeClient.Delete(ctx, &r); err != nil {
return fmt.Errorf("unable to suspend kustomization '%s': %w", kustomizationName.String(), err) 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 rbacv1.ClusterRoleList
var list helmv2.HelmReleaseList if err := kubeClient.List(ctx, &list, selector); err == nil {
if err := kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace)); err == nil { for _, r := range list.Items {
namespacedKinds = append(namespacedKinds, helmv2.HelmReleaseKind) 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 { var list rbacv1.ClusterRoleBindingList
logger.Actionf("uninstalling custom resources") if err := kubeClient.List(ctx, &list, selector); err == nil {
for _, kind := range namespacedKinds { for _, r := range list.Items {
if err := deleteAll(ctx, kind, uninstallArgs.dryRun); err != nil { if err := kubeClient.Delete(ctx, &r); err != nil {
logger.Failuref("kubectl: %s", err.Error()) logger.Failuref("ClusterRoleBinding/%s deletion failed: %s", r.Name, err.Error())
} else {
logger.Successf("ClusterRoleBinding/%s deleted", r.Name)
}
} }
} }
} }
}
var kinds []string func uninstallFinalizers(ctx context.Context, kubeClient client.Client) {
if uninstallArgs.crds { {
kinds = append(kinds, "crds") 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") var list sourcev1.HelmRepositoryList
if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil {
logger.Actionf("uninstalling components") for _, r := range list.Items {
r.Finalizers = []string{}
for _, kind := range kinds { if err := kubeClient.Update(ctx, &r); err != nil {
kubectlArgs := []string{ logger.Failuref("%s/%s/%s removing finalizers failed: %s", r.Kind, r.Namespace, r.Name, err.Error())
"delete", kind, } else {
"-l", fmt.Sprintf("app.kubernetes.io/instance=%s", rootArgs.namespace), logger.Successf("%s/%s/%s finalizers deleted", r.Kind, r.Namespace, r.Name)
"--ignore-not-found", "--timeout", rootArgs.timeout.String(), }
}
}
}
{
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 { func uninstallCustomResourceDefinitions(ctx context.Context, kubeClient client.Client, namespace string) {
kubectlArgs := []string{ selector := client.MatchingLabels{"app.kubernetes.io/instance": namespace}
"delete", kind, "--ignore-not-found", {
"--all", "--all-namespaces", var list apiextensionsv1.CustomResourceDefinitionList
"--timeout", rootArgs.timeout.String(), 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 { func uninstallNamespace(ctx context.Context, kubeClient client.Client, namespace string) {
kubectlArgs = append(kubectlArgs, "--dry-run=server") 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
} }

@ -12,7 +12,7 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way.
# Check prerequisites # Check prerequisites
flux check --pre flux check --pre
# Install the latest version of the toolkit # Install the latest version of Flux
flux install --version=master flux install --version=master
# Create a source from a public Git repository # 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 # Delete a GitRepository source
flux delete source git webapp-latest flux delete source git webapp-latest
# Uninstall the toolkit and delete CRDs # Uninstall Flux and delete CRDs
flux uninstall --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 reconcile](flux_reconcile.md) - Reconcile sources and resources
* [flux resume](flux_resume.md) - Resume suspended resources * [flux resume](flux_resume.md) - Resume suspended resources
* [flux suspend](flux_suspend.md) - Suspend 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

@ -1,10 +1,10 @@
## flux uninstall ## flux uninstall
Uninstall the toolkit components Uninstall Flux and its custom resource definitions
### Synopsis ### 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] flux uninstall [flags]
@ -13,22 +13,20 @@ flux uninstall [flags]
### Examples ### Examples
``` ```
# Dry-run uninstall of all components # Uninstall Flux components, its custom resources and namespace
flux uninstall --dry-run --namespace=flux-system flux uninstall --namespace=flux-system
# Uninstall all components and delete custom resource definitions # Uninstall Flux but keep the namespace
flux uninstall --resources --crds --namespace=flux-system flux uninstall --namespace=infra --keep-namespace=true
``` ```
### Options ### Options
``` ```
--crds removes all CRDs previously installed -h, --help help for uninstall
--dry-run only print the object that would be deleted --keep-namespace skip namespace deletion
-h, --help help for uninstall -s, --silent delete components without asking for confirmation
--resources removes custom resources such as Kustomizations, GitRepositories and HelmRepositories (default true)
-s, --silent delete components without asking for confirmation
``` ```
### Options inherited from parent commands ### Options inherited from parent commands

@ -608,11 +608,27 @@ kustomize build https://github.com/fluxcd/flux2/manifests/install?ref=main | kub
## Uninstall ## Uninstall
You can uninstall the Flux components with: You can uninstall Flux with:
```sh ```sh
flux uninstall --crds flux uninstall --namespace=flux-system
``` ```
The above command will delete the custom resources definitions, the The above command performs the following operations:
controllers, and the namespace where they were installed.
- 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.

@ -21,7 +21,6 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"github.com/fluxcd/flux2/pkg/manifestgen/install"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
@ -32,8 +31,10 @@ import (
"text/template" "text/template"
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1" rbacv1 "k8s.io/api/rbac/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiruntime "k8s.io/apimachinery/pkg/runtime" apiruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
@ -44,6 +45,7 @@ import (
kustypes "sigs.k8s.io/kustomize/api/types" kustypes "sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"github.com/fluxcd/flux2/pkg/manifestgen/install"
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
imageautov1 "github.com/fluxcd/image-automation-controller/api/v1alpha1" imageautov1 "github.com/fluxcd/image-automation-controller/api/v1alpha1"
imagereflectv1 "github.com/fluxcd/image-reflector-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() scheme := apiruntime.NewScheme()
_ = apiextensionsv1.AddToScheme(scheme)
_ = corev1.AddToScheme(scheme) _ = corev1.AddToScheme(scheme)
_ = rbacv1.AddToScheme(scheme) _ = rbacv1.AddToScheme(scheme)
_ = appsv1.AddToScheme(scheme)
_ = sourcev1.AddToScheme(scheme) _ = sourcev1.AddToScheme(scheme)
_ = kustomizev1.AddToScheme(scheme) _ = kustomizev1.AddToScheme(scheme)
_ = helmv2.AddToScheme(scheme) _ = helmv2.AddToScheme(scheme)

Loading…
Cancel
Save