Files
netbird-kubernetes-operator/internal/controller/httproute_controller.go
T

287 lines
8.1 KiB
Go
Raw Normal View History

2026-03-19 13:01:58 +01:00
package controller
import (
"context"
"encoding/json"
"fmt"
"strings"
netbird "github.com/netbirdio/netbird/shared/management/client/rest"
"github.com/netbirdio/netbird/shared/management/http/api"
"k8s.io/apimachinery/pkg/api/meta"
"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"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
"github.com/netbirdio/kubernetes-operator/internal/util"
)
const (
HTTPRouteFinalizer = "gateway.netbird.io/httproute"
ResourceIDAnnotationKey = "gateway.netbird.io/resource-ids"
ProxyIDAnnotationKey = "gateway.netbird.io/proxy-ids"
)
type HTTPRouteReconciler struct {
client.Client
Netbird *netbird.Client
ClusterDNS string
}
// nolint:gocyclo
func (r *HTTPRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := ctrl.Log.WithName("HTTPRoute").WithValues("namespace", req.Namespace, "name", req.Name)
hr := gatewayv1.HTTPRoute{}
err := r.Get(ctx, req.NamespacedName, &hr)
if err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
if !hr.DeletionTimestamp.IsZero() {
return r.reconcileDelete(ctx, hr)
}
for _, parent := range hr.Spec.ParentRefs {
// Check if controller is responsible for route.
parentNamespace := hr.Namespace
if parent.Namespace != nil {
parentNamespace = string(*parent.Namespace)
}
gw := &gatewayv1.Gateway{}
err = r.Client.Get(ctx, types.NamespacedName{Namespace: parentNamespace, Name: string(parent.Name)}, gw)
if err != nil {
return ctrl.Result{}, err
}
gwc := &gatewayv1.GatewayClass{}
err := r.Get(ctx, client.ObjectKey{Name: string(gw.Spec.GatewayClassName)}, gwc)
if err != nil {
return ctrl.Result{}, err
}
if gwc.Spec.ControllerName != GatewayControllerName {
continue
}
if !meta.IsStatusConditionTrue(gw.Status.Conditions, string(gatewayv1.GatewayConditionProgrammed)) {
logger.Info("gateway is not ready", "name", gw.ObjectMeta.Name)
return ctrl.Result{Requeue: true}, nil
}
nbrp := &netbirdiov1.NBRoutingPeer{}
err = r.Get(ctx, types.NamespacedName{Namespace: gw.Namespace, Name: gw.Spec.Infrastructure.ParametersRef.Name}, nbrp)
if err != nil {
return ctrl.Result{}, err
}
if controllerutil.AddFinalizer(&hr, HTTPRouteFinalizer) {
err = r.Client.Update(ctx, &hr)
if err != nil {
return ctrl.Result{}, err
}
}
// Create network resources.
oldResourceIDs := map[string]string{}
if s, ok := hr.Annotations[ResourceIDAnnotationKey]; ok {
err := json.Unmarshal([]byte(s), &oldResourceIDs)
if err != nil {
return ctrl.Result{}, err
}
}
resourceIDs := map[string]string{}
targets := []api.ServiceTarget{}
for _, rule := range hr.Spec.Rules {
for _, ref := range rule.BackendRefs {
// TODO (phillebaba): Support reference grants.
refNamespace := hr.Namespace
key := strings.Join([]string{string(ref.Name), refNamespace}, "/")
networkResourceReq := api.NetworkResourceRequest{
Name: fmt.Sprintf("%s/%s/%s/%s", refNamespace, gw.Name, hr.Name, ref.Name),
Enabled: true,
Address: fmt.Sprintf("%s.%s.%s", ref.Name, refNamespace, r.ClusterDNS),
Groups: []string{},
}
id, err := func() (string, error) {
if id, ok := oldResourceIDs[key]; ok {
_, err := r.Netbird.Networks.Resources(*nbrp.Status.NetworkID).Get(ctx, id)
if err != nil && !netbird.IsNotFound(err) {
return "", err
}
if err == nil {
_, err = r.Netbird.Networks.Resources(*nbrp.Status.NetworkID).Update(ctx, id, networkResourceReq)
if err != nil {
return "", err
}
delete(oldResourceIDs, key)
return id, nil
}
}
resource, err := r.Netbird.Networks.Resources(*nbrp.Status.NetworkID).Create(ctx, networkResourceReq)
if err != nil {
return "", err
}
return resource.Id, nil
}()
if err != nil {
return ctrl.Result{}, err
}
resourceIDs[key] = id
target := api.ServiceTarget{
Enabled: true,
Path: nil,
TargetId: id,
Protocol: "http",
TargetType: "domain",
}
targets = append(targets, target)
}
}
// Create proxy service.
oldProxyIDs := map[string]string{}
if s, ok := hr.Annotations[ProxyIDAnnotationKey]; ok {
err := json.Unmarshal([]byte(s), &oldProxyIDs)
if err != nil {
return ctrl.Result{}, err
}
}
proxyIDs := map[string]string{}
for _, hostname := range hr.Spec.Hostnames {
proxyCreate := api.PostApiReverseProxiesServicesJSONRequestBody{
Auth: api.ServiceAuthConfig{},
Domain: string(hostname),
Enabled: true,
Name: string(hostname),
PassHostHeader: util.Ptr(false),
RewriteRedirects: util.Ptr(false),
Targets: targets,
}
id, err := func() (string, error) {
if id, ok := oldProxyIDs[string(hostname)]; ok {
_, err := r.Netbird.ReverseProxyServices.Get(ctx, id)
if err != nil && !netbird.IsNotFound(err) {
return "", err
}
if err == nil {
_, err := r.Netbird.ReverseProxyServices.Update(ctx, id, proxyCreate)
if err != nil {
return "", nil
}
delete(oldProxyIDs, string(hostname))
return id, nil
}
}
proxy, err := r.Netbird.ReverseProxyServices.Create(ctx, proxyCreate)
if err != nil {
return "", err
}
return proxy.Id, nil
}()
if err != nil {
return ctrl.Result{}, err
}
proxyIDs[string(hostname)] = id
}
for _, id := range oldResourceIDs {
err = r.Netbird.Networks.Resources(*nbrp.Status.NetworkID).Delete(ctx, id)
if err != nil && !netbird.IsNotFound(err) {
return ctrl.Result{}, err
}
}
for _, id := range oldProxyIDs {
err = r.Netbird.ReverseProxyServices.Delete(ctx, id)
if err != nil && !netbird.IsNotFound(err) {
return ctrl.Result{}, err
}
}
b, err := json.Marshal(resourceIDs)
if err != nil {
return ctrl.Result{}, err
}
hr.Annotations[ResourceIDAnnotationKey] = string(b)
b, err = json.Marshal(proxyIDs)
if err != nil {
return ctrl.Result{}, err
}
hr.Annotations[ProxyIDAnnotationKey] = string(b)
err = r.Client.Update(ctx, &hr)
if err != nil {
return ctrl.Result{}, err
}
}
return ctrl.Result{}, nil
}
func (r *HTTPRouteReconciler) reconcileDelete(ctx context.Context, hr gatewayv1.HTTPRoute) (ctrl.Result, error) {
for _, parent := range hr.Spec.ParentRefs {
parentNamespace := hr.Namespace
if parent.Namespace != nil {
parentNamespace = string(*parent.Namespace)
}
gw := &gatewayv1.Gateway{}
err := r.Client.Get(ctx, types.NamespacedName{Namespace: parentNamespace, Name: string(parent.Name)}, gw)
if err != nil {
return ctrl.Result{}, err
}
nbrp := &netbirdiov1.NBRoutingPeer{}
err = r.Get(ctx, types.NamespacedName{Namespace: gw.Namespace, Name: gw.Spec.Infrastructure.ParametersRef.Name}, nbrp)
if err != nil {
return ctrl.Result{}, err
}
proxyIDs := map[string]string{}
if s, ok := hr.Annotations[ProxyIDAnnotationKey]; ok {
err := json.Unmarshal([]byte(s), &proxyIDs)
if err != nil {
return ctrl.Result{}, err
}
}
for _, id := range proxyIDs {
err = r.Netbird.ReverseProxyServices.Delete(ctx, id)
if err != nil && !netbird.IsNotFound(err) {
return ctrl.Result{}, err
}
}
resourceIDs := map[string]string{}
if s, ok := hr.Annotations[ResourceIDAnnotationKey]; ok {
err := json.Unmarshal([]byte(s), &resourceIDs)
if err != nil {
return ctrl.Result{}, err
}
}
for _, id := range resourceIDs {
err = r.Netbird.Networks.Resources(*nbrp.Status.NetworkID).Delete(ctx, id)
if err != nil && !netbird.IsNotFound(err) {
return ctrl.Result{}, err
}
}
if controllerutil.RemoveFinalizer(&hr, HTTPRouteFinalizer) {
err := r.Client.Update(ctx, &hr)
if err != nil {
return ctrl.Result{}, err
}
}
}
return ctrl.Result{}, nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *HTTPRouteReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&gatewayv1.HTTPRoute{}).
Complete(r)
}