Force namespace via cmdline parameter (#53)

* Force name via cmdline parameter

* Use a library to parse CLI flags

* Use less env vars

* Document it
This commit is contained in:
Andrey Pokhilko
2022-10-28 12:39:19 +01:00
committed by GitHub
parent d9edcf2f48
commit 786bddc478
8 changed files with 93 additions and 30 deletions

View File

@@ -57,14 +57,18 @@ helm dashboard
The command above will launch the local Web server and will open the UI in new browser tab. The command will hang The command above will launch the local Web server and will open the UI in new browser tab. The command will hang
waiting for you to terminate it in command-line or web UI. waiting for you to terminate it in command-line or web UI.
You can see the list of available command-line flags by running `helm dashboard --help`.
By default, the web server is only available locally. You can change that by specifying `HD_BIND` environment variable By default, the web server is only available locally. You can change that by specifying `HD_BIND` environment variable
to the desired value. For example, `0.0.0.0` would bind to all IPv4 addresses or `[::0]` would be all IPv6 addresses. to the desired value. For example, `0.0.0.0` would bind to all IPv4 addresses or `[::0]` would be all IPv6 addresses.
If your port 8080 is busy, you can specify a different port to use via `HD_PORT` environment variable. If your port 8080 is busy, you can specify a different port to use via `--port <number>` command-line flag.
If you don't want browser tab to automatically open, set `HD_NOBROWSER=1` in your environment variables. If you need to limit the operations to a specific namespace, please use `--namespace=...` in your command-line.
If you want to increase the logging verbosity and see all the debug info, set `DEBUG=1` environment variable. If you don't want browser tab to automatically open, add `--no-browser` flag in your command line.
If you want to increase the logging verbosity and see all the debug info, use the `--verbose` flag.
## Scanner Integrations ## Scanner Integrations

1
go.mod
View File

@@ -6,6 +6,7 @@ require (
github.com/gin-gonic/gin v1.8.1 github.com/gin-gonic/gin v1.8.1
github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/go-version v1.6.0
github.com/hexops/gotextdiff v1.0.3 github.com/hexops/gotextdiff v1.0.3
github.com/jessevdk/go-flags v1.5.0
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
github.com/sirupsen/logrus v1.9.0 github.com/sirupsen/logrus v1.9.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1

3
go.sum
View File

@@ -35,6 +35,8 @@ github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mO
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
@@ -107,6 +109,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

63
main.go
View File

@@ -1,7 +1,9 @@
package main package main
import ( import (
"fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/jessevdk/go-flags"
"github.com/komodorio/helm-dashboard/pkg/dashboard" "github.com/komodorio/helm-dashboard/pkg/dashboard"
"github.com/pkg/browser" "github.com/pkg/browser"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@@ -14,37 +16,70 @@ var (
date = "unknown" date = "unknown"
) )
func main() { type options struct {
setupLogging() Verbose bool `short:"v" long:"verbose" description:"Show verbose debug information"`
NoBrowser bool `short:"b" long:"no-browser" description:"Do not attempt to open Web browser upon start"`
Version bool `long:"version" description:"Show tool version"`
// TODO: proper command-line parsing Port uint `short:"p" long:"port" description:"Port to start server on" default:"8080"` // TODO: better default port to clash less?
if len(os.Args) > 1 { // dirty thing to allow --help to work
os.Exit(0) Namespace string `short:"n" long:"namespace" description:"Limit operations to a specific namespace"`
} }
address, webServerDone := dashboard.StartServer(version) func main() {
opts := parseFlags()
if os.Getenv("HD_NOBROWSER") == "" { setupLogging(opts.Verbose)
address, webServerDone := dashboard.StartServer(version, int(opts.Port), opts.Namespace, opts.Verbose)
if opts.NoBrowser {
log.Infof("Access web UI at: %s", address)
} else {
log.Infof("Opening web UI: %s", address) log.Infof("Opening web UI: %s", address)
err := browser.OpenURL(address) err := browser.OpenURL(address)
if err != nil { if err != nil {
log.Warnf("Failed to open Web browser for URL: %s", err) log.Warnf("Failed to open Web browser for URL: %s", err)
} }
} else {
log.Infof("Access web UI at: %s", address)
} }
<-webServerDone <-webServerDone
log.Infof("Done.") log.Infof("Done.")
} }
func setupLogging() { func parseFlags() options {
if os.Getenv("DEBUG") == "" { opts := options{}
log.SetLevel(log.InfoLevel) args, err := flags.Parse(&opts)
gin.SetMode(gin.ReleaseMode) if err != nil {
} else { if e, ok := err.(*flags.Error); ok {
if e.Type == flags.ErrHelp {
os.Exit(0)
}
}
// we rely on default behavior to print the problem inside `flags` library
os.Exit(1)
}
if opts.Version {
fmt.Print(version)
os.Exit(0)
}
if len(args) > 0 {
panic("The program does not take argumants, see --help for usage")
}
return opts
}
func setupLogging(verbose bool) {
if verbose {
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
gin.SetMode(gin.DebugMode) gin.SetMode(gin.DebugMode)
log.Debugf("Debug logging is enabled")
} else {
log.SetLevel(log.InfoLevel)
gin.SetMode(gin.ReleaseMode)
} }
log.Infof("Helm Dashboard by Komodor, version %s (%s @ %s)", version, commit, date) log.Infof("Helm Dashboard by Komodor, version %s (%s @ %s)", version, commit, date)
} }

View File

@@ -44,9 +44,9 @@ func contextSetter(data *subproc.DataLayer) gin.HandlerFunc {
} }
} }
func NewRouter(abortWeb utils.ControlChan, data *subproc.DataLayer) *gin.Engine { func NewRouter(abortWeb utils.ControlChan, data *subproc.DataLayer, debug bool) *gin.Engine {
var api *gin.Engine var api *gin.Engine
if os.Getenv("DEBUG") == "" { if debug {
api = gin.New() api = gin.New()
api.Use(gin.Recovery()) api.Use(gin.Recovery())
} else { } else {

View File

@@ -11,11 +11,14 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"net/http" "net/http"
"os" "os"
"strconv"
"time" "time"
) )
func StartServer(version string) (string, utils.ControlChan) { func StartServer(version string, port int, ns string, debug bool) (string, utils.ControlChan) {
data := subproc.DataLayer{} data := subproc.DataLayer{
Namespace: ns,
}
err := data.CheckConnectivity() err := data.CheckConnectivity()
if err != nil { if err != nil {
log.Errorf("Failed to check that Helm is operational, cannot continue. The error was: %s", err) log.Errorf("Failed to check that Helm is operational, cannot continue. The error was: %s", err)
@@ -32,14 +35,10 @@ func StartServer(version string) (string, utils.ControlChan) {
address = "localhost" address = "localhost"
} }
if os.Getenv("HD_PORT") == "" { address += ":" + strconv.Itoa(port)
address += ":8080" // TODO: better default port to clash less?
} else {
address += ":" + os.Getenv("HD_PORT")
}
abort := make(utils.ControlChan) abort := make(utils.ControlChan)
api := NewRouter(abort, &data) api := NewRouter(abort, &data, debug)
done := startBackgroundServer(address, api, abort) done := startBackgroundServer(address, api, abort)
return "http://" + address, done return "http://" + address, done

View File

@@ -26,6 +26,7 @@ type DataLayer struct {
Kubectl string Kubectl string
Scanners []Scanner Scanners []Scanner
VersionInfo *VersionInfo VersionInfo *VersionInfo
Namespace string
} }
type VersionInfo struct { type VersionInfo struct {
@@ -34,7 +35,11 @@ type VersionInfo struct {
} }
func (d *DataLayer) runCommand(cmd ...string) (string, error) { func (d *DataLayer) runCommand(cmd ...string) (string, error) {
log.Debugf("Starting command: %s", cmd) for i, c := range cmd {
if c == "--namespace" && i < len(cmd) { // TODO: in case it's not found - add it?
d.forceNamespace(&cmd[i+1])
}
}
return utils.RunCommand(cmd, map[string]string{"HELM_KUBECONTEXT": d.KubeContext}) return utils.RunCommand(cmd, map[string]string{"HELM_KUBECONTEXT": d.KubeContext})
} }
@@ -67,6 +72,12 @@ func (d *DataLayer) runCommandKubectl(cmd ...string) (string, error) {
return d.runCommand(cmd...) return d.runCommand(cmd...)
} }
func (d *DataLayer) forceNamespace(s *string) {
if d.Namespace != "" {
*s = d.Namespace
}
}
func (d *DataLayer) CheckConnectivity() error { func (d *DataLayer) CheckConnectivity() error {
contexts, err := d.ListContexts() contexts, err := d.ListContexts()
if err != nil { if err != nil {
@@ -128,8 +139,16 @@ func (d *DataLayer) ListContexts() (res []KubeContext, err error) {
} }
func (d *DataLayer) ListInstalled() (res []ReleaseElement, err error) { func (d *DataLayer) ListInstalled() (res []ReleaseElement, err error) {
cmd := []string{"ls", "--all", "--output", "json", "--time-format", time.RFC3339}
// TODO: filter by namespace // TODO: filter by namespace
out, err := d.runCommandHelm("ls", "--all", "--all-namespaces", "--output", "json", "--time-format", time.RFC3339) if d.Namespace == "" {
cmd = append(cmd, "--all-namespaces")
} else {
cmd = append(cmd, "--namespace", d.Namespace)
}
out, err := d.runCommandHelm(cmd...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -218,6 +237,7 @@ func enrichRepoChartsWithInstalled(charts []*RepoChartElement, installed []Relea
pieces := strings.Split(chart.Name, "/") pieces := strings.Split(chart.Name, "/")
if pieces[1] == c { if pieces[1] == c {
// TODO: there can be more than one
chart.InstalledNamespace = rel.Namespace chart.InstalledNamespace = rel.Namespace
chart.InstalledName = rel.Name chart.InstalledName = rel.Name
} }

View File

@@ -34,7 +34,7 @@ func TempFile(txt string) (string, func(), error) {
return "", nil, err return "", nil, err
} }
return file.Name(), func() { os.Remove(file.Name()) }, nil return file.Name(), func() { _ = os.Remove(file.Name()) }, nil
} }
type CmdError struct { type CmdError struct {
@@ -49,6 +49,7 @@ func (e CmdError) Error() string {
} }
func RunCommand(cmd []string, env map[string]string) (string, error) { func RunCommand(cmd []string, env map[string]string) (string, error) {
log.Debugf("Starting command: %s", cmd)
prog := exec.Command(cmd[0], cmd[1:]...) prog := exec.Command(cmd[0], cmd[1:]...)
prog.Env = os.Environ() prog.Env = os.Environ()