Skip to content

Commit

Permalink
helm release handler
Browse files Browse the repository at this point in the history
  • Loading branch information
petar-cvit committed Oct 24, 2024
1 parent 5c6e975 commit 14d3019
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 1 deletion.
3 changes: 2 additions & 1 deletion cyclops-ctrl/cmd/main/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

"github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/auth"
"github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/handler"
"github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/integrations/helm"
"github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/modulecontroller"
"github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/prometheus"
"github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/telemetry"
Expand Down Expand Up @@ -90,7 +91,7 @@ func main() {

prometheus.StartCacheMetricsUpdater(&monitor, templatesRepo.ReturnCache(), 10*time.Second, setupLog)

handler, err := handler.New(templatesRepo, k8sClient, renderer, telemetryClient, monitor)
handler, err := handler.New(templatesRepo, k8sClient, helm.NewReleaseClient(), renderer, telemetryClient, monitor)
if err != nil {
panic(err)
}
Expand Down
180 changes: 180 additions & 0 deletions cyclops-ctrl/internal/controller/helm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package controller

import (
"fmt"
"github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/integrations/helm"
"github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/mapper"
"github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/models/dto"
helm2 "github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/models/helm"
json "github.com/json-iterator/go"
"helm.sh/helm/v3/pkg/chartutil"
"net/http"
"sort"

"github.com/cyclops-ui/cyclops/cyclops-ctrl/pkg/cluster/k8sclient"
"github.com/gin-gonic/gin"
)

type Helm struct {
kubernetesClient k8sclient.IKubernetesClient
releaseClient *helm.ReleaseClient
}

func NewHelmController(kubernetes k8sclient.IKubernetesClient, releaseClient *helm.ReleaseClient) *Helm {
return &Helm{
kubernetesClient: kubernetes,
releaseClient: releaseClient,
}
}

func (h *Helm) ListReleases(ctx *gin.Context) {
ctx.Header("Access-Control-Allow-Origin", "*")

releases, err := h.releaseClient.ListReleases()
if err != nil {
fmt.Println(err)
ctx.JSON(http.StatusBadRequest, dto.NewError("Error fetching existing Helm releases", err.Error()))
return
}

mappedReleases, err := mapper.MapHelmReleases(releases)

sort.Slice(mappedReleases, func(i, j int) bool {
return mappedReleases[i].Name < mappedReleases[j].Name
})

ctx.JSON(http.StatusOK, mappedReleases)
}

func (h *Helm) GetRelease(ctx *gin.Context) {
ctx.Header("Access-Control-Allow-Origin", "*")

name := ctx.Param("name")
namespace := ctx.Param("namespace")

release, err := h.releaseClient.GetRelease(namespace, name)
if err != nil {
ctx.JSON(http.StatusBadRequest, dto.NewError("Error fetching Helm release", err.Error()))
return
}

mappedRelease, err := mapper.MapHelmRelease(release)

ctx.JSON(http.StatusOK, mappedRelease)
}

func (h *Helm) UpgradeRelease(ctx *gin.Context) {
ctx.Header("Access-Control-Allow-Origin", "*")

name := ctx.Param("name")
namespace := ctx.Param("namespace")

var values map[string]interface{}
if err := ctx.BindJSON(&values); err != nil {
ctx.JSON(http.StatusBadRequest, dto.NewError("Error binding values", err.Error()))
return
}

release, err := h.releaseClient.GetRelease(namespace, name)
if err != nil {
ctx.JSON(http.StatusBadRequest, dto.NewError("Error fetching existing release", err.Error()))
return
}

if err := h.releaseClient.UpgradeRelease(namespace, name, values, release); err != nil {
ctx.JSON(http.StatusBadRequest, dto.NewError("Error upgrading release", err.Error()))
return
}

ctx.Status(http.StatusOK)
}

func (h *Helm) UninstallRelease(ctx *gin.Context) {
ctx.Header("Access-Control-Allow-Origin", "*")

name := ctx.Param("name")
namespace := ctx.Param("namespace")

if err := h.releaseClient.UninstallRelease(namespace, name); err != nil {
fmt.Println(err)
ctx.JSON(http.StatusBadRequest, dto.NewError("Error uninstalling Helm release", err.Error()))
return
}

ctx.Status(http.StatusOK)
}

func (h *Helm) GetReleaseResources(ctx *gin.Context) {
ctx.Header("Access-Control-Allow-Origin", "*")

name := ctx.Param("name")

resources, err := h.kubernetesClient.GetResourcesForRelease(name)
if err != nil {
fmt.Println(err)
ctx.JSON(http.StatusBadRequest, dto.NewError("Error fetching Helm release resources", err.Error()))
return
}

ctx.JSON(http.StatusOK, resources)
}

func (h *Helm) GetReleaseSchema(ctx *gin.Context) {
ctx.Header("Access-Control-Allow-Origin", "*")

name := ctx.Param("name")
namespace := ctx.Param("namespace")

release, err := h.releaseClient.GetRelease(namespace, name)
if err != nil {
ctx.JSON(http.StatusInternalServerError, dto.NewError("Error fetching Helm release", err.Error()))
return
}

if release.Chart == nil {
ctx.JSON(http.StatusBadRequest, dto.NewError("Helm release does not contain chart", err.Error()))
return
}

var root *helm2.Property
err = json.Unmarshal(release.Chart.Schema, &root)
if err != nil {
ctx.JSON(http.StatusBadRequest, dto.NewError("failed unmarshaling schema", err.Error()))
return
}

if root == nil {
ctx.JSON(http.StatusBadRequest, dto.NewError("no schema found", err.Error()))
return
}

rootField := mapper.HelmSchemaToFields("", *root, root.Definitions, nil)

ctx.JSON(http.StatusOK, rootField)
}

func (h *Helm) GetReleaseValues(ctx *gin.Context) {
ctx.Header("Access-Control-Allow-Origin", "*")

name := ctx.Param("name")
namespace := ctx.Param("namespace")

release, err := h.releaseClient.GetRelease(namespace, name)
if err != nil {
ctx.JSON(http.StatusInternalServerError, dto.NewError("Error fetching Helm release", err.Error()))
return
}

if release.Chart == nil {
ctx.JSON(http.StatusBadRequest, dto.NewError("Helm release does not contain chart", err.Error()))
return
}

values, err := chartutil.CoalesceValues(release.Chart, release.Config)
if err != nil {
ctx.JSON(http.StatusBadRequest, dto.NewError("failed to merge values", err.Error()))
return
}

ctx.JSON(http.StatusOK, values)
}
15 changes: 15 additions & 0 deletions cyclops-ctrl/internal/controller/sse/resources.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sse

import (
"github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/models/dto"
"io"
"net/http"
"time"
Expand All @@ -20,6 +21,20 @@ func (s *Server) Resources(ctx *gin.Context) {
return
}

s.streamResources(ctx, resources)
}

func (s *Server) ReleaseResources(ctx *gin.Context) {
resources, err := s.k8sClient.GetWorkloadsForRelease(ctx.Param("name"))
if err != nil {
ctx.String(http.StatusInternalServerError, err.Error())
return
}

s.streamResources(ctx, resources)
}

func (s *Server) streamResources(ctx *gin.Context, resources []dto.Resource) {
watchSpecs := make([]k8sclient.ResourceWatchSpec, 0, len(resources))
for _, resource := range resources {
if !k8sclient.IsWorkload(resource.GetGroup(), resource.GetVersion(), resource.GetKind()) {
Expand Down
16 changes: 16 additions & 0 deletions cyclops-ctrl/internal/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package handler

import (
"github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/controller/sse"
"github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/integrations/helm"
"github.com/gin-gonic/gin"
"net/http"

Expand All @@ -18,6 +19,7 @@ type Handler struct {

templatesRepo templaterepo.ITemplateRepo
k8sClient k8sclient.IKubernetesClient
releaseClient *helm.ReleaseClient
renderer *render.Renderer

telemetryClient telemetry.Client
Expand All @@ -27,6 +29,7 @@ type Handler struct {
func New(
templatesRepo templaterepo.ITemplateRepo,
kubernetesClient k8sclient.IKubernetesClient,
releaseClient *helm.ReleaseClient,
renderer *render.Renderer,
telemetryClient telemetry.Client,
monitor prometheus.Monitor,
Expand All @@ -35,6 +38,7 @@ func New(
templatesRepo: templatesRepo,
k8sClient: kubernetesClient,
renderer: renderer,
releaseClient: releaseClient,
telemetryClient: telemetryClient,
monitor: monitor,
router: gin.New(),
Expand All @@ -47,12 +51,14 @@ func (h *Handler) Start() error {
templatesController := controller.NewTemplatesController(h.templatesRepo, h.k8sClient, h.telemetryClient)
modulesController := controller.NewModulesController(h.templatesRepo, h.k8sClient, h.renderer, h.telemetryClient, h.monitor)
clusterController := controller.NewClusterController(h.k8sClient)
helmController := controller.NewHelmController(h.k8sClient, h.releaseClient)

h.router = gin.New()

server := sse.NewServer(h.k8sClient)

h.router.GET("/stream/resources/:name", sse.HeadersMiddleware(), server.Resources)
h.router.GET("/stream/releases/resources/:name", sse.HeadersMiddleware(), server.ReleaseResources)
h.router.POST("/stream/resources", sse.HeadersMiddleware(), server.SingleResource)

h.router.GET("/ping", h.pong())
Expand Down Expand Up @@ -98,6 +104,16 @@ func (h *Handler) Start() error {

h.router.GET("/namespaces", clusterController.ListNamespaces)

// region helm migrator
h.router.GET("/helm/releases", helmController.ListReleases)
h.router.GET("/helm/releases/:namespace/:name", helmController.GetRelease)
h.router.POST("/helm/releases/:namespace/:name", helmController.UpgradeRelease)
h.router.DELETE("/helm/releases/:namespace/:name", helmController.UninstallRelease)
h.router.GET("/helm/releases/:namespace/:name/resources", helmController.GetReleaseResources)
h.router.GET("/helm/releases/:namespace/:name/fields", helmController.GetReleaseSchema)
h.router.GET("/helm/releases/:namespace/:name/values", helmController.GetReleaseValues)
// endregion

h.router.Use(h.options)

return h.router.Run()
Expand Down

0 comments on commit 14d3019

Please sign in to comment.