Files
helm-dashboard/pkg/dashboard/api.go
Andrey Pokhilko e13aa2fde6 [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>
2023-02-01 13:24:34 +00:00

206 lines
5.1 KiB
Go

package dashboard
import (
"context"
"embed"
"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"
"html"
"net/http"
"os"
"path"
)
//go:embed static/*
var staticFS embed.FS
func noCache(c *gin.Context) {
if c.GetHeader("Cache-Control") == "" { // default policy is not to cache
c.Header("Cache-Control", "no-cache")
}
c.Next()
}
func errorHandler(c *gin.Context) {
c.Next()
errs := ""
for _, err := range c.Errors {
log.Debugf("Error: %+v", err)
errs += err.Error() + "\n"
}
if errs != "" {
c.String(http.StatusInternalServerError, html.EscapeString(errs))
}
}
func contextSetter(data *objects.DataLayer) gin.HandlerFunc {
return func(c *gin.Context) {
ctxName := ""
if ctx, ok := c.Request.Header["X-Kubecontext"]; ok {
ctxName = ctx[0]
if err := data.SetContext(ctxName); err != nil {
c.String(http.StatusInternalServerError, err.Error())
return
}
}
app, err := data.AppForCtx(ctxName)
if err != nil {
c.String(http.StatusInternalServerError, err.Error())
return
}
c.Set(handlers.APP, app)
c.Next()
}
}
func NewRouter(abortWeb context.CancelFunc, data *objects.DataLayer, debug bool) *gin.Engine {
var api *gin.Engine
if debug {
api = gin.New()
api.Use(gin.Recovery())
} else {
api = gin.Default()
}
api.Use(contextSetter(data))
api.Use(noCache)
api.Use(errorHandler)
configureStatic(api)
configureRoutes(abortWeb, data, api)
return api
}
func configureRoutes(abortWeb context.CancelFunc, data *objects.DataLayer, api *gin.Engine) {
// server shutdown handler
api.DELETE("/", func(c *gin.Context) {
abortWeb()
c.Status(http.StatusAccepted)
})
api.GET("/status", func(c *gin.Context) {
c.Header("X-Application-Name", "Helm Dashboard by Komodor.io") // to identify ourselves by ourselves
c.IndentedJSON(http.StatusOK, data.GetStatus())
})
api.GET("/api/cache", func(c *gin.Context) { // TODO: included into OpenAPI or not?
c.IndentedJSON(http.StatusOK, data.Cache)
})
api.DELETE("/api/cache", func(c *gin.Context) { // TODO: included into OpenAPI or not?
err := data.Cache.Clear()
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return
}
c.Status(http.StatusAccepted)
})
api.POST("/diff", func(c *gin.Context) { // TODO: included into OpenAPI or not?
a := c.PostForm("a")
b := c.PostForm("b")
out := handlers.GetDiff(a, b, "current.yaml", "upgraded.yaml")
c.Header("Content-Type", "text/plain")
c.String(http.StatusOK, out)
})
api.GET("/api-docs", func(c *gin.Context) { // https://github.com/OAI/OpenAPI-Specification/search?q=api-docs
c.Redirect(http.StatusFound, "static/api-docs.html")
})
configureHelms(api.Group("/api/helm"), data)
configureKubectls(api.Group("/api/k8s"), data)
configureScanners(api.Group("/api/scanners"), data)
}
func configureHelms(api *gin.RouterGroup, data *objects.DataLayer) {
h := handlers.HelmHandler{
Contexted: &handlers.Contexted{
Data: data,
},
}
rels := api.Group("/releases")
rels.GET("", h.GetReleases)
rels.POST(":ns", h.Install)
rels.POST(":ns/:name", h.Upgrade)
rels.DELETE(":ns/:name", h.Uninstall)
rels.GET(":ns/:name/history", h.History)
rels.GET(":ns/:name/:section", h.GetInfoSection)
rels.GET(":ns/:name/resources", h.Resources)
rels.POST(":ns/:name/rollback", h.Rollback)
rels.POST(":ns/:name/test", h.RunTests)
repos := api.Group("/repositories")
repos.GET("", h.RepoList)
repos.POST("", h.RepoAdd)
repos.GET("/:name", h.RepoCharts)
repos.POST("/:name", h.RepoUpdate)
repos.DELETE("/:name", h.RepoDelete)
repos.GET("/latestver", h.RepoLatestVer) // TODO: use /versions in client insted and remove this?
repos.GET("/versions", h.RepoVersions)
repos.GET("/values", h.RepoValues)
}
func configureKubectls(api *gin.RouterGroup, data *objects.DataLayer) {
h := handlers.KubeHandler{
Contexted: &handlers.Contexted{
Data: data,
},
}
api.GET("/contexts", h.GetContexts)
api.GET("/:kind/get", h.GetResourceInfo)
api.GET("/:kind/describe", h.Describe)
api.GET("/:kind/list", h.GetNameSpaces)
}
func configureStatic(api *gin.Engine) {
fs := http.FS(staticFS)
// local dev speed-up
localDevPath := "pkg/dashboard/static"
if _, err := os.Stat(localDevPath); err == nil {
log.Warnf("Using local development path to serve static files")
// the root page
api.GET("/", func(c *gin.Context) {
c.File(path.Join(localDevPath, "index.html"))
})
// serve a directory called static
api.GET("/static/*filepath", func(c *gin.Context) {
c.File(path.Join(localDevPath, c.Param("filepath")))
})
} else {
// the root page
api.GET("/", func(c *gin.Context) {
c.FileFromFS("/static/", fs)
})
// serve a directory called static
api.GET("/static/*filepath", func(c *gin.Context) {
c.FileFromFS(c.Request.URL.Path, fs)
})
}
}
func configureScanners(api *gin.RouterGroup, data *objects.DataLayer) {
h := handlers.ScannersHandler{
Contexted: &handlers.Contexted{
Data: data,
},
}
api.GET("", h.List)
api.POST("/manifests", h.ScanManifest)
api.GET("/resource/:kind", h.ScanResource)
}