From f49f52efe4919d1b19717b1ef6e3c2325c2c3037 Mon Sep 17 00:00:00 2001 From: Andrey Pokhilko Date: Wed, 15 Feb 2023 16:45:28 +0000 Subject: [PATCH] Support working with local charts (#215) * Basic functioning * Support reconfiguring * Improve tests coverage * Always update local repo, don't offer to delete it * Handle multi-repo correctly * Document local charts usage * Screenshot for docs --- .github/pull_request_template.md | 30 +--- README.md | 29 +++- screenshot.png => images/screenshot.png | Bin images/screenshot_local_charts.png | Bin 0 -> 95745 bytes .../screenshot_run_test.png | Bin .../screenshot_run_test_result.png | Bin .../screenshot_scan_manifest.png | Bin .../screenshot_scan_resource.png | Bin main.go | 30 ++-- pkg/dashboard/handlers/helmHandlers.go | 57 +++++-- pkg/dashboard/objects/data.go | 5 +- pkg/dashboard/objects/repos.go | 155 ++++++++++++------ pkg/dashboard/objects/repos_test.go | 88 +++++----- pkg/dashboard/server.go | 15 +- pkg/dashboard/static/actions.js | 42 +++-- pkg/dashboard/static/index.html | 4 +- pkg/dashboard/static/repo.js | 4 +- 17 files changed, 282 insertions(+), 177 deletions(-) rename screenshot.png => images/screenshot.png (100%) create mode 100644 images/screenshot_local_charts.png rename screenshot_run_test.png => images/screenshot_run_test.png (100%) rename screenshot_run_test_result.png => images/screenshot_run_test_result.png (100%) rename screenshot_scan_manifest.png => images/screenshot_scan_manifest.png (100%) rename screenshot_scan_resource.png => images/screenshot_scan_resource.png (100%) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 78b49ef8..85cbd234 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,14 +1,10 @@ - +## Changes Proposed -## Fixes Issue + - + - - -## Changes proposed - - +## Check List -## Check List (Check all the applicable boxes) - -- [ ] My code follows the code style of this project. -- [ ] My change requires changes to the documentation. -- [ ] I have updated the documentation accordingly. -- [ ] All new and existing tests passed. -- [ ] The title of my pull request is a short description of the requested changes. - -## Screenshots - - - -## Note to reviewers +- [ ] The title of my pull request is a short description of the changes +- [ ] This PR relates to some issue: +- [ ] I have documented the changes made (if applicable) +- [ ] I have covered the changes with unit tests - diff --git a/README.md b/README.md index 91b3fa58..de10fc33 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ ![GitHub contributors](https://img.shields.io/github/contributors/komodorio/helm-dashboard) [![GitHub issues](https://img.shields.io/github/issues-raw/komodorio/helm-dashboard)](https://github.com/komodorio/helm-dashboard/issues) ![GitHub stars](https://img.shields.io/github/stars/komodorio/helm-dashboard?style=social) ![GitHub closed issues](https://img.shields.io/github/issues-closed-raw/komodorio/helm-dashboard) ![GitHub pull requests](https://img.shields.io/github/issues-pr/komodorio/helm-dashboard) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/komodorio/helm-dashboard) ![GitHub commit activity](https://img.shields.io/github/commit-activity/m/komodorio/helm-dashboard) [![GitHub license](https://img.shields.io/github/license/komodorio/helm-dashboard)](https://github.com/komodorio/helm-dashboard) -[Screenshot](screenshot.png) +[Screenshot](images/screenshot.png) ## Description @@ -96,29 +96,44 @@ If you want to increase the logging verbosity and see all the debug info, use th The official helm chart is [available here](https://github.com/komodorio/helm-charts/blob/master/charts/helm-dashboard) +## Selected Features -## Execute Helm tests +### Support for Local Charts + +Local Helm chart is a directory with specially named files and a `Chart.yaml` file, which you can install via Helm, without the need to publish the chart into Helm repository. Chart developers might want to experiment with the chart locally, before uploading into public repository. Also, a proprietary application might only use non-published chart as an approach to deploy the software. + +For all the above use-cases, you may use Helm Dashboard UI, spcifying location of your local chart folders via special `--local-chart` command-line parameter. The parameter might be specified multiple times, for example: + +```shell +helm-dashboard --local-chart=/opt/charts/my-private-app --local-chart=/home/dev/sources/app/chart +``` + +When _valid_ local chart sources specified, the repository list would contain a surrogate `[local]` entry, with those charts listed inside. All the chart operations are normal: installing, reconfiguring and upgrading. + +![](images/screenshot_local_charts.png) + +### Execute Helm tests For all the release(s) (installed helm charts), you can execute helm tests for that release. For the tests to execute successfully, you need to have existing tests for that helm chart. You can execute `helm test` for the specific release as below: -![](screenshot_run_test.png) +![](images/screenshot_run_test.png) The result of executed `helm test` for the release will be disapled as below: -![](screenshot_run_test_result.png) +![](images/screenshot_run_test_result.png) -## Scanner Integrations +### Scanner Integrations Upon startup, Helm Dashboard detects the presence of [Trivy](https://github.com/aquasecurity/trivy) and [Checkov](https://github.com/bridgecrewio/checkov) scanners. When available, these scanners are offered on k8s resources page, as well as install/upgrade preview page. You can request scanning of the specific k8s resource in your cluster: -![](screenshot_scan_resource.png) +![](images/screenshot_scan_resource.png) If you want to validate the k8s manifest prior to installing/reconfiguring a Helm chart, look for "Scan for Problems" button at the bottom of the dialog: -![](screenshot_scan_manifest.png) +![](images/screenshot_scan_manifest.png) ## Support Channels diff --git a/screenshot.png b/images/screenshot.png similarity index 100% rename from screenshot.png rename to images/screenshot.png diff --git a/images/screenshot_local_charts.png b/images/screenshot_local_charts.png new file mode 100644 index 0000000000000000000000000000000000000000..fe159108a07ed84930e87f3ececaf55550e209c3 GIT binary patch literal 95745 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°|=o1e~zyp((KV_pGv5 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 Ot15~b3W%qs@BRbC%n4=y literal 0 HcmV?d00001 diff --git a/screenshot_run_test.png b/images/screenshot_run_test.png similarity index 100% rename from screenshot_run_test.png rename to images/screenshot_run_test.png diff --git a/screenshot_run_test_result.png b/images/screenshot_run_test_result.png similarity index 100% rename from screenshot_run_test_result.png rename to images/screenshot_run_test_result.png diff --git a/screenshot_scan_manifest.png b/images/screenshot_scan_manifest.png similarity index 100% rename from screenshot_scan_manifest.png rename to images/screenshot_scan_manifest.png diff --git a/screenshot_scan_resource.png b/images/screenshot_scan_resource.png similarity index 100% rename from screenshot_scan_resource.png rename to images/screenshot_scan_resource.png diff --git a/main.go b/main.go index 8c4cafc1..2b62aa49 100644 --- a/main.go +++ b/main.go @@ -22,14 +22,15 @@ var ( ) type options struct { - Version bool `long:"version" description:"Show tool version"` - 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"` - NoTracking bool `long:"no-analytics" description:"Disable user analytics (GA, DataDog etc.)"` - BindHost string `long:"bind" description:"Host binding to start server (default: localhost)"` // default should be printed but not assigned as the precedence: flag > env > default - Port uint `short:"p" long:"port" description:"Port to start server on" default:"8080"` - Namespace string `short:"n" long:"namespace" description:"Namespace for HELM operations"` - Devel bool `long:"devel" description:"Include development versions of charts"` + Version bool `long:"version" description:"Show tool version"` + 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"` + NoTracking bool `long:"no-analytics" description:"Disable user analytics (Heap, DataDog etc.)"` + BindHost string `long:"bind" description:"Host binding to start server (default: localhost)"` // default should be printed but not assigned as the precedence: flag > env > default + Port uint `short:"p" long:"port" description:"Port to start server on" default:"8080"` + Namespace string `short:"n" long:"namespace" description:"Namespace for HELM operations"` + Devel bool `long:"devel" description:"Include development versions of charts"` + LocalChart []string `long:"local-chart" description:"Specify location of local chart to include into UI"` } func main() { @@ -46,12 +47,13 @@ func main() { setupLogging(opts.Verbose) server := dashboard.Server{ - Version: version, - Namespaces: strings.Split(opts.Namespace, ","), - Address: fmt.Sprintf("%s:%d", opts.BindHost, opts.Port), - Debug: opts.Verbose, - NoTracking: opts.NoTracking, - Devel: opts.Devel, + Version: version, + Namespaces: strings.Split(opts.Namespace, ","), + Address: fmt.Sprintf("%s:%d", opts.BindHost, opts.Port), + Debug: opts.Verbose, + NoTracking: opts.NoTracking, + Devel: opts.Devel, + LocalCharts: opts.LocalChart, } ctx, cancel := context.WithCancel(context.Background()) diff --git a/pkg/dashboard/handlers/helmHandlers.go b/pkg/dashboard/handlers/helmHandlers.go index 2e489a37..91540c3c 100644 --- a/pkg/dashboard/handlers/helmHandlers.go +++ b/pkg/dashboard/handlers/helmHandlers.go @@ -3,11 +3,13 @@ package handlers import ( "errors" "fmt" + "github.com/gin-gonic/gin" "github.com/hexops/gotextdiff" "github.com/hexops/gotextdiff/myers" "github.com/hexops/gotextdiff/span" "github.com/joomcode/errorx" "github.com/komodorio/helm-dashboard/pkg/dashboard/objects" + "github.com/komodorio/helm-dashboard/pkg/dashboard/utils" "github.com/rogpeppe/go-internal/semver" log "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" @@ -15,12 +17,11 @@ import ( "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/repo" helmtime "helm.sh/helm/v3/pkg/time" + "k8s.io/utils/strings/slices" "net/http" "sort" "strconv" - - "github.com/gin-gonic/gin" - "github.com/komodorio/helm-dashboard/pkg/dashboard/utils" + "strings" ) type HelmHandler struct { @@ -162,6 +163,7 @@ func (h *HelmHandler) RepoVersions(c *gin.Context) { AppVersion: r.AppVersion, Description: r.Description, Repository: r.Annotations[objects.AnnRepo], + URLs: r.URLs, }) } @@ -194,6 +196,7 @@ func (h *HelmHandler) RepoLatestVer(c *gin.Context) { AppVersion: r.AppVersion, Description: r.Description, Repository: r.Annotations[objects.AnnRepo], + URLs: r.URLs, }) } @@ -288,12 +291,19 @@ func (h *HelmHandler) Install(c *gin.Context) { return } + repoChart, err := h.checkLocalRepo(c.PostForm("chart")) + if err != nil { + _ = c.AbortWithError(http.StatusInternalServerError, err) + return + } + justTemplate := c.PostForm("preview") == "true" ns := c.Param("ns") if ns == "[empty]" { ns = "" } - rel, err := app.Releases.Install(ns, c.PostForm("name"), c.PostForm("chart"), c.PostForm("version"), justTemplate, values) + + rel, err := app.Releases.Install(ns, c.PostForm("name"), repoChart, c.PostForm("version"), justTemplate, values) if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return @@ -306,6 +316,16 @@ func (h *HelmHandler) Install(c *gin.Context) { } } +func (h *HelmHandler) checkLocalRepo(repoChart string) (string, error) { + if strings.HasPrefix(repoChart, "file://") { + repoChart = repoChart[len("file://"):] + if !slices.Contains(h.Data.LocalCharts, repoChart) { + return "", fmt.Errorf("chart path is not present in local charts: %s", repoChart) + } + } + return repoChart, nil +} + func (h *HelmHandler) Upgrade(c *gin.Context) { app := h.GetApp(c) if app == nil { @@ -325,8 +345,14 @@ func (h *HelmHandler) Upgrade(c *gin.Context) { return } + repoChart, err := h.checkLocalRepo(c.PostForm("chart")) + if err != nil { + _ = c.AbortWithError(http.StatusInternalServerError, err) + return + } + justTemplate := c.PostForm("preview") == "true" - rel, err := existing.Upgrade(c.PostForm("chart"), c.PostForm("version"), justTemplate, values) + rel, err := existing.Upgrade(repoChart, c.PostForm("version"), justTemplate, values) if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return @@ -409,7 +435,13 @@ func (h *HelmHandler) RepoValues(c *gin.Context) { return // sets error inside } - out, err := app.Repositories.GetChartValues(c.Query("chart"), c.Query("version")) + repoChart, err := h.checkLocalRepo(c.Query("chart")) + if err != nil { + _ = c.AbortWithError(http.StatusInternalServerError, err) + return + } + + out, err := app.Repositories.GetChartValues(repoChart, c.Query("version")) if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return @@ -433,8 +465,8 @@ func (h *HelmHandler) RepoList(c *gin.Context) { out := []RepositoryElement{} for _, r := range repos { out = append(out, RepositoryElement{ - Name: r.Orig.Name, - URL: r.Orig.URL, + Name: r.Name(), + URL: r.URL(), }) } @@ -521,15 +553,16 @@ func (h *HelmHandler) handleGetSection(rel *objects.Release, section string, rDi return res, nil } -type RepoChartElement struct { +type RepoChartElement struct { // TODO: do we need it at all? there is existing repo.ChartVersion in Helm Name string `json:"name"` Version string `json:"version"` AppVersion string `json:"app_version"` Description string `json:"description"` - InstalledNamespace string `json:"installed_namespace"` - InstalledName string `json:"installed_name"` - Repository string `json:"repository"` + InstalledNamespace string `json:"installed_namespace"` + InstalledName string `json:"installed_name"` + Repository string `json:"repository"` + URLs []string `json:"urls"` } func HReleaseToJSON(o *release.Release) *ReleaseElement { diff --git a/pkg/dashboard/objects/data.go b/pkg/dashboard/objects/data.go index f5883c06..d21a9feb 100644 --- a/pkg/dashboard/objects/data.go +++ b/pkg/dashboard/objects/data.go @@ -32,6 +32,7 @@ type DataLayer struct { appPerContext map[string]*Application appPerContextMx *sync.Mutex devel bool + LocalCharts []string } type StatusInfo struct { @@ -170,6 +171,8 @@ func (d *DataLayer) AppForCtx(ctx string) (*Application, error) { return nil, errorx.Decorate(err, "Failed to create application for context '%s'", ctx) } + a.Repositories.LocalCharts = d.LocalCharts + app = a d.appPerContext[ctx] = app } @@ -218,7 +221,7 @@ func (d *DataLayer) loopUpdateRepos(ctx context.Context, interval time.Duration) for _, repo := range repos { err := repo.Update() if err != nil { - log.Warnf("Failed to update repo %s: %v", repo.Orig.Name, err) + log.Warnf("Failed to update repo %s: %v", repo.Name(), err) } } } diff --git a/pkg/dashboard/objects/repos.go b/pkg/dashboard/objects/repos.go index 5aedad3a..250a91ba 100644 --- a/pkg/dashboard/objects/repos.go +++ b/pkg/dashboard/objects/repos.go @@ -11,7 +11,6 @@ import ( "github.com/pkg/errors" log "github.com/sirupsen/logrus" "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/getter" @@ -26,9 +25,10 @@ type Repositories struct { HelmConfig *action.Configuration mx sync.Mutex versionConstraint *semver.Constraints + LocalCharts []string } -func (r *Repositories) Load() (*repo.File, error) { +func (r *Repositories) load() (*repo.File, error) { r.mx.Lock() defer r.mx.Unlock() @@ -40,20 +40,28 @@ func (r *Repositories) Load() (*repo.File, error) { return f, nil } -func (r *Repositories) List() ([]*Repository, error) { - f, err := r.Load() +func (r *Repositories) List() ([]Repository, error) { + f, err := r.load() if err != nil { return nil, errorx.Decorate(err, "failed to load repo information") } - res := []*Repository{} + res := []Repository{} for _, item := range f.Repositories { - res = append(res, &Repository{ - Settings: r.Settings, - Orig: item, + res = append(res, &HelmRepo{ + Settings: r.Settings, + Orig: item, + versionConstraint: r.versionConstraint, }) } + if len(r.LocalCharts) > 0 { + lc := LocalChart{ + LocalCharts: r.LocalCharts, + } + res = append(res, &lc) + } + return res, nil } @@ -71,7 +79,7 @@ func (r *Repositories) Add(name string, url string) error { return err } - f, err := r.Load() + f, err := r.load() if err != nil { return errorx.Decorate(err, "Failed to load repo config") } @@ -114,7 +122,7 @@ func (r *Repositories) Add(name string, url string) error { } func (r *Repositories) Delete(name string) error { - f, err := r.Load() + f, err := r.load() if err != nil { return errorx.Decorate(err, "failed to load repo information") } @@ -136,25 +144,22 @@ func (r *Repositories) Delete(name string) error { return nil } -func (r *Repositories) Get(name string) (*Repository, error) { - f, err := r.Load() +func (r *Repositories) Get(name string) (Repository, error) { + l, err := r.List() if err != nil { - return nil, errorx.Decorate(err, "failed to load repo information") + return nil, errorx.Decorate(err, "failed to get list of repos") } - for _, entry := range f.Repositories { - if entry.Name == name { - return &Repository{ - Settings: r.Settings, - Orig: entry, - versionConstraint: r.versionConstraint, - }, nil + for _, entry := range l { + if entry.Name() == name { + return entry, nil } } - return nil, errorx.DataUnavailable.New("Could not find reposiroty '%s'", name) + return nil, errorx.DataUnavailable.New("Could not find repository '%s'", name) } +// Containing returns list of chart versions for the given chart name, across all repositories func (r *Repositories) Containing(name string) (repo.ChartVersions, error) { list, err := r.List() if err != nil { @@ -165,7 +170,7 @@ func (r *Repositories) Containing(name string) (repo.ChartVersions, error) { for _, rep := range list { vers, err := rep.ByName(name) if err != nil { - log.Warnf("Failed to get data from repo '%s', updating it might help", rep.Orig.Name) + log.Warnf("Failed to get data from repo '%s', updating it might help", rep.Name()) log.Debugf("The error was: %v", err) continue } @@ -178,7 +183,7 @@ func (r *Repositories) Containing(name string) (repo.ChartVersions, error) { v.Annotations = map[string]string{} } - v.Annotations[AnnRepo] = rep.Orig.Name + v.Annotations[AnnRepo] = rep.Name() // Validate the versions against semantic version constraints and filter version, err := semver.NewVersion(v.Version) @@ -199,24 +204,6 @@ func (r *Repositories) Containing(name string) (repo.ChartVersions, error) { return res, nil } -func (r *Repositories) GetChart(chart string, ver string) (*chart.Chart, error) { - // TODO: unused method? - client := action.NewShowWithConfig(action.ShowAll, r.HelmConfig) - client.Version = ver - - cp, err := client.ChartPathOptions.LocateChart(chart, r.Settings) - if err != nil { - return nil, errorx.Decorate(err, "failed to locate chart '%s'", chart) - } - - chrt, err := loader.Load(cp) - if err != nil { - return nil, errorx.Decorate(err, "failed to load chart from '%s'", cp) - } - - return chrt, nil -} - func (r *Repositories) GetChartValues(chart string, ver string) (string, error) { // comes from cmd/helm/show.go client := action.NewShowWithConfig(action.ShowValues, r.HelmConfig) @@ -234,7 +221,15 @@ func (r *Repositories) GetChartValues(chart string, ver string) (string, error) return out, nil } -type Repository struct { +type Repository interface { + Name() string + URL() string + Update() error + Charts() (repo.ChartVersions, error) + ByName(name string) (repo.ChartVersions, error) +} + +type HelmRepo struct { Settings *cli.EnvSettings Orig *repo.Entry mx sync.Mutex @@ -242,11 +237,19 @@ type Repository struct { versionConstraint *semver.Constraints } -func (r *Repository) indexFileName() string { +func (r *HelmRepo) Name() string { + return r.Orig.Name +} + +func (r *HelmRepo) URL() string { + return r.Orig.URL +} + +func (r *HelmRepo) indexFileName() string { return filepath.Join(r.Settings.RepositoryCache, helmpath.CacheIndexFile(r.Orig.Name)) } -func (r *Repository) getIndex() (*repo.IndexFile, error) { +func (r *HelmRepo) getIndex() (*repo.IndexFile, error) { r.mx.Lock() defer r.mx.Unlock() @@ -260,13 +263,13 @@ func (r *Repository) getIndex() (*repo.IndexFile, error) { return ind, nil } -func (r *Repository) Charts() ([]*repo.ChartVersion, error) { +func (r *HelmRepo) Charts() (repo.ChartVersions, error) { ind, err := r.getIndex() if err != nil { return nil, errorx.Decorate(err, "failed to get repo index") } - res := []*repo.ChartVersion{} + res := repo.ChartVersions{} for _, cv := range ind.Entries { for _, v := range cv { version, err := semver.NewVersion(v.Version) @@ -292,7 +295,7 @@ func (r *Repository) Charts() ([]*repo.ChartVersion, error) { return res, nil } -func (r *Repository) ByName(name string) (repo.ChartVersions, error) { +func (r *HelmRepo) ByName(name string) (repo.ChartVersions, error) { ind, err := r.getIndex() if err != nil { return nil, errorx.Decorate(err, "failed to get repo index") @@ -305,7 +308,7 @@ func (r *Repository) ByName(name string) (repo.ChartVersions, error) { return repo.ChartVersions{}, nil } -func (r *Repository) Update() error { +func (r *HelmRepo) Update() error { r.mx.Lock() defer r.mx.Unlock() log.Infof("Updating repository: %s", r.Orig.Name) @@ -366,3 +369,59 @@ func versionConstaint(isDevelEnabled bool) (*semver.Constraints, error) { return constraint, nil } + +type LocalChart struct { + LocalCharts []string + + charts map[string]repo.ChartVersions + mx sync.Mutex +} + +// Update reloads the chart information from disk +func (l *LocalChart) Update() error { + l.mx.Lock() + defer l.mx.Unlock() + + l.charts = map[string]repo.ChartVersions{} + for _, lc := range l.LocalCharts { + c, err := loader.Load(lc) + if err != nil { + log.Warnf("Failed to load chart from '%s': %s", lc, err) + continue + } + + // we don't filter out dev versions here, because local chart implies user wants to see the chart anyway + l.charts[c.Name()] = repo.ChartVersions{&repo.ChartVersion{ + URLs: []string{l.URL() + lc}, + Metadata: c.Metadata, + }} + } + return nil +} + +func (l *LocalChart) Name() string { + return "[local]" +} + +func (l *LocalChart) URL() string { + return "file://" +} + +func (l *LocalChart) Charts() (repo.ChartVersions, error) { + _ = l.Update() // always re-read, for chart devs to have quick debug loop + res := repo.ChartVersions{} + for _, c := range l.charts { + res = append(res, c...) + } + return res, nil +} + +func (l *LocalChart) ByName(name string) (repo.ChartVersions, error) { + _ = l.Update() // always re-read, for chart devs to have quick debug loop + for n, c := range l.charts { + if n == name { + return c, nil + } + } + return repo.ChartVersions{}, nil +} diff --git a/pkg/dashboard/objects/repos_test.go b/pkg/dashboard/objects/repos_test.go index f9c2b508..d02256d0 100644 --- a/pkg/dashboard/objects/repos_test.go +++ b/pkg/dashboard/objects/repos_test.go @@ -57,67 +57,61 @@ func initRepository(t *testing.T, filePath string, devel bool) *Repositories { Settings: settings, HelmConfig: &action.Configuration{}, // maybe use copy of getFakeHelmConfig from api_test.go versionConstraint: vc, + LocalCharts: []string{"../../../charts/helm-dashboard"}, } return testRepository } -func TestList(t *testing.T) { +func TestFlow(t *testing.T) { testRepository := initRepository(t, validRepositoryConfigPath, false) + // initial list repos, err := testRepository.List() - if err != nil { - t.Fatal(err) - } + assert.NilError(t, err) + assert.Equal(t, len(repos), 5) - assert.Equal(t, len(repos), 4) -} - -func TestAdd(t *testing.T) { testRepoName := "TEST" testRepoUrl := "https://helm.github.io/examples" - testRepository := initRepository(t, validRepositoryConfigPath, false) - err := testRepository.Add(testRepoName, testRepoUrl) - if err != nil { - t.Fatal(err, "Failed to add repo") - } + // add repo + err = testRepository.Add(testRepoName, testRepoUrl) + assert.NilError(t, err) + // get repo r, err := testRepository.Get(testRepoName) - if err != nil { - t.Fatal(err, "Failed to add repo") - } - - assert.Equal(t, r.Orig.URL, testRepoUrl) -} - -func TestDelete(t *testing.T) { - testRepository := initRepository(t, validRepositoryConfigPath, false) - - testRepoName := "charts" // don't ever delete 'testing'! - err := testRepository.Delete(testRepoName) - if err != nil { - t.Fatal(err, "Failed to delete the repo") - } - - _, err = testRepository.Get(testRepoName) - if err == nil { - t.Fatal("Failed to delete repo") - } -} - -func TestGet(t *testing.T) { - // Initial repositiry name in test file - repoName := "charts" - - testRepository := initRepository(t, validRepositoryConfigPath, false) - - repo, err := testRepository.Get(repoName) - if err != nil { - t.Fatal(err, "Failed to get th repo") - } - - assert.Equal(t, repo.Orig.Name, repoName) + assert.NilError(t, err) + assert.Equal(t, r.URL(), testRepoUrl) + + // update repo + err = r.Update() + assert.NilError(t, err) + + // list charts + c, err := r.Charts() + assert.NilError(t, err) + + // contains chart + c, err = testRepository.Containing(c[0].Name) + assert.NilError(t, err) + + // chart by name from repo + c, err = r.ByName(c[0].Name) + assert.NilError(t, err) + + // get chart values + v, err := testRepository.GetChartValues(r.Name()+"/"+c[0].Name, c[0].Version) + assert.NilError(t, err) + assert.Assert(t, v != "") + + // delete added + err = testRepository.Delete(testRepoName) + assert.NilError(t, err) + + // final list + repos, err = testRepository.List() + assert.NilError(t, err) + assert.Equal(t, len(repos), 5) } func TestRepository_Charts_DevelDisabled(t *testing.T) { diff --git a/pkg/dashboard/server.go b/pkg/dashboard/server.go index a668f57d..eaadf0d7 100644 --- a/pkg/dashboard/server.go +++ b/pkg/dashboard/server.go @@ -24,12 +24,13 @@ import ( ) type Server struct { - Version string - Namespaces []string - Address string - Debug bool - NoTracking bool - Devel bool + Version string + Namespaces []string + Address string + Debug bool + NoTracking bool + Devel bool + LocalCharts []string } func (s *Server) StartServer(ctx context.Context, cancel context.CancelFunc) (string, utils.ControlChan, error) { @@ -38,6 +39,8 @@ func (s *Server) StartServer(ctx context.Context, cancel context.CancelFunc) (st return "", nil, errorx.Decorate(err, "Failed to create data layer") } + data.LocalCharts = s.LocalCharts + isDevModeWithAnalytics := os.Getenv("HD_DEV_ANALYTICS") == "true" data.StatusInfo.Analytics = (!s.NoTracking && s.Version != "0.0.0") || isDevModeWithAnalytics diff --git a/pkg/dashboard/static/actions.js b/pkg/dashboard/static/actions.js index 43cddc46..677618ea 100644 --- a/pkg/dashboard/static/actions.js +++ b/pkg/dashboard/static/actions.js @@ -56,13 +56,7 @@ function checkUpgradeable(name) { function popUpUpgrade(elm, ns, name, verCur, lastRev) { $("#upgradeModal .btn-confirm").prop("disabled", true) - let chart = elm.repository + "/" + elm.name; - if (!elm.name) { - chart = "" - } - - $('#upgradeModal').data("chart", chart).data("initial", !verCur) - $('#upgradeModal form .chart-name').val(chart) + $('#upgradeModal').data("initial", !verCur) $('#upgradeModal').data("newManifest", "") $("#upgradeModalLabel .name").text(elm.name) @@ -93,14 +87,17 @@ function popUpUpgrade(elm, ns, name, verCur, lastRev) { $.getJSON("/api/helm/repositories/versions?name=" + elm.name).fail(function (xhr) { reportError("Failed to find chart in repo", xhr) }).done(function (vers) { + vers.sort((b, a) => (a.version > b.version) - (a.version < b.version)) + // fill versions $('#upgradeModal select').empty() for (let i = 0; i < vers.length; i++) { - const opt = $(""); + const opt = $("").data("ver", vers[i]); + const label = vers[i].repository + " @ " + vers[i].version; if (vers[i].version === verCur) { - opt.html(vers[i].version + " ·") + opt.html(label + " ✓") } else { - opt.html(vers[i].version) + opt.html(label) } $('#upgradeModal select').append(opt) } @@ -162,9 +159,7 @@ function changeTimer() { if (reconfigTimeout) { window.clearTimeout(reconfigTimeout) } - reconfigTimeout = window.setTimeout(function () { - requestChangeDiff() - }, 500) + reconfigTimeout = window.setTimeout(requestChangeDiff, 500) } $("#upgradeModal textarea").keyup(changeTimer) @@ -173,12 +168,26 @@ $("#upgradeModal .rel-ns").keyup(changeTimer) $('#upgradeModal select').change(function () { const self = $(this) + const ver = self.find("option:selected").data("ver"); + + let chart = ver.repository + "/" + ver.name; + if (!ver.name) { + chart = "" + } + + // local chart case + if (ver.urls && ver.urls.length && ver.urls[0].startsWith("file://")) { + chart = ver.urls[0]; + } + + $('#upgradeModal').data("chart", chart) + $('#upgradeModal form .chart-name').val(chart) requestChangeDiff() // fill reference values $("#upgradeModal .ref-vals").html('') - const chart = $("#upgradeModal").data("chart"); + // TODO: if chart is empty, query different URL that will restore values without repo if (chart) { $.get("/api/helm/repositories/values?chart=" + chart + "&version=" + self.val()).fail(function (xhr) { @@ -231,7 +240,6 @@ $('#upgradeModal .btn-scan').click(function () { }) function requestChangeDiff() { - const self = $('#upgradeModal select'); const diffBody = $("#upgradeModalBody"); diffBody.empty().append(' Calculating diff...') $("#upgradeModal .btn-confirm").prop("disabled", true) @@ -394,7 +402,7 @@ $("#btnAddRepository").click(function () { window.location.reload() }) -$("#btnTest").click(function() { +$("#btnTest").click(function () { const myModal = new bootstrap.Modal(document.getElementById('testModal'), {}); $("#testModal .test-result").empty().prepend(' Waiting for completion...') myModal.show() @@ -406,7 +414,7 @@ $("#btnTest").click(function() { myModal.hide() }).done(function (data) { var output; - if(data.length == 0 || data == null || data == "") { + if (data.length == 0 || data == null || data == "") { output = "
Tests executed successfully

Empty response from API
" } else { output = data.replaceAll("\n", "
") diff --git a/pkg/dashboard/static/index.html b/pkg/dashboard/static/index.html index e3fcde61..2eb94553 100644 --- a/pkg/dashboard/static/index.html +++ b/pkg/dashboard/static/index.html @@ -479,7 +479,7 @@ - + + /BANNER END --> \ No newline at end of file diff --git a/pkg/dashboard/static/repo.js b/pkg/dashboard/static/repo.js index 33f67014..b7d0fcb1 100644 --- a/pkg/dashboard/static/repo.js +++ b/pkg/dashboard/static/repo.js @@ -10,7 +10,7 @@ function loadRepoView() { data.sort((a, b) => (a.name > b.name) - (a.name < b.name)) data.forEach(function (elm) { - let opt = $('
  • '); + let opt = $('
  • '); opt.attr('title', elm.url) opt.find("input").val(elm.name).text(elm.name).data("item", elm) opt.find("span").text(elm.name) @@ -30,6 +30,8 @@ function loadRepoView() { $("#sectionRepo .repo-details h2").text(elm.name) $("#sectionRepo .repo-details .url").text(elm.url) + $("#sectionRepo .btn-remove").prop("disabled", elm.url.startsWith('file://')) + $("#sectionRepo .repo-details ul").html('') $.getJSON("/api/helm/repositories/" + elm.name).fail(function (xhr) { reportError("Failed to get list of charts in repo", xhr)