From 50d342f4577c3240d1322d5b71c38c473f33ac9a Mon Sep 17 00:00:00 2001 From: kkb0318 Date: Tue, 9 Apr 2024 22:06:54 +0900 Subject: [PATCH] kubernetes API --- internal/controller/irsasetup_controller.go | 24 ++++++- internal/kubernetes/apply.go | 73 +++++++++++++++++++++ internal/kubernetes/delete.go | 69 +++++++++++++++++++ internal/kubernetes/owner.go | 7 ++ internal/manifests/keys_secret.go | 1 + 5 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 internal/kubernetes/apply.go create mode 100644 internal/kubernetes/delete.go create mode 100644 internal/kubernetes/owner.go create mode 100644 internal/manifests/keys_secret.go diff --git a/internal/controller/irsasetup_controller.go b/internal/controller/irsasetup_controller.go index c7020c3..ff9652f 100644 --- a/internal/controller/irsasetup_controller.go +++ b/internal/controller/irsasetup_controller.go @@ -22,6 +22,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" irsav1alpha1 "github.com/kkb0318/irsa-manager/api/v1alpha1" @@ -30,6 +31,8 @@ import ( "github.com/kkb0318/irsa-manager/internal/selfhosted/oidc" ) +const irsamanagerFinalizer = "irsa.kkb0318.github.io/finalizers" + // IRSASetupReconciler reconciles a IRSASetup object type IRSASetupReconciler struct { client.Client @@ -50,12 +53,27 @@ type IRSASetupReconciler struct { // // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.16.3/pkg/reconcile -func (r *IRSASetupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { +func (r *IRSASetupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, retErr error) { log := ctrllog.FromContext(ctx) obj := &irsav1alpha1.IRSASetup{} if err := r.Get(ctx, req.NamespacedName, obj); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } + + if !controllerutil.ContainsFinalizer(obj, irsamanagerFinalizer) { + controllerutil.AddFinalizer(obj, irsamanagerFinalizer) + if err := r.Update(ctx, obj); err != nil { + log.Error(err, "Failed to update custom resource to add finalizer") + return ctrl.Result{}, err + } + return ctrl.Result{Requeue: true}, nil + } + + if !obj.DeletionTimestamp.IsZero() { + retErr = r.reconcileDelete(ctx, obj) + return + } + if err := r.reconcile(ctx, obj); err != nil { return ctrl.Result{}, err } @@ -69,6 +87,10 @@ func (r *IRSASetupReconciler) reconcile(ctx context.Context, obj *irsav1alpha1.I return err } +func (r *IRSASetupReconciler) reconcileDelete(ctx context.Context, obj *irsav1alpha1.IRSASetup) error { + return nil +} + func reconcileSelfhosted(ctx context.Context, obj *irsav1alpha1.IRSASetup, awsClient awsclient.AwsClient) error { keyPair, err := selfhosted.CreateKeyPair() if err != nil { diff --git a/internal/kubernetes/apply.go b/internal/kubernetes/apply.go new file mode 100644 index 0000000..a4f0763 --- /dev/null +++ b/internal/kubernetes/apply.go @@ -0,0 +1,73 @@ +package kubernetes + +import ( + "context" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" +) + +type Handler struct { + cleanup bool + client client.Client + owner Owner +} + +// NewHelper returns an initialized Helper. +func NewHandler(c client.Client, owner Owner, cleanup bool) (*Handler, error) { + return &Handler{ + cleanup: cleanup, + client: c, + owner: owner, + }, nil +} + +func (h Handler) Apply(ctx context.Context, obj client.Object) error { + opts := []client.PatchOption{ + client.ForceOwnership, + client.FieldOwner(h.owner.Field), + } + gvk, err := apiutil.GVKForObject(obj, h.client.Scheme()) + if err != nil { + return err + } + + u := &unstructured.Unstructured{} + unstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + if err != nil { + return err + } + u.Object = unstructured + u.SetGroupVersionKind(gvk) + u.SetManagedFields(nil) + err = h.client.Patch(ctx, u, client.Apply, opts...) + if err != nil { + return err + } + return nil +} + +func (h Handler) PatchStatus(ctx context.Context, obj client.Object) error { + opts := &client.SubResourcePatchOptions{ + PatchOptions: client.PatchOptions{ + FieldManager: h.owner.Field, + }, + } + gvk, err := apiutil.GVKForObject(obj, h.client.Scheme()) + if err != nil { + return err + } + + u := &unstructured.Unstructured{} + unstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + if err != nil { + return err + } + u.Object = unstructured + u.SetGroupVersionKind(gvk) + u.SetManagedFields(nil) + return h.client.Status().Patch(ctx, u, client.Apply, opts) +} diff --git a/internal/kubernetes/delete.go b/internal/kubernetes/delete.go new file mode 100644 index 0000000..142ded7 --- /dev/null +++ b/internal/kubernetes/delete.go @@ -0,0 +1,69 @@ +package kubernetes + +import ( + "context" + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// DeleteOptions contains options for delete requests. +type DeleteOptions struct { + // DeletionPropagation decides how the garbage collector will handle the propagation. + DeletionPropagation metav1.DeletionPropagation + + // Inclusions determines which in-cluster objects are subject to deletion + // based on the labels. + // A nil Inclusions map means all objects are subject to deletion + Inclusions map[string]string +} + +func (h *Handler) DeleteAll(ctx context.Context, resources []*unstructured.Unstructured, opts DeleteOptions) error { + if !h.cleanup { + return nil + } + for _, r := range resources { + err := h.Delete(ctx, r, opts) + if err != nil { + return err + } + } + return nil +} + +// Delete deletes the given object (not found errors are ignored). +func (h *Handler) Delete(ctx context.Context, object *unstructured.Unstructured, opts DeleteOptions) error { + if !h.cleanup { + return nil + } + existingObject := &unstructured.Unstructured{} + existingObject.SetGroupVersionKind(object.GroupVersionKind()) + err := h.client.Get(ctx, client.ObjectKeyFromObject(object), existingObject) + if err != nil { + if !errors.IsNotFound(err) { + return fmt.Errorf("failed to delete: %w", err) + } + return nil // already deleted + } + + sel, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{MatchLabels: opts.Inclusions}) + if err != nil { + return fmt.Errorf("label selector failed: %w", err) + } + + if !sel.Matches(labels.Set(existingObject.GetLabels())) { + return nil + } + + if err := h.client.Delete(ctx, existingObject, client.PropagationPolicy(opts.DeletionPropagation)); err != nil { + return fmt.Errorf("delete failed: %w", err) + } + + return nil +} diff --git a/internal/kubernetes/owner.go b/internal/kubernetes/owner.go new file mode 100644 index 0000000..22a53b6 --- /dev/null +++ b/internal/kubernetes/owner.go @@ -0,0 +1,7 @@ +package kubernetes + +// Owner contains options for setting the field manager. +type Owner struct { + // Field sets the field manager name for the given server-side apply patch. + Field string +} diff --git a/internal/manifests/keys_secret.go b/internal/manifests/keys_secret.go new file mode 100644 index 0000000..afc040d --- /dev/null +++ b/internal/manifests/keys_secret.go @@ -0,0 +1 @@ +package manifests