basereconciler

module
v0.5.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 21, 2024 License: Apache-2.0

README

basereconciler

Basereconciler is an attempt to create a reconciler that can be imported and used in any controller-runtime based controller to perform the most common tasks a controller usually performs. It's a bunch of code that it's typically written again and again for every and each controller and that can be abstracted to work in a more generic way to avoid the repetition and improve code mantainability. At the moment basereconciler can perform the following tasks:

  • Get the custom resource and perform some common tasks on it:
    • Management of initialization logic: custom initialization functions can be passed to perform initialization tasks on the custom resource. Initialization can be done persisting changes in the API server (use reconciler.WithInitializationFunc) or without persisting them (reconciler.WithInMemoryInitializationFunc).
    • Management of resource finalizer: some custom resources required more complex finalization logic. For this to happen a finalizer must be in place. Basereconciler can keep this finalizer in place and remove it when necessary during resource finalization.
    • Management of finalization logic: it checks if the resource is being finalized and executed the finalization logic passed to it if that is the case. When all finalization logic is completed it removes the finalizer on the custom resource.
  • Reconcile resources owned by the custom resource: basereconciler can keep the owned resources of a custom resource in it's desired state. It works for any resource type, and only requires that the user configures how each specific resource type has to be configured. The resource reconciler only works in "update mode" right now, so any operation to transition a given resource from its live state to its desired state will be an Update. We might add a "patch mode" in the future.
  • Reconcile custom resource status: if the custom resource implements a certain interface, basereconciler can also be in charge of reconciling the status.
  • Resource pruner: when the reconciler stops seeing a certain resource, owned by the custom resource, it will prune them as it understands that the resource is no longer required. The resource pruner can be disabled globally or enabled/disabled on a per resource basis based on an annotation.

Basic Usage

The following example is a kubebuilder bootstrapped controller that uses basereconciler to reconcile several resources owned by a custom resource. Explanations inline in the code.

package controllers

import (
	"context"

	"github.com/3scale-ops/basereconciler/config"
	"github.com/3scale-ops/basereconciler/mutators"
	"github.com/3scale-ops/basereconciler/reconciler"
	"github.com/3scale-ops/basereconciler/resource"
	"k8s.io/apimachinery/pkg/runtime/schema"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"

	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	webappv1 "my.domain/guestbook/api/v1"
)

// Use the init function to configure the behavior of the controller. In this case we use
// "SetDefaultReconcileConfigForGVK" to specify the paths that need to be reconciled/ignored
// for each resource type. Check the "github.com/3scale-ops/basereconciler/config" for more
// configuration options
func init() {
	config.SetDefaultReconcileConfigForGVK(
		schema.FromAPIVersionAndKind("apps/v1", "Deployment"),
		config.ReconcileConfigForGVK{
			EnsureProperties: []string{
				"metadata.annotations",
				"metadata.labels",
				"spec",
			},
			IgnoreProperties: []string{
				"metadata.annotations['deployment.kubernetes.io/revision']",
			},
		})
	config.SetDefaultReconcileConfigForGVK(
		// specifying a config for an empty GVK will
		// set a default fallback config for any gvk that is not
		// explicitely declared in the configuration. Think of it
		// as a wildcard.
		schema.GroupVersionKind{},
		config.ReconcileConfigForGVK{
			EnsureProperties: []string{
				"metadata.annotations",
				"metadata.labels",
				"spec",
			},
		})
}

// GuestbookReconciler reconciles a Guestbook object
type GuestbookReconciler struct {
	*reconciler.Reconciler
}

// +kubebuilder:rbac:groups=webapp.my.domain,resources=guestbooks,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=webapp.my.domain,resources=guestbooks/status,verbs=get;update;patch
// +kubebuilder:rbac:groups="core",namespace=placeholder,resources=services,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="apps",namespace=placeholder,resources=deployments,verbs=get;list;watch;create;update;patch;delete

func (r *GuestbookReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	// configure the logger for the controller. The function also returns a modified
	// copy of the context that includes the logger so it's easily passed around to other functions.
	ctx, logger := r.Logger(ctx, "guestbook", req.NamespacedName)

	// ManageResourceLifecycle will take care of retrieving the custom resoure from the API. It is also in charge of the resource
	// lifecycle: initialization and finalization logic. In this example, we are configuring a finalizer in our custom resource and passing
	// a finalization function that will casuse a log line to show when the resource is being deleted.
	guestbook := &webappv1.Guestbook{}
	result := r.ManageResourceLifecycle(ctx, req, guestbook,
		reconciler.WithInitializationFunc(
			func(context.Context, client.Client) error {
				logger.Info("initializing resource")
				return nil
			}),
		reconciler.WithFinalizer("guestbook-finalizer"),
		reconciler.WithFinalizationFunc(
			func(context.Context, client.Client) error {
				logger.Info("finalizing resource")
				return nil
			}),
	)
	if result.ShouldReturn() {
		return result.Values()
	}

	// ReconcileOwnedResources creates/updates/deletes the resoures that our custom resource owns.
	// It is a list of templates, in this case generated from the base of an object we provide.
	// Modifiers can be added to the template to get live values from the k8s API, like in this example
	// with the Service. Check the documentation of the "github.com/3scale-ops/basereconciler/resource"
	// for more information on building templates.
	result = r.ReconcileOwnedResources(ctx, guestbook, []resource.TemplateInterface{

		resource.NewTemplateFromObjectFunction[*appsv1.Deployment](
			func() *appsv1.Deployment {
				return &appsv1.Deployment{
					// define your object here
				}
			}),

		resource.NewTemplateFromObjectFunction[*corev1.Service](
			func() *corev1.Service {
				return &corev1.Service{
					// define your object here
				}
			}).
			// Retrieve the live values that kube-controller-manager sets
			// in the Service spec to avoid overwrting them
			WithMutation(mutators.SetServiceLiveValues()).
			// There are some useful mutations in the "github.com/3scale-ops/basereconciler/mutators"
			// package or you can pass your own mutation functions
			WithMutation(func(ctx context.Context, cl client.Client, desired client.Object) error {
				// your mutation logic here
				return nil
			}).
			// The templates are reconciled using the global config defined in the init() function
			// but in this case we are passing a custom config that will apply
			// only to the reconciliation of this template
			WithEnsureProperties([]resource.Property{"spec"}).
			WithIgnoreProperties([]resource.Property{"spec.clusterIP", "spec.clusterIPs"}),
	})

	if result.ShouldReturn() {
		return result.Values()
	}

	return ctrl.Result{}, nil
}

func (r *GuestbookReconciler) SetupWithManager(mgr ctrl.Manager) error {
	// SetupWithDynamicTypeWatches will configure the controller to dynamically
	// watch for any resource type that the controller owns.
	return reconciler.SetupWithDynamicTypeWatches(r,
		ctrl.NewControllerManagedBy(mgr).
			For(&webappv1.Guestbook{}),
	)
}

Then you just need to register the controller with the controller-runtime manager and you are all set!

[...]
	if err = (&controllers.GuestbookReconciler{
		Reconciler: reconciler.NewFromManager(mgr).WithLogger(ctrl.Log.WithName("controllers").WithName("Guestbook")),
	}).SetupWithManager(mgr); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "Guestbook")
		os.Exit(1)
	}
[...]

Directories

Path Synopsis
Package config provides global configuration for the basereconciler.
Package config provides global configuration for the basereconciler.
Package mutators contains useful functions to perform template mutations that require to retrieve live state values from the Kubernetes API.
Package mutators contains useful functions to perform template mutations that require to retrieve live state values from the Kubernetes API.
Package reconciler contatins types and methods that can be used in controller reconciliation logic.
Package reconciler contatins types and methods that can be used in controller reconciliation logic.
Package resource contains types and methods to reconcile controller owned resources It is generalized to work with any GroupVersionKind.
Package resource contains types and methods to reconcile controller owned resources It is generalized to work with any GroupVersionKind.
Package test contains a test controller with its test suite
Package test contains a test controller with its test suite
api/v1alpha1
Package v1alpha1 is a test API definition +kubebuilder:object:generate=true +groupName=example.com
Package v1alpha1 is a test API definition +kubebuilder:object:generate=true +groupName=example.com
Package util contains utility functions used by other packages
Package util contains utility functions used by other packages

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL