confirm before overriding installation by another manager

Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
pull/4355/head
Somtochi Onyekwere 1 year ago
parent 10cddb457f
commit 2f15ad972b

@ -17,11 +17,15 @@ limitations under the License.
package main package main
import ( import (
"context"
"crypto/elliptic" "crypto/elliptic"
"fmt" "fmt"
"strings" "strings"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/flux2/v2/internal/flags" "github.com/fluxcd/flux2/v2/internal/flags"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/v2/internal/utils"
@ -72,6 +76,8 @@ type bootstrapFlags struct {
gpgPassphrase string gpgPassphrase string
gpgKeyID string gpgKeyID string
force bool
commitMessageAppendix string commitMessageAppendix string
} }
@ -129,6 +135,7 @@ func init() {
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.commitMessageAppendix, "commit-message-appendix", "", "string to add to the commit messages, e.g. '[ci skip]'") bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.commitMessageAppendix, "commit-message-appendix", "", "string to add to the commit messages, e.g. '[ci skip]'")
bootstrapCmd.PersistentFlags().BoolVar(&bootstrapArgs.force, "force", false, "override existing Flux installation if it's managed by a diffrent tool such as Helm")
bootstrapCmd.PersistentFlags().MarkHidden("manifests") bootstrapCmd.PersistentFlags().MarkHidden("manifests")
rootCmd.AddCommand(bootstrapCmd) rootCmd.AddCommand(bootstrapCmd)
@ -188,3 +195,27 @@ func mapTeamSlice(s []string, defaultPermission string) map[string]string {
return m return m
} }
// confirmBootstrap gets a confirmation for running bootstrap over an existing Flux installation.
// It returns a nil error if Flux is not installed or the user confirms overriding an existing installation
func confirmBootstrap(ctx context.Context, kubeClient client.Client) error {
installed := true
info, err := getFluxClusterInfo(ctx, kubeClient)
if err != nil {
if !errors.IsNotFound(err) {
return fmt.Errorf("cluster info unavailable: %w", err)
}
installed = false
}
if installed {
err = confirmFluxInstallOverride(info)
if err != nil {
if err == promptui.ErrAbort {
return fmt.Errorf("bootstrap cancelled")
}
return err
}
}
return nil
}

@ -124,6 +124,13 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
if !bootstrapArgs.force {
err = confirmBootstrap(ctx, kubeClient)
if err != nil {
return err
}
}
// Manifest base // Manifest base
if ver, err := getVersion(bootstrapArgs.version); err != nil { if ver, err := getVersion(bootstrapArgs.version); err != nil {
return err return err

@ -146,6 +146,13 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
if !bootstrapArgs.force {
err = confirmBootstrap(ctx, kubeClient)
if err != nil {
return err
}
}
// Manifest base // Manifest base
if ver, err := getVersion(bootstrapArgs.version); err != nil { if ver, err := getVersion(bootstrapArgs.version); err != nil {
return err return err

@ -128,6 +128,13 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
if !bootstrapArgs.force {
err = confirmBootstrap(ctx, kubeClient)
if err != nil {
return err
}
}
// Manifest base // Manifest base
if ver, err := getVersion(bootstrapArgs.version); err != nil { if ver, err := getVersion(bootstrapArgs.version); err != nil {
return err return err

@ -145,6 +145,13 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
if !bootstrapArgs.force {
err = confirmBootstrap(ctx, kubeClient)
if err != nil {
return err
}
}
// Manifest base // Manifest base
if ver, err := getVersion(bootstrapArgs.version); err != nil { if ver, err := getVersion(bootstrapArgs.version); err != nil {
return err return err

@ -20,8 +20,8 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/manifoldco/promptui"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
@ -47,12 +47,13 @@ type fluxClusterInfo struct {
} }
// getFluxClusterInfo returns information on the Flux installation running on the cluster. // getFluxClusterInfo returns information on the Flux installation running on the cluster.
// If the information cannot be retrieved, the boolean return value will be false.
// If an error occurred, the returned error will be non-nil. // If an error occurred, the returned error will be non-nil.
// //
// This function retrieves the GitRepository CRD from the cluster and checks it // This function retrieves the GitRepository CRD from the cluster and checks it
// for a set of labels used to determine the Flux version and how Flux was installed. // for a set of labels used to determine the Flux version and how Flux was installed.
func getFluxClusterInfo(ctx context.Context, c client.Client) (fluxClusterInfo, bool, error) { // It returns the NotFound error from the underlying library if it was unable to find
// the GitRepository CRD and this can be used to check if Flux is installed.
func getFluxClusterInfo(ctx context.Context, c client.Client) (fluxClusterInfo, error) {
var info fluxClusterInfo var info fluxClusterInfo
crdMetadata := &metav1.PartialObjectMetadata{ crdMetadata := &metav1.PartialObjectMetadata{
TypeMeta: metav1.TypeMeta{ TypeMeta: metav1.TypeMeta{
@ -64,10 +65,7 @@ func getFluxClusterInfo(ctx context.Context, c client.Client) (fluxClusterInfo,
}, },
} }
if err := c.Get(ctx, client.ObjectKeyFromObject(crdMetadata), crdMetadata); err != nil { if err := c.Get(ctx, client.ObjectKeyFromObject(crdMetadata), crdMetadata); err != nil {
if errors.IsNotFound(err) { return info, err
return info, false, nil
}
return info, false, err
} }
info.version = crdMetadata.Labels["app.kubernetes.io/version"] info.version = crdMetadata.Labels["app.kubernetes.io/version"]
@ -80,8 +78,29 @@ func getFluxClusterInfo(ctx context.Context, c client.Client) (fluxClusterInfo,
info.bootstrapped = true info.bootstrapped = true
} }
// the `app.kubernetes.io` label is not set by flux but might be set by other
// tools used to install Flux e.g Helm.
if manager, ok := crdMetadata.Labels["app.kubernetes.io/managed-by"]; ok { if manager, ok := crdMetadata.Labels["app.kubernetes.io/managed-by"]; ok {
info.managedBy = manager info.managedBy = manager
} }
return info, true, nil return info, nil
}
// confirmFluxInstallOverride displays a prompt to the user so that they can confirm before overriding
// a Flux installation. It returns nil if the installation should continue,
// promptui.ErrAbort if the user doesn't confirm, or an error encountered.
func confirmFluxInstallOverride(info fluxClusterInfo) error {
// no need to display prompt if installation is managed by Flux
if info.managedBy == "" || info.managedBy == "flux" {
return nil
}
display := fmt.Sprintf("Flux %s has been installed on this cluster with %s!", info.version, info.managedBy)
fmt.Fprintln(rootCmd.ErrOrStderr(), display)
prompt := promptui.Prompt{
Label: fmt.Sprintf("Are you sure you want to override the %s installation? Y/N", info.managedBy),
IsConfirm: true,
}
_, err := prompt.Run()
return err
} }

@ -24,6 +24,7 @@ import (
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/client/fake"
@ -44,12 +45,11 @@ func Test_getFluxClusterInfo(t *testing.T) {
name string name string
labels map[string]string labels map[string]string
wantErr bool wantErr bool
wantBool bool
wantInfo fluxClusterInfo wantInfo fluxClusterInfo
}{ }{
{ {
name: "no git repository CRD present", name: "no git repository CRD present",
wantBool: false, wantErr: true,
}, },
{ {
name: "CRD with kustomize-controller labels", name: "CRD with kustomize-controller labels",
@ -58,7 +58,6 @@ func Test_getFluxClusterInfo(t *testing.T) {
fmt.Sprintf("%s/namespace", kustomizev1.GroupVersion.Group): "flux-system", fmt.Sprintf("%s/namespace", kustomizev1.GroupVersion.Group): "flux-system",
"app.kubernetes.io/version": "v2.1.0", "app.kubernetes.io/version": "v2.1.0",
}, },
wantBool: true,
wantInfo: fluxClusterInfo{ wantInfo: fluxClusterInfo{
version: "v2.1.0", version: "v2.1.0",
bootstrapped: true, bootstrapped: true,
@ -72,7 +71,6 @@ func Test_getFluxClusterInfo(t *testing.T) {
"app.kubernetes.io/version": "v2.1.0", "app.kubernetes.io/version": "v2.1.0",
"app.kubernetes.io/managed-by": "flux", "app.kubernetes.io/managed-by": "flux",
}, },
wantBool: true,
wantInfo: fluxClusterInfo{ wantInfo: fluxClusterInfo{
version: "v2.1.0", version: "v2.1.0",
bootstrapped: true, bootstrapped: true,
@ -85,7 +83,6 @@ func Test_getFluxClusterInfo(t *testing.T) {
"app.kubernetes.io/version": "v2.1.0", "app.kubernetes.io/version": "v2.1.0",
"app.kubernetes.io/managed-by": "helm", "app.kubernetes.io/managed-by": "helm",
}, },
wantBool: true,
wantInfo: fluxClusterInfo{ wantInfo: fluxClusterInfo{
version: "v2.1.0", version: "v2.1.0",
managedBy: "helm", managedBy: "helm",
@ -94,14 +91,13 @@ func Test_getFluxClusterInfo(t *testing.T) {
{ {
name: "CRD with no labels", name: "CRD with no labels",
labels: map[string]string{}, labels: map[string]string{},
wantBool: true, wantInfo: fluxClusterInfo{},
}, },
{ {
name: "CRD with only version label", name: "CRD with only version label",
labels: map[string]string{ labels: map[string]string{
"app.kubernetes.io/version": "v2.1.0", "app.kubernetes.io/version": "v2.1.0",
}, },
wantBool: true,
wantInfo: fluxClusterInfo{ wantInfo: fluxClusterInfo{
version: "v2.1.0", version: "v2.1.0",
}, },
@ -120,12 +116,14 @@ func Test_getFluxClusterInfo(t *testing.T) {
} }
client := builder.Build() client := builder.Build()
info, present, err := getFluxClusterInfo(context.Background(), client) info, err := getFluxClusterInfo(context.Background(), client)
if tt.wantErr { if tt.wantErr {
g.Expect(err).To(HaveOccurred()) g.Expect(err).To(HaveOccurred())
g.Expect(errors.IsNotFound(err)).To(BeTrue())
} else {
g.Expect(err).To(Not(HaveOccurred()))
} }
g.Expect(present).To(Equal(tt.wantBool))
g.Expect(info).To(BeEquivalentTo(tt.wantInfo)) g.Expect(info).To(BeEquivalentTo(tt.wantInfo))
}) })
} }

@ -23,7 +23,9 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/errors"
"github.com/fluxcd/flux2/v2/internal/flags" "github.com/fluxcd/flux2/v2/internal/flags"
"github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/v2/internal/utils"
@ -72,6 +74,7 @@ type installFlags struct {
tokenAuth bool tokenAuth bool
clusterDomain string clusterDomain string
tolerationKeys []string tolerationKeys []string
force bool
} }
var installArgs = NewInstallFlags() var installArgs = NewInstallFlags()
@ -98,6 +101,7 @@ func init() {
installCmd.Flags().StringVar(&installArgs.clusterDomain, "cluster-domain", rootArgs.defaults.ClusterDomain, "internal cluster domain") installCmd.Flags().StringVar(&installArgs.clusterDomain, "cluster-domain", rootArgs.defaults.ClusterDomain, "internal cluster domain")
installCmd.Flags().StringSliceVar(&installArgs.tolerationKeys, "toleration-keys", nil, installCmd.Flags().StringSliceVar(&installArgs.tolerationKeys, "toleration-keys", nil,
"list of toleration keys used to schedule the components pods onto nodes with matching taints") "list of toleration keys used to schedule the components pods onto nodes with matching taints")
installCmd.Flags().BoolVar(&installArgs.force, "force", false, "override existing Flux installation if it's managed by a diffrent tool such as Helm")
installCmd.Flags().MarkHidden("manifests") installCmd.Flags().MarkHidden("manifests")
rootCmd.AddCommand(installCmd) rootCmd.AddCommand(installCmd)
@ -188,13 +192,28 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
info, installed, err := getFluxClusterInfo(ctx, kubeClient) installed := true
info, err := getFluxClusterInfo(ctx, kubeClient)
if err != nil { if err != nil {
return fmt.Errorf("cluster info unavailable: %w", err) if !errors.IsNotFound(err) {
return fmt.Errorf("cluster info unavailable: %w", err)
}
installed = false
}
if info.bootstrapped {
return fmt.Errorf("this cluster has already been bootstrapped with Flux %s! Please use 'flux bootstrap' to upgrade",
info.version)
} }
if installed && info.bootstrapped { if installed && !installArgs.force {
return fmt.Errorf("this cluster has already been bootstrapped with Flux %s! Please use 'flux bootstrap' to upgrade", info.version) err := confirmFluxInstallOverride(info)
if err != nil {
if err == promptui.ErrAbort {
return fmt.Errorf("installation cancelled")
}
return err
}
} }
applyOutput, err := utils.Apply(ctx, kubeconfigArgs, kubeclientOptions, tmpDir, filepath.Join(tmpDir, manifest.Path)) applyOutput, err := utils.Apply(ctx, kubeconfigArgs, kubeclientOptions, tmpDir, filepath.Join(tmpDir, manifest.Path))

Loading…
Cancel
Save