Merge pull request #891 from fluxcd/refac-uninstall

Refactor flux uninstall command
pull/907/head
Stefan Prodan 4 years ago committed by GitHub
commit a2887f5776
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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,12 @@ 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"
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" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/flux2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
@ -34,33 +38,30 @@ 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
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, 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, uninstallCmd.Flags().BoolVarP(&uninstallArgs.silent, "silent", "s", false,
"delete components without asking for confirmation") "delete components without asking for confirmation")
@ -68,17 +69,9 @@ func init() {
} }
func uninstallCmdRun(cmd *cobra.Command, args []string) error { 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 { if !uninstallArgs.dryRun && !uninstallArgs.silent {
prompt := promptui.Prompt{ 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, IsConfirm: true,
} }
if _, err := prompt.Run(); err != nil { if _, err := prompt.Run(); err != nil {
@ -86,86 +79,235 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error {
} }
} }
dryRun := "--dry-run=server" ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
deleteResources := uninstallArgs.resources || uninstallArgs.crds defer cancel()
// known kinds with finalizers kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
namespacedKinds := []string{ if err != nil {
sourcev1.GitRepositoryKind, return err
sourcev1.HelmRepositoryKind,
sourcev1.BucketKind,
} }
// suspend bootstrap kustomization to avoid finalizers deadlock logger.Actionf("deleting components in %s namespace", rootArgs.namespace)
kustomizationName := types.NamespacedName{ uninstallComponents(ctx, kubeClient, rootArgs.namespace, uninstallArgs.dryRun)
Namespace: rootArgs.namespace,
Name: rootArgs.namespace, 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) logger.Successf("uninstall finished")
if err == nil { return 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) 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 networkingv1.NetworkPolicyList
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, 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 { var list corev1.ServiceAccountList
logger.Actionf("uninstalling custom resources") if err := kubeClient.List(ctx, &list, client.InNamespace(namespace), 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, opts); err != nil {
logger.Failuref("kubectl: %s", err.Error()) 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 var list rbacv1.ClusterRoleList
if uninstallArgs.crds { if err := kubeClient.List(ctx, &list, selector); err == nil {
kinds = append(kinds, "crds") 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") func uninstallFinalizers(ctx context.Context, kubeClient client.Client, dryRun bool) {
opts, dryRunStr := getUpdateOptions(dryRun)
logger.Actionf("uninstalling components") {
var list sourcev1.GitRepositoryList
for _, kind := range kinds { if err := kubeClient.List(ctx, &list, client.InNamespace("")); err == nil {
kubectlArgs := []string{ for _, r := range list.Items {
"delete", kind, r.Finalizers = []string{}
"-l", fmt.Sprintf("app.kubernetes.io/instance=%s", rootArgs.namespace), if err := kubeClient.Update(ctx, &r, opts); err != nil {
"--ignore-not-found", "--timeout", rootArgs.timeout.String(), 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") func uninstallCustomResourceDefinitions(ctx context.Context, kubeClient client.Client, namespace string, dryRun bool) {
return nil 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 { func uninstallNamespace(ctx context.Context, kubeClient client.Client, namespace string, dryRun bool) {
kubectlArgs := []string{ opts, dryRunStr := getDeleteOptions(dryRun)
"delete", kind, "--ignore-not-found", ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}
"--all", "--all-namespaces", if err := kubeClient.Delete(ctx, &ns, opts); err != nil {
"--timeout", rootArgs.timeout.String(), 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 { 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 opts, dryRunStr
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,21 @@ 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 objects that would be deleted
--dry-run only print the object that would be deleted -h, --help help for uninstall
-h, --help help for uninstall --keep-namespace skip namespace deletion
--resources removes custom resources such as Kustomizations, GitRepositories and HelmRepositories (default true) -s, --silent delete components without asking for confirmation
-s, --silent delete components without asking for confirmation
``` ```
### Options inherited from parent commands ### Options inherited from parent commands

@ -608,11 +608,28 @@ 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 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.

@ -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,11 @@ 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"
networkingv1 "k8s.io/api/networking/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 +46,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 +166,11 @@ 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)
_ = networkingv1.AddToScheme(scheme)
_ = sourcev1.AddToScheme(scheme) _ = sourcev1.AddToScheme(scheme)
_ = kustomizev1.AddToScheme(scheme) _ = kustomizev1.AddToScheme(scheme)
_ = helmv2.AddToScheme(scheme) _ = helmv2.AddToScheme(scheme)

Loading…
Cancel
Save