Skip to content

Commit

Permalink
confirm before overriding installation by another manager
Browse files Browse the repository at this point in the history
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
  • Loading branch information
somtochiama committed Oct 24, 2023
1 parent adc0465 commit 76e19e9
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 21 deletions.
3 changes: 3 additions & 0 deletions cmd/flux/bootstrap.go
Expand Up @@ -72,6 +72,8 @@ type bootstrapFlags struct {
gpgPassphrase string
gpgKeyID string

force bool

commitMessageAppendix string
}

Expand Down Expand Up @@ -129,6 +131,7 @@ func init() {

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, "overwrite existing Flux installation on the cluster")
bootstrapCmd.PersistentFlags().MarkHidden("manifests")

rootCmd.AddCommand(bootstrapCmd)
Expand Down
16 changes: 16 additions & 0 deletions cmd/flux/bootstrap_bitbucket_server.go
Expand Up @@ -24,6 +24,7 @@ import (

"github.com/fluxcd/pkg/git"
"github.com/fluxcd/pkg/git/gogit"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"

"github.com/fluxcd/flux2/v2/internal/flags"
Expand Down Expand Up @@ -124,6 +125,21 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
return err
}

if !bootstrapArgs.force {
info, err := getFluxClusterInfo(ctx, kubeClient)
if err != nil {
return fmt.Errorf("cluster info unavailable: %w", err)
}

err = confirmFluxInstallOverride(info)
if err != nil {
if err == promptui.ErrAbort {
return fmt.Errorf("bootstrap cancelled")
}
return err
}
}

// Manifest base
if ver, err := getVersion(bootstrapArgs.version); err != nil {
return err
Expand Down
15 changes: 15 additions & 0 deletions cmd/flux/bootstrap_git.go
Expand Up @@ -146,6 +146,21 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
return err
}

if !bootstrapArgs.force {
info, err := getFluxClusterInfo(ctx, kubeClient)
if err != nil {
return fmt.Errorf("cluster info unavailable: %w", err)
}

err = confirmFluxInstallOverride(info)
if err != nil {
if err == promptui.ErrAbort {
return fmt.Errorf("bootstrap cancelled")
}
return err
}
}

// Manifest base
if ver, err := getVersion(bootstrapArgs.version); err != nil {
return err
Expand Down
15 changes: 15 additions & 0 deletions cmd/flux/bootstrap_github.go
Expand Up @@ -24,6 +24,7 @@ import (

"github.com/fluxcd/pkg/git"
"github.com/fluxcd/pkg/git/gogit"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"

"github.com/fluxcd/flux2/v2/internal/flags"
Expand Down Expand Up @@ -128,6 +129,20 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
return err
}

info, err := getFluxClusterInfo(ctx, kubeClient)
if err != nil {
return fmt.Errorf("cluster info unavailable: %w", err)
}
if !bootstrapArgs.force {
err := confirmFluxInstallOverride(info)
if err != nil {
if err == promptui.ErrAbort {
return fmt.Errorf("bootstrap cancelled")
}
return err
}
}

// Manifest base
if ver, err := getVersion(bootstrapArgs.version); err != nil {
return err
Expand Down
16 changes: 16 additions & 0 deletions cmd/flux/bootstrap_gitlab.go
Expand Up @@ -26,6 +26,7 @@ import (

"github.com/fluxcd/pkg/git"
"github.com/fluxcd/pkg/git/gogit"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"

"github.com/fluxcd/flux2/v2/internal/flags"
Expand Down Expand Up @@ -145,6 +146,21 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
return err
}

if !bootstrapArgs.force {
info, err := getFluxClusterInfo(ctx, kubeClient)
if err != nil {
return fmt.Errorf("cluster info unavailable: %w", err)
}

err = confirmFluxInstallOverride(info)
if err != nil {
if err == promptui.ErrAbort {
return fmt.Errorf("bootstrap cancelled")
}
return err
}
}

// Manifest base
if ver, err := getVersion(bootstrapArgs.version); err != nil {
return err
Expand Down
33 changes: 28 additions & 5 deletions cmd/flux/cluster_info.go
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"fmt"

"github.com/manifoldco/promptui"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -38,6 +39,8 @@ var bootstrapLabels = []string{

// fluxClusterInfo contains information about an existing flux installation on a cluster.
type fluxClusterInfo struct {
// installed indicates that Flux is installed on the cluster.
installed bool
// bootstrapped indicates that Flux was installed using the `flux bootstrap` command.
bootstrapped bool
// managedBy is the name of the tool being used to manage the installation of Flux.
Expand All @@ -47,12 +50,11 @@ type fluxClusterInfo struct {
}

// 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.
//
// 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.
func getFluxClusterInfo(ctx context.Context, c client.Client) (fluxClusterInfo, bool, error) {
func getFluxClusterInfo(ctx context.Context, c client.Client) (fluxClusterInfo, error) {
var info fluxClusterInfo
crdMetadata := &metav1.PartialObjectMetadata{
TypeMeta: metav1.TypeMeta{
Expand All @@ -65,11 +67,12 @@ func getFluxClusterInfo(ctx context.Context, c client.Client) (fluxClusterInfo,
}
if err := c.Get(ctx, client.ObjectKeyFromObject(crdMetadata), crdMetadata); err != nil {
if errors.IsNotFound(err) {
return info, false, nil
return info, nil
}
return info, false, err
return info, err
}

info.installed = true
info.version = crdMetadata.Labels["app.kubernetes.io/version"]

var present bool
Expand All @@ -83,5 +86,25 @@ func getFluxClusterInfo(ctx context.Context, c client.Client) (fluxClusterInfo,
if manager, ok := crdMetadata.Labels["app.kubernetes.io/managed-by"]; ok {
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 flux is not installed or installation is
// managed by Flux
if !info.installed || 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
}
27 changes: 14 additions & 13 deletions cmd/flux/cluster_info_test.go
Expand Up @@ -44,12 +44,10 @@ func Test_getFluxClusterInfo(t *testing.T) {
name string
labels map[string]string
wantErr bool
wantBool bool
wantInfo fluxClusterInfo
}{
{
name: "no git repository CRD present",
wantBool: false,
name: "no git repository CRD present",
},
{
name: "CRD with kustomize-controller labels",
Expand All @@ -58,10 +56,10 @@ func Test_getFluxClusterInfo(t *testing.T) {
fmt.Sprintf("%s/namespace", kustomizev1.GroupVersion.Group): "flux-system",
"app.kubernetes.io/version": "v2.1.0",
},
wantBool: true,
wantInfo: fluxClusterInfo{
version: "v2.1.0",
bootstrapped: true,
installed: true,
},
},
{
Expand All @@ -72,10 +70,10 @@ func Test_getFluxClusterInfo(t *testing.T) {
"app.kubernetes.io/version": "v2.1.0",
"app.kubernetes.io/managed-by": "flux",
},
wantBool: true,
wantInfo: fluxClusterInfo{
version: "v2.1.0",
bootstrapped: true,
installed: true,
managedBy: "flux",
},
},
Expand All @@ -85,25 +83,27 @@ func Test_getFluxClusterInfo(t *testing.T) {
"app.kubernetes.io/version": "v2.1.0",
"app.kubernetes.io/managed-by": "helm",
},
wantBool: true,
wantInfo: fluxClusterInfo{
version: "v2.1.0",
managedBy: "helm",
installed: true,
},
},
{
name: "CRD with no labels",
labels: map[string]string{},
wantBool: true,
name: "CRD with no labels",
labels: map[string]string{},
wantInfo: fluxClusterInfo{
installed: true,
},
},
{
name: "CRD with only version label",
labels: map[string]string{
"app.kubernetes.io/version": "v2.1.0",
},
wantBool: true,
wantInfo: fluxClusterInfo{
version: "v2.1.0",
version: "v2.1.0",
installed: true,
},
},
}
Expand All @@ -120,12 +120,13 @@ func Test_getFluxClusterInfo(t *testing.T) {
}

client := builder.Build()
info, present, err := getFluxClusterInfo(context.Background(), client)
info, err := getFluxClusterInfo(context.Background(), client)
if tt.wantErr {
g.Expect(err).To(HaveOccurred())
} else {
g.Expect(err).To(Not(HaveOccurred()))
}

g.Expect(present).To(Equal(tt.wantBool))
g.Expect(info).To(BeEquivalentTo(tt.wantInfo))
})
}
Expand Down
20 changes: 17 additions & 3 deletions cmd/flux/install.go
Expand Up @@ -23,6 +23,7 @@ import (
"path/filepath"
"time"

"github.com/manifoldco/promptui"
"github.com/spf13/cobra"

"github.com/fluxcd/flux2/v2/internal/flags"
Expand Down Expand Up @@ -72,6 +73,7 @@ type installFlags struct {
tokenAuth bool
clusterDomain string
tolerationKeys []string
force bool
}

var installArgs = NewInstallFlags()
Expand All @@ -98,6 +100,7 @@ func init() {
installCmd.Flags().StringVar(&installArgs.clusterDomain, "cluster-domain", rootArgs.defaults.ClusterDomain, "internal cluster domain")
installCmd.Flags().StringSliceVar(&installArgs.tolerationKeys, "toleration-keys", nil,
"list of toleration keys used to schedule the components pods onto nodes with matching taints")
installCmd.Flags().BoolVar(&installArgs.force, "force", false, "overwrite existing Flux installation on the cluster")
installCmd.Flags().MarkHidden("manifests")

rootCmd.AddCommand(installCmd)
Expand Down Expand Up @@ -188,13 +191,24 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
return err
}

info, installed, err := getFluxClusterInfo(ctx, kubeClient)
info, err := getFluxClusterInfo(ctx, kubeClient)
if err != nil {
return fmt.Errorf("cluster info unavailable: %w", err)
}

if installed && info.bootstrapped {
return fmt.Errorf("this cluster has already been bootstrapped with Flux %s! Please use 'flux bootstrap' to upgrade", info.version)
if info.installed && info.bootstrapped {
return fmt.Errorf("this cluster has already been bootstrapped with Flux %s! Please use 'flux bootstrap' to upgrade",
info.version)
}

if !installArgs.force {
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))
Expand Down

0 comments on commit 76e19e9

Please sign in to comment.