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 ? (
+
+ ) : (
+
+ )}