Skip to content

Commit d6ec2cd

Browse files
committed
Merge branch 'main' into crd-managed-resources
# Conflicts: # cyclops-ctrl/internal/controller/sse/resources.go # cyclops-ctrl/pkg/cluster/k8sclient/client.go # cyclops-ctrl/pkg/cluster/k8sclient/modules.go # cyclops-ctrl/pkg/cluster/k8sclient/resources.go # install/cyclops-install.yaml # web/blog/2025-03-04-launch-week-2/index.md
2 parents 9a01e78 + 1b170c9 commit d6ec2cd

File tree

23 files changed

+525
-679
lines changed

23 files changed

+525
-679
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
<a href="https://www.linkedin.com/company/cyclops-ui">LinkedIn</a>
2121
</p>
2222

23+
2324
## 🟠 What is Cyclops?
2425

2526
Cyclops is an open-source dev tool that simplifies Kubernetes with an easy-to-use UI, making it less intimidating. Instead of creating and configuring your Kubernetes manifests with YAML, use Cyclops to painlessly configure and deploy your applications - validations included!

cyclops-ctrl/api/v1alpha1/module_types.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,23 @@ type HistoryTemplateRef struct {
121121
}
122122

123123
type HistoryEntry struct {
124-
Generation int64 `json:"generation"`
125-
TemplateRef HistoryTemplateRef `json:"template"`
126-
Values apiextensionsv1.JSON `json:"values"`
124+
Generation int64 `json:"generation"`
125+
126+
// +kubebuilder:validation:Optional
127+
TargetNamespace string `json:"targetNamespace"`
128+
TemplateRef HistoryTemplateRef `json:"template"`
129+
Values apiextensionsv1.JSON `json:"values"`
127130
}
128131

129132
//+kubebuilder:object:root=true
130133
//+kubebuilder:subresource:status
134+
//+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
135+
//+kubebuilder:printcolumn:name="Target Namespace",type=string,JSONPath=`.spec.targetNamespace`,priority=1
136+
//+kubebuilder:printcolumn:name="Template",type=string,JSONPath=`.spec.template.repo`
137+
//+kubebuilder:printcolumn:name="Template path",type=string,JSONPath=`.spec.template.path`,priority=1
138+
//+kubebuilder:printcolumn:name="Template version",type=string,JSONPath=`.spec.template.version`,priority=1
139+
//+kubebuilder:printcolumn:name="Template resolved version",type=string,JSONPath=`.status.templateResolvedVersion`,priority=1
140+
//+kubebuilder:printcolumn:name="Reconciliation Status",type=string,JSONPath=`.status.reconciliationStatus.status`
131141

132142
// Module is the Schema for the modules API
133143
type Module struct {

cyclops-ctrl/api/v1alpha1/template_auth_rule_types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ type TemplateAuthRuleSpec struct {
3737

3838
//+kubebuilder:object:root=true
3939
//+kubebuilder:subresource:status
40+
//+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
41+
//+kubebuilder:printcolumn:name="Repository",type=string,JSONPath=`.spec.repo`
4042

4143
// TemplateAuthRule is the Schema for the modules API
4244
type TemplateAuthRule struct {

cyclops-ctrl/api/v1alpha1/template_store_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ import (
2626
const IconURLAnnotation = "cyclops-ui.com/icon"
2727

2828
//+kubebuilder:object:root=true
29+
//+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
30+
//+kubebuilder:printcolumn:name="Type",type=string,JSONPath=`.spec.sourceType`
31+
//+kubebuilder:printcolumn:name="Repository",type=string,JSONPath=`.spec.repo`
32+
//+kubebuilder:printcolumn:name="Path",type=string,JSONPath=`.spec.path`
33+
//+kubebuilder:printcolumn:name="Version",type=string,JSONPath=`.spec.version`
2934

3035
// TemplateStore holds reference to a template that can be offered as a starting point
3136
type TemplateStore struct {

cyclops-ctrl/config/crd/bases/cyclops-ui.com_modules.yaml

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,33 @@ spec:
1414
singular: module
1515
scope: Namespaced
1616
versions:
17-
- name: v1alpha1
17+
- additionalPrinterColumns:
18+
- jsonPath: .metadata.creationTimestamp
19+
name: Age
20+
type: date
21+
- jsonPath: .spec.targetNamespace
22+
name: Target Namespace
23+
priority: 1
24+
type: string
25+
- jsonPath: .spec.template.repo
26+
name: Template
27+
type: string
28+
- jsonPath: .spec.template.path
29+
name: Template path
30+
priority: 1
31+
type: string
32+
- jsonPath: .spec.template.version
33+
name: Template version
34+
priority: 1
35+
type: string
36+
- jsonPath: .status.templateResolvedVersion
37+
name: Template resolved version
38+
priority: 1
39+
type: string
40+
- jsonPath: .status.reconciliationStatus.status
41+
name: Reconciliation Status
42+
type: string
43+
name: v1alpha1
1844
schema:
1945
openAPIV3Schema:
2046
description: Module is the Schema for the modules API
@@ -32,6 +58,8 @@ spec:
3258
generation:
3359
format: int64
3460
type: integer
61+
targetNamespace:
62+
type: string
3563
template:
3664
properties:
3765
CRDName:

cyclops-ctrl/config/crd/bases/cyclops-ui.com_templateauthrules.yaml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,14 @@ spec:
1414
singular: templateauthrule
1515
scope: Namespaced
1616
versions:
17-
- name: v1alpha1
17+
- additionalPrinterColumns:
18+
- jsonPath: .metadata.creationTimestamp
19+
name: Age
20+
type: date
21+
- jsonPath: .spec.repo
22+
name: Repository
23+
type: string
24+
name: v1alpha1
1825
schema:
1926
openAPIV3Schema:
2027
description: TemplateAuthRule is the Schema for the modules API

cyclops-ctrl/config/crd/bases/cyclops-ui.com_templatestores.yaml

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,23 @@ spec:
1414
singular: templatestore
1515
scope: Namespaced
1616
versions:
17-
- name: v1alpha1
17+
- additionalPrinterColumns:
18+
- jsonPath: .metadata.creationTimestamp
19+
name: Age
20+
type: date
21+
- jsonPath: .spec.sourceType
22+
name: Type
23+
type: string
24+
- jsonPath: .spec.repo
25+
name: Repository
26+
type: string
27+
- jsonPath: .spec.path
28+
name: Path
29+
type: string
30+
- jsonPath: .spec.version
31+
name: Version
32+
type: string
33+
name: v1alpha1
1834
schema:
1935
openAPIV3Schema:
2036
description: TemplateStore holds reference to a template that can be offered
@@ -58,3 +74,4 @@ spec:
5874
type: object
5975
served: true
6076
storage: true
77+
subresources: {}

cyclops-ctrl/internal/controller/modules.go

Lines changed: 168 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"io"
66
"net/http"
77
"os"
8+
"sort"
89
"strings"
910
"time"
1011

@@ -271,15 +272,14 @@ func (m *Modules) CurrentManifest(ctx *gin.Context) {
271272
func (m *Modules) DeleteModuleResource(ctx *gin.Context) {
272273
ctx.Header("Access-Control-Allow-Origin", "*")
273274

274-
var request dto.DeleteResource
275+
var request *dto.Resource
275276
if err := ctx.BindJSON(&request); err != nil {
276277
fmt.Println(err)
277278
ctx.JSON(http.StatusBadRequest, dto.NewError("Error mapping module request", err.Error()))
278279
return
279280
}
280281

281-
err := m.kubernetesClient.Delete(&request)
282-
if err != nil {
282+
if err := m.kubernetesClient.Delete(request); err != nil {
283283
fmt.Println(err)
284284
ctx.JSON(http.StatusInternalServerError, dto.NewError("Error deleting module", err.Error()))
285285
return
@@ -412,7 +412,8 @@ func (m *Modules) UpdateModule(ctx *gin.Context) {
412412
}
413413

414414
module.History = append([]v1alpha1.HistoryEntry{{
415-
Generation: curr.Generation,
415+
Generation: curr.Generation,
416+
TargetNamespace: curr.Spec.TargetNamespace,
416417
TemplateRef: v1alpha1.HistoryTemplateRef{
417418
URL: curr.Spec.TemplateRef.URL,
418419
Path: curr.Spec.TemplateRef.Path,
@@ -447,6 +448,161 @@ func (m *Modules) UpdateModule(ctx *gin.Context) {
447448
ctx.Status(http.StatusOK)
448449
}
449450

451+
func (m *Modules) HistoryEntryManifest(ctx *gin.Context) {
452+
ctx.Header("Access-Control-Allow-Origin", "*")
453+
454+
var request dto.RollbackRequest
455+
if err := ctx.BindJSON(&request); err != nil {
456+
fmt.Println(err)
457+
ctx.JSON(http.StatusBadRequest, dto.NewError("Error mapping module request", err.Error()))
458+
return
459+
}
460+
461+
curr, err := m.kubernetesClient.GetModule(request.ModuleName)
462+
if err != nil {
463+
fmt.Println(err)
464+
ctx.JSON(http.StatusInternalServerError, dto.NewError("Error fetching module", err.Error()))
465+
return
466+
}
467+
468+
var targetGeneration *v1alpha1.HistoryEntry
469+
for _, entry := range curr.History {
470+
if entry.Generation == request.Generation {
471+
targetGeneration = &entry
472+
break
473+
}
474+
}
475+
476+
if targetGeneration == nil {
477+
ctx.JSON(http.StatusInternalServerError, dto.NewError("Invalid rollback generation provided", fmt.Sprintf("Generation %d does not exist", request.Generation)))
478+
return
479+
}
480+
481+
targetTemplate, err := m.templatesRepo.GetTemplate(
482+
targetGeneration.TemplateRef.URL,
483+
targetGeneration.TemplateRef.Path,
484+
targetGeneration.TemplateRef.Version,
485+
"",
486+
targetGeneration.TemplateRef.SourceType,
487+
)
488+
if err != nil {
489+
fmt.Println(err)
490+
ctx.Status(http.StatusInternalServerError)
491+
return
492+
}
493+
494+
manifest, err := m.renderer.HelmTemplate(v1alpha1.Module{
495+
ObjectMeta: metav1.ObjectMeta{
496+
Name: request.ModuleName,
497+
},
498+
Spec: v1alpha1.ModuleSpec{
499+
TargetNamespace: targetGeneration.TargetNamespace,
500+
TemplateRef: v1alpha1.TemplateRef{
501+
URL: targetGeneration.TemplateRef.URL,
502+
Path: targetGeneration.TemplateRef.Path,
503+
Version: targetGeneration.TemplateRef.Version,
504+
SourceType: targetGeneration.TemplateRef.SourceType,
505+
},
506+
Values: targetGeneration.Values,
507+
},
508+
}, targetTemplate)
509+
if err != nil {
510+
fmt.Println(err)
511+
ctx.Status(http.StatusInternalServerError)
512+
return
513+
}
514+
515+
manifest = strings.TrimPrefix(manifest, "\n---")
516+
manifest = strings.TrimSuffix(manifest, "---\n")
517+
518+
ctx.String(http.StatusOK, manifest)
519+
}
520+
521+
func (m *Modules) RollbackModule(ctx *gin.Context) {
522+
ctx.Header("Access-Control-Allow-Origin", "*")
523+
524+
var request dto.RollbackRequest
525+
if err := ctx.BindJSON(&request); err != nil {
526+
fmt.Println(err)
527+
ctx.JSON(http.StatusBadRequest, dto.NewError("Error mapping module request", err.Error()))
528+
return
529+
}
530+
531+
curr, err := m.kubernetesClient.GetModule(request.ModuleName)
532+
if err != nil {
533+
fmt.Println(err)
534+
ctx.JSON(http.StatusInternalServerError, dto.NewError("Error fetching module", err.Error()))
535+
return
536+
}
537+
538+
var targetGeneration *v1alpha1.HistoryEntry
539+
for _, entry := range curr.History {
540+
if entry.Generation == request.Generation {
541+
targetGeneration = &entry
542+
break
543+
}
544+
}
545+
546+
if targetGeneration == nil {
547+
ctx.JSON(http.StatusInternalServerError, dto.NewError("Invalid rollback generation provided", fmt.Sprintf("Generation %d does not exist", request.Generation)))
548+
return
549+
}
550+
551+
module := curr.DeepCopy()
552+
553+
module.Kind = "Module"
554+
module.APIVersion = "cyclops-ui.com/v1alpha1"
555+
556+
history := module.History
557+
if module.History == nil {
558+
history = make([]v1alpha1.HistoryEntry, 0)
559+
}
560+
561+
module.History = append([]v1alpha1.HistoryEntry{{
562+
Generation: curr.Generation,
563+
TargetNamespace: curr.Spec.TargetNamespace,
564+
TemplateRef: v1alpha1.HistoryTemplateRef{
565+
URL: curr.Spec.TemplateRef.URL,
566+
Path: curr.Spec.TemplateRef.Path,
567+
Version: curr.Status.TemplateResolvedVersion,
568+
SourceType: curr.Spec.TemplateRef.SourceType,
569+
},
570+
Values: curr.Spec.Values,
571+
}}, history...)
572+
573+
if len(module.History) > 10 {
574+
module.History = module.History[:len(module.History)-1]
575+
}
576+
577+
module.Spec.Values = targetGeneration.Values
578+
module.Spec.TemplateRef = v1alpha1.TemplateRef{
579+
URL: targetGeneration.TemplateRef.URL,
580+
Path: targetGeneration.TemplateRef.Path,
581+
Version: targetGeneration.TemplateRef.Version,
582+
SourceType: targetGeneration.TemplateRef.SourceType,
583+
}
584+
module.Spec.TargetNamespace = targetGeneration.TargetNamespace
585+
586+
module.SetResourceVersion(curr.GetResourceVersion())
587+
588+
result, err := m.kubernetesClient.UpdateModuleStatus(module)
589+
if err != nil {
590+
fmt.Println(err)
591+
ctx.JSON(http.StatusInternalServerError, dto.NewError("Error updating module status", err.Error()))
592+
return
593+
}
594+
595+
module.ResourceVersion = result.ResourceVersion
596+
err = m.kubernetesClient.UpdateModule(module)
597+
if err != nil {
598+
fmt.Println(err)
599+
ctx.JSON(http.StatusInternalServerError, dto.NewError("Error updating module", err.Error()))
600+
return
601+
}
602+
603+
ctx.Status(http.StatusOK)
604+
}
605+
450606
func (m *Modules) ReconcileModule(ctx *gin.Context) {
451607
ctx.Header("Access-Control-Allow-Origin", "*")
452608

@@ -528,6 +684,14 @@ func (m *Modules) ResourcesForModule(ctx *gin.Context) {
528684
return
529685
}
530686

687+
sort.Slice(resources, func(i, j int) bool {
688+
if resources[i].GetGroupVersionKind() != resources[j].GetGroupVersionKind() {
689+
return resources[i].GetGroupVersionKind() < resources[j].GetGroupVersionKind()
690+
}
691+
692+
return resources[i].GetName() < resources[j].GetName()
693+
})
694+
531695
ctx.JSON(http.StatusOK, resources)
532696
}
533697

cyclops-ctrl/internal/controller/sse/resources.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func (s *Server) CRDResources(ctx *gin.Context) {
5050
s.streamResources(ctx, other.Children)
5151
}
5252

53-
func (s *Server) streamResources(ctx *gin.Context, resources []dto.Resource) {
53+
func (s *Server) streamResources(ctx *gin.Context, resources []*dto.Resource) {
5454
watchSpecs := make([]k8sclient.ResourceWatchSpec, 0, len(resources))
5555
for _, resource := range resources {
5656
if !k8sclient.IsWorkload(resource.GetGroup(), resource.GetVersion(), resource.GetKind()) {

cyclops-ctrl/internal/handler/handler.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ func (h *Handler) Start() error {
8787
h.router.DELETE("/modules/:name", modulesController.DeleteModule)
8888
h.router.POST("/modules/new", modulesController.CreateModule)
8989
h.router.POST("/modules/update", modulesController.UpdateModule)
90+
h.router.POST("/modules/rollback/manifest", modulesController.HistoryEntryManifest)
91+
h.router.POST("/modules/rollback", modulesController.RollbackModule)
9092
h.router.GET("/modules/:name/raw", modulesController.GetRawModuleManifest)
9193
h.router.POST("/modules/:name/reconcile", modulesController.ReconcileModule)
9294
h.router.GET("/modules/:name/history", modulesController.GetModuleHistory)

0 commit comments

Comments
 (0)