Skip to content
Open
6 changes: 0 additions & 6 deletions docs/deletion.md

This file was deleted.

88 changes: 88 additions & 0 deletions internal/clients/k8s/object.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Package k8s provides client abstractions to the kubernetes API
package k8s

import (
"context"
"errors"
"fmt"

"github.com/go-logr/logr"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

type ObjectClient struct {
k8sClient client.Client
log *logr.Logger
}

func NewObjectClient(k8sClient client.Client, log *logr.Logger) (*ObjectClient, error) {
if k8sClient == nil {
return nil, errors.New("k8sClient cannot be nil")
}
if log == nil {
return nil, errors.New("log cannot be nil")
}
return &ObjectClient{
k8sClient: k8sClient,
log: log,
}, nil
}

// EnsureFinalizer adds `finalizer` to obj exactly once.
// - It NO-OPs if the finalizer is already there.
// - It uses a strategic-merge Patch so you don’t overwrite concurrent changes.
// - obj must be a pointer that already contains the latest copy from the API server.
func (s *ObjectClient) EnsureFinalizer(ctx context.Context, key client.ObjectKey, obj client.Object, finalizer string) error {
err := s.k8sClient.Get(ctx, key, obj)
if err != nil {
return err
}
// if it already contains the finalizer, there's nothing to do
if controllerutil.ContainsFinalizer(obj, finalizer) {
return nil
}

//nolint:revive // we know this will serialise, even if the compiler doesn't
base := obj.DeepCopyObject().(client.Object)
controllerutil.AddFinalizer(obj, finalizer)

s.log.
WithValues("finalizer", finalizer).
WithValues("object", fmt.Sprintf("%s/%s", obj.GetNamespace(), obj.GetName())).
WithValues("kind", obj.GetObjectKind().GroupVersionKind().Kind).
Info("creating finalizer")
if err := s.k8sClient.Patch(ctx, obj, client.MergeFrom(base)); err != nil {
return fmt.Errorf("could not add finalizer %q: %w", finalizer, err)
}
return nil
}

// RemoveFinalizer deletes `finalizer` from obj exactly once.
// - NO-OPs if the finalizer is already gone.
// - Uses a strategic-merge Patch so you never clobber concurrent changes.
// - obj must be a *live* copy fetched from the API server.
func (s *ObjectClient) RemoveFinalizer(ctx context.Context, key client.ObjectKey, obj client.Object, finalizer string) error {
err := s.k8sClient.Get(ctx, key, obj)
if err != nil {
return err
}
// if there is no finalizer, there's nothing to do
if !controllerutil.ContainsFinalizer(obj, finalizer) {
return nil
}

//nolint:revive // we know this will serialise, even if the compiler doesn't
base := obj.DeepCopyObject().(client.Object)
controllerutil.RemoveFinalizer(obj, finalizer)

s.log.
WithValues("finalizer", finalizer).
WithValues("object", fmt.Sprintf("%s/%s", obj.GetNamespace(), obj.GetName())).
WithValues("kind", obj.GetObjectKind().GroupVersionKind().Kind).
Info("removing finalizer")
if err := s.k8sClient.Patch(ctx, obj, client.MergeFrom(base)); err != nil {
return fmt.Errorf("could not remove finalizer %q: %w", finalizer, err)
}
return nil
}
3 changes: 2 additions & 1 deletion internal/controller/accesstunnel/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
ctrllog "sigs.k8s.io/controller-runtime/pkg/log"

networkingv1alpha1 "github.com/adyanth/cloudflare-operator/api/v1alpha1"
"github.com/go-logr/logr"

networkingv1alpha1 "github.com/adyanth/cloudflare-operator/api/v1alpha1"
)

const CONTAINER_PORT int32 = 8000
Expand Down
3 changes: 2 additions & 1 deletion internal/controller/adapter.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package controller

import (
networkingv1alpha2 "github.com/adyanth/cloudflare-operator/api/v1alpha2"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"

networkingv1alpha2 "github.com/adyanth/cloudflare-operator/api/v1alpha2"
)

// TunnelAdapter implementation
Expand Down
45 changes: 41 additions & 4 deletions internal/controller/clustertunnel_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"

"github.com/adyanth/cloudflare-operator/internal/clients/cf"
"github.com/adyanth/cloudflare-operator/internal/clients/k8s"

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
Expand All @@ -30,8 +31,9 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
ctrllog "sigs.k8s.io/controller-runtime/pkg/log"

networkingv1alpha2 "github.com/adyanth/cloudflare-operator/api/v1alpha2"
"github.com/go-logr/logr"

networkingv1alpha2 "github.com/adyanth/cloudflare-operator/api/v1alpha2"
)

// ClusterTunnelReconciler reconciles a ClusterTunnel object
Expand Down Expand Up @@ -145,9 +147,25 @@ func (r *ClusterTunnelReconciler) Reconcile(ctx context.Context, req ctrl.Reques
if err := r.Get(ctx, req.NamespacedName, tunnel); err != nil {
if apierrors.IsNotFound(err) {
// Tunnel object not found, could have been deleted after reconcile request.
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
// Return and don't requeue
r.log.Info("Tunnel deleted, nothing to do")
// Owned objects are automatically garbage collected.
objectClient, err := k8s.NewObjectClient(r.Client, &r.log)
if err != nil {
return ctrl.Result{}, err
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic is in the wrong place (ref: the error you saw). When this condition is hit, the tunnel is already removed from etcd.

What you need is, instead of the logic in both here and tunnel_controller.go, move this to

func cleanupTunnel(r GenericTunnelReconciler) (ctrl.Result, bool, error) {
if controllerutil.ContainsFinalizer(r.GetTunnel().GetObject(), tunnelFinalizer) {
// Run finalization logic. If the finalization logic fails,
// don't remove the finalizer so that we can retry during the next reconciliation.
r.GetLog().Info("starting deletion cycle")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, derp. Thanks will fix this

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you get around to this? Would love to merge this in

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't sorry, life has been a lot this past month! Hoping to get to this next weekend

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All good. Life comes first!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wanted to give you another heads up, might be another week

}
// ensure the secret associated with the tunnel has the finalizer removed
secret := &corev1.Secret{}
err = objectClient.RemoveFinalizer(
ctx,
client.ObjectKey{
Namespace: r.GetTunnel().GetNamespace(),
Name: r.GetTunnel().GetSpec().Cloudflare.Secret,
},
secret,
tunnelFinalizer,
)
if err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
r.log.Error(err, "unable to fetch Tunnel")
Expand All @@ -158,6 +176,25 @@ func (r *ClusterTunnelReconciler) Reconcile(ctx context.Context, req ctrl.Reques
return ctrl.Result{}, err
}

objectClient, err := k8s.NewObjectClient(r.Client, &r.log)
if err != nil {
return ctrl.Result{}, err
}
// ensure the secret associated with the tunnel has a finalizer
secret := &corev1.Secret{}
err = objectClient.EnsureFinalizer(
ctx,
client.ObjectKey{
Namespace: r.GetTunnel().GetNamespace(),
Name: r.GetTunnel().GetSpec().Cloudflare.Secret,
},
secret,
tunnelFinalizer,
)
if err != nil {
return ctrl.Result{}, err
}

if res, ok, err := setupTunnel(r); !ok {
return res, err
}
Expand Down
3 changes: 2 additions & 1 deletion internal/controller/tunnel.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package controller

import (
networkingv1alpha2 "github.com/adyanth/cloudflare-operator/api/v1alpha2"
"sigs.k8s.io/controller-runtime/pkg/client"

networkingv1alpha2 "github.com/adyanth/cloudflare-operator/api/v1alpha2"
)

type Tunnel interface {
Expand Down
44 changes: 40 additions & 4 deletions internal/controller/tunnel_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"

"github.com/adyanth/cloudflare-operator/internal/clients/cf"
"github.com/adyanth/cloudflare-operator/internal/clients/k8s"

corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
Expand All @@ -28,10 +29,11 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
ctrllog "sigs.k8s.io/controller-runtime/pkg/log"

networkingv1alpha2 "github.com/adyanth/cloudflare-operator/api/v1alpha2"
"github.com/go-logr/logr"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/client-go/tools/record"

networkingv1alpha2 "github.com/adyanth/cloudflare-operator/api/v1alpha2"
)

// TunnelReconciler reconciles a Tunnel object
Expand Down Expand Up @@ -140,9 +142,24 @@ func (r *TunnelReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
if err := r.Get(ctx, req.NamespacedName, tunnel); err != nil {
if apierrors.IsNotFound(err) {
// Tunnel object not found, could have been deleted after reconcile request.
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
// Return and don't requeue
r.log.Info("Tunnel deleted, nothing to do")
// Owned objects are automatically garbage collected.
objectClient, err := k8s.NewObjectClient(r.Client, &r.log)
if err != nil {
return ctrl.Result{}, err
}
secret := &corev1.Secret{}
err = objectClient.RemoveFinalizer(
ctx,
client.ObjectKey{
Namespace: r.GetTunnel().GetNamespace(),
Name: r.GetTunnel().GetSpec().Cloudflare.Secret,
},
secret,
tunnelFinalizer,
)
if err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
r.log.Error(err, "unable to fetch Tunnel")
Expand All @@ -153,6 +170,25 @@ func (r *TunnelReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
return ctrl.Result{}, err
}

objectClient, err := k8s.NewObjectClient(r.Client, &r.log)
if err != nil {
return ctrl.Result{}, err
}
// ensure the secret associated with the tunnel has a finalizer
secret := &corev1.Secret{}
err = objectClient.EnsureFinalizer(
ctx,
client.ObjectKey{
Namespace: r.GetTunnel().GetNamespace(),
Name: r.GetTunnel().GetSpec().Cloudflare.Secret,
},
secret,
tunnelFinalizer,
)
if err != nil {
return ctrl.Result{}, err
}

if res, ok, err := setupTunnel(r); !ok {
return res, err
}
Expand Down
5 changes: 3 additions & 2 deletions internal/controller/tunnelbinding_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ import (

"github.com/adyanth/cloudflare-operator/internal/clients/cf"

networkingv1alpha1 "github.com/adyanth/cloudflare-operator/api/v1alpha1"
networkingv1alpha2 "github.com/adyanth/cloudflare-operator/api/v1alpha2"
"github.com/go-logr/logr"
yaml "gopkg.in/yaml.v3"
corev1 "k8s.io/api/core/v1"
Expand All @@ -39,6 +37,9 @@ import (
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
ctrllog "sigs.k8s.io/controller-runtime/pkg/log"

networkingv1alpha1 "github.com/adyanth/cloudflare-operator/api/v1alpha1"
networkingv1alpha2 "github.com/adyanth/cloudflare-operator/api/v1alpha2"

appsv1 "k8s.io/api/apps/v1"
"k8s.io/client-go/tools/record"
)
Expand Down