From 43910c5eebcaa316b9604c26624b9e20cddba5fe Mon Sep 17 00:00:00 2001 From: sakshisharma84 Date: Mon, 20 Nov 2023 13:42:20 -0500 Subject: [PATCH] [BOP-19] Support deletion of Addons via Manifest (#13) --- api/v1alpha1/manifest_types.go | 12 +- api/v1alpha1/zz_generated.deepcopy.go | 22 +- .../boundless.mirantis.com_manifests.yaml | 24 +- controllers/addon_controller.go | 51 +- controllers/manifest_controller.go | 1111 +++++++++++++++++ deploy/static/boundless-operator.yaml | 20 + pkg/manifest/manifest.go | 379 +----- 7 files changed, 1293 insertions(+), 326 deletions(-) diff --git a/api/v1alpha1/manifest_types.go b/api/v1alpha1/manifest_types.go index 4053d8ad..94500b54 100644 --- a/api/v1alpha1/manifest_types.go +++ b/api/v1alpha1/manifest_types.go @@ -12,8 +12,9 @@ type ManifestSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file - // Foo is an example field of Manifest. Edit manifest_types.go to remove/update - Url string `json:"url,omitempty"` + Url string `json:"url"` + Checksum string `json:"checksum"` + Objects []ManifestObject `json:"objects,omitempty"` } // ManifestStatus defines the observed state of Manifest @@ -22,6 +23,13 @@ type ManifestStatus struct { // Important: Run "make" to regenerate code after modifying this file } +// ManifestObject consists of the fields required to update/delete an object +type ManifestObject struct { + Kind string `json:"kind"` + Name string `json:"name"` + Namespace string `json:"namespace"` +} + //+kubebuilder:object:root=true //+kubebuilder:subresource:status diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index afac6cdc..1839d7ff 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -354,7 +354,7 @@ func (in *Manifest) DeepCopyInto(out *Manifest) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -423,9 +423,29 @@ func (in *ManifestList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ManifestObject) DeepCopyInto(out *ManifestObject) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManifestObject. +func (in *ManifestObject) DeepCopy() *ManifestObject { + if in == nil { + return nil + } + out := new(ManifestObject) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ManifestSpec) DeepCopyInto(out *ManifestSpec) { *out = *in + if in.Objects != nil { + in, out := &in.Objects, &out.Objects + *out = make([]ManifestObject, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManifestSpec. diff --git a/config/crd/bases/boundless.mirantis.com_manifests.yaml b/config/crd/bases/boundless.mirantis.com_manifests.yaml index b1372947..bcde6684 100644 --- a/config/crd/bases/boundless.mirantis.com_manifests.yaml +++ b/config/crd/bases/boundless.mirantis.com_manifests.yaml @@ -35,10 +35,30 @@ spec: spec: description: ManifestSpec defines the desired state of Manifest properties: + checksum: + type: string + objects: + items: + description: ManifestObject consists of the fields required to update/delete + an object + properties: + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + - namespace + type: object + type: array url: - description: Foo is an example field of Manifest. Edit manifest_types.go - to remove/update type: string + required: + - checksum + - url type: object status: description: ManifestStatus defines the observed state of Manifest diff --git a/controllers/addon_controller.go b/controllers/addon_controller.go index caf489a8..4ff7c49a 100644 --- a/controllers/addon_controller.go +++ b/controllers/addon_controller.go @@ -17,8 +17,11 @@ import ( ) const ( - kindManifest = "manifest" - kindChart = "chart" + kindManifest = "manifest" + kindChart = "chart" + BoundlessNamespace = "boundless-system" + addonHelmchartFinalizer = "boundless.mirantis.com/helmchart-finalizer" + addonManifestFinalizer = "boundless.mirantis.com/manifest-finalizer" ) // AddonReconciler reconciles a Addon object @@ -74,21 +77,19 @@ func (r *AddonReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl hc := helm.NewHelmChartController(r.Client, logger) - addonFinalizerName := "boundless.mirantis.com/finalizer" - if instance.ObjectMeta.DeletionTimestamp.IsZero() { // The object is not being deleted, so if it does not have our finalizer, // then lets add the finalizer and update the object. This is equivalent // registering our finalizer. - if !controllerutil.ContainsFinalizer(instance, addonFinalizerName) { - controllerutil.AddFinalizer(instance, addonFinalizerName) + if !controllerutil.ContainsFinalizer(instance, addonHelmchartFinalizer) { + controllerutil.AddFinalizer(instance, addonHelmchartFinalizer) if err := r.Update(ctx, instance); err != nil { return ctrl.Result{}, err } } } else { // The object is being deleted - if controllerutil.ContainsFinalizer(instance, addonFinalizerName) { + if controllerutil.ContainsFinalizer(instance, addonHelmchartFinalizer) { // our finalizer is present, so lets delete the helm chart if err := hc.DeleteHelmChart(chart, instance.Spec.Namespace); err != nil { // if fail to delete the helm chart here, return with error @@ -97,7 +98,7 @@ func (r *AddonReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl } // remove our finalizer from the list and update it. - controllerutil.RemoveFinalizer(instance, addonFinalizerName) + controllerutil.RemoveFinalizer(instance, addonHelmchartFinalizer) if err := r.Update(ctx, instance); err != nil { return ctrl.Result{}, err } @@ -114,7 +115,39 @@ func (r *AddonReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl } case kindManifest: mc := manifest.NewManifestController(r.Client, logger) - err = mc.CreateManifest(instance.Spec.Namespace, instance.Spec.Name, instance.Spec.Manifest.URL) + + if instance.ObjectMeta.DeletionTimestamp.IsZero() { + // The object is not being deleted, so if it does not have our finalizer, + // then lets add the finalizer and update the object. This is equivalent + // registering our finalizer. + if !controllerutil.ContainsFinalizer(instance, addonManifestFinalizer) { + controllerutil.AddFinalizer(instance, addonManifestFinalizer) + if err := r.Update(ctx, instance); err != nil { + return ctrl.Result{}, err + } + } + } else { + // The object is being deleted + if controllerutil.ContainsFinalizer(instance, addonManifestFinalizer) { + // our finalizer is present, so lets delete the helm chart + if err := mc.DeleteManifest(BoundlessNamespace, instance.Spec.Name, instance.Spec.Manifest.URL); err != nil { + // if fail to delete the manifest here, return with error + // so that it can be retried + return ctrl.Result{}, err + } + + // remove our finalizer from the list and update it. + controllerutil.RemoveFinalizer(instance, addonManifestFinalizer) + if err := r.Update(ctx, instance); err != nil { + return ctrl.Result{}, err + } + } + + // Stop reconciliation as the item is being deleted + return ctrl.Result{}, nil + } + + err = mc.CreateManifest(BoundlessNamespace, instance.Spec.Name, instance.Spec.Manifest.URL) if err != nil { logger.Error(err, "failed to install addon via manifest", "URL", instance.Spec.Manifest.URL) return ctrl.Result{Requeue: true}, err diff --git a/controllers/manifest_controller.go b/controllers/manifest_controller.go index 94bcb44f..36e036d9 100644 --- a/controllers/manifest_controller.go +++ b/controllers/manifest_controller.go @@ -2,13 +2,31 @@ package controllers import ( "context" + "io" + "net/http" + "strings" + "time" + "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/log" + adm_v1 "k8s.io/api/admissionregistration/v1" + apps_v1 "k8s.io/api/apps/v1" + core_v1 "k8s.io/api/core/v1" + policy_v1 "k8s.io/api/policy/v1" + rbac_v1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + boundlessv1alpha1 "github.com/mirantis/boundless-operator/api/v1alpha1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" ) // ManifestReconciler reconciles a Manifest object @@ -17,6 +35,11 @@ type ManifestReconciler struct { Scheme *runtime.Scheme } +// The checkSum map stores the checksum for each manifest. +// Storing this value is crucial as this will help the manifest controller +// differentiate between create and update requests. +var checkSum = make(map[string]string) + //+kubebuilder:rbac:groups=boundless.mirantis.com,resources=manifests,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=boundless.mirantis.com,resources=manifests/status,verbs=get;update;patch //+kubebuilder:rbac:groups=boundless.mirantis.com,resources=manifests/finalizers,verbs=update @@ -36,6 +59,107 @@ func (r *ManifestReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c logger := log.FromContext(ctx) logger.Info("Reconcile request on Manifest instance") + key := types.NamespacedName{ + Namespace: req.Namespace, + Name: req.Name, + } + + existing := &boundlessv1alpha1.Manifest{} + err := r.Client.Get(ctx, key, existing) + + if err != nil { + if strings.Contains(err.Error(), "not found") { + logger.Info("manifest does not exist", "Namespace", req.Namespace, "Name", req.Name) + return ctrl.Result{}, nil + } else { + logger.Error(err, "failed to get manifest object") + return ctrl.Result{}, err + } + } + + addonFinalizerName := "manifest/finalizer" + + if existing.ObjectMeta.DeletionTimestamp.IsZero() { + // The object is not being deleted, so if it does not have our finalizer, + // then lets add the finalizer and update the object. This is equivalent + // registering our finalizer. + if !controllerutil.ContainsFinalizer(existing, addonFinalizerName) { + controllerutil.AddFinalizer(existing, addonFinalizerName) + if err := r.Update(ctx, existing); err != nil { + logger.Info("failed to update manifest object with finalizer", "Name", req.Name, "Finalizer", addonFinalizerName) + return ctrl.Result{}, err + } + } + } else { + // The object is being deleted + if controllerutil.ContainsFinalizer(existing, addonFinalizerName) { + // The finalizer is present, so lets delete the objects for this manifest + + if err := r.DeleteManifestObjects(req, ctx); err != nil { + logger.Error(err, "failed to remove finalizer") + return ctrl.Result{}, err + } + + // Remove the finalizer from the list and update it. + controllerutil.RemoveFinalizer(existing, addonFinalizerName) + if err := r.Update(ctx, existing); err != nil { + logger.Error(err, "failed to remove finalizer") + return ctrl.Result{}, err + } + } + + // Stop reconciliation as the item is being deleted + return ctrl.Result{}, nil + } + + // Validate checksum entry in the checkSum map + sum, ok := checkSum[req.Name] + if !ok { + // Entry not present, add it. + // This will happen in case of the create request. + checkSum[req.Name] = existing.Spec.Checksum + } else { + // Present. Compare it with the new request. + if sum == existing.Spec.Checksum { + // Do nothing + logger.Info("checksum is same, no update needed", "Cache", sum, "Object", existing.Spec.Checksum) + return ctrl.Result{}, nil + } else { + logger.Info("checksum is not same, update needed", "Cache", sum, "Object", existing.Spec.Checksum) + // @TODO : Add code for update + return ctrl.Result{}, nil + } + } + + // We will reach here only in case of create request. + // Run http get request to fetch the contents of the manifest file. + var client http.Client + resp, err := client.Get(existing.Spec.Url) + if err != nil { + logger.Error(err, "failed to fetch manifest file content for url: %s", existing.Spec.Url) + return ctrl.Result{}, err + } + + defer resp.Body.Close() + + var bodyBytes []byte + if resp.StatusCode == http.StatusOK { + bodyBytes, err = io.ReadAll(resp.Body) + if err != nil { + logger.Error(err, "failed to read http response body") + return ctrl.Result{}, err + } + + } else { + logger.Error(err, "failure in http get request", "ResponseCode", resp.StatusCode) + return ctrl.Result{}, err + } + err = r.CreateManifestObjects(req, bodyBytes, logger, ctx) + if err != nil { + logger.Error(err, "failed to create manifest objects", "ResponseCode", resp.StatusCode) + return ctrl.Result{}, err + } + return ctrl.Result{}, nil } @@ -45,3 +169,990 @@ func (r *ManifestReconciler) SetupWithManager(mgr ctrl.Manager) error { For(&boundlessv1alpha1.Manifest{}). Complete(r) } + +func (r *ManifestReconciler) CreateManifestObjects(req ctrl.Request, data []byte, logger logr.Logger, ctx context.Context) error { + apiextensionsv1.AddToScheme(clientgoscheme.Scheme) + apiextensionsv1beta1.AddToScheme(clientgoscheme.Scheme) + adm_v1.AddToScheme(clientgoscheme.Scheme) + apps_v1.AddToScheme(clientgoscheme.Scheme) + core_v1.AddToScheme(clientgoscheme.Scheme) + policy_v1.AddToScheme(clientgoscheme.Scheme) + rbac_v1.AddToScheme(clientgoscheme.Scheme) + + decoder := clientgoscheme.Codecs.UniversalDeserializer() + + manifestObjs := []boundlessv1alpha1.ManifestObject{} + + for _, obj := range strings.Split(string(data), "---") { + if obj != "" { + runtimeObject, groupVersionKind, err := decoder.Decode([]byte(obj), nil, nil) + if err != nil { + logger.Info("Failed to decode yaml:", "Error", err) + return err + } + + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + switch groupVersionKind.Kind { + case "Namespace": + err := r.addNamespaceObject(runtimeObject, groupVersionKind, req, ctx, &manifestObjs) + if err != nil { + return err + } + + case "Service": + err := r.addServiceObject(runtimeObject, groupVersionKind, req, ctx, &manifestObjs) + if err != nil { + return err + } + + case "Deployment": + err := r.addDeploymentObject(runtimeObject, groupVersionKind, req, ctx, &manifestObjs) + if err != nil { + return err + } + + case "DaemonSet": + err := r.addDaemonsetObject(runtimeObject, groupVersionKind, req, ctx, &manifestObjs) + if err != nil { + return err + } + + case "PodDisruptionBudget": + err := r.addPodDisruptionBudget(runtimeObject, groupVersionKind, req, ctx, &manifestObjs) + if err != nil { + return err + } + + case "ServiceAccount": + err := r.addServiceAccount(runtimeObject, groupVersionKind, req, ctx, &manifestObjs) + if err != nil { + return err + } + + case "Role": + err := r.addRoleObject(runtimeObject, groupVersionKind, req, ctx, &manifestObjs) + if err != nil { + return err + } + + case "ClusterRole": + err := r.addClusterRoleObject(runtimeObject, groupVersionKind, req, ctx, &manifestObjs) + if err != nil { + return err + } + + case "Secret": + err := r.addSecretObject(runtimeObject, groupVersionKind, req, ctx, &manifestObjs) + if err != nil { + return err + } + + case "RoleBinding": + err := r.addRoleBindingObject(runtimeObject, groupVersionKind, req, ctx, &manifestObjs) + if err != nil { + return err + } + + case "ClusterRoleBinding": + err := r.addClusterRoleBindingObject(runtimeObject, groupVersionKind, req, ctx, &manifestObjs) + if err != nil { + return err + } + + case "ConfigMap": + err := r.addConfigMapObject(runtimeObject, groupVersionKind, req, ctx, &manifestObjs) + if err != nil { + return err + } + + case "CustomResourceDefinition": + err := r.addCRDObject(runtimeObject, groupVersionKind, req, ctx, &manifestObjs) + if err != nil { + return err + } + + case "ValidatingWebhookConfiguration": + err := r.addValidatingWebhookObject(runtimeObject, groupVersionKind, req, ctx, &manifestObjs) + if err != nil { + return err + } + + default: + logger.Info("Object kind not supported", "Kind", groupVersionKind.Kind) + } + } + } + + // Update the CRD + key := types.NamespacedName{ + Namespace: req.Namespace, + Name: req.Name, + } + + existing := &boundlessv1alpha1.Manifest{} + err := r.Client.Get(ctx, key, existing) + if err != nil { + logger.Error(err, "failed to get manifest object") + return err + } + + updatedCRD := boundlessv1alpha1.Manifest{ + ObjectMeta: metav1.ObjectMeta{ + Name: existing.Name, + Namespace: existing.Namespace, + ResourceVersion: existing.ResourceVersion, + }, + Spec: boundlessv1alpha1.ManifestSpec{ + Url: existing.Spec.Url, + Checksum: existing.Spec.Checksum, + Objects: manifestObjs, + }, + } + + if err := r.Update(ctx, &updatedCRD); err != nil { + logger.Error(err, "failed to update manifest crd with objectList") + return err + } + + return nil +} + +func (r *ManifestReconciler) addNamespaceObject(obj runtime.Object, groupVersionKind *schema.GroupVersionKind, req ctrl.Request, ctx context.Context, manifestObjs *[]boundlessv1alpha1.ManifestObject) error { + logger := log.FromContext(ctx) + + myobj := obj.(*core_v1.Namespace) + err := r.Client.Create(ctx, myobj) + if err != nil { + if strings.Contains(err.Error(), "already exists") { + logger.Info("namespace already exists:", "Namespace", myobj.Name) + return nil + } + logger.Info("failed to create namespace:", "Error", err) + return err + } + + logger.Info("namespace created successfully:", "Namespace", myobj.Name) + + // Add this object to the list + r.addObjectToList(groupVersionKind.Kind, myobj.Name, "", req, manifestObjs) + return nil +} + +func (r *ManifestReconciler) addServiceObject(obj runtime.Object, groupVersionKind *schema.GroupVersionKind, req ctrl.Request, ctx context.Context, manifestObjs *[]boundlessv1alpha1.ManifestObject) error { + logger := log.FromContext(ctx) + + myobj := obj.(*core_v1.Service) + + err := r.Client.Create(ctx, myobj) + if err != nil { + if strings.Contains(err.Error(), "already exists") { + logger.Info("service already exists:", "Service", myobj.Name) + return nil + } + logger.Info("failed to create service:", "Error", err) + return err + } + + logger.Info("service created successfully:", "Service", myobj.Name) + + // Add this object to the list + r.addObjectToList(groupVersionKind.Kind, myobj.Name, myobj.Namespace, req, manifestObjs) + return nil +} + +func (r *ManifestReconciler) addDeploymentObject(obj runtime.Object, groupVersionKind *schema.GroupVersionKind, req ctrl.Request, ctx context.Context, manifestObjs *[]boundlessv1alpha1.ManifestObject) error { + logger := log.FromContext(ctx) + myobj := obj.(*apps_v1.Deployment) + + err := r.Client.Create(ctx, myobj) + if err != nil { + if strings.Contains(err.Error(), "already exists") { + logger.Info("deployment already exists:", "Deployment", myobj.Name) + return nil + } + logger.Info("failed to create deployment:", "Error", err) + return err + } + + logger.Info("deployment created successfully:", "Deployment", myobj.Name) + + // Add this object to the list + r.addObjectToList(groupVersionKind.Kind, myobj.Name, myobj.Namespace, req, manifestObjs) + return nil + +} + +func (r *ManifestReconciler) addPodDisruptionBudget(obj runtime.Object, groupVersionKind *schema.GroupVersionKind, req ctrl.Request, ctx context.Context, manifestObjs *[]boundlessv1alpha1.ManifestObject) error { + logger := log.FromContext(ctx) + myobj := obj.(*policy_v1.PodDisruptionBudget) + + err := r.Client.Create(ctx, myobj) + if err != nil { + if strings.Contains(err.Error(), "already exists") { + logger.Info("pod disruption budget already exists:", "PodDisruption", myobj.Name) + return nil + } + logger.Info("failed to create pod disruption budget:", "Error", err) + return err + } + + logger.Info("pod disruption budget created successfully:", "PodDisruption", myobj.Name) + + // Add this object to the list + r.addObjectToList(groupVersionKind.Kind, myobj.Name, myobj.Namespace, req, manifestObjs) + return nil + +} + +func (r *ManifestReconciler) addServiceAccount(obj runtime.Object, groupVersionKind *schema.GroupVersionKind, req ctrl.Request, ctx context.Context, manifestObjs *[]boundlessv1alpha1.ManifestObject) error { + logger := log.FromContext(ctx) + myobj := obj.(*core_v1.ServiceAccount) + + err := r.Client.Create(ctx, myobj) + if err != nil { + if strings.Contains(err.Error(), "already exists") { + logger.Info("service account already exists:", "ServiceAcoount", myobj.Name) + return nil + } + logger.Info("failed to create service account:", "Error", err) + return err + } + + logger.Info("service account created successfully:", "ServiceAccount", myobj.Name) + + // Add this object to the list + r.addObjectToList(groupVersionKind.Kind, myobj.Name, myobj.Namespace, req, manifestObjs) + return nil +} + +func (r *ManifestReconciler) addCRDObject(obj runtime.Object, groupVersionKind *schema.GroupVersionKind, req ctrl.Request, ctx context.Context, manifestObjs *[]boundlessv1alpha1.ManifestObject) error { + logger := log.FromContext(ctx) + myobj := obj.(*apiextensionsv1.CustomResourceDefinition) + + err := r.Client.Create(ctx, myobj) + if err != nil { + if strings.Contains(err.Error(), "already exists") { + logger.Info("crd already exists:", "CRD", myobj.Name) + return nil + } + logger.Info("failed to create crd:", "Error", err) + return err + } + + logger.Info("crd created successfully:", "CRD", myobj.Name) + + // Add this object to the list + r.addObjectToList(groupVersionKind.Kind, myobj.Name, "", req, manifestObjs) + return nil +} + +func (r *ManifestReconciler) addDaemonsetObject(obj runtime.Object, groupVersionKind *schema.GroupVersionKind, req ctrl.Request, ctx context.Context, manifestObjs *[]boundlessv1alpha1.ManifestObject) error { + logger := log.FromContext(ctx) + myobj := obj.(*apps_v1.DaemonSet) + + err := r.Client.Create(ctx, myobj) + if err != nil { + if strings.Contains(err.Error(), "already exists") { + logger.Info("daemonset already exists:", "Daemonset", myobj.Name) + return nil + } + logger.Info("failed to create daemonset:", "Error", err) + return err + } + + logger.Info("daemonset created successfully:", "Daemonset", myobj.Name) + + // Add this object to the list + r.addObjectToList(groupVersionKind.Kind, myobj.Name, myobj.Namespace, req, manifestObjs) + return nil +} + +func (r *ManifestReconciler) addRoleObject(obj runtime.Object, groupVersionKind *schema.GroupVersionKind, req ctrl.Request, ctx context.Context, manifestObjs *[]boundlessv1alpha1.ManifestObject) error { + logger := log.FromContext(ctx) + myobj := obj.(*rbac_v1.Role) + + err := r.Client.Create(ctx, myobj) + if err != nil { + if strings.Contains(err.Error(), "already exists") { + logger.Info("role already exists:", "Role", myobj.Name) + return nil + } + logger.Info("failed to create role:", "Error", err) + return err + } + + logger.Info("role created successfully:", "Role", myobj.Name) + + // Add this object to the list + r.addObjectToList(groupVersionKind.Kind, myobj.Name, myobj.Namespace, req, manifestObjs) + return nil +} + +func (r *ManifestReconciler) addClusterRoleObject(obj runtime.Object, groupVersionKind *schema.GroupVersionKind, req ctrl.Request, ctx context.Context, manifestObjs *[]boundlessv1alpha1.ManifestObject) error { + logger := log.FromContext(ctx) + myobj := obj.(*rbac_v1.ClusterRole) + + err := r.Client.Create(ctx, myobj) + if err != nil { + if strings.Contains(err.Error(), "already exists") { + logger.Info("clusterrole already exists:", "Clusterrole", myobj.Name) + return nil + } + logger.Info("failed to create clusterrole:", "Error", err) + return err + } + + logger.Info("clusterrole created successfully:", "Clusterrole", myobj.Name) + + // Add this object to the list + r.addObjectToList(groupVersionKind.Kind, myobj.Name, myobj.Namespace, req, manifestObjs) + return nil +} + +func (r *ManifestReconciler) addRoleBindingObject(obj runtime.Object, groupVersionKind *schema.GroupVersionKind, req ctrl.Request, ctx context.Context, manifestObjs *[]boundlessv1alpha1.ManifestObject) error { + logger := log.FromContext(ctx) + myobj := obj.(*rbac_v1.RoleBinding) + + err := r.Client.Create(ctx, myobj) + if err != nil { + if strings.Contains(err.Error(), "already exists") { + logger.Info("rolebinding already exists:", "Rolebinding", myobj.Name) + return nil + } + logger.Info("failed to create rolebinding:", "Error", err) + return err + } + + logger.Info("rolebinding created successfully:", "Rolebinding", myobj.Name) + + // Add this object to the list + r.addObjectToList(groupVersionKind.Kind, myobj.Name, myobj.Namespace, req, manifestObjs) + return nil +} + +func (r *ManifestReconciler) addClusterRoleBindingObject(obj runtime.Object, groupVersionKind *schema.GroupVersionKind, req ctrl.Request, ctx context.Context, manifestObjs *[]boundlessv1alpha1.ManifestObject) error { + logger := log.FromContext(ctx) + myobj := obj.(*rbac_v1.ClusterRoleBinding) + + err := r.Client.Create(ctx, myobj) + if err != nil { + if strings.Contains(err.Error(), "already exists") { + logger.Info("clusterrolebinding already exists:", "ClusterRoleBinding", myobj.Name) + return nil + } + logger.Info("failed to create cluster role binding:", "Error", err) + return err + } + + logger.Info("cluster role binding created successfully:", "ClusterRoleBinding", myobj.Name) + + // Add this object to the list + r.addObjectToList(groupVersionKind.Kind, myobj.Name, "", req, manifestObjs) + return nil +} + +func (r *ManifestReconciler) addSecretObject(obj runtime.Object, groupVersionKind *schema.GroupVersionKind, req ctrl.Request, ctx context.Context, manifestObjs *[]boundlessv1alpha1.ManifestObject) error { + logger := log.FromContext(ctx) + myobj := obj.(*core_v1.Secret) + + err := r.Client.Create(ctx, myobj) + if err != nil { + if strings.Contains(err.Error(), "already exists") { + logger.Info("secret already exists:", "Secret", myobj.Name) + return nil + } + logger.Info("failed to create secret:", "Error", err) + return err + } + + logger.Info("secret created successfully:", "Secret", myobj.Name) + + // Add this object to the list + r.addObjectToList(groupVersionKind.Kind, myobj.Name, myobj.Namespace, req, manifestObjs) + return nil +} + +func (r *ManifestReconciler) addConfigMapObject(obj runtime.Object, groupVersionKind *schema.GroupVersionKind, req ctrl.Request, ctx context.Context, manifestObjs *[]boundlessv1alpha1.ManifestObject) error { + logger := log.FromContext(ctx) + myobj := obj.(*core_v1.ConfigMap) + + err := r.Client.Create(ctx, myobj) + if err != nil { + if strings.Contains(err.Error(), "already exists") { + logger.Info("configmap already exists:", "Configmap", myobj.Name) + return nil + } + logger.Info("failed to create configmap:", "Error", err) + return err + } + + logger.Info("configmap created successfully:", "Configmap", myobj.Name) + + // Add this object to the list + r.addObjectToList(groupVersionKind.Kind, myobj.Name, myobj.Namespace, req, manifestObjs) + return nil +} + +func (r *ManifestReconciler) addValidatingWebhookObject(obj runtime.Object, groupVersionKind *schema.GroupVersionKind, req ctrl.Request, ctx context.Context, manifestObjs *[]boundlessv1alpha1.ManifestObject) error { + logger := log.FromContext(ctx) + myobj := obj.(*adm_v1.ValidatingWebhookConfiguration) + + err := r.Client.Create(ctx, myobj) + if err != nil { + if strings.Contains(err.Error(), "already exists") { + logger.Info("validating webhook already exists:", "ValidatingWebhook", myobj.Name) + return nil + } + logger.Info("failed to create validating webhook:", "Error", err) + return err + } + + logger.Info("validating webhook created successfully:", "ValidatingWebhook", myobj.Name) + + // Add this object to the list + r.addObjectToList(groupVersionKind.Kind, myobj.Name, "", req, manifestObjs) + return nil +} + +func (r *ManifestReconciler) addObjectToList(kind string, name string, namespace string, req ctrl.Request, manifestObjs *[]boundlessv1alpha1.ManifestObject) { + + // Add this object to the list + // @TODO: Check if we can use dynamic clients to create the objects + // https://mirantis.jira.com/browse/BOP-102 + updatedObject := boundlessv1alpha1.ManifestObject{ + Kind: kind, + Name: name, + Namespace: namespace, + } + + *manifestObjs = append(*manifestObjs, updatedObject) + +} + +func (r *ManifestReconciler) DeleteManifestObjects(req ctrl.Request, ctx context.Context) error { + logger := log.FromContext(ctx) + // Fetch the existing CRD + key := types.NamespacedName{ + Namespace: req.Namespace, + Name: req.Name, + } + existing := &boundlessv1alpha1.Manifest{} + err := r.Client.Get(ctx, key, existing) + + if err != nil { + if strings.Contains(err.Error(), "not found") { + logger.Info("manifest does not exist", "Namespace", req.Namespace, "Name", req.Name) + return nil + } else { + logger.Error(err, "failed to get manifest object") + return err + } + } + // Fetch all the objects stored in the manifest object list and delete them + if len(existing.Spec.Objects) > 0 { + for _, val := range existing.Spec.Objects { + + switch val.Kind { + case "Namespace": + err := r.deleteNamespaceObject(val, ctx) + if err != nil { + return err + } + case "Service": + err := r.deleteServiceObject(val, ctx) + if err != nil { + return err + } + + case "Deployment": + err := r.deleteDeploymentObject(val, ctx) + if err != nil { + return err + } + + case "DaemonSet": + err := r.deleteDaemonsetObject(val, ctx) + if err != nil { + return err + } + + case "PodDisruptionBudget": + err := r.deletePDBObject(val, ctx) + if err != nil { + return err + } + + case "ServiceAccount": + err := r.deleteServiceAccountObject(val, ctx) + if err != nil { + return err + } + + case "Role": + err := r.deleteRoleObject(val, ctx) + if err != nil { + return err + } + + case "ClusterRole": + err := r.deleteClusterRoleObject(val, ctx) + if err != nil { + return err + } + + case "Secret": + err := r.deleteSecretObject(val, ctx) + if err != nil { + return err + } + + case "RoleBinding": + err := r.deleteRoleBindingObject(val, ctx) + if err != nil { + return err + } + + case "ClusterRoleBinding": + err := r.deleteClusterRoleBindingObject(val, ctx) + if err != nil { + return err + } + + case "ConfigMap": + err := r.deleteConfigmapObject(val, ctx) + if err != nil { + return err + } + + case "CustomResourceDefinition": + err := r.deleteCRDObject(val, ctx) + if err != nil { + return err + } + + case "ValidatingWebhookConfiguration": + err := r.deleteValidatingWebhookObject(val, ctx) + if err != nil { + return err + } + + default: + logger.Info("Object kind not supported", "Kind", val.Kind) + } + + } + } + return nil +} + +func (r *ManifestReconciler) deleteNamespaceObject(val boundlessv1alpha1.ManifestObject, ctx context.Context) error { + logger := log.FromContext(ctx) + + namespace := &core_v1.Namespace{} + err := r.Client.Get(ctx, client.ObjectKey{ + Namespace: val.Namespace, + Name: val.Name, + }, namespace) + if err != nil { + if strings.Contains(err.Error(), "not found") { + logger.Info("namespace does not exist", "Namespace", val.Name) + return nil + } else { + logger.Error(err, "failed to get namespace object") + return err + } + } + + logger.Info("namespace object retrived successfully:", "Namespace", namespace) + + err = r.Client.Delete(ctx, namespace) + if err != nil { + logger.Info("failed to delete namespace:", "Error", err) + return err + } else { + logger.Info("namespace deleted successfully:", "Namespace", namespace.Name) + } + + return nil +} + +func (r *ManifestReconciler) deleteServiceObject(val boundlessv1alpha1.ManifestObject, ctx context.Context) error { + logger := log.FromContext(ctx) + + service := &core_v1.Service{} + err := r.Client.Get(ctx, client.ObjectKey{ + Namespace: val.Namespace, + Name: val.Name, + }, service) + if err != nil { + if strings.Contains(err.Error(), "not found") { + logger.Info("service does not exist", "Service", val.Name) + return nil + } else { + logger.Error(err, "failed to get service object") + return err + } + } + + logger.Info("service object retrived successfully:", "Service", service) + + err = r.Client.Delete(ctx, service) + if err != nil { + logger.Info("failed to delete service:", "Error", err) + return err + } else { + logger.Info("service deleted successfully:", "Service", service.Name) + } + + return nil +} + +func (r *ManifestReconciler) deleteServiceAccountObject(val boundlessv1alpha1.ManifestObject, ctx context.Context) error { + logger := log.FromContext(ctx) + + serviceAccount := &core_v1.ServiceAccount{} + err := r.Client.Get(ctx, client.ObjectKey{ + Namespace: val.Namespace, + Name: val.Name, + }, serviceAccount) + if err != nil { + if strings.Contains(err.Error(), "not found") { + logger.Info("service account does not exist", "Service", val.Name) + return nil + } else { + logger.Error(err, "failed to get service account object") + return err + } + } + + err = r.Client.Delete(ctx, serviceAccount) + if err != nil { + logger.Info("failed to delete service account:", "Error", err) + return err + } else { + logger.Info("service account deleted successfully:", "Service", serviceAccount.Name) + } + + return nil +} + +func (r *ManifestReconciler) deleteCRDObject(val boundlessv1alpha1.ManifestObject, ctx context.Context) error { + logger := log.FromContext(ctx) + + crd := &apiextensionsv1.CustomResourceDefinition{} + err := r.Client.Get(ctx, client.ObjectKey{ + Namespace: val.Namespace, + Name: val.Name, + }, crd) + if err != nil { + if strings.Contains(err.Error(), "not found") { + logger.Info("crd does not exist", "Service", val.Name) + return nil + } else { + logger.Error(err, "failed to get crd object") + return err + } + } + + err = r.Client.Delete(ctx, crd) + if err != nil { + logger.Info("failed to delete crd:", "Error", err) + return err + } else { + logger.Info("crd deleted successfully:", "CRD", crd.Name) + } + + return nil +} + +func (r *ManifestReconciler) deleteDeploymentObject(val boundlessv1alpha1.ManifestObject, ctx context.Context) error { + logger := log.FromContext(ctx) + + deployment := &apps_v1.Deployment{} + err := r.Client.Get(ctx, client.ObjectKey{ + Namespace: val.Namespace, + Name: val.Name, + }, deployment) + if err != nil { + if strings.Contains(err.Error(), "not found") { + logger.Info("deployment does not exist", "Deployment", val.Name) + return nil + } else { + logger.Error(err, "failed to get deployment object") + return err + } + } + + err = r.Client.Delete(ctx, deployment) + if err != nil { + logger.Info("failed to delete deployment:", "Error", err) + return err + } else { + logger.Info("deployment deleted successfully:", "Deployment", deployment.Name) + } + + return nil +} + +func (r *ManifestReconciler) deleteDaemonsetObject(val boundlessv1alpha1.ManifestObject, ctx context.Context) error { + logger := log.FromContext(ctx) + + daemonset := &apps_v1.DaemonSet{} + err := r.Client.Get(ctx, client.ObjectKey{ + Namespace: val.Namespace, + Name: val.Name, + }, daemonset) + if err != nil { + if strings.Contains(err.Error(), "not found") { + logger.Info("daemonset does not exist", "Daemonset", val.Name) + return nil + } else { + logger.Error(err, "failed to get daemonset object") + return err + } + } + + err = r.Client.Delete(ctx, daemonset) + if err != nil { + logger.Info("failed to delete daemonset:", "Error", err) + return err + } else { + logger.Info("daemonset deleted successfully:", "Daemonset", daemonset.Name) + } + + return nil +} + +func (r *ManifestReconciler) deletePDBObject(val boundlessv1alpha1.ManifestObject, ctx context.Context) error { + logger := log.FromContext(ctx) + + pdb := &policy_v1.PodDisruptionBudget{} + err := r.Client.Get(ctx, client.ObjectKey{ + Namespace: val.Namespace, + Name: val.Name, + }, pdb) + if err != nil { + if strings.Contains(err.Error(), "not found") { + logger.Info("policy discruption budget does not exist", "policyDiscruptionBudget", val.Name) + return nil + } else { + logger.Error(err, "failed to get policy discruption budget object") + return err + } + } + + err = r.Client.Delete(ctx, pdb) + if err != nil { + logger.Info("failed to delete policy discruption budget:", "Error", err) + return err + } else { + logger.Info("policy discruption budget deleted successfully:", "PolicyDiscruptionBudget", pdb.Name) + } + + return nil +} + +func (r *ManifestReconciler) deleteRoleObject(val boundlessv1alpha1.ManifestObject, ctx context.Context) error { + logger := log.FromContext(ctx) + + role := &rbac_v1.Role{} + err := r.Client.Get(ctx, client.ObjectKey{ + Namespace: val.Namespace, + Name: val.Name, + }, role) + if err != nil { + if strings.Contains(err.Error(), "not found") { + logger.Info("role does not exist", "Role", val.Name) + return nil + } else { + logger.Error(err, "failed to get role object") + return err + } + } + + err = r.Client.Delete(ctx, role) + if err != nil { + logger.Info("failed to delete role:", "Error", err) + return err + } else { + logger.Info("role deleted successfully:", "Role", role.Name) + } + + return nil +} + +func (r *ManifestReconciler) deleteClusterRoleObject(val boundlessv1alpha1.ManifestObject, ctx context.Context) error { + logger := log.FromContext(ctx) + + clusterRole := &rbac_v1.ClusterRole{} + err := r.Client.Get(ctx, client.ObjectKey{ + Namespace: val.Namespace, + Name: val.Name, + }, clusterRole) + if err != nil { + if strings.Contains(err.Error(), "not found") { + logger.Info("clusterRole does not exist", "ClusterRole", val.Name) + return nil + } else { + logger.Error(err, "failed to get clusterRole object") + return err + } + } + + err = r.Client.Delete(ctx, clusterRole) + if err != nil { + logger.Info("failed to delete clusterRole:", "Error", err) + return err + } else { + logger.Info("clusterRole deleted successfully:", "ClusterRole", clusterRole.Name) + } + + return nil +} + +func (r *ManifestReconciler) deleteSecretObject(val boundlessv1alpha1.ManifestObject, ctx context.Context) error { + logger := log.FromContext(ctx) + + secret := &core_v1.Secret{} + err := r.Client.Get(ctx, client.ObjectKey{ + Namespace: val.Namespace, + Name: val.Name, + }, secret) + if err != nil { + if strings.Contains(err.Error(), "not found") { + logger.Info("secret does not exist", "Secret", val.Name) + return nil + } else { + logger.Error(err, "failed to get secret object") + return err + } + } + + err = r.Client.Delete(ctx, secret) + if err != nil { + logger.Info("failed to delete secret:", "Error", err) + return err + } else { + logger.Info("secret deleted successfully:", "Secret", secret.Name) + } + + return nil +} + +func (r *ManifestReconciler) deleteRoleBindingObject(val boundlessv1alpha1.ManifestObject, ctx context.Context) error { + logger := log.FromContext(ctx) + + roleBinding := &rbac_v1.RoleBinding{} + err := r.Client.Get(ctx, client.ObjectKey{ + Namespace: val.Namespace, + Name: val.Name, + }, roleBinding) + if err != nil { + if strings.Contains(err.Error(), "not found") { + logger.Info("roleBinding does not exist", "RoleBinding", val.Name) + return nil + } else { + logger.Error(err, "failed to get roleBinding object") + return err + } + } + + err = r.Client.Delete(ctx, roleBinding) + if err != nil { + logger.Info("failed to delete roleBinding:", "Error", err) + return err + } else { + logger.Info("roleBinding deleted successfully:", "RoleBinding", roleBinding.Name) + } + + return nil +} + +func (r *ManifestReconciler) deleteClusterRoleBindingObject(val boundlessv1alpha1.ManifestObject, ctx context.Context) error { + logger := log.FromContext(ctx) + + clusterRoleBinding := &rbac_v1.ClusterRoleBinding{} + err := r.Client.Get(ctx, client.ObjectKey{ + Namespace: val.Namespace, + Name: val.Name, + }, clusterRoleBinding) + if err != nil { + if strings.Contains(err.Error(), "not found") { + logger.Info("clusterRoleBinding does not exist", "ClusterRoleBinding", val.Name) + return nil + } else { + logger.Error(err, "failed to get clusterRoleBinding object") + return err + } + } + + err = r.Client.Delete(ctx, clusterRoleBinding) + if err != nil { + logger.Info("failed to delete clusterRoleBinding:", "Error", err) + return err + } else { + logger.Info("clusterRoleBinding deleted successfully:", "ClusterRoleBinding", clusterRoleBinding.Name) + } + + return nil +} + +func (r *ManifestReconciler) deleteConfigmapObject(val boundlessv1alpha1.ManifestObject, ctx context.Context) error { + logger := log.FromContext(ctx) + + configMap := &core_v1.ConfigMap{} + err := r.Client.Get(ctx, client.ObjectKey{ + Namespace: val.Namespace, + Name: val.Name, + }, configMap) + if err != nil { + if strings.Contains(err.Error(), "not found") { + logger.Info("configMap does not exist", "ConfigMap", val.Name) + return nil + } else { + logger.Error(err, "failed to get configMap object") + return err + } + } + + err = r.Client.Delete(ctx, configMap) + if err != nil { + logger.Info("failed to delete configMap:", "Error", err) + return err + } else { + logger.Info("configMap deleted successfully:", "ConfigMap", configMap.Name) + } + + return nil +} + +func (r *ManifestReconciler) deleteValidatingWebhookObject(val boundlessv1alpha1.ManifestObject, ctx context.Context) error { + logger := log.FromContext(ctx) + + webhook := &adm_v1.ValidatingWebhookConfiguration{} + err := r.Client.Get(ctx, client.ObjectKey{ + Namespace: val.Namespace, + Name: val.Name, + }, webhook) + if err != nil { + if strings.Contains(err.Error(), "not found") { + logger.Info("webhook does not exist", "Webhook", val.Name) + return nil + } else { + logger.Error(err, "failed to get webhook object") + return err + } + } + + err = r.Client.Delete(ctx, webhook) + if err != nil { + logger.Info("failed to delete webhook:", "Error", err) + return err + } else { + logger.Info("webhook deleted successfully:", "Webhook", webhook.Name) + } + + return nil +} diff --git a/deploy/static/boundless-operator.yaml b/deploy/static/boundless-operator.yaml index 31fa577f..b4f8a94c 100644 --- a/deploy/static/boundless-operator.yaml +++ b/deploy/static/boundless-operator.yaml @@ -293,9 +293,29 @@ spec: spec: description: ManifestSpec defines the desired state of Manifest properties: + checksum: + type: string + objects: + items: + properties: + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + - namespace + type: object + type: array url: description: Foo is an example field of Manifest. Edit manifest_types.go to remove/update type: string + required: + - checksum + - url type: object status: description: ManifestStatus defines the observed state of Manifest diff --git a/pkg/manifest/manifest.go b/pkg/manifest/manifest.go index a39a2d7b..7c5cd16d 100644 --- a/pkg/manifest/manifest.go +++ b/pkg/manifest/manifest.go @@ -2,6 +2,8 @@ package manifest import ( "context" + "crypto/sha256" + "encoding/hex" "fmt" "io" "net/http" @@ -9,21 +11,13 @@ import ( "time" "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" boundlessv1alpha1 "github.com/mirantis/boundless-operator/api/v1alpha1" - adm_v1 "k8s.io/api/admissionregistration/v1" - apps_v1 "k8s.io/api/apps/v1" - core_v1 "k8s.io/api/core/v1" - policy_v1 "k8s.io/api/policy/v1" - rbac_v1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type ManifestController struct { @@ -39,66 +33,60 @@ func NewManifestController(client client.Client, logger logr.Logger) *ManifestCo } func (mc *ManifestController) CreateManifest(namespace, name, url string) error { - - var client http.Client - - // Run http get request to fetch the contents of the manifest file - resp, err := client.Get(url) + sum, err := mc.getCheckSumUrl(url) if err != nil { - mc.logger.Error(err, "failed to run Unable to read response") + mc.logger.Error(err, "Failed to get checksum for url") return err } - defer resp.Body.Close() - - var bodyBytes []byte - if resp.StatusCode == http.StatusOK { - bodyBytes, err = io.ReadAll(resp.Body) - if err != nil { - mc.logger.Error(err, "failed to read http response body") - return err - } - - } else { - mc.logger.Error(err, "failure in http get request", "ResponseCode", resp.StatusCode) - return err + m := boundlessv1alpha1.Manifest{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: boundlessv1alpha1.ManifestSpec{ + Url: url, + Checksum: sum, + }, } - return mc.createOrUpdateManifest(namespace, name, url, bodyBytes) + return mc.createOrUpdateManifest(m) } -func (mc *ManifestController) createOrUpdateManifest(namespace, name, url string, bodyBytes []byte) error { +func (mc *ManifestController) createOrUpdateManifest(m boundlessv1alpha1.Manifest) error { ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() - existing, err := mc.getExistingManifest(ctx, namespace, name) + existing, err := mc.getExistingManifest(m.Namespace, m.Name) if err != nil { return err } if existing != nil { // ToDo : add code for update - mc.logger.Info("manifest crd already exists", "Manifest", existing) + // Use checksum to see if any updates are required. return nil } else { - mc.logger.Info("manifest crd does not exist, creating", "ManifestName", name, "Namespace", namespace) - - // Deserialize the manifest contents and fetch all the objects - _, err := mc.CreateManifestCRD(namespace, name, url, bodyBytes) + mc.logger.Info("manifest crd does not exist, creating", "ManifestName", m.Name, "Namespace", m.Namespace) + err := mc.client.Create(ctx, &m) if err != nil { - mc.logger.Error(err, "failed to create manifest CRD") + mc.logger.Info("failed to create manifest crd", "Error", err) return err } + mc.logger.Info("manifest created successfully", "ManifestName", m.Name) } return nil } -func (mc *ManifestController) getExistingManifest(ctx context.Context, namespace, name string) (*boundlessv1alpha1.Manifest, error) { +func (mc *ManifestController) getExistingManifest(namespace, name string) (*boundlessv1alpha1.Manifest, error) { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + key := types.NamespacedName{ Namespace: namespace, Name: name, @@ -117,295 +105,62 @@ func (mc *ManifestController) getExistingManifest(ctx context.Context, namespace return existing, nil } -func (mc *ManifestController) CreateManifestCRD(namespace, name, url string, data []byte) ([]*runtime.Object, error) { - apiextensionsv1.AddToScheme(scheme.Scheme) - apiextensionsv1beta1.AddToScheme(scheme.Scheme) - adm_v1.AddToScheme(scheme.Scheme) - apps_v1.AddToScheme(scheme.Scheme) - core_v1.AddToScheme(scheme.Scheme) - policy_v1.AddToScheme(scheme.Scheme) - rbac_v1.AddToScheme(scheme.Scheme) - - decoder := scheme.Codecs.UniversalDeserializer() - - for _, obj := range strings.Split(string(data), "---") { - if obj != "" { - runtimeObject, groupVersionKind, err := decoder.Decode([]byte(obj), nil, nil) - if err != nil { - mc.logger.Info("Failed to decode yaml:", "Error", err) - return nil, err - } - - mc.logger.Info("Decode details", "runtimeObject", runtimeObject, "groupVersionKind", groupVersionKind) - - ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) - defer cancel() - - mc.logger.Info("The object recvd is:", "Kind", groupVersionKind.Kind) - - switch groupVersionKind.Kind { - case "Namespace": - // @TODO: create the namespace if it doesn't exist - namespaceObj := convertToNamespaceObject(runtimeObject) - err := mc.client.Create(ctx, namespaceObj) - if err != nil { - if strings.Contains(err.Error(), "already exists") { - mc.logger.Info("Namespace already exists:", "Namespace", namespaceObj.Name) - return nil, nil - } - return nil, err - } - mc.logger.Info("Namespace created successfully:", "Namespace", namespaceObj.Name) - - case "Service": - // @TODO: create the service if it doesn't exist - serviceObj := convertToServiceObject(runtimeObject) - if serviceObj.Namespace == "" { - serviceObj.Namespace = "default" - } - err = mc.client.Create(ctx, serviceObj) - if err != nil { - mc.logger.Info("Failed to create service:", "Error", err) - return nil, err - } - mc.logger.Info("Service created successfully:", "Service", serviceObj.Name) - - case "Deployment": - // @TODO: create the deployment if it doesn't exist - deploymentObj := convertToDeploymentObject(runtimeObject) - if deploymentObj.Namespace == "" { - deploymentObj.Namespace = "default" - } - err = mc.client.Create(ctx, deploymentObj) - if err != nil { - mc.logger.Info("Failed to create deployment:", "Error", err) - return nil, err - } - mc.logger.Info("Deployment created successfully:", "Deployment", deploymentObj.Name) - - case "DaemonSet": - // @TODO: create the daemonSet if it doesn't exist - daemonsetObj := convertToDaemonsetObject(runtimeObject) - if daemonsetObj.Namespace == "" { - daemonsetObj.Namespace = "default" - } - err = mc.client.Create(ctx, daemonsetObj) - if err != nil { - mc.logger.Info("Failed to create daemonset:", "Error", err) - return nil, err - } - mc.logger.Info("daemonset created successfully:", "Daemonset", daemonsetObj.Name) - - case "PodDisruptionBudget": - pdbObj := convertToPodDisruptionBudget(runtimeObject) - if pdbObj.Namespace == "" { - pdbObj.Namespace = "default" - } - err = mc.client.Create(ctx, pdbObj) - if err != nil { - mc.logger.Info("Failed to create pod disruption budget:", "Error", err) - return nil, err - } - mc.logger.Info("Pod disruption budget created successfully:", "PodDisruptionBudget", pdbObj.Name) - - case "ServiceAccount": - serviceAccObj := convertToServiceAccount(runtimeObject) - if serviceAccObj.Namespace == "" { - serviceAccObj.Namespace = "default" - } - err = mc.client.Create(ctx, serviceAccObj) - if err != nil { - mc.logger.Info("Failed to create service account:", "Error", err) - return nil, err - } - mc.logger.Info("service account created successfully:", "ServiceAccount", serviceAccObj.Name) - - case "Role": - roleObj := convertToRoleObject(runtimeObject) - if roleObj.Namespace == "" { - roleObj.Namespace = "default" - } - err = mc.client.Create(ctx, roleObj) - if err != nil { - mc.logger.Info("Failed to create role:", "Error", err) - return nil, err - } - mc.logger.Info("Role created successfully:", "Role", roleObj.Name) - - case "ClusterRole": - clusterRoleObj := convertToClusterRoleObject(runtimeObject) - if clusterRoleObj.Namespace == "" { - clusterRoleObj.Namespace = "default" - } - err = mc.client.Create(ctx, clusterRoleObj) - if err != nil { - mc.logger.Info("Failed to create clusterrole:", "Error", err) - return nil, err - } - mc.logger.Info("ClusterRole created successfully:", "Role", clusterRoleObj.Name) - - case "Secret": - secretObj := convertToSecretObject(runtimeObject) - if secretObj.Namespace == "" { - secretObj.Namespace = "default" - } - err = mc.client.Create(ctx, secretObj) - if err != nil { - mc.logger.Info("Failed to create secret:", "Error", err) - return nil, err - } - mc.logger.Info("secret created successfully:", "Secret", secretObj.Name) - - case "RoleBinding": - roleBindingObj := convertToRoleBindingObject(runtimeObject) - if roleBindingObj.Namespace == "" { - roleBindingObj.Namespace = "default" - } - err = mc.client.Create(ctx, roleBindingObj) - if err != nil { - mc.logger.Info("Failed to create role binding:", "Error", err) - return nil, err - } - mc.logger.Info("role binding created successfully:", "RoleBinding", roleBindingObj.Name) - - case "ClusterRoleBinding": - clusterRoleBindingObj := convertToClusterRoleBindingObject(runtimeObject) - mc.logger.Info("Creating cluster role binding", "Name", clusterRoleBindingObj.Name) - - err = mc.client.Create(ctx, clusterRoleBindingObj) - if err != nil { - mc.logger.Info("Failed to create cluster role binding:", "Error", err) - return nil, err - } - mc.logger.Info("cluster role binding created successfully:", "ClusterRoleBinding", clusterRoleBindingObj.Name) - - case "ConfigMap": - configMapObj := convertToConfigMapObject(runtimeObject) - if configMapObj.Namespace == "" { - configMapObj.Namespace = "default" - } - err = mc.client.Create(ctx, configMapObj) - if err != nil { - mc.logger.Info("Failed to create configmap:", "Error", err) - return nil, err - } - mc.logger.Info("configmap created successfully:", "ConfigMap", configMapObj.Name) - - case "CustomResourceDefinition": - crdObj := convertToCRDObject(runtimeObject) - - err = mc.client.Create(ctx, crdObj) - if err != nil { - mc.logger.Info("Failed to create crd:", "Error", err) - return nil, err - } - mc.logger.Info("crd created successfully:", "CRD", crdObj.Name) - - case "ValidatingWebhookConfiguration": - webhookObj := convertToValidatingWebhookObject(runtimeObject) - - err = mc.client.Create(ctx, webhookObj) - if err != nil { - mc.logger.Info("Failed to create validating webhook:", "Error", err) - return nil, err - } - mc.logger.Info("validating webhook created successfully:", "ValidatingWebhook", webhookObj.Name) - - default: - mc.logger.Info("Object kind not supported", "Kind", groupVersionKind.Kind) - } - } - } - - ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) - defer cancel() - - manifest := &boundlessv1alpha1.Manifest{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: boundlessv1alpha1.ManifestSpec{ - Url: url, - }, - } +func (mc *ManifestController) getCheckSumUrl(url string) (string, error) { + var Client http.Client - err := mc.client.Create(ctx, manifest) + // Run http get request to fetch the contents of the manifest file + resp, err := Client.Get(url) if err != nil { - mc.logger.Info("failed to create manifest crd", "Error", err) - return nil, err + mc.logger.Error(err, "failed to read response") + return "", err } - mc.logger.Info("manifest created successfully", "ManifestName", "name") - return nil, nil -} + defer resp.Body.Close() -func convertToNamespaceObject(obj runtime.Object) *core_v1.Namespace { - myobj := obj.(*core_v1.Namespace) - return myobj -} + var bodyBytes []byte + if resp.StatusCode == http.StatusOK { + bodyBytes, err = io.ReadAll(resp.Body) + if err != nil { + mc.logger.Error(err, "failed to read http response body") + return "", err + } -func convertToServiceObject(obj runtime.Object) *core_v1.Service { - myobj := obj.(*core_v1.Service) - return myobj -} + } else { + mc.logger.Error(err, "failure in http get request", "ResponseCode", resp.StatusCode) + return "", err + } -func convertToDeploymentObject(obj runtime.Object) *apps_v1.Deployment { - myobj := obj.(*apps_v1.Deployment) - return myobj -} + sum := sha256.Sum256(bodyBytes) + mc.logger.Info("computed checksum :", "Checksum", hex.EncodeToString(sum[:])) -func convertToPodDisruptionBudget(obj runtime.Object) *policy_v1.PodDisruptionBudget { - myobj := obj.(*policy_v1.PodDisruptionBudget) - return myobj + return hex.EncodeToString(sum[:]), nil } -func convertToServiceAccount(obj runtime.Object) *core_v1.ServiceAccount { - myobj := obj.(*core_v1.ServiceAccount) - return myobj -} +func (mc *ManifestController) DeleteManifest(namespace, name, url string) error { -func convertToCRDObject(obj runtime.Object) *apiextensionsv1.CustomResourceDefinition { - myobj := obj.(*apiextensionsv1.CustomResourceDefinition) - return myobj -} - -func convertToDaemonsetObject(obj runtime.Object) *apps_v1.DaemonSet { - myobj := obj.(*apps_v1.DaemonSet) - return myobj -} + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() -func convertToRoleObject(obj runtime.Object) *rbac_v1.Role { - myobj := obj.(*rbac_v1.Role) - return myobj -} + existing, err := mc.getExistingManifest(namespace, name) + if err != nil { + return err + } -func convertToClusterRoleObject(obj runtime.Object) *rbac_v1.ClusterRole { - myobj := obj.(*rbac_v1.ClusterRole) - return myobj -} + if existing == nil { + mc.logger.Info("manifest object does not exist", "Name", name) + return nil -func convertToRoleBindingObject(obj runtime.Object) *rbac_v1.RoleBinding { - myobj := obj.(*rbac_v1.RoleBinding) - return myobj -} + } -func convertToClusterRoleBindingObject(obj runtime.Object) *rbac_v1.ClusterRoleBinding { - myobj := obj.(*rbac_v1.ClusterRoleBinding) - return myobj -} + mc.logger.Info("deleting the manifest crd", "ManifestName", name, "Namespace", namespace) -func convertToSecretObject(obj runtime.Object) *core_v1.Secret { - myobj := obj.(*core_v1.Secret) - return myobj -} + err = mc.client.Delete(ctx, existing) + if err != nil { + mc.logger.Info("failed to delete manifest crd", "Error", err) + return err + } + mc.logger.Info("manifest deleted successfully", "ManifestName", name) -func convertToConfigMapObject(obj runtime.Object) *core_v1.ConfigMap { - myobj := obj.(*core_v1.ConfigMap) - return myobj -} + return nil -func convertToValidatingWebhookObject(obj runtime.Object) *adm_v1.ValidatingWebhookConfiguration { - myobj := obj.(*adm_v1.ValidatingWebhookConfiguration) - return myobj }