Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EVEREST-1788 Adjust namespaces remove CLI command #1000

Merged
merged 8 commits into from
Jan 20, 2025
29 changes: 14 additions & 15 deletions commands/namespaces/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ const forceUninstallHint = "HINT: use --force to remove the namespace and all it
// NewRemoveCommand returns a new command to remove an existing namespace.
func NewRemoveCommand(l *zap.SugaredLogger) *cobra.Command {
cmd := &cobra.Command{
Use: "remove",
Long: "Remove an existing namespace",
Short: "Remove an existing namespace",
Example: `everestctl namespaces remove [NAMESPACE] [FLAGS]`,
Use: "remove [flags] namespaces",
maxkondr marked this conversation as resolved.
Show resolved Hide resolved
Long: "Remove an existing and managed by Everest namespaces",
Short: "Remove an existing and managed by Everest namespaces",
maxkondr marked this conversation as resolved.
Show resolved Hide resolved
Example: `everestctl namespaces remove --keep-namespace --force ns-1,ns-2`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
initRemoveViperFlags(cmd)
c := &namespaces.NamespaceRemoveConfig{}
Expand All @@ -32,28 +33,26 @@ func NewRemoveCommand(l *zap.SugaredLogger) *cobra.Command {
l.Error(err)
return
}

if len(args) != 1 {
output.PrintError(fmt.Errorf("invalid number of arguments: expected 1, got %d", len(args)), l, true)
os.Exit(1)
}

namespace := args[0]
c.Namespaces = []string{namespace}
c.Namespaces = args[0]

enableLogging := viper.GetBool("verbose") || viper.GetBool("json")
c.Pretty = !enableLogging

if err := c.Populate(cmd.Context()); err != nil {
if errors.Is(err, namespaces.ErrNamespaceNotEmpty) {
err = fmt.Errorf("%w. %s", err, forceUninstallHint)
}
output.PrintError(err, l, !enableLogging)
os.Exit(1)
}

op, err := namespaces.NewNamespaceRemove(*c, l)
if err != nil {
output.PrintError(err, l, !enableLogging)
return
}

if err := op.Run(cmd.Context()); err != nil {
if errors.Is(err, namespaces.ErrNamespaceNotEmpty) {
err = fmt.Errorf("%w. %s", err, forceUninstallHint)
}
output.PrintError(err, l, !enableLogging)
os.Exit(1)
}
Expand Down
102 changes: 75 additions & 27 deletions pkg/cli/namespaces/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@ import (
cliutils "github.com/percona/everest/pkg/cli/utils"
"github.com/percona/everest/pkg/common"
"github.com/percona/everest/pkg/kubernetes"
"github.com/percona/everest/pkg/output"
)

const (
pollInterval = 5 * time.Second
pollTimeout = 5 * time.Minute
)

// ErrNamespaceNotEmpty is returned when the namespace is not empty.
var ErrNamespaceNotEmpty = errors.New("cannot remove namespace with running database clusters")

// NamespaceRemoveConfig is the configuration for the namespace removal operation.
type NamespaceRemoveConfig struct {
// KubeconfigPath is a path to a kubeconfig
Expand All @@ -37,8 +39,76 @@ type NamespaceRemoveConfig struct {
// If set, we will print the pretty output.
Pretty bool

// Namespaces (DB Namespaces) to remove
Namespaces []string
// Namespaces (DB Namespaces) passed by user to remove.
Namespaces string
// NamespaceList is a list of namespaces to remove.
// This is populated internally after validating the Namespaces field.:
NamespaceList []string
}

// Populate the configuration with the required values.
func (cfg *NamespaceRemoveConfig) Populate(ctx context.Context) error {
if err := cfg.populateNamespaces(); err != nil {
return err
}

for _, ns := range cfg.NamespaceList {
if err := cfg.validateNamespaceOwnership(ctx, ns); err != nil {
return fmt.Errorf("invalid namespace (%s): %w", ns, err)
}

if err := cfg.validateDatabasesAbsent(ctx, ns); err != nil {
return fmt.Errorf("invalid namespace (%s): %w", ns, err)
}
}
return nil
}

func (cfg *NamespaceRemoveConfig) populateNamespaces() error {
namespaces := cfg.Namespaces
list, err := ValidateNamespaces(namespaces)
if err != nil {
return err
}
cfg.NamespaceList = list
return nil
}

func (cfg *NamespaceRemoveConfig) validateNamespaceOwnership(ctx context.Context, namespace string) error {
k, err := cliutils.NewKubeclient(zap.NewNop().Sugar(), cfg.KubeconfigPath)
if err != nil {
return err
}

nsExists, ownedByEverest, err := namespaceExists(ctx, namespace, k)
if err != nil {
return err
}

if !nsExists {
return ErrNsDoesNotExist
}
if !ownedByEverest {
return ErrNamespaceNotManagedByEverest
}
return nil
}

func (cfg *NamespaceRemoveConfig) validateDatabasesAbsent(ctx context.Context, namespace string) error {
k, err := cliutils.NewKubeclient(zap.NewNop().Sugar(), cfg.KubeconfigPath)
if err != nil {
return err
}

dbsExist, err := k.DatabasesExist(ctx, namespace)
if err != nil {
return errors.Join(err, errors.New("failed to check if databases exist"))
}

if dbsExist && !cfg.Force {
return ErrNamespaceNotEmpty
}
return nil
}

// NamespaceRemover is the CLI operation to remove namespaces.
Expand Down Expand Up @@ -66,9 +136,6 @@ func NewNamespaceRemove(c NamespaceRemoveConfig, l *zap.SugaredLogger) (*Namespa
return n, nil
}

// ErrNamespaceNotEmpty is returned when the namespace is not empty.
var ErrNamespaceNotEmpty = errors.New("cannot remove namespace with running database clusters")

// Run the namespace removal operation.
func (r *NamespaceRemover) Run(ctx context.Context) error {
// This command expects a Helm based installation (< 1.4.0)
Expand All @@ -77,27 +144,8 @@ func (r *NamespaceRemover) Run(ctx context.Context) error {
return err
}

dbsExist, err := r.kubeClient.DatabasesExist(ctx, r.config.Namespaces...)
if err != nil {
return errors.Join(err, errors.New("failed to check if databases exist"))
}

if dbsExist && !r.config.Force {
return ErrNamespaceNotEmpty
}

removalSteps := []steps.Step{}
for _, ns := range r.config.Namespaces {
// Check that the namespace exists.
exists, managedByEverest, err := namespaceExists(ctx, ns, r.kubeClient)
if err != nil {
return errors.Join(err, errors.New("failed to check if namespace exists"))
}
if !exists || !managedByEverest {
r.l.Infof("Namespace '%s' does not exist or not managed by Everest", ns)
fmt.Fprint(os.Stdout, output.Warn("Namespace (%s) does not exist or not managed by Everest, skipping..", ns))
continue
}
var removalSteps []steps.Step
for _, ns := range r.config.NamespaceList {
removalSteps = append(removalSteps, NewRemoveNamespaceSteps(ns, r.config.KeepNamespace, r.kubeClient)...)
}

Expand Down
Loading