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
dryRun bool
silent 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,
}
if _, err := prompt.Run(); err != nil {
return fmt.Errorf("aborting")
}
}
dryRun := "--dry-run=server" logger.Actionf("deleting toolkit.fluxcd.io finalizers in all namespaces")
deleteResources := uninstallArgs.resources || uninstallArgs.crds uninstallFinalizers(ctx, kubeClient)
// known kinds with finalizers logger.Actionf("deleting toolkit.fluxcd.io custom resource definitions")
namespacedKinds := []string{ uninstallCustomResourceDefinitions(ctx, kubeClient, rootArgs.namespace)
sourcev1.GitRepositoryKind,
sourcev1.HelmRepositoryKind, if !uninstallArgs.keepNamespace {
sourcev1.BucketKind, uninstallNamespace(ctx, kubeClient, rootArgs.namespace)
} }
// 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)
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)
} }
} }
if err == nil || apierrors.IsNotFound(err) {
namespacedKinds = append(namespacedKinds, kustomizev1.KustomizationKind)
} }
{
// add HelmRelease kind to deletion list if exists var list corev1.ServiceList
var list helmv2.HelmReleaseList if err := kubeClient.List(ctx, &list, client.InNamespace(namespace), 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("Service/%s/%s deletion failed: %s", r.Namespace, r.Name, err.Error())
} else {
logger.Successf("Service/%s/%s deleted", r.Namespace, 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 kinds []string var list corev1.ServiceAccountList
if uninstallArgs.crds { if err := kubeClient.List(ctx, &list, client.InNamespace(namespace), selector); err == nil {
kinds = append(kinds, "crds") 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)
} }
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(),
} }
if uninstallArgs.dryRun {
kubectlArgs = append(kubectlArgs, dryRun)
} }
if _, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...); err != nil { }
return fmt.Errorf("uninstall failed: %w", err) {
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)
}
}
}
}
{
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)
}
}
} }
} }
logger.Successf("uninstall finished")
return nil
} }
func deleteAll(ctx context.Context, kind string, dryRun bool) error { func uninstallFinalizers(ctx context.Context, kubeClient client.Client) {
kubectlArgs := []string{ {
"delete", kind, "--ignore-not-found", var list sourcev1.GitRepositoryList
"--all", "--all-namespaces", if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil {
"--timeout", rootArgs.timeout.String(), 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.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)
}
}
} }
}
{
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)
}
}
}
}
{
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)
}
}
}
}
}
if dryRun { func uninstallCustomResourceDefinitions(ctx context.Context, kubeClient client.Client, namespace string) {
kubectlArgs = append(kubectlArgs, "--dry-run=server") 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)
}
}
} }
}
}
_, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...) func uninstallNamespace(ctx context.Context, kubeClient client.Client, namespace string) {
return err 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)
}
} }

@ -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,21 +13,19 @@ 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
--dry-run only print the object that would be deleted
-h, --help help for uninstall -h, --help help for uninstall
--resources removes custom resources such as Kustomizations, GitRepositories and HelmRepositories (default true) --keep-namespace skip namespace deletion
-s, --silent delete components without asking for confirmation -s, --silent delete components without asking for confirmation
``` ```

@ -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