From c15e46644abc525bd2563e1674e2e2e2cd3e5509 Mon Sep 17 00:00:00 2001 From: petar-cvit Date: Thu, 29 Aug 2024 12:38:09 +0200 Subject: [PATCH 1/8] module target namespace --- cyclops-ctrl/api/v1alpha1/module_types.go | 6 +- .../api/v1alpha1/zz_generated.deepcopy.go | 1 - .../crd/bases/cyclops-ui.com_modules.yaml | 2 + .../cluster/k8sclient/apiresources.go | 31 ++++++ .../internal/cluster/k8sclient/client.go | 29 ++++- .../internal/cluster/k8sclient/modules.go | 31 +++++- cyclops-ctrl/internal/controller/modules.go | 2 +- cyclops-ctrl/internal/mapper/modules.go | 16 +-- cyclops-ctrl/internal/models/dto/modules.go | 1 + .../modulecontroller/module_controller.go | 2 +- .../internal/template/render/render.go | 10 +- cyclops-ui/src/components/form/Metadata.tsx | 62 +++++++++++ cyclops-ui/src/components/form/custom.css | 8 ++ .../src/components/pages/ModuleDetails.tsx | 4 +- .../components/pages/NewModule/NewModule.tsx | 105 ++++++++++++------ 15 files changed, 254 insertions(+), 56 deletions(-) create mode 100644 cyclops-ctrl/internal/cluster/k8sclient/apiresources.go create mode 100644 cyclops-ui/src/components/form/Metadata.tsx create mode 100644 cyclops-ui/src/components/form/custom.css diff --git a/cyclops-ctrl/api/v1alpha1/module_types.go b/cyclops-ctrl/api/v1alpha1/module_types.go index f53611db..8a3f460e 100644 --- a/cyclops-ctrl/api/v1alpha1/module_types.go +++ b/cyclops-ctrl/api/v1alpha1/module_types.go @@ -26,8 +26,10 @@ import ( // ModuleSpec defines the desired state of Module type ModuleSpec struct { - TemplateRef TemplateRef `json:"template"` - Values apiextensionsv1.JSON `json:"values"` + // +kubebuilder:validation:Optional + TargetNamespace string `json:"targetNamespace"` + TemplateRef TemplateRef `json:"template"` + Values apiextensionsv1.JSON `json:"values"` } type ModuleValue struct { diff --git a/cyclops-ctrl/api/v1alpha1/zz_generated.deepcopy.go b/cyclops-ctrl/api/v1alpha1/zz_generated.deepcopy.go index 09676bab..0bce7172 100644 --- a/cyclops-ctrl/api/v1alpha1/zz_generated.deepcopy.go +++ b/cyclops-ctrl/api/v1alpha1/zz_generated.deepcopy.go @@ -1,5 +1,4 @@ //go:build !ignore_autogenerated -// +build !ignore_autogenerated /* Copyright 2023. 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 426f786b..0da88077 100644 --- a/cyclops-ctrl/config/crd/bases/cyclops-ui.com_modules.yaml +++ b/cyclops-ctrl/config/crd/bases/cyclops-ui.com_modules.yaml @@ -66,6 +66,8 @@ spec: spec: description: ModuleSpec defines the desired state of Module properties: + targetNamespace: + type: string template: properties: path: diff --git a/cyclops-ctrl/internal/cluster/k8sclient/apiresources.go b/cyclops-ctrl/internal/cluster/k8sclient/apiresources.go new file mode 100644 index 00000000..b6dcc252 --- /dev/null +++ b/cyclops-ctrl/internal/cluster/k8sclient/apiresources.go @@ -0,0 +1,31 @@ +package k8sclient + +import ( + "fmt" + "github.com/pkg/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type apiResources struct { + resourcesList []*metav1.APIResourceList +} + +func (a apiResources) isResourceNamespaced(gvk schema.GroupVersionKind) (bool, error) { + for _, resource := range a.resourcesList { + gv, err := schema.ParseGroupVersion(resource.GroupVersion) + if err != nil { + return false, err + } + + for _, apiResource := range resource.APIResources { + if apiResource.Kind == gvk.Kind && + gv.Group == gvk.Group && + gv.Version == gvk.Version { + return apiResource.Namespaced, nil + } + } + } + + return false, errors.New(fmt.Sprintf("group version kind not found: %v", gvk.String())) +} diff --git a/cyclops-ctrl/internal/cluster/k8sclient/client.go b/cyclops-ctrl/internal/cluster/k8sclient/client.go index d1589fdb..ee438758 100644 --- a/cyclops-ctrl/internal/cluster/k8sclient/client.go +++ b/cyclops-ctrl/internal/cluster/k8sclient/client.go @@ -419,18 +419,30 @@ func (k *KubernetesClient) Delete(resource dto.Resource) error { ) } -func (k *KubernetesClient) CreateDynamic(resource v1alpha1.GroupVersionResource, obj *unstructured.Unstructured) error { +func (k *KubernetesClient) CreateDynamic( + resource v1alpha1.GroupVersionResource, + obj *unstructured.Unstructured, + targetNamespace string, +) error { gvr := schema.GroupVersionResource{ Group: resource.Group, Version: resource.Version, Resource: resource.Resource, } - objNamespace := obj.GetNamespace() - if len(strings.TrimSpace(objNamespace)) == 0 { - objNamespace = apiv1.NamespaceDefault + var objNamespace string + + objNamespace = apiv1.NamespaceDefault + + if len(strings.TrimSpace(targetNamespace)) != 0 { + obj.SetNamespace(strings.TrimSpace(targetNamespace)) } + if len(strings.TrimSpace(obj.GetNamespace())) != 0 { + objNamespace = obj.GetNamespace() + } + obj.SetNamespace(objNamespace) + isNamespaced, err := k.isResourceNamespaced(obj.GroupVersionKind()) if err != nil { return err @@ -1009,6 +1021,15 @@ func (k *KubernetesClient) isResourceNamespaced(gvk schema.GroupVersionKind) (bo return false, errors.New(fmt.Sprintf("group version kind not found: %v", gvk.String())) } +func (k *KubernetesClient) clusterApiResources() (*apiResources, error) { + resourcesList, err := k.discovery.ServerPreferredResources() + if err != nil { + return nil, err + } + + return &apiResources{resourcesList: resourcesList}, nil +} + func (k *KubernetesClient) mapRole(group, version, kind, name, namespace string) (*dto.Role, error) { role, err := k.clientset.RbacV1().Roles(namespace).Get(context.Background(), name, metav1.GetOptions{}) diff --git a/cyclops-ctrl/internal/cluster/k8sclient/modules.go b/cyclops-ctrl/internal/cluster/k8sclient/modules.go index fbe19f1e..c42ed68b 100644 --- a/cyclops-ctrl/internal/cluster/k8sclient/modules.go +++ b/cyclops-ctrl/internal/cluster/k8sclient/modules.go @@ -152,9 +152,15 @@ func (k *KubernetesClient) getManagedGVRs(moduleName string) ([]schema.GroupVers func (k *KubernetesClient) GetDeletedResources( resources []dto.Resource, manifest string, + targetNamespace string, ) ([]dto.Resource, error) { resourcesFromTemplate := make(map[string][]dto.Resource, 0) + ar, err := k.clusterApiResources() + if err != nil { + return nil, err + } + for _, s := range strings.Split(manifest, "\n---\n") { s := strings.TrimSpace(s) if len(s) == 0 { @@ -165,13 +171,32 @@ func (k *KubernetesClient) GetDeletedResources( decoder := yaml2.NewYAMLOrJSONDecoder(strings.NewReader(s), len(s)) if err := decoder.Decode(&obj); err != nil { - panic(err) + return nil, err } objGVK := obj.GetObjectKind().GroupVersionKind().String() - resourcesFromTemplate[objGVK] = append(resourcesFromTemplate[objGVK], &dto.Service{ + + objNamespace := apiv1.NamespaceDefault + if len(strings.TrimSpace(targetNamespace)) != 0 { + obj.SetNamespace(strings.TrimSpace(targetNamespace)) + } + + if len(strings.TrimSpace(obj.GetNamespace())) != 0 { + objNamespace = obj.GetNamespace() + } + + namespaced, err := ar.isResourceNamespaced(obj.GroupVersionKind()) + if err != nil { + return nil, err + } + + if !namespaced { + objNamespace = "" + } + + resourcesFromTemplate[objGVK] = append(resourcesFromTemplate[objGVK], &dto.Other{ Name: obj.GetName(), - Namespace: obj.GetNamespace(), + Namespace: objNamespace, }) } diff --git a/cyclops-ctrl/internal/controller/modules.go b/cyclops-ctrl/internal/controller/modules.go index bb9b8ae4..1b25c2e8 100644 --- a/cyclops-ctrl/internal/controller/modules.go +++ b/cyclops-ctrl/internal/controller/modules.go @@ -386,7 +386,7 @@ func (m *Modules) ResourcesForModule(ctx *gin.Context) { return } - resources, err = m.kubernetesClient.GetDeletedResources(resources, manifest) + resources, err = m.kubernetesClient.GetDeletedResources(resources, manifest, module.Spec.TargetNamespace) if err != nil { fmt.Println(err) ctx.JSON(http.StatusInternalServerError, dto.NewError("Error fetching deleted module resources", err.Error())) diff --git a/cyclops-ctrl/internal/mapper/modules.go b/cyclops-ctrl/internal/mapper/modules.go index ee7aa667..189cf1cd 100644 --- a/cyclops-ctrl/internal/mapper/modules.go +++ b/cyclops-ctrl/internal/mapper/modules.go @@ -27,7 +27,8 @@ func RequestToModule(req dto.Module) (cyclopsv1alpha1.Module, error) { Name: req.Name, }, Spec: cyclopsv1alpha1.ModuleSpec{ - TemplateRef: DtoTemplateRefToK8s(req.Template), + TargetNamespace: req.Namespace, + TemplateRef: DtoTemplateRefToK8s(req.Template), Values: apiextensionsv1.JSON{ Raw: data, }, @@ -38,12 +39,13 @@ func RequestToModule(req dto.Module) (cyclopsv1alpha1.Module, error) { func ModuleToDTO(module cyclopsv1alpha1.Module) (dto.Module, error) { return dto.Module{ - Name: module.Name, - Namespace: module.Namespace, - Version: module.Spec.TemplateRef.Version, - Template: k8sTemplateRefToDTO(module.Spec.TemplateRef, module.Status.TemplateResolvedVersion), - Values: module.Spec.Values, - IconURL: module.Status.IconURL, + Name: module.Name, + Namespace: module.Namespace, + TargetNamespace: module.Spec.TargetNamespace, + Version: module.Spec.TemplateRef.Version, + Template: k8sTemplateRefToDTO(module.Spec.TemplateRef, module.Status.TemplateResolvedVersion), + Values: module.Spec.Values, + IconURL: module.Status.IconURL, ReconciliationStatus: dto.ReconciliationStatus{ Status: dto.ReconciliationStatusState(module.Status.ReconciliationStatus.Status), Reason: module.Status.ReconciliationStatus.Reason, diff --git a/cyclops-ctrl/internal/models/dto/modules.go b/cyclops-ctrl/internal/models/dto/modules.go index ad9209c3..5be00371 100644 --- a/cyclops-ctrl/internal/models/dto/modules.go +++ b/cyclops-ctrl/internal/models/dto/modules.go @@ -3,6 +3,7 @@ package dto type Module struct { Name string `json:"name"` Namespace string `json:"namespace"` + TargetNamespace string `json:"targetNamespace"` Template Template `json:"template"` Version string `json:"version"` Values interface{} `json:"values"` diff --git a/cyclops-ctrl/internal/modulecontroller/module_controller.go b/cyclops-ctrl/internal/modulecontroller/module_controller.go index 1d2f23bb..3579e3cd 100644 --- a/cyclops-ctrl/internal/modulecontroller/module_controller.go +++ b/cyclops-ctrl/internal/modulecontroller/module_controller.go @@ -284,7 +284,7 @@ func (r *ModuleReconciler) generateResources( } childrenGVRs = append(childrenGVRs, gvr) - if err := kClient.CreateDynamic(gvr, &obj); err != nil { + if err := kClient.CreateDynamic(gvr, &obj, module.Spec.TargetNamespace); err != nil { r.logger.Error(err, "could not apply resource", "module namespaced name", module.Name, diff --git a/cyclops-ctrl/internal/template/render/render.go b/cyclops-ctrl/internal/template/render/render.go index 5840a70e..162d1492 100644 --- a/cyclops-ctrl/internal/template/render/render.go +++ b/cyclops-ctrl/internal/template/render/render.go @@ -61,7 +61,7 @@ func (r *Renderer) HelmTemplate(module cyclopsv1alpha1.Module, moduleTemplate *m top["Values"] = values top["Release"] = map[string]interface{}{ "Name": module.Name, - "Namespace": "default", + "Namespace": mapTargetNamespace(module.Spec.TargetNamespace), } versionInfo, err := r.k8sClient.VersionInfo() @@ -150,6 +150,14 @@ func mapMetadata(metadata *helm.Metadata) *helmchart.Metadata { } } +func mapTargetNamespace(namespace string) string { + if len(namespace) == 0 { + return "default" + } + + return namespace +} + type CapabilitiesKubeVersion struct { Version string Minor string diff --git a/cyclops-ui/src/components/form/Metadata.tsx b/cyclops-ui/src/components/form/Metadata.tsx new file mode 100644 index 00000000..01ae5591 --- /dev/null +++ b/cyclops-ui/src/components/form/Metadata.tsx @@ -0,0 +1,62 @@ +import React, { useState } from "react"; +import { Col, Divider, Collapse, Input, Form } from "antd"; +import "./custom.css"; +import { isArray } from "node:util"; + +const ModuleMeta = () => { + const [collapseOpened, setCollapseOpened] = useState(false); + + const metadataCollapseColor = () => { + if (collapseOpened) { + return "#faca93"; + } else { + return "#fae8d4"; + } + }; + + return ( + + + + + Deploy to namespace +

+ All resources will be by default deployed to the selected + namespace +

+ + } + > + +
+
+
+ + ); +}; + +export default ModuleMeta; diff --git a/cyclops-ui/src/components/form/custom.css b/cyclops-ui/src/components/form/custom.css new file mode 100644 index 00000000..08e20665 --- /dev/null +++ b/cyclops-ui/src/components/form/custom.css @@ -0,0 +1,8 @@ +.ant-collapse-content-box { + padding-top: 12px; + border: 1px solid #d3d3d3; + border-top: none; + border-left: 3px solid #faca93; + border-bottom-left-radius: 7px; + border-bottom-right-radius: 7px; +} diff --git a/cyclops-ui/src/components/pages/ModuleDetails.tsx b/cyclops-ui/src/components/pages/ModuleDetails.tsx index bed4cf9f..457db41d 100644 --- a/cyclops-ui/src/components/pages/ModuleDetails.tsx +++ b/cyclops-ui/src/components/pages/ModuleDetails.tsx @@ -101,6 +101,7 @@ const { Title } = Typography; interface module { name: string; namespace: string; + targetNamespace: string; template: templateRef; iconURL: string; } @@ -136,6 +137,7 @@ const ModuleDetails = () => { const [module, setModule] = useState({ name: "", namespace: "", + targetNamespace: "", template: { repo: "", path: "", @@ -676,7 +678,7 @@ const ModuleDetails = () => { fontWeight: "550", }} > - {module.namespace} + {module.targetNamespace} diff --git a/cyclops-ui/src/components/pages/NewModule/NewModule.tsx b/cyclops-ui/src/components/pages/NewModule/NewModule.tsx index e32b6eec..1602b8af 100644 --- a/cyclops-ui/src/components/pages/NewModule/NewModule.tsx +++ b/cyclops-ui/src/components/pages/NewModule/NewModule.tsx @@ -31,6 +31,7 @@ import { } from "../../errors/FormValidationErrors"; import { mapResponseError } from "../../../utils/api/errors"; import TemplateFormFields from "../../form/TemplateFormFields"; +import ModuleMeta from "../../form/Metadata"; const { Title } = Typography; const layout = { @@ -193,12 +194,14 @@ const NewModule = () => { const handleSubmit = (values: any) => { const moduleName = values["cyclops_module_name"]; + const moduleNamespace = values["cyclops_module_namespace"]; values = findMaps(config.root.properties, values, initialValuesRaw); axios .post(`/api/modules/new`, { name: moduleName, + namespace: moduleNamespace, values: values, template: { repo: template.repo, @@ -498,7 +501,7 @@ const NewModule = () => { )} > - Module template + Template @@ -541,42 +544,74 @@ const NewModule = () => { - Module name + Metadata - - Module name -

- Enter a unique module name -

- - } - rules={[ - { - required: true, - message: "Module name is required", - }, - { - max: 63, - message: - "Module name must contain no more than 63 characters", - }, - { - pattern: /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/, // only alphanumeric characters and hyphens, cannot start or end with a hyphen and the alpha characters can only be lowercase - message: - "Module name must follow the Kubernetes naming convention", - }, - ]} - hasFeedback={true} - validateDebounce={1000} + +
+ + Module name +

+ Enter a unique module name +

+
+ } + rules={[ + { + required: true, + message: "Module name is required", + }, + { + max: 63, + message: + "Module name must contain no more than 63 characters", + }, + { + pattern: /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/, // only alphanumeric characters and hyphens, cannot start or end with a hyphen and the alpha characters can only be lowercase + message: + "Module name must follow the Kubernetes naming convention", + }, + ]} + hasFeedback={true} + validateDebounce={1000} + > + +
+ + Target namespace +

+ Namespace used to deploy resources to +

+ + } + hasFeedback={true} + validateDebounce={1000} + > + +
+ + + - - - - Define Module + Configure {renderFormFields()}
From 559799f03b092a00a15480607731165dccdeed9d Mon Sep 17 00:00:00 2001 From: petar-cvit Date: Thu, 29 Aug 2024 13:27:49 +0200 Subject: [PATCH 2/8] remove metadata --- cyclops-ui/src/components/form/Metadata.tsx | 62 ------------------- .../components/pages/NewModule/NewModule.tsx | 1 - 2 files changed, 63 deletions(-) delete mode 100644 cyclops-ui/src/components/form/Metadata.tsx diff --git a/cyclops-ui/src/components/form/Metadata.tsx b/cyclops-ui/src/components/form/Metadata.tsx deleted file mode 100644 index 01ae5591..00000000 --- a/cyclops-ui/src/components/form/Metadata.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import React, { useState } from "react"; -import { Col, Divider, Collapse, Input, Form } from "antd"; -import "./custom.css"; -import { isArray } from "node:util"; - -const ModuleMeta = () => { - const [collapseOpened, setCollapseOpened] = useState(false); - - const metadataCollapseColor = () => { - if (collapseOpened) { - return "#faca93"; - } else { - return "#fae8d4"; - } - }; - - return ( - - - - - Deploy to namespace -

- All resources will be by default deployed to the selected - namespace -

-
- } - > - - - - - - ); -}; - -export default ModuleMeta; diff --git a/cyclops-ui/src/components/pages/NewModule/NewModule.tsx b/cyclops-ui/src/components/pages/NewModule/NewModule.tsx index 1602b8af..79edca15 100644 --- a/cyclops-ui/src/components/pages/NewModule/NewModule.tsx +++ b/cyclops-ui/src/components/pages/NewModule/NewModule.tsx @@ -31,7 +31,6 @@ import { } from "../../errors/FormValidationErrors"; import { mapResponseError } from "../../../utils/api/errors"; import TemplateFormFields from "../../form/TemplateFormFields"; -import ModuleMeta from "../../form/Metadata"; const { Title } = Typography; const layout = { From aeef8783c950348a0b7a7f54ba1e1ab0ad94bab9 Mon Sep 17 00:00:00 2001 From: petar-cvit Date: Sun, 8 Sep 2024 14:00:21 +0200 Subject: [PATCH 3/8] resolve --- .../pkg/cluster/k8sclient/apiresources.go | 31 +++++++++++++++++++ cyclops-ctrl/pkg/cluster/k8sclient/client.go | 29 ++++++++++++++--- cyclops-ctrl/pkg/cluster/k8sclient/modules.go | 31 +++++++++++++++++-- 3 files changed, 84 insertions(+), 7 deletions(-) create mode 100644 cyclops-ctrl/pkg/cluster/k8sclient/apiresources.go diff --git a/cyclops-ctrl/pkg/cluster/k8sclient/apiresources.go b/cyclops-ctrl/pkg/cluster/k8sclient/apiresources.go new file mode 100644 index 00000000..b6dcc252 --- /dev/null +++ b/cyclops-ctrl/pkg/cluster/k8sclient/apiresources.go @@ -0,0 +1,31 @@ +package k8sclient + +import ( + "fmt" + "github.com/pkg/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type apiResources struct { + resourcesList []*metav1.APIResourceList +} + +func (a apiResources) isResourceNamespaced(gvk schema.GroupVersionKind) (bool, error) { + for _, resource := range a.resourcesList { + gv, err := schema.ParseGroupVersion(resource.GroupVersion) + if err != nil { + return false, err + } + + for _, apiResource := range resource.APIResources { + if apiResource.Kind == gvk.Kind && + gv.Group == gvk.Group && + gv.Version == gvk.Version { + return apiResource.Namespaced, nil + } + } + } + + return false, errors.New(fmt.Sprintf("group version kind not found: %v", gvk.String())) +} diff --git a/cyclops-ctrl/pkg/cluster/k8sclient/client.go b/cyclops-ctrl/pkg/cluster/k8sclient/client.go index d1589fdb..ee438758 100644 --- a/cyclops-ctrl/pkg/cluster/k8sclient/client.go +++ b/cyclops-ctrl/pkg/cluster/k8sclient/client.go @@ -419,18 +419,30 @@ func (k *KubernetesClient) Delete(resource dto.Resource) error { ) } -func (k *KubernetesClient) CreateDynamic(resource v1alpha1.GroupVersionResource, obj *unstructured.Unstructured) error { +func (k *KubernetesClient) CreateDynamic( + resource v1alpha1.GroupVersionResource, + obj *unstructured.Unstructured, + targetNamespace string, +) error { gvr := schema.GroupVersionResource{ Group: resource.Group, Version: resource.Version, Resource: resource.Resource, } - objNamespace := obj.GetNamespace() - if len(strings.TrimSpace(objNamespace)) == 0 { - objNamespace = apiv1.NamespaceDefault + var objNamespace string + + objNamespace = apiv1.NamespaceDefault + + if len(strings.TrimSpace(targetNamespace)) != 0 { + obj.SetNamespace(strings.TrimSpace(targetNamespace)) } + if len(strings.TrimSpace(obj.GetNamespace())) != 0 { + objNamespace = obj.GetNamespace() + } + obj.SetNamespace(objNamespace) + isNamespaced, err := k.isResourceNamespaced(obj.GroupVersionKind()) if err != nil { return err @@ -1009,6 +1021,15 @@ func (k *KubernetesClient) isResourceNamespaced(gvk schema.GroupVersionKind) (bo return false, errors.New(fmt.Sprintf("group version kind not found: %v", gvk.String())) } +func (k *KubernetesClient) clusterApiResources() (*apiResources, error) { + resourcesList, err := k.discovery.ServerPreferredResources() + if err != nil { + return nil, err + } + + return &apiResources{resourcesList: resourcesList}, nil +} + func (k *KubernetesClient) mapRole(group, version, kind, name, namespace string) (*dto.Role, error) { role, err := k.clientset.RbacV1().Roles(namespace).Get(context.Background(), name, metav1.GetOptions{}) diff --git a/cyclops-ctrl/pkg/cluster/k8sclient/modules.go b/cyclops-ctrl/pkg/cluster/k8sclient/modules.go index fbe19f1e..c42ed68b 100644 --- a/cyclops-ctrl/pkg/cluster/k8sclient/modules.go +++ b/cyclops-ctrl/pkg/cluster/k8sclient/modules.go @@ -152,9 +152,15 @@ func (k *KubernetesClient) getManagedGVRs(moduleName string) ([]schema.GroupVers func (k *KubernetesClient) GetDeletedResources( resources []dto.Resource, manifest string, + targetNamespace string, ) ([]dto.Resource, error) { resourcesFromTemplate := make(map[string][]dto.Resource, 0) + ar, err := k.clusterApiResources() + if err != nil { + return nil, err + } + for _, s := range strings.Split(manifest, "\n---\n") { s := strings.TrimSpace(s) if len(s) == 0 { @@ -165,13 +171,32 @@ func (k *KubernetesClient) GetDeletedResources( decoder := yaml2.NewYAMLOrJSONDecoder(strings.NewReader(s), len(s)) if err := decoder.Decode(&obj); err != nil { - panic(err) + return nil, err } objGVK := obj.GetObjectKind().GroupVersionKind().String() - resourcesFromTemplate[objGVK] = append(resourcesFromTemplate[objGVK], &dto.Service{ + + objNamespace := apiv1.NamespaceDefault + if len(strings.TrimSpace(targetNamespace)) != 0 { + obj.SetNamespace(strings.TrimSpace(targetNamespace)) + } + + if len(strings.TrimSpace(obj.GetNamespace())) != 0 { + objNamespace = obj.GetNamespace() + } + + namespaced, err := ar.isResourceNamespaced(obj.GroupVersionKind()) + if err != nil { + return nil, err + } + + if !namespaced { + objNamespace = "" + } + + resourcesFromTemplate[objGVK] = append(resourcesFromTemplate[objGVK], &dto.Other{ Name: obj.GetName(), - Namespace: obj.GetNamespace(), + Namespace: objNamespace, }) } From 441b0df35554dbc55f4ce06554094035bf9016aa Mon Sep 17 00:00:00 2001 From: petar-cvit Date: Sun, 8 Sep 2024 14:02:39 +0200 Subject: [PATCH 4/8] remove unused client --- .../cluster/k8sclient/apiresources.go | 31 - .../internal/cluster/k8sclient/client.go | 1100 ----------------- .../internal/cluster/k8sclient/modules.go | 807 ------------ .../cluster/k8sclient/templateauthrules.go | 28 - .../cluster/k8sclient/templatestore.go | 32 - 5 files changed, 1998 deletions(-) delete mode 100644 cyclops-ctrl/internal/cluster/k8sclient/apiresources.go delete mode 100644 cyclops-ctrl/internal/cluster/k8sclient/client.go delete mode 100644 cyclops-ctrl/internal/cluster/k8sclient/modules.go delete mode 100644 cyclops-ctrl/internal/cluster/k8sclient/templateauthrules.go delete mode 100644 cyclops-ctrl/internal/cluster/k8sclient/templatestore.go diff --git a/cyclops-ctrl/internal/cluster/k8sclient/apiresources.go b/cyclops-ctrl/internal/cluster/k8sclient/apiresources.go deleted file mode 100644 index b6dcc252..00000000 --- a/cyclops-ctrl/internal/cluster/k8sclient/apiresources.go +++ /dev/null @@ -1,31 +0,0 @@ -package k8sclient - -import ( - "fmt" - "github.com/pkg/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -type apiResources struct { - resourcesList []*metav1.APIResourceList -} - -func (a apiResources) isResourceNamespaced(gvk schema.GroupVersionKind) (bool, error) { - for _, resource := range a.resourcesList { - gv, err := schema.ParseGroupVersion(resource.GroupVersion) - if err != nil { - return false, err - } - - for _, apiResource := range resource.APIResources { - if apiResource.Kind == gvk.Kind && - gv.Group == gvk.Group && - gv.Version == gvk.Version { - return apiResource.Namespaced, nil - } - } - } - - return false, errors.New(fmt.Sprintf("group version kind not found: %v", gvk.String())) -} diff --git a/cyclops-ctrl/internal/cluster/k8sclient/client.go b/cyclops-ctrl/internal/cluster/k8sclient/client.go deleted file mode 100644 index ee438758..00000000 --- a/cyclops-ctrl/internal/cluster/k8sclient/client.go +++ /dev/null @@ -1,1100 +0,0 @@ -package k8sclient - -import ( - "bufio" - "bytes" - "context" - "errors" - "fmt" - "io" - networkingv1 "k8s.io/api/networking/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "os" - "os/exec" - "sort" - "strings" - "time" - - "github.com/cyclops-ui/cyclops/cyclops-ctrl/api/v1alpha1" - - "gopkg.in/yaml.v2" - - v12 "k8s.io/api/apps/v1" - v1 "k8s.io/api/autoscaling/v1" - apiv1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/version" - "k8s.io/client-go/discovery" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/kubernetes" - ctrl "sigs.k8s.io/controller-runtime" - - "github.com/cyclops-ui/cyclops/cyclops-ctrl/api/v1alpha1/client" - "github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/models/dto" -) - -const ( - kubectl = "kubectl" - cyclopsNamespace = "cyclops" -) - -type KubernetesClient struct { - Dynamic dynamic.Interface - - clientset *kubernetes.Clientset - - discovery *discovery.DiscoveryClient - - moduleset *client.CyclopsV1Alpha1Client -} - -func New() (*KubernetesClient, error) { - return createLocalClient() -} - -func createLocalClient() (*KubernetesClient, error) { - config := ctrl.GetConfigOrDie() - clientset, err := kubernetes.NewForConfig(config) - if err != nil { - return nil, err - } - - moduleSet, err := client.NewForConfig(config) - if err != nil { - panic(err) - } - - discovery := discovery.NewDiscoveryClientForConfigOrDie(config) - - dynamic, err := dynamic.NewForConfig(config) - if err != nil { - panic(err.Error()) - } - - return &KubernetesClient{ - Dynamic: dynamic, - discovery: discovery, - clientset: clientset, - moduleset: moduleSet, - }, nil -} - -func (k *KubernetesClient) VersionInfo() (*version.Info, error) { - return k.discovery.ServerVersion() -} - -func (k *KubernetesClient) GetDeployment(namespace, name string) (*v12.Deployment, error) { - deploymentClient := k.clientset.AppsV1().Deployments(namespace) - return deploymentClient.Get(context.TODO(), name, metav1.GetOptions{}) -} - -func (k *KubernetesClient) GetDeployments(namespace string) ([]v12.Deployment, error) { - deploymentClient := k.clientset.AppsV1().Deployments(namespace) - deploymentList, err := deploymentClient.List(context.TODO(), metav1.ListOptions{}) - if err != nil { - return nil, err - } - - return deploymentList.Items, err -} - -func (k *KubernetesClient) GetScale(namespace, name string) (*v1.Scale, error) { - deploymentClient := k.clientset.AppsV1().Deployments(namespace) - return deploymentClient.GetScale(context.TODO(), name, metav1.GetOptions{}) -} - -func (k *KubernetesClient) UpdateScale(namespace, name string, sc v1.Scale) error { - deploymentClient := k.clientset.AppsV1().Deployments(namespace) - _, err := deploymentClient.UpdateScale(context.TODO(), name, &sc, metav1.UpdateOptions{}) - return err -} - -func (k *KubernetesClient) Deploy(deploymentSpec *v12.Deployment) error { - namespace := deploymentSpec.Namespace - if len(namespace) == 0 { - namespace = apiv1.NamespaceDefault - } - deploymentClient := k.clientset.AppsV1().Deployments(namespace) - - _, err := deploymentClient.Get(context.TODO(), deploymentSpec.Name, metav1.GetOptions{}) - if err != nil { - if k8serrors.IsNotFound(err) { - _, err := deploymentClient.Create(context.TODO(), deploymentSpec, metav1.CreateOptions{}) - return err - } else { - return err - } - } else { - _, err := deploymentClient.Update(context.TODO(), deploymentSpec, metav1.UpdateOptions{}) - return err - } -} - -func (k *KubernetesClient) DeployService(service *apiv1.Service) error { - namespace := service.Namespace - if len(namespace) == 0 { - namespace = apiv1.NamespaceDefault - } - serviceClient := k.clientset.CoreV1().Services(namespace) - - _, err := serviceClient.Get(context.TODO(), service.Name, metav1.GetOptions{}) - if err != nil { - if k8serrors.IsNotFound(err) { - _, err := serviceClient.Create(context.TODO(), service, metav1.CreateOptions{}) - return err - } else { - return err - } - } else { - _, err := serviceClient.Update(context.TODO(), service, metav1.UpdateOptions{}) - return err - } -} - -func (k *KubernetesClient) GetPods(namespace, name string) ([]apiv1.Pod, error) { - podClient := k.clientset.CoreV1().Pods(namespace) - podList, err := podClient.List(context.TODO(), metav1.ListOptions{ - LabelSelector: fmt.Sprintf("app=%v", name), - }) - - if err != nil { - return nil, err - } - - return podList.Items, err -} - -func (k *KubernetesClient) GetPodLogs(namespace, container, name string, numLogs *int64) ([]string, error) { - podLogOptions := apiv1.PodLogOptions{ - Container: container, - TailLines: numLogs, - Timestamps: true, - } - podClient := k.clientset.CoreV1().Pods(namespace).GetLogs(name, &podLogOptions) - stream, err := podClient.Stream(context.Background()) - if err != nil { - return nil, err - } - - defer func(stream io.ReadCloser) { - err := stream.Close() - if err != nil { - return - } - }(stream) - - var logs []string - scanner := bufio.NewScanner(stream) - for scanner.Scan() { - logs = append(logs, scanner.Text()) - } - if err := scanner.Err(); err != nil { - return nil, err - } - - return logs, nil -} - -func (k *KubernetesClient) GetDeploymentLogs(namespace, container, deployment string, numLogs *int64) ([]string, error) { - deploymentClient := k.clientset.AppsV1().Deployments(namespace) - deploymentObj, err := deploymentClient.Get(context.Background(), deployment, metav1.GetOptions{}) - if err != nil { - return nil, err - } - - pods, err := k.clientset.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{ - LabelSelector: labels.Set(deploymentObj.Spec.Selector.MatchLabels).String(), - }) - if err != nil { - return nil, err - } - - var logs []string - for _, pod := range pods.Items { - podLogs, err := k.GetPodLogs(namespace, container, pod.Name, numLogs) - if err != nil { - return nil, err - } - logs = append(logs, podLogs...) - } - sort.Strings(logs) - return logs, nil -} - -func (k *KubernetesClient) GetStatefulSetsLogs(namespace, container, name string, numLogs *int64) ([]string, error) { - statefulsetClient := k.clientset.AppsV1().StatefulSets(namespace) - statefulsetObj, err := statefulsetClient.Get(context.Background(), name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - pods, err := k.clientset.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{ - LabelSelector: labels.Set(statefulsetObj.Spec.Selector.MatchLabels).String(), - }) - if err != nil { - return nil, err - } - - var logs []string - for _, pod := range pods.Items { - podLogs, err := k.GetPodLogs(namespace, container, pod.Name, numLogs) - if err != nil { - return nil, err - } - logs = append(logs, podLogs...) - } - sort.Strings(logs) - return logs, nil -} - -func (k *KubernetesClient) RestartDeployment(name, namespace string) error { - deployment, err := k.clientset.AppsV1().Deployments(namespace).Get(context.Background(), name, metav1.GetOptions{}) - if err != nil { - return err - } - - if deployment.Spec.Template.ObjectMeta.Annotations == nil { - deployment.Spec.Template.ObjectMeta.Annotations = make(map[string]string) - } - deployment.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339) - - _, err = k.clientset.AppsV1().Deployments(namespace).Update(context.Background(), deployment, metav1.UpdateOptions{}) - return err -} - -func (k *KubernetesClient) RestartStatefulSet(name, namespace string) error { - statefulset, err := k.clientset.AppsV1().StatefulSets(namespace).Get(context.Background(), name, metav1.GetOptions{}) - if err != nil { - return err - } - - if statefulset.Spec.Template.ObjectMeta.Annotations == nil { - statefulset.Spec.Template.ObjectMeta.Annotations = make(map[string]string) - } - statefulset.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339) - - _, err = k.clientset.AppsV1().StatefulSets(namespace).Update(context.Background(), statefulset, metav1.UpdateOptions{}) - return err -} - -func (k *KubernetesClient) RestartDaemonSet(name, namespace string) error { - daemonset, err := k.clientset.AppsV1().DaemonSets(namespace).Get(context.Background(), name, metav1.GetOptions{}) - if err != nil { - return err - } - - if daemonset.Spec.Template.ObjectMeta.Annotations == nil { - daemonset.Spec.Template.ObjectMeta.Annotations = make(map[string]string) - } - daemonset.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339) - - _, err = k.clientset.AppsV1().DaemonSets(namespace).Update(context.Background(), daemonset, metav1.UpdateOptions{}) - return err -} - -func (k *KubernetesClient) GetManifest(group, version, kind, name, namespace string, includeManagedFields bool) (string, error) { - apiResourceName, err := k.GVKtoAPIResourceName(schema.GroupVersion{Group: group, Version: version}, kind) - if err != nil { - return "", err - } - - resource, err := k.Dynamic.Resource(schema.GroupVersionResource{ - Group: group, - Version: version, - Resource: apiResourceName, - }).Namespace(namespace).Get(context.Background(), name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - if !includeManagedFields { - resource.SetManagedFields(nil) - } - - data, err := yaml.Marshal(resource.Object) - if err != nil { - return "", err - } - - return string(data), nil -} - -func (k *KubernetesClient) Restart(group, version, kind, name, namespace string) error { - switch { - case isDeployment(group, version, kind): - return k.RestartDeployment(name, namespace) - case isDaemonSet(group, version, kind): - return k.RestartDaemonSet(name, namespace) - case isStatefulSet(group, version, kind): - return k.RestartStatefulSet(name, namespace) - } - - return errors.New(fmt.Sprintf("cannot restart: %v/%v %v %v/%v", group, version, kind, namespace, name)) -} - -func (k *KubernetesClient) GetResource(group, version, kind, name, namespace string) (any, error) { - switch { - case isDeployment(group, version, kind): - return k.mapDeployment(group, version, kind, name, namespace) - case isDaemonSet(group, version, kind): - return k.mapDaemonSet(group, version, kind, name, namespace) - case isService(group, version, kind): - return k.mapService(group, version, kind, name, namespace) - case isStatefulSet(group, version, kind): - return k.mapStatefulSet(group, version, kind, name, namespace) - case isPod(group, version, kind): - return k.mapPod(group, version, kind, name, namespace) - case isConfigMap(group, version, kind): - return k.mapConfigMap(group, version, kind, name, namespace) - case isPersistentVolume(group, version, kind): - return k.mapPersistentVolumes(group, version, kind, name, namespace) - case isPersistentVolumeClaims(group, version, kind): - return k.mapPersistentVolumeClaims(group, version, kind, name, namespace) - case isSecret(group, version, kind): - return k.mapSecret(group, version, kind, name, namespace) - case isCronJob(group, version, kind): - return k.mapCronJob(group, version, kind, name, namespace) - case isJob(group, version, kind): - return k.mapJob(group, version, kind, name, namespace) - case isRole(group, version, kind): - return k.mapRole(group, version, kind, name, namespace) - case isNetworkPolicy(group, version, kind): - return k.mapNetworkPolicy(group, version, kind, name, namespace) - } - - return nil, nil -} - -func (k *KubernetesClient) GetAllNamespacePods() ([]apiv1.Pod, error) { - podClient := k.clientset.CoreV1().Pods(apiv1.NamespaceDefault) - podList, err := podClient.List(context.TODO(), metav1.ListOptions{}) - if err != nil { - return nil, err - } - - return podList.Items, nil -} - -func (k *KubernetesClient) GetNamespaces() ([]apiv1.Namespace, error) { - namespaceClient := k.clientset.CoreV1().Namespaces() - namespaces, err := namespaceClient.List(context.TODO(), metav1.ListOptions{}) - if err != nil { - return nil, err - } - - return namespaces.Items, err -} - -func (k *KubernetesClient) GetDeploymentsYaml(name string, namespace string) (*bytes.Buffer, error) { - buff := new(bytes.Buffer) - command := exec.Command(kubectl, "get", "deployments", name, "-n", namespace, "-o", "yaml") - command.Stdout = buff - command.Stderr = os.Stderr - return buff, command.Run() -} - -func (k *KubernetesClient) Delete(resource dto.Resource) error { - apiResourceName, err := k.GVKtoAPIResourceName( - schema.GroupVersion{ - Group: resource.GetGroup(), - Version: resource.GetVersion(), - }, resource.GetKind()) - if err != nil { - return err - } - - gvr := schema.GroupVersionResource{ - Group: resource.GetGroup(), - Version: resource.GetVersion(), - Resource: apiResourceName, - } - - return k.Dynamic.Resource(gvr).Namespace(resource.GetNamespace()).Delete( - context.Background(), - resource.GetName(), - metav1.DeleteOptions{}, - ) -} - -func (k *KubernetesClient) CreateDynamic( - resource v1alpha1.GroupVersionResource, - obj *unstructured.Unstructured, - targetNamespace string, -) error { - gvr := schema.GroupVersionResource{ - Group: resource.Group, - Version: resource.Version, - Resource: resource.Resource, - } - - var objNamespace string - - objNamespace = apiv1.NamespaceDefault - - if len(strings.TrimSpace(targetNamespace)) != 0 { - obj.SetNamespace(strings.TrimSpace(targetNamespace)) - } - - if len(strings.TrimSpace(obj.GetNamespace())) != 0 { - objNamespace = obj.GetNamespace() - } - obj.SetNamespace(objNamespace) - - isNamespaced, err := k.isResourceNamespaced(obj.GroupVersionKind()) - if err != nil { - return err - } - - if !isNamespaced { - return k.createDynamicNonNamespaced(gvr, obj) - } - - return k.createDynamicNamespaced(gvr, objNamespace, obj) -} - -func (k *KubernetesClient) createDynamicNamespaced( - gvr schema.GroupVersionResource, - namespace string, - obj *unstructured.Unstructured, -) error { - current, err := k.Dynamic.Resource(gvr).Namespace(namespace).Get(context.TODO(), obj.GetName(), metav1.GetOptions{}) - if err != nil { - if k8serrors.IsNotFound(err) { - _, err := k.Dynamic.Resource(gvr).Namespace(namespace).Create( - context.Background(), - obj, - metav1.CreateOptions{}, - ) - - return err - } - return err - } - - if isJob(obj.GroupVersionKind().Group, obj.GroupVersionKind().Version, obj.GroupVersionKind().Kind) { - if err := copyJobSelectors(current, obj); err != nil { - return err - } - } - - if isPersistentVolumeClaims(obj.GroupVersionKind().Group, obj.GroupVersionKind().Version, obj.GroupVersionKind().Kind) { - if err := mergePVCWithCurrent(current, obj); err != nil { - return err - } - } - - obj.SetResourceVersion(current.GetResourceVersion()) - - _, err = k.Dynamic.Resource(gvr).Namespace(namespace).Update( - context.Background(), - obj, - metav1.UpdateOptions{}, - ) - - return err -} - -func (k *KubernetesClient) createDynamicNonNamespaced( - gvr schema.GroupVersionResource, - obj *unstructured.Unstructured, -) error { - current, err := k.Dynamic.Resource(gvr).Get(context.TODO(), obj.GetName(), metav1.GetOptions{}) - if err != nil { - if k8serrors.IsNotFound(err) { - _, err := k.Dynamic.Resource(gvr).Create( - context.Background(), - obj, - metav1.CreateOptions{}, - ) - - return err - } - return err - } - - obj.SetResourceVersion(current.GetResourceVersion()) - - _, err = k.Dynamic.Resource(gvr).Update( - context.Background(), - obj, - metav1.UpdateOptions{}, - ) - - return err -} - -func (k *KubernetesClient) ApplyCRD(obj *unstructured.Unstructured) error { - gvr := schema.GroupVersionResource{ - Group: "apiextensions.k8s.io", - Version: "v1", - Resource: "customresourcedefinitions", - } - - _, err := k.Dynamic.Resource(gvr).Apply( - context.Background(), - obj.GetName(), - obj, - metav1.ApplyOptions{ - FieldManager: "cyclops-ctrl", - }, - ) - - return err -} - -func copyJobSelectors(source, destination *unstructured.Unstructured) error { - selectors, ok, err := unstructured.NestedMap(source.Object, "spec", "selector") - if err != nil { - return err - } - if !ok { - return errors.New(fmt.Sprintf("job %v selectors not found", source.GetName())) - } - - templateLabels, ok, err := unstructured.NestedMap(source.Object, "spec", "template", "metadata", "labels") - if err != nil { - return err - } - if !ok { - return errors.New(fmt.Sprintf("job %v selectors not found", source.GetName())) - } - - if err := unstructured.SetNestedMap(destination.Object, selectors, "spec", "selector"); err != nil { - return err - } - - return unstructured.SetNestedMap(destination.Object, templateLabels, "spec", "template", "metadata", "labels") -} - -func mergePVCWithCurrent(current, obj *unstructured.Unstructured) error { - requests, ok, err := unstructured.NestedMap(obj.Object, "spec", "resources", "requests") - if err != nil { - return err - } - if !ok { - return fmt.Errorf("PVC %v spec.resources.requests not found", obj.GetName()) - } - - for key, value := range current.Object { - obj.Object[key] = value - } - - return unstructured.SetNestedMap(current.Object, requests, "spec", "resources", "requests") -} - -func (k *KubernetesClient) ListNodes() ([]apiv1.Node, error) { - nodeList, err := k.clientset.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{}) - return nodeList.Items, err -} - -func (k *KubernetesClient) GetNode(name string) (*apiv1.Node, error) { - return k.clientset.CoreV1().Nodes().Get(context.TODO(), name, metav1.GetOptions{}) -} - -func (k *KubernetesClient) GetPodsForNode(nodeName string) ([]apiv1.Pod, error) { - podList, err := k.clientset.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{ - FieldSelector: "spec.nodeName=" + nodeName, - }) - return podList.Items, err -} - -func (k *KubernetesClient) mapDeployment(group, version, kind, name, namespace string) (*dto.Deployment, error) { - deployment, err := k.clientset.AppsV1().Deployments(namespace).Get(context.Background(), name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - - pods, err := k.getPods(*deployment) - if err != nil { - return nil, err - } - - return &dto.Deployment{ - Group: group, - Version: version, - Kind: kind, - Name: deployment.Name, - Namespace: deployment.Namespace, - Replicas: int(*deployment.Spec.Replicas), - Pods: pods, - Status: getDeploymentStatus(pods), - }, nil -} - -func (k *KubernetesClient) mapDaemonSet(group, version, kind, name, namespace string) (*dto.DaemonSet, error) { - daemonSet, err := k.clientset.AppsV1().DaemonSets(namespace).Get(context.Background(), name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - - pods, err := k.getPodsForDaemonSet(*daemonSet) - if err != nil { - return nil, err - } - - return &dto.DaemonSet{ - Group: group, - Version: version, - Kind: kind, - Name: daemonSet.Name, - Namespace: daemonSet.Namespace, - Pods: pods, - Status: getDaemonSetStatus(pods), - }, nil -} - -func (k *KubernetesClient) mapStatefulSet(group, version, kind, name, namespace string) (*dto.StatefulSet, error) { - statefulset, err := k.clientset.AppsV1().StatefulSets(namespace).Get(context.Background(), name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - - pods, err := k.getStatefulsetPods(*statefulset) - if err != nil { - return nil, err - } - - return &dto.StatefulSet{ - Group: group, - Version: version, - Kind: kind, - Name: name, - Namespace: namespace, - Replicas: int(*statefulset.Spec.Replicas), - Pods: pods, - Status: getDeploymentStatus(pods), - }, nil -} - -func (k *KubernetesClient) mapPod(group, version, kind, name, namespace string) (*dto.Pod, error) { - item, err := k.clientset.CoreV1().Pods(namespace).Get(context.Background(), name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - - containers := make([]dto.Container, 0, len(item.Spec.Containers)) - - for _, cnt := range item.Spec.Containers { - env := make(map[string]string) - for _, envVar := range cnt.Env { - env[envVar.Name] = envVar.Value - } - - var status apiv1.ContainerStatus - for _, c := range item.Status.ContainerStatuses { - if c.Name == cnt.Name { - status = c - break - } - } - - containers = append(containers, dto.Container{ - Name: cnt.Name, - Image: cnt.Image, - Env: env, - Status: containerStatus(status), - }) - } - - initContainers := make([]dto.Container, 0, len(item.Spec.InitContainers)) - for _, cnt := range item.Spec.InitContainers { - env := make(map[string]string) - for _, envVar := range cnt.Env { - env[envVar.Name] = envVar.Value - } - - var status apiv1.ContainerStatus - for _, c := range item.Status.ContainerStatuses { - if c.Name == cnt.Name { - status = c - break - } - } - - initContainers = append(initContainers, dto.Container{ - Name: cnt.Name, - Image: cnt.Image, - Env: env, - Status: containerStatus(status), - }) - } - - return &dto.Pod{ - Group: group, - Version: version, - Kind: kind, - Name: name, - Namespace: namespace, - Containers: containers, - InitContainers: initContainers, - Node: item.Spec.NodeName, - PodPhase: string(item.Status.Phase), - Status: getPodStatus(containers), - Started: item.Status.StartTime, - Deleted: false, - }, nil -} - -func (k *KubernetesClient) mapService(group, version, kind, name, namespace string) (*dto.Service, error) { - service, err := k.clientset.CoreV1().Services(namespace).Get(context.Background(), name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - - return &dto.Service{ - Group: group, - Version: version, - Kind: kind, - Name: name, - Namespace: namespace, - Ports: service.Spec.Ports, - }, nil -} - -func (k *KubernetesClient) mapConfigMap(group, version, kind, name, namespace string) (*dto.ConfigMap, error) { - configmap, err := k.clientset.CoreV1().ConfigMaps(namespace).Get(context.Background(), name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - - return &dto.ConfigMap{ - Group: group, - Version: version, - Kind: kind, - Name: name, - Namespace: namespace, - Data: configmap.Data, - }, nil -} - -func (k *KubernetesClient) mapPersistentVolumeClaims(group, version, kind, name, namespace string) (*dto.PersistentVolumeClaim, error) { - persistentvolumeclaim, err := k.clientset.CoreV1().PersistentVolumeClaims(namespace).Get(context.Background(), name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - - storage := "" - if persistentvolumeclaim.Spec.Resources.Requests != nil && persistentvolumeclaim.Spec.Resources.Requests.Storage() != nil { - storage = persistentvolumeclaim.Spec.Resources.Requests.Storage().String() - } - - return &dto.PersistentVolumeClaim{ - Group: group, - Version: version, - Kind: kind, - Name: name, - Namespace: namespace, - AccessModes: persistentvolumeclaim.Spec.AccessModes, - Size: storage, - }, nil -} - -func (k *KubernetesClient) mapPersistentVolumes(group, version, kind, name, namespace string) (*dto.PersistentVolume, error) { - persistentVolume, err := k.clientset.CoreV1().PersistentVolumes().Get(context.Background(), name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - - capacity := "" - if persistentVolume.Spec.Capacity != nil && persistentVolume.Spec.Capacity.Storage() != nil { - capacity = persistentVolume.Spec.Capacity.Storage().String() - } - - claimRef := "" - if persistentVolume.Spec.ClaimRef != nil && persistentVolume.Spec.ClaimRef.Name != "" { - claimRef = persistentVolume.Spec.ClaimRef.Name - } - - return &dto.PersistentVolume{ - Group: group, - Version: version, - Kind: kind, - Name: name, - Namespace: namespace, - AccessModes: persistentVolume.Spec.AccessModes, - PersistentVolumeClaim: claimRef, - Capacity: capacity, - ReclaimPolicy: persistentVolume.Spec.PersistentVolumeReclaimPolicy, - StorageClass: persistentVolume.Spec.StorageClassName, - Status: persistentVolume.Status, - }, nil -} - -func (k *KubernetesClient) mapSecret(group, version, kind, name, namespace string) (*dto.Secret, error) { - secret, err := k.clientset.CoreV1().Secrets(namespace).Get(context.Background(), name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - - dataKeys := make([]string, 0, len(secret.Data)) - for key := range secret.Data { - dataKeys = append(dataKeys, key) - } - - return &dto.Secret{ - Group: group, - Version: version, - Kind: kind, - Name: name, - Namespace: namespace, - DataKeys: dataKeys, - Type: string(secret.Type), - }, nil -} - -func (k *KubernetesClient) mapCronJob(group, version, kind, name, namespace string) (*dto.CronJob, error) { - cronJob, err := k.clientset.BatchV1().CronJobs(namespace).Get(context.Background(), name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - pods, err := k.getPodsForCronJob(*cronJob) - if err != nil { - return nil, err - } - - status := dto.StatusCronJob{ - LastScheduleTime: cronJob.Status.LastScheduleTime, - LastSuccessfulTime: cronJob.Status.LastSuccessfulTime, - } - - return &dto.CronJob{ - Group: group, - Version: version, - Kind: kind, - Name: cronJob.Name, - Namespace: cronJob.Namespace, - Schedule: cronJob.Spec.Schedule, - Status: status, - Pods: pods, - }, nil -} - -func (k *KubernetesClient) mapJob(group, version, kind, name, namespace string) (*dto.Job, error) { - job, err := k.clientset.BatchV1().Jobs(namespace).Get(context.Background(), name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - pods, err := k.getPodsForJob(*job) - if err != nil { - return nil, err - } - - startTime := "" - if job.Status.StartTime != nil { - startTime = job.Status.StartTime.String() - } - - completionTime := "" - if job.Status.CompletionTime != nil { - completionTime = job.Status.CompletionTime.String() - } - - return &dto.Job{ - Group: group, - Version: version, - Kind: kind, - Name: job.Name, - Namespace: job.Namespace, - CompletionTime: completionTime, - StartTime: startTime, - Pods: pods, - }, nil -} - -func (k *KubernetesClient) mapNetworkPolicy(group, version, kind, name, namespace string) (*dto.NetworkPolicy, error) { - networkPolicy, err := k.clientset.NetworkingV1().NetworkPolicies(namespace).Get(context.Background(), name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - pods, err := k.getPodsForNetworkPolicy(*networkPolicy) - if err != nil { - return nil, err - } - - mappedPolicy := &dto.NetworkPolicy{ - Group: group, - Version: version, - Kind: kind, - Name: networkPolicy.Name, - Namespace: networkPolicy.Namespace, - Pods: pods, - Ingress: mapNetworkPolicyIngressRules(networkPolicy.Spec.Ingress), - Egress: mapNetworkPolicyEgressRules(networkPolicy.Spec.Egress), - } - - return mappedPolicy, nil -} - -func mapNetworkPolicyIngressRules(rules []networkingv1.NetworkPolicyIngressRule) []dto.NetworkPolicyIngressRule { - mapped := make([]dto.NetworkPolicyIngressRule, len(rules)) - for i, rule := range rules { - mapped[i] = dto.NetworkPolicyIngressRule{ - Ports: mapNetworkPolicyPorts(rule.Ports), - From: mapNetworkPolicyPeers(rule.From), - } - } - return mapped -} - -func mapNetworkPolicyEgressRules(rules []networkingv1.NetworkPolicyEgressRule) []dto.NetworkPolicyEgressRule { - mapped := make([]dto.NetworkPolicyEgressRule, len(rules)) - for i, rule := range rules { - mapped[i] = dto.NetworkPolicyEgressRule{ - Ports: mapNetworkPolicyPorts(rule.Ports), - To: mapNetworkPolicyPeers(rule.To), - } - } - return mapped -} - -func mapNetworkPolicyPorts(ports []networkingv1.NetworkPolicyPort) []dto.NetworkPolicyPort { - mapped := make([]dto.NetworkPolicyPort, len(ports)) - for i, port := range ports { - protocol := "" - if port.Protocol != nil { - protocol = string(*port.Protocol) - } - - portValue := intstr.IntOrString{} - if port.Port != nil { - portValue = *port.Port - } - - var endPort int32 - if port.EndPort != nil { - endPort = *port.EndPort - } - - mapped[i] = dto.NetworkPolicyPort{ - Protocol: protocol, - Port: portValue, - EndPort: endPort, - } - } - return mapped -} - -func mapNetworkPolicyPeers(peers []networkingv1.NetworkPolicyPeer) []dto.NetworkPolicyPeer { - mapped := make([]dto.NetworkPolicyPeer, len(peers)) - for i, peer := range peers { - mapped[i] = dto.NetworkPolicyPeer{ - IPBlock: mapIPBlock(peer.IPBlock), - } - } - return mapped -} - -func mapIPBlock(block *networkingv1.IPBlock) *dto.IPBlock { - if block == nil { - return nil - } - return &dto.IPBlock{ - CIDR: block.CIDR, - Except: block.Except, - } -} - -func (k *KubernetesClient) isResourceNamespaced(gvk schema.GroupVersionKind) (bool, error) { - resourcesList, err := k.discovery.ServerPreferredResources() - if err != nil { - return false, err - } - - for _, resource := range resourcesList { - gv, err := schema.ParseGroupVersion(resource.GroupVersion) - if err != nil { - return false, err - } - - for _, apiResource := range resource.APIResources { - if apiResource.Kind == gvk.Kind && - gv.Group == gvk.Group && - gv.Version == gvk.Version { - return apiResource.Namespaced, nil - } - } - } - - return false, errors.New(fmt.Sprintf("group version kind not found: %v", gvk.String())) -} - -func (k *KubernetesClient) clusterApiResources() (*apiResources, error) { - resourcesList, err := k.discovery.ServerPreferredResources() - if err != nil { - return nil, err - } - - return &apiResources{resourcesList: resourcesList}, nil -} - -func (k *KubernetesClient) mapRole(group, version, kind, name, namespace string) (*dto.Role, error) { - role, err := k.clientset.RbacV1().Roles(namespace).Get(context.Background(), name, metav1.GetOptions{}) - - if err != nil { - return nil, err - } - - return &dto.Role{ - Group: group, - Version: version, - Kind: kind, - Name: role.Name, - Namespace: namespace, - Rules: role.Rules, - }, nil -} - -func isDeployment(group, version, kind string) bool { - return group == "apps" && version == "v1" && kind == "Deployment" -} - -func isDaemonSet(group, version, kind string) bool { - return group == "apps" && version == "v1" && kind == "DaemonSet" -} - -func isStatefulSet(group, version, kind string) bool { - return group == "apps" && version == "v1" && kind == "StatefulSet" -} - -func isPod(group, version, kind string) bool { - return group == "" && version == "v1" && kind == "Pod" -} - -func isJob(group, version, kind string) bool { - return group == "batch" && version == "v1" && kind == "Job" -} - -func isService(group, version, kind string) bool { - return group == "" && version == "v1" && kind == "Service" -} - -func isConfigMap(group, version, kind string) bool { - return group == "" && version == "v1" && kind == "ConfigMap" -} - -func isPersistentVolumeClaims(group, version, kind string) bool { - return group == "" && version == "v1" && kind == "PersistentVolumeClaim" -} - -func isPersistentVolume(group, version, kind string) bool { - return group == "" && version == "v1" && kind == "PersistentVolume" -} - -func isSecret(group, version, kind string) bool { - return group == "" && version == "v1" && kind == "Secret" -} - -func isCronJob(group, version, kind string) bool { - return group == "batch" && version == "v1" && kind == "CronJob" -} - -func isRole(group, version, kind string) bool { - return group == "rbac.authorization.k8s.io" && version == "v1" && kind == "Role" -} - -func isNetworkPolicy(group, version, kind string) bool { - return group == "networking.k8s.io" && version == "v1" && kind == "NetworkPolicy" -} diff --git a/cyclops-ctrl/internal/cluster/k8sclient/modules.go b/cyclops-ctrl/internal/cluster/k8sclient/modules.go deleted file mode 100644 index c42ed68b..00000000 --- a/cyclops-ctrl/internal/cluster/k8sclient/modules.go +++ /dev/null @@ -1,807 +0,0 @@ -package k8sclient - -import ( - "context" - "sort" - "strings" - - "github.com/pkg/errors" - - appsv1 "k8s.io/api/apps/v1" - batchv1 "k8s.io/api/batch/v1" - apiv1 "k8s.io/api/core/v1" - networkingv1 "k8s.io/api/networking/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime/schema" - yaml2 "k8s.io/apimachinery/pkg/util/yaml" - - cyclopsv1alpha1 "github.com/cyclops-ui/cyclops/cyclops-ctrl/api/v1alpha1" - "github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/models/dto" -) - -const ( - statusUndefined = "undefined" - statusHealthy = "healthy" - statusUnhealthy = "unhealthy" -) - -func (k *KubernetesClient) ListModules() ([]cyclopsv1alpha1.Module, error) { - moduleList, err := k.moduleset.Modules(cyclopsNamespace).List(metav1.ListOptions{}) - return moduleList, err -} - -func (k *KubernetesClient) CreateModule(module cyclopsv1alpha1.Module) error { - _, err := k.moduleset.Modules(cyclopsNamespace).Create(&module) - return err -} - -func (k *KubernetesClient) UpdateModule(module *cyclopsv1alpha1.Module) error { - _, err := k.moduleset.Modules(cyclopsNamespace).Update(module) - return err -} - -func (k *KubernetesClient) UpdateModuleStatus(module *cyclopsv1alpha1.Module) (*cyclopsv1alpha1.Module, error) { - return k.moduleset.Modules(cyclopsNamespace).PatchStatus(module) -} - -func (k *KubernetesClient) DeleteModule(name string) error { - return k.moduleset.Modules(cyclopsNamespace).Delete(name) -} - -func (k *KubernetesClient) GetModule(name string) (*cyclopsv1alpha1.Module, error) { - return k.moduleset.Modules(cyclopsNamespace).Get(name) -} - -func (k *KubernetesClient) GetResourcesForModule(name string) ([]dto.Resource, error) { - out := make([]dto.Resource, 0, 0) - - managedGVRs, err := k.getManagedGVRs(name) - if err != nil { - return nil, err - } - - other := make([]unstructured.Unstructured, 0) - for _, gvr := range managedGVRs { - rs, err := k.Dynamic.Resource(gvr).List(context.Background(), metav1.ListOptions{ - LabelSelector: "cyclops.module=" + name, - }) - if err != nil { - continue - } - - for _, item := range rs.Items { - other = append(other, item) - } - } - - for _, o := range other { - status, err := k.getResourceStatus(o) - if err != nil { - return nil, err - } - - out = append(out, &dto.Other{ - Group: o.GroupVersionKind().Group, - Version: o.GroupVersionKind().Version, - Kind: o.GroupVersionKind().Kind, - Name: o.GetName(), - Namespace: o.GetNamespace(), - Status: status, - Deleted: false, - }) - } - - sort.Slice(out, func(i, j int) bool { - if out[i].GetGroupVersionKind() != out[j].GetGroupVersionKind() { - return out[i].GetGroupVersionKind() < out[j].GetGroupVersionKind() - } - - return out[i].GetName() < out[j].GetName() - }) - - return out, nil -} - -func (k *KubernetesClient) getManagedGVRs(moduleName string) ([]schema.GroupVersionResource, error) { - module, _ := k.GetModule(moduleName) - - if module != nil && len(module.Status.ManagedGVRs) != 0 { - existing := make([]schema.GroupVersionResource, 0, len(module.Status.ManagedGVRs)) - for _, r := range module.Status.ManagedGVRs { - existing = append(existing, schema.GroupVersionResource{ - Group: r.Group, - Version: r.Version, - Resource: r.Resource, - }) - } - - return existing, nil - } - - apiResources, err := k.clientset.Discovery().ServerPreferredResources() - if err != nil { - return nil, err - } - - gvrs := make([]schema.GroupVersionResource, 0) - for _, resource := range apiResources { - gvk, err := schema.ParseGroupVersion(resource.GroupVersion) - if err != nil { - continue - } - - for _, apiResource := range resource.APIResources { - if gvk.Group == "discovery.k8s.io" && gvk.Version == "v1" && apiResource.Kind == "EndpointSlice" || - gvk.Group == "" && gvk.Version == "v1" && apiResource.Kind == "Endpoints" { - continue - } - - gvrs = append(gvrs, schema.GroupVersionResource{ - Group: gvk.Group, - Version: gvk.Version, - Resource: apiResource.Name, - }) - } - } - - return gvrs, nil -} - -func (k *KubernetesClient) GetDeletedResources( - resources []dto.Resource, - manifest string, - targetNamespace string, -) ([]dto.Resource, error) { - resourcesFromTemplate := make(map[string][]dto.Resource, 0) - - ar, err := k.clusterApiResources() - if err != nil { - return nil, err - } - - for _, s := range strings.Split(manifest, "\n---\n") { - s := strings.TrimSpace(s) - if len(s) == 0 { - continue - } - - var obj unstructured.Unstructured - - decoder := yaml2.NewYAMLOrJSONDecoder(strings.NewReader(s), len(s)) - if err := decoder.Decode(&obj); err != nil { - return nil, err - } - - objGVK := obj.GetObjectKind().GroupVersionKind().String() - - objNamespace := apiv1.NamespaceDefault - if len(strings.TrimSpace(targetNamespace)) != 0 { - obj.SetNamespace(strings.TrimSpace(targetNamespace)) - } - - if len(strings.TrimSpace(obj.GetNamespace())) != 0 { - objNamespace = obj.GetNamespace() - } - - namespaced, err := ar.isResourceNamespaced(obj.GroupVersionKind()) - if err != nil { - return nil, err - } - - if !namespaced { - objNamespace = "" - } - - resourcesFromTemplate[objGVK] = append(resourcesFromTemplate[objGVK], &dto.Other{ - Name: obj.GetName(), - Namespace: objNamespace, - }) - } - - out := make([]dto.Resource, 0, len(resources)) - for _, resource := range resources { - gvk := resource.GetGroupVersionKind() - - if _, ok := resourcesFromTemplate[gvk]; !ok { - resource.SetDeleted(true) - out = append(out, resource) - continue - } - - found := false - for _, rs := range resourcesFromTemplate[gvk] { - if resource.GetName() == rs.GetName() && (resource.GetNamespace() == rs.GetNamespace() || rs.GetNamespace() == "") { - found = true - break - } - } - - if found == false { - resource.SetDeleted(true) - } - - out = append(out, resource) - } - - return out, nil -} - -func (k *KubernetesClient) GetModuleResourcesHealth(name string) (string, error) { - resourcesWithHealth := 0 - - deployments, err := k.clientset.AppsV1().Deployments("").List(context.Background(), metav1.ListOptions{ - LabelSelector: "cyclops.module=" + name, - }) - if err != nil { - return statusUndefined, err - } - - resourcesWithHealth += len(deployments.Items) - for _, item := range deployments.Items { - if item.Generation != item.Status.ObservedGeneration || - item.Status.Replicas != item.Status.UpdatedReplicas || - item.Status.UnavailableReplicas != 0 { - return statusUnhealthy, nil - } - } - - statefulsets, err := k.clientset.AppsV1().StatefulSets("").List(context.Background(), metav1.ListOptions{ - LabelSelector: "cyclops.module=" + name, - }) - if err != nil { - return statusUndefined, err - } - - resourcesWithHealth += len(statefulsets.Items) - for _, item := range statefulsets.Items { - if item.Generation != item.Status.ObservedGeneration || - item.Status.Replicas != item.Status.UpdatedReplicas || - item.Status.Replicas != item.Status.AvailableReplicas { - return statusUnhealthy, nil - } - } - - daemonsets, err := k.clientset.AppsV1().DaemonSets("").List(context.Background(), metav1.ListOptions{ - LabelSelector: "cyclops.module=" + name, - }) - if err != nil { - return statusUndefined, err - } - - resourcesWithHealth += len(daemonsets.Items) - for _, item := range daemonsets.Items { - if item.Generation != item.Status.ObservedGeneration || - item.Status.UpdatedNumberScheduled != item.Status.DesiredNumberScheduled || - item.Status.NumberUnavailable != 0 { - return statusUnhealthy, nil - } - } - - pvcs, err := k.clientset.CoreV1().PersistentVolumeClaims("").List(context.Background(), metav1.ListOptions{ - LabelSelector: "cyclops.module=" + name, - }) - if err != nil { - return statusUndefined, err - } - - resourcesWithHealth += len(pvcs.Items) - for _, item := range pvcs.Items { - if item.Status.Phase != apiv1.ClaimBound { - return statusUnhealthy, nil - } - } - - pods, err := k.clientset.CoreV1().Pods("").List(context.Background(), metav1.ListOptions{ - LabelSelector: "cyclops.module=" + name, - }) - if err != nil { - return statusUndefined, err - } - - resourcesWithHealth += len(pods.Items) - for _, item := range pods.Items { - for _, cnt := range item.Spec.Containers { - var status apiv1.ContainerStatus - for _, c := range item.Status.ContainerStatuses { - if c.Name == cnt.Name { - status = c - break - } - } - - if !containerStatus(status).Running { - return statusUnhealthy, nil - } - } - } - - if resourcesWithHealth == 0 { - return statusUndefined, nil - } - - return statusHealthy, nil -} - -func (k *KubernetesClient) GVKtoAPIResourceName(gv schema.GroupVersion, kind string) (string, error) { - apiResources, err := k.clientset.Discovery().ServerResourcesForGroupVersion(gv.String()) - if err != nil { - return "", err - } - - for _, resource := range apiResources.APIResources { - if resource.Kind == kind && len(resource.Name) != 0 { - return resource.Name, nil - } - } - - return "", errors.Errorf("could not find api-resource for groupVersion: %v and kind: %v", gv.String(), kind) -} - -func (k *KubernetesClient) getResourceStatus(o unstructured.Unstructured) (string, error) { - if isPod(o.GroupVersionKind().Group, o.GroupVersionKind().Version, o.GetKind()) { - pod, err := k.clientset.CoreV1().Pods(o.GetNamespace()).Get(context.Background(), o.GetName(), metav1.GetOptions{}) - if err != nil { - return statusUndefined, err - } - - for _, cnt := range pod.Spec.Containers { - var status apiv1.ContainerStatus - for _, c := range pod.Status.ContainerStatuses { - if c.Name == cnt.Name { - status = c - break - } - } - - if !containerStatus(status).Running { - return statusUnhealthy, nil - } - } - - return statusHealthy, err - } - - if isDeployment(o.GroupVersionKind().Group, o.GroupVersionKind().Version, o.GetKind()) { - deployment, err := k.clientset.AppsV1().Deployments(o.GetNamespace()).Get(context.Background(), o.GetName(), metav1.GetOptions{}) - if err != nil { - return statusUndefined, err - } - - if deployment.Generation == deployment.Status.ObservedGeneration && - deployment.Status.Replicas == deployment.Status.UpdatedReplicas && - deployment.Status.UnavailableReplicas == 0 { - return statusHealthy, nil - } - - return statusUnhealthy, nil - } - - if isStatefulSet(o.GroupVersionKind().Group, o.GroupVersionKind().Version, o.GetKind()) { - statefulset, err := k.clientset.AppsV1().StatefulSets(o.GetNamespace()).Get(context.Background(), o.GetName(), metav1.GetOptions{}) - if err != nil { - return statusUndefined, err - } - - if statefulset.Generation == statefulset.Status.ObservedGeneration && - statefulset.Status.Replicas == statefulset.Status.UpdatedReplicas && - statefulset.Status.Replicas == statefulset.Status.AvailableReplicas { - return statusHealthy, nil - } - - return statusUnhealthy, nil - } - - if isDaemonSet(o.GroupVersionKind().Group, o.GroupVersionKind().Version, o.GetKind()) { - daemonset, err := k.clientset.AppsV1().DaemonSets(o.GetNamespace()).Get(context.Background(), o.GetName(), metav1.GetOptions{}) - if err != nil { - return statusUndefined, err - } - - if daemonset.Generation == daemonset.Status.ObservedGeneration && - daemonset.Status.UpdatedNumberScheduled == daemonset.Status.DesiredNumberScheduled && - daemonset.Status.NumberUnavailable == 0 { - return statusHealthy, nil - } - - return statusUnhealthy, nil - } - - if isPersistentVolumeClaims(o.GroupVersionKind().Group, o.GroupVersionKind().Version, o.GetKind()) { - pvc, err := k.clientset.CoreV1().PersistentVolumeClaims(o.GetNamespace()).Get(context.Background(), o.GetName(), metav1.GetOptions{}) - if err != nil { - return statusUndefined, err - } - - if pvc.Status.Phase == apiv1.ClaimBound { - return statusHealthy, nil - } - - return statusUnhealthy, nil - } - - return statusUndefined, nil -} - -func (k *KubernetesClient) getPods(deployment appsv1.Deployment) ([]dto.Pod, error) { - pods, err := k.clientset.CoreV1().Pods(deployment.Namespace).List(context.Background(), metav1.ListOptions{ - LabelSelector: labels.Set(deployment.Spec.Selector.MatchLabels).String(), - }) - if err != nil { - return nil, err - } - - out := make([]dto.Pod, 0, len(pods.Items)) - for _, item := range pods.Items { - containers := make([]dto.Container, 0, len(item.Spec.Containers)) - - for _, cnt := range item.Spec.Containers { - env := make(map[string]string) - for _, envVar := range cnt.Env { - env[envVar.Name] = envVar.Value - } - - var status apiv1.ContainerStatus - for _, c := range item.Status.ContainerStatuses { - if c.Name == cnt.Name { - status = c - break - } - } - - containers = append(containers, dto.Container{ - Name: cnt.Name, - Image: cnt.Image, - Env: env, - Status: containerStatus(status), - }) - } - - out = append(out, dto.Pod{ - Name: item.Name, - Containers: containers, - Node: item.Spec.NodeName, - PodPhase: string(item.Status.Phase), - Started: item.Status.StartTime, - }) - } - - return out, nil -} - -func (k *KubernetesClient) getPodsForDaemonSet(daemonSet appsv1.DaemonSet) ([]dto.Pod, error) { - pods, err := k.clientset.CoreV1().Pods(daemonSet.Namespace).List(context.Background(), metav1.ListOptions{ - LabelSelector: labels.Set(daemonSet.Spec.Selector.MatchLabels).String(), - }) - if err != nil { - return nil, err - } - - out := make([]dto.Pod, 0, len(pods.Items)) - for _, item := range pods.Items { - containers := make([]dto.Container, 0, len(item.Spec.Containers)) - - for _, cnt := range item.Spec.Containers { - env := make(map[string]string) - for _, envVar := range cnt.Env { - env[envVar.Name] = envVar.Value - } - - var status apiv1.ContainerStatus - for _, c := range item.Status.ContainerStatuses { - if c.Name == cnt.Name { - status = c - break - } - } - - containers = append(containers, dto.Container{ - Name: cnt.Name, - Image: cnt.Image, - Env: env, - Status: containerStatus(status), - }) - } - - out = append(out, dto.Pod{ - Name: item.Name, - Containers: containers, - Node: item.Spec.NodeName, - PodPhase: string(item.Status.Phase), - Started: item.Status.StartTime, - }) - } - - return out, nil -} - -func (k *KubernetesClient) getStatefulsetPods(deployment appsv1.StatefulSet) ([]dto.Pod, error) { - pods, err := k.clientset.CoreV1().Pods(deployment.Namespace).List(context.Background(), metav1.ListOptions{ - LabelSelector: labels.Set(deployment.Spec.Selector.MatchLabels).String(), - }) - if err != nil { - return nil, err - } - - out := make([]dto.Pod, 0, len(pods.Items)) - for _, item := range pods.Items { - containers := make([]dto.Container, 0, len(item.Spec.Containers)) - - for _, cnt := range item.Spec.Containers { - env := make(map[string]string) - for _, envVar := range cnt.Env { - env[envVar.Name] = envVar.Value - } - - var status apiv1.ContainerStatus - for _, c := range item.Status.ContainerStatuses { - if c.Name == cnt.Name { - status = c - break - } - } - - containers = append(containers, dto.Container{ - Name: cnt.Name, - Image: cnt.Image, - Env: env, - Status: containerStatus(status), - }) - } - - initContainers := make([]dto.Container, 0, len(item.Spec.Containers)) - for _, cnt := range item.Spec.InitContainers { - env := make(map[string]string) - for _, envVar := range cnt.Env { - env[envVar.Name] = envVar.Value - } - - var status apiv1.ContainerStatus - for _, c := range item.Status.ContainerStatuses { - if c.Name == cnt.Name { - status = c - break - } - } - - initContainers = append(initContainers, dto.Container{ - Name: cnt.Name, - Image: cnt.Image, - Env: env, - Status: containerStatus(status), - }) - } - - out = append(out, dto.Pod{ - Name: item.Name, - Containers: containers, - InitContainers: initContainers, - Node: item.Spec.NodeName, - PodPhase: string(item.Status.Phase), - Started: item.Status.StartTime, - }) - } - - return out, nil -} - -func (k *KubernetesClient) getPodsForCronJob(cronJob batchv1.CronJob) ([]dto.Pod, error) { - jobTemplateLabels := cronJob.Spec.JobTemplate.Spec.Template.Labels - jobLabelSelector := labels.SelectorFromSet(jobTemplateLabels).String() - - jobs, err := k.clientset.BatchV1().Jobs(cronJob.Namespace).List(context.Background(), metav1.ListOptions{ - LabelSelector: jobLabelSelector, - }) - if err != nil { - return nil, err - } - - out := make([]dto.Pod, 0) - - for _, job := range jobs.Items { - podTemplateLabels := job.Spec.Template.Labels - podLabelSelector := labels.SelectorFromSet(podTemplateLabels).String() - - pods, err := k.clientset.CoreV1().Pods(cronJob.Namespace).List(context.Background(), metav1.ListOptions{ - LabelSelector: podLabelSelector, - }) - - if err != nil { - return nil, err - } - - for _, item := range pods.Items { - containers := make([]dto.Container, 0, len(item.Spec.Containers)) - - for _, cnt := range item.Spec.Containers { - env := make(map[string]string) - for _, envVar := range cnt.Env { - env[envVar.Name] = envVar.Value - } - - var status apiv1.ContainerStatus - for _, c := range item.Status.ContainerStatuses { - if c.Name == cnt.Name { - status = c - break - } - } - - containers = append(containers, dto.Container{ - Name: cnt.Name, - Image: cnt.Image, - Env: env, - Status: containerStatus(status), - }) - } - - out = append(out, dto.Pod{ - Name: item.Name, - Containers: containers, - Node: item.Spec.NodeName, - PodPhase: string(item.Status.Phase), - Started: item.Status.StartTime, - }) - } - } - - return out, nil - -} - -func (k *KubernetesClient) getPodsForJob(job batchv1.Job) ([]dto.Pod, error) { - pods, err := k.clientset.CoreV1().Pods(job.Namespace).List(context.Background(), metav1.ListOptions{ - LabelSelector: labels.Set(job.Spec.Selector.MatchLabels).String(), - }) - - if err != nil { - return nil, err - } - - out := make([]dto.Pod, 0, len(pods.Items)) - for _, item := range pods.Items { - containers := make([]dto.Container, 0, len(item.Spec.Containers)) - - for _, cnt := range item.Spec.Containers { - env := make(map[string]string) - for _, envVar := range cnt.Env { - env[envVar.Name] = envVar.Value - } - - var status apiv1.ContainerStatus - for _, c := range item.Status.ContainerStatuses { - if c.Name == cnt.Name { - status = c - break - } - } - - containers = append(containers, dto.Container{ - Name: cnt.Name, - Image: cnt.Image, - Env: env, - Status: containerStatus(status), - }) - } - - out = append(out, dto.Pod{ - Name: item.Name, - Containers: containers, - Node: item.Spec.NodeName, - PodPhase: string(item.Status.Phase), - Started: item.Status.StartTime, - }) - } - - return out, nil - -} - -func (k *KubernetesClient) getPodsForNetworkPolicy(policy networkingv1.NetworkPolicy) ([]dto.Pod, error) { - pods, err := k.clientset.CoreV1().Pods(policy.Namespace).List(context.Background(), metav1.ListOptions{ - LabelSelector: labels.Set(policy.Spec.PodSelector.MatchLabels).String(), - }) - - if err != nil { - return nil, err - } - - out := make([]dto.Pod, 0, len(pods.Items)) - for _, item := range pods.Items { - containers := make([]dto.Container, 0, len(item.Spec.Containers)) - - for _, cnt := range item.Spec.Containers { - env := make(map[string]string) - for _, envVar := range cnt.Env { - env[envVar.Name] = envVar.Value - } - - var status apiv1.ContainerStatus - for _, c := range item.Status.ContainerStatuses { - if c.Name == cnt.Name { - status = c - break - } - } - - containers = append(containers, dto.Container{ - Name: cnt.Name, - Image: cnt.Image, - Env: env, - Status: containerStatus(status), - }) - } - - out = append(out, dto.Pod{ - Name: item.Name, - Containers: containers, - Node: item.Spec.NodeName, - PodPhase: string(item.Status.Phase), - Started: item.Status.StartTime, - }) - } - - return out, nil -} - -func containerStatus(status apiv1.ContainerStatus) dto.ContainerStatus { - if status.State.Waiting != nil { - return dto.ContainerStatus{ - Status: status.State.Waiting.Reason, - Message: status.State.Waiting.Message, - Running: false, - } - } - - if status.State.Terminated != nil { - return dto.ContainerStatus{ - Status: status.State.Terminated.Reason, - Message: status.State.Terminated.Message, - Running: status.State.Terminated.ExitCode == 0, - } - } - - return dto.ContainerStatus{ - Status: "running", - Running: true, - } -} - -func getDeploymentStatus(pods []dto.Pod) bool { - for _, pod := range pods { - for _, container := range pod.Containers { - if !container.Status.Running { - return false - } - } - } - - return true -} - -func getDaemonSetStatus(pods []dto.Pod) bool { - if len(pods) == 0 { - return false - } - - for _, pod := range pods { - for _, container := range pod.Containers { - if !container.Status.Running { - return false - } - } - } - - return true -} - -func getPodStatus(containers []dto.Container) bool { - for _, container := range containers { - if !container.Status.Running { - return false - } - } - - return true -} diff --git a/cyclops-ctrl/internal/cluster/k8sclient/templateauthrules.go b/cyclops-ctrl/internal/cluster/k8sclient/templateauthrules.go deleted file mode 100644 index 6b00d084..00000000 --- a/cyclops-ctrl/internal/cluster/k8sclient/templateauthrules.go +++ /dev/null @@ -1,28 +0,0 @@ -package k8sclient - -import ( - "context" - - "github.com/pkg/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - cyclopsv1alpha1 "github.com/cyclops-ui/cyclops/cyclops-ctrl/api/v1alpha1" -) - -func (k *KubernetesClient) ListTemplateAuthRules() ([]cyclopsv1alpha1.TemplateAuthRule, error) { - return k.moduleset.TemplateAuthRules(cyclopsNamespace).List(metav1.ListOptions{}) -} - -func (k *KubernetesClient) GetTemplateAuthRuleSecret(name, key string) (string, error) { - secret, err := k.clientset.CoreV1().Secrets(cyclopsNamespace).Get(context.Background(), name, metav1.GetOptions{}) - if err != nil { - return "", err - } - - secretValue, ok := secret.Data[key] - if !ok { - return "", errors.New("key not found") - } - - return string(secretValue), err -} diff --git a/cyclops-ctrl/internal/cluster/k8sclient/templatestore.go b/cyclops-ctrl/internal/cluster/k8sclient/templatestore.go deleted file mode 100644 index 5cb15274..00000000 --- a/cyclops-ctrl/internal/cluster/k8sclient/templatestore.go +++ /dev/null @@ -1,32 +0,0 @@ -package k8sclient - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - cyclopsv1alpha1 "github.com/cyclops-ui/cyclops/cyclops-ctrl/api/v1alpha1" -) - -func (k *KubernetesClient) ListTemplateStore() ([]cyclopsv1alpha1.TemplateStore, error) { - return k.moduleset.TemplateStore(cyclopsNamespace).List(metav1.ListOptions{}) -} - -func (k *KubernetesClient) CreateTemplateStore(ts *cyclopsv1alpha1.TemplateStore) error { - _, err := k.moduleset.TemplateStore(cyclopsNamespace).Create(ts) - return err -} - -func (k *KubernetesClient) UpdateTemplateStore(ts *cyclopsv1alpha1.TemplateStore) error { - curr, err := k.moduleset.TemplateStore(cyclopsNamespace).Get(ts.Name) - if err != nil { - return err - } - - ts.SetResourceVersion(curr.GetResourceVersion()) - - _, err = k.moduleset.TemplateStore(cyclopsNamespace).Update(ts) - return err -} - -func (k *KubernetesClient) DeleteTemplateStore(name string) error { - return k.moduleset.TemplateStore(cyclopsNamespace).Delete(name) -} From a37ec728b42c6731be0d0b1aa971487ab627f88d Mon Sep 17 00:00:00 2001 From: petar-cvit Date: Sun, 8 Sep 2024 14:43:14 +0200 Subject: [PATCH 5/8] update ns defaults --- cyclops-ctrl/pkg/cluster/k8sclient/client.go | 2 +- cyclops-ctrl/pkg/cluster/k8sclient/modules.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cyclops-ctrl/pkg/cluster/k8sclient/client.go b/cyclops-ctrl/pkg/cluster/k8sclient/client.go index ee438758..c7d439c4 100644 --- a/cyclops-ctrl/pkg/cluster/k8sclient/client.go +++ b/cyclops-ctrl/pkg/cluster/k8sclient/client.go @@ -435,7 +435,7 @@ func (k *KubernetesClient) CreateDynamic( objNamespace = apiv1.NamespaceDefault if len(strings.TrimSpace(targetNamespace)) != 0 { - obj.SetNamespace(strings.TrimSpace(targetNamespace)) + objNamespace = strings.TrimSpace(targetNamespace) } if len(strings.TrimSpace(obj.GetNamespace())) != 0 { diff --git a/cyclops-ctrl/pkg/cluster/k8sclient/modules.go b/cyclops-ctrl/pkg/cluster/k8sclient/modules.go index c42ed68b..60a09626 100644 --- a/cyclops-ctrl/pkg/cluster/k8sclient/modules.go +++ b/cyclops-ctrl/pkg/cluster/k8sclient/modules.go @@ -178,7 +178,7 @@ func (k *KubernetesClient) GetDeletedResources( objNamespace := apiv1.NamespaceDefault if len(strings.TrimSpace(targetNamespace)) != 0 { - obj.SetNamespace(strings.TrimSpace(targetNamespace)) + objNamespace = strings.TrimSpace(targetNamespace) } if len(strings.TrimSpace(obj.GetNamespace())) != 0 { From a6c62f0784aeb1cb00c5f39bd1a69c3c0ca26fef Mon Sep 17 00:00:00 2001 From: petar-cvit Date: Sun, 8 Sep 2024 14:51:46 +0200 Subject: [PATCH 6/8] update ns defaults --- cyclops-ctrl/pkg/cluster/k8sclient/client.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cyclops-ctrl/pkg/cluster/k8sclient/client.go b/cyclops-ctrl/pkg/cluster/k8sclient/client.go index c7d439c4..e0e1bb0b 100644 --- a/cyclops-ctrl/pkg/cluster/k8sclient/client.go +++ b/cyclops-ctrl/pkg/cluster/k8sclient/client.go @@ -430,9 +430,7 @@ func (k *KubernetesClient) CreateDynamic( Resource: resource.Resource, } - var objNamespace string - - objNamespace = apiv1.NamespaceDefault + objNamespace := apiv1.NamespaceDefault if len(strings.TrimSpace(targetNamespace)) != 0 { objNamespace = strings.TrimSpace(targetNamespace) From 146b4a9114c01c7a677f693f67fbdfa2d1c5d2ae Mon Sep 17 00:00:00 2001 From: petar-cvit Date: Sun, 8 Sep 2024 16:50:19 +0200 Subject: [PATCH 7/8] copy target namespace on update --- cyclops-ctrl/internal/controller/modules.go | 2 ++ install/cyclops-install.yaml | 22 ++++++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/cyclops-ctrl/internal/controller/modules.go b/cyclops-ctrl/internal/controller/modules.go index c0603667..c22a7a49 100644 --- a/cyclops-ctrl/internal/controller/modules.go +++ b/cyclops-ctrl/internal/controller/modules.go @@ -296,6 +296,8 @@ func (m *Modules) UpdateModule(ctx *gin.Context) { module.Status.ReconciliationStatus = curr.Status.ReconciliationStatus module.Status.IconURL = curr.Status.IconURL module.Status.ManagedGVRs = curr.Status.ManagedGVRs + + module.Spec.TargetNamespace = curr.Spec.TargetNamespace result, err := m.kubernetesClient.UpdateModuleStatus(&module) if err != nil { diff --git a/install/cyclops-install.yaml b/install/cyclops-install.yaml index 400795cc..9d6feb4a 100644 --- a/install/cyclops-install.yaml +++ b/install/cyclops-install.yaml @@ -1,8 +1,9 @@ +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.11.3 + controller-gen.kubebuilder.io/version: v0.14.0 name: modules.cyclops-ui.com spec: group: cyclops-ui.com @@ -19,9 +20,11 @@ spec: description: Module is the Schema for the modules API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string history: items: @@ -51,15 +54,20 @@ spec: type: object type: array kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object spec: description: ModuleSpec defines the desired state of Module properties: + targetNamespace: + type: string template: properties: path: From 08435b0a3b991b8fc9694f385758241587d77056 Mon Sep 17 00:00:00 2001 From: petar-cvit Date: Sun, 8 Sep 2024 16:52:09 +0200 Subject: [PATCH 8/8] remove divider --- cyclops-ctrl/internal/controller/modules.go | 2 +- install/cyclops-install.yaml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/cyclops-ctrl/internal/controller/modules.go b/cyclops-ctrl/internal/controller/modules.go index c22a7a49..5fbed43f 100644 --- a/cyclops-ctrl/internal/controller/modules.go +++ b/cyclops-ctrl/internal/controller/modules.go @@ -296,7 +296,7 @@ func (m *Modules) UpdateModule(ctx *gin.Context) { module.Status.ReconciliationStatus = curr.Status.ReconciliationStatus module.Status.IconURL = curr.Status.IconURL module.Status.ManagedGVRs = curr.Status.ManagedGVRs - + module.Spec.TargetNamespace = curr.Spec.TargetNamespace result, err := m.kubernetesClient.UpdateModuleStatus(&module) diff --git a/install/cyclops-install.yaml b/install/cyclops-install.yaml index 9d6feb4a..876a1f6f 100644 --- a/install/cyclops-install.yaml +++ b/install/cyclops-install.yaml @@ -1,4 +1,3 @@ ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: