From 4ebb1eeb7a21fcbce52f14c952f7ad0c4ebe83ae Mon Sep 17 00:00:00 2001 From: KaradzaJuraj Date: Fri, 13 Dec 2024 21:41:09 +0100 Subject: [PATCH 01/30] gitops-dropdown --- .../components/pages/NewModule/NewModule.tsx | 112 ++++++++++++++++-- 1 file changed, 100 insertions(+), 12 deletions(-) diff --git a/cyclops-ui/src/components/pages/NewModule/NewModule.tsx b/cyclops-ui/src/components/pages/NewModule/NewModule.tsx index cbf04628..5252b132 100644 --- a/cyclops-ui/src/components/pages/NewModule/NewModule.tsx +++ b/cyclops-ui/src/components/pages/NewModule/NewModule.tsx @@ -12,9 +12,15 @@ import { Modal, Spin, notification, + Switch, } from "antd"; import axios from "axios"; -import { deepMerge, findMaps, flattenObjectKeys, mapsToArray } from "../../../utils/form"; +import { + deepMerge, + findMaps, + flattenObjectKeys, + mapsToArray, +} from "../../../utils/form"; import "./custom.css"; import defaultTemplate from "../../../static/img/default-template-icon.png"; @@ -85,6 +91,8 @@ const NewModule = () => { const [namespaces, setNamespaces] = useState([]); + const [gitopsToggle, SetGitopsToggle] = useState(false); + const [notificationApi, contextHolder] = notification.useNotification(); const openNotification = (errors: FeedbackError[]) => { notificationApi.error({ @@ -111,6 +119,11 @@ const NewModule = () => { const handleSubmit = (values: any) => { const moduleName = values["cyclops_module_name"]; const moduleNamespace = values["cyclops_module_namespace"]; + const gitopsConfig = { + repo: values["gitops-repo"], + path: values["gitops-path"], + branch: values["gitops-branch"], + }; values = findMaps(config.root.properties, values, initialValuesRaw); @@ -118,6 +131,7 @@ const NewModule = () => { .post(`/api/modules/new`, { name: moduleName, namespace: moduleNamespace, + gitops: gitopsConfig, values: values, template: { repo: template.repo, @@ -310,8 +324,8 @@ const NewModule = () => { const handleImportValues = () => { let yamlValues = null; try { - yamlValues = YAML.parse(loadedValues) - } catch(err: any) { + yamlValues = YAML.parse(loadedValues); + } catch (err: any) { if (err instanceof YAMLError) { setError({ message: err.name, @@ -327,15 +341,17 @@ const NewModule = () => { return; } - const currentValues = findMaps(config.root.properties, form.getFieldsValue(), null); - const values = deepMerge(currentValues, yamlValues) - - form.setFieldsValue( - mapsToArray(config.root.properties, values), + const currentValues = findMaps( + config.root.properties, + form.getFieldsValue(), + null, ); + const values = deepMerge(currentValues, yamlValues); + + form.setFieldsValue(mapsToArray(config.root.properties, values)); setLoadedValues(""); setLoadingValuesModal(false); - setError({message: "", description: ""}); + setError({ message: "", description: "" }); }; const onFinishFailed = (errors: any) => { @@ -527,6 +543,74 @@ const NewModule = () => { +
+ + + GitOps Workflow +

+ Will you be using a GitOps workflow to deploy this + module? +

+
+ } + style={{ padding: "0px 12px 0px 12px" }} + > + { + SetGitopsToggle(!gitopsToggle); + }} + /> + +
+ + + + + + + + + + + +
+
{advancedOptionsExpanded ? (
@@ -612,9 +696,13 @@ const NewModule = () => { style={{ marginBottom: "20px" }} /> )} -
- You can paste your values in YAML format here, and after submitting them, you can see them in the form and edit them further. - If you set a value in YAML that does not exist in the UI, it will not be applied to your Module. +
+ You can paste your values in YAML format here, and after submitting + them, you can see them in the form and edit them further. If you set a + value in YAML that does not exist in the UI, it will not be applied to + your Module.
Date: Mon, 16 Dec 2024 11:09:08 +0100 Subject: [PATCH 02/30] v0.1.5 --- cyclops-ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cyclops-ui/package.json b/cyclops-ui/package.json index 6ff8a515..06ed109d 100644 --- a/cyclops-ui/package.json +++ b/cyclops-ui/package.json @@ -1,6 +1,6 @@ { "name": "@cyclopsui/cyclops-ui", - "version": "0.1.0", + "version": "0.1.5", "private": false, "license": "Apache-2.0", "dependencies": { From 56e0ae4ce484d007d6aea3472541accbfbfc59c4 Mon Sep 17 00:00:00 2001 From: petar-cvit Date: Mon, 16 Dec 2024 11:13:48 +0100 Subject: [PATCH 03/30] v0.1.6 --- cyclops-ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cyclops-ui/package.json b/cyclops-ui/package.json index 06ed109d..b81445ea 100644 --- a/cyclops-ui/package.json +++ b/cyclops-ui/package.json @@ -1,6 +1,6 @@ { "name": "@cyclopsui/cyclops-ui", - "version": "0.1.5", + "version": "0.1.6", "private": false, "license": "Apache-2.0", "dependencies": { From 51d3508b552e3e6dd8c86bb294c5031f587bbb47 Mon Sep 17 00:00:00 2001 From: petar-cvit Date: Mon, 16 Dec 2024 12:00:15 +0100 Subject: [PATCH 04/30] v0.1.7 --- cyclops-ui/package.json | 2 +- .../src/components/pages/ModuleDetails/custom.css | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) delete mode 100644 cyclops-ui/src/components/pages/ModuleDetails/custom.css diff --git a/cyclops-ui/package.json b/cyclops-ui/package.json index b81445ea..515dd384 100644 --- a/cyclops-ui/package.json +++ b/cyclops-ui/package.json @@ -1,6 +1,6 @@ { "name": "@cyclopsui/cyclops-ui", - "version": "0.1.6", + "version": "0.1.7", "private": false, "license": "Apache-2.0", "dependencies": { diff --git a/cyclops-ui/src/components/pages/ModuleDetails/custom.css b/cyclops-ui/src/components/pages/ModuleDetails/custom.css deleted file mode 100644 index 20632cf6..00000000 --- a/cyclops-ui/src/components/pages/ModuleDetails/custom.css +++ /dev/null @@ -1,12 +0,0 @@ -.ant-col.ant-form-item-label { - padding: 0 !important; -} - -label.ant-form-item-required:before { - bottom: 1em !important; - position: relative !important; -} - -code { - color: #000080; -} From c00ab83858a45a771089717476d98e23138b3825 Mon Sep 17 00:00:00 2001 From: petar-cvit Date: Mon, 16 Dec 2024 17:00:50 +0100 Subject: [PATCH 05/30] git wirte backend --- cyclops-ctrl/api/v1alpha1/module_types.go | 6 +- .../api/v1alpha1/zz_generated.deepcopy.go | 6 +- cyclops-ctrl/cmd/main/main.go | 8 +- cyclops-ctrl/internal/controller/modules.go | 48 ++++-- cyclops-ctrl/internal/git/writeclient.go | 141 ++++++++++++++++++ cyclops-ctrl/internal/handler/handler.go | 14 +- cyclops-ctrl/internal/mapper/modules.go | 24 ++- cyclops-ctrl/internal/models/dto/modules.go | 7 + .../modulecontroller/module_controller.go | 6 +- 9 files changed, 239 insertions(+), 21 deletions(-) create mode 100644 cyclops-ctrl/internal/git/writeclient.go diff --git a/cyclops-ctrl/api/v1alpha1/module_types.go b/cyclops-ctrl/api/v1alpha1/module_types.go index 3789d0a3..1b74ff4f 100644 --- a/cyclops-ctrl/api/v1alpha1/module_types.go +++ b/cyclops-ctrl/api/v1alpha1/module_types.go @@ -43,6 +43,10 @@ const ( TemplateSourceTypeGit TemplateSourceType = "git" TemplateSourceTypeHelm TemplateSourceType = "helm" TemplateSourceTypeOCI TemplateSourceType = "oci" + + GitOpsWriteRepoAnnotation = "cyclops-ui.com/write-repo" + GitOpsWritePathAnnotation = "cyclops-ui.com/write-path" + GitOpsWriteRevisionAnnotation = "cyclops-ui.com/write-revision" ) type TemplateRef struct { @@ -121,7 +125,7 @@ type Module struct { metav1.ObjectMeta `json:"metadata,omitempty"` Spec ModuleSpec `json:"spec,omitempty"` - Status ModuleStatus `json:"status,omitempty"` + Status *ModuleStatus `json:"status,omitempty"` History []HistoryEntry `json:"history,omitempty"` } diff --git a/cyclops-ctrl/api/v1alpha1/zz_generated.deepcopy.go b/cyclops-ctrl/api/v1alpha1/zz_generated.deepcopy.go index 0bce7172..7bceb55b 100644 --- a/cyclops-ctrl/api/v1alpha1/zz_generated.deepcopy.go +++ b/cyclops-ctrl/api/v1alpha1/zz_generated.deepcopy.go @@ -77,7 +77,11 @@ func (in *Module) DeepCopyInto(out *Module) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) + if in.Status != nil { + in, out := &in.Status, &out.Status + *out = new(ModuleStatus) + (*in).DeepCopyInto(*out) + } if in.History != nil { in, out := &in.History, &out.History *out = make([]HistoryEntry, len(*in)) diff --git a/cyclops-ctrl/cmd/main/main.go b/cyclops-ctrl/cmd/main/main.go index 96bf0115..038c98b3 100644 --- a/cyclops-ctrl/cmd/main/main.go +++ b/cyclops-ctrl/cmd/main/main.go @@ -3,6 +3,7 @@ package main import ( "flag" "fmt" + "github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/git" "os" "strconv" "time" @@ -86,8 +87,10 @@ func main() { panic(err) } + credsResolver := auth.NewTemplatesResolver(k8sClient) + templatesRepo := template.NewRepo( - auth.NewTemplatesResolver(k8sClient), + credsResolver, cache.NewInMemoryTemplatesCache(), ) @@ -101,8 +104,9 @@ func main() { prometheus.StartCacheMetricsUpdater(&monitor, templatesRepo.ReturnCache(), 10*time.Second, setupLog) helmReleaseClient := helm.NewReleaseClient(helmWatchNamespace) + gitWriteClient := git.NewWriteClient(credsResolver) - handler, err := handler.New(templatesRepo, k8sClient, helmReleaseClient, renderer, moduleTargetNamespace, telemetryClient, monitor) + handler, err := handler.New(templatesRepo, k8sClient, helmReleaseClient, renderer, gitWriteClient, moduleTargetNamespace, telemetryClient, monitor) if err != nil { panic(err) } diff --git a/cyclops-ctrl/internal/controller/modules.go b/cyclops-ctrl/internal/controller/modules.go index 7e9afe08..1cd733b1 100644 --- a/cyclops-ctrl/internal/controller/modules.go +++ b/cyclops-ctrl/internal/controller/modules.go @@ -2,6 +2,7 @@ package controller import ( "fmt" + "github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/git" "io" "net/http" "os" @@ -27,6 +28,7 @@ type Modules struct { kubernetesClient k8sclient.IKubernetesClient templatesRepo template.ITemplateRepo renderer *render.Renderer + gitWriteClient *git.WriteClient moduleTargetNamespace string @@ -38,6 +40,7 @@ func NewModulesController( templatesRepo template.ITemplateRepo, kubernetes k8sclient.IKubernetesClient, renderer *render.Renderer, + gitWriteClient *git.WriteClient, moduleTargetNamespace string, telemetryClient telemetry.Client, monitor prometheus.Monitor, @@ -46,6 +49,7 @@ func NewModulesController( kubernetesClient: kubernetes, templatesRepo: templatesRepo, renderer: renderer, + gitWriteClient: gitWriteClient, moduleTargetNamespace: moduleTargetNamespace, telemetryClient: telemetryClient, monitor: monitor, @@ -279,6 +283,15 @@ func (m *Modules) CreateModule(ctx *gin.Context) { m.telemetryClient.ModuleCreation() + if len(module.GetAnnotations()[v1alpha1.GitOpsWriteRepoAnnotation]) != 0 { + err := m.gitWriteClient.Write(module) + if err != nil { + fmt.Println(err) + ctx.JSON(http.StatusInternalServerError, dto.NewError("Error pushing to git", err.Error())) + } + return + } + err = m.kubernetesClient.CreateModule(module) if err != nil { fmt.Println(err) @@ -314,6 +327,31 @@ func (m *Modules) UpdateModule(ctx *gin.Context) { return } + module.Spec.TemplateRef.SourceType = curr.Spec.TemplateRef.SourceType + + fmt.Println("request.Template", request.Template) + module.Status.TemplateResolvedVersion = request.Template.ResolvedVersion + module.Status.ReconciliationStatus = curr.Status.ReconciliationStatus + module.Status.IconURL = curr.Status.IconURL + module.Status.ManagedGVRs = curr.Status.ManagedGVRs + + module.Spec.TargetNamespace = curr.Spec.TargetNamespace + module.SetLabels(curr.GetLabels()) + + annotations := curr.GetAnnotations() + delete(annotations, "kubectl.kubernetes.io/last-applied-configuration") + fmt.Println(annotations) + module.SetAnnotations(annotations) + + if len(module.GetAnnotations()[v1alpha1.GitOpsWriteRepoAnnotation]) != 0 { + err := m.gitWriteClient.Write(module) + if err != nil { + fmt.Println(err) + ctx.JSON(http.StatusInternalServerError, dto.NewError("Error pushing to git", err.Error())) + } + return + } + history := curr.History if curr.History == nil { history = make([]v1alpha1.HistoryEntry, 0) @@ -336,16 +374,6 @@ func (m *Modules) UpdateModule(ctx *gin.Context) { module.SetResourceVersion(curr.GetResourceVersion()) - module.Spec.TemplateRef.SourceType = curr.Spec.TemplateRef.SourceType - - module.Status.TemplateResolvedVersion = request.Template.ResolvedVersion - module.Status.ReconciliationStatus = curr.Status.ReconciliationStatus - module.Status.IconURL = curr.Status.IconURL - module.Status.ManagedGVRs = curr.Status.ManagedGVRs - - module.Spec.TargetNamespace = curr.Spec.TargetNamespace - module.SetLabels(curr.GetLabels()) - result, err := m.kubernetesClient.UpdateModuleStatus(&module) if err != nil { fmt.Println(err) diff --git a/cyclops-ctrl/internal/git/writeclient.go b/cyclops-ctrl/internal/git/writeclient.go new file mode 100644 index 00000000..b25c62a1 --- /dev/null +++ b/cyclops-ctrl/internal/git/writeclient.go @@ -0,0 +1,141 @@ +package git + +import ( + "errors" + "fmt" + cyclopsv1alpha1 "github.com/cyclops-ui/cyclops/cyclops-ctrl/api/v1alpha1" + "github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/auth" + "github.com/go-git/go-billy/v5/memfs" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/go-git/go-git/v5/plumbing/transport/http" + "github.com/go-git/go-git/v5/storage/memory" + path2 "path" + "sigs.k8s.io/yaml" + "time" +) + +type WriteClient struct { + templatesResolver auth.TemplatesResolver +} + +func NewWriteClient(templatesResolver auth.TemplatesResolver) *WriteClient { + return &WriteClient{ + templatesResolver: templatesResolver, + } +} + +func (c *WriteClient) Write(module cyclopsv1alpha1.Module) error { + module.Status = nil + + repoURL, exists := module.GetAnnotations()[cyclopsv1alpha1.GitOpsWriteRepoAnnotation] + if !exists { + return errors.New(fmt.Sprintf("module passed to write without git repository; set cyclops-ui.com/write-repo annotation in module %v", module.Name)) + } + + path := module.GetAnnotations()[cyclopsv1alpha1.GitOpsWritePathAnnotation] + revision := module.GetAnnotations()[cyclopsv1alpha1.GitOpsWriteRevisionAnnotation] + + creds, err := c.templatesResolver.RepoAuthCredentials(repoURL) + if err != nil { + return err + } + + storer := memory.NewStorage() + fs := memfs.New() + repo, err := git.Clone(storer, fs, &git.CloneOptions{ + URL: repoURL, + Auth: httpBasicAuthCredentials(creds), + }) + if err != nil { + return fmt.Errorf("failed to clone repository: %w", err) + } + + if path2.Ext(path) != "yaml" || path2.Ext(path) != "yml" { + path = path2.Join(path, fmt.Sprintf("%v.yaml", module.Name)) + } + + worktree, err := repo.Worktree() + if err != nil { + return fmt.Errorf("failed to get worktree: %w", err) + } + + if len(revision) != 0 { + branch := plumbing.NewBranchReferenceName(revision) + err = worktree.Checkout(&git.CheckoutOptions{ + Branch: branch, + Create: false, + }) + if err != nil { + if errors.Is(err, plumbing.ErrReferenceNotFound) { + err = worktree.Checkout(&git.CheckoutOptions{ + Branch: branch, + Create: true, + }) + } else { + return fmt.Errorf("failed to checkout branch %s: %w", branch.String(), err) + } + + err = worktree.Pull(&git.PullOptions{ + Auth: httpBasicAuthCredentials(creds), + ReferenceName: branch, + RemoteName: "origin", + SingleBranch: true, + }) + if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) { + return fmt.Errorf("failed to pull changes: %w", err) + } + } + } + + file, err := fs.Create(path) + if err != nil { + return fmt.Errorf("failed to create file in repository: %w", err) + } + + moduleData, err := yaml.Marshal(module) + if err != nil { + return err + } + + if _, err := file.Write(moduleData); err != nil { + return fmt.Errorf("failed to write JSON data to file: %w", err) + } + file.Close() + + if _, err := worktree.Add(path); err != nil { + fmt.Println("err worktree.Add", path) + return fmt.Errorf("failed to add file to worktree: %w", err) + } + + commitMessage := fmt.Sprintf("Update %s with new module data", path) + _, err = worktree.Commit(commitMessage, &git.CommitOptions{ + Author: &object.Signature{ + Name: "Cyclops UI", + When: time.Now(), + }, + }) + if err != nil { + return fmt.Errorf("failed to commit changes: %w", err) + } + + if err := repo.Push(&git.PushOptions{ + Auth: httpBasicAuthCredentials(creds), + }); err != nil { + return fmt.Errorf("failed to push changes: %w", err) + } + + return nil +} + +func httpBasicAuthCredentials(creds *auth.Credentials) *http.BasicAuth { + if creds == nil { + return nil + } + + return &http.BasicAuth{ + Username: creds.Username, + Password: creds.Password, + } +} diff --git a/cyclops-ctrl/internal/handler/handler.go b/cyclops-ctrl/internal/handler/handler.go index eb5d6cea..adc0225b 100644 --- a/cyclops-ctrl/internal/handler/handler.go +++ b/cyclops-ctrl/internal/handler/handler.go @@ -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/git" "github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/integrations/helm" "github.com/gin-gonic/gin" "net/http" @@ -17,10 +18,11 @@ import ( type Handler struct { router *gin.Engine - templatesRepo templaterepo.ITemplateRepo - k8sClient k8sclient.IKubernetesClient - releaseClient *helm.ReleaseClient - renderer *render.Renderer + templatesRepo templaterepo.ITemplateRepo + k8sClient k8sclient.IKubernetesClient + releaseClient *helm.ReleaseClient + renderer *render.Renderer + gitWriteClient *git.WriteClient moduleTargetNamespace string @@ -33,6 +35,7 @@ func New( kubernetesClient k8sclient.IKubernetesClient, releaseClient *helm.ReleaseClient, renderer *render.Renderer, + gitWriteClient *git.WriteClient, moduleTargetNamespace string, telemetryClient telemetry.Client, monitor prometheus.Monitor, @@ -42,6 +45,7 @@ func New( k8sClient: kubernetesClient, renderer: renderer, releaseClient: releaseClient, + gitWriteClient: gitWriteClient, moduleTargetNamespace: moduleTargetNamespace, telemetryClient: telemetryClient, monitor: monitor, @@ -53,7 +57,7 @@ func (h *Handler) Start() error { gin.SetMode(gin.DebugMode) templatesController := controller.NewTemplatesController(h.templatesRepo, h.k8sClient, h.telemetryClient) - modulesController := controller.NewModulesController(h.templatesRepo, h.k8sClient, h.renderer, h.moduleTargetNamespace, h.telemetryClient, h.monitor) + modulesController := controller.NewModulesController(h.templatesRepo, h.k8sClient, h.renderer, h.gitWriteClient, h.moduleTargetNamespace, h.telemetryClient, h.monitor) clusterController := controller.NewClusterController(h.k8sClient) helmController := controller.NewHelmController(h.k8sClient, h.releaseClient, h.telemetryClient) diff --git a/cyclops-ctrl/internal/mapper/modules.go b/cyclops-ctrl/internal/mapper/modules.go index 2ed94ffe..4e5e96f7 100644 --- a/cyclops-ctrl/internal/mapper/modules.go +++ b/cyclops-ctrl/internal/mapper/modules.go @@ -20,13 +20,21 @@ func RequestToModule(req dto.Module) (cyclopsv1alpha1.Module, error) { return cyclopsv1alpha1.Module{}, err } + annotations := make(map[string]string) + if req.GitOpsWrite != nil { + annotations[cyclopsv1alpha1.GitOpsWriteRepoAnnotation] = req.GitOpsWrite.Repo + annotations[cyclopsv1alpha1.GitOpsWritePathAnnotation] = req.GitOpsWrite.Path + annotations[cyclopsv1alpha1.GitOpsWriteRevisionAnnotation] = req.GitOpsWrite.Branch + } + return cyclopsv1alpha1.Module{ TypeMeta: metav1.TypeMeta{ Kind: "Module", APIVersion: "cyclops-ui.com/v1alpha1", }, ObjectMeta: metav1.ObjectMeta{ - Name: req.Name, + Name: req.Name, + Annotations: annotations, }, Spec: cyclopsv1alpha1.ModuleSpec{ TargetNamespace: mapTargetNamespace(req.Namespace), @@ -35,6 +43,7 @@ func RequestToModule(req dto.Module) (cyclopsv1alpha1.Module, error) { Raw: data, }, }, + Status: &cyclopsv1alpha1.ModuleStatus{}, History: make([]cyclopsv1alpha1.HistoryEntry, 0), }, nil } @@ -48,6 +57,7 @@ func ModuleToDTO(module cyclopsv1alpha1.Module) (dto.Module, error) { Template: k8sTemplateRefToDTO(module.Spec.TemplateRef, module.Status.TemplateResolvedVersion), Values: module.Spec.Values, IconURL: module.Status.IconURL, + GitOpsWrite: mapGitOpsWrite(module), ReconciliationStatus: dto.ReconciliationStatus{ Status: dto.ReconciliationStatusState(module.Status.ReconciliationStatus.Status), Reason: module.Status.ReconciliationStatus.Reason, @@ -151,3 +161,15 @@ func mapTargetNamespace(targetNamespace string) string { return targetNamespace } + +func mapGitOpsWrite(module cyclopsv1alpha1.Module) *dto.GitOpsWrite { + if repo, ok := module.GetAnnotations()[cyclopsv1alpha1.GitOpsWriteRepoAnnotation]; !ok || len(repo) == 0 { + return nil + } + + return &dto.GitOpsWrite{ + Repo: module.GetAnnotations()[cyclopsv1alpha1.GitOpsWriteRepoAnnotation], + Path: module.GetAnnotations()[cyclopsv1alpha1.GitOpsWritePathAnnotation], + Branch: module.GetAnnotations()[cyclopsv1alpha1.GitOpsWriteRevisionAnnotation], + } +} diff --git a/cyclops-ctrl/internal/models/dto/modules.go b/cyclops-ctrl/internal/models/dto/modules.go index c1e4339d..9eaf8ff2 100644 --- a/cyclops-ctrl/internal/models/dto/modules.go +++ b/cyclops-ctrl/internal/models/dto/modules.go @@ -4,6 +4,7 @@ type Module struct { Name string `json:"name"` Namespace string `json:"namespace"` TargetNamespace string `json:"targetNamespace"` + GitOpsWrite *GitOpsWrite `json:"gitOpsWrite,omitempty"` Template Template `json:"template"` Version string `json:"version"` Values interface{} `json:"values"` @@ -35,6 +36,12 @@ type Template struct { SourceType string `json:"sourceType"` } +type GitOpsWrite struct { + Repo string `json:"repo"` + Path string `json:"path"` + Branch string `json:"branch"` +} + type TemplatesResponse struct { Current string `json:"current"` New string `json:"new"` diff --git a/cyclops-ctrl/internal/modulecontroller/module_controller.go b/cyclops-ctrl/internal/modulecontroller/module_controller.go index 533efcdd..23f85d43 100644 --- a/cyclops-ctrl/internal/modulecontroller/module_controller.go +++ b/cyclops-ctrl/internal/modulecontroller/module_controller.go @@ -137,6 +137,10 @@ func (r *ModuleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr r.logger.Info("upsert module", "namespaced name", req.NamespacedName) + if module.Status == nil { + module.Status = &cyclopsv1alpha1.ModuleStatus{} + } + templateVersion := module.Status.TemplateResolvedVersion if len(templateVersion) == 0 { templateVersion = module.Spec.TemplateRef.Version @@ -442,7 +446,7 @@ func (r *ModuleReconciler) setStatus( trv = templateResolvedVersion } - module.Status = cyclopsv1alpha1.ModuleStatus{ + module.Status = &cyclopsv1alpha1.ModuleStatus{ ReconciliationStatus: cyclopsv1alpha1.ReconciliationStatus{ Status: status, Reason: reason, From 69e93b60112d1673d4c447723749284fd1ec1341 Mon Sep 17 00:00:00 2001 From: petar-cvit Date: Mon, 16 Dec 2024 17:13:48 +0100 Subject: [PATCH 06/30] edit module advanced ection --- .../components/pages/NewModule/NewModule.tsx | 4 +- .../shared/EditModule/EditModule.tsx | 299 +++++++++++++----- .../components/shared/EditModule/custom.css | 21 +- 3 files changed, 239 insertions(+), 85 deletions(-) diff --git a/cyclops-ui/src/components/pages/NewModule/NewModule.tsx b/cyclops-ui/src/components/pages/NewModule/NewModule.tsx index 5252b132..fcca3fcc 100644 --- a/cyclops-ui/src/components/pages/NewModule/NewModule.tsx +++ b/cyclops-ui/src/components/pages/NewModule/NewModule.tsx @@ -119,7 +119,7 @@ const NewModule = () => { const handleSubmit = (values: any) => { const moduleName = values["cyclops_module_name"]; const moduleNamespace = values["cyclops_module_namespace"]; - const gitopsConfig = { + const gitOpsWrite = { repo: values["gitops-repo"], path: values["gitops-path"], branch: values["gitops-branch"], @@ -131,7 +131,7 @@ const NewModule = () => { .post(`/api/modules/new`, { name: moduleName, namespace: moduleNamespace, - gitops: gitopsConfig, + gitOpsWrite: gitOpsWrite, values: values, template: { repo: template.repo, diff --git a/cyclops-ui/src/components/shared/EditModule/EditModule.tsx b/cyclops-ui/src/components/shared/EditModule/EditModule.tsx index b98c2692..4eeb6929 100644 --- a/cyclops-ui/src/components/shared/EditModule/EditModule.tsx +++ b/cyclops-ui/src/components/shared/EditModule/EditModule.tsx @@ -10,9 +10,15 @@ import { notification, Row, Spin, + Switch, Typography, } from "antd"; -import { LockFilled, UnlockFilled } from "@ant-design/icons"; +import { + DownOutlined, + LockFilled, + UnlockFilled, + UpOutlined, +} from "@ant-design/icons"; import { findMaps, flattenObjectKeys, mapsToArray } from "../../../utils/form"; import "./custom.css"; @@ -85,6 +91,7 @@ export const EditModuleComponent = ({ const [form] = Form.useForm(); const [editTemplateForm] = Form.useForm(); + const [gitOpsWriteForm] = Form.useForm(); const [initialValuesRaw, setInitialValuesRaw] = useState({}); @@ -100,6 +107,9 @@ export const EditModuleComponent = ({ }, }); + const [advancedOptionsExpanded, setAdvancedOptionsExpanded] = useState(false); + const [gitopsToggle, setGitopsToggle] = useState(false); + const [templateRef, setTemplateRef] = useState({ repo: "", path: "", @@ -139,6 +149,14 @@ export const EditModuleComponent = ({ path: module.template.path, version: module.template.version, }); + + setGitopsToggle(String(module.gitOpsWrite.repo).length > 0); + gitOpsWriteForm.setFieldsValue({ + "gitops-repo": module.gitOpsWrite.repo, + "gitops-path": module.gitOpsWrite.path, + "gitops-branch": module.gitOpsWrite.branch, + }); + setLoadValues(true); setTemplateRef({ @@ -398,6 +416,11 @@ export const EditModuleComponent = ({ ); } else return templateRef.resolvedVersion.substring(0, 7); }; + + const toggleExpand = () => { + setAdvancedOptionsExpanded(!advancedOptionsExpanded); + }; + return (
-
( - - - {required ? ( - - * - - ) : ( - <> - )} - - {label} - - )} +
- {lockButton()} - ( + + + {required ? ( + + * + + ) : ( + <> + )} + + {label} + + )} > - - -
- / + {lockButton()} + + + +
+ / +
+ + + +
+ @ +
+ + + + + + + +
+
{ + console.log(e); + }} + requiredMark={(label, { required }) => ( + + + {required ? ( + + * + + ) : ( + <> + )} + + {label} + + )} + > + + + GitOps Workflow +

+ Will you be using a GitOps workflow to deploy this + module? +

+
+ } + style={{ padding: "0px 12px 0px 12px" }} + > + { + setGitopsToggle(!gitopsToggle); + }} + /> + +
+ + + + + + + + + + + +
+
- - - -
- @ +
+ {advancedOptionsExpanded ? ( +
+ Advanced + +
+ ) : ( +
+ Advanced + +
+ )}
- - - - - - - +
diff --git a/cyclops-ui/src/components/shared/EditModule/custom.css b/cyclops-ui/src/components/shared/EditModule/custom.css index 7fe6e4e7..d4dd0e91 100644 --- a/cyclops-ui/src/components/shared/EditModule/custom.css +++ b/cyclops-ui/src/components/shared/EditModule/custom.css @@ -7,10 +7,6 @@ label.ant-form-item-required:before { position: relative !important; } -code { - color: #000080; -} - .custom { .ant-collapse-content-box { padding: 0; @@ -20,3 +16,20 @@ code { .linkToTemplate:hover { text-decoration: underline; } + +.expandadvanced { + width: 100%; + height: 32px; + background-color: #eee; + border-radius: 0 0 7px 7px; + transition: background-color 0.2s ease; + display: flex; + justify-content: center; + align-items: center; + text-align: center; + cursor: pointer; +} + +.expandadvanced:hover { + background-color: #e0e0e0; +} From 9e501a4583ee074b1546019c60799d584f651120 Mon Sep 17 00:00:00 2001 From: KaradzaJuraj Date: Mon, 16 Dec 2024 22:56:05 +0100 Subject: [PATCH 07/30] enable edit --- cyclops-ctrl/internal/controller/modules.go | 16 ++++- .../components/pages/NewModule/NewModule.tsx | 14 ++-- .../shared/EditModule/EditModule.tsx | 68 ++++++++++++------- cyclops-ui/src/utils/api/api.tsx | 2 + 4 files changed, 68 insertions(+), 32 deletions(-) diff --git a/cyclops-ctrl/internal/controller/modules.go b/cyclops-ctrl/internal/controller/modules.go index 1cd733b1..56f87a02 100644 --- a/cyclops-ctrl/internal/controller/modules.go +++ b/cyclops-ctrl/internal/controller/modules.go @@ -2,13 +2,14 @@ package controller import ( "fmt" - "github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/git" "io" "net/http" "os" "strings" "time" + "github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/git" + "sigs.k8s.io/yaml" "github.com/gin-gonic/gin" @@ -339,8 +340,19 @@ func (m *Modules) UpdateModule(ctx *gin.Context) { module.SetLabels(curr.GetLabels()) annotations := curr.GetAnnotations() + moduleAnnotations := module.GetAnnotations() + if moduleAnnotations != nil { + if _, ok := moduleAnnotations["cyclops-ui.com/write-repo"]; ok { + annotations["cyclops-ui.com/write-repo"] = moduleAnnotations["cyclops-ui.com/write-repo"] + } + if _, ok := moduleAnnotations["cyclops-ui.com/write-repo"]; ok { + annotations["cyclops-ui.com/write-path"] = moduleAnnotations["cyclops-ui.com/write-path"] + } + if _, ok := moduleAnnotations["cyclops-ui.com/write-revision"]; ok { + annotations["cyclops-ui.com/write-revision"] = moduleAnnotations["cyclops-ui.com/write-revision"] + } + } delete(annotations, "kubectl.kubernetes.io/last-applied-configuration") - fmt.Println(annotations) module.SetAnnotations(annotations) if len(module.GetAnnotations()[v1alpha1.GitOpsWriteRepoAnnotation]) != 0 { diff --git a/cyclops-ui/src/components/pages/NewModule/NewModule.tsx b/cyclops-ui/src/components/pages/NewModule/NewModule.tsx index fcca3fcc..1af0123c 100644 --- a/cyclops-ui/src/components/pages/NewModule/NewModule.tsx +++ b/cyclops-ui/src/components/pages/NewModule/NewModule.tsx @@ -141,7 +141,9 @@ const NewModule = () => { }, }) .then((res) => { - window.location.href = "/modules/" + moduleName; + window.location.href = gitopsToggle + ? "/modules/" + : "/modules/" + moduleName; }) .catch((error) => { setLoading(false); @@ -552,18 +554,18 @@ const NewModule = () => { id="gitops" label={
- GitOps Workflow + Push changes to Git?

- Will you be using a GitOps workflow to deploy this - module? + Instead of deploying to the cluster, Cyclops will push + the changes to a git repository.

} style={{ padding: "0px 12px 0px 12px" }} > { - SetGitopsToggle(!gitopsToggle); + onChange={(e) => { + SetGitopsToggle(e); }} /> diff --git a/cyclops-ui/src/components/shared/EditModule/EditModule.tsx b/cyclops-ui/src/components/shared/EditModule/EditModule.tsx index 4eeb6929..9724142a 100644 --- a/cyclops-ui/src/components/shared/EditModule/EditModule.tsx +++ b/cyclops-ui/src/components/shared/EditModule/EditModule.tsx @@ -62,6 +62,7 @@ export interface EditModuleProps { moduleName: string, templateRef: any, values: string, + gitOpsValues: any, ) => Promise; onUpdateModuleSuccess: (moduleName: string) => void; } @@ -98,6 +99,7 @@ export const EditModuleComponent = ({ const [values, setValues] = useState({}); const [isChanged, setIsChanged] = useState(false); const [isTemplateChanged, setIsTemplateChanged] = useState(false); + const [isGitOpsChanged, setIsGitOpsChanged] = useState(false); const [config, setConfig] = useState