diff --git a/README.md b/README.md
index a94240a..5dd3106 100644
--- a/README.md
+++ b/README.md
@@ -1,22 +1,26 @@
-# Helm Dashboard
+#
Helm Dashboard
A simplified way of working with Helm.
+[
](screenshot.png)
+
## Local Testing
-Until we make our repo public, we have to use a custom way to install the plugin.
+Prerequisites: `helm` and `kubectl` binaries installed and operational.
-To install, checkout the source code and run from source dir:
-```shell
-helm plugin install .
-```
+Until we make our repo public, we have to use a custom way to install the plugin.
There is a need to build binary for plugin to function, run:
```shell
go build -o bin/dashboard .
```
-Local install of plugin just creates a symlink, so making the changes and rebuilding the binary would not require reinstall of a plugin.
+To install, checkout the source code and run from source dir:
+```shell
+helm plugin install .
+```
+
+Local install of plugin just creates a symlink, so making the changes and rebuilding the binary would not require to reinstall a plugin.
To use the plugin, run in your terminal:
```shell
@@ -64,4 +68,5 @@ Browsing repositories
Adding new repository
Recognise & show ArgoCD-originating charts/objects
-Have cleaner idea on the web API structure
\ No newline at end of file
+Have cleaner idea on the web API structure
+See if we can build in Chechov or Validkube validation
\ No newline at end of file
diff --git a/pkg/dashboard/api.go b/pkg/dashboard/api.go
index 5b8d94c..ece5eeb 100644
--- a/pkg/dashboard/api.go
+++ b/pkg/dashboard/api.go
@@ -14,6 +14,11 @@ import (
//go:embed static/*
var staticFS embed.FS
+func noCache(c *gin.Context) {
+ c.Header("Cache-Control", "no-cache")
+ c.Next()
+}
+
func errorHandler(c *gin.Context) {
c.Next()
@@ -28,7 +33,7 @@ func errorHandler(c *gin.Context) {
}
}
-func NewRouter(abortWeb ControlChan, data DataLayer) *gin.Engine {
+func NewRouter(abortWeb ControlChan, data *DataLayer) *gin.Engine {
var api *gin.Engine
if os.Getenv("DEBUG") == "" {
api = gin.New()
@@ -37,6 +42,8 @@ func NewRouter(abortWeb ControlChan, data DataLayer) *gin.Engine {
api = gin.Default()
}
+ api.Use(noCache)
+ api.Use(contextSetter(data))
api.Use(errorHandler)
configureStatic(api)
@@ -44,7 +51,7 @@ func NewRouter(abortWeb ControlChan, data DataLayer) *gin.Engine {
return api
}
-func configureRoutes(abortWeb ControlChan, data DataLayer, api *gin.Engine) {
+func configureRoutes(abortWeb ControlChan, data *DataLayer, api *gin.Engine) {
// server shutdown handler
api.DELETE("/", func(c *gin.Context) {
abortWeb <- struct{}{}
@@ -84,7 +91,19 @@ func configureRoutes(abortWeb ControlChan, data DataLayer, api *gin.Engine) {
c.IndentedJSON(http.StatusOK, res)
})
- api.GET("/api/helm/charts/manifest/diff", func(c *gin.Context) {
+ sections := map[string]SectionFn{
+ "manifests": data.RevisionManifests,
+ "values": data.RevisionValues,
+ "notes": data.RevisionNotes,
+ }
+
+ api.GET("/api/helm/charts/:section", func(c *gin.Context) {
+ functor, found := sections[c.Param("section")]
+ if !found {
+ _ = c.AbortWithError(http.StatusNotFound, errors.New("unsupported section: "+c.Param("section")))
+ return
+ }
+
cName := c.Query("chart")
cNamespace := c.Query("namespace")
if cName == "" {
@@ -92,26 +111,40 @@ func configureRoutes(abortWeb ControlChan, data DataLayer, api *gin.Engine) {
return
}
- cRev1, err := strconv.Atoi(c.Query("revision1"))
+ cRev, err := strconv.Atoi(c.Query("revision"))
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
+ flag := c.Query("flag") == "true"
+ rDiff := c.Query("revisionDiff")
+ if rDiff != "" {
+ cRevDiff, err := strconv.Atoi(rDiff)
+ if err != nil {
+ _ = c.AbortWithError(http.StatusInternalServerError, err)
+ return
+ }
- cRev2, err := strconv.Atoi(c.Query("revision2"))
- if err != nil {
- _ = c.AbortWithError(http.StatusInternalServerError, err)
- return
- }
+ ext := ".yaml"
+ if c.Param("section") == "notes" {
+ ext = ".txt"
+ }
- res, err := data.RevisionManifestsDiff(cNamespace, cName, cRev1, cRev2)
- if err != nil {
- _ = c.AbortWithError(http.StatusInternalServerError, err)
- return
+ res, err := RevisionDiff(functor, ext, cNamespace, cName, cRevDiff, cRev, flag)
+ if err != nil {
+ _ = c.AbortWithError(http.StatusInternalServerError, err)
+ return
+ }
+ c.String(http.StatusOK, res)
+ } else {
+ res, err := functor(cNamespace, cName, cRev, flag)
+ if err != nil {
+ _ = c.AbortWithError(http.StatusInternalServerError, err)
+ return
+ }
+ c.String(http.StatusOK, res)
}
- c.IndentedJSON(http.StatusOK, res)
})
-
}
func configureStatic(api *gin.Engine) {
@@ -143,3 +176,13 @@ func configureStatic(api *gin.Engine) {
})
}
}
+
+func contextSetter(data *DataLayer) gin.HandlerFunc {
+ return func(c *gin.Context) {
+ if context, ok := c.Request.Header["X-Kubecontext"]; ok {
+ log.Debugf("Setting current context to: %s", context)
+ data.KubeContext = context[0]
+ }
+ c.Next()
+ }
+}
diff --git a/pkg/dashboard/data.go b/pkg/dashboard/data.go
index 73a0b4c..02ea4aa 100644
--- a/pkg/dashboard/data.go
+++ b/pkg/dashboard/data.go
@@ -25,7 +25,6 @@ type DataLayer struct {
}
func (l *DataLayer) runCommand(cmd ...string) (string, error) {
- // TODO: --kube-context=context-name to juggle clusters
log.Debugf("Starting command: %s", cmd)
prog := exec.Command(cmd[0], cmd[1:]...)
prog.Env = os.Environ()
@@ -44,7 +43,7 @@ func (l *DataLayer) runCommand(cmd ...string) (string, error) {
log.Warnf("STDERR:\n%s", serr)
}
if eerr, ok := err.(*exec.ExitError); ok {
- return "", fmt.Errorf("failed to run command %s: %s", cmd, eerr)
+ return "", fmt.Errorf("failed to run command %s:\nError: %s\nSTDERR:%s", cmd, eerr, serr)
}
return "", err
}
@@ -93,10 +92,12 @@ func (l *DataLayer) CheckConnectivity() error {
return errors.New("did not find any kubectl contexts configured")
}
- _, err = l.runCommandHelm("env")
- if err != nil {
- return err
- }
+ /*
+ _, err = l.runCommandHelm("env") // no point in doing is, since the default context may be invalid
+ if err != nil {
+ return err
+ }
+ */
return nil
}
@@ -158,7 +159,7 @@ func (l *DataLayer) ListInstalled() (res []releaseElement, err error) {
func (l *DataLayer) ChartHistory(namespace string, chartName string) (res []*historyElement, err error) {
// TODO: there is `max` but there is no `offset`
- out, err := l.runCommandHelm("history", chartName, "--namespace", namespace, "--max", "5", "--output", "json")
+ out, err := l.runCommandHelm("history", chartName, "--namespace", namespace, "--output", "json", "--max", "18")
if err != nil {
return nil, err
}
@@ -221,7 +222,9 @@ func (l *DataLayer) ChartRepoVersions(chartName string) (res []repoChartElement,
return res, nil
}
-func (l *DataLayer) RevisionManifests(namespace string, chartName string, revision int) (res string, err error) {
+type SectionFn = func(string, string, int, bool) (string, error)
+
+func (l *DataLayer) RevisionManifests(namespace string, chartName string, revision int, _ bool) (res string, err error) {
out, err := l.runCommandHelm("get", "manifest", chartName, "--namespace", namespace, "--revision", strconv.Itoa(revision))
if err != nil {
return "", err
@@ -229,19 +232,45 @@ func (l *DataLayer) RevisionManifests(namespace string, chartName string, revisi
return out, nil
}
-func (l *DataLayer) RevisionManifestsDiff(namespace string, name string, revision1 int, revision2 int) (string, error) {
- manifest1, err := l.RevisionManifests(namespace, name, revision1)
+func (l *DataLayer) RevisionNotes(namespace string, chartName string, revision int, _ bool) (res string, err error) {
+ out, err := l.runCommandHelm("get", "notes", chartName, "--namespace", namespace, "--revision", strconv.Itoa(revision))
if err != nil {
+ return "", err
+ }
+ return out, nil
+}
+
+func (l *DataLayer) RevisionValues(namespace string, chartName string, revision int, onlyUserDefined bool) (res string, err error) {
+ cmd := []string{"get", "values", chartName, "--namespace", namespace, "--revision", strconv.Itoa(revision), "--output", "yaml"}
+ if !onlyUserDefined {
+ cmd = append(cmd, "--all")
+ }
+ out, err := l.runCommandHelm(cmd...)
+ if err != nil {
+ return "", err
+ }
+ return out, nil
+}
+
+func RevisionDiff(functor SectionFn, ext string, namespace string, name string, revision1 int, revision2 int, flag bool) (string, error) {
+ if revision1 == 0 || revision2 == 0 {
+ log.Debugf("One of revisions is zero: %d %d", revision1, revision2)
return "", nil
}
- manifest2, err := l.RevisionManifests(namespace, name, revision2)
+ manifest1, err := functor(namespace, name, revision1, flag)
if err != nil {
- return "", nil
+ return "", err
+ }
+
+ manifest2, err := functor(namespace, name, revision2, flag)
+ if err != nil {
+ return "", err
}
edits := myers.ComputeEdits(span.URIFromPath(""), manifest1, manifest2)
- unified := gotextdiff.ToUnified("a.txt", "b.txt", manifest1, edits)
+ unified := gotextdiff.ToUnified(strconv.Itoa(revision1)+ext, strconv.Itoa(revision2)+ext, manifest1, edits)
diff := fmt.Sprint(unified)
+ log.Debugf("The diff is: %s", diff)
return diff, nil
}
diff --git a/pkg/dashboard/data_test.go b/pkg/dashboard/data_test.go
index 32f94f4..60ad8c1 100644
--- a/pkg/dashboard/data_test.go
+++ b/pkg/dashboard/data_test.go
@@ -55,13 +55,13 @@ func TestFlow(t *testing.T) {
}
_ = upgrade
- manifests, err := data.RevisionManifests(chart.Namespace, chart.Name, history[len(history)-1].Revision)
+ manifests, err := data.RevisionManifests(chart.Namespace, chart.Name, history[len(history)-1].Revision, true)
if err != nil {
t.Fatal(err)
}
_ = manifests
- diff, err := data.RevisionManifestsDiff(chart.Namespace, chart.Name, history[len(history)-1].Revision, history[len(history)-2].Revision)
+ diff, err := RevisionDiff(data.RevisionManifests, ".yaml", chart.Namespace, chart.Name, history[len(history)-1].Revision, history[len(history)-2].Revision, true)
if err != nil {
t.Fatal(err)
}
diff --git a/pkg/dashboard/server.go b/pkg/dashboard/server.go
index f91900d..b0a4fa6 100644
--- a/pkg/dashboard/server.go
+++ b/pkg/dashboard/server.go
@@ -28,7 +28,7 @@ func StartServer() (string, ControlChan) {
}
abort := make(ControlChan)
- api := NewRouter(abort, data)
+ api := NewRouter(abort, &data)
done := startBackgroundServer(address, api, abort)
return "http://" + address, done
diff --git a/pkg/dashboard/static/index.html b/pkg/dashboard/static/index.html
index 6cd31f8..8f94af5 100644
--- a/pkg/dashboard/static/index.html
+++ b/pkg/dashboard/static/index.html
@@ -9,7 +9,8 @@
integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
-
+
+