[WIP] Major release 1.0 (#147)

* Object model with self-sufficient binary (#131)

* Code cosmetics

* Experimenting with object model and direct HELM usage

* Experiment with object model

* replacing the kubectl

* Progressing

* Save the progress

* Able to start with migration in mind

* Migrated two pieces

* List releases via Helm

* Forgotten field

* Cristallized the problem of ctx switcher

* Reworked to multi-context

* Rollback is also new style

* More migration

* Refactoring

* Describe via code

* Bye-bye kubectl binary

* Eliminate more old code

* Refactor a bit

* Merges

* No binaries in dockerfile

* Commit

* Progress with getting the data

* Learned the thing about get

* One field less

* Sstart with repos

* Repo add

* repo remove

* Repos! Icons!

* Simplified access to data

* Ver listing works

* Ver check works

* Caching and values

* fixup

* Done with repos

* Working on install

* Install work-ish

* Fix UI failing on install

* Upgrade flow works

* Fix image building

* Remove outdated test file

* Move files around

* REfactorings

* Cosmetics

* Test for cache control (#151)

* Files import formatted

* Added go-test tools

* Added test for no-cache header

* added changes

* test for cache behaviour of app

* test for static route (#153)

* Tests: route configuration & context setter (#154)

* Test for route configuration

* Test for context setter middleware

* implemented changes

* Restore coverage profile

Fixes #156

* Cosmetics

* Test for `NewRouter` function (#157)

* Test for `configureScanners` (#158)

* Test for `configureKubectls` (#163)

* Test for repository loading (#169)

- Created `repos_test.go`
- Test: `Load()` of Repositories

* Build all PRs

* Fixes failing test (#171)

* Fixes failing test
- Fixes failing test of repo loading

* handles error for

* Did some changes

* Test for listing of repos (#173)

- and did some code formatting

Signed-off-by: OmAxiani0 <aximaniom@gmail.com>

Signed-off-by: OmAxiani0 <aximaniom@gmail.com>

* Test for adding repo (#175)

- Modified the `repositories.yml` file

Signed-off-by: OmAxiani0 <aximaniom@gmail.com>

Signed-off-by: OmAxiani0 <aximaniom@gmail.com>

* Test for deleting the repository (#176)

* Test for deleting the repository
- Also added cleanup function for `TestAdd`

* Fixes failing test

* Add auto labeler for PR's (#174)

* Add auto labeler for PR's

* Add all file under .github/workflow to 'ci' label

Co-authored-by: Harshit Mehta <harshitm@nvidia.com>

* Test for getting repository (#177)

* Add github workflow for auto PR labeling (#181)

Co-authored-by: Harshit Mehta <harshitm@nvidia.com>

* Stub compilation

* Fixes around installing

* More complex test

* Using object model to execute helm test (#191)

* Expand test

* More test

* Coverage

* Add mutex for operations

* Rectore cluster detection code

* Change receiver to pointer

* Support multiple namespaces

* Cosmetics

* Update repos periodically

* fix tests

* Fix error display

* Allow reconfiguring chart without repo

* mute  linter

* Cosmetics

* Failing approach to parse manifests

Relates to #30

* Report the error properly

*  Add test for dashboard/objects/data.go NewDataLayer (#199)

* Fix problem of wrong namespace

* Added unit tests for releases (#204)

* Rework API routes (#197)

* Bootstrap OpenAPI doc

* Renaming some routes

* Listing namespaces

* k8s part of things

* Repositories section

* Document scanners API

* One more API call

* Progress

* Reworked install flow

* History endpoint

* Textual info section

* Resources endpoint

* Rollback endpoint

* Rollback endpoint

* Unit tests

* Cleanup

* Forgotten tags

* Fix tests

* TODOs

* Rework manifest scanning

* add hasTests flag

* Adding more information on UI for helm test API response (#195)

* Hide test button when no tests

Fixes #115
Improves #195

---------

Signed-off-by: OmAxiani0 <aximaniom@gmail.com>
Co-authored-by: Om Aximani <75031769+OmAximani0@users.noreply.github.com>
Co-authored-by: Harshit Mehta <hdm23061993@gmail.com>
Co-authored-by: Harshit Mehta <harshitm@nvidia.com>
Co-authored-by: Todd Turner <todd@toddtee.sh>
Co-authored-by: arvindsundararajan98 <109727359+arvindsundararajan98@users.noreply.github.com>
This commit is contained in:
Andrey Pokhilko
2023-02-01 13:24:34 +00:00
committed by GitHub
parent 6ffcdf2b8e
commit e13aa2fde6
44 changed files with 4338 additions and 1278 deletions

View File

@@ -0,0 +1,31 @@
package handlers
import (
"github.com/gin-gonic/gin"
"github.com/joomcode/errorx"
"github.com/komodorio/helm-dashboard/pkg/dashboard/objects"
"net/http"
)
const APP = "app"
type Contexted struct {
Data *objects.DataLayer
}
func (h *Contexted) GetApp(c *gin.Context) *objects.Application {
var app *objects.Application
if a, ok := c.Get(APP); ok {
app = a.(*objects.Application)
} else {
err := errorx.IllegalState.New("No application context found")
_ = c.AbortWithError(http.StatusBadRequest, err)
return nil
}
return app
}
func (h *Contexted) EnableClientCache(c *gin.Context) {
c.Header("Cache-Control", "max-age=43200")
}

View File

@@ -2,35 +2,72 @@ package handlers
import (
"errors"
"fmt"
"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/rogpeppe/go-internal/semver"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/repo"
helmtime "helm.sh/helm/v3/pkg/time"
"net/http"
"sort"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"github.com/komodorio/helm-dashboard/pkg/dashboard/subproc"
"github.com/komodorio/helm-dashboard/pkg/dashboard/utils"
)
type HelmHandler struct {
Data *subproc.DataLayer
*Contexted
}
func (h *HelmHandler) GetCharts(c *gin.Context) {
res, err := h.Data.ListInstalled()
func (h *HelmHandler) getRelease(c *gin.Context) *objects.Release {
app := h.GetApp(c)
if app == nil {
return nil
}
rel, err := app.Releases.ByName(c.Param("ns"), c.Param("name"))
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return nil
}
return rel
}
func (h *HelmHandler) GetReleases(c *gin.Context) {
app := h.GetApp(c)
if app == nil {
return // sets error inside
}
rels, err := app.Releases.List()
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
res := []*ReleaseElement{}
for _, r := range rels {
res = append(res, HReleaseToJSON(r.Orig))
}
c.IndentedJSON(http.StatusOK, res)
}
func (h *HelmHandler) Uninstall(c *gin.Context) {
qp, err := utils.GetQueryProps(c, false)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return
rel := h.getRelease(c)
if rel == nil {
return // error state is set inside
}
err = h.Data.ReleaseUninstall(qp.Namespace, qp.Name)
err := rel.Uninstall()
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
@@ -39,13 +76,18 @@ func (h *HelmHandler) Uninstall(c *gin.Context) {
}
func (h *HelmHandler) Rollback(c *gin.Context) {
qp, err := utils.GetQueryProps(c, true)
rel := h.getRelease(c)
if rel == nil {
return // error state is set inside
}
revn, err := strconv.Atoi(c.PostForm("revision"))
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
err = h.Data.Rollback(qp.Namespace, qp.Name, qp.Revision)
err = rel.Rollback(revn)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
@@ -54,73 +96,178 @@ func (h *HelmHandler) Rollback(c *gin.Context) {
}
func (h *HelmHandler) History(c *gin.Context) {
qp, err := utils.GetQueryProps(c, false)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return
rel := h.getRelease(c)
if rel == nil {
return // error state is set inside
}
res, err := h.Data.ReleaseHistory(qp.Namespace, qp.Name)
revs, err := rel.History()
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
res := []*HistoryElement{}
for _, r := range revs {
res = append(res, HReleaseToHistElem(r.Orig))
}
sort.Slice(res, func(i, j int) bool {
return res[i].Revision < res[j].Revision
})
c.IndentedJSON(http.StatusOK, res)
}
func (h *HelmHandler) Resources(c *gin.Context) {
qp, err := utils.GetQueryProps(c, true)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return
h.EnableClientCache(c)
rel := h.getRelease(c)
if rel == nil {
return // error state is set inside
}
res, err := h.Data.RevisionManifestsParsed(qp.Namespace, qp.Name, qp.Revision)
res, err := objects.ParseManifests(rel.Orig.Manifest)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.IndentedJSON(http.StatusOK, res)
}
func (h *HelmHandler) RepoSearch(c *gin.Context) {
qp, err := utils.GetQueryProps(c, false)
func (h *HelmHandler) RepoVersions(c *gin.Context) {
qp, err := utils.GetQueryProps(c)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return
}
res, err := h.Data.ChartRepoVersions(qp.Name)
app := h.GetApp(c)
if app == nil {
return // sets error inside
}
repos, err := app.Repositories.Containing(qp.Name)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
res := []*RepoChartElement{}
for _, r := range repos {
res = append(res, &RepoChartElement{
Name: r.Name,
Version: r.Version,
AppVersion: r.AppVersion,
Description: r.Description,
Repository: r.Annotations[objects.AnnRepo],
})
}
c.IndentedJSON(http.StatusOK, res)
}
func (h *HelmHandler) RepoLatestVer(c *gin.Context) {
qp, err := utils.GetQueryProps(c)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return
}
app := h.GetApp(c)
if app == nil {
return // sets error inside
}
rep, err := app.Repositories.Containing(qp.Name)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
res := []*RepoChartElement{}
for _, r := range rep {
res = append(res, &RepoChartElement{
Name: r.Name,
Version: r.Version,
AppVersion: r.AppVersion,
Description: r.Description,
Repository: r.Annotations[objects.AnnRepo],
})
}
sort.Slice(res, func(i, j int) bool {
return semver.Compare(res[i].Version, res[j].Version) > 0
})
if len(res) > 0 {
c.IndentedJSON(http.StatusOK, res[:1])
} else {
c.Status(http.StatusNoContent)
}
}
func (h *HelmHandler) RepoCharts(c *gin.Context) {
qp, err := utils.GetQueryProps(c, false)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return
app := h.GetApp(c)
if app == nil {
return // sets error inside
}
res, err := h.Data.ChartRepoCharts(qp.Name)
rep, err := app.Repositories.Get(c.Param("name"))
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.IndentedJSON(http.StatusOK, res)
}
func (h *HelmHandler) RepoUpdate(c *gin.Context) {
qp, err := utils.GetQueryProps(c, false)
charts, err := rep.Charts()
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
err = h.Data.ChartRepoUpdate(qp.Name)
installed, err := app.Releases.List()
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
// TODO: enrich with installed
enrichRepoChartsWithInstalled(charts, installed)
sort.Slice(charts, func(i, j int) bool {
return charts[i].Name < charts[j].Name
})
c.IndentedJSON(http.StatusOK, charts)
}
func enrichRepoChartsWithInstalled(charts []*repo.ChartVersion, installed []*objects.Release) {
for _, rchart := range charts {
for _, rel := range installed {
if rchart.Metadata.Name == rel.Orig.Chart.Name() {
log.Debugf("Matched") // TODO: restore implementation
// TODO: there can be more than one
//rchart.InstalledNamespace = rel.Orig.Namespace
//rchart.InstalledName = rel.Orig.Name
}
}
}
}
func (h *HelmHandler) RepoUpdate(c *gin.Context) {
app := h.GetApp(c)
if app == nil {
return // sets error inside
}
rep, err := app.Repositories.Get(c.Param("name"))
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
err = rep.Update()
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
@@ -128,80 +275,125 @@ func (h *HelmHandler) RepoUpdate(c *gin.Context) {
c.Status(http.StatusNoContent)
}
func (h *HelmHandler) Show(c *gin.Context) {
qp, err := utils.GetQueryProps(c, false)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return
func (h *HelmHandler) Install(c *gin.Context) {
app := h.GetApp(c)
if app == nil {
return // sets error inside
}
res, err := h.Data.ShowChart(qp.Name)
values := map[string]interface{}{}
err := yaml.Unmarshal([]byte(c.PostForm("values")), &values)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.IndentedJSON(http.StatusOK, res)
}
func (h *HelmHandler) Install(c *gin.Context) {
qp, err := utils.GetQueryProps(c, false)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return
justTemplate := c.PostForm("preview") == "true"
ns := c.Param("ns")
if ns == "[empty]" {
ns = ""
}
justTemplate := c.Query("flag") != "true"
isInitial := c.Query("initial") != "true"
out, err := h.Data.ChartInstall(qp.Namespace, qp.Name, c.Query("chart"), c.Query("version"), justTemplate, c.PostForm("values"), isInitial)
rel, err := app.Releases.Install(ns, c.PostForm("name"), c.PostForm("chart"), c.PostForm("version"), justTemplate, values)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
if justTemplate {
manifests := ""
if isInitial {
manifests, err = h.Data.RevisionManifests(qp.Namespace, qp.Name, 0, false)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
}
out = subproc.GetDiff(strings.TrimSpace(manifests), out, "current.yaml", "upgraded.yaml")
c.IndentedJSON(http.StatusOK, rel)
} else {
c.Header("Content-Type", "application/json")
c.IndentedJSON(http.StatusAccepted, rel)
}
c.String(http.StatusAccepted, out)
}
func (h *HelmHandler) Tests(c *gin.Context) {
qp, err := utils.GetQueryProps(c, false)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return
func (h *HelmHandler) Upgrade(c *gin.Context) {
app := h.GetApp(c)
if app == nil {
return // sets error inside
}
out, err := h.Data.RunTests(qp.Namespace, qp.Name)
existing, err := app.Releases.ByName(c.Param("ns"), c.Param("name"))
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
values := map[string]interface{}{}
err = yaml.Unmarshal([]byte(c.PostForm("values")), &values)
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)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
if justTemplate {
c.IndentedJSON(http.StatusOK, rel)
} else {
c.IndentedJSON(http.StatusAccepted, rel)
}
}
func (h *HelmHandler) RunTests(c *gin.Context) {
rel := h.getRelease(c)
if rel == nil {
return // error state is set inside
}
out, err := rel.RunTests()
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.String(http.StatusOK, out)
}
func (h *HelmHandler) GetInfoSection(c *gin.Context) {
qp, err := utils.GetQueryProps(c, true)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
if c.Query("revision") != "" { // don't cache if latest is requested
h.EnableClientCache(c)
}
rel := h.getRelease(c)
if rel == nil {
return // error state is set inside
}
revn, err := strconv.Atoi(c.Query("revision"))
if c.Query("revision") != "" && err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
flag := c.Query("flag") == "true"
rDiff := c.Query("revisionDiff")
res, err := handleGetSection(h.Data, c.Param("section"), rDiff, qp, flag)
rev, err := rel.GetRev(revn)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
var revDiff *objects.Release
revS := c.Query("revisionDiff")
if revS != "" {
revN, err := strconv.Atoi(revS)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
revDiff, err = rel.GetRev(revN)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
}
flag := c.Query("userDefined") == "true"
res, err := h.handleGetSection(rev, c.Param("section"), revDiff, flag)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
@@ -210,25 +402,53 @@ func (h *HelmHandler) GetInfoSection(c *gin.Context) {
}
func (h *HelmHandler) RepoValues(c *gin.Context) {
out, err := h.Data.ShowValues(c.Query("chart"), c.Query("version"))
h.EnableClientCache(c)
app := h.GetApp(c)
if app == nil {
return // sets error inside
}
out, err := app.Repositories.GetChartValues(c.Query("chart"), c.Query("version"))
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.String(http.StatusOK, out)
}
func (h *HelmHandler) RepoList(c *gin.Context) {
out, err := h.Data.ChartRepoList()
app := h.GetApp(c)
if app == nil {
return // sets error inside
}
repos, err := app.Repositories.List()
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
out := []RepositoryElement{}
for _, r := range repos {
out = append(out, RepositoryElement{
Name: r.Orig.Name,
URL: r.Orig.URL,
})
}
c.IndentedJSON(http.StatusOK, out)
}
func (h *HelmHandler) RepoAdd(c *gin.Context) {
_, err := h.Data.ChartRepoAdd(c.PostForm("name"), c.PostForm("url"))
app := h.GetApp(c)
if app == nil {
return // sets error inside
}
// TODO: more repo options to accept
err := app.Repositories.Add(c.PostForm("name"), c.PostForm("url"))
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
@@ -237,13 +457,12 @@ func (h *HelmHandler) RepoAdd(c *gin.Context) {
}
func (h *HelmHandler) RepoDelete(c *gin.Context) {
qp, err := utils.GetQueryProps(c, false)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return
app := h.GetApp(c)
if app == nil {
return // sets error inside
}
_, err = h.Data.ChartRepoDelete(qp.Name)
err := app.Repositories.Delete(c.Param("name"))
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
@@ -251,11 +470,30 @@ func (h *HelmHandler) RepoDelete(c *gin.Context) {
c.Status(http.StatusNoContent)
}
func handleGetSection(data *subproc.DataLayer, section string, rDiff string, qp *utils.QueryProps, flag bool) (string, error) {
sections := map[string]subproc.SectionFn{
"manifests": data.RevisionManifests,
"values": data.RevisionValues,
"notes": data.RevisionNotes,
func (h *HelmHandler) handleGetSection(rel *objects.Release, section string, rDiff *objects.Release, flag bool) (string, error) {
sections := map[string]objects.SectionFn{
"manifests": func(qp *release.Release, b bool) (string, error) { return qp.Manifest, nil },
"notes": func(qp *release.Release, b bool) (string, error) { return qp.Info.Notes, nil },
"values": func(qp *release.Release, b bool) (string, error) {
allVals := qp.Config
if !b {
merged, err := chartutil.CoalesceValues(qp.Chart, qp.Config)
if err != nil {
return "", errorx.Decorate(err, "failed to merge chart vals with user defined")
}
allVals = merged
}
if len(allVals) > 0 {
data, err := yaml.Marshal(allVals)
if err != nil {
return "", errorx.Decorate(err, "failed to serialize values into YAML")
}
return string(data), nil
}
return "", nil
},
}
functor, found := sections[section]
@@ -263,27 +501,130 @@ func handleGetSection(data *subproc.DataLayer, section string, rDiff string, qp
return "", errors.New("unsupported section: " + section)
}
if rDiff != "" {
cRevDiff, err := strconv.Atoi(rDiff)
if err != nil {
return "", err
}
if rDiff != nil {
ext := ".yaml"
if section == "notes" {
ext = ".txt"
}
res, err := subproc.RevisionDiff(functor, ext, qp.Namespace, qp.Name, cRevDiff, qp.Revision, flag)
res, err := RevisionDiff(functor, ext, rDiff.Orig, rel.Orig, flag)
if err != nil {
return "", err
}
return res, nil
}
res, err := functor(qp.Namespace, qp.Name, qp.Revision, flag)
res, err := functor(rel.Orig, flag)
if err != nil {
return "", err
return "", errorx.Decorate(err, "failed to get section info")
}
return res, nil
}
type RepoChartElement struct {
Name string `json:"name"`
Version string `json:"version"`
AppVersion string `json:"app_version"`
Description string `json:"description"`
InstalledNamespace string `json:"installed_namespace"` // custom addition on top of Helm
InstalledName string `json:"installed_name"` // custom addition on top of Helm
Repository string `json:"repository"`
}
func HReleaseToJSON(o *release.Release) *ReleaseElement {
return &ReleaseElement{
Name: o.Name,
Namespace: o.Namespace,
Revision: strconv.Itoa(o.Version),
Updated: o.Info.LastDeployed,
Status: o.Info.Status,
Chart: fmt.Sprintf("%s-%s", o.Chart.Name(), o.Chart.Metadata.Version),
AppVersion: o.Chart.AppVersion(),
Icon: o.Chart.Metadata.Icon,
Description: o.Chart.Metadata.Description,
}
}
type ReleaseElement struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Revision string `json:"revision"`
Updated helmtime.Time `json:"updated"`
Status release.Status `json:"status"`
Chart string `json:"chart"`
AppVersion string `json:"app_version"`
Icon string `json:"icon"`
Description string `json:"description"`
}
type RepositoryElement struct {
Name string `json:"name"`
URL string `json:"url"`
}
type HistoryElement struct {
Revision int `json:"revision"`
Updated helmtime.Time `json:"updated"`
Status release.Status `json:"status"`
Chart string `json:"chart"`
AppVersion string `json:"app_version"`
Description string `json:"description"`
ChartName string `json:"chart_name"` // custom addition on top of Helm
ChartVer string `json:"chart_ver"` // custom addition on top of Helm
HasTests bool `json:"has_tests"`
}
func HReleaseToHistElem(o *release.Release) *HistoryElement {
return &HistoryElement{
Revision: o.Version,
Updated: o.Info.LastDeployed,
Status: o.Info.Status,
Chart: fmt.Sprintf("%s-%s", o.Chart.Name(), o.Chart.Metadata.Version),
AppVersion: o.Chart.AppVersion(),
Description: o.Info.Description,
ChartName: o.Chart.Name(),
ChartVer: o.Chart.Metadata.Version,
HasTests: releaseHasTests(o),
}
}
func RevisionDiff(functor objects.SectionFn, ext string, revision1 *release.Release, revision2 *release.Release, flag bool) (string, error) {
if revision1 == nil || revision2 == nil {
log.Debugf("One of revisions is nil: %v %v", revision1, revision2)
return "", nil
}
manifest1, err := functor(revision1, flag)
if err != nil {
return "", err
}
manifest2, err := functor(revision2, flag)
if err != nil {
return "", err
}
diff := GetDiff(manifest1, manifest2, strconv.Itoa(revision1.Version)+ext, strconv.Itoa(revision2.Version)+ext)
return diff, nil
}
func GetDiff(text1 string, text2 string, name1 string, name2 string) string {
edits := myers.ComputeEdits(span.URIFromPath(""), text1, text2)
unified := gotextdiff.ToUnified(name1, name2, text1, edits)
diff := fmt.Sprint(unified)
log.Debugf("The diff is: %s", diff)
return diff
}
func releaseHasTests(o *release.Release) bool {
for _, h := range o.Hooks {
for _, e := range h.Events {
if e == release.HookTest {
return true
}
}
}
return false
}

View File

@@ -1,20 +1,25 @@
package handlers
import (
"github.com/joomcode/errorx"
"k8s.io/apimachinery/pkg/api/errors"
"net/http"
"github.com/gin-gonic/gin"
"github.com/komodorio/helm-dashboard/pkg/dashboard/subproc"
"github.com/komodorio/helm-dashboard/pkg/dashboard/utils"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v12 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
)
type KubeHandler struct {
Data *subproc.DataLayer
*Contexted
}
func (h *KubeHandler) GetContexts(c *gin.Context) {
app := h.GetApp(c)
if app == nil {
return // sets error inside
}
res, err := h.Data.ListContexts()
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
@@ -24,21 +29,33 @@ func (h *KubeHandler) GetContexts(c *gin.Context) {
}
func (h *KubeHandler) GetResourceInfo(c *gin.Context) {
qp, err := utils.GetQueryProps(c, false)
qp, err := utils.GetQueryProps(c)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return
}
res, err := h.Data.GetResource(qp.Namespace, &v12.Carp{
TypeMeta: v1.TypeMeta{Kind: c.Param("kind")},
ObjectMeta: v1.ObjectMeta{Name: qp.Name},
})
if err != nil {
app := h.GetApp(c)
if app == nil {
return // sets error inside
}
res, err := app.K8s.GetResourceInfo(c.Param("kind"), qp.Namespace, qp.Name)
if errors.IsNotFound(err) {
res = &v12.Carp{Status: v12.CarpStatus{Phase: "NotFound", Message: err.Error()}}
//_ = c.AbortWithError(http.StatusNotFound, err)
//return
} else if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
EnhanceStatus(res)
c.IndentedJSON(http.StatusOK, res)
}
func EnhanceStatus(res *v12.Carp) {
// custom logic to provide most meaningful status for the resource
if res.Status.Phase == "Active" || res.Status.Phase == "Error" {
_ = res.Name + ""
@@ -52,18 +69,21 @@ func (h *KubeHandler) GetResourceInfo(c *gin.Context) {
} else if res.Status.Phase == "" {
res.Status.Phase = "Exists"
}
c.IndentedJSON(http.StatusOK, res)
}
func (h *KubeHandler) Describe(c *gin.Context) {
qp, err := utils.GetQueryProps(c, false)
qp, err := utils.GetQueryProps(c)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return
}
res, err := h.Data.DescribeResource(qp.Namespace, c.Param("kind"), qp.Name)
app := h.GetApp(c)
if app == nil {
return // sets error inside
}
res, err := app.K8s.DescribeResource(c.Param("kind"), qp.Namespace, qp.Name)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
@@ -73,11 +93,21 @@ func (h *KubeHandler) Describe(c *gin.Context) {
}
func (h *KubeHandler) GetNameSpaces(c *gin.Context) {
res, err := h.Data.GetNameSpaces()
if c.Param("kind") != "namespaces" {
_ = c.AbortWithError(http.StatusBadRequest, errorx.AssertionFailed.New("Only 'namespaces' kind is allowed for listing"))
return
}
app := h.GetApp(c)
if app == nil {
return // sets error inside
}
res, err := app.K8s.GetNameSpaces()
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.JSON(http.StatusOK, res)
c.IndentedJSON(http.StatusOK, res)
}

View File

@@ -8,7 +8,7 @@ import (
)
type ScannersHandler struct {
Data *subproc.DataLayer
*Contexted
}
func (h *ScannersHandler) List(c *gin.Context) {
@@ -26,23 +26,10 @@ func (h *ScannersHandler) List(c *gin.Context) {
c.IndentedJSON(http.StatusOK, res)
}
func (h *ScannersHandler) ScanDraftManifest(c *gin.Context) {
qp, err := utils.GetQueryProps(c, false)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return
}
reuseVals := c.Query("initial") != "true"
mnf, err := h.Data.ChartInstall(qp.Namespace, qp.Name, c.Query("chart"), c.Query("version"), true, c.PostForm("values"), reuseVals)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
func (h *ScannersHandler) ScanManifest(c *gin.Context) {
reps := map[string]*subproc.ScanResults{}
for _, scanner := range h.Data.Scanners {
sr, err := scanner.ScanManifests(mnf)
sr, err := scanner.ScanManifests(c.PostForm("manifest"))
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
@@ -55,7 +42,7 @@ func (h *ScannersHandler) ScanDraftManifest(c *gin.Context) {
}
func (h *ScannersHandler) ScanResource(c *gin.Context) {
qp, err := utils.GetQueryProps(c, false)
qp, err := utils.GetQueryProps(c)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return