From 543846eb867b44815efa34e579972d2f123ee576 Mon Sep 17 00:00:00 2001 From: Gabe Cook Date: Wed, 25 Oct 2023 12:05:19 -0500 Subject: [PATCH] feat(status): Add status subcommand --- .goreleaser.yml | 2 + cmd/cmd.go | 2 + cmd/status/status.go | 133 ++++++++++++++++++++++++++++++++++++++++++ docs/kubedb.md | 1 + docs/kubedb_status.md | 35 +++++++++++ go.mod | 1 + go.sum | 2 + 7 files changed, 176 insertions(+) create mode 100644 cmd/status/status.go create mode 100644 docs/kubedb_status.md diff --git a/.goreleaser.yml b/.goreleaser.yml index cf0b44e0..d85c24a4 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -65,6 +65,7 @@ brews: man1.install "manpages/{{ .ProjectName }}-exec.1.gz" man1.install "manpages/{{ .ProjectName }}-port-forward.1.gz" man1.install "manpages/{{ .ProjectName }}-restore.1.gz" + man1.install "manpages/{{ .ProjectName }}-status.1.gz" bash_completion.install "completions/{{ .ProjectName }}.bash" => "{{ .ProjectName }}" zsh_completion.install "completions/{{ .ProjectName }}.zsh" => "_{{ .ProjectName }}" fish_completion.install "completions/{{ .ProjectName }}.fish" @@ -156,6 +157,7 @@ aurs: install -Dm644 "./manpages/{{ .ProjectName }}-exec.1.gz" "${pkgdir}/usr/share/man/man1/{{ .ProjectName }}-exec.1.gz" install -Dm644 "./manpages/{{ .ProjectName }}-port-forward.1.gz" "${pkgdir}/usr/share/man/man1/{{ .ProjectName }}-port-forward.1.gz" install -Dm644 "./manpages/{{ .ProjectName }}-restore.1.gz" "${pkgdir}/usr/share/man/man1/{{ .ProjectName }}-restore.1.gz" + install -Dm644 "./manpages/{{ .ProjectName }}-status.1.gz" "${pkgdir}/usr/share/man/man1/{{ .ProjectName }}-status.1.gz" # completion install -Dm644 "./completions/{{ .ProjectName }}.bash" "${pkgdir}/usr/share/bash-completion/completions/{{ .ProjectName }}" install -Dm644 "./completions/{{ .ProjectName }}.zsh" "${pkgdir}/usr/share/zsh/site-functions/_{{ .ProjectName }}" diff --git a/cmd/cmd.go b/cmd/cmd.go index 6c37e92b..4389e8d4 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -11,6 +11,7 @@ import ( "github.com/clevyr/kubedb/cmd/exec" "github.com/clevyr/kubedb/cmd/port_forward" "github.com/clevyr/kubedb/cmd/restore" + "github.com/clevyr/kubedb/cmd/status" "github.com/clevyr/kubedb/internal/config/flags" "github.com/clevyr/kubedb/internal/consts" "github.com/clevyr/kubedb/internal/util" @@ -55,6 +56,7 @@ func NewCommand() *cobra.Command { dump.New(), restore.New(), port_forward.New(), + status.New(), ) return cmd diff --git a/cmd/status/status.go b/cmd/status/status.go new file mode 100644 index 00000000..71ad8e4a --- /dev/null +++ b/cmd/status/status.go @@ -0,0 +1,133 @@ +package status + +import ( + "errors" + "fmt" + "os" + "strconv" + "strings" + "time" + + "github.com/clevyr/kubedb/internal/config" + "github.com/clevyr/kubedb/internal/config/flags" + "github.com/clevyr/kubedb/internal/consts" + "github.com/clevyr/kubedb/internal/database" + "github.com/clevyr/kubedb/internal/kubernetes" + "github.com/clevyr/kubedb/internal/util" + "github.com/fatih/color" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func New() *cobra.Command { + cmd := &cobra.Command{ + Use: "status", + Short: "View connection status", + GroupID: "ro", + PreRunE: preRun, + RunE: run, + } + flags.JobPodLabels(cmd) + flags.Port(cmd) + flags.Database(cmd) + flags.Username(cmd) + flags.Password(cmd) + return cmd +} + +var conf config.Global + +func preRun(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + log.SetLevel(log.WarnLevel) + flags.BindJobPodLabels(cmd) + return nil +} + +func run(cmd *cobra.Command, args []string) error { + prefixOk := color.GreenString(" ✓") + prefixErr := color.RedString(" ✗") + bold := color.New(color.Bold) + + viper.Set(consts.NamespaceFilterKey, false) + defaultSetupErr := util.DefaultSetup(cmd, &conf, util.SetupOptions{Name: "status"}) + + fmt.Println(bold.Sprintf("Cluster Info")) + if conf.Client.ClientSet != nil { + fmt.Println( + prefixOk, + "Using cluster", + bold.Sprintf(conf.Context), + "at", + bold.Sprintf(conf.Client.ClientConfig.Host), + ) + } else { + fmt.Println(prefixErr, "Failed to load cluster config:", defaultSetupErr.Error()) + os.Exit(1) + } + + if serverVersion, err := conf.Client.Discovery.ServerVersion(); err == nil { + fmt.Println(prefixOk, "Cluster version is", bold.Sprintf(serverVersion.String())) + } else { + fmt.Println(prefixErr, "Cluster version check failed:", err.Error()) + } + + if namespaces, err := conf.Client.Namespaces().List(cmd.Context(), metav1.ListOptions{}); err == nil { + fmt.Println(prefixOk, "Cluster has", bold.Sprintf(strconv.Itoa(len(namespaces.Items))), "namespaces") + } else { + fmt.Println(prefixErr, "Failed to list namespaces:", err.Error()) + } + + fmt.Println(prefixOk, "Using namespace", bold.Sprintf(conf.Client.Namespace)) + + fmt.Println(bold.Sprintf("Database Info")) + if defaultSetupErr == nil { + fmt.Println( + prefixOk, + "Found", + bold.Sprintf(conf.Dialect.Name()), + "database", + bold.Sprintf(conf.DbPod.ObjectMeta.Name), + ) + } else { + if errors.Is(defaultSetupErr, database.ErrDatabaseNotFound) { + fmt.Println(prefixErr, "Could not detect a database") + } else { + fmt.Println(prefixErr, "Failed to search for database:", defaultSetupErr.Error()) + } + os.Exit(1) + } + + if err := util.CreateJob(cmd, &conf, util.SetupOptions{Name: "status"}); err == nil { + fmt.Println(prefixOk, "Jobs can be created") + } else { + fmt.Println(prefixErr, "Job creation failed:", err.Error()) + os.Exit(1) + } + defer util.Teardown(cmd, &conf) + + var buf strings.Builder + listTablesCmd := conf.Dialect.ExecCommand(config.Exec{ + Global: conf, + DisableHeaders: true, + Command: conf.Dialect.ListTablesQuery(), + }) + execOpts := kubernetes.ExecOptions{ + Pod: conf.JobPod, + Cmd: listTablesCmd.String(), + Stdout: &buf, + Stderr: os.Stderr, + PingPeriod: 5 * time.Second, + } + if err := conf.Client.Exec(cmd.Context(), execOpts); err == nil { + names := strings.Split(buf.String(), "\n") + fmt.Println(prefixOk, "Database has", bold.Sprintf(strconv.Itoa(len(names))), "tables") + } else { + fmt.Println(prefixErr, "Failed to connect to database", err.Error()) + os.Exit(1) + } + + return nil +} diff --git a/docs/kubedb.md b/docs/kubedb.md index 0a286a4c..1a425e03 100644 --- a/docs/kubedb.md +++ b/docs/kubedb.md @@ -22,4 +22,5 @@ Painlessly work with databases in Kubernetes. * [kubedb exec](kubedb_exec.md) - Connect to an interactive shell * [kubedb port-forward](kubedb_port-forward.md) - Set up a local port forward * [kubedb restore](kubedb_restore.md) - Restore a sql file to a database +* [kubedb status](kubedb_status.md) - View connection status diff --git a/docs/kubedb_status.md b/docs/kubedb_status.md new file mode 100644 index 00000000..725ba036 --- /dev/null +++ b/docs/kubedb_status.md @@ -0,0 +1,35 @@ +## kubedb status + +View connection status + +``` +kubedb status [flags] +``` + +### Options + +``` + -d, --dbname string Database name to use (default discovered) + -h, --help help for status + --job-pod-labels stringToString Pod labels to add to the job (default []) + -p, --password string Database password (default discovered) + --port uint16 Database port (default discovered) + -U, --username string Database username (default discovered) +``` + +### Options inherited from parent commands + +``` + --context string Kubernetes context name + --dialect string Database dialect. One of (postgres|mariadb|mongodb) (default discovered) + --kubeconfig string Paths to the kubeconfig file (default "$HOME/.kube/config") + --log-format string Log formatter. One of (text|json) (default "text") + --log-level string Log level. One of (trace|debug|info|warning|error|fatal|panic) (default "info") + -n, --namespace string Kubernetes namespace + --pod string Perform detection from a pod instead of searching the namespace +``` + +### SEE ALSO + +* [kubedb](kubedb.md) - Painlessly work with databases in Kubernetes. + diff --git a/go.mod b/go.mod index 9a923049..79e85670 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.21 require ( github.com/AlecAivazis/survey/v2 v2.3.7 github.com/alessio/shellescape v1.4.2 + github.com/fatih/color v1.15.0 github.com/gabe565/go-spinners v1.0.1 github.com/jedib0t/go-pretty/v6 v6.4.7 github.com/klauspost/pgzip v1.2.6 diff --git a/go.sum b/go.sum index 10704849..76b468f8 100644 --- a/go.sum +++ b/go.sum @@ -76,6 +76,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=