Add ingress feature to controller (#5)

Co-authored-by: Maycon Santos <mlsmaycon@gmail.com>
This commit is contained in:
M. Essam
2025-03-06 09:57:45 +01:00
committed by GitHub
co-authored by Maycon Santos
parent cea60745d2
commit 166091b8e0
54 changed files with 5992 additions and 189 deletions
+82
View File
@@ -0,0 +1,82 @@
package v1
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
netbird "github.com/netbirdio/netbird/management/client/rest"
)
// nolint:unused
// log is for logging in this package.
var nbgrouplog = logf.Log.WithName("nbgroup-resource")
// SetupNBGroupWebhookWithManager registers the webhook for NBGroup in the manager.
func SetupNBGroupWebhookWithManager(mgr ctrl.Manager, managementURL, apiKey string) error {
return ctrl.NewWebhookManagedBy(mgr).For(&netbirdiov1.NBGroup{}).
WithValidator(&NBGroupCustomValidator{netbird: netbird.New(managementURL, apiKey), client: mgr.GetClient()}).
Complete()
}
// NBGroupCustomValidator struct is responsible for validating the NBGroup resource
// when it is created, updated, or deleted.
type NBGroupCustomValidator struct {
netbird *netbird.Client
client client.Client
}
var _ webhook.CustomValidator = &NBGroupCustomValidator{}
// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type NBGroup.
func (v *NBGroupCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
return nil, nil
}
// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type NBGroup.
func (v *NBGroupCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
return nil, nil
}
// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type NBGroup.
func (v *NBGroupCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
nbgroup, ok := obj.(*netbirdiov1.NBGroup)
if !ok {
return nil, fmt.Errorf("expected a NBGroup object but got %T", obj)
}
nbgrouplog.Info("Validation for NBGroup upon deletion", "name", nbgroup.GetName())
for _, o := range nbgroup.OwnerReferences {
if o.Kind == (&netbirdiov1.NBResource{}).Kind {
var nbResource netbirdiov1.NBResource
err := v.client.Get(ctx, types.NamespacedName{Namespace: nbgroup.Namespace, Name: o.Name}, &nbResource)
if err != nil && !errors.IsNotFound(err) {
return nil, err
}
if err == nil && nbResource.DeletionTimestamp == nil {
return nil, fmt.Errorf("group attached to NBResource %s/%s", nbgroup.Namespace, o.Name)
}
}
if o.Kind == (&netbirdiov1.NBRoutingPeer{}).Kind {
var nbResource netbirdiov1.NBRoutingPeer
err := v.client.Get(ctx, types.NamespacedName{Namespace: nbgroup.Namespace, Name: o.Name}, &nbResource)
if err != nil && !errors.IsNotFound(err) {
return nil, err
}
if err == nil && nbResource.DeletionTimestamp == nil {
return nil, fmt.Errorf("group attached to NBRoutingPeer %s/%s", nbgroup.Namespace, o.Name)
}
}
}
return nil, nil
}
@@ -0,0 +1,56 @@
package v1
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
// TODO (user): Add any additional imports if needed
)
var _ = Describe("NBGroup Webhook", func() {
var (
obj *netbirdiov1.NBGroup
oldObj *netbirdiov1.NBGroup
validator NBGroupCustomValidator
)
BeforeEach(func() {
Skip("Not implemented yet")
obj = &netbirdiov1.NBGroup{}
oldObj = &netbirdiov1.NBGroup{}
validator = NBGroupCustomValidator{}
Expect(validator).NotTo(BeNil(), "Expected validator to be initialized")
Expect(oldObj).NotTo(BeNil(), "Expected oldObj to be initialized")
Expect(obj).NotTo(BeNil(), "Expected obj to be initialized")
// TODO (user): Add any setup logic common to all tests
})
AfterEach(func() {
// TODO (user): Add any teardown logic common to all tests
})
Context("When creating or updating NBGroup under Validating Webhook", func() {
// TODO (user): Add logic for validating webhooks
// Example:
// It("Should deny creation if a required field is missing", func() {
// By("simulating an invalid creation scenario")
// obj.SomeRequiredField = ""
// Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred())
// })
//
// It("Should admit creation if all required fields are present", func() {
// By("simulating an invalid creation scenario")
// obj.SomeRequiredField = "valid_value"
// Expect(validator.ValidateCreate(ctx, obj)).To(BeNil())
// })
//
// It("Should validate updates correctly", func() {
// By("simulating a valid update scenario")
// oldObj.SomeRequiredField = "updated_value"
// obj.SomeRequiredField = "updated_value"
// Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil())
// })
})
})
+70
View File
@@ -0,0 +1,70 @@
package v1
import (
"context"
"fmt"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
"github.com/netbirdio/kubernetes-operator/internal/controller"
netbird "github.com/netbirdio/netbird/management/client/rest"
)
// nolint:unused
// log is for logging in this package.
var nbresourcelog = logf.Log.WithName("nbresource-resource")
// SetupNBResourceWebhookWithManager registers the webhook for NBResource in the manager.
func SetupNBResourceWebhookWithManager(mgr ctrl.Manager, managementURL, apiKey string) error {
return ctrl.NewWebhookManagedBy(mgr).For(&netbirdiov1.NBResource{}).
WithValidator(&NBResourceCustomValidator{netbird: netbird.New(managementURL, apiKey), client: mgr.GetClient()}).
Complete()
}
// NBResourceCustomValidator struct is responsible for validating the NBResource resource
// when it is created, updated, or deleted.
type NBResourceCustomValidator struct {
netbird *netbird.Client
client client.Client
}
var _ webhook.CustomValidator = &NBResourceCustomValidator{}
// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type NBResource.
func (v *NBResourceCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
return nil, nil
}
// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type NBResource.
func (v *NBResourceCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
return nil, nil
}
// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type NBResource.
func (v *NBResourceCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
nbresource, ok := obj.(*netbirdiov1.NBResource)
if !ok {
return nil, fmt.Errorf("expected a NBResource object but got %T", obj)
}
nbresourcelog.Info("Validation for NBResource upon deletion", "name", nbresource.GetName())
var svc corev1.Service
err := v.client.Get(ctx, types.NamespacedName{Namespace: nbresource.Namespace, Name: nbresource.Name}, &svc)
if err != nil {
return nil, err
}
if _, ok := svc.Annotations[controller.ServiceExposeAnnotation]; ok && svc.DeletionTimestamp == nil {
return nil, fmt.Errorf("service %s/%s still has netbird.io/expose annotation", svc.Namespace, svc.Name)
}
return nil, nil
}
@@ -0,0 +1,56 @@
package v1
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
// TODO (user): Add any additional imports if needed
)
var _ = Describe("NBResource Webhook", func() {
var (
obj *netbirdiov1.NBResource
oldObj *netbirdiov1.NBResource
validator NBResourceCustomValidator
)
BeforeEach(func() {
Skip("Not implemented yet")
obj = &netbirdiov1.NBResource{}
oldObj = &netbirdiov1.NBResource{}
validator = NBResourceCustomValidator{}
Expect(validator).NotTo(BeNil(), "Expected validator to be initialized")
Expect(oldObj).NotTo(BeNil(), "Expected oldObj to be initialized")
Expect(obj).NotTo(BeNil(), "Expected obj to be initialized")
// TODO (user): Add any setup logic common to all tests
})
AfterEach(func() {
// TODO (user): Add any teardown logic common to all tests
})
Context("When creating or updating NBResource under Validating Webhook", func() {
// TODO (user): Add logic for validating webhooks
// Example:
// It("Should deny creation if a required field is missing", func() {
// By("simulating an invalid creation scenario")
// obj.SomeRequiredField = ""
// Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred())
// })
//
// It("Should admit creation if all required fields are present", func() {
// By("simulating an invalid creation scenario")
// obj.SomeRequiredField = "valid_value"
// Expect(validator.ValidateCreate(ctx, obj)).To(BeNil())
// })
//
// It("Should validate updates correctly", func() {
// By("simulating a valid update scenario")
// oldObj.SomeRequiredField = "updated_value"
// obj.SomeRequiredField = "updated_value"
// Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil())
// })
})
})
@@ -0,0 +1,76 @@
package v1
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
netbird "github.com/netbirdio/netbird/management/client/rest"
)
// nolint:unused
// log is for logging in this package.
var nbroutingpeerlog = logf.Log.WithName("nbroutingpeer-resource")
// SetupNBRoutingPeerWebhookWithManager registers the webhook for NBRoutingPeer in the manager.
func SetupNBRoutingPeerWebhookWithManager(mgr ctrl.Manager, managementURL, apiKey string) error {
return ctrl.NewWebhookManagedBy(mgr).For(&netbirdiov1.NBRoutingPeer{}).
WithValidator(&NBRoutingPeerCustomValidator{netbird: netbird.New(managementURL, apiKey), client: mgr.GetClient()}).
Complete()
}
// NBRoutingPeerCustomValidator struct is responsible for validating the NBRoutingPeer resource
// when it is created, updated, or deleted.
type NBRoutingPeerCustomValidator struct {
netbird *netbird.Client
client client.Client
}
var _ webhook.CustomValidator = &NBRoutingPeerCustomValidator{}
// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type NBRoutingPeer.
func (v *NBRoutingPeerCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
return nil, nil
}
// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type NBRoutingPeer.
func (v *NBRoutingPeerCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
return nil, nil
}
// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type NBRoutingPeer.
func (v *NBRoutingPeerCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
nbroutingpeer, ok := obj.(*netbirdiov1.NBRoutingPeer)
if !ok {
return nil, fmt.Errorf("expected a NBRoutingPeer object but got %T", obj)
}
nbroutingpeerlog.Info("Validation for NBRoutingPeer upon deletion", "name", nbroutingpeer.GetName())
if nbroutingpeer.Status.NetworkID == nil {
return nil, nil
}
var nbResources netbirdiov1.NBResourceList
err := v.client.List(ctx, &nbResources)
if err != nil {
return nil, err
}
for _, r := range nbResources.Items {
if r.Spec.NetworkID == *nbroutingpeer.Status.NetworkID {
err = v.client.Delete(ctx, &r, client.DryRunAll)
if err != nil {
return nil, fmt.Errorf("%s/%s: %w", r.Namespace, r.Name, err)
}
}
}
return nil, nil
}
@@ -0,0 +1,56 @@
package v1
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
// TODO (user): Add any additional imports if needed
)
var _ = Describe("NBRoutingPeer Webhook", func() {
var (
obj *netbirdiov1.NBRoutingPeer
oldObj *netbirdiov1.NBRoutingPeer
validator NBRoutingPeerCustomValidator
)
BeforeEach(func() {
Skip("Not implemented yet")
obj = &netbirdiov1.NBRoutingPeer{}
oldObj = &netbirdiov1.NBRoutingPeer{}
validator = NBRoutingPeerCustomValidator{}
Expect(validator).NotTo(BeNil(), "Expected validator to be initialized")
Expect(oldObj).NotTo(BeNil(), "Expected oldObj to be initialized")
Expect(obj).NotTo(BeNil(), "Expected obj to be initialized")
// TODO (user): Add any setup logic common to all tests
})
AfterEach(func() {
// TODO (user): Add any teardown logic common to all tests
})
Context("When creating or updating NBRoutingPeer under Validating Webhook", func() {
// TODO (user): Add logic for validating webhooks
// Example:
// It("Should deny creation if a required field is missing", func() {
// By("simulating an invalid creation scenario")
// obj.SomeRequiredField = ""
// Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred())
// })
//
// It("Should admit creation if all required fields are present", func() {
// By("simulating an invalid creation scenario")
// obj.SomeRequiredField = "valid_value"
// Expect(validator.ValidateCreate(ctx, obj)).To(BeNil())
// })
//
// It("Should validate updates correctly", func() {
// By("simulating a valid update scenario")
// oldObj.SomeRequiredField = "updated_value"
// obj.SomeRequiredField = "updated_value"
// Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil())
// })
})
})
+1 -1
View File
@@ -83,7 +83,7 @@ func (d *PodNetbirdInjector) Default(ctx context.Context, obj runtime.Object) er
// ensure the NBSetupKey is ready.
ready := false
for _, c := range nbSetupKey.Status.Conditions {
if c.Type == netbirdiov1.Ready {
if c.Type == netbirdiov1.NBSetupKeyReady {
ready = c.Status == corev1.ConditionTrue
}
}
+2 -2
View File
@@ -108,9 +108,9 @@ var _ = Describe("Pod Webhook", func() {
Expect(err).NotTo(HaveOccurred())
sk.Status = netbirdiov1.NBSetupKeyStatus{
Conditions: []netbirdiov1.NBSetupKeyCondition{
Conditions: []netbirdiov1.NBCondition{
{
Type: netbirdiov1.Ready,
Type: netbirdiov1.NBSetupKeyReady,
Status: corev1.ConditionTrue,
},
},
@@ -124,6 +124,15 @@ var _ = BeforeSuite(func() {
err = SetupNBSetupKeyWebhookWithManager(mgr)
Expect(err).NotTo(HaveOccurred())
err = SetupNBResourceWebhookWithManager(mgr, "", "")
Expect(err).NotTo(HaveOccurred())
err = SetupNBRoutingPeerWebhookWithManager(mgr, "", "")
Expect(err).NotTo(HaveOccurred())
err = SetupNBGroupWebhookWithManager(mgr, "", "")
Expect(err).NotTo(HaveOccurred())
// +kubebuilder:scaffold:webhook
go func() {