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 }}
- 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/ \

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

@ -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
`,
}

@ -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
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)
logger.Actionf("deleting toolkit.fluxcd.io custom resource definitions")
uninstallCustomResourceDefinitions(ctx, kubeClient, rootArgs.namespace)
dryRun := "--dry-run=server"
deleteResources := uninstallArgs.resources || uninstallArgs.crds
if !uninstallArgs.keepNamespace {
uninstallNamespace(ctx, kubeClient, rootArgs.namespace)
}
// known kinds with finalizers
namespacedKinds := []string{
sourcev1.GitRepositoryKind,
sourcev1.HelmRepositoryKind,
sourcev1.BucketKind,
logger.Successf("uninstall finished")
return nil
}
// suspend bootstrap kustomization to avoid finalizers deadlock
kustomizationName := types.NamespacedName{
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 helmv2.HelmReleaseList
if err := kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace)); err == nil {
namespacedKinds = append(namespacedKinds, helmv2.HelmReleaseKind)
{
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 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
if uninstallArgs.crds {
kinds = append(kinds, "crds")
{
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)
}
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 {
kubectlArgs := []string{
"delete", kind, "--ignore-not-found",
"--all", "--all-namespaces",
"--timeout", rootArgs.timeout.String(),
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)
}
}
}
}
{
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 {
kubectlArgs = append(kubectlArgs, "--dry-run=server")
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)
}
}
}
}
}
_, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...)
return err
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)
}
}

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

@ -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,21 +13,19 @@ 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)
--keep-namespace skip namespace deletion
-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
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.

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

Loading…
Cancel
Save