Skip to content

Commit

Permalink
Add config get CLI command
Browse files Browse the repository at this point in the history
  • Loading branch information
mszostok committed Aug 24, 2023
1 parent e88f808 commit 2a32ec8
Show file tree
Hide file tree
Showing 22 changed files with 663 additions and 214 deletions.
17 changes: 17 additions & 0 deletions cmd/cli/cmd/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package config

import "github.com/spf13/cobra"

// NewCmd returns a new cobra.Command subcommand for config-related operations.
func NewCmd() *cobra.Command {
root := &cobra.Command{
Use: "config",
Aliases: []string{"cfg"},
Short: "This command consists of multiple subcommands for working with Botkube configuration",
}

root.AddCommand(
NewGet(),
)
return root
}
130 changes: 130 additions & 0 deletions cmd/cli/cmd/config/get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package config

import (
"fmt"
"os"
"reflect"

"github.com/spf13/cobra"
"gopkg.in/yaml.v3"

"github.com/kubeshop/botkube/internal/cli"
"github.com/kubeshop/botkube/internal/cli/config"
"github.com/kubeshop/botkube/internal/cli/heredoc"
"github.com/kubeshop/botkube/internal/cli/printer"
"github.com/kubeshop/botkube/internal/kubex"
)

type GetOptions struct {
OmitEmpty bool
Exporter config.ExporterOptions
}

// NewGet returns a cobra.Command for getting Botkube configuration.
func NewGet() *cobra.Command {
var opts GetOptions

resourcePrinter := printer.NewForResource(os.Stdout, printer.WithJSON(), printer.WithYAML())

cmd := &cobra.Command{
Use: "get",
Short: "Displays Botkube configuration",
Example: heredoc.WithCLIName(`
# Show configuration for currently installed Botkube
<cli> config get
# Show configuration in JSON format
<cli> config get -ojson
# Save configuration in file
<cli> config get > config.yaml
`, cli.Name),
RunE: func(cmd *cobra.Command, args []string) (err error) {
status := printer.NewStatus(cmd.ErrOrStderr(), "Fetching Botkube configuration")
defer func() {
status.End(err == nil)
}()

k8sCfg, err := kubex.LoadRestConfigWithMetaInformation()
if err != nil {
return fmt.Errorf("while creating k8s config: %w", err)
}

err = status.InfoStructFields("Export details:", exportDetails{
ExporterVersion: opts.Exporter.Tag,
K8sCtx: k8sCfg.CurrentContext,
LookupNamespace: opts.Exporter.BotkubePodNamespace,
LookupPodLabel: opts.Exporter.BotkubePodLabel,
})
if err != nil {
return err
}

status.Step("Fetching configuration")
cfg, botkubeVersionStr, err := config.GetFromCluster(cmd.Context(), k8sCfg.K8s, opts.Exporter)
if err != nil {
return fmt.Errorf("while getting configuration: %w", err)
}
status.End(true)

var raw interface{}
err = yaml.Unmarshal(cfg, &raw)
if err != nil {
return fmt.Errorf("while loading configuration: %w", err)
}

if opts.OmitEmpty {
status.Step("Removing empty keys from configuration")
raw = removeEmptyValues(raw)
status.End(true)
}

status.Step("Exported Botkube configuration installed in version %s", botkubeVersionStr)
status.End(true)

return resourcePrinter.Print(raw)
},
}

flags := cmd.Flags()

flags.BoolVar(&opts.OmitEmpty, "omit-empty-values", true, "Omits empty keys from printed configuration")

opts.Exporter.RegisterFlags(flags)

resourcePrinter.RegisterFlags(flags)

return cmd
}

type exportDetails struct {
K8sCtx string `pretty:"Kubernetes Context"`
ExporterVersion string `pretty:"Exporter Version"`
LookupNamespace string `pretty:"Lookup Namespace"`
LookupPodLabel string `pretty:"Lookup Pod Label"`
}

func removeEmptyValues(obj any) any {
switch v := obj.(type) {
case map[string]any:
newObj := make(map[string]any)
for key, value := range v {
if value != nil {
newValue := removeEmptyValues(value)
if newValue != nil {
newObj[key] = newValue
}
}
}
if len(newObj) == 0 {
return nil
}
return newObj
default:
val := reflect.ValueOf(v)
if val.IsZero() {
return nil
}
return obj
}
}
3 changes: 1 addition & 2 deletions cmd/cli/cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"os"
"time"

"github.com/pkg/errors"
"github.com/spf13/cobra"

"github.com/kubeshop/botkube/internal/cli"
Expand Down Expand Up @@ -36,7 +35,7 @@ func NewInstall() *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
config, err := kubex.LoadRestConfigWithMetaInformation()
if err != nil {
return errors.Wrap(err, "while creating k8s config")
return fmt.Errorf("while creating k8s config: %w", err)
}

return install.Install(cmd.Context(), os.Stdout, config, opts)
Expand Down
41 changes: 7 additions & 34 deletions cmd/cli/cmd/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,17 @@ package cmd

import (
"fmt"
"strings"
"time"

"github.com/AlecAivazis/survey/v2"
"github.com/fatih/color"
semver "github.com/hashicorp/go-version"
"github.com/pkg/browser"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"go.szostok.io/version"
corev1 "k8s.io/api/core/v1"

"github.com/kubeshop/botkube/internal/cli"
"github.com/kubeshop/botkube/internal/cli/config"
"github.com/kubeshop/botkube/internal/cli/heredoc"
"github.com/kubeshop/botkube/internal/cli/migrate"
"github.com/kubeshop/botkube/internal/cli/printer"
Expand All @@ -23,12 +21,8 @@ import (

const (
botkubeVersionConstraints = ">= 1.0, < 1.3"

containerName = "botkube"
)

var DefaultImageTag = "v9.99.9-dev"

// NewMigrate returns a cobra.Command for migrate the OS into Cloud.
func NewMigrate() *cobra.Command {
var opts migrate.Options
Expand Down Expand Up @@ -58,7 +52,7 @@ func NewMigrate() *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) (err error) {
k8sConfig, err := kubex.LoadRestConfigWithMetaInformation()
if err != nil {
return errors.Wrap(err, "while creating k8s config")
return fmt.Errorf("while creating k8s config: %w", err)
}

status := printer.NewStatus(cmd.OutOrStdout(), "Migrating Botkube installation to Cloud")
Expand All @@ -67,15 +61,11 @@ func NewMigrate() *cobra.Command {
}()

status.Step("Fetching Botkube configuration")
cfg, pod, err := migrate.GetConfigFromCluster(cmd.Context(), k8sConfig.K8s, opts)
cfg, botkubeVersionStr, err := config.GetFromCluster(cmd.Context(), k8sConfig.K8s, opts.ConfigExporter)
if err != nil {
return err
}

botkubeVersionStr, err := getBotkubeVersion(pod)
if err != nil {
return err
}
status.Infof("Checking if Botkube version %q can be migrated safely", botkubeVersionStr)

constraint, err := semver.NewConstraint(botkubeVersionConstraints)
Expand Down Expand Up @@ -141,36 +131,19 @@ func NewMigrate() *cobra.Command {
}

flags := login.Flags()
kubex.RegisterKubeconfigFlag(flags)

flags.DurationVar(&opts.Timeout, "timeout", 10*time.Minute, `Maximum time during which the Botkube installation is being watched, where "0" means "infinite". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`)
flags.StringVar(&opts.Token, "token", "", "Botkube Cloud authentication token")
flags.BoolVarP(&opts.Watch, "watch", "w", true, "Watches the status of the Botkube installation until it finish or the defined `--timeout` occurs.")
flags.StringVar(&opts.InstanceName, "instance-name", "", "Botkube Cloud Instance name that will be created")
flags.StringVar(&opts.CloudAPIURL, "cloud-api-url", "https://api.botkube.io/graphql", "Botkube Cloud API URL")
flags.StringVar(&opts.CloudDashboardURL, "cloud-dashboard-url", "https://app.botkube.io", "Botkube Cloud URL")
flags.StringVarP(&opts.Label, "label", "l", "app=botkube", "Label of Botkube pod")
flags.StringVarP(&opts.Namespace, "namespace", "n", "botkube", "Namespace of Botkube pod")
flags.BoolVarP(&opts.SkipConnect, "skip-connect", "q", false, "Skips connecting to Botkube Cloud after migration")
flags.BoolVar(&opts.SkipOpenBrowser, "skip-open-browser", false, "Skips opening web browser after migration")
flags.BoolVarP(&opts.AutoApprove, "auto-approve", "y", false, "Skips interactive approval for upgrading Botkube installation.")
flags.StringVar(&opts.ConfigExporter.Registry, "cfg-exporter-image-registry", "ghcr.io", "Config Exporter job image registry")
flags.StringVar(&opts.ConfigExporter.Repository, "cfg-exporter-image-repo", "kubeshop/botkube-config-exporter", "Config Exporter job image repository")
flags.StringVar(&opts.ConfigExporter.Tag, "cfg-exporter-image-tag", DefaultImageTag, "Config Exporter job image tag")
flags.DurationVar(&opts.ConfigExporter.PollPeriod, "cfg-exporter-poll-period", 1*time.Second, "Config Exporter job poll period")
flags.DurationVar(&opts.ConfigExporter.Timeout, "cfg-exporter-timeout", 1*time.Minute, "Config Exporter job timeout")

return login
}
opts.ConfigExporter.RegisterFlags(flags)
kubex.RegisterKubeconfigFlag(flags)

func getBotkubeVersion(p *corev1.Pod) (string, error) {
for _, c := range p.Spec.Containers {
if c.Name == containerName {
fqin := strings.Split(c.Image, ":")
if len(fqin) > 1 {
return fqin[len(fqin)-1], nil
}
break
}
}
return "", fmt.Errorf("unable to get botkube version: pod %q does not have botkube container", p.Name)
return login
}
2 changes: 2 additions & 0 deletions cmd/cli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/spf13/cobra"
"go.szostok.io/version/extension"

"github.com/kubeshop/botkube/cmd/cli/cmd/config"
"github.com/kubeshop/botkube/internal/cli"
"github.com/kubeshop/botkube/internal/cli/heredoc"
)
Expand Down Expand Up @@ -47,6 +48,7 @@ func NewRoot() *cobra.Command {
NewDocs(),
NewInstall(),
NewUninstall(),
config.NewCmd(),
extension.NewVersionCobraCmd(
extension.WithUpgradeNotice(orgName, repoName),
),
Expand Down
4 changes: 2 additions & 2 deletions cmd/cli/cmd/uninstall.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package cmd

import (
"fmt"
"os"
"time"

"github.com/pkg/errors"
"github.com/spf13/cobra"

"github.com/kubeshop/botkube/internal/cli"
Expand Down Expand Up @@ -35,7 +35,7 @@ func NewUninstall() *cobra.Command {
return err
}
if err != nil {
return errors.Wrap(err, "while creating k8s config")
return fmt.Errorf("while creating k8s config: %w", err)
}

return uninstall.Uninstall(cmd.Context(), os.Stdout, config, opts)
Expand Down
1 change: 1 addition & 0 deletions cmd/cli/docs/botkube.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ botkube [flags]

### SEE ALSO

* [botkube config](botkube_config.md) - This command consists of multiple subcommands for working with Botkube configuration
* [botkube install](botkube_install.md) - install or upgrade Botkube in k8s cluster
* [botkube login](botkube_login.md) - Login to a Botkube Cloud
* [botkube migrate](botkube_migrate.md) - Automatically migrates Botkube installation into Botkube Cloud
Expand Down
25 changes: 25 additions & 0 deletions cmd/cli/docs/botkube_config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
title: botkube config
---

## botkube config

This command consists of multiple subcommands for working with Botkube configuration

### Options

```
-h, --help help for config
```

### Options inherited from parent commands

```
-v, --verbose int/string[=simple] Prints more verbose output. Allowed values: 0 - disable, 1 - simple, 2 - trace (default 0 - disable)
```

### SEE ALSO

* [botkube](botkube.md) - Botkube CLI
* [botkube config get](botkube_config_get.md) - Displays Botkube configuration

51 changes: 51 additions & 0 deletions cmd/cli/docs/botkube_config_get.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
title: botkube config get
---

## botkube config get

Displays Botkube configuration

```
botkube config get [flags]
```

### Examples

```
# Show configuration for currently installed Botkube
botkube config get
# Show configuration in JSON format
botkube config get -ojson
# Save configuration in file
botkube config get > config.yaml
```

### Options

```
--cfg-exporter-image-registry string Registry for the Config Exporter job image (default "ghcr.io")
--cfg-exporter-image-repo string Repository for the Config Exporter job image (default "kubeshop/botkube-config-exporter")
--cfg-exporter-image-tag string Tag of the Config Exporter job image (default "v9.99.9-dev")
--cfg-exporter-poll-period duration Interval used to check if Config Exporter job was finished (default 1s)
--cfg-exporter-timeout duration Maximum execution time for the Config Exporter job (default 1m0s)
-h, --help help for get
-l, --label string Label used for identifying the Botkube pod (default "app=botkube")
-n, --namespace string Namespace of Botkube pod (default "botkube")
--omit-empty-values Omits empty keys from printed configuration (default true)
-o, --output string Output format. One of: json | yaml (default "yaml")
```

### Options inherited from parent commands

```
-v, --verbose int/string[=simple] Prints more verbose output. Allowed values: 0 - disable, 1 - simple, 2 - trace (default 0 - disable)
```

### SEE ALSO

* [botkube config](botkube_config.md) - This command consists of multiple subcommands for working with Botkube configuration

Loading

0 comments on commit 2a32ec8

Please sign in to comment.