mirror of
https://github.com/fluxcd/flux2.git
synced 2025-11-02 18:58:33 +08:00
confirm before overriding installation by another manager
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
This commit is contained in:
@ -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 installed && info.bootstrapped {
|
if info.bootstrapped {
|
||||||
return fmt.Errorf("this cluster has already been bootstrapped with Flux %s! Please use 'flux bootstrap' to upgrade", info.version)
|
return fmt.Errorf("this cluster has already been bootstrapped with Flux %s! Please use 'flux bootstrap' to upgrade",
|
||||||
|
info.version)
|
||||||
|
}
|
||||||
|
|
||||||
|
if installed && !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))
|
applyOutput, err := utils.Apply(ctx, kubeconfigArgs, kubeclientOptions, tmpDir, filepath.Join(tmpDir, manifest.Path))
|
||||||
|
|||||||
Reference in New Issue
Block a user