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

Add config get CLI command #1208

Merged
merged 3 commits into from
Aug 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
129 changes: 129 additions & 0 deletions cmd/cli/cmd/config/get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
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.Infof("Fetching configuration")
cfg, botkubeVersionStr, err := config.GetFromCluster(cmd.Context(), k8sCfg.K8s, opts.Exporter, false)
if err != nil {
return fmt.Errorf("while getting configuration: %w", err)
}

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
45 changes: 9 additions & 36 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,24 +52,20 @@ 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")
defer func() {
status.End(err == nil)
}()

status.Step("Fetching Botkube configuration")
cfg, pod, err := migrate.GetConfigFromCluster(cmd.Context(), k8sConfig.K8s, opts)
status.Infof("Fetching Botkube configuration")
cfg, botkubeVersionStr, err := config.GetFromCluster(cmd.Context(), k8sConfig.K8s, opts.ConfigExporter, opts.AutoApprove)
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,37 +131,20 @@ 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.StringVarP(&opts.ImageTag, "image-tag", "", "", "Botkube image tag, possible values latest, v1.2.0, ...")
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")
flags.StringVarP(&opts.ImageTag, "image-tag", "", "", "Botkube image tag, possible values latest, v1.2.0, ...")

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

54 changes: 54 additions & 0 deletions cmd/cli/docs/botkube_config_get.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
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)
--cloud-env-api-key string API key environment variable name specified under Deployment for cloud installation. (default "CONFIG_PROVIDER_API_KEY")
--cloud-env-endpoint string Endpoint environment variable name specified under Deployment for cloud installation. (default "CONFIG_PROVIDER_ENDPOINT")
--cloud-env-id string Identifier environment variable name specified under Deployment for cloud installation. (default "CONFIG_PROVIDER_IDENTIFIER")
-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
Loading