mirror of
https://github.com/komodorio/helm-dashboard.git
synced 2026-03-24 11:48:04 +00:00
* 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>
413 lines
11 KiB
Go
413 lines
11 KiB
Go
package dashboard
|
|
|
|
import (
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/komodorio/helm-dashboard/pkg/dashboard/handlers"
|
|
"github.com/komodorio/helm-dashboard/pkg/dashboard/objects"
|
|
log "github.com/sirupsen/logrus"
|
|
"gotest.tools/v3/assert"
|
|
"helm.sh/helm/v3/pkg/action"
|
|
"helm.sh/helm/v3/pkg/chartutil"
|
|
"helm.sh/helm/v3/pkg/cli"
|
|
kubefake "helm.sh/helm/v3/pkg/kube/fake"
|
|
"helm.sh/helm/v3/pkg/registry"
|
|
"helm.sh/helm/v3/pkg/storage"
|
|
"helm.sh/helm/v3/pkg/storage/driver"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
var inMemStorage *storage.Storage
|
|
var repoFile string
|
|
|
|
func TestMain(m *testing.M) { // fixture to set logging level via env variable
|
|
if os.Getenv("DEBUG") != "" {
|
|
log.SetLevel(log.DebugLevel)
|
|
log.Debugf("Set logging level")
|
|
}
|
|
|
|
inMemStorage = storage.Init(driver.NewMemory())
|
|
d, err := ioutil.TempDir("", "helm")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
repoFile = filepath.Join(d, "repositories.yaml")
|
|
|
|
m.Run()
|
|
inMemStorage = nil
|
|
repoFile = ""
|
|
}
|
|
|
|
func GetTestGinContext(w *httptest.ResponseRecorder) *gin.Context {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
ctx, _ := gin.CreateTestContext(w)
|
|
ctx.Request = &http.Request{
|
|
Header: make(http.Header),
|
|
}
|
|
|
|
return ctx
|
|
}
|
|
|
|
func TestNoCacheMiddleware(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
con := GetTestGinContext(w)
|
|
noCache(con)
|
|
assert.Equal(t, w.Header().Get("Cache-Control"), "no-cache")
|
|
}
|
|
|
|
func TestEnableCacheControl(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
con := GetTestGinContext(w)
|
|
|
|
// Sets deafault policy to `no-cache`
|
|
noCache(con)
|
|
|
|
h := handlers.HelmHandler{
|
|
Contexted: &handlers.Contexted{
|
|
Data: &objects.DataLayer{},
|
|
},
|
|
}
|
|
h.EnableClientCache(con)
|
|
assert.Equal(t, w.Header().Get("Cache-Control"), "max-age=43200")
|
|
}
|
|
|
|
func TestConfigureStatic(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
|
|
req, err := http.NewRequest("GET", "/", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create an API Engine
|
|
api := gin.Default()
|
|
|
|
// Configure static routes
|
|
configureStatic(api)
|
|
|
|
// Start the server
|
|
api.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, w.Code, http.StatusOK)
|
|
}
|
|
|
|
func TestConfigureRoutes(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
|
|
req, err := http.NewRequest("GET", "/status", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create a API Engine
|
|
api := gin.Default()
|
|
|
|
// Required arguements for route configuration
|
|
abortWeb := func() {}
|
|
data, err := objects.NewDataLayer([]string{"TestSpace"}, "T-1", NewHelmConfig)
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Configure routes to API engine
|
|
configureRoutes(abortWeb, data, api)
|
|
|
|
// Start the server
|
|
api.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, w.Code, http.StatusOK)
|
|
}
|
|
|
|
func TestContextSetter(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
con := GetTestGinContext(w)
|
|
|
|
// Required arguements
|
|
data, err := objects.NewDataLayer([]string{"TestSpace"}, "T-1", NewHelmConfig)
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Set the context
|
|
ctxHandler := contextSetter(data)
|
|
ctxHandler(con)
|
|
|
|
appName, exists := con.Get("app")
|
|
|
|
if !exists {
|
|
t.Fatal("Value app doesn't exist in context")
|
|
}
|
|
|
|
tmp := handlers.Contexted{Data: data}
|
|
|
|
assert.Equal(t, appName, tmp.GetApp(con))
|
|
}
|
|
|
|
func TestNewRouter(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
req, err := http.NewRequest("GET", "/status", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Required arguemnets
|
|
abortWeb := func() {}
|
|
data, err := objects.NewDataLayer([]string{"TestSpace"}, "T-1", NewHelmConfig)
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create a new router with the function
|
|
newRouter := NewRouter(abortWeb, data, false)
|
|
|
|
newRouter.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, w.Code, http.StatusOK)
|
|
}
|
|
|
|
func TestConfigureScanners(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
req, err := http.NewRequest("GET", "/api/scanners", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Required arguemnets
|
|
data, err := objects.NewDataLayer([]string{"TestSpace"}, "T-1", NewHelmConfig)
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
apiEngine := gin.Default()
|
|
|
|
configureScanners(apiEngine.Group("/api/scanners"), data)
|
|
|
|
apiEngine.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, w.Code, http.StatusOK)
|
|
}
|
|
|
|
func TestConfigureKubectls(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
req, err := http.NewRequest("GET", "/api/kube/contexts", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Required arguemnets
|
|
data, err := objects.NewDataLayer([]string{"TestSpace"}, "T-1", NewHelmConfig)
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
apiEngine := gin.Default()
|
|
|
|
// Required middleware for kubectl api configuration
|
|
apiEngine.Use(contextSetter(data))
|
|
|
|
configureKubectls(apiEngine.Group("/api/kube"), data)
|
|
|
|
apiEngine.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, w.Code, http.StatusOK)
|
|
}
|
|
|
|
func TestE2E(t *testing.T) {
|
|
// Initialize data layer
|
|
data, err := objects.NewDataLayer([]string{""}, "0.0.0-test", getFakeHelmConfig)
|
|
assert.NilError(t, err)
|
|
|
|
// Create a new router with the function
|
|
abortWeb := func() {}
|
|
newRouter := NewRouter(abortWeb, data, false)
|
|
|
|
// initially, we don't have any releases
|
|
w := httptest.NewRecorder()
|
|
req, err := http.NewRequest("GET", "/api/helm/releases", nil)
|
|
assert.NilError(t, err)
|
|
newRouter.ServeHTTP(w, req)
|
|
assert.Equal(t, w.Code, http.StatusOK)
|
|
assert.Equal(t, w.Body.String(), "[]")
|
|
|
|
// initially, we don't have any repositories
|
|
w = httptest.NewRecorder()
|
|
req, err = http.NewRequest("GET", "/api/helm/repositories", nil)
|
|
assert.NilError(t, err)
|
|
newRouter.ServeHTTP(w, req)
|
|
assert.Equal(t, w.Code, http.StatusOK)
|
|
assert.Equal(t, w.Body.String(), "[]")
|
|
|
|
// then we add one repository
|
|
w = httptest.NewRecorder()
|
|
form := url.Values{}
|
|
form.Add("name", "komodorio")
|
|
form.Add("url", "https://helm-charts.komodor.io")
|
|
req, err = http.NewRequest("POST", "/api/helm/repositories", strings.NewReader(form.Encode()))
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
assert.NilError(t, err)
|
|
newRouter.ServeHTTP(w, req)
|
|
assert.Equal(t, w.Code, http.StatusNoContent)
|
|
assert.Equal(t, w.Body.String(), "")
|
|
|
|
// now, we have one repo
|
|
w = httptest.NewRecorder()
|
|
req, err = http.NewRequest("GET", "/api/helm/repositories", nil)
|
|
assert.NilError(t, err)
|
|
newRouter.ServeHTTP(w, req)
|
|
assert.Equal(t, w.Code, http.StatusOK)
|
|
assert.Equal(t, w.Body.String(), `[
|
|
{
|
|
"name": "komodorio",
|
|
"url": "https://helm-charts.komodor.io"
|
|
}
|
|
]`)
|
|
|
|
// what's the latest version of that chart
|
|
w = httptest.NewRecorder()
|
|
req, err = http.NewRequest("GET", "/api/helm/repositories/latestver?name=helm-dashboard", nil)
|
|
assert.NilError(t, err)
|
|
newRouter.ServeHTTP(w, req)
|
|
assert.Equal(t, w.Code, http.StatusOK)
|
|
|
|
// generate template for potential release
|
|
w = httptest.NewRecorder()
|
|
form = url.Values{}
|
|
form.Add("preview", "true")
|
|
form.Add("name", "release1")
|
|
form.Add("chart", "komodorio/helm-dashboard")
|
|
req, err = http.NewRequest("POST", "/api/helm/releases/test1", strings.NewReader(form.Encode()))
|
|
assert.NilError(t, err)
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
newRouter.ServeHTTP(w, req)
|
|
assert.Equal(t, w.Code, http.StatusOK)
|
|
|
|
// install the release
|
|
w = httptest.NewRecorder()
|
|
form = url.Values{}
|
|
form.Add("name", "release1")
|
|
form.Add("chart", "komodorio/helm-dashboard")
|
|
req, err = http.NewRequest("POST", "/api/helm/releases/test1", strings.NewReader(form.Encode()))
|
|
assert.NilError(t, err)
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
newRouter.ServeHTTP(w, req)
|
|
assert.Equal(t, w.Code, http.StatusAccepted)
|
|
|
|
// get list of releases
|
|
w = httptest.NewRecorder()
|
|
req, err = http.NewRequest("GET", "/api/helm/releases", nil)
|
|
assert.NilError(t, err)
|
|
newRouter.ServeHTTP(w, req)
|
|
assert.Equal(t, w.Code, http.StatusOK)
|
|
t.Logf("Release: %s", w.Body.String())
|
|
//assert.Equal(t, w.Body.String(), "[]")
|
|
|
|
// upgrade/reconfigure release
|
|
w = httptest.NewRecorder()
|
|
form = url.Values{}
|
|
form.Add("chart", "komodorio/helm-dashboard")
|
|
form.Add("values", "dashboard:\n allowWriteActions: true\n")
|
|
req, err = http.NewRequest("POST", "/api/helm/releases/test1/release1", strings.NewReader(form.Encode()))
|
|
assert.NilError(t, err)
|
|
newRouter.ServeHTTP(w, req)
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
assert.Equal(t, w.Code, http.StatusAccepted)
|
|
|
|
// get history of revisions for release
|
|
w = httptest.NewRecorder()
|
|
req, err = http.NewRequest("GET", "/api/helm/releases/test1/release1/history", nil)
|
|
assert.NilError(t, err)
|
|
newRouter.ServeHTTP(w, req)
|
|
assert.Equal(t, w.Code, http.StatusOK)
|
|
t.Logf("Revs: %s", w.Body.String())
|
|
//assert.Equal(t, w.Body.String(), "[]")
|
|
|
|
// get values for revision
|
|
w = httptest.NewRecorder()
|
|
req, err = http.NewRequest("GET", "/api/helm/releases/test1/release1/values?revision=2&userDefined=true", nil)
|
|
assert.NilError(t, err)
|
|
newRouter.ServeHTTP(w, req)
|
|
assert.Equal(t, w.Code, http.StatusOK)
|
|
//assert.Equal(t, w.Body.String(), "[]")
|
|
|
|
// rollback
|
|
w = httptest.NewRecorder()
|
|
form = url.Values{}
|
|
form.Add("revision", "1")
|
|
req, err = http.NewRequest("POST", "/api/helm/releases/test1/release1/rollback", strings.NewReader(form.Encode()))
|
|
assert.NilError(t, err)
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
newRouter.ServeHTTP(w, req)
|
|
assert.Equal(t, w.Code, http.StatusAccepted)
|
|
|
|
// get manifest diff for release
|
|
w = httptest.NewRecorder()
|
|
req, err = http.NewRequest("GET", "/api/helm/releases/test1/release1/manifests?revision=1&revisionDiff=2", nil)
|
|
assert.NilError(t, err)
|
|
newRouter.ServeHTTP(w, req)
|
|
assert.Equal(t, w.Code, http.StatusOK)
|
|
//assert.Equal(t, w.Body.String(), "[]")
|
|
|
|
// delete repo
|
|
w = httptest.NewRecorder()
|
|
req, err = http.NewRequest("DELETE", "/api/helm/repositories/komodorio", nil)
|
|
assert.NilError(t, err)
|
|
newRouter.ServeHTTP(w, req)
|
|
assert.Equal(t, w.Code, http.StatusNoContent)
|
|
|
|
// reconfigure release without repo connection
|
|
w = httptest.NewRecorder()
|
|
form = url.Values{}
|
|
form.Add("chart", "komodorio/helm-dashboard")
|
|
form.Add("values", "dashboard:\n allowWriteActions: false\n")
|
|
req, err = http.NewRequest("POST", "/api/helm/releases/test1/release1", strings.NewReader(form.Encode()))
|
|
assert.NilError(t, err)
|
|
newRouter.ServeHTTP(w, req)
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
assert.Equal(t, w.Code, http.StatusAccepted)
|
|
t.Logf("Upgraded: %s", w.Body.String())
|
|
|
|
// uninstall
|
|
w = httptest.NewRecorder()
|
|
req, err = http.NewRequest("DELETE", "/api/helm/releases/test1/release1", nil)
|
|
assert.NilError(t, err)
|
|
newRouter.ServeHTTP(w, req)
|
|
assert.Equal(t, w.Code, http.StatusAccepted)
|
|
|
|
// check we don't have releases again
|
|
w = httptest.NewRecorder()
|
|
req, err = http.NewRequest("GET", "/api/helm/releases", nil)
|
|
assert.NilError(t, err)
|
|
newRouter.ServeHTTP(w, req)
|
|
assert.Equal(t, w.Code, http.StatusOK)
|
|
assert.Equal(t, w.Body.String(), "[]")
|
|
}
|
|
|
|
func getFakeHelmConfig(settings *cli.EnvSettings, _ string) (*action.Configuration, error) {
|
|
settings.RepositoryConfig = repoFile
|
|
|
|
registryClient, err := registry.NewClient()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &action.Configuration{
|
|
Releases: inMemStorage,
|
|
KubeClient: &kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: os.Stderr}},
|
|
Capabilities: chartutil.DefaultCapabilities,
|
|
RegistryClient: registryClient,
|
|
Log: log.Infof,
|
|
}, nil
|
|
}
|