mirror of
https://github.com/YuzuZensai/netbird-kubernetes-operator.git
synced 2026-09-13 18:59:09 +00:00
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)
|
||
|
|
}
|