Skip to content

Commit

Permalink
💨 track deployed GVRs (#400)
Browse files Browse the repository at this point in the history
* custom unmarshal prop type

* use display name for objects

* remove unused space

* show display name for objects on edit

* store managed gvrs to the module crd

* fix tests
  • Loading branch information
petar-cvit authored Jul 8, 2024
1 parent 0fafd86 commit 7495c0b
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 49 deletions.
8 changes: 8 additions & 0 deletions cyclops-ctrl/api/v1alpha1/module_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,19 @@ type ReconciliationStatus struct {
Errors []string `json:"errors"`
}

type GroupVersionResource struct {
Group string `json:"group"`
Version string `json:"version"`
Resource string `json:"resource"`
}

// ModuleStatus defines the observed state of Module
type ModuleStatus struct {
ReconciliationStatus ReconciliationStatus `json:"reconciliationStatus"`
TemplateResolvedVersion string `json:"templateResolvedVersion"`
// +kubebuilder:validation:Optional
ManagedGVRs []GroupVersionResource `json:"managedGVRs"`
// +kubebuilder:validation:Optional
IconURL string `json:"iconURL"`
}

Expand Down
20 changes: 20 additions & 0 deletions cyclops-ctrl/api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions cyclops-ctrl/config/crd/bases/cyclops-ui.com_modules.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,21 @@ spec:
properties:
iconURL:
type: string
managedGVRs:
items:
properties:
group:
type: string
resource:
type: string
version:
type: string
required:
- group
- resource
- version
type: object
type: array
reconciliationStatus:
properties:
errors:
Expand Down
14 changes: 5 additions & 9 deletions cyclops-ctrl/internal/cluster/k8sclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"context"
"errors"
"fmt"
"github.com/cyclops-ui/cyclops/cyclops-ctrl/api/v1alpha1"
"io"
"os"
"os/exec"
Expand Down Expand Up @@ -346,16 +347,11 @@ func (k *KubernetesClient) Delete(resource dto.Resource) error {
)
}

func (k *KubernetesClient) CreateDynamic(obj *unstructured.Unstructured) error {
resourceName, err := k.GVKtoAPIResourceName(obj.GroupVersionKind().GroupVersion(), obj.GroupVersionKind().Kind)
if err != nil {
return err
}

func (k *KubernetesClient) CreateDynamic(resource v1alpha1.GroupVersionResource, obj *unstructured.Unstructured) error {
gvr := schema.GroupVersionResource{
Group: obj.GroupVersionKind().Group,
Version: obj.GroupVersionKind().Version,
Resource: resourceName,
Group: resource.Group,
Version: resource.Version,
Resource: resource.Resource,
}

objNamespace := obj.GetNamespace()
Expand Down
83 changes: 59 additions & 24 deletions cyclops-ctrl/internal/cluster/k8sclient/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,39 +56,27 @@ func (k *KubernetesClient) GetModule(name string) (*cyclopsv1alpha1.Module, erro
func (k *KubernetesClient) GetResourcesForModule(name string) ([]dto.Resource, error) {
out := make([]dto.Resource, 0, 0)

apiResources, err := k.clientset.Discovery().ServerPreferredResources()
module, err := k.GetModule(name)
if err != nil {
return nil, err
}

other := make([]unstructured.Unstructured, 0)
managedGVRs, err := k.getManagedGVRs(module)
if err != nil {
return nil, err
}

for _, resource := range apiResources {
gvk, err := schema.ParseGroupVersion(resource.GroupVersion)
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 _, 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
}

rs, err := k.Dynamic.Resource(schema.GroupVersionResource{
Group: gvk.Group,
Version: gvk.Version,
Resource: apiResource.Name,
}).List(context.Background(), metav1.ListOptions{
LabelSelector: "cyclops.module=" + name,
})
if err != nil {
continue
}

for _, item := range rs.Items {
other = append(other, item)
}
for _, item := range rs.Items {
other = append(other, item)
}
}

Expand Down Expand Up @@ -120,6 +108,53 @@ func (k *KubernetesClient) GetResourcesForModule(name string) ([]dto.Resource, e
return out, nil
}

func (k *KubernetesClient) getManagedGVRs(module *cyclopsv1alpha1.Module) ([]schema.GroupVersionResource, error) {
if module == nil {
return nil, errors.New("nil module provided")
}

if 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,
Expand Down
87 changes: 71 additions & 16 deletions cyclops-ctrl/internal/modulecontroller/module_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,18 +133,18 @@ func (r *ModuleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
if err != nil {
r.logger.Error(err, "error fetching module template", "namespaced name", req.NamespacedName)

if err = r.setStatus(ctx, module, req.NamespacedName, cyclopsv1alpha1.Failed, templateVersion, err.Error(), nil, ""); err != nil {
if err = r.setStatus(ctx, module, req.NamespacedName, cyclopsv1alpha1.Failed, templateVersion, err.Error(), nil, nil, ""); err != nil {
return ctrl.Result{}, err
}

return ctrl.Result{}, err
}

installErrors, err := r.moduleToResources(req.Name, template)
installErrors, childrenResources, err := r.moduleToResources(req.Name, template)
if err != nil {
r.logger.Error(err, "error on upsert module", "namespaced name", req.NamespacedName)

if err = r.setStatus(ctx, module, req.NamespacedName, cyclopsv1alpha1.Failed, template.ResolvedVersion, err.Error(), nil, template.IconURL); err != nil {
if err = r.setStatus(ctx, module, req.NamespacedName, cyclopsv1alpha1.Failed, template.ResolvedVersion, err.Error(), nil, nil, template.IconURL); err != nil {
return ctrl.Result{}, err
}

Expand All @@ -160,11 +160,22 @@ func (r *ModuleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
template.ResolvedVersion,
"error decoding/applying resources",
installErrors,
childrenResources,
template.IconURL,
)
}

return ctrl.Result{}, r.setStatus(ctx, module, req.NamespacedName, cyclopsv1alpha1.Succeeded, template.ResolvedVersion, "", nil, template.IconURL)
return ctrl.Result{}, r.setStatus(
ctx,
module,
req.NamespacedName,
cyclopsv1alpha1.Succeeded,
template.ResolvedVersion,
"",
nil,
childrenResources,
template.IconURL,
)
}

// SetupWithManager sets up the controller with the Manager.
Expand All @@ -174,32 +185,34 @@ func (r *ModuleReconciler) SetupWithManager(mgr ctrl.Manager) error {
Complete(r)
}

func (r *ModuleReconciler) moduleToResources(name string, template *models.Template) ([]string, error) {
func (r *ModuleReconciler) moduleToResources(name string, template *models.Template) ([]string, []cyclopsv1alpha1.GroupVersionResource, error) {
module, err := r.kubernetesClient.GetModule(name)
if err != nil {
return nil, err
return nil, nil, err
}

crdInstallErrors := r.applyCRDs(template)
if err != nil {
return nil, err
}

installErrors, err := r.generateResources(r.kubernetesClient, *module, template)
installErrors, childrenGVRs, err := r.generateResources(r.kubernetesClient, *module, template)
if err != nil {
return nil, err
return nil, nil, err
}

return append(crdInstallErrors, installErrors...), nil
return append(crdInstallErrors, installErrors...), childrenGVRs, nil
}

func (r *ModuleReconciler) generateResources(kClient *k8sclient.KubernetesClient, module cyclopsv1alpha1.Module, moduleTemplate *models.Template) ([]string, error) {
func (r *ModuleReconciler) generateResources(
kClient *k8sclient.KubernetesClient,
module cyclopsv1alpha1.Module,
moduleTemplate *models.Template,
) ([]string, []cyclopsv1alpha1.GroupVersionResource, error) {
out, err := r.renderer.HelmTemplate(module, moduleTemplate)
if err != nil {
return nil, err
return nil, nil, err
}

installErrors := make([]string, 0)
childrenGVRs := make([]cyclopsv1alpha1.GroupVersionResource, 0)

for _, s := range strings.Split(out, "\n---\n") {
s := strings.TrimSpace(s)
Expand Down Expand Up @@ -245,7 +258,29 @@ func (r *ModuleReconciler) generateResources(kClient *k8sclient.KubernetesClient
labels["cyclops.module"] = module.Name
obj.SetLabels(labels)

if err := kClient.CreateDynamic(&obj); err != nil {
resourceName, err := kClient.GVKtoAPIResourceName(obj.GroupVersionKind().GroupVersion(), obj.GroupVersionKind().Kind)
if err != nil {
installErrors = append(installErrors, fmt.Sprintf(
"%v%v/%v %v/%v failed to apply: %v",
obj.GroupVersionKind().Group,
obj.GroupVersionKind().Version,
obj.GroupVersionKind().Kind,
obj.GetNamespace(),
obj.GetName(),
err.Error(),
))

continue
}

gvr := cyclopsv1alpha1.GroupVersionResource{
Group: obj.GroupVersionKind().Group,
Version: obj.GroupVersionKind().Version,
Resource: resourceName,
}
childrenGVRs = append(childrenGVRs, gvr)

if err := kClient.CreateDynamic(gvr, &obj); err != nil {
r.logger.Error(err, "could not apply resource",
"module namespaced name",
module.Name,
Expand All @@ -269,7 +304,7 @@ func (r *ModuleReconciler) generateResources(kClient *k8sclient.KubernetesClient
}
}

return installErrors, nil
return installErrors, childrenGVRs, nil
}

func (r *ModuleReconciler) applyCRDs(template *models.Template) []string {
Expand Down Expand Up @@ -338,6 +373,24 @@ func (r *ModuleReconciler) applyCRDFile(file *chart.File) []string {
return installErrors
}

func (r *ModuleReconciler) mergeChildrenGVRs(existing, current []cyclopsv1alpha1.GroupVersionResource) []cyclopsv1alpha1.GroupVersionResource {
unique := make(map[cyclopsv1alpha1.GroupVersionResource]struct{})
for _, resource := range existing {
unique[resource] = struct{}{}
}

for _, resource := range current {
unique[resource] = struct{}{}
}

merged := make([]cyclopsv1alpha1.GroupVersionResource, 0)
for u := range unique {
merged = append(merged, u)
}

return merged
}

func (r *ModuleReconciler) setStatus(
ctx context.Context,
module cyclopsv1alpha1.Module,
Expand All @@ -346,6 +399,7 @@ func (r *ModuleReconciler) setStatus(
templateResolvedVersion string,
reason string,
installErrors []string,
childrenResources []cyclopsv1alpha1.GroupVersionResource,
iconURL string,
) error {
trv := module.Status.TemplateResolvedVersion
Expand All @@ -359,6 +413,7 @@ func (r *ModuleReconciler) setStatus(
Reason: reason,
Errors: installErrors,
},
ManagedGVRs: r.mergeChildrenGVRs(module.Status.ManagedGVRs, childrenResources),
TemplateResolvedVersion: templateResolvedVersion,
IconURL: iconURL,
}
Expand Down
2 changes: 2 additions & 0 deletions cyclops-ctrl/internal/template/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,12 +216,14 @@ func (r Repo) mapHelmChart(chartName string, files map[string][]byte) (*models.T
// unmarshal values schema only if present
if len(schemaBytes) > 0 {
if err := json.Unmarshal(schemaBytes, &schema); err != nil {
fmt.Println("error on schema bytes", chartName)
return &models.Template{}, err
}
}

var metadata *helm.Metadata
if err := yaml.Unmarshal(metadataBytes, &metadata); err != nil {
fmt.Println("error on meta unm", chartName)
return &models.Template{}, err
}

Expand Down

0 comments on commit 7495c0b

Please sign in to comment.