diff --git a/cmd/main.go b/cmd/main.go index 90111ba..5fbbf21 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -40,6 +40,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" "github.com/netbirdio/kubernetes-operator/internal/controller" @@ -57,6 +58,7 @@ func init() { utilruntime.Must(netbirdiov1.AddToScheme(scheme)) utilruntime.Must(corev1.AddToScheme(scheme)) + utilruntime.Must(gatewayv1.Install(scheme)) // +kubebuilder:scaffold:scheme } @@ -274,6 +276,27 @@ func main() { os.Exit(1) } } + + if err = (&controller.GatewayClassReconciler{ + Client: mgr.GetClient(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "GatewayClass") + os.Exit(1) + } + if err = (&controller.GatewayReconciler{ + Client: mgr.GetClient(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Gateway") + os.Exit(1) + } + if err = (&controller.HTTPRouteReconciler{ + Client: mgr.GetClient(), + Netbird: netbird, + ClusterDNS: clusterDNS, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "HTTPRoute") + os.Exit(1) + } } else { setupLog.Info("netbird API key not provided, ingress capabilities disabled") } diff --git a/examples/gateway-api/gateway.yaml b/examples/gateway-api/gateway.yaml new file mode 100644 index 0000000..29714d3 --- /dev/null +++ b/examples/gateway-api/gateway.yaml @@ -0,0 +1,30 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: GatewayClass +metadata: + name: public +spec: + controllerName: "gateway.netbird.io/controller" +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: netbird + namespace: netbird +spec: + gatewayClassName: public + listeners: + - protocol: HTTP + port: 80 + name: dummy + infrastructure: + parametersRef: + group: netbird.io + kind: NBRoutingPeer + name: netbird +--- +apiVersion: netbird.io/v1 +kind: NBRoutingPeer +metadata: + name: netbird + namespace: netbird +spec: {} diff --git a/examples/gateway-api/nginx.yaml b/examples/gateway-api/nginx.yaml new file mode 100644 index 0000000..71b6440 --- /dev/null +++ b/examples/gateway-api/nginx.yaml @@ -0,0 +1,61 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: nginx + namespace: default + labels: + app: nginx +spec: + hostnames: + - nginx-test-app.eu1.netbird.services + parentRefs: + - name: netbird + namespace: netbird + rules: + - backendRefs: + - name: nginx + port: 80 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx + namespace: default + labels: + app: nginx +spec: + replicas: 1 + selector: + matchLabels: + app: nginx + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + labels: + app: nginx + spec: + containers: + - image: nginx + imagePullPolicy: Always + name: nginx +--- +apiVersion: v1 +kind: Service +metadata: + name: nginx + namespace: default + labels: + app: nginx +spec: + type: ClusterIP + ports: + - name: http + port: 80 + protocol: TCP + targetPort: 80 + selector: + app: nginx diff --git a/examples/ingress/values-kubernetes-operator.yaml b/examples/ingress/values-kubernetes-operator.yaml index 426cda5..718e19e 100644 --- a/examples/ingress/values-kubernetes-operator.yaml +++ b/examples/ingress/values-kubernetes-operator.yaml @@ -5,8 +5,6 @@ ingress: enabled: true netbirdAPI: - # Replace with valid NetBird Service Account token (PAT) - # https://docs.netbird.io/how-to/access-netbird-public-api#creating-an-access-token - #key: "nbp_m0LM9yZvDUzF8fpY20iChDOTxJgKFM3DIqmZ" - # Use keyFromSecret instead of plain text secret - keyFromSecret: "netbird-mgmt-api-key" \ No newline at end of file + keyFromSecret: + name: "netbird-mgmt-api-key" + key: "NB_API_KEY" diff --git a/go.mod b/go.mod index 7d51806..8a9d824 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( k8s.io/apimachinery v0.35.2 k8s.io/client-go v0.35.2 sigs.k8s.io/controller-runtime v0.23.3 + sigs.k8s.io/gateway-api v1.5.1 ) require ( @@ -24,14 +25,14 @@ require ( github.com/cockroachdb/cockroach-go v2.0.1+incompatible // indirect github.com/coreos/go-oidc v2.5.0+incompatible // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/jsonpointer v0.21.2 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.1 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.7.0 // indirect @@ -39,7 +40,7 @@ require ( github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -51,7 +52,7 @@ require ( github.com/prometheus/common v0.66.1 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/spf13/pflag v1.0.9 // indirect + github.com/spf13/pflag v1.0.10 // indirect github.com/x448/float16 v0.8.4 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect @@ -67,7 +68,7 @@ require ( golang.org/x/time v0.14.0 // indirect golang.org/x/tools v0.41.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect @@ -77,10 +78,10 @@ require ( k8s.io/apiextensions-apiserver v0.35.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect - k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + k8s.io/utils v0.0.0-20260108192941-914a6e750570 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index c5988f5..9d192b3 100644 --- a/go.sum +++ b/go.sum @@ -76,7 +76,6 @@ github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOum github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU= github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -103,8 +102,8 @@ github.com/eko/gocache/store/go_cache/v4 v4.2.2 h1:tAI9nl6TLoJyKG1ujF0CS0n/IgTEM github.com/eko/gocache/store/go_cache/v4 v4.2.2/go.mod h1:T9zkHokzr8K9EiC7RfMbDg6HSwaV6rv3UdcNu13SGcA= github.com/eko/gocache/store/redis/v4 v4.2.2 h1:Thw31fzGuH3WzJywsdbMivOmP550D6JS7GDHhvCJPA0= github.com/eko/gocache/store/redis/v4 v4.2.2/go.mod h1:LaTxLKx9TG/YUEybQvPMij++D7PBTIJ4+pzvk0ykz0w= -github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= -github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= @@ -131,14 +130,12 @@ github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/jsonpointer v0.21.2 h1:AqQaNADVwq/VnkCmQg6ogE+M3FOsKTytwges0JdwVuA= +github.com/go-openapi/jsonpointer v0.21.2/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= @@ -217,11 +214,8 @@ github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zt github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= @@ -236,8 +230,8 @@ github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tA github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= @@ -327,20 +321,15 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= -github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= -github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/testcontainers/testcontainers-go v0.37.0 h1:L2Qc0vkTw2EHWQ08djon0D2uw7Z/PtHS/QzZZ5Ra/hg= @@ -439,8 +428,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -481,15 +470,17 @@ k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20260108192941-914a6e750570 h1:JT4W8lsdrGENg9W+YwwdLJxklIuKWdRm+BC+xt33FOY= +k8s.io/utils v0.0.0-20260108192941-914a6e750570/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80= sigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0= +sigs.k8s.io/gateway-api v1.5.1 h1:RqVRIlkhLhUO8wOHKTLnTJA6o/1un4po4/6M1nRzdd0= +sigs.k8s.io/gateway-api v1.5.1/go.mod h1:GvCETiaMAlLym5CovLxGjS0NysqFk3+Yuq3/rh6QL2o= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 h1:2WOzJpHUBVrrkDjU4KBT8n5LDcj824eX0I5UKcgeRUs= -sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/internal/controller/gateway_controller.go b/internal/controller/gateway_controller.go new file mode 100644 index 0000000..3a7ad98 --- /dev/null +++ b/internal/controller/gateway_controller.go @@ -0,0 +1,212 @@ +/* +Copyright 2025. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + "fmt" + "slices" + "time" + + netbird "github.com/netbirdio/netbird/shared/management/client/rest" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "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" +) + +const ( + GatewayFinalizer = "gateway.netbird.io/gateway" +) + +type GatewayReconciler struct { + client.Client +} + +func (r *GatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + gw := gatewayv1.Gateway{} + err := r.Get(ctx, req.NamespacedName, &gw) + if err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + // Check if referenced class belongs to this controller. + gwc := &gatewayv1.GatewayClass{} + nn := types.NamespacedName{ + Name: string(gw.Spec.GatewayClassName), + } + err = r.Get(ctx, nn, gwc) + if err != nil { + return ctrl.Result{}, err + } + if string(gwc.Spec.ControllerName) != GatewayControllerName { + return ctrl.Result{}, nil + } + if !meta.IsStatusConditionTrue(gwc.Status.Conditions, string(gatewayv1.GatewayClassConditionStatusAccepted)) { + return ctrl.Result{RequeueAfter: 5 * time.Second}, nil + } + + // Handle resource deletion. + if !gw.DeletionTimestamp.IsZero() { + return r.reconcileDelete(ctx, gw) + } + + // Verify Gateway configuration. + if gw.Spec.Infrastructure == nil || gw.Spec.Infrastructure.ParametersRef == nil { + cond := metav1.Condition{ + Type: string(gatewayv1.GatewayConditionAccepted), + Status: metav1.ConditionFalse, + Reason: string(gatewayv1.GatewayReasonInvalidParameters), + Message: "Gateway expected to reference a NBRoutingPeer", + } + if meta.SetStatusCondition(&gw.Status.Conditions, cond) { + err = r.Status().Update(ctx, &gw) + if err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, nil + } + return ctrl.Result{}, nil + } + parametersRef := gw.Spec.Infrastructure.ParametersRef + if parametersRef.Group != "netbird.io" && parametersRef.Kind != "NBRoutingPeer" { + cond := metav1.Condition{ + Type: string(gatewayv1.GatewayConditionAccepted), + Status: metav1.ConditionFalse, + Reason: string(gatewayv1.GatewayReasonInvalidParameters), + Message: fmt.Sprintf("unsupported parameter group and kind %s.%s", parametersRef.Group, parametersRef.Kind), + } + if meta.SetStatusCondition(&gw.Status.Conditions, cond) { + err = r.Status().Update(ctx, &gw) + if err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, nil + } + return ctrl.Result{}, nil + } + + cond := metav1.Condition{ + Type: string(gatewayv1.GatewayConditionAccepted), + Status: metav1.ConditionTrue, + Reason: string(gatewayv1.GatewayReasonAccepted), + } + if meta.SetStatusCondition(&gw.Status.Conditions, cond) { + err = r.Status().Update(ctx, &gw) + if err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, nil + } + if controllerutil.AddFinalizer(&gw, GatewayFinalizer) { + err = r.Client.Update(ctx, &gw) + if err != nil { + return ctrl.Result{}, err + } + } + + // Ensure routing peer is ready. + nbrp := &netbirdiov1.NBRoutingPeer{} + err = r.Get(ctx, types.NamespacedName{Namespace: req.Namespace, Name: parametersRef.Name}, nbrp) + if err != nil { + return ctrl.Result{}, err + } + idx := slices.IndexFunc(nbrp.Status.Conditions, func(cond netbirdiov1.NBCondition) bool { + return cond.Type == netbirdiov1.NBSetupKeyReady + }) + if idx == -1 || nbrp.Status.Conditions[idx].Status != corev1.ConditionStatus(metav1.ConditionTrue) { + cond := metav1.Condition{ + Type: string(gatewayv1.GatewayConditionProgrammed), + Status: metav1.ConditionFalse, + Reason: string(gatewayv1.GatewayReasonProgrammed), + Message: fmt.Sprintf("NBRoutingPeer %s is not ready", parametersRef.Name), + } + if meta.SetStatusCondition(&gw.Status.Conditions, cond) { + err = r.Status().Update(ctx, &gw) + if err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, nil + } + return ctrl.Result{RequeueAfter: 5 * time.Second}, nil + } + + // Signal Gateway is programmed. + cond = metav1.Condition{ + Type: string(gatewayv1.GatewayConditionProgrammed), + Status: metav1.ConditionTrue, + Reason: string(gatewayv1.GatewayReasonProgrammed), + } + if meta.SetStatusCondition(&gw.Status.Conditions, cond) { + err = r.Status().Update(ctx, &gw) + if err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, nil + } + + return ctrl.Result{}, nil +} + +func (r *GatewayReconciler) reconcileDelete(ctx context.Context, gw gatewayv1.Gateway) (ctrl.Result, error) { + var httpRouteList gatewayv1.HTTPRouteList + err := r.Client.List(ctx, &httpRouteList) + if err != nil { + return ctrl.Result{}, err + } + gvk := gw.GroupVersionKind() + for _, route := range httpRouteList.Items { + for _, ref := range route.Spec.ParentRefs { + group := gvk.Group + if ref.Group != nil { + group = string(*ref.Group) + } + kind := gvk.Kind + if ref.Kind != nil { + kind = string(*ref.Kind) + } + namespace := route.Namespace + if ref.Namespace != nil { + namespace = string(*ref.Namespace) + } + if group == gvk.Group && kind == gvk.Kind && namespace == gw.Namespace && string(ref.Name) == gw.Name { + return ctrl.Result{RequeueAfter: 5 * time.Second}, nil + } + } + } + + if controllerutil.RemoveFinalizer(&gw, GatewayFinalizer) { + err := r.Client.Update(ctx, &gw) + if err != nil && !netbird.IsNotFound(err) { + return ctrl.Result{}, err + } + } + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *GatewayReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&gatewayv1.Gateway{}). + Complete(r) +} diff --git a/internal/controller/gatewayclass_controller.go b/internal/controller/gatewayclass_controller.go new file mode 100644 index 0000000..9e75345 --- /dev/null +++ b/internal/controller/gatewayclass_controller.go @@ -0,0 +1,107 @@ +package controller + +import ( + "context" + "time" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + 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" +) + +const ( + GatewayClassFinalizer = "gateway.netbird.io/gatewayclass" + GatewayControllerName = "gateway.netbird.io/controller" +) + +type GatewayClassReconciler struct { + client.Client +} + +func (r *GatewayClassReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + gwc := gatewayv1.GatewayClass{} + err := r.Client.Get(ctx, req.NamespacedName, &gwc) + if err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + // Controller name does not match. + if gwc.Spec.ControllerName != GatewayControllerName { + return ctrl.Result{}, nil + } + + // Gateway class is being deleted. + if !gwc.GetDeletionTimestamp().IsZero() { + return r.reconcileDelete(ctx, gwc) + } + + // Validate configuration. + if gwc.Spec.ParametersRef != nil { + cond := metav1.Condition{ + Type: string(gatewayv1.GatewayClassConditionStatusAccepted), + Status: metav1.ConditionFalse, + Reason: string(gatewayv1.GatewayClassReasonInvalidParameters), + Message: "Parameters references is not supported.", + } + if meta.SetStatusCondition(&gwc.Status.Conditions, cond) { + err = r.Client.Status().Update(ctx, &gwc) + if err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, nil + } + } + + // Add finalizer to validate deletion. + if controllerutil.AddFinalizer(&gwc, GatewayClassFinalizer) { + err = r.Client.Update(ctx, &gwc) + if err != nil { + return ctrl.Result{}, err + } + } + + // Set condition to accepted. + cond := metav1.Condition{ + Type: string(gatewayv1.GatewayClassConditionStatusAccepted), + Status: metav1.ConditionTrue, + Reason: string(gatewayv1.GatewayClassReasonAccepted), + Message: "Reconciled by Netbird Operator.", + } + meta.SetStatusCondition(&gwc.Status.Conditions, cond) + err = r.Client.Status().Update(ctx, &gwc) + if err != nil { + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil +} + +func (r *GatewayClassReconciler) reconcileDelete(ctx context.Context, gwc gatewayv1.GatewayClass) (ctrl.Result, error) { + var gatewayList gatewayv1.GatewayList + err := r.Client.List(ctx, &gatewayList) + if err != nil { + return ctrl.Result{}, err + } + for _, gw := range gatewayList.Items { + if string(gw.Spec.GatewayClassName) == gwc.ObjectMeta.Name { + return ctrl.Result{RequeueAfter: 5 * time.Second}, nil + } + } + if controllerutil.RemoveFinalizer(&gwc, GatewayClassFinalizer) { + err = r.Client.Update(ctx, &gwc) + if err != nil { + return ctrl.Result{}, err + } + } + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *GatewayClassReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&gatewayv1.GatewayClass{}). + Complete(r) +} diff --git a/internal/controller/httproute_controller.go b/internal/controller/httproute_controller.go new file mode 100644 index 0000000..a8e6ec7 --- /dev/null +++ b/internal/controller/httproute_controller.go @@ -0,0 +1,286 @@ +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) +} diff --git a/internal/controller/nbpolicy_controller.go b/internal/controller/nbpolicy_controller.go index a116caa..f6757e7 100644 --- a/internal/controller/nbpolicy_controller.go +++ b/internal/controller/nbpolicy_controller.go @@ -23,8 +23,7 @@ import ( type NBPolicyReconciler struct { client.Client - Netbird *netbird.Client - ClusterName string + Netbird *netbird.Client } var ( diff --git a/internal/controller/nbpolicy_controller_test.go b/internal/controller/nbpolicy_controller_test.go index 24b12fb..cea40c8 100644 --- a/internal/controller/nbpolicy_controller_test.go +++ b/internal/controller/nbpolicy_controller_test.go @@ -89,9 +89,8 @@ var _ = Describe("NBPolicy Controller", func() { When("Not enough information to create policy", func() { It("should not create any policy", func() { controllerReconciler := &NBPolicyReconciler{ - Client: k8sClient, - Netbird: netbirdClient, - ClusterName: "Kubernetes", + Client: k8sClient, + Netbird: netbirdClient, } mux.HandleFunc("/api/groups", func(w http.ResponseWriter, r *http.Request) { @@ -117,9 +116,8 @@ var _ = Describe("NBPolicy Controller", func() { When("Enough information to create TCP policy", func() { It("should create 1 policy", func() { controllerReconciler := &NBPolicyReconciler{ - Client: k8sClient, - Netbird: netbirdClient, - ClusterName: "Kubernetes", + Client: k8sClient, + Netbird: netbirdClient, } nbResource := &netbirdiov1.NBResource{ @@ -215,9 +213,8 @@ var _ = Describe("NBPolicy Controller", func() { When("TCP information no longer sufficient", func() { It("should delete tcp policy", func() { controllerReconciler := &NBPolicyReconciler{ - Client: k8sClient, - Netbird: netbirdClient, - ClusterName: "Kubernetes", + Client: k8sClient, + Netbird: netbirdClient, } nbpolicy.Status.ManagedServiceList = append(nbpolicy.Status.ManagedServiceList, "default/noexist") @@ -257,9 +254,8 @@ var _ = Describe("NBPolicy Controller", func() { When("Enough information to create UDP policy", func() { It("should create 1 policy", func() { controllerReconciler := &NBPolicyReconciler{ - Client: k8sClient, - Netbird: netbirdClient, - ClusterName: "Kubernetes", + Client: k8sClient, + Netbird: netbirdClient, } nbResource := &netbirdiov1.NBResource{ @@ -355,9 +351,8 @@ var _ = Describe("NBPolicy Controller", func() { When("UDP information no longer sufficient", func() { It("should delete udp policy", func() { controllerReconciler := &NBPolicyReconciler{ - Client: k8sClient, - Netbird: netbirdClient, - ClusterName: "Kubernetes", + Client: k8sClient, + Netbird: netbirdClient, } nbpolicy.Status.ManagedServiceList = append(nbpolicy.Status.ManagedServiceList, "default/noexist") @@ -397,9 +392,8 @@ var _ = Describe("NBPolicy Controller", func() { When("Existing protocol gets restricted", func() { It("Should delete protocol policy", func() { controllerReconciler := &NBPolicyReconciler{ - Client: k8sClient, - Netbird: netbirdClient, - ClusterName: "Kubernetes", + Client: k8sClient, + Netbird: netbirdClient, } nbResource := &netbirdiov1.NBResource{ @@ -477,9 +471,8 @@ var _ = Describe("NBPolicy Controller", func() { It("Should give all information to Update method", func() { controllerReconciler := &NBPolicyReconciler{ - Client: k8sClient, - Netbird: netbirdClient, - ClusterName: "Kubernetes", + Client: k8sClient, + Netbird: netbirdClient, } nbResource := &netbirdiov1.NBResource{ @@ -595,9 +588,8 @@ var _ = Describe("NBPolicy Controller", func() { When("NBPolicy is set for deletion", func() { It("should delete Policies", func() { controllerReconciler := &NBPolicyReconciler{ - Client: k8sClient, - Netbird: netbirdClient, - ClusterName: "Kubernetes", + Client: k8sClient, + Netbird: netbirdClient, } nbpolicy.Status.TCPPolicyID = util.Ptr("policyidtcp")