diff --git a/cyclops-ctrl/api/v1alpha1/module_types.go b/cyclops-ctrl/api/v1alpha1/module_types.go index 3466c341..aedf4831 100644 --- a/cyclops-ctrl/api/v1alpha1/module_types.go +++ b/cyclops-ctrl/api/v1alpha1/module_types.go @@ -111,9 +111,12 @@ type HistoryTemplateRef struct { } type HistoryEntry struct { - Generation int64 `json:"generation"` - TemplateRef HistoryTemplateRef `json:"template"` - Values apiextensionsv1.JSON `json:"values"` + Generation int64 `json:"generation"` + + // +kubebuilder:validation:Optional + TargetNamespace string `json:"targetNamespace"` + TemplateRef HistoryTemplateRef `json:"template"` + Values apiextensionsv1.JSON `json:"values"` } //+kubebuilder:object:root=true diff --git a/cyclops-ctrl/config/crd/bases/cyclops-ui.com_modules.yaml b/cyclops-ctrl/config/crd/bases/cyclops-ui.com_modules.yaml index c601273f..f4c3e798 100644 --- a/cyclops-ctrl/config/crd/bases/cyclops-ui.com_modules.yaml +++ b/cyclops-ctrl/config/crd/bases/cyclops-ui.com_modules.yaml @@ -32,6 +32,8 @@ spec: generation: format: int64 type: integer + targetNamespace: + type: string template: properties: path: diff --git a/cyclops-ctrl/internal/controller/modules.go b/cyclops-ctrl/internal/controller/modules.go index b2ac07c3..89d5864f 100644 --- a/cyclops-ctrl/internal/controller/modules.go +++ b/cyclops-ctrl/internal/controller/modules.go @@ -409,7 +409,8 @@ func (m *Modules) UpdateModule(ctx *gin.Context) { } module.History = append([]v1alpha1.HistoryEntry{{ - Generation: curr.Generation, + Generation: curr.Generation, + TargetNamespace: curr.Spec.TargetNamespace, TemplateRef: v1alpha1.HistoryTemplateRef{ URL: curr.Spec.TemplateRef.URL, Path: curr.Spec.TemplateRef.Path, @@ -443,6 +444,160 @@ func (m *Modules) UpdateModule(ctx *gin.Context) { ctx.Status(http.StatusOK) } +func (m *Modules) HistoryEntryManifest(ctx *gin.Context) { + ctx.Header("Access-Control-Allow-Origin", "*") + + var request dto.RollbackRequest + if err := ctx.BindJSON(&request); err != nil { + fmt.Println(err) + ctx.JSON(http.StatusBadRequest, dto.NewError("Error mapping module request", err.Error())) + return + } + + curr, err := m.kubernetesClient.GetModule(request.ModuleName) + if err != nil { + fmt.Println(err) + ctx.JSON(http.StatusInternalServerError, dto.NewError("Error fetching module", err.Error())) + return + } + + var targetGeneration *v1alpha1.HistoryEntry + for _, entry := range curr.History { + if entry.Generation == request.Generation { + targetGeneration = &entry + break + } + } + + if targetGeneration == nil { + ctx.JSON(http.StatusInternalServerError, dto.NewError("Invalid rollback generation provided", fmt.Sprintf("Generation %d does not exist", request.Generation))) + return + } + + targetTemplate, err := m.templatesRepo.GetTemplate( + targetGeneration.TemplateRef.URL, + targetGeneration.TemplateRef.Path, + targetGeneration.TemplateRef.Version, + "", + targetGeneration.TemplateRef.SourceType, + ) + if err != nil { + fmt.Println(err) + ctx.Status(http.StatusInternalServerError) + return + } + + manifest, err := m.renderer.HelmTemplate(v1alpha1.Module{ + ObjectMeta: metav1.ObjectMeta{ + Name: request.ModuleName, + }, + Spec: v1alpha1.ModuleSpec{ + TargetNamespace: targetGeneration.TargetNamespace, + TemplateRef: v1alpha1.TemplateRef{ + URL: targetGeneration.TemplateRef.URL, + Path: targetGeneration.TemplateRef.Path, + Version: targetGeneration.TemplateRef.Version, + SourceType: targetGeneration.TemplateRef.SourceType, + }, + Values: targetGeneration.Values, + }, + }, targetTemplate) + if err != nil { + fmt.Println(err) + ctx.Status(http.StatusInternalServerError) + return + } + + manifest = strings.TrimPrefix(manifest, "\n---") + manifest = strings.TrimSuffix(manifest, "---\n") + + ctx.String(http.StatusOK, manifest) +} + +func (m *Modules) RollbackModule(ctx *gin.Context) { + ctx.Header("Access-Control-Allow-Origin", "*") + + var request dto.RollbackRequest + if err := ctx.BindJSON(&request); err != nil { + fmt.Println(err) + ctx.JSON(http.StatusBadRequest, dto.NewError("Error mapping module request", err.Error())) + return + } + + curr, err := m.kubernetesClient.GetModule(request.ModuleName) + if err != nil { + fmt.Println(err) + ctx.JSON(http.StatusInternalServerError, dto.NewError("Error fetching module", err.Error())) + return + } + + var targetGeneration *v1alpha1.HistoryEntry + for _, entry := range curr.History { + if entry.Generation == request.Generation { + targetGeneration = &entry + break + } + } + + if targetGeneration == nil { + ctx.JSON(http.StatusInternalServerError, dto.NewError("Invalid rollback generation provided", fmt.Sprintf("Generation %d does not exist", request.Generation))) + return + } + + module := curr.DeepCopy() + + module.Kind = "Module" + module.APIVersion = "cyclops-ui.com/v1alpha1" + + history := module.History + if module.History == nil { + history = make([]v1alpha1.HistoryEntry, 0) + } + + module.History = append([]v1alpha1.HistoryEntry{{ + Generation: curr.Generation, + TemplateRef: v1alpha1.HistoryTemplateRef{ + URL: curr.Spec.TemplateRef.URL, + Path: curr.Spec.TemplateRef.Path, + Version: curr.Status.TemplateResolvedVersion, + SourceType: curr.Spec.TemplateRef.SourceType, + }, + Values: curr.Spec.Values, + }}, history...) + + if len(module.History) > 10 { + module.History = module.History[:len(module.History)-1] + } + + module.Spec.Values = targetGeneration.Values + module.Spec.TemplateRef = v1alpha1.TemplateRef{ + URL: targetGeneration.TemplateRef.URL, + Path: targetGeneration.TemplateRef.Path, + Version: targetGeneration.TemplateRef.Version, + SourceType: targetGeneration.TemplateRef.SourceType, + } + module.Spec.TargetNamespace = targetGeneration.TargetNamespace + + module.SetResourceVersion(curr.GetResourceVersion()) + + result, err := m.kubernetesClient.UpdateModuleStatus(module) + if err != nil { + fmt.Println(err) + ctx.JSON(http.StatusInternalServerError, dto.NewError("Error updating module status", err.Error())) + return + } + + module.ResourceVersion = result.ResourceVersion + err = m.kubernetesClient.UpdateModule(module) + if err != nil { + fmt.Println(err) + ctx.JSON(http.StatusInternalServerError, dto.NewError("Error updating module", err.Error())) + return + } + + ctx.Status(http.StatusOK) +} + func (m *Modules) ReconcileModule(ctx *gin.Context) { ctx.Header("Access-Control-Allow-Origin", "*") diff --git a/cyclops-ctrl/internal/handler/handler.go b/cyclops-ctrl/internal/handler/handler.go index 7fc28b1b..c7e8c25f 100644 --- a/cyclops-ctrl/internal/handler/handler.go +++ b/cyclops-ctrl/internal/handler/handler.go @@ -86,6 +86,8 @@ func (h *Handler) Start() error { h.router.DELETE("/modules/:name", modulesController.DeleteModule) h.router.POST("/modules/new", modulesController.CreateModule) h.router.POST("/modules/update", modulesController.UpdateModule) + h.router.POST("/modules/rollback/manifest", modulesController.HistoryEntryManifest) + h.router.POST("/modules/rollback", modulesController.RollbackModule) h.router.GET("/modules/:name/raw", modulesController.GetRawModuleManifest) h.router.POST("/modules/:name/reconcile", modulesController.ReconcileModule) h.router.GET("/modules/:name/history", modulesController.GetModuleHistory) diff --git a/cyclops-ctrl/internal/models/dto/modules.go b/cyclops-ctrl/internal/models/dto/modules.go index 9eaf8ff2..10ef9f45 100644 --- a/cyclops-ctrl/internal/models/dto/modules.go +++ b/cyclops-ctrl/internal/models/dto/modules.go @@ -47,6 +47,11 @@ type TemplatesResponse struct { New string `json:"new"` } +type RollbackRequest struct { + ModuleName string `json:"moduleName"` + Generation int64 `json:"generation"` +} + type DeleteResource struct { Group string `json:"group"` Version string `json:"version"` diff --git a/cyclops-ui/src/components/pages/History.tsx b/cyclops-ui/src/components/pages/History.tsx index 228b0d22..ab98b54e 100644 --- a/cyclops-ui/src/components/pages/History.tsx +++ b/cyclops-ui/src/components/pages/History.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from "react"; -import { Button, Col, Modal, Row, Table, Typography } from "antd"; +import { Button, Col, Modal, Row, Spin, Table, Typography } from "antd"; import axios from "axios"; import { useParams } from "react-router-dom"; import ReactDiffViewer from "react-diff-viewer"; @@ -22,6 +22,7 @@ const ModuleHistory = () => { description: "", }); + const [loadingDiff, setLoadingDiff] = useState(false); const [diff, setDiff] = useState({ curr: "", previous: "", @@ -69,18 +70,10 @@ const ModuleHistory = () => { generation: 0, }); - let target: any = {}; - historyEntries.forEach((h: any) => { - if (h.generation === diffModal.generation) { - target = h; - } - }); - axios - .post(`/api/modules/update`, { - values: target.values, - name: moduleName, - template: target.template, + .post(`/api/modules/rollback`, { + moduleName: moduleName, + generation: diffModal.generation, }) .then((res) => { window.location.href = "/modules/" + moduleName; @@ -112,10 +105,11 @@ const ModuleHistory = () => { } }); + setLoadingDiff(true); axios - .post("/api/modules/" + moduleName + "/manifest", { - template: target.template, - values: target.values, + .post("/api/modules/rollback/manifest", { + moduleName: moduleName, + generation: target.generation, }) .then(function (res) { setDiff({ @@ -125,6 +119,9 @@ const ModuleHistory = () => { }) .catch(function (error) { setError(mapResponseError(error)); + }) + .finally(() => { + setLoadingDiff(false); }); setDiffModal({ @@ -142,9 +139,9 @@ const ModuleHistory = () => { }); axios - .post("/api/modules/" + moduleName + "/manifest", { - template: target.template, - values: target.values, + .post("/api/modules/rollback/manifest", { + moduleName: moduleName, + generation: target.generation, }) .then(function (res) { setManifest(res.data); @@ -255,14 +252,18 @@ const ModuleHistory = () => { onCancel={handleCancelDiff} width={"60%"} > - + {loadingDiff ? ( + + ) : ( + + )}