mirror of
https://github.com/YuzuZensai/netbird-kubernetes-operator.git
synced 2026-09-13 10:49:15 +00:00
This change adds support for the new proxy service to the operator through Gateway API. This change attempts to standardize concepts around the Gateway API to allow for compatibility with other projects. Fixes #111 Fixes #44 Signed-off-by: Philip Laine <philip.laine@gmail.com>
287 lines
8.1 KiB
Go
287 lines
8.1 KiB
Go
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)
|
|
}
|