Kuadrant Extension SDK — Developer Guide¶
This guide shows the minimal scaffold to build an extension: CRD, reconciler, and main wiring.
Project structure¶
my-extension/
├── main.go
├── api/
│ └── v1alpha1/
│ ├── groupversion_info.go
│ ├── mypolicy_types.go
│ └── zz_generated.deepcopy.go
├── config/
│ ├── crd/
│ │ ├── kustomization.yaml
│ │ └── bases/
│ │ └── extensions.kuadrant.io_mypolicies.yaml
│ ├── deploy/
│ │ └── kustomization.yaml
│ └── rbac/
│ ├── kustomization.yaml
│ └── role.yaml
└── internal/
└── controller/
└── mypolicy_reconciler.go
Policy type (CRD)¶
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
)
type MyPolicySpec struct {
TargetRefs []gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName `json:"targetRefs"`
CustomConfig string `json:"customConfig,omitempty"`
}
type MyPolicyStatus struct {
Conditions []metav1.Condition `json:"conditions,omitempty"`
}
//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
type MyPolicy struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec MyPolicySpec `json:"spec,omitempty"`
Status MyPolicyStatus `json:"status,omitempty"`
}
// Implement the Policy interface
func (p *MyPolicy) GetTargetRefs() []gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName {
return p.Spec.TargetRefs
}
Reconciler¶
package controller
import (
"context"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"github.com/kuadrant/kuadrant-operator/pkg/extension/types"
"your-module/api/v1alpha1"
)
type MyPolicyReconciler struct { types.ExtensionBase }
func NewMyPolicyReconciler() *MyPolicyReconciler { return &MyPolicyReconciler{} }
func (r *MyPolicyReconciler) Reconcile(ctx context.Context, req reconcile.Request, kctx types.KuadrantCtx) (reconcile.Result, error) {
pol := &v1alpha1.MyPolicy{}
if err := r.Client.Get(ctx, req.NamespacedName, pol); err != nil { return reconcile.Result{}, client.IgnoreNotFound(err) }
// Compute via CEL
addrs, err := kctx.Resolve(ctx, pol, `self.findGateways().map(g, g.status.addresses.map(a, a.value)).flatten()`, false)
if err != nil { return reconcile.Result{}, err }
// Publish a binding for request-time evaluation downstream
_ = kctx.AddDataTo(ctx, pol, types.DomainRequest, "gateway.addresses", addrs)
return reconcile.Result{}, nil
}
Main¶
package main
import (
"os"
corev1 "k8s.io/api/core/v1"
k8sruntime "k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
ctrl "sigs.k8s.io/controller-runtime"
gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
kuadrantv1 "github.com/kuadrant/kuadrant-operator/api/v1"
extcontroller "github.com/kuadrant/kuadrant-operator/pkg/extension/controller"
"your-module/api/v1alpha1"
"your-module/internal/controller"
)
var scheme = k8sruntime.NewScheme()
func init() {
utilruntime.Must(corev1.AddToScheme(scheme))
utilruntime.Must(gatewayapiv1alpha2.Install(scheme))
utilruntime.Must(kuadrantv1.AddToScheme(scheme))
utilruntime.Must(v1alpha1.AddToScheme(scheme))
}
func main() {
r := controller.NewMyPolicyReconciler()
b, logger := extcontroller.NewBuilder("my-policy-controller")
c, err := b.WithScheme(scheme).WithReconciler(r.Reconcile).For(&v1alpha1.MyPolicy{}).Owns(&corev1.ConfigMap{}).Build()
if err != nil { logger.Error(err, "unable to create controller"); os.Exit(1) }
if err = c.Start(ctrl.SetupSignalHandler()); err != nil { logger.Error(err, "unable to start extension controller"); os.Exit(1) }
}
Concrete examples in this repo¶
- Plan policy: publishes a CEL program to DomainAuth (
plan) that is evaluated by Authorino at request time. - See:
cmd/extensions/plan-policy/internal/controller/planpolicy_reconciler.go(AddDataTo withtypes.DomainAuth, keyplan). - Telemetry policy: publishes CEL label expressions to DomainRequest for request-time metrics.
- See:
cmd/extensions/telemetry-policy/internal/controller/telemetrypolicy_reconciler.go(AddDataTo withtypes.DomainRequest,types.KuadrantMetricBinding(...)).