mirror of
https://github.com/komodorio/helm-dashboard.git
synced 2026-03-26 14:28:04 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15ce9170f3 | ||
|
|
9a144c1c6f | ||
|
|
f897c0f197 | ||
|
|
671fa949df | ||
|
|
612352d69f | ||
|
|
ef31263797 | ||
|
|
f64fbd4a2e | ||
|
|
dffb8a726b | ||
|
|
14d4886e61 | ||
|
|
0012b0a797 |
27
Makefile
27
Makefile
@@ -1,9 +1,24 @@
|
|||||||
pull:
|
DATE ?= $(shell date +%FT%T%z)
|
||||||
git pull
|
VERSION ?= $(git describe --tags --always --dirty --match=v* 2> /dev/null || \
|
||||||
|
cat $(CURDIR)/.version 2> /dev/null || echo "v0")
|
||||||
|
|
||||||
build:
|
.PHONY: test
|
||||||
go build -o bin/dashboard .
|
test: ; $(info $(M) start unit testing...) @
|
||||||
|
@go test $$(go list ./... | grep -v /mocks/) --race -v -short -coverprofile=profile.cov
|
||||||
|
@echo "\n*****************************"
|
||||||
|
@echo "** TOTAL COVERAGE: $$(go tool cover -func profile.cov | grep total | grep -Eo '[0-9]+\.[0-9]+')% **"
|
||||||
|
@echo "*****************************\n"
|
||||||
|
|
||||||
|
.PHONY: pull
|
||||||
|
pull: ; $(info $(M) Pulling source...) @
|
||||||
|
@git pull
|
||||||
|
|
||||||
debug:
|
.PHONY: build
|
||||||
DEBUG=1 ./bin/dashboard
|
build: $(BIN) ; $(info $(M) Building executable...) @ ## Build program binary
|
||||||
|
$Q $(GO) build \
|
||||||
|
-ldflags '-X main.version=$(VERSION) -X main.buildDate=$(DATE)' \
|
||||||
|
-o bin/dashboard .
|
||||||
|
|
||||||
|
.PHONY: debug
|
||||||
|
debug: ; $(info $(M) Running dashboard in debug mode...) @
|
||||||
|
@DEBUG=1 ./bin/dashboard
|
||||||
2
go.mod
2
go.mod
@@ -28,8 +28,10 @@ require (
|
|||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/leodido/go-urn v1.2.1 // indirect
|
github.com/leodido/go-urn v1.2.1 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.3 // indirect
|
github.com/pelletier/go-toml/v2 v2.0.3 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.7 // indirect
|
github.com/ugorji/go/codec v1.2.7 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 // indirect
|
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 // indirect
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -54,12 +54,16 @@ github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
|||||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||||
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.3 h1:h9JoA60e1dVEOpp0PFwJSmt1Htu057NUq9/bUwaO61s=
|
github.com/pelletier/go-toml/v2 v2.0.3 h1:h9JoA60e1dVEOpp0PFwJSmt1Htu057NUq9/bUwaO61s=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.3/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
|
github.com/pelletier/go-toml/v2 v2.0.3/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
|
||||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||||
|
|||||||
9
main.go
9
main.go
@@ -35,7 +35,7 @@ func main() {
|
|||||||
address, webServerDone := dashboard.StartServer(version, int(opts.Port), opts.Namespace, opts.Verbose, opts.NoTracking)
|
address, webServerDone := dashboard.StartServer(version, int(opts.Port), opts.Namespace, opts.Verbose, opts.NoTracking)
|
||||||
|
|
||||||
if !opts.NoTracking {
|
if !opts.NoTracking {
|
||||||
log.Infof("User analytics collected to improve the quality, disable it with --no-analytics")
|
log.Infof("User analytics is collected to improve the quality, disable it with --no-analytics")
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.NoBrowser {
|
if opts.NoBrowser {
|
||||||
@@ -53,7 +53,12 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseFlags() options {
|
func parseFlags() options {
|
||||||
opts := options{}
|
ns := os.Getenv("HELM_NAMESPACE")
|
||||||
|
if ns == "default" {
|
||||||
|
ns = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := options{Namespace: ns}
|
||||||
args, err := flags.Parse(&opts)
|
args, err := flags.Parse(&opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if e, ok := err.(*flags.Error); ok {
|
if e, ok := err.(*flags.Error); ok {
|
||||||
|
|||||||
@@ -2,14 +2,15 @@ package dashboard
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/komodorio/helm-dashboard/pkg/dashboard/handlers"
|
"github.com/komodorio/helm-dashboard/pkg/dashboard/handlers"
|
||||||
"github.com/komodorio/helm-dashboard/pkg/dashboard/subproc"
|
"github.com/komodorio/helm-dashboard/pkg/dashboard/subproc"
|
||||||
"github.com/komodorio/helm-dashboard/pkg/dashboard/utils"
|
"github.com/komodorio/helm-dashboard/pkg/dashboard/utils"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed static/*
|
//go:embed static/*
|
||||||
@@ -72,7 +73,7 @@ func configureRoutes(abortWeb utils.ControlChan, data *subproc.DataLayer, api *g
|
|||||||
|
|
||||||
api.GET("/status", func(c *gin.Context) {
|
api.GET("/status", func(c *gin.Context) {
|
||||||
c.Header("X-Application-Name", "Helm Dashboard by Komodor.io") // to identify ourselves by ourselves
|
c.Header("X-Application-Name", "Helm Dashboard by Komodor.io") // to identify ourselves by ourselves
|
||||||
c.IndentedJSON(http.StatusOK, data.VersionInfo)
|
c.IndentedJSON(http.StatusOK, data.StatusInfo)
|
||||||
})
|
})
|
||||||
|
|
||||||
configureHelms(api.Group("/api/helm"), data)
|
configureHelms(api.Group("/api/helm"), data)
|
||||||
@@ -106,6 +107,7 @@ func configureKubectls(api *gin.RouterGroup, data *subproc.DataLayer) {
|
|||||||
api.GET("/contexts", h.GetContexts)
|
api.GET("/contexts", h.GetContexts)
|
||||||
api.GET("/resources/:kind", h.GetResourceInfo)
|
api.GET("/resources/:kind", h.GetResourceInfo)
|
||||||
api.GET("/describe/:kind", h.Describe)
|
api.GET("/describe/:kind", h.Describe)
|
||||||
|
api.GET("/namespaces", h.GetNameSpaces)
|
||||||
}
|
}
|
||||||
|
|
||||||
func configureStatic(api *gin.Engine) {
|
func configureStatic(api *gin.Engine) {
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/komodorio/helm-dashboard/pkg/dashboard/subproc"
|
"github.com/komodorio/helm-dashboard/pkg/dashboard/subproc"
|
||||||
"github.com/komodorio/helm-dashboard/pkg/dashboard/utils"
|
"github.com/komodorio/helm-dashboard/pkg/dashboard/utils"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
v12 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
|
v12 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
|
||||||
"net/http"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type KubeHandler struct {
|
type KubeHandler struct {
|
||||||
@@ -69,3 +70,13 @@ func (h *KubeHandler) Describe(c *gin.Context) {
|
|||||||
|
|
||||||
c.String(http.StatusOK, res)
|
c.String(http.StatusOK, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *KubeHandler) GetNameSpaces(c *gin.Context) {
|
||||||
|
res, err := h.Data.GetNameSpaces()
|
||||||
|
if err != nil {
|
||||||
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, res)
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,11 +12,18 @@ type ScannersHandler struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *ScannersHandler) List(c *gin.Context) {
|
func (h *ScannersHandler) List(c *gin.Context) {
|
||||||
res := make([]string, 0)
|
type ScannerInfo struct {
|
||||||
for _, scanner := range h.Data.Scanners {
|
SupportedResourceKinds []string
|
||||||
res = append(res, scanner.Name())
|
ManifestScannable bool
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, res)
|
res := map[string]ScannerInfo{}
|
||||||
|
for _, scanner := range h.Data.Scanners {
|
||||||
|
res[scanner.Name()] = ScannerInfo{
|
||||||
|
SupportedResourceKinds: scanner.SupportedResourceKinds(),
|
||||||
|
ManifestScannable: scanner.ManifestIsScannable(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.IndentedJSON(http.StatusOK, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ScannersHandler) ScanDraftManifest(c *gin.Context) {
|
func (h *ScannersHandler) ScanDraftManifest(c *gin.Context) {
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
package scanners
|
package scanners
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"github.com/komodorio/helm-dashboard/pkg/dashboard/subproc"
|
"github.com/komodorio/helm-dashboard/pkg/dashboard/subproc"
|
||||||
"github.com/komodorio/helm-dashboard/pkg/dashboard/utils"
|
"github.com/komodorio/helm-dashboard/pkg/dashboard/utils"
|
||||||
|
"github.com/olekukonko/tablewriter"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
|
v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,11 +14,46 @@ type Checkov struct {
|
|||||||
Data *subproc.DataLayer
|
Data *subproc.DataLayer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Checkov) ManifestIsScannable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checkov) SupportedResourceKinds() []string {
|
||||||
|
// from https://github.com/bridgecrewio/checkov//blob/master/docs/5.Policy%20Index/kubernetes.md
|
||||||
|
return []string{
|
||||||
|
"AdmissionConfiguration",
|
||||||
|
"ClusterRole",
|
||||||
|
"ClusterRoleBinding",
|
||||||
|
"ConfigMap",
|
||||||
|
"CronJob",
|
||||||
|
"DaemonSet",
|
||||||
|
"Deployment",
|
||||||
|
"DeploymentConfig",
|
||||||
|
"Ingress",
|
||||||
|
"Job",
|
||||||
|
"Pod",
|
||||||
|
"PodSecurityPolicy",
|
||||||
|
"PodTemplate",
|
||||||
|
"Policy",
|
||||||
|
"ReplicaSet",
|
||||||
|
"ReplicationController",
|
||||||
|
"Role",
|
||||||
|
"RoleBinding",
|
||||||
|
"Secret",
|
||||||
|
"Service",
|
||||||
|
"ServiceAccount",
|
||||||
|
"StatefulSet",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Checkov) Name() string {
|
func (c *Checkov) Name() string {
|
||||||
return "Checkov"
|
return "Checkov"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checkov) Test() bool {
|
func (c *Checkov) Test() bool {
|
||||||
|
utils.FailLogLevel = log.DebugLevel
|
||||||
|
defer func() { utils.FailLogLevel = log.WarnLevel }()
|
||||||
|
|
||||||
res, err := utils.RunCommand([]string{"checkov", "--version"}, nil)
|
res, err := utils.RunCommand([]string{"checkov", "--version"}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
@@ -33,7 +69,7 @@ func (c *Checkov) ScanManifests(mnf string) (*subproc.ScanResults, error) {
|
|||||||
}
|
}
|
||||||
defer fclose()
|
defer fclose()
|
||||||
|
|
||||||
cmd := []string{"checkov", "--quiet", "--soft-fail", "--framework", "kubernetes", "--output", "cli", "--file", fname}
|
cmd := []string{"checkov", "--quiet", "--soft-fail", "--framework", "kubernetes", "--output", "json", "--file", fname}
|
||||||
out, err := utils.RunCommand(cmd, nil)
|
out, err := utils.RunCommand(cmd, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -41,7 +77,10 @@ func (c *Checkov) ScanManifests(mnf string) (*subproc.ScanResults, error) {
|
|||||||
|
|
||||||
res := &subproc.ScanResults{}
|
res := &subproc.ScanResults{}
|
||||||
|
|
||||||
res.OrigReport = out
|
err = json.Unmarshal([]byte(out), res.OrigReport)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
@@ -61,41 +100,46 @@ func (c *Checkov) ScanResource(ns string, kind string, name string) (*subproc.Sc
|
|||||||
}
|
}
|
||||||
defer fclose()
|
defer fclose()
|
||||||
|
|
||||||
cmd := []string{"checkov", "--quiet", "--soft-fail", "--framework", "kubernetes", "--output", "cli", "--file", fname}
|
cmd := []string{"checkov", "--quiet", "--soft-fail", "--framework", "kubernetes", "--output", "json", "--file", fname}
|
||||||
out, err := utils.RunCommand(cmd, nil)
|
out, err := utils.RunCommand(cmd, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
res := subproc.ScanResults{}
|
cr := CheckovReport{}
|
||||||
_, out, _ = strings.Cut(out, "\n") // kubernetes scan results:
|
err = json.Unmarshal([]byte(out), &cr)
|
||||||
_, out, _ = strings.Cut(out, "\n") // empty line
|
if err != nil {
|
||||||
line, out, found := strings.Cut(out, "\n") // status line
|
return nil, err
|
||||||
if found {
|
|
||||||
parts := strings.FieldsFunc(line, func(r rune) bool {
|
|
||||||
return r == ':' || r == ','
|
|
||||||
})
|
|
||||||
if cnt, err := strconv.Atoi(strings.TrimSpace(parts[1])); err == nil {
|
|
||||||
res.PassedCount = cnt
|
|
||||||
} else {
|
|
||||||
log.Warnf("Failed to parse Checkov output: %s", err)
|
|
||||||
}
|
|
||||||
if cnt, err := strconv.Atoi(strings.TrimSpace(parts[3])); err == nil {
|
|
||||||
res.FailedCount = cnt
|
|
||||||
} else {
|
|
||||||
log.Warnf("Failed to parse Checkov output: %s", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Warnf("Failed to parse Checkov output")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res.OrigReport = strings.TrimSpace(out)
|
res := &subproc.ScanResults{
|
||||||
|
PassedCount: cr.Summary.Passed,
|
||||||
|
FailedCount: cr.Summary.Failed,
|
||||||
|
OrigReport: checkovReportTable(&cr),
|
||||||
|
}
|
||||||
|
|
||||||
return &res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type CheckovResults struct {
|
func checkovReportTable(c *CheckovReport) string {
|
||||||
Summary CheckovSummary
|
data := [][]string{}
|
||||||
|
for _, item := range c.Results.FailedChecks {
|
||||||
|
data = append(data, []string{item.Id, item.Name + "\n", item.Guideline})
|
||||||
|
}
|
||||||
|
|
||||||
|
tableString := &strings.Builder{}
|
||||||
|
table := tablewriter.NewWriter(tableString)
|
||||||
|
table.SetHeader([]string{"ID", "Name", "Guideline"})
|
||||||
|
table.SetBorder(false)
|
||||||
|
table.SetColWidth(64)
|
||||||
|
table.AppendBulk(data)
|
||||||
|
table.Render()
|
||||||
|
return tableString.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheckovReport struct {
|
||||||
|
Summary CheckovSummary `json:"summary"`
|
||||||
|
Results CheckovResults `json:"results"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CheckovSummary struct {
|
type CheckovSummary struct {
|
||||||
@@ -105,3 +149,16 @@ type CheckovSummary struct {
|
|||||||
// parsing errors?
|
// parsing errors?
|
||||||
// skipped ?
|
// skipped ?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CheckovResults struct {
|
||||||
|
FailedChecks []CheckovCheck `json:"failed_checks"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheckovCheck struct {
|
||||||
|
Id string `json:"check_id"`
|
||||||
|
BcId string `json:"bc_check_id"`
|
||||||
|
Name string `json:"check_name"`
|
||||||
|
Resource string `json:"resource"`
|
||||||
|
Guideline string `json:"guideline"`
|
||||||
|
FileLineRange []int `json:"file_line_range"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,11 +12,31 @@ type Trivy struct {
|
|||||||
Data *subproc.DataLayer
|
Data *subproc.DataLayer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Trivy) ManifestIsScannable() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Trivy) SupportedResourceKinds() []string {
|
||||||
|
// from https://github.com/aquasecurity/trivy-kubernetes/blob/main/pkg/k8s/k8s.go#L190
|
||||||
|
return []string{
|
||||||
|
"ReplicaSet",
|
||||||
|
"ReplicationController",
|
||||||
|
"StatefulSet",
|
||||||
|
"Deployment",
|
||||||
|
"CronJob",
|
||||||
|
"DaemonSet",
|
||||||
|
"Job",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Trivy) Name() string {
|
func (c *Trivy) Name() string {
|
||||||
return "Trivy"
|
return "Trivy"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Trivy) Test() bool {
|
func (c *Trivy) Test() bool {
|
||||||
|
utils.FailLogLevel = log.DebugLevel
|
||||||
|
defer func() { utils.FailLogLevel = log.WarnLevel }()
|
||||||
|
|
||||||
res, err := utils.RunCommand([]string{"trivy", "--version"}, nil)
|
res, err := utils.RunCommand([]string{"trivy", "--version"}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -26,11 +26,12 @@ func StartServer(version string, port int, ns string, debug bool, noTracking boo
|
|||||||
os.Exit(1) // TODO: propagate error instead?
|
os.Exit(1) // TODO: propagate error instead?
|
||||||
}
|
}
|
||||||
|
|
||||||
data.VersionInfo = &subproc.VersionInfo{
|
data.StatusInfo = &subproc.StatusInfo{
|
||||||
CurVer: version,
|
CurVer: version,
|
||||||
Analytics: !noTracking,
|
Analytics: !noTracking,
|
||||||
|
LimitedToNamespace: ns,
|
||||||
}
|
}
|
||||||
go checkUpgrade(data.VersionInfo)
|
go checkUpgrade(data.StatusInfo)
|
||||||
|
|
||||||
discoverScanners(&data)
|
discoverScanners(&data)
|
||||||
|
|
||||||
@@ -102,7 +103,7 @@ func discoverScanners(data *subproc.DataLayer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkUpgrade(d *subproc.VersionInfo) {
|
func checkUpgrade(d *subproc.StatusInfo) {
|
||||||
url := "https://api.github.com/repos/komodorio/helm-dashboard/releases/latest"
|
url := "https://api.github.com/repos/komodorio/helm-dashboard/releases/latest"
|
||||||
type GHRelease struct {
|
type GHRelease struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|||||||
@@ -71,6 +71,18 @@ function popUpUpgrade(elm, ns, name, verCur, lastRev) {
|
|||||||
$("#upgradeModal .rel-ns").prop("disabled", false).val("")
|
$("#upgradeModal .rel-ns").prop("disabled", false).val("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$.getJSON("/api/kube/namespaces").fail(function (xhr) {
|
||||||
|
reportError("Failed to get namespaces", xhr)
|
||||||
|
}).done(function(res) {
|
||||||
|
const ns = res.items.map(i => i.metadata.name)
|
||||||
|
$.each(ns, function(i, item) {
|
||||||
|
$("#upgradeModal #ns-datalist").append($("<option>", {
|
||||||
|
value: item,
|
||||||
|
text: item
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
$.getJSON("/api/helm/repo/search?name=" + elm.name).fail(function (xhr) {
|
$.getJSON("/api/helm/repo/search?name=" + elm.name).fail(function (xhr) {
|
||||||
reportError("Failed to find chart in repo", xhr)
|
reportError("Failed to find chart in repo", xhr)
|
||||||
}).done(function (vers) {
|
}).done(function (vers) {
|
||||||
@@ -337,4 +349,4 @@ $("#btnRollback").click(function () {
|
|||||||
$("#btnAddRepository").click(function () {
|
$("#btnAddRepository").click(function () {
|
||||||
setHashParam("section", "repository")
|
setHashParam("section", "repository")
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ const xhr = new XMLHttpRequest();
|
|||||||
xhr.onload = function () {
|
xhr.onload = function () {
|
||||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||||
const status = JSON.parse(xhr.responseText);
|
const status = JSON.parse(xhr.responseText);
|
||||||
if (status.Analytics && status.version !== "dev") {
|
const version = status.CurVer
|
||||||
enableDD(status.version)
|
if (status.Analytics && version !== "dev") {
|
||||||
|
enableDD(version)
|
||||||
enableHeap()
|
enableHeap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,6 +150,12 @@ function showResources(namespace, chart, revision) {
|
|||||||
$.getJSON(url).fail(function (xhr) {
|
$.getJSON(url).fail(function (xhr) {
|
||||||
reportError("Failed to get list of resources", xhr)
|
reportError("Failed to get list of resources", xhr)
|
||||||
}).done(function (data) {
|
}).done(function (data) {
|
||||||
|
const scanners = $("body").data("scanners");
|
||||||
|
const scannableResKinds = new Set();
|
||||||
|
for (let k in scanners) {
|
||||||
|
scanners[k].SupportedResourceKinds.forEach(scannableResKinds.add, scannableResKinds)
|
||||||
|
}
|
||||||
|
|
||||||
resBody.empty();
|
resBody.empty();
|
||||||
for (let i = 0; i < data.length; i++) {
|
for (let i = 0; i < data.length; i++) {
|
||||||
const res = data[i]
|
const res = data[i]
|
||||||
@@ -159,7 +165,7 @@ function showResources(namespace, chart, revision) {
|
|||||||
<div class="col-3 res-name text-break fw-bold"></div>
|
<div class="col-3 res-name text-break fw-bold"></div>
|
||||||
<div class="col-1 res-status overflow-hidden"><span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span></div>
|
<div class="col-1 res-status overflow-hidden"><span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span></div>
|
||||||
<div class="col-4 res-statusmsg"><span class="text-muted small">Getting status...</span></div>
|
<div class="col-4 res-statusmsg"><span class="text-muted small">Getting status...</span></div>
|
||||||
<div class="col-2 res-actions"></div>
|
<div class="col-2 res-actions"><button class='btn btn-sm ms-2 visually-hidden'>Vertical-sizer</button></div>
|
||||||
</div>
|
</div>
|
||||||
`)
|
`)
|
||||||
|
|
||||||
@@ -195,11 +201,13 @@ function showResources(namespace, chart, revision) {
|
|||||||
showDescribe(ns, res.kind, res.metadata.name, badge.clone())
|
showDescribe(ns, res.kind, res.metadata.name, badge.clone())
|
||||||
})
|
})
|
||||||
|
|
||||||
const btn2 = $("<button class='btn btn-sm btn-white border-secondary ms-2'>Scan</button>");
|
if (scannableResKinds.has(res.kind)) {
|
||||||
resBlock.find(".res-actions").append(btn2)
|
const btn2 = $("<button class='btn btn-sm btn-white border-secondary ms-2'>Scan</button>");
|
||||||
btn2.click(function () {
|
resBlock.find(".res-actions").append(btn2)
|
||||||
scanResource(ns, res.kind, res.metadata.name, badge.clone())
|
btn2.click(function () {
|
||||||
})
|
scanResource(ns, res.kind, res.metadata.name, badge.clone())
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -237,25 +245,36 @@ function scanResource(ns, kind, name, badge) {
|
|||||||
body.append("No information from scanners. Make sure you have installed some and scanned object is supported.")
|
body.append("No information from scanners. Make sure you have installed some and scanned object is supported.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tabs = $('<ul class="nav nav-tabs mt-3" role="tablist"></ul>')
|
||||||
|
const content = $('<div class="tab-content"></div>')
|
||||||
|
|
||||||
for (let name in data) {
|
for (let name in data) {
|
||||||
const res = data[name]
|
const res = data[name]
|
||||||
|
|
||||||
if (!res.OrigReport) continue
|
if (!res.OrigReport && !res.PassedCount) continue
|
||||||
const hdr = $("<h3>" + name + " Scan Results</h3>");
|
|
||||||
|
const hdr = $(`<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="` + name + `-tab" data-bs-toggle="tab" data-bs-target="#` + name + `-tab-pane" type="button" role="tab">` + name + `</button>
|
||||||
|
</li>`)
|
||||||
|
|
||||||
if (res.FailedCount) {
|
if (res.FailedCount) {
|
||||||
hdr.append("<span class='badge bg-danger ms-3'>" + res.FailedCount + " failed</span>")
|
hdr.find('button').append("<span class='badge bg-danger ms-2'>" + res.FailedCount + " failed</span>")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.PassedCount) {
|
if (res.PassedCount) {
|
||||||
hdr.append("<span class='badge bg-info ms-3'>" + res.PassedCount + " passed</span>")
|
hdr.find('button').append("<span class='badge bg-info ms-2'>" + res.PassedCount + " passed</span>")
|
||||||
}
|
}
|
||||||
|
|
||||||
body.append(hdr)
|
|
||||||
|
|
||||||
const hl = hljs.highlight(res.OrigReport, {language: 'yaml'}).value
|
const hl = hljs.highlight(res.OrigReport, {language: 'yaml'}).value
|
||||||
const pre = $("<pre class='bg-white rounded p-3' style='font-size: inherit; overflow: unset'></pre>").html(hl)
|
const pre = $("<pre class='bg-white rounded p-3' style='font-size: inherit; overflow: unset'></pre>").html(hl)
|
||||||
body.append(pre)
|
const div = $('<div class="tab-pane fade" id="' + name + '-tab-pane" role="tabpanel" aria-labelledby="profile-tab" tabindex="0"></div>').append(pre)
|
||||||
|
|
||||||
|
tabs.append(hdr)
|
||||||
|
content.append(div)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.append(tabs)
|
||||||
|
body.append(content)
|
||||||
|
tabs.find('li').first().find('button').click()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,18 +90,17 @@
|
|||||||
<button class="btn btn-sm btn-light bg-white border border-secondary btn-remove">
|
<button class="btn btn-sm btn-light bg-white border border-secondary btn-remove">
|
||||||
<i class="bi-trash3"></i> Remove
|
<i class="bi-trash3"></i> Remove
|
||||||
</button>
|
</button>
|
||||||
|
<p class="my-3"><input class="form-control form-control-sm" type="text" placeholder="Filter..."
|
||||||
|
id="inputSearch"></p>
|
||||||
</div>
|
</div>
|
||||||
<div><span class="text-muted small fw-bold me-3">REPOSITORY</span></div>
|
<div><span class="text-muted small fw-bold me-3">REPOSITORY</span></div>
|
||||||
<h2 class="mb-3">name-of-repo</h2>
|
<h2 class="mb-3">name-of-repo</h2>
|
||||||
<div class="mb-5">
|
<div class="mb-5">
|
||||||
<span class="rounded rounded-1 me-2 p-1 px-2 bg-tag text-dark">URL: <span class="url fw-bold">http://somerepo/somepath</span></span>
|
<span class="rounded rounded-1 me-2 p-1 px-2 bg-tag text-dark">URL: <span class="url fw-bold">http://somerepo/somepath</span></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="py-2 mb-3 float-end">
|
||||||
|
|
||||||
<!-- TODO
|
|
||||||
<div class="float-end">
|
|
||||||
<input class="form-control form-control-sm" type="text" placeholder="Filter...">
|
|
||||||
</div>
|
</div>
|
||||||
-->
|
|
||||||
<div class="row bg-secondary rounded px-3 py-2 mb-3 fw-bold small"
|
<div class="row bg-secondary rounded px-3 py-2 mb-3 fw-bold small"
|
||||||
style="text-transform: uppercase">
|
style="text-transform: uppercase">
|
||||||
<div class="col-3">Chart Name</div>
|
<div class="col-3">Chart Name</div>
|
||||||
@@ -109,7 +108,7 @@
|
|||||||
<div class="col-1">Version</div>
|
<div class="col-1">Version</div>
|
||||||
<div class="col-1"></div>
|
<div class="col-1"></div>
|
||||||
</div>
|
</div>
|
||||||
<ul class="list-unstyled mt-4"></ul>
|
<ul class="list-unstyled mt-4 charts"></ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -122,6 +121,7 @@
|
|||||||
<ul class="list-unstyled" id="cluster">
|
<ul class="list-unstyled" id="cluster">
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<h4 id="limitNamespace" class="display-none">Forced Namespace: <span></span></h4>
|
||||||
<!-- TODO
|
<!-- TODO
|
||||||
<h4 class="mt-4">Namespaces</h4>
|
<h4 class="mt-4">Namespaces</h4>
|
||||||
<ul class="list-unstyled" id="namespaces">
|
<ul class="list-unstyled" id="namespaces">
|
||||||
@@ -358,7 +358,9 @@
|
|||||||
Release Name: <input class="form-control rel-name">
|
Release Name: <input class="form-control rel-name">
|
||||||
</label>
|
</label>
|
||||||
<label class="form-label me-4 text-dark">
|
<label class="form-label me-4 text-dark">
|
||||||
Namespace (optional): <input class="form-control rel-ns">
|
Namespace (optional):
|
||||||
|
<input type="text" class="form-control rel-ns" list="ns-datalist"/>
|
||||||
|
<datalist id="ns-datalist"></datalist>
|
||||||
</label>
|
</label>
|
||||||
<label class="form-label me-4 text-dark">
|
<label class="form-label me-4 text-dark">
|
||||||
Cluster: <span class="form-label rel-cluster"></span>
|
Cluster: <span class="form-label rel-cluster"></span>
|
||||||
@@ -391,7 +393,8 @@
|
|||||||
<div id="upgradeModalBody" class="small"></div>
|
<div id="upgradeModalBody" class="small"></div>
|
||||||
</form>
|
</form>
|
||||||
<div class="modal-footer d-flex">
|
<div class="modal-footer d-flex">
|
||||||
<button type="button" class="btn btn-scan bg-white border-secondary">Scan for Problems</button>
|
<button type="button" class="btn btn-scan bg-white border-secondary display-none">Scan for Problems
|
||||||
|
</button>
|
||||||
<button type="button" class="btn btn-primary btn-confirm">Confirm</button>
|
<button type="button" class="btn btn-primary btn-confirm">Confirm</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ function loadRepoView() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
items.find("input").click(function () {
|
items.find("input").click(function () {
|
||||||
|
$("#inputSearch").val('')
|
||||||
const self = $(this)
|
const self = $(this)
|
||||||
const elm = self.data("item");
|
const elm = self.data("item");
|
||||||
setHashParam("repo", elm.name)
|
setHashParam("repo", elm.name)
|
||||||
@@ -61,6 +62,19 @@ function loadRepoView() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$("#inputSearch").keyup(function () {
|
||||||
|
let val = $(this).val().toLowerCase();
|
||||||
|
|
||||||
|
$(".charts li").each(function () {
|
||||||
|
let chartName = $(this.firstElementChild).text().toLowerCase()
|
||||||
|
if (chartName.indexOf(val) >= 0) {
|
||||||
|
$(this).show()
|
||||||
|
} else {
|
||||||
|
$(this).hide()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
$("#sectionRepo .repo-list .btn").click(function () {
|
$("#sectionRepo .repo-list .btn").click(function () {
|
||||||
const myModal = new bootstrap.Modal(document.getElementById('repoAddModal'), {});
|
const myModal = new bootstrap.Modal(document.getElementById('repoAddModal'), {});
|
||||||
myModal.show()
|
myModal.show()
|
||||||
|
|||||||
@@ -17,8 +17,11 @@ $(function () {
|
|||||||
$.getJSON("/api/scanners").fail(function (xhr) {
|
$.getJSON("/api/scanners").fail(function (xhr) {
|
||||||
reportError("Failed to get list of scanners", xhr)
|
reportError("Failed to get list of scanners", xhr)
|
||||||
}).done(function (data) {
|
}).done(function (data) {
|
||||||
if (!data || !data.length) {
|
$("body").data("scanners", data)
|
||||||
$("#upgradeModal .btn-scan").hide()
|
for (let k in data) {
|
||||||
|
if (data[k].ManifestScannable) {
|
||||||
|
$("#upgradeModal .btn-scan").show() // TODO: move this to install flow
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -26,6 +29,9 @@ $(function () {
|
|||||||
reportError("Failed to get tool version", xhr)
|
reportError("Failed to get tool version", xhr)
|
||||||
}).done(function (data) {
|
}).done(function (data) {
|
||||||
fillToolVersion(data)
|
fillToolVersion(data)
|
||||||
|
if (data.LimitedToNamespace) {
|
||||||
|
$("#limitNamespace").show().find("span").text(data.LimitedToNamespace)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -26,10 +26,6 @@ body > .container-fluid {
|
|||||||
min-height: 100% !important;
|
min-height: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#topNav.navbar {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.navbar-brand > a > img {
|
.navbar-brand > a > img {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
@@ -211,17 +207,17 @@ span.link {
|
|||||||
position: static;
|
position: static;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-tabs {
|
nav .nav-tabs {
|
||||||
border: none;
|
border: none;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-tabs .nav-link {
|
nav .nav-tabs .nav-link {
|
||||||
padding-bottom: 0.25rem;
|
padding-bottom: 0.25rem;
|
||||||
color: #3B3D45;
|
color: #3B3D45;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-tabs .nav-link.active {
|
nav .nav-tabs .nav-link.active {
|
||||||
border: none;
|
border: none;
|
||||||
border-bottom: 3px solid #3B3D45;
|
border-bottom: 3px solid #3B3D45;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/hexops/gotextdiff"
|
"github.com/hexops/gotextdiff"
|
||||||
"github.com/hexops/gotextdiff/myers"
|
"github.com/hexops/gotextdiff/myers"
|
||||||
"github.com/hexops/gotextdiff/span"
|
"github.com/hexops/gotextdiff/span"
|
||||||
@@ -13,11 +19,6 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"helm.sh/helm/v3/pkg/release"
|
"helm.sh/helm/v3/pkg/release"
|
||||||
v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
|
v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DataLayer struct {
|
type DataLayer struct {
|
||||||
@@ -25,14 +26,15 @@ type DataLayer struct {
|
|||||||
Helm string
|
Helm string
|
||||||
Kubectl string
|
Kubectl string
|
||||||
Scanners []Scanner
|
Scanners []Scanner
|
||||||
VersionInfo *VersionInfo
|
StatusInfo *StatusInfo
|
||||||
Namespace string
|
Namespace string
|
||||||
}
|
}
|
||||||
|
|
||||||
type VersionInfo struct {
|
type StatusInfo struct {
|
||||||
CurVer string
|
CurVer string
|
||||||
LatestVer string
|
LatestVer string
|
||||||
Analytics bool
|
Analytics bool
|
||||||
|
LimitedToNamespace string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DataLayer) runCommand(cmd ...string) (string, error) {
|
func (d *DataLayer) runCommand(cmd ...string) (string, error) {
|
||||||
@@ -162,7 +164,7 @@ func (d *DataLayer) ListInstalled() (res []ReleaseElement, err error) {
|
|||||||
|
|
||||||
func (d *DataLayer) ChartHistory(namespace string, chartName string) (res []*HistoryElement, err error) {
|
func (d *DataLayer) ChartHistory(namespace string, chartName string) (res []*HistoryElement, err error) {
|
||||||
// TODO: there is `max` but there is no `offset`
|
// TODO: there is `max` but there is no `offset`
|
||||||
out, err := d.runCommandHelm("history", chartName, "--namespace", namespace, "--output", "json", "--max", "18")
|
out, err := d.runCommandHelm("history", chartName, "--namespace", namespace, "--output", "json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -177,7 +179,7 @@ func (d *DataLayer) ChartHistory(namespace string, chartName string) (res []*His
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
elm.ChartName = chartRepoName
|
elm.ChartName = chartRepoName // TODO: move it to frontend?
|
||||||
elm.ChartVer = curVer
|
elm.ChartVer = curVer
|
||||||
elm.Updated.Time = elm.Updated.Time.Round(time.Second)
|
elm.Updated.Time = elm.Updated.Time.Round(time.Second)
|
||||||
}
|
}
|
||||||
@@ -478,6 +480,20 @@ func (d *DataLayer) ChartRepoDelete(name string) (string, error) {
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DataLayer) GetNameSpaces() (res *NamespaceElement, err error) {
|
||||||
|
out, err := d.runCommandKubectl("get", "namespaces", "-o", "json")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal([]byte(out), &res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
func RevisionDiff(functor SectionFn, ext string, namespace string, name string, revision1 int, revision2 int, flag bool) (string, error) {
|
func RevisionDiff(functor SectionFn, ext string, namespace string, name string, revision1 int, revision2 int, flag bool) (string, error) {
|
||||||
if revision1 == 0 || revision2 == 0 {
|
if revision1 == 0 || revision2 == 0 {
|
||||||
log.Debugf("One of revisions is zero: %d %d", revision1, revision2)
|
log.Debugf("One of revisions is zero: %d %d", revision1, revision2)
|
||||||
|
|||||||
@@ -24,8 +24,9 @@ type HistoryElement struct {
|
|||||||
Chart string `json:"chart"`
|
Chart string `json:"chart"`
|
||||||
AppVersion string `json:"app_version"`
|
AppVersion string `json:"app_version"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
ChartName string `json:"chart_name"`
|
|
||||||
ChartVer string `json:"chart_ver"`
|
ChartName string `json:"chart_name"` // custom addition on top of Helm
|
||||||
|
ChartVer string `json:"chart_ver"` // custom addition on top of Helm
|
||||||
}
|
}
|
||||||
|
|
||||||
type RepoChartElement struct {
|
type RepoChartElement struct {
|
||||||
@@ -42,3 +43,11 @@ type RepositoryElement struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NamespaceElement struct {
|
||||||
|
Items []struct {
|
||||||
|
Metadata struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
} `json:"metadata"`
|
||||||
|
} `json:"items"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ type Scanner interface {
|
|||||||
Test() bool // test if the scanner is available
|
Test() bool // test if the scanner is available
|
||||||
ScanManifests(mnf string) (*ScanResults, error) // run the scanner on manifests
|
ScanManifests(mnf string) (*ScanResults, error) // run the scanner on manifests
|
||||||
ScanResource(ns string, kind string, name string) (*ScanResults, error) // run the scanner on k8s resource
|
ScanResource(ns string, kind string, name string) (*ScanResults, error) // run the scanner on k8s resource
|
||||||
|
SupportedResourceKinds() []string
|
||||||
|
ManifestIsScannable() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type ScanResults struct {
|
type ScanResults struct {
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var FailLogLevel = log.WarnLevel // allows to suppress error logging in some situations
|
||||||
|
|
||||||
type ControlChan = chan struct{}
|
type ControlChan = chan struct{}
|
||||||
|
|
||||||
func ChartAndVersion(x string) (string, string, error) {
|
func ChartAndVersion(x string) (string, string, error) {
|
||||||
@@ -64,10 +66,10 @@ func RunCommand(cmd []string, env map[string]string) (string, error) {
|
|||||||
prog.Stderr = &stderr
|
prog.Stderr = &stderr
|
||||||
|
|
||||||
if err := prog.Run(); err != nil {
|
if err := prog.Run(); err != nil {
|
||||||
log.Warnf("Failed command: %s", cmd)
|
log.StandardLogger().Logf(FailLogLevel, "Failed command: %s", cmd)
|
||||||
serr := stderr.Bytes()
|
serr := stderr.Bytes()
|
||||||
if serr != nil {
|
if serr != nil {
|
||||||
log.Warnf("STDERR:\n%s", serr)
|
log.StandardLogger().Logf(FailLogLevel, "STDERR:\n%s", serr)
|
||||||
}
|
}
|
||||||
if eerr, ok := err.(*exec.ExitError); ok {
|
if eerr, ok := err.(*exec.ExitError); ok {
|
||||||
return "", CmdError{
|
return "", CmdError{
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
name: "dashboard"
|
name: "dashboard"
|
||||||
version: "0.2.4"
|
version: "0.2.6"
|
||||||
usage: "A simplified way of working with Helm"
|
usage: "A simplified way of working with Helm"
|
||||||
description: "View HELM situation in nice web UI"
|
description: "View HELM situation in nice web UI"
|
||||||
command: "$HELM_PLUGIN_DIR/bin/helm-dashboard"
|
command: "$HELM_PLUGIN_DIR/bin/helm-dashboard"
|
||||||
|
ignoreFlags: false
|
||||||
hooks:
|
hooks:
|
||||||
install: "cd $HELM_PLUGIN_DIR; scripts/install_plugin.sh"
|
install: "cd $HELM_PLUGIN_DIR; scripts/install_plugin.sh"
|
||||||
update: "cd $HELM_PLUGIN_DIR; scripts/install_plugin.sh"
|
update: "cd $HELM_PLUGIN_DIR; scripts/install_plugin.sh"
|
||||||
|
|||||||
Reference in New Issue
Block a user