Skip to content

Commit

Permalink
EVEREST-1817 Add support CLI command namespaces list (#1083)
Browse files Browse the repository at this point in the history
* EVEREST-1815 Replase AlecAivazis/survey lib with charmbracelet/bubbletea

* EVEREST-1815 Adjust styles

* EVEREST-1815 Test

* EVEREST-1815 Logger test

* EVEREST-1815 Logger test

* TEST

* TEST 2

* Fix Helm config

* EVEREST-1815 Fix running actions in case of tty absence

* Fix go.mod

* Fix tests

* Fix tests

* Fix tests

* Fix password validation regexp

* Fix password validation regexp

* Add tmate to minikube

* Adjust docker daemon config for GH workflowswq

* Adjust docker daemon config for GH workflowswq

* Adjust docker daemon config for GH workflowswq

* Adjust docker config for FE GH e2e workflow

* Fix comments for account CLI commands

* Fix comments

* Fix formatting

* Replace Unicode digits in post-install message

* Add mark for current cursor position

* EVEREST-1817 Add `namespaces list` command support to CLI

* Fixes based on comments

* Update go.mod

* Update configure.go

* Get info about installed operators from DB Engines CRs instead of OLM

---------

Co-authored-by: Percona Platform Robot <61465387+percona-robot@users.noreply.github.com>
  • Loading branch information
maxkondr and percona-robot authored Feb 12, 2025
1 parent 48bbf84 commit 6d44f70
Show file tree
Hide file tree
Showing 4 changed files with 288 additions and 0 deletions.
1 change: 1 addition & 0 deletions commands/namespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ func init() {
namespacesCmd.AddCommand(namespaces.GetNamespacesAddCmd())
namespacesCmd.AddCommand(namespaces.GetNamespacesRemoveCmd())
namespacesCmd.AddCommand(namespaces.GetNamespacesUpdateCmd())
namespacesCmd.AddCommand(namespaces.GetNamespacesListCmd())
}
122 changes: 122 additions & 0 deletions commands/namespaces/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// everest
// Copyright (C) 2025 Percona LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package namespaces provides the namespaces CLI command.
package namespaces

import (
"fmt"
"os"
"strings"

"github.com/rodaine/table"
"github.com/spf13/cobra"

"github.com/percona/everest/pkg/cli"
"github.com/percona/everest/pkg/cli/namespaces"
"github.com/percona/everest/pkg/logger"
"github.com/percona/everest/pkg/output"
)

var (
namespacesListCmd = &cobra.Command{
Use: "list [flags]",
Args: cobra.NoArgs,
Long: "List namespaces managed by Everest.",
Short: "List namespaces managed by Everest.",
Example: `everestctl namespaces list --all`,
PreRun: namespacesListPreRun,
Run: namespacesListRun,
}
namespacesListCfg = &namespaces.NamespaceListConfig{}
)

func init() {
// local command flags
namespacesListCmd.Flags().BoolVarP(&namespacesListCfg.ListAllNamespaces, cli.FlagNamespaceAll, "a", false, "If set, returns all namespaces in kubernetes cluster (excludes system and Everest core namespaces)")
}

func namespacesListPreRun(cmd *cobra.Command, _ []string) { //nolint:revive
// Copy global flags to config
namespacesListCfg.Pretty = !(cmd.Flag(cli.FlagVerbose).Changed || cmd.Flag(cli.FlagJSON).Changed)
namespacesListCfg.KubeconfigPath = cmd.Flag(cli.FlagKubeconfig).Value.String()
}

func namespacesListRun(cmd *cobra.Command, _ []string) {
op, err := namespaces.NewNamespaceLister(*namespacesListCfg, logger.GetLogger())
if err != nil {
output.PrintError(err, logger.GetLogger(), namespacesListCfg.Pretty)
os.Exit(1)
}

if nsList, err := op.Run(cmd.Context()); err != nil {
output.PrintError(err, logger.GetLogger(), namespacesListCfg.Pretty)
os.Exit(1)
} else {
if err := printNamespacesTable(nsList); err != nil {
output.PrintError(err, logger.GetLogger(), namespacesListCfg.Pretty)
os.Exit(1)
}
}
}

// GetNamespacesListCmd returns the command to list a namespaces.
func GetNamespacesListCmd() *cobra.Command {
return namespacesListCmd
}

const (
// columnName is the column name for the namespace.
columnName = "namespace"
// columnManagedByEverest is the column name for the namespace managed by Everest.
columnManagedByEverest = "managed"
// columnOperators is the column name for the installed Everest operators.
columnOperators = "operators"
)

// Print namespaces to console.
func printNamespacesTable(nsList []namespaces.NamespaceInfo) error {
// Prepare table headings.
headings := []interface{}{columnName, columnManagedByEverest, columnOperators}
// Prepare table header.
tbl := table.New(headings...)
tbl.WithHeaderFormatter(func(format string, vals ...interface{}) string {
// Print all in caps.
return strings.ToUpper(fmt.Sprintf(format, vals...))
})

// Return a table row for the given account.
row := func(ns namespaces.NamespaceInfo) []any {
var row []any
for _, heading := range headings {
switch heading {
case columnName:
row = append(row, ns.Name)
case columnManagedByEverest:
row = append(row, len(ns.InstalledOperators) != 0)
case columnOperators:
row = append(row, strings.Join(ns.InstalledOperators, ", "))
}
}
return row
}

for _, ns := range nsList {
tbl.AddRow(row(ns)...)
}

tbl.Print()
return nil
}
2 changes: 2 additions & 0 deletions pkg/cli/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ const (
FlagKeepNamespace = "keep-namespace"
// FlagNamespaceForce is the name of the force flag.
FlagNamespaceForce = "force"
// FlagNamespaceAll is the name of the all flag.
FlagNamespaceAll = "all"

// `upgrade` flags

Expand Down
163 changes: 163 additions & 0 deletions pkg/cli/namespaces/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// everest
// Copyright (C) 2025 Percona LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package namespaces provides the functionality to manage namespaces.
package namespaces

import (
"context"
"fmt"
"slices"

"go.uber.org/zap"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

cliutils "github.com/percona/everest/pkg/cli/utils"
"github.com/percona/everest/pkg/common"
"github.com/percona/everest/pkg/kubernetes"
)

// skipNamespaces is a list of namespaces that cannot be added to Everest management.
// It contains Kubernetes system, reserved by Everest core and cloud providers specific namespaces.
// Note: this list is not exhaustive and can be extended.
var skipNamespaces = []string{
// Kubernetes native system namespaces.
"kube-system",
"kube-public",
"kube-node-lease",

// Everest core namespaces.
common.SystemNamespace,
common.MonitoringNamespace,
kubernetes.OLMNamespace,

// GKE namespaces.
"gke-managed-cim",
"gke-managed-system",
"gke-managed-volumepopulator",
"gmp-public",
"gmp-system",
}

type (
// NamespaceListConfig is the configuration for the namespace listing operation.
NamespaceListConfig struct {
// KubeconfigPath is a path to a kubeconfig
KubeconfigPath string
// Pretty if set print the output in pretty mode.
Pretty bool
// ListAllNamespaces if set, list all namespaces.
// Note: this flag skips namespaces that cannot be added to Everest management
// (i.e. Kubernetes system, specific to cloud providers and Everest Core namespaces).
ListAllNamespaces bool
}

// NamespaceInfo contains information about a namespace.
NamespaceInfo struct {
// Name is the namespace name.
Name string
// InstalledOperators is a list of installed Percona operators in the namespace.
InstalledOperators []string
}

// NamespaceLister is the CLI operation to list namespaces.
NamespaceLister struct {
cfg NamespaceListConfig
kubeClient *kubernetes.Kubernetes
l *zap.SugaredLogger
}
)

// NewNamespaceLister returns a new CLI operation to list namespaces.
func NewNamespaceLister(c NamespaceListConfig, l *zap.SugaredLogger) (*NamespaceLister, error) {
n := &NamespaceLister{
cfg: c,
l: l.With("component", "namespace-remover"),
}
if c.Pretty {
n.l = zap.NewNop().Sugar()
}

k, err := cliutils.NewKubeclient(n.l, n.cfg.KubeconfigPath)
if err != nil {
return nil, err
}
n.kubeClient = k
return n, nil
}

// Run the namespace list operation.
func (nsL *NamespaceLister) Run(ctx context.Context) ([]NamespaceInfo, error) {
var err error
// This command expects a Helm based installation (< 1.4.0)
_, err = cliutils.CheckHelmInstallation(ctx, nsL.kubeClient)
if err != nil {
return nil, err
}

var nsList *corev1.NamespaceList
var labelSelector string

if !nsL.cfg.ListAllNamespaces {
// show only namespaces already managed by Everest.
labelSelector = fmt.Sprintf("%s=%s", common.KubernetesManagedByLabel, common.Everest)
}

if nsList, err = nsL.kubeClient.ListNamespaces(ctx, metav1.ListOptions{
FieldSelector: fmt.Sprintf("status.phase=%s", corev1.NamespaceActive),
LabelSelector: labelSelector,
}); err != nil {
return nil, err
}

// filter out namespaces that are listed in skipNamespaces and non-active namespaces.
nsList.Items = slices.DeleteFunc(nsList.Items, func(ns corev1.Namespace) bool {
return slices.Contains(skipNamespaces, ns.Name)
})

var toReturn []NamespaceInfo
for _, ns := range nsList.Items {
nsInfo := NamespaceInfo{Name: ns.Name}
if nsInfo.InstalledOperators, err = nsL.getNamespaceOperators(ctx, &ns); err != nil {
return nil, fmt.Errorf("cannot get namespace subscriptions: %w", err)
}
toReturn = append(toReturn, nsInfo)
}
return toReturn, nil
}

// getNamespaceOperators returns a list of installed operators in the namespace.
// It returns an empty list if the namespace is not managed by Everest.
func (nsL *NamespaceLister) getNamespaceOperators(ctx context.Context, ns *v1.Namespace) ([]string, error) {
var toReturn []string
if isManagedByEverest(ns) {
// no need to look for installed operators from namespaces not managed by Everest.
dbEngines, err := nsL.kubeClient.ListDatabaseEngines(ctx, ns.Name)
if err != nil {
return []string{}, fmt.Errorf("cannot list installed DB Engines: %w", err)
}

for _, dbE := range dbEngines.Items {
// need to skip DB Engines that are not installed in this particular namespaces.
if dbE.Status.State != "installed" {
continue
}
toReturn = append(toReturn, fmt.Sprintf("%s(v%s)", dbE.Spec.Type, dbE.Status.OperatorVersion))
}
}
return toReturn, nil
}

0 comments on commit 6d44f70

Please sign in to comment.