From 0cac34a03b82a8335b18085847b7102cb3ed16bd Mon Sep 17 00:00:00 2001 From: M Essam Hamed Date: Thu, 20 Feb 2025 21:44:12 +0200 Subject: [PATCH 01/11] Add ingress feature to controller --- PROJECT | 46 ++ api/v1/nbgroup_types.go | 52 ++ api/v1/nbpolicy_types.go | 81 ++ api/v1/nbresource_types.go | 76 ++ api/v1/nbroutingpeer_types.go | 69 ++ api/v1/nbsetupkey_types.go | 53 +- api/v1/zz_generated.deepcopy.go | 542 ++++++++++++- cmd/main.go | 125 ++- go.mod | 12 +- go.sum | 275 ++++++- helm/kubernetes-operator/Chart.yaml | 2 +- .../crds/netbird.io_nbgroups.yaml | 95 +++ .../crds/netbird.io_nbpolicies.yaml | 131 ++++ .../crds/netbird.io_nbresources.yaml | 136 ++++ .../crds/netbird.io_nbroutingpeers.yaml | 203 +++++ .../crds/netbird.io_nbsetupkeys.yaml | 3 +- .../templates/deployment.yaml | 21 + .../templates/nbpolicies.yaml | 28 + .../templates/nbroutingpeers.yaml | 75 ++ .../templates/pre-delete.yaml | 69 ++ helm/kubernetes-operator/templates/rbac.yaml | 81 +- .../kubernetes-operator/templates/secret.yaml | 11 + .../templates/webhook.yaml | 125 ++- helm/kubernetes-operator/values.yaml | 46 ++ internal/controller/nbgroup_controller.go | 179 +++++ .../controller/nbgroup_controller_test.go | 70 ++ internal/controller/nbpolicy_controller.go | 372 +++++++++ .../controller/nbpolicy_controller_test.go | 70 ++ internal/controller/nbresource_controller.go | 400 ++++++++++ .../controller/nbresource_controller_test.go | 69 ++ .../controller/nbroutingpeer_controller.go | 637 ++++++++++++++++ .../nbroutingpeer_controller_test.go | 70 ++ internal/controller/nbsetupkey_controller.go | 22 +- internal/controller/service_controller.go | 256 +++++++ .../controller/service_controller_test.go | 17 + internal/controller/suite_test.go | 5 + internal/util/ptr.go | 6 + internal/util/slices.go | 41 + internal/webhook/v1/nbgroup_webhook.go | 82 ++ internal/webhook/v1/nbgroup_webhook_test.go | 56 ++ internal/webhook/v1/nbresource_webhook.go | 70 ++ .../webhook/v1/nbresource_webhook_test.go | 56 ++ internal/webhook/v1/nbroutingpeer_webhook.go | 76 ++ .../webhook/v1/nbroutingpeer_webhook_test.go | 56 ++ internal/webhook/v1/pod_webhook.go | 2 +- internal/webhook/v1/pod_webhook_test.go | 4 +- internal/webhook/v1/webhook_suite_test.go | 9 + manifests/install.yaml | 709 +++++++++++++++++- 48 files changed, 5602 insertions(+), 89 deletions(-) create mode 100644 api/v1/nbgroup_types.go create mode 100644 api/v1/nbpolicy_types.go create mode 100644 api/v1/nbresource_types.go create mode 100644 api/v1/nbroutingpeer_types.go create mode 100644 helm/kubernetes-operator/crds/netbird.io_nbgroups.yaml create mode 100644 helm/kubernetes-operator/crds/netbird.io_nbpolicies.yaml create mode 100644 helm/kubernetes-operator/crds/netbird.io_nbresources.yaml create mode 100644 helm/kubernetes-operator/crds/netbird.io_nbroutingpeers.yaml create mode 100644 helm/kubernetes-operator/templates/nbpolicies.yaml create mode 100644 helm/kubernetes-operator/templates/nbroutingpeers.yaml create mode 100644 helm/kubernetes-operator/templates/pre-delete.yaml create mode 100644 helm/kubernetes-operator/templates/secret.yaml create mode 100644 internal/controller/nbgroup_controller.go create mode 100644 internal/controller/nbgroup_controller_test.go create mode 100644 internal/controller/nbpolicy_controller.go create mode 100644 internal/controller/nbpolicy_controller_test.go create mode 100644 internal/controller/nbresource_controller.go create mode 100644 internal/controller/nbresource_controller_test.go create mode 100644 internal/controller/nbroutingpeer_controller.go create mode 100644 internal/controller/nbroutingpeer_controller_test.go create mode 100644 internal/controller/service_controller.go create mode 100644 internal/controller/service_controller_test.go create mode 100644 internal/util/ptr.go create mode 100644 internal/util/slices.go create mode 100644 internal/webhook/v1/nbgroup_webhook.go create mode 100644 internal/webhook/v1/nbgroup_webhook_test.go create mode 100644 internal/webhook/v1/nbresource_webhook.go create mode 100644 internal/webhook/v1/nbresource_webhook_test.go create mode 100644 internal/webhook/v1/nbroutingpeer_webhook.go create mode 100644 internal/webhook/v1/nbroutingpeer_webhook_test.go diff --git a/PROJECT b/PROJECT index 42072a5..193376e 100644 --- a/PROJECT +++ b/PROJECT @@ -29,4 +29,50 @@ resources: webhooks: defaulting: true webhookVersion: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: netbird.io + kind: NBRoutingPeer + path: github.com/netbirdio/kubernetes-operator/api/v1 + version: v1 + webhooks: + validation: true + webhookVersion: v1 +- controller: true + external: true + kind: Service + path: k8s.io/api/core/v1 + version: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: netbird.io + kind: NBResource + path: github.com/netbirdio/kubernetes-operator/api/v1 + version: v1 + webhooks: + validation: true + webhookVersion: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: netbird.io + kind: NBGroup + path: github.com/netbirdio/kubernetes-operator/api/v1 + version: v1 + webhooks: + validation: true + webhookVersion: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: netbird.io + kind: NBPolicy + path: github.com/netbirdio/kubernetes-operator/api/v1 + version: v1 version: "3" diff --git a/api/v1/nbgroup_types.go b/api/v1/nbgroup_types.go new file mode 100644 index 0000000..5878196 --- /dev/null +++ b/api/v1/nbgroup_types.go @@ -0,0 +1,52 @@ +package v1 + +import ( + "slices" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// NBGroupSpec defines the desired state of NBGroup. +type NBGroupSpec struct { + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" + // +kubebuilder:validation:MinLength=1 + Name string `json:"name"` +} + +// NBGroupStatus defines the observed state of NBGroup. +type NBGroupStatus struct { + // +optional + GroupID *string `json:"groupID"` + // +optional + Conditions []NBCondition `json:"conditions,omitempty"` +} + +// Equal returns if NBGroupStatus is equal to this one +func (a NBGroupStatus) Equal(b NBGroupStatus) bool { + return a.GroupID == b.GroupID && slices.Equal(a.Conditions, b.Conditions) +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// NBGroup is the Schema for the nbgroups API. +type NBGroup struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NBGroupSpec `json:"spec,omitempty"` + Status NBGroupStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// NBGroupList contains a list of NBGroup. +type NBGroupList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NBGroup `json:"items"` +} + +func init() { + SchemeBuilder.Register(&NBGroup{}, &NBGroupList{}) +} diff --git a/api/v1/nbpolicy_types.go b/api/v1/nbpolicy_types.go new file mode 100644 index 0000000..60027c3 --- /dev/null +++ b/api/v1/nbpolicy_types.go @@ -0,0 +1,81 @@ +package v1 + +import ( + "slices" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// NBPolicySpec defines the desired state of NBPolicy. +type NBPolicySpec struct { + // Name Policy name + // +kubebuilder:validation:MinLength=1 + Name string `json:"name"` + // +optional + Description string `json:"description,omitempty"` + // +optional + // +kubebuilder:validation:items:MinLength=1 + SourceGroups []string `json:"sourceGroups,omitempty"` + // +optional + // +kubebuilder:validation:items:MinLength=1 + DestinationGroups []string `json:"destinationGroups,omitempty"` + // +optional + // +kubebuilder:validation:items:Enum=tcp;udp + Protocols []string `json:"protocols,omitempty"` + // +optional + // +kubebuilder:validation:items:Minimum=0 + // +kubebuilder:validation:items:Maximum=65535 + Ports []int32 `json:"ports,omitempty"` + // +optional + // +default:value=true + Bidirectional bool `json:"bidirectional"` +} + +// NBPolicyStatus defines the observed state of NBPolicy. +type NBPolicyStatus struct { + // +optional + TCPPolicyID *string `json:"tcpPolicyID"` + // +optional + UDPPolicyID *string `json:"udpPolicyID"` + // +optional + LastUpdatedAt *metav1.Time `json:"lastUpdatedAt"` + // +optional + ManagedServiceList []string `json:"managedServiceList"` + // +optional + Conditions []NBCondition `json:"conditions,omitempty"` +} + +// Equal returns if NBPolicyStatus is equal to this one +func (a NBPolicyStatus) Equal(b NBPolicyStatus) bool { + return a.TCPPolicyID == b.TCPPolicyID && + a.UDPPolicyID == b.UDPPolicyID && + a.LastUpdatedAt == b.LastUpdatedAt && + slices.Equal(a.ManagedServiceList, b.ManagedServiceList) && + slices.Equal(a.Conditions, b.Conditions) +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Cluster + +// NBPolicy is the Schema for the nbpolicies API. +type NBPolicy struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NBPolicySpec `json:"spec,omitempty"` + Status NBPolicyStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// NBPolicyList contains a list of NBPolicy. +type NBPolicyList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NBPolicy `json:"items"` +} + +func init() { + SchemeBuilder.Register(&NBPolicy{}, &NBPolicyList{}) +} diff --git a/api/v1/nbresource_types.go b/api/v1/nbresource_types.go new file mode 100644 index 0000000..a1957d4 --- /dev/null +++ b/api/v1/nbresource_types.go @@ -0,0 +1,76 @@ +package v1 + +import ( + "slices" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// NBResourceSpec defines the desired state of NBResource. +type NBResourceSpec struct { + // +kubebuilder:validation:MinLength=1 + Name string `json:"name"` + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" + NetworkID string `json:"networkID"` + // +kubebuilder:validation:MinLength=1 + Address string `json:"address"` + // +kubebuilder:validation:items:MinLength=1 + Groups []string `json:"groups"` + // +optional + PolicyName string `json:"policyName,omitempty"` + // +optional + TCPPorts []int32 `json:"tcpPorts,omitempty"` + // +optional + UDPPorts []int32 `json:"udpPorts,omitempty"` +} + +// NBResourceStatus defines the observed state of NBResource. +type NBResourceStatus struct { + // +optional + NetworkResourceID *string `json:"networkResourceID,omitempty"` + // +optional + PolicyName *string `json:"policyName,omitempty"` + // +optional + TCPPorts []int32 `json:"tcpPorts,omitempty"` + // +optional + UDPPorts []int32 `json:"udpPorts,omitempty"` + // +optional + Groups []string `json:"groups,omitempty"` + // +optional + Conditions []NBCondition `json:"conditions,omitempty"` +} + +// Equal returns if NBResourceStatus is equal to this one +func (a NBResourceStatus) Equal(b NBResourceStatus) bool { + return a.NetworkResourceID == b.NetworkResourceID && + a.PolicyName == b.PolicyName && + slices.Equal(a.TCPPorts, b.TCPPorts) && + slices.Equal(a.UDPPorts, b.UDPPorts) && + slices.Equal(a.Groups, b.Groups) && + slices.Equal(a.Conditions, b.Conditions) +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// NBResource is the Schema for the nbresources API. +type NBResource struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NBResourceSpec `json:"spec,omitempty"` + Status NBResourceStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// NBResourceList contains a list of NBResource. +type NBResourceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NBResource `json:"items"` +} + +func init() { + SchemeBuilder.Register(&NBResource{}, &NBResourceList{}) +} diff --git a/api/v1/nbroutingpeer_types.go b/api/v1/nbroutingpeer_types.go new file mode 100644 index 0000000..0fd7f23 --- /dev/null +++ b/api/v1/nbroutingpeer_types.go @@ -0,0 +1,69 @@ +package v1 + +import ( + "slices" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// NBRoutingPeerSpec defines the desired state of NBRoutingPeer. +type NBRoutingPeerSpec struct { + // +optional + Replicas *int32 `json:"replicas"` + // +optional + Resources corev1.ResourceRequirements `json:"resources"` + // +optional + Labels map[string]string `json:"labels"` + // +optional + Annotations map[string]string `json:"annotations"` + // +optional + NodeSelector map[string]string `json:"nodeSelector"` + // +optional + Tolerations []corev1.Toleration `json:"tolerations"` +} + +// NBRoutingPeerStatus defines the observed state of NBRoutingPeer. +type NBRoutingPeerStatus struct { + // +optional + NetworkID *string `json:"networkID"` + // +optional + SetupKeyID *string `json:"setupKeyID"` + // +optional + RouterID *string `json:"routerID"` + // +optional + Conditions []NBCondition `json:"conditions,omitempty"` +} + +// Equal returns if NBRoutingPeerStatus is equal to this one +func (a NBRoutingPeerStatus) Equal(b NBRoutingPeerStatus) bool { + return a.NetworkID == b.NetworkID && + a.SetupKeyID == b.SetupKeyID && + a.RouterID == b.RouterID && + slices.Equal(a.Conditions, b.Conditions) +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// NBRoutingPeer is the Schema for the nbroutingpeers API. +type NBRoutingPeer struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NBRoutingPeerSpec `json:"spec,omitempty"` + Status NBRoutingPeerStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// NBRoutingPeerList contains a list of NBRoutingPeer. +type NBRoutingPeerList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NBRoutingPeer `json:"items"` +} + +func init() { + SchemeBuilder.Register(&NBRoutingPeer{}, &NBRoutingPeerList{}) +} diff --git a/api/v1/nbsetupkey_types.go b/api/v1/nbsetupkey_types.go index 1678b41..41ca68b 100644 --- a/api/v1/nbsetupkey_types.go +++ b/api/v1/nbsetupkey_types.go @@ -21,13 +21,13 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// NBSetupKeyConditionType is a valid value for PodCondition.Type -type NBSetupKeyConditionType string +// NBConditionType is a valid value for PodCondition.Type +type NBConditionType string // These are built-in conditions of pod. An application may use a custom condition not listed here. const ( - // Ready indicates whether NBSetupKey is valid and ready to use. - Ready NBSetupKeyConditionType = "Ready" + // NBSetupKeyReady indicates whether NBSetupKey is valid and ready to use. + NBSetupKeyReady NBConditionType = "Ready" ) // NBSetupKeySpec defines the desired state of NBSetupKey. @@ -40,28 +40,55 @@ type NBSetupKeySpec struct { // NBSetupKeyStatus defines the observed state of NBSetupKey. type NBSetupKeyStatus struct { - Conditions []NBSetupKeyCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,2,rep,name=conditions"` + // +optional + Conditions []NBCondition `json:"conditions,omitempty"` } -// NBSetupKeyCondition defines a condition in NBSetupKey status. -type NBSetupKeyCondition struct { +// NBCondition defines a condition in NBSetupKey status. +type NBCondition struct { // Type is the type of the condition. - Type NBSetupKeyConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=NBSetupKeyConditionType"` + Type NBConditionType `json:"type"` // Status is the status of the condition. // Can be True, False, Unknown. - Status corev1.ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=ConditionStatus"` + Status corev1.ConditionStatus `json:"status"` // Last time we probed the condition. // +optional - LastProbeTime metav1.Time `json:"lastProbeTime,omitempty" protobuf:"bytes,3,opt,name=lastProbeTime"` + LastProbeTime metav1.Time `json:"lastProbeTime,omitempty"` // Last time the condition transitioned from one status to another. // +optional - LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,4,opt,name=lastTransitionTime"` + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"` // Unique, one-word, CamelCase reason for the condition's last transition. // +optional - Reason string `json:"reason,omitempty" protobuf:"bytes,5,opt,name=reason"` + Reason string `json:"reason,omitempty"` // Human-readable message indicating details about last transition. // +optional - Message string `json:"message,omitempty" protobuf:"bytes,6,opt,name=message"` + Message string `json:"message,omitempty"` +} + +// NBConditionTrue returns default true condition +func NBConditionTrue() []NBCondition { + return []NBCondition{ + { + Type: NBSetupKeyReady, + LastProbeTime: metav1.Now(), + LastTransitionTime: metav1.Now(), + Status: corev1.ConditionTrue, + }, + } +} + +// NBConditionFalse returns default false condition +func NBConditionFalse(reason, msg string) []NBCondition { + return []NBCondition{ + { + Type: NBSetupKeyReady, + LastProbeTime: metav1.Now(), + LastTransitionTime: metav1.Now(), + Status: corev1.ConditionFalse, + Reason: reason, + Message: msg, + }, + } } // +kubebuilder:object:root=true diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index fdfc726..123fa86 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -5,11 +5,130 @@ package v1 import ( + corev1 "k8s.io/api/core/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NBSetupKey) DeepCopyInto(out *NBSetupKey) { +func (in *NBCondition) DeepCopyInto(out *NBCondition) { + *out = *in + in.LastProbeTime.DeepCopyInto(&out.LastProbeTime) + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBCondition. +func (in *NBCondition) DeepCopy() *NBCondition { + if in == nil { + return nil + } + out := new(NBCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NBGroup) DeepCopyInto(out *NBGroup) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBGroup. +func (in *NBGroup) DeepCopy() *NBGroup { + if in == nil { + return nil + } + out := new(NBGroup) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NBGroup) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NBGroupList) DeepCopyInto(out *NBGroupList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NBGroup, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBGroupList. +func (in *NBGroupList) DeepCopy() *NBGroupList { + if in == nil { + return nil + } + out := new(NBGroupList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NBGroupList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NBGroupSpec) DeepCopyInto(out *NBGroupSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBGroupSpec. +func (in *NBGroupSpec) DeepCopy() *NBGroupSpec { + if in == nil { + return nil + } + out := new(NBGroupSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NBGroupStatus) DeepCopyInto(out *NBGroupStatus) { + *out = *in + if in.GroupID != nil { + in, out := &in.GroupID, &out.GroupID + *out = new(string) + **out = **in + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]NBCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBGroupStatus. +func (in *NBGroupStatus) DeepCopy() *NBGroupStatus { + if in == nil { + return nil + } + out := new(NBGroupStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NBPolicy) DeepCopyInto(out *NBPolicy) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) @@ -17,18 +136,18 @@ func (in *NBSetupKey) DeepCopyInto(out *NBSetupKey) { in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBSetupKey. -func (in *NBSetupKey) DeepCopy() *NBSetupKey { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBPolicy. +func (in *NBPolicy) DeepCopy() *NBPolicy { if in == nil { return nil } - out := new(NBSetupKey) + out := new(NBPolicy) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *NBSetupKey) DeepCopyObject() runtime.Object { +func (in *NBPolicy) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -36,22 +155,421 @@ func (in *NBSetupKey) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NBSetupKeyCondition) DeepCopyInto(out *NBSetupKeyCondition) { +func (in *NBPolicyList) DeepCopyInto(out *NBPolicyList) { *out = *in - in.LastProbeTime.DeepCopyInto(&out.LastProbeTime) - in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NBPolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBPolicyList. +func (in *NBPolicyList) DeepCopy() *NBPolicyList { + if in == nil { + return nil + } + out := new(NBPolicyList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NBPolicyList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NBPolicySpec) DeepCopyInto(out *NBPolicySpec) { + *out = *in + if in.SourceGroups != nil { + in, out := &in.SourceGroups, &out.SourceGroups + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.DestinationGroups != nil { + in, out := &in.DestinationGroups, &out.DestinationGroups + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Protocols != nil { + in, out := &in.Protocols, &out.Protocols + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Ports != nil { + in, out := &in.Ports, &out.Ports + *out = make([]int32, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBPolicySpec. +func (in *NBPolicySpec) DeepCopy() *NBPolicySpec { + if in == nil { + return nil + } + out := new(NBPolicySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NBPolicyStatus) DeepCopyInto(out *NBPolicyStatus) { + *out = *in + if in.TCPPolicyID != nil { + in, out := &in.TCPPolicyID, &out.TCPPolicyID + *out = new(string) + **out = **in + } + if in.UDPPolicyID != nil { + in, out := &in.UDPPolicyID, &out.UDPPolicyID + *out = new(string) + **out = **in + } + if in.LastUpdatedAt != nil { + in, out := &in.LastUpdatedAt, &out.LastUpdatedAt + *out = (*in).DeepCopy() + } + if in.ManagedServiceList != nil { + in, out := &in.ManagedServiceList, &out.ManagedServiceList + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]NBCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBPolicyStatus. +func (in *NBPolicyStatus) DeepCopy() *NBPolicyStatus { + if in == nil { + return nil + } + out := new(NBPolicyStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NBResource) DeepCopyInto(out *NBResource) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBResource. +func (in *NBResource) DeepCopy() *NBResource { + if in == nil { + return nil + } + out := new(NBResource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NBResource) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NBResourceList) DeepCopyInto(out *NBResourceList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NBResource, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBResourceList. +func (in *NBResourceList) DeepCopy() *NBResourceList { + if in == nil { + return nil + } + out := new(NBResourceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NBResourceList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NBResourceSpec) DeepCopyInto(out *NBResourceSpec) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.TCPPorts != nil { + in, out := &in.TCPPorts, &out.TCPPorts + *out = make([]int32, len(*in)) + copy(*out, *in) + } + if in.UDPPorts != nil { + in, out := &in.UDPPorts, &out.UDPPorts + *out = make([]int32, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBResourceSpec. +func (in *NBResourceSpec) DeepCopy() *NBResourceSpec { + if in == nil { + return nil + } + out := new(NBResourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NBResourceStatus) DeepCopyInto(out *NBResourceStatus) { + *out = *in + if in.NetworkResourceID != nil { + in, out := &in.NetworkResourceID, &out.NetworkResourceID + *out = new(string) + **out = **in + } + if in.PolicyName != nil { + in, out := &in.PolicyName, &out.PolicyName + *out = new(string) + **out = **in + } + if in.TCPPorts != nil { + in, out := &in.TCPPorts, &out.TCPPorts + *out = make([]int32, len(*in)) + copy(*out, *in) + } + if in.UDPPorts != nil { + in, out := &in.UDPPorts, &out.UDPPorts + *out = make([]int32, len(*in)) + copy(*out, *in) + } + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]NBCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBResourceStatus. +func (in *NBResourceStatus) DeepCopy() *NBResourceStatus { + if in == nil { + return nil + } + out := new(NBResourceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NBRoutingPeer) DeepCopyInto(out *NBRoutingPeer) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBRoutingPeer. +func (in *NBRoutingPeer) DeepCopy() *NBRoutingPeer { + if in == nil { + return nil + } + out := new(NBRoutingPeer) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NBRoutingPeer) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NBRoutingPeerList) DeepCopyInto(out *NBRoutingPeerList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NBRoutingPeer, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBRoutingPeerList. +func (in *NBRoutingPeerList) DeepCopy() *NBRoutingPeerList { + if in == nil { + return nil + } + out := new(NBRoutingPeerList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NBRoutingPeerList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NBRoutingPeerSpec) DeepCopyInto(out *NBRoutingPeerSpec) { + *out = *in + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + *out = new(int32) + **out = **in + } + in.Resources.DeepCopyInto(&out.Resources) + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Tolerations != nil { + in, out := &in.Tolerations, &out.Tolerations + *out = make([]corev1.Toleration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBSetupKeyCondition. -func (in *NBSetupKeyCondition) DeepCopy() *NBSetupKeyCondition { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBRoutingPeerSpec. +func (in *NBRoutingPeerSpec) DeepCopy() *NBRoutingPeerSpec { if in == nil { return nil } - out := new(NBSetupKeyCondition) + out := new(NBRoutingPeerSpec) in.DeepCopyInto(out) return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NBRoutingPeerStatus) DeepCopyInto(out *NBRoutingPeerStatus) { + *out = *in + if in.NetworkID != nil { + in, out := &in.NetworkID, &out.NetworkID + *out = new(string) + **out = **in + } + if in.SetupKeyID != nil { + in, out := &in.SetupKeyID, &out.SetupKeyID + *out = new(string) + **out = **in + } + if in.RouterID != nil { + in, out := &in.RouterID, &out.RouterID + *out = new(string) + **out = **in + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]NBCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBRoutingPeerStatus. +func (in *NBRoutingPeerStatus) DeepCopy() *NBRoutingPeerStatus { + if in == nil { + return nil + } + out := new(NBRoutingPeerStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NBSetupKey) DeepCopyInto(out *NBSetupKey) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBSetupKey. +func (in *NBSetupKey) DeepCopy() *NBSetupKey { + if in == nil { + return nil + } + out := new(NBSetupKey) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NBSetupKey) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NBSetupKeyList) DeepCopyInto(out *NBSetupKeyList) { *out = *in @@ -105,7 +623,7 @@ func (in *NBSetupKeyStatus) DeepCopyInto(out *NBSetupKeyStatus) { *out = *in if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions - *out = make([]NBSetupKeyCondition, len(*in)) + *out = make([]NBCondition, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } diff --git a/cmd/main.go b/cmd/main.go index d1a392b..6252d3d 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -19,6 +19,7 @@ package main import ( "crypto/tls" "flag" + "fmt" "os" "path/filepath" @@ -45,6 +46,10 @@ import ( // +kubebuilder:scaffold:imports ) +const ( + inClusterNamespacePath = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" +) + var ( scheme = runtime.NewScheme() setupLog = ctrl.Log.WithName("setup") @@ -62,11 +67,29 @@ func init() { func main() { // NB Specific flags var ( - managementURL string - clientImage string + managementURL string + clientImage string + clusterName string + namespacedNetworks bool + clusterDNS string + netbirdAPIKey string ) flag.StringVar(&managementURL, "netbird-management-url", "https://api.netbird.io", "Management service URL") flag.StringVar(&clientImage, "netbird-client-image", "netbirdio/netbird:latest", "Image for netbird client container") + flag.StringVar( + &clusterName, + "cluster-name", + "kubernetes", + "User-friendly name for kubernetes cluster for NetBird resource creation", + ) + flag.BoolVar( + &namespacedNetworks, + "namespaced-networks", + false, + "Create NetBird Network per namespace, set to true if a NetworkPolicy exists that would require this", + ) + flag.StringVar(&clusterDNS, "cluster-dns", "svc.cluster.local", "Cluster DNS name") + flag.StringVar(&netbirdAPIKey, "netbird-api-key", "", "API key for NetBird API operations") // Controller generic flags var ( @@ -177,6 +200,87 @@ func main() { os.Exit(1) } } + if len(netbirdAPIKey) > 0 { + if err = (&controller.NBRoutingPeerReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + ClientImage: clientImage, + ClusterName: clusterName, + APIKey: netbirdAPIKey, + ManagementURL: managementURL, + NamespacedNetworks: namespacedNetworks, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "NBRoutingPeer") + os.Exit(1) + } + + controllerNamespace, err := getInClusterNamespace() + if err != nil { + setupLog.Error(err, "unable to get main namespace", "controller", "Service") + os.Exit(1) + } + + if err = (&controller.ServiceReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + ClusterName: clusterName, + ClusterDNS: clusterDNS, + NamespacedNetworks: namespacedNetworks, + ControllerNamespace: controllerNamespace, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Service") + os.Exit(1) + } + + if err = (&controller.NBResourceReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + APIKey: netbirdAPIKey, + ManagementURL: managementURL, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "NBResource") + os.Exit(1) + } + + if err = (&controller.NBGroupReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + APIKey: netbirdAPIKey, + ManagementURL: managementURL, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "NBGroup") + os.Exit(1) + } + + if err = (&controller.NBPolicyReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + APIKey: netbirdAPIKey, + ManagementURL: managementURL, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "NBPolicy") + os.Exit(1) + } + + if enableWebhooks { + if err = webhooknetbirdiov1.SetupNBResourceWebhookWithManager(mgr, managementURL, netbirdAPIKey); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "NBResource") + os.Exit(1) + } + + if err = webhooknetbirdiov1.SetupNBRoutingPeerWebhookWithManager(mgr, managementURL, netbirdAPIKey); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "NBRoutingPeer") + os.Exit(1) + } + + if err = webhooknetbirdiov1.SetupNBGroupWebhookWithManager(mgr, managementURL, netbirdAPIKey); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "NBGroup") + os.Exit(1) + } + } + } else { + setupLog.Info("netbird API key not provided, ingress capabilities disabled") + } // +kubebuilder:scaffold:builder if webhookCertWatcher != nil { @@ -202,3 +306,20 @@ func main() { os.Exit(1) } } + +func getInClusterNamespace() (string, error) { + // Check whether the namespace file exists. + // If not, we are not running in cluster so can't guess the namespace. + if _, err := os.Stat(inClusterNamespacePath); os.IsNotExist(err) { + return "", fmt.Errorf("not running in-cluster, please specify LeaderElectionNamespace") + } else if err != nil { + return "", fmt.Errorf("error checking namespace file: %w", err) + } + + // Load the namespace file and return its content + namespace, err := os.ReadFile(inClusterNamespacePath) + if err != nil { + return "", fmt.Errorf("error reading namespace file: %w", err) + } + return string(namespace), nil +} diff --git a/go.mod b/go.mod index 7548b0f..28d1e60 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ godebug default=go1.23 require ( github.com/google/uuid v1.6.0 + github.com/netbirdio/netbird v0.36.7 github.com/onsi/ginkgo/v2 v2.21.0 github.com/onsi/gomega v1.35.1 k8s.io/api v0.32.0 @@ -46,16 +47,17 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/pflag v1.0.5 // 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 - golang.org/x/net v0.30.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/term v0.25.0 // indirect - golang.org/x/text v0.19.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.26.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 834b820..28ac98d 100644 --- a/go.sum +++ b/go.sum @@ -1,26 +1,110 @@ +cloud.google.com/go/auth v0.3.0 h1:PRyzEpGfx/Z9e8+lHsbkoUVXD0gnu4MNmm7Gp8TQNIs= +cloud.google.com/go/auth v0.3.0/go.mod h1:lBv6NKTWp8E3LPzmO1TbiiRKc4drLOfHsgmlH9ogv5w= +cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= +cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Microsoft/hcsshim v0.12.3 h1:LS9NXqXhMoqNCplK1ApmVSfB4UnVLRDWRapB6EIlxE0= +github.com/Microsoft/hcsshim v0.12.3/go.mod h1:Iyl1WVpZzr+UkzjekHZbV8o5Z9ZkxNGx6CtY2Qg/JVQ= +github.com/TheJumpCloud/jcapi-go v3.0.0+incompatible h1:hqcTK6ZISdip65SR792lwYJTa/axESA0889D3UlZbLo= +github.com/TheJumpCloud/jcapi-go v3.0.0+incompatible/go.mod h1:6B1nuc1MUs6c62ODZDl7hVE5Pv7O2XGSkgg2olnq34I= +github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 h1:pami0oPhVosjOu/qRHepRmdjD6hGILF7DBr+qQZeP10= +github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2/go.mod h1:jNIx5ykW1MroBuaTja9+VpglmaJOUzezumfhLlER3oY= +github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY= +github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= +github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90= +github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg= +github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI= +github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII= +github.com/aws/aws-sdk-go-v2/service/route53 v1.42.3 h1:MmLCRqP4U4Cw9gJ4bNrCG0mWqEtBlmAVleyelcHARMU= +github.com/aws/aws-sdk-go-v2/service/route53 v1.42.3/go.mod h1:AMPjK2YnRh0YgOID3PqhJA1BRNfXDfGOnSsKHtAe8yA= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ= +github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= +github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw= +github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= +github.com/c-robinson/iplib v1.0.3 h1:NG0UF0GoEsrC1/vyfX1Lx2Ss7CySWl3KqqXh3q4DdPU= +github.com/c-robinson/iplib v1.0.3/go.mod h1:i3LuuFL1hRT5gFpBRnEydzw8R6yhGkF4szNDIbF8pgo= +github.com/caddyserver/certmagic v0.21.3 h1:pqRRry3yuB4CWBVq9+cUqu+Y6E2z8TswbhNx1AZeYm0= +github.com/caddyserver/certmagic v0.21.3/go.mod h1:Zq6pklO9nVRl3DIFUw9gVUfXKdpc/0qwTUAQMBlfgtI= +github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= +github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/containerd/containerd v1.7.16 h1:7Zsfe8Fkj4Wi2My6DXGQ87hiqIrmOXolm72ZEkFU5Mg= +github.com/containerd/containerd v1.7.16/go.mod h1:NL49g7A/Fui7ccmxV6zkBWwqMgmMxFWzujYCc+JLt7k= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= +github.com/cpuguy83/dockercfg v0.3.1/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= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v26.1.5+incompatible h1:NEAxTwEjxV6VbBMBoGG3zPqbiJosIApZjxlbrG9q3/g= +github.com/docker/docker v26.1.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/eko/gocache/v3 v3.1.1 h1:r3CBwLnqPkcK56h9Do2CWw1kZ4TeKK0wDE1Oo/YZnhs= +github.com/eko/gocache/v3 v3.1.1/go.mod h1:UpP/LyHAioP/a/dizgl0MpgZ3A3CkS4NbG/mWkGTQ9M= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.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.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 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= @@ -29,10 +113,18 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En 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-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= @@ -47,14 +139,55 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= +github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357 h1:Fkzd8ktnpOR9h47SXHe2AYPwelXLH2GjGsjlAloiWfo= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357/go.mod h1:w9Y7gY31krpLmrVU5ZPG9H7l9fZuRu5/3R3S3FMtVQ4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng= +github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= +github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +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= @@ -62,24 +195,68 @@ 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/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= +github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= +github.com/libdns/route53 v1.5.0 h1:2SKdpPFl/qgWsXQvsLNJJAoX7rSxlk7zgoL4jnWdXVA= +github.com/libdns/route53 v1.5.0/go.mod h1:joT4hKmaTNKHEwb7GmZ65eoDz1whTu7KKYPS8ZqIh6Q= +github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI= +github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/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/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k= +github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U= +github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs= +github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= +github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/netbirdio/management-integrations/integrations v0.0.0-20250115083837-a09722b8d2a6 h1:I/ODkZ8rSDOzlJbhEjD2luSI71zl+s5JgNvFHY0+mBU= +github.com/netbirdio/management-integrations/integrations v0.0.0-20250115083837-a09722b8d2a6/go.mod h1:izUUs1NT7ja+PwSX3kJ7ox8Kkn478tboBJSjL4kU6J0= +github.com/netbirdio/netbird v0.36.7 h1:ndJ+ab0D2PG33kvgd/p7XekOjKR2yy6wb+I9BJlK6Rc= +github.com/netbirdio/netbird v0.36.7/go.mod h1:6O7o2wr2MyD1O8V7Z8/FtO0/aHvcy5wt4eBwRRj3U4c= +github.com/okta/okta-sdk-golang/v2 v2.18.0 h1:cfDasMb7CShbZvOrF6n+DnLevWwiHgedWMGJ8M8xKDc= +github.com/okta/okta-sdk-golang/v2 v2.18.0/go.mod h1:dz30v3ctAiMb7jpsCngGfQUAEGm1/NsWT92uTbNDQIs= github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= +github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pegasus-kv/thrift v0.13.0 h1:4ESwaNoHImfbHa9RUGJiJZ4hrxorihZHk5aarYwY8d4= +github.com/pegasus-kv/thrift v0.13.0/go.mod h1:Gl9NT/WHG6ABm6NsrbfE8LiJN0sAyneCrvB4qN4NPqQ= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= @@ -90,56 +267,108 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rs/cors v1.8.0 h1:P2KMzcFwrPoSjkF1WLRPsp3UMLyql8L4v9hQpVeK5so= +github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM= +github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4= +github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU= +github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/testcontainers/testcontainers-go v0.31.0 h1:W0VwIhcEVhRflwL9as3dhY6jXjVCA27AkmbnZ+UTh3U= +github.com/testcontainers/testcontainers-go v0.31.0/go.mod h1:D2lAoA0zUFiSY+eAflqK5mcUx/A5hrrORaEQrd0SefI= +github.com/testcontainers/testcontainers-go/modules/mysql v0.31.0 h1:790+S8ewZYCbG+o8IiFlZ8ZZ33XbNO6zV9qhU6xhlRk= +github.com/testcontainers/testcontainers-go/modules/mysql v0.31.0/go.mod h1:REFmO+lSG9S6uSBEwIMZCxeI36uhScjTwChYADeO3JA= +github.com/testcontainers/testcontainers-go/modules/postgres v0.31.0 h1:isAwFS3KNKRbJMbWv+wolWqOFUECmjYZ+sIRZCIBc/E= +github.com/testcontainers/testcontainers-go/modules/postgres v0.31.0/go.mod h1:ZNYY8vumNCEG9YI59A9d6/YaMY49uwRhmeU563EzFGw= +github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= +github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= +github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= +github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= +github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/exporters/prometheus v0.48.0 h1:sBQe3VNGUjY9IKWQC6z2lNqa5iGbDSxhs60ABwK4y0s= +go.opentelemetry.io/otel/exporters/prometheus v0.48.0/go.mod h1:DtrbMzoZWwQHyrQmCfLam5DZbnmorsGbOtTbYHycU5o= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/sdk/metric v1.26.0 h1:cWSks5tfriHPdWFnl+qpX3P681aAYqlZHcAyHw5aU9Y= +go.opentelemetry.io/otel/sdk/metric v1.26.0/go.mod h1:ClMFFknnThJCksebJwz7KIyEDHO+nTB6gK8obLy8RyE= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +goauthentik.io/api/v3 v3.2023051.3 h1:NebAhD/TeTWNo/9X3/Uj+rM5fG1HaiLOlKTNLQv9Qq4= +goauthentik.io/api/v3 v3.2023051.3/go.mod h1:nYECml4jGbp/541hj8GcylKQG1gVBsKppHy4+7G8u4U= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -152,8 +381,20 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.zx2c4.com/wireguard v0.0.0-20230704135630-469159ecf7d1 h1:EY138uSo1JYlDq+97u1FtcOUwPpIU6WL1Lkt7WpYjPA= +golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE= +golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80= +golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= +golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/api v0.177.0 h1:8a0p/BbPa65GlqGWtUKxot4p0TV8OGOfyTjtmkXNXmk= +google.golang.org/api v0.177.0/go.mod h1:srbhue4MLjkjbkux5p3dw/ocYOSZTaIEvf7bCOnFQDw= +google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -163,9 +404,23 @@ gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSP gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= +gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 h1:yiW+nvdHb9LVqSHQBXfZCieqV4fzYhNBql77zY0ykqs= +gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM= +gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA= +gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I= +gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE= k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0= k8s.io/apiextensions-apiserver v0.32.0 h1:S0Xlqt51qzzqjKPxfgX1xh4HBZE+p8KKBq+k2SWNOE0= diff --git a/helm/kubernetes-operator/Chart.yaml b/helm/kubernetes-operator/Chart.yaml index 6598ea2..58e239d 100644 --- a/helm/kubernetes-operator/Chart.yaml +++ b/helm/kubernetes-operator/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: kubernetes-operator description: A Helm chart for Kubernetes type: application -version: 0.1.0 +version: 0.1.1 appVersion: "v0.1.0" diff --git a/helm/kubernetes-operator/crds/netbird.io_nbgroups.yaml b/helm/kubernetes-operator/crds/netbird.io_nbgroups.yaml new file mode 100644 index 0000000..71b8673 --- /dev/null +++ b/helm/kubernetes-operator/crds/netbird.io_nbgroups.yaml @@ -0,0 +1,95 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: nbgroups.netbird.io +spec: + group: netbird.io + names: + kind: NBGroup + listKind: NBGroupList + plural: nbgroups + singular: nbgroup + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: NBGroup is the Schema for the nbgroups API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: NBGroupSpec defines the desired state of NBGroup. + properties: + name: + minLength: 1 + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + required: + - name + type: object + status: + description: NBGroupStatus defines the observed state of NBGroup. + properties: + conditions: + items: + description: NBCondition defines a condition in NBSetupKey status. + properties: + lastProbeTime: + description: Last time we probed the condition. + format: date-time + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: Human-readable message indicating details about + last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's + last transition. + type: string + status: + description: |- + Status is the status of the condition. + Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + required: + - status + - type + type: object + type: array + groupID: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/kubernetes-operator/crds/netbird.io_nbpolicies.yaml b/helm/kubernetes-operator/crds/netbird.io_nbpolicies.yaml new file mode 100644 index 0000000..bffff40 --- /dev/null +++ b/helm/kubernetes-operator/crds/netbird.io_nbpolicies.yaml @@ -0,0 +1,131 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: nbpolicies.netbird.io +spec: + group: netbird.io + names: + kind: NBPolicy + listKind: NBPolicyList + plural: nbpolicies + singular: nbpolicy + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: NBPolicy is the Schema for the nbpolicies API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: NBPolicySpec defines the desired state of NBPolicy. + properties: + bidirectional: + default: true + type: boolean + description: + type: string + destinationGroups: + items: + minLength: 1 + type: string + type: array + name: + description: Name Policy name + minLength: 1 + type: string + ports: + items: + format: int32 + maximum: 65535 + minimum: 0 + type: integer + type: array + protocols: + items: + enum: + - tcp + - udp + type: string + type: array + sourceGroups: + items: + minLength: 1 + type: string + type: array + required: + - name + type: object + status: + description: NBPolicyStatus defines the observed state of NBPolicy. + properties: + conditions: + items: + description: NBCondition defines a condition in NBSetupKey status. + properties: + lastProbeTime: + description: Last time we probed the condition. + format: date-time + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: Human-readable message indicating details about + last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's + last transition. + type: string + status: + description: |- + Status is the status of the condition. + Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + required: + - status + - type + type: object + type: array + lastUpdatedAt: + format: date-time + type: string + managedServiceList: + items: + type: string + type: array + tcpPolicyID: + type: string + udpPolicyID: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/kubernetes-operator/crds/netbird.io_nbresources.yaml b/helm/kubernetes-operator/crds/netbird.io_nbresources.yaml new file mode 100644 index 0000000..4e73570 --- /dev/null +++ b/helm/kubernetes-operator/crds/netbird.io_nbresources.yaml @@ -0,0 +1,136 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: nbresources.netbird.io +spec: + group: netbird.io + names: + kind: NBResource + listKind: NBResourceList + plural: nbresources + singular: nbresource + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: NBResource is the Schema for the nbresources API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: NBResourceSpec defines the desired state of NBResource. + properties: + address: + minLength: 1 + type: string + groups: + items: + minLength: 1 + type: string + type: array + name: + minLength: 1 + type: string + networkID: + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + policyName: + type: string + tcpPorts: + items: + format: int32 + type: integer + type: array + udpPorts: + items: + format: int32 + type: integer + type: array + required: + - address + - groups + - name + - networkID + type: object + status: + description: NBResourceStatus defines the observed state of NBResource. + properties: + conditions: + items: + description: NBCondition defines a condition in NBSetupKey status. + properties: + lastProbeTime: + description: Last time we probed the condition. + format: date-time + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: Human-readable message indicating details about + last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's + last transition. + type: string + status: + description: |- + Status is the status of the condition. + Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + required: + - status + - type + type: object + type: array + groups: + items: + type: string + type: array + networkResourceID: + type: string + policyName: + type: string + tcpPorts: + items: + format: int32 + type: integer + type: array + udpPorts: + items: + format: int32 + type: integer + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/kubernetes-operator/crds/netbird.io_nbroutingpeers.yaml b/helm/kubernetes-operator/crds/netbird.io_nbroutingpeers.yaml new file mode 100644 index 0000000..3a233c2 --- /dev/null +++ b/helm/kubernetes-operator/crds/netbird.io_nbroutingpeers.yaml @@ -0,0 +1,203 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: nbroutingpeers.netbird.io +spec: + group: netbird.io + names: + kind: NBRoutingPeer + listKind: NBRoutingPeerList + plural: nbroutingpeers + singular: nbroutingpeer + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: NBRoutingPeer is the Schema for the nbroutingpeers API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: NBRoutingPeerSpec defines the desired state of NBRoutingPeer. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + nodeSelector: + additionalProperties: + type: string + type: object + replicas: + format: int32 + type: integer + resources: + description: ResourceRequirements describes the compute resource requirements. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + tolerations: + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + status: + description: NBRoutingPeerStatus defines the observed state of NBRoutingPeer. + properties: + conditions: + items: + description: NBCondition defines a condition in NBSetupKey status. + properties: + lastProbeTime: + description: Last time we probed the condition. + format: date-time + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: Human-readable message indicating details about + last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's + last transition. + type: string + status: + description: |- + Status is the status of the condition. + Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + required: + - status + - type + type: object + type: array + networkID: + type: string + routerID: + type: string + setupKeyID: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/kubernetes-operator/crds/netbird.io_nbsetupkeys.yaml b/helm/kubernetes-operator/crds/netbird.io_nbsetupkeys.yaml index 559c660..6f0148d 100644 --- a/helm/kubernetes-operator/crds/netbird.io_nbsetupkeys.yaml +++ b/helm/kubernetes-operator/crds/netbird.io_nbsetupkeys.yaml @@ -75,8 +75,7 @@ spec: properties: conditions: items: - description: NBSetupKeyCondition defines a condition in NBSetupKey - status. + description: NBCondition defines a condition in NBSetupKey status. properties: lastProbeTime: description: Last time we probed the condition. diff --git a/helm/kubernetes-operator/templates/deployment.yaml b/helm/kubernetes-operator/templates/deployment.yaml index 962f474..d7466d9 100644 --- a/helm/kubernetes-operator/templates/deployment.yaml +++ b/helm/kubernetes-operator/templates/deployment.yaml @@ -48,6 +48,18 @@ spec: {{- if .Values.managementURL }} - --netbird-management-url={{.Values.managementURL}} {{- end }} + {{- if .Values.cluster.name }} + - --cluster-name={{.Values.cluster.name}} + {{- end }} + {{- if .Values.ingress.namespacedNetworks }} + - --namespaced-networks={{.Values.ingress.namespacedNetworks}} + {{- end }} + {{- if .Values.cluster.dns }} + - --cluster-dns={{.Values.cluster.dns}} + {{- end }} + {{- if or .Values.netbirdAPI.key .Values.netbirdAPI.keyFromSecret }} + - --netbird-api-key=$(NB_API_KEY) + {{- end }} ports: - name: webhook-server containerPort: {{ .Values.webhook.service.port }} @@ -62,6 +74,15 @@ spec: periodSeconds: {{ .Values.operator.livenessProbe.periodSeconds }} successThreshold: {{ .Values.operator.livenessProbe.successThreshold }} timeoutSeconds: {{ .Values.operator.livenessProbe.timeoutSeconds }} + {{- if or .Values.netbirdAPI.key .Values.netbirdAPI.keyFromSecret }} + envFrom: + - secretRef: + {{- if .Values.netbirdAPI.keyFromSecret }} + name: {{.Values.netbirdAPI.keyFromSecret}} + {{- else }} + name: {{ include "kubernetes-operator.fullname" . }} + {{- end }} + {{- end }} readinessProbe: failureThreshold: 3 httpGet: diff --git a/helm/kubernetes-operator/templates/nbpolicies.yaml b/helm/kubernetes-operator/templates/nbpolicies.yaml new file mode 100644 index 0000000..5171b1f --- /dev/null +++ b/helm/kubernetes-operator/templates/nbpolicies.yaml @@ -0,0 +1,28 @@ +{{- range $k, $v := $.Values.ingress.policies }} +--- +apiVersion: netbird.io/v1 +kind: NBPolicy +metadata: + finalizers: + - netbird.io/cleanup + labels: + app.kubernetes.io/component: operator + {{- include "kubernetes-operator.labels" $ | nindent 4 }} + name: {{ $k }} +spec: + name: {{ $v.name }} + sourceGroups: + {{ toYaml $v.sourceGroups | nindent 4}} + {{- if $v.description }} + description: {{ $v.description }} + {{- end }} + {{- if $v.protocols }} + protocols: {{ $v.protocols }} + {{- end }} + {{- if $v.ports }} + ports: {{ $v.ports }} + {{- end }} + {{- if hasKey $v "bidirectional" }} + bidirectional: {{ $v.bidirectional }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/kubernetes-operator/templates/nbroutingpeers.yaml b/helm/kubernetes-operator/templates/nbroutingpeers.yaml new file mode 100644 index 0000000..e8da3fb --- /dev/null +++ b/helm/kubernetes-operator/templates/nbroutingpeers.yaml @@ -0,0 +1,75 @@ +{{- if and .Values.ingress.enabled .Values.ingress.router.enabled }} +{{- if .Values.ingress.namespacedNetworks }} +{{ $defaults := .Values.ingress.router }} +{{ range $k, $v := .Values.ingress.router.namespaces }} +apiVersion: netbird.io/v1 +kind: NBRoutingPeer +metadata: + finalizers: + - netbird.io/cleanup + labels: + app.kubernetes.io/component: operator + {{- include "kubernetes-operator.labels" $ | nindent 4 }} + name: router + namespace: {{ $k }} +{{ $spec := merge $defaults $v }} +{{- if or (or (or $spec.replicas $spec.resources) (or $spec.labels $spec.annotations)) (or $spec.nodeSelector $spec.tolerations) }} +spec: + {{- if $spec.replicas }} + replicas: {{ $spec.replicas }} + {{- end }} + {{- if $spec.resources }} + resources: {{ $spec.resources }} + {{- end }} + {{- if $spec.labels }} + labels: {{ $spec.labels }} + {{- end }} + {{- if $spec.annotations }} + annotations: {{ $spec.annotations }} + {{- end }} + {{- if $spec.nodeSelector }} + nodeSelector: {{ $spec.nodeSelector }} + {{- end }} + {{- if $spec.tolerations }} + tolerations: {{ $spec.tolerations }} + {{- end }} +{{- end }} +--- +{{- end }} +{{- else }} +{{- with .Values.ingress.router }} +apiVersion: netbird.io/v1 +kind: NBRoutingPeer +metadata: + finalizers: + - netbird.io/cleanup + labels: + app.kubernetes.io/component: operator + {{- include "kubernetes-operator.labels" $ | nindent 4 }} + name: router +{{- if or (or (or .replicas .resources) (or .labels .annotations)) (or .nodeSelector .tolerations) }} +spec: + {{- if .replicas }} + replicas: {{ .replicas }} + {{- end }} + {{- if .resources }} + resources: {{ .resources }} + {{- end }} + {{- if .labels }} + labels: {{ .labels }} + {{- end }} + {{- if .annotations }} + annotations: {{ .annotations }} + {{- end }} + {{- if .nodeSelector }} + nodeSelector: {{ .nodeSelector }} + {{- end }} + {{- if .tolerations }} + tolerations: {{ .tolerations }} + {{- end }} +{{- else }} +spec: {} +{{- end }} +{{- end }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/kubernetes-operator/templates/pre-delete.yaml b/helm/kubernetes-operator/templates/pre-delete.yaml new file mode 100644 index 0000000..7ab3551 --- /dev/null +++ b/helm/kubernetes-operator/templates/pre-delete.yaml @@ -0,0 +1,69 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "kubernetes-operator.fullname" . }}-delete-routers + labels: + app.kubernetes.io/component: operator + {{- include "kubernetes-operator.labels" . | nindent 4 }} + annotations: + helm.sh/hook: pre-delete + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded +spec: + backOffLimit: 3 + template: + metadata: + name: {{ include "kubernetes-operator.fullname" . }} + labels: + app.kubernetes.io/component: operator + {{- include "kubernetes-operator.labels" . | nindent 8 }} + {{- with .Values.operator.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + containers: + - name: pre-delete + image: "bitnami/kubectl:latest" + args: + - delete + - --all + - -A + - --cascade=foreground + - --ignore-not-found + - NBRoutingPeer + serviceAccountName: {{ include "kubernetes-operator.serviceAccountName" . }} + restartPolicy: Never +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "kubernetes-operator.fullname" . }}-delete-policies + labels: + app.kubernetes.io/component: operator + {{- include "kubernetes-operator.labels" . | nindent 4 }} + annotations: + helm.sh/hook: pre-delete + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded +spec: + backOffLimit: 3 + template: + metadata: + name: {{ include "kubernetes-operator.fullname" . }} + labels: + app.kubernetes.io/component: operator + {{- include "kubernetes-operator.labels" . | nindent 8 }} + {{- with .Values.operator.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + containers: + - name: pre-delete + image: "bitnami/kubectl:latest" + args: + - delete + - --all + - --cascade=foreground + - --ignore-not-found + - NBPolicy + serviceAccountName: {{ include "kubernetes-operator.serviceAccountName" . }} + restartPolicy: Never +--- \ No newline at end of file diff --git a/helm/kubernetes-operator/templates/rbac.yaml b/helm/kubernetes-operator/templates/rbac.yaml index 62af8bc..2593d21 100644 --- a/helm/kubernetes-operator/templates/rbac.yaml +++ b/helm/kubernetes-operator/templates/rbac.yaml @@ -27,6 +27,79 @@ rules: - get - patch - update +{{- if .Values.ingress.enabled }} +- apiGroups: + - netbird.io + resources: + - nbgroups + - nbresources + - nbroutingpeers + - nbpolicies + verbs: + - get + - patch + - update + - list + - watch + - create + - delete +- apiGroups: + - netbird.io + resources: + - nbgroups/status + - nbresources/status + - nbroutingpeers/status + - nbpolicies/status + verbs: + - get + - patch + - update +- apiGroups: + - netbird.io + resources: + - nbgroups/finalizers + - nbresources/finalizers + - nbroutingpeers/finalizers + - nbpolicies/finalizers + verbs: + - update +- apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch + - update + - patch +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - services/finalizers + verbs: + - update +- apiGroups: + - apps + resources: + - deployments + verbs: + - get + - patch + - update + - list + - watch + - create + - delete +{{- end }} - apiGroups: - "" resources: @@ -35,7 +108,7 @@ rules: - get - list - watch -{{- if .Values.clusterSecretsPermissions.allowAllSecrets }} +{{- if or .Values.ingress.enabled .Values.clusterSecretsPermissions.allowAllSecrets }} - apiGroups: - "" resources: @@ -44,6 +117,12 @@ rules: - get - list - watch +{{- if .Values.ingress.enabled }} + - patch + - update + - create + - delete +{{- end }} {{- end }} --- apiVersion: rbac.authorization.k8s.io/v1 diff --git a/helm/kubernetes-operator/templates/secret.yaml b/helm/kubernetes-operator/templates/secret.yaml new file mode 100644 index 0000000..099517e --- /dev/null +++ b/helm/kubernetes-operator/templates/secret.yaml @@ -0,0 +1,11 @@ +{{- if .Values.netbirdAPI.key }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "kubernetes-operator.fullname" . }} + labels: + app.kubernetes.io/component: operator + {{- include "kubernetes-operator.labels" . | nindent 4 }} +stringData: + NB_API_KEY: {{ .Values.netbirdAPI.key }} +{{- end }} \ No newline at end of file diff --git a/helm/kubernetes-operator/templates/webhook.yaml b/helm/kubernetes-operator/templates/webhook.yaml index 52e9afc..5ac8055 100644 --- a/helm/kubernetes-operator/templates/webhook.yaml +++ b/helm/kubernetes-operator/templates/webhook.yaml @@ -12,7 +12,7 @@ metadata: {{- include "kubernetes-operator.labels" . | nindent 4 }} webhooks: - clientConfig: - {{- if not $.Values.webhook.enableCertManager -}} + {{- if not $.Values.webhook.enableCertManager }} caBundle: {{ $tls.caCert }} {{ end }} service: @@ -64,7 +64,7 @@ metadata: {{- include "kubernetes-operator.labels" . | nindent 4 }} webhooks: - clientConfig: - {{- if not $.Values.webhook.enableCertManager -}} + {{- if not $.Values.webhook.enableCertManager }} caBundle: {{ $tls.caCert }} {{ end }} service: @@ -89,8 +89,127 @@ webhooks: - CREATE - UPDATE resources: - - "*/*" + - "nbsetupkeys" sideEffects: None +{{- if and $.Values.ingress.enabled (or .Values.netbirdAPI.key .Values.netbirdAPI.keyFromSecret) }} +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: +{{- if $.Values.webhook.enableCertManager }} + annotations: + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ template "kubernetes-operator.fullname" . }}-serving-cert +{{- end }} + name: {{ include "kubernetes-operator.fullname" . }}-vnbresource-webhook + labels: + {{- include "kubernetes-operator.labels" . | nindent 4 }} +webhooks: +- clientConfig: + {{- if not $.Values.webhook.enableCertManager }} + caBundle: {{ $tls.caCert }} + {{ end }} + service: + name: {{ template "kubernetes-operator.webhookService" . }} + namespace: {{ $.Release.Namespace }} + path: /validate-netbird-io-v1-nbresource + failurePolicy: Fail + name: vnbresource-v1.netbird.io + admissionReviewVersions: + - v1 + {{- if .Values.webhook.namespaceSelectors }} + namespaceSelector: + matchExpressions: + {{ toYaml .Values.webhook.namespaceSelectors | nindent 4 }} + {{ end }} + rules: + - apiGroups: + - netbird.io + apiVersions: + - v1 + operations: + - DELETE + resources: + - "nbresources" + sideEffects: None +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: +{{- if $.Values.webhook.enableCertManager }} + annotations: + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ template "kubernetes-operator.fullname" . }}-serving-cert +{{- end }} + name: {{ include "kubernetes-operator.fullname" . }}-vnbroutingpeer-webhook + labels: + {{- include "kubernetes-operator.labels" . | nindent 4 }} +webhooks: +- clientConfig: + {{- if not $.Values.webhook.enableCertManager }} + caBundle: {{ $tls.caCert }} + {{ end }} + service: + name: {{ template "kubernetes-operator.webhookService" . }} + namespace: {{ $.Release.Namespace }} + path: /validate-netbird-io-v1-nbroutingpeer + failurePolicy: Fail + name: vnbroutingpeer-v1.netbird.io + admissionReviewVersions: + - v1 + {{- if .Values.webhook.namespaceSelectors }} + namespaceSelector: + matchExpressions: + {{ toYaml .Values.webhook.namespaceSelectors | nindent 4 }} + {{ end }} + rules: + - apiGroups: + - netbird.io + apiVersions: + - v1 + operations: + - DELETE + resources: + - "nbroutingpeers" + sideEffects: None +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: +{{- if $.Values.webhook.enableCertManager }} + annotations: + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ template "kubernetes-operator.fullname" . }}-serving-cert +{{- end }} + name: {{ include "kubernetes-operator.fullname" . }}-vnbgroup-webhook + labels: + {{- include "kubernetes-operator.labels" . | nindent 4 }} +webhooks: +- clientConfig: + {{- if not $.Values.webhook.enableCertManager }} + caBundle: {{ $tls.caCert }} + {{ end }} + service: + name: {{ template "kubernetes-operator.webhookService" . }} + namespace: {{ $.Release.Namespace }} + path: /validate-netbird-io-v1-nbgroup + failurePolicy: Fail + name: vnbgroup-v1.netbird.io + admissionReviewVersions: + - v1 + {{- if .Values.webhook.namespaceSelectors }} + namespaceSelector: + matchExpressions: + {{ toYaml .Values.webhook.namespaceSelectors | nindent 4 }} + {{ end }} + rules: + - apiGroups: + - netbird.io + apiVersions: + - v1 + operations: + - DELETE + resources: + - "nbgroups" + sideEffects: None +{{- end }} --- {{- if not $.Values.webhook.enableCertManager }} apiVersion: v1 diff --git a/helm/kubernetes-operator/values.yaml b/helm/kubernetes-operator/values.yaml index bf3c61a..24f2167 100644 --- a/helm/kubernetes-operator/values.yaml +++ b/helm/kubernetes-operator/values.yaml @@ -128,3 +128,49 @@ operator: tolerations: [] affinity: {} + +ingress: + enabled: false + namespacedNetworks: false + router: + enabled: false + # replicas: 3 + # resources: + # requests: + # cpu: 100m + # memory: 100Mi + # limits: + # cpu: 100m + # memory: 100Mi + # labels: {} + # annotations: {} + # nodeSelector: {} + # tolerations: [] + # Only needed for namespacedNetworks + # namespaces: + # default: + # replicas: 3 + # resources: + # requests: + # cpu: 100m + # memory: 100Mi + # limits: + # cpu: 100m + # memory: 100Mi + # labels: {} + # annotations: {} + # nodeSelector: {} + # tolerations: [] + policies: {} + # default: + # name: Kubernetes Default Policy + # sourceGroups: + # - All + +cluster: {} + # name: kubernetes + # dns: svc.cluster.local + +netbirdAPI: {} + # key: "nbp_m0LM9ZZvDUzFO0pY50iChDOTxJgKFM3DIqmZ" + # keyFromSecret: "Secret name with NB_API_KEY=Service Account Token" \ No newline at end of file diff --git a/internal/controller/nbgroup_controller.go b/internal/controller/nbgroup_controller.go new file mode 100644 index 0000000..0302edb --- /dev/null +++ b/internal/controller/nbgroup_controller.go @@ -0,0 +1,179 @@ +package controller + +import ( + "context" + "fmt" + "strings" + "time" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" + "github.com/netbirdio/kubernetes-operator/internal/util" + netbird "github.com/netbirdio/netbird/management/client/rest" + "github.com/netbirdio/netbird/management/server/http/api" +) + +// NBGroupReconciler reconciles a NBGroup object +type NBGroupReconciler struct { + client.Client + Scheme *runtime.Scheme + APIKey string + ManagementURL string + netbird *netbird.Client +} + +const ( + defaultRequeueAfter = 15 * time.Minute +) + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +func (r *NBGroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, err error) { + _ = log.FromContext(ctx) + + ctrl.Log.Info("NBGroup: Reconciling", "namespace", req.Namespace, "name", req.Name) + + nbGroup := netbirdiov1.NBGroup{} + err = r.Client.Get(ctx, req.NamespacedName, &nbGroup) + if err != nil { + if !errors.IsNotFound(err) { + ctrl.Log.Error(errKubernetesAPI, "error getting NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + } + return ctrl.Result{RequeueAfter: defaultRequeueAfter}, nil + } + + originalGroup := nbGroup.DeepCopy() + defer func() { + if !originalGroup.Status.Equal(nbGroup.Status) { + updateErr := r.Client.Status().Update(ctx, &nbGroup) + if updateErr != nil { + err = updateErr + } + } + if !res.Requeue && res.RequeueAfter == 0 { + res.RequeueAfter = defaultRequeueAfter + } + }() + + if nbGroup.DeletionTimestamp != nil { + if len(nbGroup.Finalizers) == 0 { + return ctrl.Result{}, nil + } + return ctrl.Result{}, r.handleDelete(ctx, req, nbGroup) + } + + groups, err := r.netbird.Groups.List(ctx) + var group *api.Group + for _, g := range groups { + if g.Name == nbGroup.Spec.Name { + group = &g + } + } + if nbGroup.Status.GroupID == nil && group == nil { + ctrl.Log.Info("NBGroup: Creating group on NetBird", "name", nbGroup.Spec.Name) + group, err := r.netbird.Groups.Create(ctx, api.GroupRequest{ + Name: nbGroup.Spec.Name, + }) + ctrl.Log.Info("NBGroup: Created group on NetBird", "name", nbGroup.Spec.Name, "id", group.Id) + + if err != nil { + nbGroup.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("NetBird API Error: %v", err)) + ctrl.Log.Error(errNetBirdAPI, "error creating group", "err", err, "namespace", req.Namespace, "name", req.Name) + return ctrl.Result{}, err + } + + nbGroup.Status.GroupID = &group.Id + nbGroup.Status.Conditions = netbirdiov1.NBConditionTrue() + } else if nbGroup.Status.GroupID == nil && group != nil { + ctrl.Log.Info("NBGroup: Found group with same name on NetBird", "name", nbGroup.Spec.Name, "id", group.Id) + nbGroup.Status.GroupID = &group.Id + nbGroup.Status.Conditions = netbirdiov1.NBConditionTrue() + } else if group == nil { + ctrl.Log.Info("NBGroup: Group was deleted", "name", nbGroup.Spec.Name, "id", *nbGroup.Status.GroupID) + nbGroup.Status.GroupID = nil + nbGroup.Status.Conditions = netbirdiov1.NBConditionFalse("GroupGone", "Group was deleted from NetBird API") + return ctrl.Result{Requeue: true}, nil + } else { + nbGroup.Status.Conditions = netbirdiov1.NBConditionTrue() + } + + if nbGroup.Status.GroupID != nil && group != nil && *nbGroup.Status.GroupID != group.Id { + // There are two possibilities here, either someone deleted and created the group in NetBird, thus the changed ID + // Or there's a conflict with something else, either way, we just need to take the new ID here + nbGroup.Status.GroupID = &group.Id + nbGroup.Status.Conditions = netbirdiov1.NBConditionTrue() + } + + return ctrl.Result{}, nil +} + +func (r *NBGroupReconciler) handleDelete(ctx context.Context, req ctrl.Request, nbGroup netbirdiov1.NBGroup) error { + if nbGroup.Status.GroupID == nil { + nbGroup.Finalizers = util.Without(nbGroup.Finalizers, "netbird.io/group-cleanup") + err := r.Client.Update(ctx, &nbGroup) + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error updating NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + return err + } + + return nil + } + + err := r.netbird.Groups.Delete(ctx, *nbGroup.Status.GroupID) + if err != nil && !strings.Contains(err.Error(), "not found") && !strings.Contains(err.Error(), "linked") { + ctrl.Log.Error(errNetBirdAPI, "error deleting group", "err", err, "namespace", req.Namespace, "name", req.Name) + return err + } + + if err != nil && strings.Contains(err.Error(), "linked") { + ctrl.Log.Info("group still linked to resources on netbird", "err", err, "namespace", req.Namespace, "name", req.Name) + // Check if group is defined elsewhere in the cluster + var groups netbirdiov1.NBGroupList + listErr := r.Client.List(ctx, &groups) + if listErr != nil { + ctrl.Log.Error(errKubernetesAPI, "error listing NBGroups", "err", listErr) + return listErr + } + for _, v := range groups.Items { + if v.UID == nbGroup.UID { + continue + } + if v.Status.GroupID != nil && nbGroup.Status.GroupID != nil && *v.Status.GroupID == *nbGroup.Status.GroupID { + // Same group, multiple resources + ctrl.Log.Info("group exists in another namespace", "namespace", v.Namespace, "name", v.Name) + nbGroup.Finalizers = util.Without(nbGroup.Finalizers, "netbird.io/group-cleanup") + err = r.Client.Update(ctx, &nbGroup) + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error updating NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + return err + } + return nil + } + } + return err + } + + nbGroup.Finalizers = util.Without(nbGroup.Finalizers, "netbird.io/group-cleanup") + err = r.Client.Update(ctx, &nbGroup) + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error updating NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + return err + } + + return nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *NBGroupReconciler) SetupWithManager(mgr ctrl.Manager) error { + r.netbird = netbird.New(r.ManagementURL, r.APIKey) + + return ctrl.NewControllerManagedBy(mgr). + For(&netbirdiov1.NBGroup{}). + Named("nbgroup"). + Complete(r) +} diff --git a/internal/controller/nbgroup_controller_test.go b/internal/controller/nbgroup_controller_test.go new file mode 100644 index 0000000..60ea7ef --- /dev/null +++ b/internal/controller/nbgroup_controller_test.go @@ -0,0 +1,70 @@ +package controller + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" +) + +var _ = Describe("NBGroup Controller", func() { + Context("When reconciling a resource", func() { + const resourceName = "test-resource" + + ctx := context.Background() + + typeNamespacedName := types.NamespacedName{ + Name: resourceName, + Namespace: "default", // TODO(user):Modify as needed + } + nbgroup := &netbirdiov1.NBGroup{} + + BeforeEach(func() { + Skip("Not implemented yet") + By("creating the custom resource for the Kind NBGroup") + err := k8sClient.Get(ctx, typeNamespacedName, nbgroup) + if err != nil && errors.IsNotFound(err) { + resource := &netbirdiov1.NBGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourceName, + Namespace: "default", + }, + // TODO(user): Specify other spec details if needed. + } + Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + } + }) + + AfterEach(func() { + // TODO(user): Cleanup logic after each test, like removing the resource instance. + resource := &netbirdiov1.NBGroup{} + err := k8sClient.Get(ctx, typeNamespacedName, resource) + Expect(err).NotTo(HaveOccurred()) + + By("Cleanup the specific resource instance NBGroup") + Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) + }) + It("should successfully reconcile the resource", func() { + Skip("Not implemented yet") + By("Reconciling the created resource") + controllerReconciler := &NBGroupReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + } + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. + // Example: If you expect a certain status condition after reconciliation, verify it here. + }) + }) +}) diff --git a/internal/controller/nbpolicy_controller.go b/internal/controller/nbpolicy_controller.go new file mode 100644 index 0000000..a388339 --- /dev/null +++ b/internal/controller/nbpolicy_controller.go @@ -0,0 +1,372 @@ +package controller + +import ( + "context" + "fmt" + "strconv" + "strings" + + "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" + "sigs.k8s.io/controller-runtime/pkg/log" + + netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" + "github.com/netbirdio/kubernetes-operator/internal/util" + netbird "github.com/netbirdio/netbird/management/client/rest" + "github.com/netbirdio/netbird/management/server/http/api" +) + +// NBPolicyReconciler reconciles a NBPolicy object +type NBPolicyReconciler struct { + client.Client + Scheme *runtime.Scheme + ClusterName string + APIKey string + ManagementURL string + netbird *netbird.Client +} + +var ( + errUnknownProtocol = fmt.Errorf("Unknown protocol") + errKubernetesAPI = fmt.Errorf("kubernetes API error") + errNetBirdAPI = fmt.Errorf("netbird API error") +) + +func (r *NBPolicyReconciler) getResources(ctx context.Context, nbPolicy *netbirdiov1.NBPolicy) ([]netbirdiov1.NBResource, error) { + var resourceList []netbirdiov1.NBResource + var updatedManagedServiceList []string + for _, rss := range nbPolicy.Status.ManagedServiceList { + var resource netbirdiov1.NBResource + namespacedName := types.NamespacedName{Namespace: strings.Split(rss, "/")[0], Name: strings.Split(rss, "/")[1]} + err := r.Client.Get(ctx, namespacedName, &resource) + if err != nil && !errors.IsNotFound(err) { + ctrl.Log.Error(errKubernetesAPI, "Error getting NBResource", "namespace", namespacedName.Namespace, "name", namespacedName.Name) + nbPolicy.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("Error getting NBResource: %v", err)) + return nil, err + } + if err == nil && resource.DeletionTimestamp == nil { + updatedManagedServiceList = append(updatedManagedServiceList, rss) + resourceList = append(resourceList, resource) + } + } + + nbPolicy.Status.ManagedServiceList = updatedManagedServiceList + + return resourceList, nil +} + +func (r *NBPolicyReconciler) mapResources(ctx context.Context, req ctrl.Request, nbPolicy *netbirdiov1.NBPolicy, resources []netbirdiov1.NBResource) (map[string][]int32, []string, error) { + portMapping := map[string]map[int32]interface{}{ + "tcp": make(map[int32]interface{}), + "udp": make(map[int32]interface{}), + } + groups, err := r.groupNamesToIDs(ctx, req, nbPolicy.Spec.DestinationGroups) + if err != nil { + return nil, nil, err + } + + for _, resource := range resources { + if resource.Status.PolicyName != nil && *resource.Status.PolicyName == nbPolicy.Name { + // Groups + groups = append(groups, resource.Status.Groups...) + + for _, p := range resource.Spec.TCPPorts { + portMapping["tcp"][p] = nil + } + for _, p := range resource.Spec.UDPPorts { + portMapping["udp"][p] = nil + } + } + } + + ports := make(map[string][]int32) + for k, vs := range portMapping { + for v := range vs { + ports[k] = append(ports[k], v) + } + } + + return ports, groups, nil +} + +func (r *NBPolicyReconciler) createPolicy(ctx context.Context, req ctrl.Request, nbPolicy *netbirdiov1.NBPolicy, protocol string, sourceGroupIDs, destinationGroupIDs, ports []string) (*string, error) { + policyName := fmt.Sprintf("%s %s", nbPolicy.Spec.Name, strings.ToUpper(protocol)) + ctrl.Log.Info("Creating NetBird Policy", "name", policyName, "description", nbPolicy.Spec.Description, "protocol", protocol, "sources", sourceGroupIDs, "destinations", destinationGroupIDs, "ports", ports, "bidirectional", nbPolicy.Spec.Bidirectional) + policy, err := r.netbird.Policies.Create(ctx, api.PostApiPoliciesJSONRequestBody{ + Enabled: true, + Name: policyName, + Description: &nbPolicy.Spec.Description, + Rules: []api.PolicyRuleUpdate{ + { + Enabled: true, + Name: policyName, + Description: &nbPolicy.Spec.Description, + Action: api.PolicyRuleUpdateActionAccept, + Protocol: api.PolicyRuleUpdateProtocol(protocol), + Bidirectional: nbPolicy.Spec.Bidirectional, + Sources: &sourceGroupIDs, + Destinations: &destinationGroupIDs, + Ports: &ports, + }, + }, + }) + + if err != nil { + ctrl.Log.Error(errNetBirdAPI, "Error creating Policy", "namespace", req.Namespace, "name", req.Name, "err", err) + nbPolicy.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("Error creating policy: %v", err)) + return nil, err + } + + return policy.Id, nil +} + +func (r *NBPolicyReconciler) updatePolicy(ctx context.Context, req ctrl.Request, policyID *string, nbPolicy *netbirdiov1.NBPolicy, protocol string, sourceGroupIDs, destinationGroupIDs, ports []string) (*string, bool, error) { + policyName := fmt.Sprintf("%s %s", nbPolicy.Spec.Name, strings.ToUpper(protocol)) + ctrl.Log.Info("Updating NetBird Policy", "name", policyName, "description", nbPolicy.Spec.Description, "protocol", protocol, "sources", sourceGroupIDs, "destinations", destinationGroupIDs, "ports", ports, "bidirectional", nbPolicy.Spec.Bidirectional) + policy, err := r.netbird.Policies.Update(ctx, *policyID, api.PutApiPoliciesPolicyIdJSONRequestBody{ + Enabled: true, + Name: policyName, + Description: &nbPolicy.Spec.Description, + Rules: []api.PolicyRuleUpdate{ + { + Enabled: true, + Name: policyName, + Description: &nbPolicy.Spec.Description, + Action: api.PolicyRuleUpdateActionAccept, + Protocol: api.PolicyRuleUpdateProtocol(protocol), + Bidirectional: nbPolicy.Spec.Bidirectional, + Sources: &sourceGroupIDs, + Destinations: &destinationGroupIDs, + Ports: &ports, + }, + }, + }) + + if err != nil && !strings.Contains(err.Error(), "not found") { + ctrl.Log.Error(errNetBirdAPI, "Error updating Policy", "namespace", req.Namespace, "name", req.Name, "err", err) + nbPolicy.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("Error updating policy: %v", err)) + return policyID, false, err + } + + requeue := false + + if err != nil && strings.Contains(err.Error(), "not found") { + ctrl.Log.Info("Policy deleted from NetBird API, recreating", "namespace", req.Namespace, "name", req.Name, "protocol", protocol) + policyID = nil + requeue = true + nbPolicy.Status.Conditions = netbirdiov1.NBConditionFalse("Gone", "Policy deleted from NetBird API") + } + + if err == nil && (policyID == nil || *policy.Id != *policyID) { + policyID = policy.Id + } + return policyID, requeue, nil +} + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +func (r *NBPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, err error) { + _ = log.FromContext(ctx) + + ctrl.Log.Info("NBPolicy: Reconciling", "namespace", req.Namespace, "name", req.Name) + + var nbPolicy netbirdiov1.NBPolicy + err = r.Client.Get(ctx, req.NamespacedName, &nbPolicy) + if err != nil { + if errors.IsNotFound(err) { + err = nil + } + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error getting NBPolicy", "err", err, "namespace", req.Namespace, "name", req.Name) + } + return ctrl.Result{RequeueAfter: defaultRequeueAfter}, err + } + + originalPolicy := nbPolicy.DeepCopy() + + defer func() { + if !originalPolicy.Status.Equal(nbPolicy.Status) { + updateErr := r.Client.Status().Update(ctx, &nbPolicy) + if updateErr != nil { + err = updateErr + } + } + if !res.Requeue && res.RequeueAfter == 0 { + res.RequeueAfter = defaultRequeueAfter + } + }() + + if nbPolicy.DeletionTimestamp != nil { + if len(nbPolicy.Finalizers) == 0 { + return ctrl.Result{}, nil + } + return ctrl.Result{}, r.handleDelete(ctx, req, nbPolicy) + } + + resourceList, err := r.getResources(ctx, &nbPolicy) + if err != nil { + return ctrl.Result{}, err + } + + portMapping, destGroups, err := r.mapResources(ctx, req, &nbPolicy, resourceList) + if err != nil { + return ctrl.Result{}, err + } + + sourceGroupIDs, err := r.groupNamesToIDs(ctx, req, nbPolicy.Spec.SourceGroups) + if err != nil { + nbPolicy.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("Error getting group IDs: %v", err)) + return ctrl.Result{}, err + } + + requeue, err := r.handlePolicies(ctx, req, &nbPolicy, sourceGroupIDs, destGroups, portMapping) + + if requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err + } + + nbPolicy.Status.Conditions = netbirdiov1.NBConditionTrue() + + return ctrl.Result{}, nil +} + +func (r *NBPolicyReconciler) handlePolicies(ctx context.Context, req ctrl.Request, nbPolicy *netbirdiov1.NBPolicy, sourceGroups, destGroups []string, portMapping map[string][]int32) (bool, error) { + requeue := false + + for protocol, ports := range portMapping { + var policyID *string + switch protocol { + case "tcp": + policyID = nbPolicy.Status.TCPPolicyID + case "udp": + policyID = nbPolicy.Status.UDPPolicyID + default: + ctrl.Log.Error(errKubernetesAPI, "Unknown protocol", "namespace", req.Namespace, "name", req.Name, "protocol", protocol) + nbPolicy.Status.Conditions = netbirdiov1.NBConditionFalse("ConfigError", fmt.Sprintf("Unknown protocol: %s", protocol)) + return requeue, errUnknownProtocol + } + + if len(nbPolicy.Spec.Protocols) > 0 && !util.Contains(nbPolicy.Spec.Protocols, protocol) { + if policyID != nil { + ctrl.Log.Info("Deleting protocol policy as NBPolicy has restricted protocols", "protocol", protocol) + err := r.netbird.Policies.Delete(ctx, *policyID) + if err != nil && !strings.Contains(err.Error(), "not found") { + nbPolicy.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("Error deleting policy: %v", err)) + return requeue, err + } + policyID = nil + + } else { + ctrl.Log.Info("Ignoring protocol as NBPolicy has restricted protocols", "protocol", protocol) + } + } else if len(ports) == 0 && policyID == nil { + ctrl.Log.Info("0 ports found for protocol in policy", "namespace", req.Namespace, "name", req.Name, "protocol", protocol) + continue + } else if len(destGroups) == 0 && policyID == nil { + ctrl.Log.Info("no destinations found for protocol in policy", "namespace", req.Namespace, "name", req.Name, "protocol", protocol) + continue + } else if len(sourceGroups) == 0 && policyID == nil { + ctrl.Log.Info("no sources found for protocol in policy", "namespace", req.Namespace, "name", req.Name, "protocol", protocol) + continue + } else if len(ports) == 0 || len(destGroups) == 0 || len(sourceGroups) == 0 { + // Delete policy + ctrl.Log.Info("Deleting policy", "namespace", req.Namespace, "name", req.Name, "protocol", protocol) + err := r.netbird.Policies.Delete(ctx, *policyID) + if err != nil && !strings.Contains(err.Error(), "not found") { + nbPolicy.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("Error deleting policy: %v", err)) + return requeue, err + } + policyID = nil + } else { + var stringPorts []string + for _, v := range ports { + stringPorts = append(stringPorts, strconv.FormatInt(int64(v), 10)) + } + for _, v := range nbPolicy.Spec.Ports { + stringPorts = append(stringPorts, strconv.FormatInt(int64(v), 10)) + } + + var err error + if policyID == nil { + policyID, err = r.createPolicy(ctx, req, nbPolicy, protocol, sourceGroups, destGroups, stringPorts) + } else { + policyID, requeue, err = r.updatePolicy(ctx, req, policyID, nbPolicy, protocol, sourceGroups, destGroups, stringPorts) + } + if err != nil { + return requeue, err + } + } + + switch protocol { + case "tcp": + nbPolicy.Status.TCPPolicyID = policyID + case "udp": + nbPolicy.Status.UDPPolicyID = policyID + default: + ctrl.Log.Error(errKubernetesAPI, "Unknown protocol", "namespace", req.Namespace, "name", req.Name, "protocol", protocol) + nbPolicy.Status.Conditions = netbirdiov1.NBConditionFalse("ConfigError", fmt.Sprintf("Unknown protocol: %s", protocol)) + return requeue, errUnknownProtocol + } + } + + return requeue, nil +} + +func (r *NBPolicyReconciler) handleDelete(ctx context.Context, req ctrl.Request, nbPolicy netbirdiov1.NBPolicy) error { + if nbPolicy.Status.TCPPolicyID != nil { + err := r.netbird.Policies.Delete(ctx, *nbPolicy.Status.TCPPolicyID) + if err != nil { + return err + } + nbPolicy.Status.TCPPolicyID = nil + } + if nbPolicy.Status.UDPPolicyID != nil { + err := r.netbird.Policies.Delete(ctx, *nbPolicy.Status.UDPPolicyID) + if err != nil { + return err + } + nbPolicy.Status.UDPPolicyID = nil + } + if util.Contains(nbPolicy.Finalizers, "netbird.io/cleanup") { + nbPolicy.Finalizers = util.Without(nbPolicy.Finalizers, "netbird.io/cleanup") + err := r.Client.Update(ctx, &nbPolicy) + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "Error updating NBPolicy", "namespace", req.Namespace, "name", req.Name, "err", err) + return err + } + } + return nil +} + +func (r *NBPolicyReconciler) groupNamesToIDs(ctx context.Context, req ctrl.Request, groupNames []string) ([]string, error) { + groups, err := r.netbird.Groups.List(ctx) + if err != nil { + ctrl.Log.Error(errNetBirdAPI, "Error listing Groups", "namespace", req.Namespace, "name", req.Name, "err", err) + return nil, err + } + + groupNameIDMapping := make(map[string]string) + for _, g := range groups { + groupNameIDMapping[g.Name] = g.Id + } + + ret := make([]string, 0, len(groupNames)) + for _, g := range groupNames { + ret = append(ret, groupNameIDMapping[g]) + } + + return ret, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *NBPolicyReconciler) SetupWithManager(mgr ctrl.Manager) error { + r.netbird = netbird.New(r.ManagementURL, r.APIKey) + + return ctrl.NewControllerManagedBy(mgr). + For(&netbirdiov1.NBPolicy{}). + Named("nbpolicy"). + Complete(r) +} diff --git a/internal/controller/nbpolicy_controller_test.go b/internal/controller/nbpolicy_controller_test.go new file mode 100644 index 0000000..729bbd4 --- /dev/null +++ b/internal/controller/nbpolicy_controller_test.go @@ -0,0 +1,70 @@ +package controller + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" +) + +var _ = Describe("NBPolicy Controller", func() { + Context("When reconciling a resource", func() { + const resourceName = "test-resource" + + ctx := context.Background() + + typeNamespacedName := types.NamespacedName{ + Name: resourceName, + Namespace: "default", // TODO(user):Modify as needed + } + nbpolicy := &netbirdiov1.NBPolicy{} + + BeforeEach(func() { + Skip("Not implemented yet") + By("creating the custom resource for the Kind NBPolicy") + err := k8sClient.Get(ctx, typeNamespacedName, nbpolicy) + if err != nil && errors.IsNotFound(err) { + resource := &netbirdiov1.NBPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourceName, + Namespace: "default", + }, + // TODO(user): Specify other spec details if needed. + } + Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + } + }) + + AfterEach(func() { + // TODO(user): Cleanup logic after each test, like removing the resource instance. + resource := &netbirdiov1.NBPolicy{} + err := k8sClient.Get(ctx, typeNamespacedName, resource) + Expect(err).NotTo(HaveOccurred()) + + By("Cleanup the specific resource instance NBPolicy") + Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) + }) + It("should successfully reconcile the resource", func() { + Skip("Not implemented yet") + By("Reconciling the created resource") + controllerReconciler := &NBPolicyReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + } + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. + // Example: If you expect a certain status condition after reconciliation, verify it here. + }) + }) +}) diff --git a/internal/controller/nbresource_controller.go b/internal/controller/nbresource_controller.go new file mode 100644 index 0000000..2c8b8c0 --- /dev/null +++ b/internal/controller/nbresource_controller.go @@ -0,0 +1,400 @@ +package controller + +import ( + "context" + "fmt" + "strings" + "time" + + "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/log" + + netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" + "github.com/netbirdio/kubernetes-operator/internal/util" + netbird "github.com/netbirdio/netbird/management/client/rest" + "github.com/netbirdio/netbird/management/server/http/api" +) + +// NBResourceReconciler reconciles a NBResource object +type NBResourceReconciler struct { + client.Client + Scheme *runtime.Scheme + APIKey string + ManagementURL string + netbird *netbird.Client +} + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +func (r *NBResourceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, err error) { + _ = log.FromContext(ctx) + ctrl.Log.Info("NBResource: Reconciling", "namespace", req.Namespace, "name", req.Name) + + nbResource := &netbirdiov1.NBResource{} + err = r.Client.Get(ctx, req.NamespacedName, nbResource) + if err != nil { + if !errors.IsNotFound(err) { + ctrl.Log.Error(errKubernetesAPI, "error getting NBResource", "err", err, "namespace", req.Namespace, "name", req.Name) + } + return ctrl.Result{RequeueAfter: defaultRequeueAfter}, nil + } + + originalResource := nbResource.DeepCopy() + + defer func() { + if !originalResource.Status.Equal(nbResource.Status) { + updateErr := r.Client.Status().Update(ctx, nbResource) + if updateErr != nil { + err = updateErr + } + } + if !res.Requeue && res.RequeueAfter == 0 { + res.RequeueAfter = defaultRequeueAfter + } + }() + + if nbResource.DeletionTimestamp != nil { + if len(nbResource.Finalizers) == 0 { + return ctrl.Result{}, nil + } + return ctrl.Result{}, r.handleDelete(ctx, req, nbResource) + } + + groupIDs, result, err := r.handleGroups(ctx, req, nbResource) + if result != nil { + nbResource.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("Error occurred handling groups: %v", err)) + return *result, err + } + + resource, err := r.handleNetBirdResource(ctx, req, nbResource, groupIDs) + if err != nil { + nbResource.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("Error occurred handling NetBird Network Resource: %v", err)) + return ctrl.Result{}, err + } + + // resource is only nil if requeue is expected + if resource == nil { + return ctrl.Result{Requeue: true}, nil + } + + err = r.handleGroupUpdate(ctx, req, nbResource, groupIDs, resource) + if err != nil { + nbResource.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("Error occurred handling groups: %v", err)) + return ctrl.Result{}, err + } + + err = r.handlePolicy(ctx, req, nbResource, groupIDs) + if err != nil { + nbResource.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("Error occurred handling policy changes: %v", err)) + } + + nbResource.Status.Conditions = netbirdiov1.NBConditionTrue() + + return ctrl.Result{}, nil +} + +func (r *NBResourceReconciler) handlePolicy(ctx context.Context, req ctrl.Request, nbResource *netbirdiov1.NBResource, groupIDs []string) error { + if nbResource.Status.PolicyName == nil && nbResource.Spec.PolicyName == "" { + return nil + } + + updatePolicyStatus := false + + var nbPolicy netbirdiov1.NBPolicy + if nbResource.Spec.PolicyName == "" && nbResource.Status.PolicyName != nil { + nbResource.Status.PolicyName = nil + err := r.Client.Get(ctx, types.NamespacedName{Name: *nbResource.Status.PolicyName}, &nbPolicy) + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error getting NBPolicy", "err", err, "namespace", req.Namespace, "name", req.Name, "policyName", nbResource.Spec.PolicyName) + return err + } + if util.Contains(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String()) { + nbPolicy.Status.ManagedServiceList = util.Without(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String()) + nbPolicy.Status.LastUpdatedAt = &v1.Time{Time: time.Now()} + updatePolicyStatus = true + } + } else { + err := r.Client.Get(ctx, types.NamespacedName{Name: nbResource.Spec.PolicyName}, &nbPolicy) + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error getting NBPolicy", "err", err, "namespace", req.Namespace, "name", req.Name, "policyName", nbResource.Spec.PolicyName) + return err + } + + if nbResource.Status.PolicyName == nil || *nbResource.Status.PolicyName != nbPolicy.Name { + nbResource.Status.PolicyName = &nbPolicy.Name + } + + if !util.Contains(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String()) { + nbPolicy.Status.ManagedServiceList = append(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String()) + nbPolicy.Status.LastUpdatedAt = &v1.Time{Time: time.Now()} + updatePolicyStatus = true + } + + if !util.Equivalent(nbResource.Spec.TCPPorts, nbResource.Status.TCPPorts) { + nbResource.Status.TCPPorts = nbResource.Spec.TCPPorts + nbPolicy.Status.LastUpdatedAt = &v1.Time{Time: time.Now()} + updatePolicyStatus = true + } + + if !util.Equivalent(nbResource.Spec.UDPPorts, nbResource.Status.UDPPorts) { + nbResource.Status.UDPPorts = nbResource.Spec.UDPPorts + nbPolicy.Status.LastUpdatedAt = &v1.Time{Time: time.Now()} + updatePolicyStatus = true + } + + if !util.Equivalent(nbResource.Status.Groups, groupIDs) { + nbResource.Status.Groups = groupIDs + nbPolicy.Status.LastUpdatedAt = &v1.Time{Time: time.Now()} + updatePolicyStatus = true + } + } + + if updatePolicyStatus { + err := r.Client.Status().Update(ctx, &nbPolicy) + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error updating NBPolicy", "err", err, "namespace", req.Namespace, "name", req.Name, "policyName", nbResource.Spec.PolicyName) + return err + } + } + + return nil +} + +func (r *NBResourceReconciler) handleGroupUpdate(ctx context.Context, req ctrl.Request, nbResource *netbirdiov1.NBResource, groupIDs []string, resource *api.NetworkResource) error { + // Handle possible updated group IDs + groupIDMap := make(map[string]interface{}) + for _, g := range groupIDs { + groupIDMap[g] = nil + } + + diffFound := len(groupIDs) != len(resource.Groups) + for _, g := range resource.Groups { + if _, ok := groupIDMap[g.Id]; !ok { + diffFound = true + } + } + + if diffFound { + _, err := r.netbird.Networks.Resources(nbResource.Spec.NetworkID).Update(ctx, resource.Id, api.NetworkResourceRequest{ + Name: nbResource.Spec.Name, + Description: &networkDescription, + Address: nbResource.Spec.Address, + Enabled: true, + Groups: groupIDs, + }) + + if err != nil { + ctrl.Log.Error(errNetBirdAPI, "error updating resource", "err", err, "namespace", req.Namespace, "name", req.Name) + return err + } + } + + return nil +} + +func (r *NBResourceReconciler) handleNetBirdResource(ctx context.Context, req ctrl.Request, nbResource *netbirdiov1.NBResource, groupIDs []string) (*api.NetworkResource, error) { + var resource *api.NetworkResource + var err error + if nbResource.Status.NetworkResourceID != nil { + resource, err = r.netbird.Networks.Resources(nbResource.Spec.NetworkID).Get(ctx, *nbResource.Status.NetworkResourceID) + if err != nil && !strings.Contains(err.Error(), "not found") { + ctrl.Log.Error(errNetBirdAPI, "error getting network resource", "err", err, "namespace", req.Namespace, "name", req.Name) + return nil, err + } + } + if nbResource.Status.NetworkResourceID == nil && resource == nil { + resource, err := r.netbird.Networks.Resources(nbResource.Spec.NetworkID).Create(ctx, api.NetworkResourceRequest{ + Address: nbResource.Spec.Address, + Enabled: true, + Groups: groupIDs, + Description: &networkDescription, + Name: nbResource.Spec.Name, + }) + + if err != nil { + ctrl.Log.Error(errNetBirdAPI, "error creating resource", "err", err, "namespace", req.Namespace, "name", req.Name) + return nil, err + } + + nbResource.Status.NetworkResourceID = &resource.Id + } else if nbResource.Status.NetworkResourceID == nil && resource != nil { + nbResource.Status.NetworkResourceID = &resource.Id + } else if resource == nil { + // Status remembers networkResourceID but resource was deleted elsewhere + // remove networkID from status and re-enqueue + nbResource.Status.NetworkResourceID = nil + } else { + resourceGroups := make([]string, 0, len(resource.Groups)) + for _, v := range resource.Groups { + resourceGroups = append(resourceGroups, v.Id) + } + if resource.Address != nbResource.Spec.Address || + !resource.Enabled || + !util.Equivalent(resourceGroups, groupIDs) || + *resource.Description != networkDescription || + resource.Name != nbResource.Spec.Name { + _, err = r.netbird.Networks.Resources(nbResource.Spec.NetworkID).Update(ctx, *nbResource.Status.NetworkResourceID, api.NetworkResourceRequest{ + Address: nbResource.Spec.Address, + Enabled: true, + Groups: groupIDs, + Description: &networkDescription, + Name: nbResource.Spec.Name, + }) + if err != nil { + return resource, err + } + } + } + + return resource, nil +} + +func (r *NBResourceReconciler) handleGroups(ctx context.Context, req ctrl.Request, nbResource *netbirdiov1.NBResource) ([]string, *ctrl.Result, error) { + var groupIDs []string + + for _, groupName := range nbResource.Spec.Groups { + nbGroup := netbirdiov1.NBGroup{} + groupNameRFC := strings.ToLower(groupName) + groupNameRFC = strings.ReplaceAll(groupNameRFC, " ", "-") + err := r.Client.Get(ctx, types.NamespacedName{Namespace: req.Namespace, Name: groupNameRFC}, &nbGroup) + if err != nil && !errors.IsNotFound(err) { + ctrl.Log.Error(errKubernetesAPI, "error getting NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + return nil, &ctrl.Result{}, err + } else if errors.IsNotFound(err) { + nbGroup = netbirdiov1.NBGroup{ + ObjectMeta: v1.ObjectMeta{ + Name: groupNameRFC, + Namespace: nbResource.Namespace, + OwnerReferences: []v1.OwnerReference{ + { + APIVersion: nbResource.APIVersion, + Kind: nbResource.Kind, + Name: nbResource.Name, + UID: nbResource.UID, + BlockOwnerDeletion: util.Ptr(true), + }, + }, + Finalizers: []string{"netbird.io/group-cleanup", "netbird.io/resource-cleanup"}, + }, + Spec: netbirdiov1.NBGroupSpec{ + Name: groupName, + }, + } + + err = r.Client.Create(ctx, &nbGroup) + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error creating NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + return nil, &ctrl.Result{}, err + } + + continue + } else { + ownerExists := false + for _, o := range nbGroup.OwnerReferences { + if o.UID == nbResource.UID { + ownerExists = true + } + } + + if !ownerExists { + nbGroup.OwnerReferences = append(nbGroup.OwnerReferences, v1.OwnerReference{ + APIVersion: nbResource.APIVersion, + Kind: nbResource.Kind, + Name: nbResource.Name, + UID: nbResource.UID, + BlockOwnerDeletion: util.Ptr(true), + }) + + err = r.Client.Update(ctx, &nbGroup) + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error updating NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + return nil, &ctrl.Result{}, err + } + } + } + + if nbGroup.Status.GroupID != nil { + groupIDs = append(groupIDs, *nbGroup.Status.GroupID) + } + } + + if len(groupIDs) != len(nbResource.Spec.Groups) { + return nil, &ctrl.Result{RequeueAfter: 5 * time.Second}, nil + } + + return groupIDs, nil, nil +} + +func (r *NBResourceReconciler) handleDelete(ctx context.Context, req ctrl.Request, nbResource *netbirdiov1.NBResource) error { + if nbResource.Status.PolicyName != nil { + var nbPolicy netbirdiov1.NBPolicy + err := r.Client.Get(ctx, types.NamespacedName{Name: *nbResource.Status.PolicyName}, &nbPolicy) + if err != nil && !errors.IsNotFound(err) { + ctrl.Log.Error(errKubernetesAPI, "error getting NBPolicy", "err", err, "namespace", req.Namespace, "name", req.Name, "policyName", nbResource.Spec.PolicyName) + return err + } + + if !errors.IsNotFound(err) && util.Contains(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String()) { + nbPolicy.Status.ManagedServiceList = util.Without(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String()) + nbPolicy.Status.LastUpdatedAt = &v1.Time{Time: time.Now()} + err = r.Client.Status().Update(ctx, &nbPolicy) + if err != nil { + return err + } + } + } + + if nbResource.Status.NetworkResourceID != nil { + err := r.netbird.Networks.Resources(nbResource.Spec.NetworkID).Delete(ctx, *nbResource.Status.NetworkResourceID) + if err != nil && !strings.Contains(err.Error(), "not found") { + ctrl.Log.Error(errNetBirdAPI, "error deleting resource", "err", err, "namespace", req.Namespace, "name", req.Name) + return err + } + + nbResource.Status.NetworkResourceID = nil + } + + nbGroupList := netbirdiov1.NBGroupList{} + err := r.Client.List(ctx, &nbGroupList, &client.ListOptions{Namespace: req.Namespace}) + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error listing NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + return err + } + + for _, g := range nbGroupList.Items { + if len(g.OwnerReferences) > 0 && g.OwnerReferences[0].UID == nbResource.UID { + g.Finalizers = util.Without(g.Finalizers, "netbird.io/resource-cleanup") + err = r.Client.Update(ctx, &g) + if err != nil && !errors.IsNotFound(err) { + ctrl.Log.Error(errKubernetesAPI, "error updating NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + return err + } + } + } + + nbResource.Finalizers = nil + err = r.Client.Update(ctx, nbResource) + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error updating NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + return err + } + + return nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *NBResourceReconciler) SetupWithManager(mgr ctrl.Manager) error { + r.netbird = netbird.New(r.ManagementURL, r.APIKey) + + return ctrl.NewControllerManagedBy(mgr). + For(&netbirdiov1.NBResource{}). + Named("nbresource"). + Watches(&netbirdiov1.NBGroup{}, handler.EnqueueRequestForOwner(r.Scheme, mgr.GetRESTMapper(), &netbirdiov1.NBResource{})). + Complete(r) +} diff --git a/internal/controller/nbresource_controller_test.go b/internal/controller/nbresource_controller_test.go new file mode 100644 index 0000000..4ddcd4d --- /dev/null +++ b/internal/controller/nbresource_controller_test.go @@ -0,0 +1,69 @@ +package controller + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" +) + +var _ = Describe("NBResource Controller", func() { + Context("When reconciling a resource", func() { + const resourceName = "test-resource" + + ctx := context.Background() + + typeNamespacedName := types.NamespacedName{ + Name: resourceName, + Namespace: "default", // TODO(user):Modify as needed + } + nbresource := &netbirdiov1.NBResource{} + + BeforeEach(func() { + Skip("Not implemented yet") + By("creating the custom resource for the Kind NBResource") + err := k8sClient.Get(ctx, typeNamespacedName, nbresource) + if err != nil && errors.IsNotFound(err) { + resource := &netbirdiov1.NBResource{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourceName, + Namespace: "default", + }, + // TODO(user): Specify other spec details if needed. + } + Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + } + }) + + AfterEach(func() { + // TODO(user): Cleanup logic after each test, like removing the resource instance. + resource := &netbirdiov1.NBResource{} + err := k8sClient.Get(ctx, typeNamespacedName, resource) + Expect(err).NotTo(HaveOccurred()) + + By("Cleanup the specific resource instance NBResource") + Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) + }) + It("should successfully reconcile the resource", func() { + By("Reconciling the created resource") + controllerReconciler := &NBResourceReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + } + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. + // Example: If you expect a certain status condition after reconciliation, verify it here. + }) + }) +}) diff --git a/internal/controller/nbroutingpeer_controller.go b/internal/controller/nbroutingpeer_controller.go new file mode 100644 index 0000000..e59183a --- /dev/null +++ b/internal/controller/nbroutingpeer_controller.go @@ -0,0 +1,637 @@ +package controller + +import ( + "context" + "fmt" + "strings" + "time" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/log" + + netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" + "github.com/netbirdio/kubernetes-operator/internal/util" + netbird "github.com/netbirdio/netbird/management/client/rest" + "github.com/netbirdio/netbird/management/server/http/api" +) + +// NBRoutingPeerReconciler reconciles a NBRoutingPeer object +type NBRoutingPeerReconciler struct { + client.Client + Scheme *runtime.Scheme + ClientImage string + ClusterName string + APIKey string + ManagementURL string + NamespacedNetworks bool + netbird *netbird.Client +} + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +func (r *NBRoutingPeerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, err error) { + _ = log.FromContext(ctx) + + ctrl.Log.Info("NBRoutingPeer: Reconciling", "namespace", req.Namespace, "name", req.Name) + + nbrp := &netbirdiov1.NBRoutingPeer{} + err = r.Get(ctx, req.NamespacedName, nbrp) + if err != nil { + if !errors.IsNotFound(err) { + ctrl.Log.Error(errKubernetesAPI, "error getting NBRoutingPeer", "err", err, "namespace", req.Namespace, "name", req.Name) + } + return ctrl.Result{RequeueAfter: defaultRequeueAfter}, nil + } + + originalNBRP := nbrp.DeepCopy() + defer func() { + if originalNBRP.Status.NetworkID != nbrp.Status.NetworkID || + originalNBRP.Status.RouterID != nbrp.Status.RouterID || + originalNBRP.Status.SetupKeyID != nbrp.Status.SetupKeyID || + !util.Equivalent(originalNBRP.Status.Conditions, nbrp.Status.Conditions) { + err = r.Client.Status().Update(ctx, nbrp) + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error updating NBRoutingPeer Status", "err", err, "namespace", req.Namespace, "name", req.Name) + } + } + if !res.Requeue && res.RequeueAfter == 0 { + res.RequeueAfter = defaultRequeueAfter + } + }() + + if nbrp.DeletionTimestamp != nil { + if len(nbrp.Finalizers) == 0 { + return ctrl.Result{}, nil + } + return r.handleDelete(ctx, req, nbrp) + } + + ctrl.Log.Info("NBRoutingPeer: Checking network", "namespace", req.Namespace, "name", req.Name) + err = r.handleNetwork(ctx, req, nbrp) + if err != nil { + return ctrl.Result{}, err + } + + ctrl.Log.Info("NBRoutingPeer: Checking groups", "namespace", req.Namespace, "name", req.Name) + nbGroup, result, err := r.handleGroup(ctx, req, nbrp) + if nbGroup == nil { + return *result, err + } + + ctrl.Log.Info("NBRoutingPeer: Checking setup keys", "namespace", req.Namespace, "name", req.Name) + result, err = r.handleSetupKey(ctx, req, nbrp, *nbGroup) + if result != nil { + return *result, err + } + + ctrl.Log.Info("NBRoutingPeer: Checking network router", "namespace", req.Namespace, "name", req.Name) + err = r.handleRouter(ctx, req, nbrp, *nbGroup) + if err != nil { + return ctrl.Result{}, err + } + + ctrl.Log.Info("NBRoutingPeer: Checking deployment", "namespace", req.Namespace, "name", req.Name) + err = r.handleDeployment(ctx, req, nbrp) + if err != nil { + return ctrl.Result{}, err + } + + nbrp.Status.Conditions = netbirdiov1.NBConditionTrue() + return ctrl.Result{}, nil +} + +func (r *NBRoutingPeerReconciler) handleDeployment(ctx context.Context, req ctrl.Request, nbrp *netbirdiov1.NBRoutingPeer) error { + routingPeerDeployment := appsv1.Deployment{} + err := r.Client.Get(ctx, req.NamespacedName, &routingPeerDeployment) + if err != nil && !errors.IsNotFound(err) { + ctrl.Log.Error(errKubernetesAPI, "error getting Deployment", "err", err, "namespace", req.Namespace, "name", req.Name) + nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("error getting Deployment: %v", err)) + return err + } + + if errors.IsNotFound(err) { + var replicas int32 = 3 + if nbrp.Spec.Replicas != nil { + replicas = *nbrp.Spec.Replicas + } + routingPeerDeployment = appsv1.Deployment{ + ObjectMeta: v1.ObjectMeta{ + Name: nbrp.Name, + Namespace: nbrp.Namespace, + OwnerReferences: []v1.OwnerReference{ + { + APIVersion: nbrp.APIVersion, + Kind: nbrp.Kind, + Name: nbrp.Name, + UID: nbrp.UID, + BlockOwnerDeletion: util.Ptr(true), + }, + }, + Labels: nbrp.Spec.Labels, + Annotations: nbrp.Spec.Annotations, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: &replicas, + Selector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "app.kubernetes.io/name": "netbird-router", + }, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: v1.ObjectMeta{ + Labels: map[string]string{ + "app.kubernetes.io/name": "netbird-router", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "netbird", + Image: r.ClientImage, + Env: []corev1.EnvVar{ + { + Name: "NB_SETUP_KEY", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: nbrp.Name, + }, + Key: "setupKey", + }, + }, + }, + { + Name: "NB_MANAGEMENT_URL", + Value: r.ManagementURL, + }, + }, + SecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{ + "NET_ADMIN", + }, + }, + }, + }, + }, + }, + }, + }, + } + + err = r.Client.Create(ctx, &routingPeerDeployment) + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error creating Deployment", "err", err, "namespace", req.Namespace, "name", req.Name) + nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("error creating Deployment: %v", err)) + return err + } + } else { + updatedDeployment := routingPeerDeployment.DeepCopy() + updatedDeployment.ObjectMeta.Name = nbrp.Name + updatedDeployment.ObjectMeta.Namespace = nbrp.Namespace + updatedDeployment.ObjectMeta.OwnerReferences = []v1.OwnerReference{ + { + APIVersion: nbrp.APIVersion, + Kind: nbrp.Kind, + Name: nbrp.Name, + UID: nbrp.UID, + BlockOwnerDeletion: util.Ptr(true), + }, + } + updatedDeployment.ObjectMeta.Labels = nbrp.Spec.Labels + for k, v := range nbrp.Spec.Annotations { + updatedDeployment.ObjectMeta.Annotations[k] = nbrp.Spec.Annotations[v] + } + var replicas int32 = 3 + if nbrp.Spec.Replicas != nil { + replicas = *nbrp.Spec.Replicas + } + updatedDeployment.Spec.Replicas = &replicas + updatedDeployment.Spec.Selector = &v1.LabelSelector{ + MatchLabels: map[string]string{ + "app.kubernetes.io/name": "netbird-router", + }, + } + updatedDeployment.Spec.Template.ObjectMeta.Labels = map[string]string{ + "app.kubernetes.io/name": "netbird-router", + } + if len(updatedDeployment.Spec.Template.Spec.Containers) != 1 { + updatedDeployment.Spec.Template.Spec.Containers = []corev1.Container{} + } + updatedDeployment.Spec.Template.Spec.Containers[0].Name = "netbird" + updatedDeployment.Spec.Template.Spec.Containers[0].Image = r.ClientImage + updatedDeployment.Spec.Template.Spec.Containers[0].Env = []corev1.EnvVar{ + { + Name: "NB_SETUP_KEY", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: nbrp.Name, + }, + Key: "setupKey", + }, + }, + }, + { + Name: "NB_MANAGEMENT_URL", + Value: r.ManagementURL, + }, + } + updatedDeployment.Spec.Template.Spec.Containers[0].SecurityContext = &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{ + "NET_ADMIN", + }, + }, + } + + patch := client.StrategicMergeFrom(&routingPeerDeployment) + bs, _ := patch.Data(updatedDeployment) + // Minimum patch size is 2 for "{}" + if len(bs) <= 2 { + return nil + } + err = r.Client.Patch(ctx, updatedDeployment, patch) + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error updating Deployment", "err", err, "namespace", req.Namespace, "name", req.Name) + nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("error updating Deployment: %v", err)) + return err + } + } + + return nil +} + +func (r *NBRoutingPeerReconciler) handleRouter(ctx context.Context, req ctrl.Request, nbrp *netbirdiov1.NBRoutingPeer, nbGroup netbirdiov1.NBGroup) error { + // Refresh nbrp + err := r.Client.Get(ctx, req.NamespacedName, nbrp) + if err != nil { + return err + } + + // Check NetworkRouter exists + routers, err := r.netbird.Networks.Routers(*nbrp.Status.NetworkID).List(ctx) + + if err != nil { + ctrl.Log.Error(errNetBirdAPI, "error listing network routers", "err", err, "namespace", req.Namespace, "name", req.Name) + nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("error listing network routers: %v", err)) + return err + } + + if nbrp.Status.RouterID == nil || len(routers) == 0 { + if len(routers) > 0 { + nbrp.Status.RouterID = &routers[0].Id + } else { + router, err := r.netbird.Networks.Routers(*nbrp.Status.NetworkID).Create(ctx, api.NetworkRouterRequest{ + Enabled: true, + Masquerade: true, + Metric: 9999, + PeerGroups: &[]string{*nbGroup.Status.GroupID}, + }) + + if err != nil { + ctrl.Log.Error(errNetBirdAPI, "error creating network router", "err", err, "namespace", req.Namespace, "name", req.Name) + nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("error creating network router: %v", err)) + return err + } + + nbrp.Status.RouterID = &router.Id + } + } else { + if !routers[0].Enabled || !routers[0].Masquerade || routers[0].Metric != 9999 || len(*routers[0].PeerGroups) != 1 || (*routers[0].PeerGroups)[0] != *nbGroup.Status.GroupID { + _, err = r.netbird.Networks.Routers(*nbrp.Status.NetworkID).Update(ctx, routers[0].Id, api.NetworkRouterRequest{ + Enabled: true, + Masquerade: true, + Metric: 9999, + PeerGroups: &[]string{*nbGroup.Status.GroupID}, + }) + + if err != nil { + ctrl.Log.Error(errNetBirdAPI, "error updating network router", "err", err, "namespace", req.Namespace, "name", req.Name) + nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("error updating network router: %v", err)) + return err + } + } + } + + return nil +} + +func (r *NBRoutingPeerReconciler) handleSetupKey(ctx context.Context, req ctrl.Request, nbrp *netbirdiov1.NBRoutingPeer, nbGroup netbirdiov1.NBGroup) (*ctrl.Result, error) { + networkName := r.ClusterName + if r.NamespacedNetworks { + networkName += "-" + req.Namespace + } + + // Check if setup key exists + if nbrp.Status.SetupKeyID == nil { + // Create new setup key with group Status.GroupID + setupKey, err := r.netbird.SetupKeys.Create(ctx, api.CreateSetupKeyRequest{ + AutoGroups: []string{*nbGroup.Status.GroupID}, + Ephemeral: util.Ptr(true), + Name: networkName, + Type: "reusable", + }) + + if err != nil { + ctrl.Log.Error(errNetBirdAPI, "error creating setup key", "err", err, "namespace", req.Namespace, "name", req.Name) + nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("error creating setup key: %v", err)) + return &ctrl.Result{}, err + } + + nbrp.Status.SetupKeyID = &setupKey.Id + + skSecret := corev1.Secret{ + ObjectMeta: v1.ObjectMeta{ + Name: nbrp.Name, + Namespace: nbrp.Namespace, + OwnerReferences: []v1.OwnerReference{ + { + APIVersion: nbrp.APIVersion, + Kind: nbrp.Kind, + Name: nbrp.Name, + UID: nbrp.UID, + BlockOwnerDeletion: util.Ptr(true), + }, + }, + }, + StringData: map[string]string{ + "setupKey": setupKey.Key, + }, + } + err = r.Client.Create(ctx, &skSecret) + if errors.IsAlreadyExists(err) { + err = r.Client.Update(ctx, &skSecret) + } + + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error creating Secret", "err", err, "namespace", req.Namespace, "name", req.Name) + nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("error creating secret: %v", err)) + return &ctrl.Result{}, err + } + } else { + // Check SetupKey is not revoked + setupKey, err := r.netbird.SetupKeys.Get(ctx, *nbrp.Status.SetupKeyID) + if err != nil && !strings.Contains(err.Error(), "not found") { + ctrl.Log.Error(errNetBirdAPI, "error getting setup key", "err", err, "namespace", req.Namespace, "name", req.Name) + nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("error getting setup key: %v", err)) + return &ctrl.Result{}, err + } + + if (err != nil && strings.Contains(err.Error(), "not found")) || setupKey.Revoked { + nbrp.Status.SetupKeyID = nil + + return &ctrl.Result{Requeue: true}, nil + } + + // Check if valid setup key exists + skSecret := corev1.Secret{} + err = r.Client.Get(ctx, req.NamespacedName, &skSecret) + if err != nil && !errors.IsNotFound(err) { + ctrl.Log.Error(errKubernetesAPI, "error getting Secret", "err", err, "namespace", req.Namespace, "name", req.Name) + nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("error getting secret: %v", err)) + return &ctrl.Result{}, err + } + + if _, ok := skSecret.Data["setupKey"]; errors.IsNotFound(err) || !ok { + // Someone deleted setup key secret + // Revoke SK from NetBird and re-generate + err = r.netbird.SetupKeys.Delete(ctx, *nbrp.Status.SetupKeyID) + + if err != nil { + ctrl.Log.Error(errNetBirdAPI, "error deleting setup key", "err", err, "namespace", req.Namespace, "name", req.Name) + nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("error deleting setup key: %v", err)) + return &ctrl.Result{}, err + } + + nbrp.Status.SetupKeyID = nil + + nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("Gone", "generated secret was deleted") + // Requeue to avoid repeating code + return &ctrl.Result{Requeue: true}, nil + } + } + + return nil, nil +} + +func (r *NBRoutingPeerReconciler) handleGroup(ctx context.Context, req ctrl.Request, nbrp *netbirdiov1.NBRoutingPeer) (*netbirdiov1.NBGroup, *ctrl.Result, error) { + networkName := r.ClusterName + if r.NamespacedNetworks { + networkName += "-" + req.Namespace + } + + // Check if NetBird Group exists + nbGroup := netbirdiov1.NBGroup{} + err := r.Client.Get(ctx, req.NamespacedName, &nbGroup) + if err != nil && !errors.IsNotFound(err) { + ctrl.Log.Error(errKubernetesAPI, "error getting NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("error getting NBGroup: %v", err)) + return nil, &ctrl.Result{}, err + } + + if errors.IsNotFound(err) { + nbGroup = netbirdiov1.NBGroup{ + ObjectMeta: v1.ObjectMeta{ + Name: nbrp.Name, + Namespace: nbrp.Namespace, + OwnerReferences: []v1.OwnerReference{ + { + APIVersion: nbrp.APIVersion, + Kind: nbrp.Kind, + Name: nbrp.Name, + UID: nbrp.UID, + BlockOwnerDeletion: util.Ptr(true), + }, + }, + Finalizers: []string{"netbird.io/group-cleanup", "netbird.io/routing-peer-cleanup"}, + }, + Spec: netbirdiov1.NBGroupSpec{ + Name: networkName, + }, + } + + err = r.Client.Create(ctx, &nbGroup) + + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error creating NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("error creating NBGroup: %v", err)) + return nil, &ctrl.Result{}, err + } + + // Requeue after 5 seconds to ensure group creation is successful by NBGroup controller. + return nil, &ctrl.Result{RequeueAfter: 5 * time.Second}, nil + } + + if nbGroup.Status.GroupID == nil { + // Group is not yet created successfully, requeue + return nil, &ctrl.Result{RequeueAfter: 10 * time.Second}, nil + } + + return &nbGroup, nil, nil +} + +func (r *NBRoutingPeerReconciler) handleNetwork(ctx context.Context, req ctrl.Request, nbrp *netbirdiov1.NBRoutingPeer) error { + networkName := r.ClusterName + if r.NamespacedNetworks { + networkName += "-" + req.Namespace + } + + if nbrp.Status.NetworkID == nil { + // Check if network exists + networks, err := r.netbird.Networks.List(ctx) + if err != nil { + ctrl.Log.Error(errNetBirdAPI, "error listing networks", "err", err, "namespace", req.Namespace, "name", req.Name) + nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("error listing networks: %v", err)) + return err + } + var network *api.Network + for _, n := range networks { + if n.Name == networkName { + ctrl.Log.Info("network already exists", "network-id", n.Id) + network = &n + } + } + + if network != nil { + nbrp.Status.NetworkID = &network.Id + } else { + ctrl.Log.Info("creating network", "name", networkName) + network, err := r.netbird.Networks.Create(ctx, api.NetworkRequest{ + Name: networkName, + Description: &networkDescription, + }) + if err != nil { + ctrl.Log.Error(errNetBirdAPI, "error creating network", "err", err, "namespace", req.Namespace, "name", req.Name) + nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("error creating network: %v", err)) + return err + } + + nbrp.Status.NetworkID = &network.Id + } + } + return nil +} + +func (r *NBRoutingPeerReconciler) handleDelete(ctx context.Context, req ctrl.Request, nbrp *netbirdiov1.NBRoutingPeer) (ctrl.Result, error) { + nbDeployment := appsv1.Deployment{} + err := r.Client.Get(ctx, req.NamespacedName, &nbDeployment) + if err != nil && !errors.IsNotFound(err) { + ctrl.Log.Error(errKubernetesAPI, "error getting Deployment", "err", err, "namespace", req.Namespace, "name", req.Name) + return ctrl.Result{}, err + } + if err == nil { + err = r.Client.Delete(ctx, &nbDeployment) + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error deleting Deployment", "err", err, "namespace", req.Namespace, "name", req.Name) + return ctrl.Result{}, err + } + } + + if nbrp.Status.SetupKeyID != nil { + ctrl.Log.Info("Deleting setup key", "id", *nbrp.Status.SetupKeyID) + err = r.netbird.SetupKeys.Delete(ctx, *nbrp.Status.SetupKeyID) + if err != nil && !strings.Contains(err.Error(), "not found") { + ctrl.Log.Error(errNetBirdAPI, "error deleting setupKey", "err", err, "namespace", req.Namespace, "name", req.Name) + return ctrl.Result{}, err + } + + setupKeyID := *nbrp.Status.SetupKeyID + nbrp.Status.SetupKeyID = nil + ctrl.Log.Info("Setup key deleted", "id", setupKeyID) + } + + if nbrp.Status.RouterID != nil { + err = r.netbird.Networks.Routers(*nbrp.Status.NetworkID).Delete(ctx, *nbrp.Status.RouterID) + if err != nil && !strings.Contains(err.Error(), "not found") { + ctrl.Log.Error(errNetBirdAPI, "error deleting Network Router", "err", err, "namespace", req.Namespace, "name", req.Name) + return ctrl.Result{}, err + } + + nbrp.Status.RouterID = nil + } + + nbGroup := netbirdiov1.NBGroup{} + err = r.Client.Get(ctx, req.NamespacedName, &nbGroup) + if err != nil && !errors.IsNotFound(err) { + ctrl.Log.Error(errKubernetesAPI, "error getting NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + return ctrl.Result{}, err + } + + if nbrp.Status.NetworkID != nil { + nbResourceList := netbirdiov1.NBResourceList{} + err = r.Client.List(ctx, &nbResourceList) + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error listing NBResource", "err", err, "namespace", req.Namespace, "name", req.Name) + return ctrl.Result{}, err + } + + for _, nbrs := range nbResourceList.Items { + if nbrs.Spec.NetworkID == *nbrp.Status.NetworkID { + ctrl.Log.Info("Deleting NBResource", "namespace", nbrs.Namespace, "name", nbrs.Name) + err = r.Client.Delete(ctx, &nbrs) + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error deleting NBResource", "err", err, "namespace", req.Namespace, "name", req.Name) + return ctrl.Result{}, err + } + } + } + + if len(nbResourceList.Items) == 0 { + ctrl.Log.Info("Deleting NetBird Network", "id", *nbrp.Status.NetworkID) + err = r.netbird.Networks.Delete(ctx, *nbrp.Status.NetworkID) + if err != nil && !strings.Contains(err.Error(), "not found") { + ctrl.Log.Error(errNetBirdAPI, "error deleting Network", "err", err, "namespace", req.Namespace, "name", req.Name) + return ctrl.Result{}, err + } + + nbrp.Status.NetworkID = nil + } + } + + if nbGroup.Spec.Name != "" && util.Contains(nbGroup.Finalizers, "netbird.io/routing-peer-cleanup") { + nbGroup.Finalizers = util.Without(nbGroup.Finalizers, "netbird.io/routing-peer-cleanup") + ctrl.Log.Info("Removing netbird.io/routing-peer-cleanup finalizer NBGroup", "namespace", nbGroup.Namespace, "name", nbGroup.Name) + err = r.Client.Update(ctx, &nbGroup) + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error deleting NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + return ctrl.Result{}, err + } + } + + if nbrp.Status.NetworkID != nil { + return ctrl.Result{RequeueAfter: 5 * time.Second}, nil + } + + if len(nbrp.Finalizers) > 0 { + ctrl.Log.Info("Removing finalizers", "namespace", nbrp.Namespace, "name", nbrp.Name) + nbrp.Finalizers = nil + err = r.Client.Update(ctx, nbrp) + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error updating NBRoutingPeer finalizers", "err", err, "namespace", req.Namespace, "name", req.Name) + return ctrl.Result{}, err + } + } + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *NBRoutingPeerReconciler) SetupWithManager(mgr ctrl.Manager) error { + r.netbird = netbird.New(r.ManagementURL, r.APIKey) + + return ctrl.NewControllerManagedBy(mgr). + For(&netbirdiov1.NBRoutingPeer{}). + Named("nbroutingpeer"). + Watches(&appsv1.Deployment{}, handler.EnqueueRequestForOwner(r.Scheme, mgr.GetRESTMapper(), &netbirdiov1.NBRoutingPeer{})). + Watches(&corev1.Secret{}, handler.EnqueueRequestForOwner(r.Scheme, mgr.GetRESTMapper(), &netbirdiov1.NBRoutingPeer{})). + Watches(&netbirdiov1.NBGroup{}, handler.EnqueueRequestForOwner(r.Scheme, mgr.GetRESTMapper(), &netbirdiov1.NBRoutingPeer{})). + Complete(r) +} diff --git a/internal/controller/nbroutingpeer_controller_test.go b/internal/controller/nbroutingpeer_controller_test.go new file mode 100644 index 0000000..0b6b993 --- /dev/null +++ b/internal/controller/nbroutingpeer_controller_test.go @@ -0,0 +1,70 @@ +package controller + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" +) + +var _ = Describe("NBRoutingPeer Controller", func() { + Context("When reconciling a resource", func() { + const resourceName = "test-resource" + + ctx := context.Background() + + typeNamespacedName := types.NamespacedName{ + Name: resourceName, + Namespace: "default", // TODO(user):Modify as needed + } + nbroutingpeer := &netbirdiov1.NBRoutingPeer{} + + BeforeEach(func() { + Skip("Not implemented yet") + By("creating the custom resource for the Kind NBRoutingPeer") + err := k8sClient.Get(ctx, typeNamespacedName, nbroutingpeer) + if err != nil && errors.IsNotFound(err) { + resource := &netbirdiov1.NBRoutingPeer{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourceName, + Namespace: "default", + }, + // TODO(user): Specify other spec details if needed. + } + Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + } + }) + + AfterEach(func() { + // TODO(user): Cleanup logic after each test, like removing the resource instance. + resource := &netbirdiov1.NBRoutingPeer{} + err := k8sClient.Get(ctx, typeNamespacedName, resource) + Expect(err).NotTo(HaveOccurred()) + + By("Cleanup the specific resource instance NBRoutingPeer") + Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) + }) + It("should successfully reconcile the resource", func() { + Skip("Not implemented yet") + By("Reconciling the created resource") + controllerReconciler := &NBRoutingPeerReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + } + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. + // Example: If you expect a certain status condition after reconciliation, verify it here. + }) + }) +}) diff --git a/internal/controller/nbsetupkey_controller.go b/internal/controller/nbsetupkey_controller.go index 6669d8d..f1b790b 100644 --- a/internal/controller/nbsetupkey_controller.go +++ b/internal/controller/nbsetupkey_controller.go @@ -57,9 +57,9 @@ func (r *NBSetupKeyReconciler) Reconcile(ctx context.Context, req ctrl.Request) if nbSetupKey.Spec.SecretKeyRef.Name == "" || nbSetupKey.Spec.SecretKeyRef.Key == "" { ctrl.Log.Error(fmt.Errorf("invalid NBSetupKey"), "secretKeyRef must contain both secret name and secret key", "namespace", req.Namespace, "name", req.Name) return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{ - Conditions: []netbirdiov1.NBSetupKeyCondition{ + Conditions: []netbirdiov1.NBCondition{ { - Type: netbirdiov1.Ready, + Type: netbirdiov1.NBSetupKeyReady, Status: corev1.ConditionFalse, LastProbeTime: v1.Now(), Reason: "InvalidConfig", @@ -86,8 +86,8 @@ func (r *NBSetupKeyReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, err } ctrl.Log.Error(fmt.Errorf("invalid NBSetupKey"), "secret referenced not found", "err", err, "namespace", req.Namespace, "name", req.Name) - return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{Conditions: []netbirdiov1.NBSetupKeyCondition{{ - Type: netbirdiov1.Ready, + return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{Conditions: []netbirdiov1.NBCondition{{ + Type: netbirdiov1.NBSetupKeyReady, Status: corev1.ConditionFalse, LastProbeTime: v1.Now(), Reason: "SecretNotExists", @@ -98,8 +98,8 @@ func (r *NBSetupKeyReconciler) Reconcile(ctx context.Context, req ctrl.Request) uuidBytes, ok := secret.Data[nbSetupKey.Spec.SecretKeyRef.Key] if !ok { ctrl.Log.Error(fmt.Errorf("invalid NBSetupKey"), "secret key referenced not found", "namespace", req.Namespace, "name", req.Name) - return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{Conditions: []netbirdiov1.NBSetupKeyCondition{{ - Type: netbirdiov1.Ready, + return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{Conditions: []netbirdiov1.NBCondition{{ + Type: netbirdiov1.NBSetupKeyReady, Status: corev1.ConditionFalse, LastProbeTime: v1.Now(), Reason: "SecretKeyNotExists", @@ -110,16 +110,16 @@ func (r *NBSetupKeyReconciler) Reconcile(ctx context.Context, req ctrl.Request) _, err = uuid.Parse(string(uuidBytes)) if err != nil { ctrl.Log.Error(fmt.Errorf("invalid NBSetupKey"), "setupKey is not a valid UUID", "err", err, "namespace", req.Namespace, "name", req.Name) - return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{Conditions: []netbirdiov1.NBSetupKeyCondition{{ - Type: netbirdiov1.Ready, + return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{Conditions: []netbirdiov1.NBCondition{{ + Type: netbirdiov1.NBSetupKeyReady, Status: corev1.ConditionFalse, LastProbeTime: v1.Now(), Reason: "InvalidSetupKey", Message: "Referenced secret is not a valid SetupKey", }}}) } - return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{Conditions: []netbirdiov1.NBSetupKeyCondition{{ - Type: netbirdiov1.Ready, + return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{Conditions: []netbirdiov1.NBCondition{{ + Type: netbirdiov1.NBSetupKeyReady, Status: corev1.ConditionTrue, LastProbeTime: v1.Now(), }}}) @@ -151,6 +151,6 @@ func (r *NBSetupKeyReconciler) SetupWithManager(mgr ctrl.Manager) error { return nil }), - ). // Trigger reconciliation when the labeled Busybox resource changes + ). // Trigger reconciliation when a referenced secret changes Complete(r) } diff --git a/internal/controller/service_controller.go b/internal/controller/service_controller.go new file mode 100644 index 0000000..a7b1e71 --- /dev/null +++ b/internal/controller/service_controller.go @@ -0,0 +1,256 @@ +package controller + +import ( + "context" + "fmt" + "strconv" + "strings" + "time" + + netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" + "github.com/netbirdio/kubernetes-operator/internal/util" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +// ServiceReconciler reconciles a Service object +type ServiceReconciler struct { + client.Client + Scheme *runtime.Scheme + ClusterName string + ClusterDNS string + NamespacedNetworks bool + ControllerNamespace string +} + +const ( + // ServiceExposeAnnotation Service annotation for exposing + ServiceExposeAnnotation = "netbird.io/expose" + serviceGroupsAnnotation = "netbird.io/groups" + serviceResourceAnnotation = "netbird.io/resource-name" + servicePolicyAnnotation = "netbird.io/policy" + servicePortsAnnotation = "netbird.io/policy-ports" + serviceProtocolAnnotation = "netbird.io/policy-protocol" +) + +var ( + networkDescription = "Created by kubernetes-operator" +) + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +func (r *ServiceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + ctrl.Log.Info("Service: Reconciling", "namespace", req.Namespace, "name", req.Name) + + svc := corev1.Service{} + err := r.Get(ctx, req.NamespacedName, &svc) + if err != nil { + if !errors.IsNotFound(err) { + ctrl.Log.Error(errKubernetesAPI, "error getting Service", "err", err, "namespace", req.Namespace, "name", req.Name) + } + return ctrl.Result{}, nil + } + + _, shouldExpose := svc.Annotations[ServiceExposeAnnotation] + + // If Service is being deleted, un-expose + shouldExpose = shouldExpose && svc.DeletionTimestamp == nil + + if shouldExpose { + return r.exposeService(ctx, req, svc) + } + + return r.hideService(ctx, req, svc) +} + +func (r *ServiceReconciler) hideService(ctx context.Context, req ctrl.Request, svc corev1.Service) (ctrl.Result, error) { + var nbResource netbirdiov1.NBResource + err := r.Client.Get(ctx, req.NamespacedName, &nbResource) + if err != nil && !errors.IsNotFound(err) { + ctrl.Log.Error(errKubernetesAPI, "error getting NBResource", "err", err, "namespace", req.Namespace, "name", req.Name) + return ctrl.Result{}, err + } + + if !errors.IsNotFound(err) { + err = r.Client.Delete(ctx, &nbResource) + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error deleting NBResource", "err", err, "namespace", req.Namespace, "name", req.Name) + return ctrl.Result{}, err + } + } + + if util.Contains(svc.Finalizers, "netbird.io/cleanup") { + svc.Finalizers = util.Without(svc.Finalizers, "netbird.io/cleanup") + err := r.Client.Update(ctx, &svc) + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error updating Service", "err", err, "namespace", req.Namespace, "name", req.Name) + return ctrl.Result{}, err + } + } + + return ctrl.Result{}, nil +} + +func (r *ServiceReconciler) exposeService(ctx context.Context, req ctrl.Request, svc corev1.Service) (ctrl.Result, error) { + routerNamespace := r.ControllerNamespace + if r.NamespacedNetworks { + routerNamespace = req.Namespace + } + + if !util.Contains(svc.Finalizers, "netbird.io/cleanup") { + svc.Finalizers = append(svc.Finalizers, "netbird.io/cleanup") + err := r.Client.Update(ctx, &svc) + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error updating Service", "err", err, "namespace", req.Namespace, "name", req.Name) + return ctrl.Result{}, err + } + } + + var routingPeer netbirdiov1.NBRoutingPeer + // Check if NBRoutingPeer exists + err := r.Client.Get(ctx, types.NamespacedName{Namespace: routerNamespace, Name: "router"}, &routingPeer) + if err != nil && !errors.IsNotFound(err) { + ctrl.Log.Error(errKubernetesAPI, "error getting NBRoutingPeer", "err", err, "namespace", req.Namespace, "name", req.Name) + return ctrl.Result{}, err + } + + // Create NBRoutingPeer with default values if not exists + if errors.IsNotFound(err) { + routingPeer = netbirdiov1.NBRoutingPeer{ + ObjectMeta: v1.ObjectMeta{ + Name: "router", + Namespace: routerNamespace, + Finalizers: []string{"netbird.io/cleanup"}, + }, + Spec: netbirdiov1.NBRoutingPeerSpec{}, + } + + err = r.Client.Create(ctx, &routingPeer) + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error creating NBRoutingPeer", "err", err, "namespace", req.Namespace, "name", req.Name) + return ctrl.Result{}, err + } + + ctrl.Log.Info("Network not available") + // Requeue to make sure network is created + return ctrl.Result{RequeueAfter: 5 * time.Second}, nil + } + + if routingPeer.Status.NetworkID == nil { + ctrl.Log.Info("Network not available") + return ctrl.Result{RequeueAfter: 5 * time.Second}, nil + } + + var nbResource netbirdiov1.NBResource + err = r.Client.Get(ctx, req.NamespacedName, &nbResource) + if err != nil && !errors.IsNotFound(err) { + ctrl.Log.Error(errKubernetesAPI, "error getting NBResource", "err", err, "namespace", req.Namespace, "name", req.Name) + return ctrl.Result{}, err + } + + nbrsErr := r.reconcileNBResource(&nbResource, req, svc, routingPeer) + if nbrsErr != nil { + return ctrl.Result{}, nbrsErr + } + + if errors.IsNotFound(err) { + err = r.Client.Create(ctx, &nbResource) + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error creating NBResource", "err", err, "namespace", req.Namespace, "name", req.Name) + return ctrl.Result{}, err + } + } else { + err = r.Client.Update(ctx, &nbResource) + if err != nil { + ctrl.Log.Error(errKubernetesAPI, "error updating NBResource", "err", err, "namespace", req.Namespace, "name", req.Name) + return ctrl.Result{}, err + } + } + + return ctrl.Result{}, nil +} + +func (r *ServiceReconciler) reconcileNBResource(nbResource *netbirdiov1.NBResource, req ctrl.Request, svc corev1.Service, routingPeer netbirdiov1.NBRoutingPeer) error { + groups := []string{fmt.Sprintf("%s-%s-%s", r.ClusterName, req.Namespace, req.Name)} + if v, ok := svc.Annotations[serviceGroupsAnnotation]; ok { + groups = nil + for _, g := range strings.Split(v, ",") { + groups = append(groups, strings.TrimSpace(g)) + } + } + + resourceName := fmt.Sprintf("%s-%s", req.Namespace, req.Name) + if v, ok := svc.Annotations[serviceResourceAnnotation]; ok { + resourceName = v + } + + nbResource.ObjectMeta.Name = req.Name + nbResource.ObjectMeta.Namespace = req.Namespace + nbResource.Finalizers = []string{"netbird.io/cleanup"} + nbResource.Spec.Name = resourceName + nbResource.Spec.NetworkID = *routingPeer.Status.NetworkID + nbResource.Spec.Address = fmt.Sprintf("%s.%s.%s", svc.Name, svc.Namespace, r.ClusterDNS) + nbResource.Spec.Groups = groups + + if v, ok := svc.Annotations[servicePolicyAnnotation]; ok { + nbResource.Spec.PolicyName = v + var filterProtocols []string + if v, ok := svc.Annotations[serviceProtocolAnnotation]; ok { + filterProtocols = []string{v} + } + var filterPorts []int32 + if v, ok := svc.Annotations[servicePortsAnnotation]; ok { + for _, v := range strings.Split(v, ",") { + port, err := strconv.ParseInt(v, 10, 64) + if err != nil { + return err + } + + filterPorts = append(filterPorts, int32(port)) + } + } + + for _, p := range svc.Spec.Ports { + if len(filterProtocols) > 0 && !util.Contains(filterProtocols, string(p.Protocol)) { + continue + } + if len(filterPorts) > 0 && !util.Contains(filterPorts, p.Port) { + continue + } + switch p.Protocol { + case corev1.ProtocolSCTP: + if !util.Contains(nbResource.Spec.TCPPorts, p.Port) { + nbResource.Spec.TCPPorts = append(nbResource.Spec.TCPPorts, p.Port) + } + case corev1.ProtocolTCP: + if !util.Contains(nbResource.Spec.TCPPorts, p.Port) { + nbResource.Spec.TCPPorts = append(nbResource.Spec.TCPPorts, p.Port) + } + case corev1.ProtocolUDP: + if !util.Contains(nbResource.Spec.UDPPorts, p.Port) { + nbResource.Spec.UDPPorts = append(nbResource.Spec.UDPPorts, p.Port) + } + default: + return errUnknownProtocol + } + } + } + + return nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *ServiceReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&corev1.Service{}). + Named("service"). + Complete(r) +} diff --git a/internal/controller/service_controller_test.go b/internal/controller/service_controller_test.go new file mode 100644 index 0000000..0067a76 --- /dev/null +++ b/internal/controller/service_controller_test.go @@ -0,0 +1,17 @@ +package controller + +import ( + . "github.com/onsi/ginkgo/v2" +) + +var _ = Describe("Service Controller", func() { + Context("When reconciling a resource", func() { + + It("should successfully reconcile the resource", func() { + Skip("Not implemented yet") + + // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. + // Example: If you expect a certain status condition after reconciliation, verify it here. + }) + }) +}) diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index 431ceba..4e0db20 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -32,6 +32,8 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" + foobarv1 "k8s.io/api/core/v1" + netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" // +kubebuilder:scaffold:imports ) @@ -62,6 +64,9 @@ var _ = BeforeSuite(func() { err = netbirdiov1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) + err = foobarv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + // +kubebuilder:scaffold:scheme By("bootstrapping test environment") diff --git a/internal/util/ptr.go b/internal/util/ptr.go new file mode 100644 index 0000000..8b972e2 --- /dev/null +++ b/internal/util/ptr.go @@ -0,0 +1,6 @@ +package util + +// Ptr return pointer to any value for API purposes +func Ptr[T any, PT *T](x T) PT { + return &x +} diff --git a/internal/util/slices.go b/internal/util/slices.go new file mode 100644 index 0000000..223ed70 --- /dev/null +++ b/internal/util/slices.go @@ -0,0 +1,41 @@ +package util + +// Contains return if y is in slice x +func Contains[T comparable](x []T, y T) bool { + for _, v := range x { + if v == y { + return true + } + } + return false +} + +// Without return all of x in same order without y +func Without[T comparable](x []T, y T) []T { + var ret []T + for _, v := range x { + if v != y { + ret = append(ret, v) + } + } + return ret +} + +// Equivalent return true if x and y are equal when sorted +func Equivalent[T comparable](x, y []T) bool { + if len(x) != len(y) { + return false + } + + mp := make(map[T]interface{}) + for _, v := range x { + mp[v] = nil + } + for _, v := range y { + if _, ok := mp[v]; !ok { + return false + } + } + + return true +} diff --git a/internal/webhook/v1/nbgroup_webhook.go b/internal/webhook/v1/nbgroup_webhook.go new file mode 100644 index 0000000..357ee42 --- /dev/null +++ b/internal/webhook/v1/nbgroup_webhook.go @@ -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 +} diff --git a/internal/webhook/v1/nbgroup_webhook_test.go b/internal/webhook/v1/nbgroup_webhook_test.go new file mode 100644 index 0000000..d922061 --- /dev/null +++ b/internal/webhook/v1/nbgroup_webhook_test.go @@ -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()) + // }) + }) + +}) diff --git a/internal/webhook/v1/nbresource_webhook.go b/internal/webhook/v1/nbresource_webhook.go new file mode 100644 index 0000000..66c73e7 --- /dev/null +++ b/internal/webhook/v1/nbresource_webhook.go @@ -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 +} diff --git a/internal/webhook/v1/nbresource_webhook_test.go b/internal/webhook/v1/nbresource_webhook_test.go new file mode 100644 index 0000000..29738be --- /dev/null +++ b/internal/webhook/v1/nbresource_webhook_test.go @@ -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()) + // }) + }) + +}) diff --git a/internal/webhook/v1/nbroutingpeer_webhook.go b/internal/webhook/v1/nbroutingpeer_webhook.go new file mode 100644 index 0000000..7e829b6 --- /dev/null +++ b/internal/webhook/v1/nbroutingpeer_webhook.go @@ -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 +} diff --git a/internal/webhook/v1/nbroutingpeer_webhook_test.go b/internal/webhook/v1/nbroutingpeer_webhook_test.go new file mode 100644 index 0000000..f739f17 --- /dev/null +++ b/internal/webhook/v1/nbroutingpeer_webhook_test.go @@ -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()) + // }) + }) + +}) diff --git a/internal/webhook/v1/pod_webhook.go b/internal/webhook/v1/pod_webhook.go index 23abd62..b3bda17 100644 --- a/internal/webhook/v1/pod_webhook.go +++ b/internal/webhook/v1/pod_webhook.go @@ -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 } } diff --git a/internal/webhook/v1/pod_webhook_test.go b/internal/webhook/v1/pod_webhook_test.go index 7708cf4..c2003b9 100644 --- a/internal/webhook/v1/pod_webhook_test.go +++ b/internal/webhook/v1/pod_webhook_test.go @@ -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, }, }, diff --git a/internal/webhook/v1/webhook_suite_test.go b/internal/webhook/v1/webhook_suite_test.go index be779d4..22173b0 100644 --- a/internal/webhook/v1/webhook_suite_test.go +++ b/internal/webhook/v1/webhook_suite_test.go @@ -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() { diff --git a/manifests/install.yaml b/manifests/install.yaml index eae0dec..c675914 100644 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -1,3 +1,580 @@ +--- +# Source: kubernetes-operator/crds/netbird.io_nbgroups.yaml +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: nbgroups.netbird.io +spec: + group: netbird.io + names: + kind: NBGroup + listKind: NBGroupList + plural: nbgroups + singular: nbgroup + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: NBGroup is the Schema for the nbgroups API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: NBGroupSpec defines the desired state of NBGroup. + properties: + name: + minLength: 1 + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + required: + - name + type: object + status: + description: NBGroupStatus defines the observed state of NBGroup. + properties: + conditions: + items: + description: NBCondition defines a condition in NBSetupKey status. + properties: + lastProbeTime: + description: Last time we probed the condition. + format: date-time + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: Human-readable message indicating details about + last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's + last transition. + type: string + status: + description: |- + Status is the status of the condition. + Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + required: + - status + - type + type: object + type: array + groupID: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} + +--- +# Source: kubernetes-operator/crds/netbird.io_nbpolicies.yaml +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: nbpolicies.netbird.io +spec: + group: netbird.io + names: + kind: NBPolicy + listKind: NBPolicyList + plural: nbpolicies + singular: nbpolicy + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: NBPolicy is the Schema for the nbpolicies API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: NBPolicySpec defines the desired state of NBPolicy. + properties: + bidirectional: + default: true + type: boolean + description: + type: string + destinationGroups: + items: + minLength: 1 + type: string + type: array + name: + description: Name Policy name + minLength: 1 + type: string + ports: + items: + format: int32 + maximum: 65535 + minimum: 0 + type: integer + type: array + protocols: + items: + enum: + - tcp + - udp + type: string + type: array + sourceGroups: + items: + minLength: 1 + type: string + type: array + required: + - name + type: object + status: + description: NBPolicyStatus defines the observed state of NBPolicy. + properties: + conditions: + items: + description: NBCondition defines a condition in NBSetupKey status. + properties: + lastProbeTime: + description: Last time we probed the condition. + format: date-time + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: Human-readable message indicating details about + last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's + last transition. + type: string + status: + description: |- + Status is the status of the condition. + Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + required: + - status + - type + type: object + type: array + lastUpdatedAt: + format: date-time + type: string + managedServiceList: + items: + type: string + type: array + tcpPolicyID: + type: string + udpPolicyID: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} + +--- +# Source: kubernetes-operator/crds/netbird.io_nbresources.yaml +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: nbresources.netbird.io +spec: + group: netbird.io + names: + kind: NBResource + listKind: NBResourceList + plural: nbresources + singular: nbresource + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: NBResource is the Schema for the nbresources API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: NBResourceSpec defines the desired state of NBResource. + properties: + address: + minLength: 1 + type: string + groups: + items: + minLength: 1 + type: string + type: array + name: + minLength: 1 + type: string + networkID: + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + policyName: + type: string + tcpPorts: + items: + format: int32 + type: integer + type: array + udpPorts: + items: + format: int32 + type: integer + type: array + required: + - address + - groups + - name + - networkID + type: object + status: + description: NBResourceStatus defines the observed state of NBResource. + properties: + conditions: + items: + description: NBCondition defines a condition in NBSetupKey status. + properties: + lastProbeTime: + description: Last time we probed the condition. + format: date-time + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: Human-readable message indicating details about + last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's + last transition. + type: string + status: + description: |- + Status is the status of the condition. + Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + required: + - status + - type + type: object + type: array + groups: + items: + type: string + type: array + networkResourceID: + type: string + policyName: + type: string + tcpPorts: + items: + format: int32 + type: integer + type: array + udpPorts: + items: + format: int32 + type: integer + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} + +--- +# Source: kubernetes-operator/crds/netbird.io_nbroutingpeers.yaml +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: nbroutingpeers.netbird.io +spec: + group: netbird.io + names: + kind: NBRoutingPeer + listKind: NBRoutingPeerList + plural: nbroutingpeers + singular: nbroutingpeer + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: NBRoutingPeer is the Schema for the nbroutingpeers API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: NBRoutingPeerSpec defines the desired state of NBRoutingPeer. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + nodeSelector: + additionalProperties: + type: string + type: object + replicas: + format: int32 + type: integer + resources: + description: ResourceRequirements describes the compute resource requirements. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + tolerations: + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + status: + description: NBRoutingPeerStatus defines the observed state of NBRoutingPeer. + properties: + conditions: + items: + description: NBCondition defines a condition in NBSetupKey status. + properties: + lastProbeTime: + description: Last time we probed the condition. + format: date-time + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: Human-readable message indicating details about + last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's + last transition. + type: string + status: + description: |- + Status is the status of the condition. + Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + required: + - status + - type + type: object + type: array + networkID: + type: string + routerID: + type: string + setupKeyID: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} + --- # Source: kubernetes-operator/crds/netbird.io_nbsetupkeys.yaml --- @@ -77,8 +654,7 @@ spec: properties: conditions: items: - description: NBSetupKeyCondition defines a condition in NBSetupKey - status. + description: NBCondition defines a condition in NBSetupKey status. properties: lastProbeTime: description: Last time we probed the condition. @@ -124,7 +700,7 @@ kind: ServiceAccount metadata: name: kubernetes-operator labels: - helm.sh/chart: kubernetes-operator-0.1.0 + helm.sh/chart: kubernetes-operator-0.1.1 app.kubernetes.io/name: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/version: "v0.1.0" @@ -137,7 +713,7 @@ kind: ClusterRole metadata: name: kubernetes-operator labels: - helm.sh/chart: kubernetes-operator-0.1.0 + helm.sh/chart: kubernetes-operator-0.1.1 app.kubernetes.io/name: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/version: "v0.1.0" @@ -188,7 +764,7 @@ kind: ClusterRoleBinding metadata: name: kubernetes-operator labels: - helm.sh/chart: kubernetes-operator-0.1.0 + helm.sh/chart: kubernetes-operator-0.1.1 app.kubernetes.io/name: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/version: "v0.1.0" @@ -200,7 +776,7 @@ roleRef: subjects: - kind: ServiceAccount name: kubernetes-operator - namespace: default + namespace: production --- # Source: kubernetes-operator/templates/rbac.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -208,7 +784,7 @@ kind: Role metadata: name: kubernetes-operator labels: - helm.sh/chart: kubernetes-operator-0.1.0 + helm.sh/chart: kubernetes-operator-0.1.1 app.kubernetes.io/name: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/version: "v0.1.0" @@ -252,7 +828,7 @@ kind: RoleBinding metadata: name: kubernetes-operator labels: - helm.sh/chart: kubernetes-operator-0.1.0 + helm.sh/chart: kubernetes-operator-0.1.1 app.kubernetes.io/name: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/version: "v0.1.0" @@ -264,7 +840,7 @@ roleRef: subjects: - kind: ServiceAccount name: kubernetes-operator - namespace: default + namespace: production --- # Source: kubernetes-operator/templates/service.yaml apiVersion: v1 @@ -272,7 +848,7 @@ kind: Service metadata: name: kubernetes-operator-metrics labels: - helm.sh/chart: kubernetes-operator-0.1.0 + helm.sh/chart: kubernetes-operator-0.1.1 app.kubernetes.io/name: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/version: "v0.1.0" @@ -294,7 +870,7 @@ kind: Service metadata: name: kubernetes-operator-webhook-service labels: - helm.sh/chart: kubernetes-operator-0.1.0 + helm.sh/chart: kubernetes-operator-0.1.1 app.kubernetes.io/name: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/version: "v0.1.0" @@ -317,7 +893,7 @@ metadata: name: kubernetes-operator labels: app.kubernetes.io/component: operator - helm.sh/chart: kubernetes-operator-0.1.0 + helm.sh/chart: kubernetes-operator-0.1.1 app.kubernetes.io/name: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/version: "v0.1.0" @@ -332,7 +908,7 @@ spec: metadata: labels: app.kubernetes.io/component: operator - helm.sh/chart: kubernetes-operator-0.1.0 + helm.sh/chart: kubernetes-operator-0.1.1 app.kubernetes.io/name: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/version: "v0.1.0" @@ -400,17 +976,17 @@ apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: kubernetes-operator-serving-cert - namespace: default + namespace: production labels: - helm.sh/chart: kubernetes-operator-0.1.0 + helm.sh/chart: kubernetes-operator-0.1.1 app.kubernetes.io/name: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/version: "v0.1.0" app.kubernetes.io/managed-by: Helm spec: dnsNames: - - kubernetes-operator-webhook-service.default.svc - - kubernetes-operator-webhook-service.default.svc.cluster.local + - kubernetes-operator-webhook-service.production.svc + - kubernetes-operator-webhook-service.production.svc.cluster.local issuerRef: kind: Issuer name: kubernetes-operator-selfsigned-issuer @@ -421,9 +997,9 @@ apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: kubernetes-operator-selfsigned-issuer - namespace: default + namespace: production labels: - helm.sh/chart: kubernetes-operator-0.1.0 + helm.sh/chart: kubernetes-operator-0.1.1 app.kubernetes.io/name: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/version: "v0.1.0" @@ -436,10 +1012,10 @@ apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration metadata: annotations: - cert-manager.io/inject-ca-from: default/kubernetes-operator-serving-cert + cert-manager.io/inject-ca-from: production/kubernetes-operator-serving-cert name: kubernetes-operator-mpod-webhook labels: - helm.sh/chart: kubernetes-operator-0.1.0 + helm.sh/chart: kubernetes-operator-0.1.1 app.kubernetes.io/name: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/version: "v0.1.0" @@ -448,7 +1024,7 @@ webhooks: - clientConfig: service: name: kubernetes-operator-webhook-service - namespace: default + namespace: production path: /mutate--v1-pod failurePolicy: Fail name: mpod-v1.netbird.io @@ -476,10 +1052,10 @@ apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: annotations: - cert-manager.io/inject-ca-from: default/kubernetes-operator-serving-cert + cert-manager.io/inject-ca-from: production/kubernetes-operator-serving-cert name: kubernetes-operator-vnbsetupkey-webhook labels: - helm.sh/chart: kubernetes-operator-0.1.0 + helm.sh/chart: kubernetes-operator-0.1.1 app.kubernetes.io/name: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/version: "v0.1.0" @@ -488,7 +1064,7 @@ webhooks: - clientConfig: service: name: kubernetes-operator-webhook-service - namespace: default + namespace: production path: /validate-netbird-io-v1-nbsetupkey failurePolicy: Fail name: vnbsetupkey-v1.netbird.io @@ -503,5 +1079,86 @@ webhooks: - CREATE - UPDATE resources: - - "*/*" + - "nbsetupkeys" sideEffects: None +--- +# Source: kubernetes-operator/templates/pre-delete.yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: kubernetes-operator-delete-routers + labels: + app.kubernetes.io/component: operator + helm.sh/chart: kubernetes-operator-0.1.1 + app.kubernetes.io/name: kubernetes-operator + app.kubernetes.io/instance: kubernetes-operator + app.kubernetes.io/version: "v0.1.0" + app.kubernetes.io/managed-by: Helm + annotations: + helm.sh/hook: pre-delete + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded +spec: + backOffLimit: 3 + template: + metadata: + name: kubernetes-operator + labels: + app.kubernetes.io/component: operator + helm.sh/chart: kubernetes-operator-0.1.1 + app.kubernetes.io/name: kubernetes-operator + app.kubernetes.io/instance: kubernetes-operator + app.kubernetes.io/version: "v0.1.0" + app.kubernetes.io/managed-by: Helm + spec: + containers: + - name: pre-delete + image: "bitnami/kubectl:latest" + args: + - delete + - --all + - -A + - --cascade=foreground + - --ignore-not-found + - NBRoutingPeer + serviceAccountName: kubernetes-operator + restartPolicy: Never +--- +# Source: kubernetes-operator/templates/pre-delete.yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: kubernetes-operator-delete-policies + labels: + app.kubernetes.io/component: operator + helm.sh/chart: kubernetes-operator-0.1.1 + app.kubernetes.io/name: kubernetes-operator + app.kubernetes.io/instance: kubernetes-operator + app.kubernetes.io/version: "v0.1.0" + app.kubernetes.io/managed-by: Helm + annotations: + helm.sh/hook: pre-delete + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded +spec: + backOffLimit: 3 + template: + metadata: + name: kubernetes-operator + labels: + app.kubernetes.io/component: operator + helm.sh/chart: kubernetes-operator-0.1.1 + app.kubernetes.io/name: kubernetes-operator + app.kubernetes.io/instance: kubernetes-operator + app.kubernetes.io/version: "v0.1.0" + app.kubernetes.io/managed-by: Helm + spec: + containers: + - name: pre-delete + image: "bitnami/kubectl:latest" + args: + - delete + - --all + - --cascade=foreground + - --ignore-not-found + - NBPolicy + serviceAccountName: kubernetes-operator + restartPolicy: Never From 00ba79660d542e24054b1f558c4778e95c0bc421 Mon Sep 17 00:00:00 2001 From: M Essam Hamed Date: Sun, 2 Mar 2025 11:58:04 +0200 Subject: [PATCH 02/11] Restructure NBGroup controller --- go.mod | 2 +- internal/controller/nbgroup_controller.go | 47 +++++++++++++---------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/go.mod b/go.mod index 28d1e60..9034512 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.23.0 godebug default=go1.23 require ( + github.com/go-logr/logr v1.4.2 github.com/google/uuid v1.6.0 github.com/netbirdio/netbird v0.36.7 github.com/onsi/ginkgo/v2 v2.21.0 @@ -23,7 +24,6 @@ require ( github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/go-logr/logr v1.4.2 // 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 diff --git a/internal/controller/nbgroup_controller.go b/internal/controller/nbgroup_controller.go index 0302edb..1de3bc0 100644 --- a/internal/controller/nbgroup_controller.go +++ b/internal/controller/nbgroup_controller.go @@ -10,8 +10,8 @@ import ( "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" + "github.com/go-logr/logr" netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" "github.com/netbirdio/kubernetes-operator/internal/util" netbird "github.com/netbirdio/netbird/management/client/rest" @@ -34,15 +34,14 @@ const ( // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. func (r *NBGroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, err error) { - _ = log.FromContext(ctx) - - ctrl.Log.Info("NBGroup: Reconciling", "namespace", req.Namespace, "name", req.Name) + logger := ctrl.Log.WithName("NBGroup").WithValues("namespace", req.Namespace, "name", req.Name) + logger.Info("Reconciling NBGroup") nbGroup := netbirdiov1.NBGroup{} err = r.Client.Get(ctx, req.NamespacedName, &nbGroup) if err != nil { if !errors.IsNotFound(err) { - ctrl.Log.Error(errKubernetesAPI, "error getting NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error getting NBGroup", "err", err) } return ctrl.Result{RequeueAfter: defaultRequeueAfter}, nil } @@ -64,10 +63,18 @@ func (r *NBGroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (re if len(nbGroup.Finalizers) == 0 { return ctrl.Result{}, nil } - return ctrl.Result{}, r.handleDelete(ctx, req, nbGroup) + return ctrl.Result{}, r.handleDelete(ctx, nbGroup, logger) } + return r.syncNetBirdGroup(ctx, &nbGroup, logger) +} + +func (r *NBGroupReconciler) syncNetBirdGroup(ctx context.Context, nbGroup *netbirdiov1.NBGroup, logger logr.Logger) (ctrl.Result, error) { groups, err := r.netbird.Groups.List(ctx) + if err != nil { + logger.Error(errNetBirdAPI, "error listing groups", "err", err) + return ctrl.Result{}, err + } var group *api.Group for _, g := range groups { if g.Name == nbGroup.Spec.Name { @@ -75,26 +82,25 @@ func (r *NBGroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (re } } if nbGroup.Status.GroupID == nil && group == nil { - ctrl.Log.Info("NBGroup: Creating group on NetBird", "name", nbGroup.Spec.Name) + logger.Info("NBGroup: Creating group on NetBird", "name", nbGroup.Spec.Name) group, err := r.netbird.Groups.Create(ctx, api.GroupRequest{ Name: nbGroup.Spec.Name, }) - ctrl.Log.Info("NBGroup: Created group on NetBird", "name", nbGroup.Spec.Name, "id", group.Id) - if err != nil { nbGroup.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("NetBird API Error: %v", err)) - ctrl.Log.Error(errNetBirdAPI, "error creating group", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errNetBirdAPI, "error creating group", "err", err) return ctrl.Result{}, err } + logger.Info("NBGroup: Created group on NetBird", "name", nbGroup.Spec.Name, "id", group.Id) nbGroup.Status.GroupID = &group.Id nbGroup.Status.Conditions = netbirdiov1.NBConditionTrue() } else if nbGroup.Status.GroupID == nil && group != nil { - ctrl.Log.Info("NBGroup: Found group with same name on NetBird", "name", nbGroup.Spec.Name, "id", group.Id) + logger.Info("NBGroup: Found group with same name on NetBird", "name", nbGroup.Spec.Name, "id", group.Id) nbGroup.Status.GroupID = &group.Id nbGroup.Status.Conditions = netbirdiov1.NBConditionTrue() } else if group == nil { - ctrl.Log.Info("NBGroup: Group was deleted", "name", nbGroup.Spec.Name, "id", *nbGroup.Status.GroupID) + logger.Info("NBGroup: Group was deleted", "name", nbGroup.Spec.Name, "id", *nbGroup.Status.GroupID) nbGroup.Status.GroupID = nil nbGroup.Status.Conditions = netbirdiov1.NBConditionFalse("GroupGone", "Group was deleted from NetBird API") return ctrl.Result{Requeue: true}, nil @@ -108,16 +114,15 @@ func (r *NBGroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (re nbGroup.Status.GroupID = &group.Id nbGroup.Status.Conditions = netbirdiov1.NBConditionTrue() } - return ctrl.Result{}, nil } -func (r *NBGroupReconciler) handleDelete(ctx context.Context, req ctrl.Request, nbGroup netbirdiov1.NBGroup) error { +func (r *NBGroupReconciler) handleDelete(ctx context.Context, nbGroup netbirdiov1.NBGroup, logger logr.Logger) error { if nbGroup.Status.GroupID == nil { nbGroup.Finalizers = util.Without(nbGroup.Finalizers, "netbird.io/group-cleanup") err := r.Client.Update(ctx, &nbGroup) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error updating NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error updating NBGroup", "err", err) return err } @@ -126,17 +131,17 @@ func (r *NBGroupReconciler) handleDelete(ctx context.Context, req ctrl.Request, err := r.netbird.Groups.Delete(ctx, *nbGroup.Status.GroupID) if err != nil && !strings.Contains(err.Error(), "not found") && !strings.Contains(err.Error(), "linked") { - ctrl.Log.Error(errNetBirdAPI, "error deleting group", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errNetBirdAPI, "error deleting group", "err", err) return err } if err != nil && strings.Contains(err.Error(), "linked") { - ctrl.Log.Info("group still linked to resources on netbird", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Info("group still linked to resources on netbird", "err", err) // Check if group is defined elsewhere in the cluster var groups netbirdiov1.NBGroupList listErr := r.Client.List(ctx, &groups) if listErr != nil { - ctrl.Log.Error(errKubernetesAPI, "error listing NBGroups", "err", listErr) + logger.Error(errKubernetesAPI, "error listing NBGroups", "err", listErr) return listErr } for _, v := range groups.Items { @@ -145,11 +150,11 @@ func (r *NBGroupReconciler) handleDelete(ctx context.Context, req ctrl.Request, } if v.Status.GroupID != nil && nbGroup.Status.GroupID != nil && *v.Status.GroupID == *nbGroup.Status.GroupID { // Same group, multiple resources - ctrl.Log.Info("group exists in another namespace", "namespace", v.Namespace, "name", v.Name) + logger.Info("group exists in another namespace", "namespace", v.Namespace, "name", v.Name) nbGroup.Finalizers = util.Without(nbGroup.Finalizers, "netbird.io/group-cleanup") err = r.Client.Update(ctx, &nbGroup) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error updating NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error updating NBGroup", "err", err) return err } return nil @@ -161,7 +166,7 @@ func (r *NBGroupReconciler) handleDelete(ctx context.Context, req ctrl.Request, nbGroup.Finalizers = util.Without(nbGroup.Finalizers, "netbird.io/group-cleanup") err = r.Client.Update(ctx, &nbGroup) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error updating NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error updating NBGroup", "err", err) return err } From dd8e64f0886a67fa230ff8c1b0249f8a092c53d2 Mon Sep 17 00:00:00 2001 From: M Essam Hamed Date: Sun, 2 Mar 2025 11:58:10 +0200 Subject: [PATCH 03/11] Unify controller logging --- internal/controller/nbpolicy_controller.go | 73 ++++++----- internal/controller/nbresource_controller.go | 56 ++++----- .../controller/nbroutingpeer_controller.go | 117 ++++++++---------- internal/controller/nbsetupkey_controller.go | 16 +-- internal/controller/service_controller.go | 39 +++--- 5 files changed, 146 insertions(+), 155 deletions(-) diff --git a/internal/controller/nbpolicy_controller.go b/internal/controller/nbpolicy_controller.go index a388339..ed49475 100644 --- a/internal/controller/nbpolicy_controller.go +++ b/internal/controller/nbpolicy_controller.go @@ -11,8 +11,8 @@ import ( "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" + "github.com/go-logr/logr" netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" "github.com/netbirdio/kubernetes-operator/internal/util" netbird "github.com/netbirdio/netbird/management/client/rest" @@ -35,7 +35,7 @@ var ( errNetBirdAPI = fmt.Errorf("netbird API error") ) -func (r *NBPolicyReconciler) getResources(ctx context.Context, nbPolicy *netbirdiov1.NBPolicy) ([]netbirdiov1.NBResource, error) { +func (r *NBPolicyReconciler) getResources(ctx context.Context, nbPolicy *netbirdiov1.NBPolicy, logger logr.Logger) ([]netbirdiov1.NBResource, error) { var resourceList []netbirdiov1.NBResource var updatedManagedServiceList []string for _, rss := range nbPolicy.Status.ManagedServiceList { @@ -43,7 +43,7 @@ func (r *NBPolicyReconciler) getResources(ctx context.Context, nbPolicy *netbird namespacedName := types.NamespacedName{Namespace: strings.Split(rss, "/")[0], Name: strings.Split(rss, "/")[1]} err := r.Client.Get(ctx, namespacedName, &resource) if err != nil && !errors.IsNotFound(err) { - ctrl.Log.Error(errKubernetesAPI, "Error getting NBResource", "namespace", namespacedName.Namespace, "name", namespacedName.Name) + logger.Error(errKubernetesAPI, "Error getting NBResource", "namespace", namespacedName.Namespace, "name", namespacedName.Name) nbPolicy.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("Error getting NBResource: %v", err)) return nil, err } @@ -58,12 +58,12 @@ func (r *NBPolicyReconciler) getResources(ctx context.Context, nbPolicy *netbird return resourceList, nil } -func (r *NBPolicyReconciler) mapResources(ctx context.Context, req ctrl.Request, nbPolicy *netbirdiov1.NBPolicy, resources []netbirdiov1.NBResource) (map[string][]int32, []string, error) { +func (r *NBPolicyReconciler) mapResources(ctx context.Context, nbPolicy *netbirdiov1.NBPolicy, resources []netbirdiov1.NBResource, logger logr.Logger) (map[string][]int32, []string, error) { portMapping := map[string]map[int32]interface{}{ "tcp": make(map[int32]interface{}), "udp": make(map[int32]interface{}), } - groups, err := r.groupNamesToIDs(ctx, req, nbPolicy.Spec.DestinationGroups) + groups, err := r.groupNamesToIDs(ctx, nbPolicy.Spec.DestinationGroups, logger) if err != nil { return nil, nil, err } @@ -92,9 +92,9 @@ func (r *NBPolicyReconciler) mapResources(ctx context.Context, req ctrl.Request, return ports, groups, nil } -func (r *NBPolicyReconciler) createPolicy(ctx context.Context, req ctrl.Request, nbPolicy *netbirdiov1.NBPolicy, protocol string, sourceGroupIDs, destinationGroupIDs, ports []string) (*string, error) { +func (r *NBPolicyReconciler) createPolicy(ctx context.Context, nbPolicy *netbirdiov1.NBPolicy, protocol string, sourceGroupIDs, destinationGroupIDs, ports []string, logger logr.Logger) (*string, error) { policyName := fmt.Sprintf("%s %s", nbPolicy.Spec.Name, strings.ToUpper(protocol)) - ctrl.Log.Info("Creating NetBird Policy", "name", policyName, "description", nbPolicy.Spec.Description, "protocol", protocol, "sources", sourceGroupIDs, "destinations", destinationGroupIDs, "ports", ports, "bidirectional", nbPolicy.Spec.Bidirectional) + logger.Info("Creating NetBird Policy", "name", policyName, "description", nbPolicy.Spec.Description, "protocol", protocol, "sources", sourceGroupIDs, "destinations", destinationGroupIDs, "ports", ports, "bidirectional", nbPolicy.Spec.Bidirectional) policy, err := r.netbird.Policies.Create(ctx, api.PostApiPoliciesJSONRequestBody{ Enabled: true, Name: policyName, @@ -115,7 +115,7 @@ func (r *NBPolicyReconciler) createPolicy(ctx context.Context, req ctrl.Request, }) if err != nil { - ctrl.Log.Error(errNetBirdAPI, "Error creating Policy", "namespace", req.Namespace, "name", req.Name, "err", err) + logger.Error(errNetBirdAPI, "Error creating Policy", "err", err) nbPolicy.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("Error creating policy: %v", err)) return nil, err } @@ -123,9 +123,9 @@ func (r *NBPolicyReconciler) createPolicy(ctx context.Context, req ctrl.Request, return policy.Id, nil } -func (r *NBPolicyReconciler) updatePolicy(ctx context.Context, req ctrl.Request, policyID *string, nbPolicy *netbirdiov1.NBPolicy, protocol string, sourceGroupIDs, destinationGroupIDs, ports []string) (*string, bool, error) { +func (r *NBPolicyReconciler) updatePolicy(ctx context.Context, policyID *string, nbPolicy *netbirdiov1.NBPolicy, protocol string, sourceGroupIDs, destinationGroupIDs, ports []string, logger logr.Logger) (*string, bool, error) { policyName := fmt.Sprintf("%s %s", nbPolicy.Spec.Name, strings.ToUpper(protocol)) - ctrl.Log.Info("Updating NetBird Policy", "name", policyName, "description", nbPolicy.Spec.Description, "protocol", protocol, "sources", sourceGroupIDs, "destinations", destinationGroupIDs, "ports", ports, "bidirectional", nbPolicy.Spec.Bidirectional) + logger.Info("Updating NetBird Policy", "name", policyName, "description", nbPolicy.Spec.Description, "protocol", protocol, "sources", sourceGroupIDs, "destinations", destinationGroupIDs, "ports", ports, "bidirectional", nbPolicy.Spec.Bidirectional) policy, err := r.netbird.Policies.Update(ctx, *policyID, api.PutApiPoliciesPolicyIdJSONRequestBody{ Enabled: true, Name: policyName, @@ -146,7 +146,7 @@ func (r *NBPolicyReconciler) updatePolicy(ctx context.Context, req ctrl.Request, }) if err != nil && !strings.Contains(err.Error(), "not found") { - ctrl.Log.Error(errNetBirdAPI, "Error updating Policy", "namespace", req.Namespace, "name", req.Name, "err", err) + logger.Error(errNetBirdAPI, "Error updating Policy", "err", err) nbPolicy.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("Error updating policy: %v", err)) return policyID, false, err } @@ -154,7 +154,7 @@ func (r *NBPolicyReconciler) updatePolicy(ctx context.Context, req ctrl.Request, requeue := false if err != nil && strings.Contains(err.Error(), "not found") { - ctrl.Log.Info("Policy deleted from NetBird API, recreating", "namespace", req.Namespace, "name", req.Name, "protocol", protocol) + logger.Info("Policy deleted from NetBird API, recreating", "protocol", protocol) policyID = nil requeue = true nbPolicy.Status.Conditions = netbirdiov1.NBConditionFalse("Gone", "Policy deleted from NetBird API") @@ -169,9 +169,8 @@ func (r *NBPolicyReconciler) updatePolicy(ctx context.Context, req ctrl.Request, // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. func (r *NBPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, err error) { - _ = log.FromContext(ctx) - - ctrl.Log.Info("NBPolicy: Reconciling", "namespace", req.Namespace, "name", req.Name) + logger := ctrl.Log.WithName("NBPolicy").WithValues("namespace", req.Namespace, "name", req.Name) + logger.Info("Reconciling NBPolicy") var nbPolicy netbirdiov1.NBPolicy err = r.Client.Get(ctx, req.NamespacedName, &nbPolicy) @@ -180,7 +179,7 @@ func (r *NBPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (r err = nil } if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error getting NBPolicy", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error getting NBPolicy", "err", err) } return ctrl.Result{RequeueAfter: defaultRequeueAfter}, err } @@ -203,26 +202,26 @@ func (r *NBPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (r if len(nbPolicy.Finalizers) == 0 { return ctrl.Result{}, nil } - return ctrl.Result{}, r.handleDelete(ctx, req, nbPolicy) + return ctrl.Result{}, r.handleDelete(ctx, nbPolicy, logger) } - resourceList, err := r.getResources(ctx, &nbPolicy) + resourceList, err := r.getResources(ctx, &nbPolicy, logger) if err != nil { return ctrl.Result{}, err } - portMapping, destGroups, err := r.mapResources(ctx, req, &nbPolicy, resourceList) + portMapping, destGroups, err := r.mapResources(ctx, &nbPolicy, resourceList, logger) if err != nil { return ctrl.Result{}, err } - sourceGroupIDs, err := r.groupNamesToIDs(ctx, req, nbPolicy.Spec.SourceGroups) + sourceGroupIDs, err := r.groupNamesToIDs(ctx, nbPolicy.Spec.SourceGroups, logger) if err != nil { nbPolicy.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("Error getting group IDs: %v", err)) return ctrl.Result{}, err } - requeue, err := r.handlePolicies(ctx, req, &nbPolicy, sourceGroupIDs, destGroups, portMapping) + requeue, err := r.handlePolicies(ctx, &nbPolicy, sourceGroupIDs, destGroups, portMapping, logger) if requeue || err != nil { return ctrl.Result{Requeue: requeue}, err @@ -233,7 +232,7 @@ func (r *NBPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (r return ctrl.Result{}, nil } -func (r *NBPolicyReconciler) handlePolicies(ctx context.Context, req ctrl.Request, nbPolicy *netbirdiov1.NBPolicy, sourceGroups, destGroups []string, portMapping map[string][]int32) (bool, error) { +func (r *NBPolicyReconciler) handlePolicies(ctx context.Context, nbPolicy *netbirdiov1.NBPolicy, sourceGroups, destGroups []string, portMapping map[string][]int32, logger logr.Logger) (bool, error) { requeue := false for protocol, ports := range portMapping { @@ -244,14 +243,14 @@ func (r *NBPolicyReconciler) handlePolicies(ctx context.Context, req ctrl.Reques case "udp": policyID = nbPolicy.Status.UDPPolicyID default: - ctrl.Log.Error(errKubernetesAPI, "Unknown protocol", "namespace", req.Namespace, "name", req.Name, "protocol", protocol) + logger.Error(errKubernetesAPI, "Unknown protocol", "protocol", protocol) nbPolicy.Status.Conditions = netbirdiov1.NBConditionFalse("ConfigError", fmt.Sprintf("Unknown protocol: %s", protocol)) return requeue, errUnknownProtocol } if len(nbPolicy.Spec.Protocols) > 0 && !util.Contains(nbPolicy.Spec.Protocols, protocol) { if policyID != nil { - ctrl.Log.Info("Deleting protocol policy as NBPolicy has restricted protocols", "protocol", protocol) + logger.Info("Deleting protocol policy as NBPolicy has restricted protocols", "protocol", protocol) err := r.netbird.Policies.Delete(ctx, *policyID) if err != nil && !strings.Contains(err.Error(), "not found") { nbPolicy.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("Error deleting policy: %v", err)) @@ -260,20 +259,20 @@ func (r *NBPolicyReconciler) handlePolicies(ctx context.Context, req ctrl.Reques policyID = nil } else { - ctrl.Log.Info("Ignoring protocol as NBPolicy has restricted protocols", "protocol", protocol) + logger.Info("Ignoring protocol as NBPolicy has restricted protocols", "protocol", protocol) } } else if len(ports) == 0 && policyID == nil { - ctrl.Log.Info("0 ports found for protocol in policy", "namespace", req.Namespace, "name", req.Name, "protocol", protocol) + logger.Info("0 ports found for protocol in policy", "protocol", protocol) continue } else if len(destGroups) == 0 && policyID == nil { - ctrl.Log.Info("no destinations found for protocol in policy", "namespace", req.Namespace, "name", req.Name, "protocol", protocol) + logger.Info("no destinations found for protocol in policy", "protocol", protocol) continue } else if len(sourceGroups) == 0 && policyID == nil { - ctrl.Log.Info("no sources found for protocol in policy", "namespace", req.Namespace, "name", req.Name, "protocol", protocol) + logger.Info("no sources found for protocol in policy", "protocol", protocol) continue } else if len(ports) == 0 || len(destGroups) == 0 || len(sourceGroups) == 0 { // Delete policy - ctrl.Log.Info("Deleting policy", "namespace", req.Namespace, "name", req.Name, "protocol", protocol) + logger.Info("Deleting policy", "protocol", protocol) err := r.netbird.Policies.Delete(ctx, *policyID) if err != nil && !strings.Contains(err.Error(), "not found") { nbPolicy.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("Error deleting policy: %v", err)) @@ -291,9 +290,9 @@ func (r *NBPolicyReconciler) handlePolicies(ctx context.Context, req ctrl.Reques var err error if policyID == nil { - policyID, err = r.createPolicy(ctx, req, nbPolicy, protocol, sourceGroups, destGroups, stringPorts) + policyID, err = r.createPolicy(ctx, nbPolicy, protocol, sourceGroups, destGroups, stringPorts, logger) } else { - policyID, requeue, err = r.updatePolicy(ctx, req, policyID, nbPolicy, protocol, sourceGroups, destGroups, stringPorts) + policyID, requeue, err = r.updatePolicy(ctx, policyID, nbPolicy, protocol, sourceGroups, destGroups, stringPorts, logger) } if err != nil { return requeue, err @@ -306,7 +305,7 @@ func (r *NBPolicyReconciler) handlePolicies(ctx context.Context, req ctrl.Reques case "udp": nbPolicy.Status.UDPPolicyID = policyID default: - ctrl.Log.Error(errKubernetesAPI, "Unknown protocol", "namespace", req.Namespace, "name", req.Name, "protocol", protocol) + logger.Error(errKubernetesAPI, "Unknown protocol", "protocol", protocol) nbPolicy.Status.Conditions = netbirdiov1.NBConditionFalse("ConfigError", fmt.Sprintf("Unknown protocol: %s", protocol)) return requeue, errUnknownProtocol } @@ -315,7 +314,7 @@ func (r *NBPolicyReconciler) handlePolicies(ctx context.Context, req ctrl.Reques return requeue, nil } -func (r *NBPolicyReconciler) handleDelete(ctx context.Context, req ctrl.Request, nbPolicy netbirdiov1.NBPolicy) error { +func (r *NBPolicyReconciler) handleDelete(ctx context.Context, nbPolicy netbirdiov1.NBPolicy, logger logr.Logger) error { if nbPolicy.Status.TCPPolicyID != nil { err := r.netbird.Policies.Delete(ctx, *nbPolicy.Status.TCPPolicyID) if err != nil { @@ -325,7 +324,7 @@ func (r *NBPolicyReconciler) handleDelete(ctx context.Context, req ctrl.Request, } if nbPolicy.Status.UDPPolicyID != nil { err := r.netbird.Policies.Delete(ctx, *nbPolicy.Status.UDPPolicyID) - if err != nil { + if err != nil && !strings.Contains("not found", err.Error()) { return err } nbPolicy.Status.UDPPolicyID = nil @@ -334,17 +333,17 @@ func (r *NBPolicyReconciler) handleDelete(ctx context.Context, req ctrl.Request, nbPolicy.Finalizers = util.Without(nbPolicy.Finalizers, "netbird.io/cleanup") err := r.Client.Update(ctx, &nbPolicy) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "Error updating NBPolicy", "namespace", req.Namespace, "name", req.Name, "err", err) + logger.Error(errKubernetesAPI, "Error updating NBPolicy", "err", err) return err } } return nil } -func (r *NBPolicyReconciler) groupNamesToIDs(ctx context.Context, req ctrl.Request, groupNames []string) ([]string, error) { +func (r *NBPolicyReconciler) groupNamesToIDs(ctx context.Context, groupNames []string, logger logr.Logger) ([]string, error) { groups, err := r.netbird.Groups.List(ctx) if err != nil { - ctrl.Log.Error(errNetBirdAPI, "Error listing Groups", "namespace", req.Namespace, "name", req.Name, "err", err) + logger.Error(errNetBirdAPI, "Error listing Groups", "err", err) return nil, err } diff --git a/internal/controller/nbresource_controller.go b/internal/controller/nbresource_controller.go index 2c8b8c0..cf15e3b 100644 --- a/internal/controller/nbresource_controller.go +++ b/internal/controller/nbresource_controller.go @@ -13,8 +13,8 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/log" + "github.com/go-logr/logr" netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" "github.com/netbirdio/kubernetes-operator/internal/util" netbird "github.com/netbirdio/netbird/management/client/rest" @@ -33,14 +33,14 @@ type NBResourceReconciler struct { // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. func (r *NBResourceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, err error) { - _ = log.FromContext(ctx) - ctrl.Log.Info("NBResource: Reconciling", "namespace", req.Namespace, "name", req.Name) + logger := ctrl.Log.WithName("NBResource").WithValues("namespace", req.Namespace, "name", req.Name) + logger.Info("Reconciling NBResource") nbResource := &netbirdiov1.NBResource{} err = r.Client.Get(ctx, req.NamespacedName, nbResource) if err != nil { if !errors.IsNotFound(err) { - ctrl.Log.Error(errKubernetesAPI, "error getting NBResource", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error getting NBResource", "err", err) } return ctrl.Result{RequeueAfter: defaultRequeueAfter}, nil } @@ -63,16 +63,16 @@ func (r *NBResourceReconciler) Reconcile(ctx context.Context, req ctrl.Request) if len(nbResource.Finalizers) == 0 { return ctrl.Result{}, nil } - return ctrl.Result{}, r.handleDelete(ctx, req, nbResource) + return ctrl.Result{}, r.handleDelete(ctx, req, nbResource, logger) } - groupIDs, result, err := r.handleGroups(ctx, req, nbResource) + groupIDs, result, err := r.handleGroups(ctx, req, nbResource, logger) if result != nil { nbResource.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("Error occurred handling groups: %v", err)) return *result, err } - resource, err := r.handleNetBirdResource(ctx, req, nbResource, groupIDs) + resource, err := r.handleNetBirdResource(ctx, nbResource, groupIDs, logger) if err != nil { nbResource.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("Error occurred handling NetBird Network Resource: %v", err)) return ctrl.Result{}, err @@ -83,13 +83,13 @@ func (r *NBResourceReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{Requeue: true}, nil } - err = r.handleGroupUpdate(ctx, req, nbResource, groupIDs, resource) + err = r.handleGroupUpdate(ctx, nbResource, groupIDs, resource, logger) if err != nil { nbResource.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("Error occurred handling groups: %v", err)) return ctrl.Result{}, err } - err = r.handlePolicy(ctx, req, nbResource, groupIDs) + err = r.handlePolicy(ctx, req, nbResource, groupIDs, logger) if err != nil { nbResource.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("Error occurred handling policy changes: %v", err)) } @@ -99,7 +99,7 @@ func (r *NBResourceReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, nil } -func (r *NBResourceReconciler) handlePolicy(ctx context.Context, req ctrl.Request, nbResource *netbirdiov1.NBResource, groupIDs []string) error { +func (r *NBResourceReconciler) handlePolicy(ctx context.Context, req ctrl.Request, nbResource *netbirdiov1.NBResource, groupIDs []string, logger logr.Logger) error { if nbResource.Status.PolicyName == nil && nbResource.Spec.PolicyName == "" { return nil } @@ -111,7 +111,7 @@ func (r *NBResourceReconciler) handlePolicy(ctx context.Context, req ctrl.Reques nbResource.Status.PolicyName = nil err := r.Client.Get(ctx, types.NamespacedName{Name: *nbResource.Status.PolicyName}, &nbPolicy) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error getting NBPolicy", "err", err, "namespace", req.Namespace, "name", req.Name, "policyName", nbResource.Spec.PolicyName) + logger.Error(errKubernetesAPI, "error getting NBPolicy", "err", err, "policyName", nbResource.Spec.PolicyName) return err } if util.Contains(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String()) { @@ -122,7 +122,7 @@ func (r *NBResourceReconciler) handlePolicy(ctx context.Context, req ctrl.Reques } else { err := r.Client.Get(ctx, types.NamespacedName{Name: nbResource.Spec.PolicyName}, &nbPolicy) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error getting NBPolicy", "err", err, "namespace", req.Namespace, "name", req.Name, "policyName", nbResource.Spec.PolicyName) + logger.Error(errKubernetesAPI, "error getting NBPolicy", "err", err, "policyName", nbResource.Spec.PolicyName) return err } @@ -158,7 +158,7 @@ func (r *NBResourceReconciler) handlePolicy(ctx context.Context, req ctrl.Reques if updatePolicyStatus { err := r.Client.Status().Update(ctx, &nbPolicy) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error updating NBPolicy", "err", err, "namespace", req.Namespace, "name", req.Name, "policyName", nbResource.Spec.PolicyName) + logger.Error(errKubernetesAPI, "error updating NBPolicy", "err", err, "policyName", nbResource.Spec.PolicyName) return err } } @@ -166,7 +166,7 @@ func (r *NBResourceReconciler) handlePolicy(ctx context.Context, req ctrl.Reques return nil } -func (r *NBResourceReconciler) handleGroupUpdate(ctx context.Context, req ctrl.Request, nbResource *netbirdiov1.NBResource, groupIDs []string, resource *api.NetworkResource) error { +func (r *NBResourceReconciler) handleGroupUpdate(ctx context.Context, nbResource *netbirdiov1.NBResource, groupIDs []string, resource *api.NetworkResource, logger logr.Logger) error { // Handle possible updated group IDs groupIDMap := make(map[string]interface{}) for _, g := range groupIDs { @@ -190,7 +190,7 @@ func (r *NBResourceReconciler) handleGroupUpdate(ctx context.Context, req ctrl.R }) if err != nil { - ctrl.Log.Error(errNetBirdAPI, "error updating resource", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errNetBirdAPI, "error updating resource", "err", err) return err } } @@ -198,13 +198,13 @@ func (r *NBResourceReconciler) handleGroupUpdate(ctx context.Context, req ctrl.R return nil } -func (r *NBResourceReconciler) handleNetBirdResource(ctx context.Context, req ctrl.Request, nbResource *netbirdiov1.NBResource, groupIDs []string) (*api.NetworkResource, error) { +func (r *NBResourceReconciler) handleNetBirdResource(ctx context.Context, nbResource *netbirdiov1.NBResource, groupIDs []string, logger logr.Logger) (*api.NetworkResource, error) { var resource *api.NetworkResource var err error if nbResource.Status.NetworkResourceID != nil { resource, err = r.netbird.Networks.Resources(nbResource.Spec.NetworkID).Get(ctx, *nbResource.Status.NetworkResourceID) if err != nil && !strings.Contains(err.Error(), "not found") { - ctrl.Log.Error(errNetBirdAPI, "error getting network resource", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errNetBirdAPI, "error getting network resource", "err", err) return nil, err } } @@ -218,7 +218,7 @@ func (r *NBResourceReconciler) handleNetBirdResource(ctx context.Context, req ct }) if err != nil { - ctrl.Log.Error(errNetBirdAPI, "error creating resource", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errNetBirdAPI, "error creating resource", "err", err) return nil, err } @@ -255,7 +255,7 @@ func (r *NBResourceReconciler) handleNetBirdResource(ctx context.Context, req ct return resource, nil } -func (r *NBResourceReconciler) handleGroups(ctx context.Context, req ctrl.Request, nbResource *netbirdiov1.NBResource) ([]string, *ctrl.Result, error) { +func (r *NBResourceReconciler) handleGroups(ctx context.Context, req ctrl.Request, nbResource *netbirdiov1.NBResource, logger logr.Logger) ([]string, *ctrl.Result, error) { var groupIDs []string for _, groupName := range nbResource.Spec.Groups { @@ -264,7 +264,7 @@ func (r *NBResourceReconciler) handleGroups(ctx context.Context, req ctrl.Reques groupNameRFC = strings.ReplaceAll(groupNameRFC, " ", "-") err := r.Client.Get(ctx, types.NamespacedName{Namespace: req.Namespace, Name: groupNameRFC}, &nbGroup) if err != nil && !errors.IsNotFound(err) { - ctrl.Log.Error(errKubernetesAPI, "error getting NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error getting NBGroup", "err", err) return nil, &ctrl.Result{}, err } else if errors.IsNotFound(err) { nbGroup = netbirdiov1.NBGroup{ @@ -289,7 +289,7 @@ func (r *NBResourceReconciler) handleGroups(ctx context.Context, req ctrl.Reques err = r.Client.Create(ctx, &nbGroup) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error creating NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error creating NBGroup", "err", err) return nil, &ctrl.Result{}, err } @@ -313,7 +313,7 @@ func (r *NBResourceReconciler) handleGroups(ctx context.Context, req ctrl.Reques err = r.Client.Update(ctx, &nbGroup) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error updating NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error updating NBGroup", "err", err) return nil, &ctrl.Result{}, err } } @@ -331,12 +331,12 @@ func (r *NBResourceReconciler) handleGroups(ctx context.Context, req ctrl.Reques return groupIDs, nil, nil } -func (r *NBResourceReconciler) handleDelete(ctx context.Context, req ctrl.Request, nbResource *netbirdiov1.NBResource) error { +func (r *NBResourceReconciler) handleDelete(ctx context.Context, req ctrl.Request, nbResource *netbirdiov1.NBResource, logger logr.Logger) error { if nbResource.Status.PolicyName != nil { var nbPolicy netbirdiov1.NBPolicy err := r.Client.Get(ctx, types.NamespacedName{Name: *nbResource.Status.PolicyName}, &nbPolicy) if err != nil && !errors.IsNotFound(err) { - ctrl.Log.Error(errKubernetesAPI, "error getting NBPolicy", "err", err, "namespace", req.Namespace, "name", req.Name, "policyName", nbResource.Spec.PolicyName) + logger.Error(errKubernetesAPI, "error getting NBPolicy", "err", err, "policyName", nbResource.Spec.PolicyName) return err } @@ -353,7 +353,7 @@ func (r *NBResourceReconciler) handleDelete(ctx context.Context, req ctrl.Reques if nbResource.Status.NetworkResourceID != nil { err := r.netbird.Networks.Resources(nbResource.Spec.NetworkID).Delete(ctx, *nbResource.Status.NetworkResourceID) if err != nil && !strings.Contains(err.Error(), "not found") { - ctrl.Log.Error(errNetBirdAPI, "error deleting resource", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errNetBirdAPI, "error deleting resource", "err", err) return err } @@ -363,7 +363,7 @@ func (r *NBResourceReconciler) handleDelete(ctx context.Context, req ctrl.Reques nbGroupList := netbirdiov1.NBGroupList{} err := r.Client.List(ctx, &nbGroupList, &client.ListOptions{Namespace: req.Namespace}) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error listing NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error listing NBGroup", "err", err) return err } @@ -372,7 +372,7 @@ func (r *NBResourceReconciler) handleDelete(ctx context.Context, req ctrl.Reques g.Finalizers = util.Without(g.Finalizers, "netbird.io/resource-cleanup") err = r.Client.Update(ctx, &g) if err != nil && !errors.IsNotFound(err) { - ctrl.Log.Error(errKubernetesAPI, "error updating NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error updating NBGroup", "err", err) return err } } @@ -381,7 +381,7 @@ func (r *NBResourceReconciler) handleDelete(ctx context.Context, req ctrl.Reques nbResource.Finalizers = nil err = r.Client.Update(ctx, nbResource) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error updating NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error updating NBGroup", "err", err) return err } diff --git a/internal/controller/nbroutingpeer_controller.go b/internal/controller/nbroutingpeer_controller.go index e59183a..7dbb7c1 100644 --- a/internal/controller/nbroutingpeer_controller.go +++ b/internal/controller/nbroutingpeer_controller.go @@ -14,8 +14,8 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/log" + "github.com/go-logr/logr" netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" "github.com/netbirdio/kubernetes-operator/internal/util" netbird "github.com/netbirdio/netbird/management/client/rest" @@ -37,15 +37,14 @@ type NBRoutingPeerReconciler struct { // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. func (r *NBRoutingPeerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, err error) { - _ = log.FromContext(ctx) - - ctrl.Log.Info("NBRoutingPeer: Reconciling", "namespace", req.Namespace, "name", req.Name) + logger := ctrl.Log.WithName("NBRoutingPeer").WithValues("namespace", req.Namespace, "name", req.Name) + logger.Info("Reconciling NBRoutingPeer") nbrp := &netbirdiov1.NBRoutingPeer{} err = r.Get(ctx, req.NamespacedName, nbrp) if err != nil { if !errors.IsNotFound(err) { - ctrl.Log.Error(errKubernetesAPI, "error getting NBRoutingPeer", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error getting NBRoutingPeer", "err", err) } return ctrl.Result{RequeueAfter: defaultRequeueAfter}, nil } @@ -58,7 +57,7 @@ func (r *NBRoutingPeerReconciler) Reconcile(ctx context.Context, req ctrl.Reques !util.Equivalent(originalNBRP.Status.Conditions, nbrp.Status.Conditions) { err = r.Client.Status().Update(ctx, nbrp) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error updating NBRoutingPeer Status", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error updating NBRoutingPeer Status", "err", err) } } if !res.Requeue && res.RequeueAfter == 0 { @@ -70,35 +69,35 @@ func (r *NBRoutingPeerReconciler) Reconcile(ctx context.Context, req ctrl.Reques if len(nbrp.Finalizers) == 0 { return ctrl.Result{}, nil } - return r.handleDelete(ctx, req, nbrp) + return r.handleDelete(ctx, req, nbrp, logger) } - ctrl.Log.Info("NBRoutingPeer: Checking network", "namespace", req.Namespace, "name", req.Name) - err = r.handleNetwork(ctx, req, nbrp) + logger.Info("NBRoutingPeer: Checking network") + err = r.handleNetwork(ctx, req, nbrp, logger) if err != nil { return ctrl.Result{}, err } - ctrl.Log.Info("NBRoutingPeer: Checking groups", "namespace", req.Namespace, "name", req.Name) - nbGroup, result, err := r.handleGroup(ctx, req, nbrp) + logger.Info("NBRoutingPeer: Checking groups") + nbGroup, result, err := r.handleGroup(ctx, req, nbrp, logger) if nbGroup == nil { return *result, err } - ctrl.Log.Info("NBRoutingPeer: Checking setup keys", "namespace", req.Namespace, "name", req.Name) - result, err = r.handleSetupKey(ctx, req, nbrp, *nbGroup) + logger.Info("NBRoutingPeer: Checking setup keys") + result, err = r.handleSetupKey(ctx, req, nbrp, *nbGroup, logger) if result != nil { return *result, err } - ctrl.Log.Info("NBRoutingPeer: Checking network router", "namespace", req.Namespace, "name", req.Name) - err = r.handleRouter(ctx, req, nbrp, *nbGroup) + logger.Info("NBRoutingPeer: Checking network router") + err = r.handleRouter(ctx, req, nbrp, *nbGroup, logger) if err != nil { return ctrl.Result{}, err } - ctrl.Log.Info("NBRoutingPeer: Checking deployment", "namespace", req.Namespace, "name", req.Name) - err = r.handleDeployment(ctx, req, nbrp) + logger.Info("NBRoutingPeer: Checking deployment") + err = r.handleDeployment(ctx, req, nbrp, logger) if err != nil { return ctrl.Result{}, err } @@ -107,11 +106,11 @@ func (r *NBRoutingPeerReconciler) Reconcile(ctx context.Context, req ctrl.Reques return ctrl.Result{}, nil } -func (r *NBRoutingPeerReconciler) handleDeployment(ctx context.Context, req ctrl.Request, nbrp *netbirdiov1.NBRoutingPeer) error { +func (r *NBRoutingPeerReconciler) handleDeployment(ctx context.Context, req ctrl.Request, nbrp *netbirdiov1.NBRoutingPeer, logger logr.Logger) error { routingPeerDeployment := appsv1.Deployment{} err := r.Client.Get(ctx, req.NamespacedName, &routingPeerDeployment) if err != nil && !errors.IsNotFound(err) { - ctrl.Log.Error(errKubernetesAPI, "error getting Deployment", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error getting Deployment", "err", err) nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("error getting Deployment: %v", err)) return err } @@ -188,7 +187,7 @@ func (r *NBRoutingPeerReconciler) handleDeployment(ctx context.Context, req ctrl err = r.Client.Create(ctx, &routingPeerDeployment) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error creating Deployment", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error creating Deployment", "err", err) nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("error creating Deployment: %v", err)) return err } @@ -260,7 +259,7 @@ func (r *NBRoutingPeerReconciler) handleDeployment(ctx context.Context, req ctrl } err = r.Client.Patch(ctx, updatedDeployment, patch) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error updating Deployment", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error updating Deployment", "err", err) nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("error updating Deployment: %v", err)) return err } @@ -269,18 +268,12 @@ func (r *NBRoutingPeerReconciler) handleDeployment(ctx context.Context, req ctrl return nil } -func (r *NBRoutingPeerReconciler) handleRouter(ctx context.Context, req ctrl.Request, nbrp *netbirdiov1.NBRoutingPeer, nbGroup netbirdiov1.NBGroup) error { - // Refresh nbrp - err := r.Client.Get(ctx, req.NamespacedName, nbrp) - if err != nil { - return err - } - +func (r *NBRoutingPeerReconciler) handleRouter(ctx context.Context, req ctrl.Request, nbrp *netbirdiov1.NBRoutingPeer, nbGroup netbirdiov1.NBGroup, logger logr.Logger) error { // Check NetworkRouter exists routers, err := r.netbird.Networks.Routers(*nbrp.Status.NetworkID).List(ctx) if err != nil { - ctrl.Log.Error(errNetBirdAPI, "error listing network routers", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errNetBirdAPI, "error listing network routers", "err", err) nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("error listing network routers: %v", err)) return err } @@ -297,7 +290,7 @@ func (r *NBRoutingPeerReconciler) handleRouter(ctx context.Context, req ctrl.Req }) if err != nil { - ctrl.Log.Error(errNetBirdAPI, "error creating network router", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errNetBirdAPI, "error creating network router", "err", err) nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("error creating network router: %v", err)) return err } @@ -314,7 +307,7 @@ func (r *NBRoutingPeerReconciler) handleRouter(ctx context.Context, req ctrl.Req }) if err != nil { - ctrl.Log.Error(errNetBirdAPI, "error updating network router", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errNetBirdAPI, "error updating network router", "err", err) nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("error updating network router: %v", err)) return err } @@ -324,7 +317,7 @@ func (r *NBRoutingPeerReconciler) handleRouter(ctx context.Context, req ctrl.Req return nil } -func (r *NBRoutingPeerReconciler) handleSetupKey(ctx context.Context, req ctrl.Request, nbrp *netbirdiov1.NBRoutingPeer, nbGroup netbirdiov1.NBGroup) (*ctrl.Result, error) { +func (r *NBRoutingPeerReconciler) handleSetupKey(ctx context.Context, req ctrl.Request, nbrp *netbirdiov1.NBRoutingPeer, nbGroup netbirdiov1.NBGroup, logger logr.Logger) (*ctrl.Result, error) { networkName := r.ClusterName if r.NamespacedNetworks { networkName += "-" + req.Namespace @@ -341,7 +334,7 @@ func (r *NBRoutingPeerReconciler) handleSetupKey(ctx context.Context, req ctrl.R }) if err != nil { - ctrl.Log.Error(errNetBirdAPI, "error creating setup key", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errNetBirdAPI, "error creating setup key", "err", err) nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("error creating setup key: %v", err)) return &ctrl.Result{}, err } @@ -372,7 +365,7 @@ func (r *NBRoutingPeerReconciler) handleSetupKey(ctx context.Context, req ctrl.R } if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error creating Secret", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error creating Secret", "err", err) nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("error creating secret: %v", err)) return &ctrl.Result{}, err } @@ -380,7 +373,7 @@ func (r *NBRoutingPeerReconciler) handleSetupKey(ctx context.Context, req ctrl.R // Check SetupKey is not revoked setupKey, err := r.netbird.SetupKeys.Get(ctx, *nbrp.Status.SetupKeyID) if err != nil && !strings.Contains(err.Error(), "not found") { - ctrl.Log.Error(errNetBirdAPI, "error getting setup key", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errNetBirdAPI, "error getting setup key", "err", err) nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("error getting setup key: %v", err)) return &ctrl.Result{}, err } @@ -395,7 +388,7 @@ func (r *NBRoutingPeerReconciler) handleSetupKey(ctx context.Context, req ctrl.R skSecret := corev1.Secret{} err = r.Client.Get(ctx, req.NamespacedName, &skSecret) if err != nil && !errors.IsNotFound(err) { - ctrl.Log.Error(errKubernetesAPI, "error getting Secret", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error getting Secret", "err", err) nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("error getting secret: %v", err)) return &ctrl.Result{}, err } @@ -406,7 +399,7 @@ func (r *NBRoutingPeerReconciler) handleSetupKey(ctx context.Context, req ctrl.R err = r.netbird.SetupKeys.Delete(ctx, *nbrp.Status.SetupKeyID) if err != nil { - ctrl.Log.Error(errNetBirdAPI, "error deleting setup key", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errNetBirdAPI, "error deleting setup key", "err", err) nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("error deleting setup key: %v", err)) return &ctrl.Result{}, err } @@ -422,7 +415,7 @@ func (r *NBRoutingPeerReconciler) handleSetupKey(ctx context.Context, req ctrl.R return nil, nil } -func (r *NBRoutingPeerReconciler) handleGroup(ctx context.Context, req ctrl.Request, nbrp *netbirdiov1.NBRoutingPeer) (*netbirdiov1.NBGroup, *ctrl.Result, error) { +func (r *NBRoutingPeerReconciler) handleGroup(ctx context.Context, req ctrl.Request, nbrp *netbirdiov1.NBRoutingPeer, logger logr.Logger) (*netbirdiov1.NBGroup, *ctrl.Result, error) { networkName := r.ClusterName if r.NamespacedNetworks { networkName += "-" + req.Namespace @@ -432,7 +425,7 @@ func (r *NBRoutingPeerReconciler) handleGroup(ctx context.Context, req ctrl.Requ nbGroup := netbirdiov1.NBGroup{} err := r.Client.Get(ctx, req.NamespacedName, &nbGroup) if err != nil && !errors.IsNotFound(err) { - ctrl.Log.Error(errKubernetesAPI, "error getting NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error getting NBGroup", "err", err) nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("error getting NBGroup: %v", err)) return nil, &ctrl.Result{}, err } @@ -461,7 +454,7 @@ func (r *NBRoutingPeerReconciler) handleGroup(ctx context.Context, req ctrl.Requ err = r.Client.Create(ctx, &nbGroup) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error creating NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error creating NBGroup", "err", err) nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("error creating NBGroup: %v", err)) return nil, &ctrl.Result{}, err } @@ -478,7 +471,7 @@ func (r *NBRoutingPeerReconciler) handleGroup(ctx context.Context, req ctrl.Requ return &nbGroup, nil, nil } -func (r *NBRoutingPeerReconciler) handleNetwork(ctx context.Context, req ctrl.Request, nbrp *netbirdiov1.NBRoutingPeer) error { +func (r *NBRoutingPeerReconciler) handleNetwork(ctx context.Context, req ctrl.Request, nbrp *netbirdiov1.NBRoutingPeer, logger logr.Logger) error { networkName := r.ClusterName if r.NamespacedNetworks { networkName += "-" + req.Namespace @@ -488,14 +481,14 @@ func (r *NBRoutingPeerReconciler) handleNetwork(ctx context.Context, req ctrl.Re // Check if network exists networks, err := r.netbird.Networks.List(ctx) if err != nil { - ctrl.Log.Error(errNetBirdAPI, "error listing networks", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errNetBirdAPI, "error listing networks", "err", err) nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("error listing networks: %v", err)) return err } var network *api.Network for _, n := range networks { if n.Name == networkName { - ctrl.Log.Info("network already exists", "network-id", n.Id) + logger.Info("network already exists", "network-id", n.Id) network = &n } } @@ -503,13 +496,13 @@ func (r *NBRoutingPeerReconciler) handleNetwork(ctx context.Context, req ctrl.Re if network != nil { nbrp.Status.NetworkID = &network.Id } else { - ctrl.Log.Info("creating network", "name", networkName) + logger.Info("creating network", "name", networkName) network, err := r.netbird.Networks.Create(ctx, api.NetworkRequest{ Name: networkName, Description: &networkDescription, }) if err != nil { - ctrl.Log.Error(errNetBirdAPI, "error creating network", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errNetBirdAPI, "error creating network", "err", err) nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("error creating network: %v", err)) return err } @@ -520,38 +513,38 @@ func (r *NBRoutingPeerReconciler) handleNetwork(ctx context.Context, req ctrl.Re return nil } -func (r *NBRoutingPeerReconciler) handleDelete(ctx context.Context, req ctrl.Request, nbrp *netbirdiov1.NBRoutingPeer) (ctrl.Result, error) { +func (r *NBRoutingPeerReconciler) handleDelete(ctx context.Context, req ctrl.Request, nbrp *netbirdiov1.NBRoutingPeer, logger logr.Logger) (ctrl.Result, error) { nbDeployment := appsv1.Deployment{} err := r.Client.Get(ctx, req.NamespacedName, &nbDeployment) if err != nil && !errors.IsNotFound(err) { - ctrl.Log.Error(errKubernetesAPI, "error getting Deployment", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error getting Deployment", "err", err) return ctrl.Result{}, err } if err == nil { err = r.Client.Delete(ctx, &nbDeployment) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error deleting Deployment", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error deleting Deployment", "err", err) return ctrl.Result{}, err } } if nbrp.Status.SetupKeyID != nil { - ctrl.Log.Info("Deleting setup key", "id", *nbrp.Status.SetupKeyID) + logger.Info("Deleting setup key", "id", *nbrp.Status.SetupKeyID) err = r.netbird.SetupKeys.Delete(ctx, *nbrp.Status.SetupKeyID) if err != nil && !strings.Contains(err.Error(), "not found") { - ctrl.Log.Error(errNetBirdAPI, "error deleting setupKey", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errNetBirdAPI, "error deleting setupKey", "err", err) return ctrl.Result{}, err } setupKeyID := *nbrp.Status.SetupKeyID nbrp.Status.SetupKeyID = nil - ctrl.Log.Info("Setup key deleted", "id", setupKeyID) + logger.Info("Setup key deleted", "id", setupKeyID) } if nbrp.Status.RouterID != nil { err = r.netbird.Networks.Routers(*nbrp.Status.NetworkID).Delete(ctx, *nbrp.Status.RouterID) if err != nil && !strings.Contains(err.Error(), "not found") { - ctrl.Log.Error(errNetBirdAPI, "error deleting Network Router", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errNetBirdAPI, "error deleting Network Router", "err", err) return ctrl.Result{}, err } @@ -561,7 +554,7 @@ func (r *NBRoutingPeerReconciler) handleDelete(ctx context.Context, req ctrl.Req nbGroup := netbirdiov1.NBGroup{} err = r.Client.Get(ctx, req.NamespacedName, &nbGroup) if err != nil && !errors.IsNotFound(err) { - ctrl.Log.Error(errKubernetesAPI, "error getting NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error getting NBGroup", "err", err) return ctrl.Result{}, err } @@ -569,26 +562,26 @@ func (r *NBRoutingPeerReconciler) handleDelete(ctx context.Context, req ctrl.Req nbResourceList := netbirdiov1.NBResourceList{} err = r.Client.List(ctx, &nbResourceList) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error listing NBResource", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error listing NBResource", "err", err) return ctrl.Result{}, err } for _, nbrs := range nbResourceList.Items { if nbrs.Spec.NetworkID == *nbrp.Status.NetworkID { - ctrl.Log.Info("Deleting NBResource", "namespace", nbrs.Namespace, "name", nbrs.Name) + logger.Info("Deleting NBResource", "namespace", nbrs.Namespace, "name", nbrs.Name) err = r.Client.Delete(ctx, &nbrs) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error deleting NBResource", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error deleting NBResource", "err", err) return ctrl.Result{}, err } } } if len(nbResourceList.Items) == 0 { - ctrl.Log.Info("Deleting NetBird Network", "id", *nbrp.Status.NetworkID) + logger.Info("Deleting NetBird Network", "id", *nbrp.Status.NetworkID) err = r.netbird.Networks.Delete(ctx, *nbrp.Status.NetworkID) if err != nil && !strings.Contains(err.Error(), "not found") { - ctrl.Log.Error(errNetBirdAPI, "error deleting Network", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errNetBirdAPI, "error deleting Network", "err", err) return ctrl.Result{}, err } @@ -598,10 +591,10 @@ func (r *NBRoutingPeerReconciler) handleDelete(ctx context.Context, req ctrl.Req if nbGroup.Spec.Name != "" && util.Contains(nbGroup.Finalizers, "netbird.io/routing-peer-cleanup") { nbGroup.Finalizers = util.Without(nbGroup.Finalizers, "netbird.io/routing-peer-cleanup") - ctrl.Log.Info("Removing netbird.io/routing-peer-cleanup finalizer NBGroup", "namespace", nbGroup.Namespace, "name", nbGroup.Name) + logger.Info("Removing netbird.io/routing-peer-cleanup finalizer NBGroup", "namespace", nbGroup.Namespace, "name", nbGroup.Name) err = r.Client.Update(ctx, &nbGroup) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error deleting NBGroup", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error deleting NBGroup", "err", err) return ctrl.Result{}, err } } @@ -611,11 +604,11 @@ func (r *NBRoutingPeerReconciler) handleDelete(ctx context.Context, req ctrl.Req } if len(nbrp.Finalizers) > 0 { - ctrl.Log.Info("Removing finalizers", "namespace", nbrp.Namespace, "name", nbrp.Name) + logger.Info("Removing finalizers", "namespace", nbrp.Namespace, "name", nbrp.Name) nbrp.Finalizers = nil err = r.Client.Update(ctx, nbrp) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error updating NBRoutingPeer finalizers", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error updating NBRoutingPeer finalizers", "err", err) return ctrl.Result{}, err } } diff --git a/internal/controller/nbsetupkey_controller.go b/internal/controller/nbsetupkey_controller.go index f1b790b..f999370 100644 --- a/internal/controller/nbsetupkey_controller.go +++ b/internal/controller/nbsetupkey_controller.go @@ -29,7 +29,6 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" @@ -45,17 +44,18 @@ type NBSetupKeyReconciler struct { // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. func (r *NBSetupKeyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = log.FromContext(ctx) + logger := ctrl.Log.WithName("NBSetupKey").WithValues("namespace", req.Namespace, "name", req.Name) + logger.Info("Reconciling NBSetupKey") nbSetupKey := netbirdiov1.NBSetupKey{} err := r.Get(ctx, req.NamespacedName, &nbSetupKey) if err != nil { - ctrl.Log.Error(fmt.Errorf("internalError"), "error getting NBSetupKey", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(fmt.Errorf("internalError"), "error getting NBSetupKey", "err", err) return ctrl.Result{}, nil } if nbSetupKey.Spec.SecretKeyRef.Name == "" || nbSetupKey.Spec.SecretKeyRef.Key == "" { - ctrl.Log.Error(fmt.Errorf("invalid NBSetupKey"), "secretKeyRef must contain both secret name and secret key", "namespace", req.Namespace, "name", req.Name) + logger.Error(fmt.Errorf("invalid NBSetupKey"), "secretKeyRef must contain both secret name and secret key") return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{ Conditions: []netbirdiov1.NBCondition{ { @@ -82,10 +82,10 @@ func (r *NBSetupKeyReconciler) Reconcile(ctx context.Context, req ctrl.Request) err = r.Get(ctx, types.NamespacedName{Namespace: nbSetupKey.Namespace, Name: nbSetupKey.Spec.SecretKeyRef.Name}, &secret) if err != nil { if !errors.IsNotFound(err) { - ctrl.Log.Error(fmt.Errorf("internalError"), "error getting secret", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(fmt.Errorf("internalError"), "error getting secret", "err", err) return ctrl.Result{}, err } - ctrl.Log.Error(fmt.Errorf("invalid NBSetupKey"), "secret referenced not found", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(fmt.Errorf("invalid NBSetupKey"), "secret referenced not found", "err", err) return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{Conditions: []netbirdiov1.NBCondition{{ Type: netbirdiov1.NBSetupKeyReady, Status: corev1.ConditionFalse, @@ -97,7 +97,7 @@ func (r *NBSetupKeyReconciler) Reconcile(ctx context.Context, req ctrl.Request) uuidBytes, ok := secret.Data[nbSetupKey.Spec.SecretKeyRef.Key] if !ok { - ctrl.Log.Error(fmt.Errorf("invalid NBSetupKey"), "secret key referenced not found", "namespace", req.Namespace, "name", req.Name) + logger.Error(fmt.Errorf("invalid NBSetupKey"), "secret key referenced not found") return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{Conditions: []netbirdiov1.NBCondition{{ Type: netbirdiov1.NBSetupKeyReady, Status: corev1.ConditionFalse, @@ -109,7 +109,7 @@ func (r *NBSetupKeyReconciler) Reconcile(ctx context.Context, req ctrl.Request) _, err = uuid.Parse(string(uuidBytes)) if err != nil { - ctrl.Log.Error(fmt.Errorf("invalid NBSetupKey"), "setupKey is not a valid UUID", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(fmt.Errorf("invalid NBSetupKey"), "setupKey is not a valid UUID", "err", err) return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{Conditions: []netbirdiov1.NBCondition{{ Type: netbirdiov1.NBSetupKeyReady, Status: corev1.ConditionFalse, diff --git a/internal/controller/service_controller.go b/internal/controller/service_controller.go index a7b1e71..247f063 100644 --- a/internal/controller/service_controller.go +++ b/internal/controller/service_controller.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/go-logr/logr" netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" "github.com/netbirdio/kubernetes-operator/internal/util" corev1 "k8s.io/api/core/v1" @@ -16,7 +17,6 @@ import ( "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" ) // ServiceReconciler reconciles a Service object @@ -46,15 +46,14 @@ var ( // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. func (r *ServiceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = log.FromContext(ctx) - - ctrl.Log.Info("Service: Reconciling", "namespace", req.Namespace, "name", req.Name) + logger := ctrl.Log.WithName("Service").WithValues("namespace", req.Namespace, "name", req.Name) + logger.Info("Reconciling Service") svc := corev1.Service{} err := r.Get(ctx, req.NamespacedName, &svc) if err != nil { if !errors.IsNotFound(err) { - ctrl.Log.Error(errKubernetesAPI, "error getting Service", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error getting Service", "err", err) } return ctrl.Result{}, nil } @@ -65,24 +64,24 @@ func (r *ServiceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct shouldExpose = shouldExpose && svc.DeletionTimestamp == nil if shouldExpose { - return r.exposeService(ctx, req, svc) + return r.exposeService(ctx, req, svc, logger) } - return r.hideService(ctx, req, svc) + return r.hideService(ctx, req, svc, logger) } -func (r *ServiceReconciler) hideService(ctx context.Context, req ctrl.Request, svc corev1.Service) (ctrl.Result, error) { +func (r *ServiceReconciler) hideService(ctx context.Context, req ctrl.Request, svc corev1.Service, logger logr.Logger) (ctrl.Result, error) { var nbResource netbirdiov1.NBResource err := r.Client.Get(ctx, req.NamespacedName, &nbResource) if err != nil && !errors.IsNotFound(err) { - ctrl.Log.Error(errKubernetesAPI, "error getting NBResource", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error getting NBResource", "err", err) return ctrl.Result{}, err } if !errors.IsNotFound(err) { err = r.Client.Delete(ctx, &nbResource) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error deleting NBResource", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error deleting NBResource", "err", err) return ctrl.Result{}, err } } @@ -91,7 +90,7 @@ func (r *ServiceReconciler) hideService(ctx context.Context, req ctrl.Request, s svc.Finalizers = util.Without(svc.Finalizers, "netbird.io/cleanup") err := r.Client.Update(ctx, &svc) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error updating Service", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error updating Service", "err", err) return ctrl.Result{}, err } } @@ -99,7 +98,7 @@ func (r *ServiceReconciler) hideService(ctx context.Context, req ctrl.Request, s return ctrl.Result{}, nil } -func (r *ServiceReconciler) exposeService(ctx context.Context, req ctrl.Request, svc corev1.Service) (ctrl.Result, error) { +func (r *ServiceReconciler) exposeService(ctx context.Context, req ctrl.Request, svc corev1.Service, logger logr.Logger) (ctrl.Result, error) { routerNamespace := r.ControllerNamespace if r.NamespacedNetworks { routerNamespace = req.Namespace @@ -109,7 +108,7 @@ func (r *ServiceReconciler) exposeService(ctx context.Context, req ctrl.Request, svc.Finalizers = append(svc.Finalizers, "netbird.io/cleanup") err := r.Client.Update(ctx, &svc) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error updating Service", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error updating Service", "err", err) return ctrl.Result{}, err } } @@ -118,7 +117,7 @@ func (r *ServiceReconciler) exposeService(ctx context.Context, req ctrl.Request, // Check if NBRoutingPeer exists err := r.Client.Get(ctx, types.NamespacedName{Namespace: routerNamespace, Name: "router"}, &routingPeer) if err != nil && !errors.IsNotFound(err) { - ctrl.Log.Error(errKubernetesAPI, "error getting NBRoutingPeer", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error getting NBRoutingPeer", "err", err) return ctrl.Result{}, err } @@ -135,24 +134,24 @@ func (r *ServiceReconciler) exposeService(ctx context.Context, req ctrl.Request, err = r.Client.Create(ctx, &routingPeer) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error creating NBRoutingPeer", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error creating NBRoutingPeer", "err", err) return ctrl.Result{}, err } - ctrl.Log.Info("Network not available") + logger.Info("Network not available") // Requeue to make sure network is created return ctrl.Result{RequeueAfter: 5 * time.Second}, nil } if routingPeer.Status.NetworkID == nil { - ctrl.Log.Info("Network not available") + logger.Info("Network not available") return ctrl.Result{RequeueAfter: 5 * time.Second}, nil } var nbResource netbirdiov1.NBResource err = r.Client.Get(ctx, req.NamespacedName, &nbResource) if err != nil && !errors.IsNotFound(err) { - ctrl.Log.Error(errKubernetesAPI, "error getting NBResource", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error getting NBResource", "err", err) return ctrl.Result{}, err } @@ -164,13 +163,13 @@ func (r *ServiceReconciler) exposeService(ctx context.Context, req ctrl.Request, if errors.IsNotFound(err) { err = r.Client.Create(ctx, &nbResource) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error creating NBResource", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error creating NBResource", "err", err) return ctrl.Result{}, err } } else { err = r.Client.Update(ctx, &nbResource) if err != nil { - ctrl.Log.Error(errKubernetesAPI, "error updating NBResource", "err", err, "namespace", req.Namespace, "name", req.Name) + logger.Error(errKubernetesAPI, "error updating NBResource", "err", err) return ctrl.Result{}, err } } From 00cf45bdf874e25a7f48afa8b90d3bfa9f2e391e Mon Sep 17 00:00:00 2001 From: M Essam Hamed Date: Sun, 2 Mar 2025 21:10:22 +0200 Subject: [PATCH 04/11] Update chart appVersion --- helm/kubernetes-operator/Chart.yaml | 2 +- internal/controller/nbroutingpeer_controller.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/helm/kubernetes-operator/Chart.yaml b/helm/kubernetes-operator/Chart.yaml index 58e239d..10e2eb3 100644 --- a/helm/kubernetes-operator/Chart.yaml +++ b/helm/kubernetes-operator/Chart.yaml @@ -3,4 +3,4 @@ name: kubernetes-operator description: A Helm chart for Kubernetes type: application version: 0.1.1 -appVersion: "v0.1.0" +appVersion: "v0.1.0-rc.3" diff --git a/internal/controller/nbroutingpeer_controller.go b/internal/controller/nbroutingpeer_controller.go index 7dbb7c1..c4a3923 100644 --- a/internal/controller/nbroutingpeer_controller.go +++ b/internal/controller/nbroutingpeer_controller.go @@ -91,7 +91,7 @@ func (r *NBRoutingPeerReconciler) Reconcile(ctx context.Context, req ctrl.Reques } logger.Info("NBRoutingPeer: Checking network router") - err = r.handleRouter(ctx, req, nbrp, *nbGroup, logger) + err = r.handleRouter(ctx, nbrp, *nbGroup, logger) if err != nil { return ctrl.Result{}, err } @@ -268,7 +268,7 @@ func (r *NBRoutingPeerReconciler) handleDeployment(ctx context.Context, req ctrl return nil } -func (r *NBRoutingPeerReconciler) handleRouter(ctx context.Context, req ctrl.Request, nbrp *netbirdiov1.NBRoutingPeer, nbGroup netbirdiov1.NBGroup, logger logr.Logger) error { +func (r *NBRoutingPeerReconciler) handleRouter(ctx context.Context, nbrp *netbirdiov1.NBRoutingPeer, nbGroup netbirdiov1.NBGroup, logger logr.Logger) error { // Check NetworkRouter exists routers, err := r.netbird.Networks.Routers(*nbrp.Status.NetworkID).List(ctx) From 4ad239b35a23caae15f10171b962fe92bc4d6475 Mon Sep 17 00:00:00 2001 From: M Essam Hamed Date: Sun, 2 Mar 2025 13:13:56 +0200 Subject: [PATCH 05/11] Add documentation --- README.md | 92 ++++---------- docs/usage.md | 112 ++++++++++++++++++ examples/ingress/exposed-nginx.yaml | 47 ++++++++ examples/ingress/values.yaml | 12 ++ examples/setup-keys/example.yaml | 49 ++++++++ helm/kubernetes-operator/Chart.yaml | 2 +- .../templates/_helpers.tpl | 2 +- .../templates/webhook.yaml | 2 +- helm/kubernetes-operator/values.yaml | 26 ++-- internal/controller/nbgroup_controller.go | 13 ++ internal/controller/nbpolicy_controller.go | 11 +- internal/controller/nbresource_controller.go | 13 ++ .../controller/nbroutingpeer_controller.go | 13 +- internal/controller/service_controller.go | 4 + 14 files changed, 311 insertions(+), 87 deletions(-) create mode 100644 docs/usage.md create mode 100644 examples/ingress/exposed-nginx.yaml create mode 100644 examples/ingress/values.yaml create mode 100644 examples/setup-keys/example.yaml diff --git a/README.md b/README.md index 3c46956..1c34577 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # NetBird Kubernetes Operator For easily provisioning access to Kubernetes resources using NetBird. +https://github.com/user-attachments/assets/5472a499-e63d-4301-a513-ad84cfe5ca7b + ## Description This operator enables easily provisioning NetBird access on kubernetes clusters, allowing users to access internal resources directly. @@ -8,94 +10,40 @@ This operator enables easily provisioning NetBird access on kubernetes clusters, ## Getting Started ### Prerequisites -- helm version 3+ +- (Recommended) helm version 3+ - kubectl version v1.11.3+. - Access to a Kubernetes v1.11.3+ cluster. -- (Optional for Helm chart installation) Cert Manager. - -### To Deploy on the cluster +- (Recommended) Cert Manager. -**Using the install.yaml** -```sh -kubectl create namespace netbird -kubectl apply -n netbird -f https://github.com/netbirdio/kubernetes-operator/releases/latest/manifests/install.yaml -``` +### Deployment +> [!NOTE] +> Helm Installation method is recommended due to automation of multiple settings within the deployment. -**Using the Helm Chart** +#### Using Helm +1. Add helm repository. ```sh helm repo add netbirdio https://netbirdio.github.io/kubernetes-operator -helm install -n netbird kubernetes-operator netbirdio/kubernetes-operator ``` +2. (Recommended) Install [cert-manager](https://cert-manager.io/docs/installation/#default-static-install). +1. (Recommended) Create a values.yaml file, check `helm show values netbirdio/kubernetes-operator` for more info. +1. Install using `helm install --create-namespace -f values.yaml -n netbird netbird-operator netbirdio/kubernetes-operator`. -For more options, check the default values by running -```sh -helm show values netbirdio/kubernetes-operator -``` +#### Using install.yaml -### To Uninstall -**Using install.yaml** +> [!IMPORTANT] +> install.yaml only includes a very basic template for deploying a stripped down version of kubernetes-operator. +> This excludes any and all configuration for ingress capabilities, and requires cert-manager to be installed. ```sh -kubectl delete -n netbird -f https://github.com/netbirdio/kubernetes-operator/releases/latest/manifests/install.yaml -kubectl delete namespace netbird -``` - -**Using helm** - -```sh -helm uninstall -n netbird kubernetes-operator +kubectl create namespace netbird +kubectl apply -n netbird -f https://raw.githubusercontent.com/netbirdio/kubernetes-operator/refs/heads/main/manifests/install.yaml ``` -### Provision pods with NetBird access - -1. Create a Setup Key in your [NetBird console](https://docs.netbird.io/how-to/register-machines-using-setup-keys#using-setup-keys). -1. Create a Secret object in the namespace where you need to provision NetBird access (secret name and field can be anything). -```yaml -apiVersion: v1 -stringData: - setupkey: EEEEEEEE-EEEE-EEEE-EEEE-EEEEEEEEEEEE -kind: Secret -metadata: - name: test -``` -1. Create an NBSetupKey object referring to your secret. -```yaml -apiVersion: netbird.io/v1 -kind: NBSetupKey -metadata: - name: test -spec: - # Optional, overrides management URL for this setupkey only - # defaults to https://api.netbird.io - managementURL: https://netbird.example.com - secretKeyRef: - name: test # Required - key: setupkey # Required -``` -1. Annotate the pods you need to inject NetBird into with `netbird.io/setup-key`. -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: deployment -spec: - selector: - matchLabels: - app: myapp - template: - metadata: - labels: - app: myapp - annotations: - netbird.io/setup-key: test # Must match the name of an NBSetupKey object in the same namespace - spec: - containers: - - image: yourimage - name: container +### Usage -``` +Checks [usage.md](docs/usage.md). ## Contributing diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 0000000..8e68da6 --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,112 @@ +# Usage + +## Provision pods with NetBird access + +1. Create a Setup Key in your [NetBird console](https://docs.netbird.io/how-to/register-machines-using-setup-keys#using-setup-keys). +1. Create a Secret object in the namespace where you need to provision NetBird access (secret name and field can be anything). +```yaml +apiVersion: v1 +stringData: + setupkey: EEEEEEEE-EEEE-EEEE-EEEE-EEEEEEEEEEEE +kind: Secret +metadata: + name: test +``` +1. Create an NBSetupKey object referring to your secret. +```yaml +apiVersion: netbird.io/v1 +kind: NBSetupKey +metadata: + name: test +spec: + # Optional, overrides management URL for this setupkey only + # defaults to https://api.netbird.io + managementURL: https://netbird.example.com + secretKeyRef: + name: test # Required + key: setupkey # Required +``` +1. Annotate the pods you need to inject NetBird into with `netbird.io/setup-key`. +```yaml +kind: Deployment +... +spec: +... + template: + metadata: + annotations: + netbird.io/setup-key: test # Must match the name of an NBSetupKey object in the same namespace +... + spec: + containers: +... +``` + +## Provisioning Networks (Ingress Functionality) + +### Granting controller access to NetBird Management + +> [!IMPORTANT] +> NetBird kubernetes operator generates configurations using NetBird API, editing or deleting these configurations in the NetBird console may cause temporary network disconnection until the operator reconciles the configuration. + +1. Create a Service User on your NetBird dashboard (Must be Admin). [Doc](https://docs.netbird.io/how-to/access-netbird-public-api#creating-a-service-user). +1. Create access token for the Service User (Must be Admin). [Doc](https://docs.netbird.io/how-to/access-netbird-public-api#creating-a-service-user). +1. Add access token to your helm values file under `netbirdAPI.key`. + 1. Alternatively, provision secret in the same namespace as the operator and set the key `NB_API_KEY` to the access token generated. + 1. Set `netbirdAPI.keyFromSecret` to the name of the secret created. +1. Set `ingress.enabled` to `true`. + 1. Optionally, to provision network immediately, set `ingress.router.enabled` to `true`. + 1. Optionally, to provision 1 network per kubernetes namespace, set `ingress.namespacedNetworks` to `true`. +1. Run `helm install` or `helm upgrade`. + +### Exposing a Service + +> [!IMPORTANT] +> Ingress DNS Resolution requires DNS Wildcard Routing to be enabled, and at least one DNS Nameserver configured for clients. + +|Annotation|Description|Default|Valid Values| +|---|---|---|---| +|`netbird.io/expose`|Expose service using NetBird Network Resource||(`null`, `true`)| +|`netbird.io/groups`|Comma-separated list of group names to assign to Network Resource|`{ClusterName}-{Namespace}-{Service}`|Any comma-separated list of strings.| +|`netbird.io/resource-name`|Network Resource name|`{Namespace}-{Service}`|Any valid network resource name, make sure they're unique!| +|`netbird.io/policy`|Name of NBPolicy to propagate service ports as destination.||Name of any NBPolicy resource| +|`netbird.io/policy-ports`|Narrow down exposed ports in policy, leave empty for all ports.||Comma-separated integer list, integers must be between 0-65535| +|`netbird.io/policy-protocol`|Narrow down protocol for use in policy, leave empty for all protocols.||(`tcp`,`udp`)| + +### Notes +* `netbird.io/expose` will interpret any string as true value, the only false value is `null`. +* The operator does **not** handle duplicate resource names within the same network, it is up to you to ensure resource names are unique within the same network. +* While the NetBird console will allow group names to contain commas, this is not allowed in `netbird.io/groups` annotation as commas are used as separators. +* If a group already exists on NetBird console with the same name, NetBird Operator will use that group ID instead of creating a new group. +* NetBird Operator will attempt to clean up any resources created, including groups created for resources. + * In case a group is used by resources that cannot be cleaned up by the operator, the operator will eventually ignore the group in NetBird. + * It's recommended to use unique groups per NetBird Operator installation to remove any possible conflicts. +* NetBird Operator does not validate service annotations on update, as this may cause unnecessary overhead on any Service update. + +### Managing Policies + +Simply add policies under `ingress.policies`, for example: +1. Add the following configuration in your `values.yaml` file. +```yaml +ingress: + policies: + default: + name: Kubernetes Default Policy # Required, name of policy in NetBird console + description: Default # Optional + sourceGroups: # Required, name of groups to assign as source in Policy. + - All + ports: # Optional, resources annotated 'netbird.io/policy=default' will append to this. + - 443 + protocols: # Optional, restricts protocols allowed to resources, defaults to ['tcp', 'udp']. + - tcp + - udp + bidirectional: true # Optional, defaults to true +``` +2. Reference policy in Services using `netbird.io/policy=default`, this will add relevant ports and destination groups to Policy. +3. (Optional) limit specific ports in exposed service by adding `netbird.io/policy-ports=443`. +4. (Optional) limit specific protocol in exposed service by adding `netbird.io/policy-protocol=tcp`. + +#### Notes +* Each NBPolicy will only create policies in NetBird console when information provided is enough to create one. If there are no services acting as destination, or specified services do not conform to the protocol(s) defined, policy will not be created. +* Each NBPolicy will create one Policy in NetBird console per protocol specified as long as protocol has destinations, this ensures better secured policies by separating ports for TCP and UDP. +* Policies currently do not support ICMP protocol, as ICMP is not supported in kubernetes services, and there are [no current plans to support it](https://discuss.kubernetes.io/t/icmp-support-for-kubernetes-service/21738). diff --git a/examples/ingress/exposed-nginx.yaml b/examples/ingress/exposed-nginx.yaml new file mode 100644 index 0000000..6e44d2a --- /dev/null +++ b/examples/ingress/exposed-nginx.yaml @@ -0,0 +1,47 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: test + name: test + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + app: test + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + labels: + app: test + spec: + containers: + - image: nginx + imagePullPolicy: Always + name: nginx +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + netbird.io/expose: "true" + netbird.io/policy: default + netbird.io/resource-name: nginx + labels: + app: test + name: test + namespace: default +spec: + ports: + - name: http + port: 80 + protocol: TCP + targetPort: 80 + selector: + app: test + type: ClusterIP diff --git a/examples/ingress/values.yaml b/examples/ingress/values.yaml new file mode 100644 index 0000000..1139206 --- /dev/null +++ b/examples/ingress/values.yaml @@ -0,0 +1,12 @@ +ingress: + enabled: true + router: + enabled: true + policies: + default: + name: Kubernetes Default Policy + sourceGroups: + - All + +netbirdAPI: + key: "nbp_m0LM9ZZvDUzFO0pY50iChDOTxJgKFM3DIqmZ" # Replace with valid NetBird Service Account token diff --git a/examples/setup-keys/example.yaml b/examples/setup-keys/example.yaml new file mode 100644 index 0000000..a2a8a8a --- /dev/null +++ b/examples/setup-keys/example.yaml @@ -0,0 +1,49 @@ +apiVersion: v1 +kind: Secret +metadata: + name: test + namespace: default +stringData: + SETUP_KEY: 50445ABC-8901-4050-8047-0A390658A79B # Replace with valid setup key +--- +apiVersion: netbird.io/v1 +kind: NBSetupKey +metadata: + name: test + namespace: default +spec: + secretKeyRef: + name: test + key: SETUP_KEY +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: test + name: test + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + app: test + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + labels: + app: test + annotations: + netbird.io/setup-key: test + spec: + containers: + - image: ubuntu + imagePullPolicy: Always + name: ubuntu + command: + - sleep + - inf diff --git a/helm/kubernetes-operator/Chart.yaml b/helm/kubernetes-operator/Chart.yaml index 10e2eb3..370c351 100644 --- a/helm/kubernetes-operator/Chart.yaml +++ b/helm/kubernetes-operator/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: kubernetes-operator description: A Helm chart for Kubernetes type: application -version: 0.1.1 +version: 0.1.2 appVersion: "v0.1.0-rc.3" diff --git a/helm/kubernetes-operator/templates/_helpers.tpl b/helm/kubernetes-operator/templates/_helpers.tpl index e80a9cf..2c98e59 100644 --- a/helm/kubernetes-operator/templates/_helpers.tpl +++ b/helm/kubernetes-operator/templates/_helpers.tpl @@ -92,7 +92,7 @@ caCert: {{ index $secret.data "ca.crt" }} clientCert: {{ index $secret.data "tls.crt" }} clientKey: {{ index $secret.data "tls.key" }} {{- else -}} -{{- $altNames := list (printf "%s.%s" $serviceName .Release.Namespace) (printf "%s.%s.svc" $serviceName .Release.Namespace) (printf "%s.%s.svc.%s" $serviceName .Release.Namespace .Values.webhook.cluster.dnsDomain) -}} +{{- $altNames := list (printf "%s.%s" $serviceName .Release.Namespace) (printf "%s.%s.svc" $serviceName .Release.Namespace) (printf "%s.%s.%s" $serviceName .Release.Namespace .Values.cluster.dns) -}} {{- $ca := genCA "kubernetes-operator-ca" 3650 -}} {{- $cert := genSignedCert (include "kubernetes-operator.fullname" .) nil $altNames 3650 $ca -}} caCert: {{ $ca.Cert | b64enc }} diff --git a/helm/kubernetes-operator/templates/webhook.yaml b/helm/kubernetes-operator/templates/webhook.yaml index 5ac8055..4ba96f3 100644 --- a/helm/kubernetes-operator/templates/webhook.yaml +++ b/helm/kubernetes-operator/templates/webhook.yaml @@ -235,7 +235,7 @@ metadata: spec: dnsNames: - {{ template "kubernetes-operator.webhookService" . }}.{{ .Release.Namespace }}.svc - - {{ template "kubernetes-operator.webhookService" . }}.{{ .Release.Namespace }}.svc.{{ .Values.webhook.cluster.dnsDomain }} + - {{ template "kubernetes-operator.webhookService" . }}.{{ .Release.Namespace }}.{{ .Values.cluster.dns }} issuerRef: kind: Issuer name: {{ template "kubernetes-operator.fullname" . }}-selfsigned-issuer diff --git a/helm/kubernetes-operator/values.yaml b/helm/kubernetes-operator/values.yaml index 24f2167..d512b2d 100644 --- a/helm/kubernetes-operator/values.yaml +++ b/helm/kubernetes-operator/values.yaml @@ -1,4 +1,6 @@ clusterSecretsPermissions: + # Required for NBSetupKey validation + # Required for Ingress functionality to create and validate secrets for routing peers allowAllSecrets: true webhook: @@ -7,22 +9,21 @@ webhook: port: 443 targetPort: 9443 - cluster: - # Cluster DNS domain (required for requesting TLS certificates) - dnsDomain: cluster.local - # TLS configuration for webhook + # Optional, unused if webhook.enableCertManager is set to true tls: {} - # Use cert-manager to provision webhook certificates + # Use cert-manager to provision webhook certificates (recommended) enableCertManager: true + # Narrow down validation and mutation webhooks namespaces namespaceSelectors: [] # - key: foo # operator: In # values: # - bar + # Narrow down validation and mutation webhooks objects objectSelector: matchExpressions: [] # - key: app.kubernetes.io/name @@ -130,9 +131,12 @@ operator: affinity: {} ingress: + # Enable ingress capabilities to expose services enabled: false + # Create router per namespace, useful for strict networking requirements namespacedNetworks: false router: + # Deploy routing peer(s) enabled: false # replicas: 3 # resources: @@ -146,8 +150,8 @@ ingress: # annotations: {} # nodeSelector: {} # tolerations: [] - # Only needed for namespacedNetworks - # namespaces: + # Only needed if namespacedNetworks is set to true + namespaces: {} # default: # replicas: 3 # resources: @@ -161,16 +165,20 @@ ingress: # annotations: {} # nodeSelector: {} # tolerations: [] + # NetBird Policies for use with exposed services policies: {} # default: # name: Kubernetes Default Policy # sourceGroups: # - All -cluster: {} +cluster: + # Cluster DNS name (used for webhooks certificates and for network resource DNS names) + dns: svc.cluster.local + # Cluster name (used for generating network and network resource names in NetBird) # name: kubernetes - # dns: svc.cluster.local netbirdAPI: {} + # NetBird Service Account Token # key: "nbp_m0LM9ZZvDUzFO0pY50iChDOTxJgKFM3DIqmZ" # keyFromSecret: "Secret name with NB_API_KEY=Service Account Token" \ No newline at end of file diff --git a/internal/controller/nbgroup_controller.go b/internal/controller/nbgroup_controller.go index 1de3bc0..a7d2b3a 100644 --- a/internal/controller/nbgroup_controller.go +++ b/internal/controller/nbgroup_controller.go @@ -28,6 +28,11 @@ type NBGroupReconciler struct { } const ( + // defaultRequeueAfter default requeue duration + // due to controller-runtime limitations, sync periods may reach up to 10 hours if no changes are detected + // in watched resources. + // This may cause issues when NetBird-side resources are out-of-sync and need to be reconciled, this is a temporary + // fix to this issue by syncing with NetBird more frequently. defaultRequeueAfter = 15 * time.Minute ) @@ -69,7 +74,9 @@ func (r *NBGroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (re return r.syncNetBirdGroup(ctx, &nbGroup, logger) } +// syncNetBirdGroup reconciliation logic for non-deleted objects. func (r *NBGroupReconciler) syncNetBirdGroup(ctx context.Context, nbGroup *netbirdiov1.NBGroup, logger logr.Logger) (ctrl.Result, error) { + // Get all NetBird groups to ensure no group duplication groups, err := r.netbird.Groups.List(ctx) if err != nil { logger.Error(errNetBirdAPI, "error listing groups", "err", err) @@ -81,6 +88,8 @@ func (r *NBGroupReconciler) syncNetBirdGroup(ctx context.Context, nbGroup *netbi group = &g } } + + // Create group if not exists, and update status.groupId if nbGroup.Status.GroupID == nil && group == nil { logger.Info("NBGroup: Creating group on NetBird", "name", nbGroup.Spec.Name) group, err := r.netbird.Groups.Create(ctx, api.GroupRequest{ @@ -118,6 +127,7 @@ func (r *NBGroupReconciler) syncNetBirdGroup(ctx context.Context, nbGroup *netbi } func (r *NBGroupReconciler) handleDelete(ctx context.Context, nbGroup netbirdiov1.NBGroup, logger logr.Logger) error { + // Group doesn't exist on NetBird, no need for cleanup if nbGroup.Status.GroupID == nil { nbGroup.Finalizers = util.Without(nbGroup.Finalizers, "netbird.io/group-cleanup") err := r.Client.Update(ctx, &nbGroup) @@ -160,6 +170,9 @@ func (r *NBGroupReconciler) handleDelete(ctx context.Context, nbGroup netbirdiov return nil } } + + // No other NBGroup with same name on the cluster + // This could be a group created by user elsewhere or some resources belonging to the group are still deleting. return err } diff --git a/internal/controller/nbpolicy_controller.go b/internal/controller/nbpolicy_controller.go index ed49475..4621d05 100644 --- a/internal/controller/nbpolicy_controller.go +++ b/internal/controller/nbpolicy_controller.go @@ -35,6 +35,7 @@ var ( errNetBirdAPI = fmt.Errorf("netbird API error") ) +// getResources get all NBResource objects in policy.status.managedServiceList func (r *NBPolicyReconciler) getResources(ctx context.Context, nbPolicy *netbirdiov1.NBPolicy, logger logr.Logger) ([]netbirdiov1.NBResource, error) { var resourceList []netbirdiov1.NBResource var updatedManagedServiceList []string @@ -58,6 +59,8 @@ func (r *NBPolicyReconciler) getResources(ctx context.Context, nbPolicy *netbird return resourceList, nil } +// mapResources map each NBResource ports and protocols into one object to generate the policy +// returns map[protocol] => ports, destination group IDs func (r *NBPolicyReconciler) mapResources(ctx context.Context, nbPolicy *netbirdiov1.NBPolicy, resources []netbirdiov1.NBResource, logger logr.Logger) (map[string][]int32, []string, error) { portMapping := map[string]map[int32]interface{}{ "tcp": make(map[int32]interface{}), @@ -92,6 +95,7 @@ func (r *NBPolicyReconciler) mapResources(ctx context.Context, nbPolicy *netbird return ports, groups, nil } +// createPolicy helper for creating policy with settings func (r *NBPolicyReconciler) createPolicy(ctx context.Context, nbPolicy *netbirdiov1.NBPolicy, protocol string, sourceGroupIDs, destinationGroupIDs, ports []string, logger logr.Logger) (*string, error) { policyName := fmt.Sprintf("%s %s", nbPolicy.Spec.Name, strings.ToUpper(protocol)) logger.Info("Creating NetBird Policy", "name", policyName, "description", nbPolicy.Spec.Description, "protocol", protocol, "sources", sourceGroupIDs, "destinations", destinationGroupIDs, "ports", ports, "bidirectional", nbPolicy.Spec.Bidirectional) @@ -123,6 +127,7 @@ func (r *NBPolicyReconciler) createPolicy(ctx context.Context, nbPolicy *netbird return policy.Id, nil } +// updatePolicy helper for updating policy with settings func (r *NBPolicyReconciler) updatePolicy(ctx context.Context, policyID *string, nbPolicy *netbirdiov1.NBPolicy, protocol string, sourceGroupIDs, destinationGroupIDs, ports []string, logger logr.Logger) (*string, bool, error) { policyName := fmt.Sprintf("%s %s", nbPolicy.Spec.Name, strings.ToUpper(protocol)) logger.Info("Updating NetBird Policy", "name", policyName, "description", nbPolicy.Spec.Description, "protocol", protocol, "sources", sourceGroupIDs, "destinations", destinationGroupIDs, "ports", ports, "bidirectional", nbPolicy.Spec.Bidirectional) @@ -221,7 +226,7 @@ func (r *NBPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (r return ctrl.Result{}, err } - requeue, err := r.handlePolicies(ctx, &nbPolicy, sourceGroupIDs, destGroups, portMapping, logger) + requeue, err := r.syncPolicy(ctx, &nbPolicy, sourceGroupIDs, destGroups, portMapping, logger) if requeue || err != nil { return ctrl.Result{Requeue: requeue}, err @@ -232,7 +237,8 @@ func (r *NBPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (r return ctrl.Result{}, nil } -func (r *NBPolicyReconciler) handlePolicies(ctx context.Context, nbPolicy *netbirdiov1.NBPolicy, sourceGroups, destGroups []string, portMapping map[string][]int32, logger logr.Logger) (bool, error) { +// syncPolicy ensure upstream policy is up-to-date +func (r *NBPolicyReconciler) syncPolicy(ctx context.Context, nbPolicy *netbirdiov1.NBPolicy, sourceGroups, destGroups []string, portMapping map[string][]int32, logger logr.Logger) (bool, error) { requeue := false for protocol, ports := range portMapping { @@ -340,6 +346,7 @@ func (r *NBPolicyReconciler) handleDelete(ctx context.Context, nbPolicy netbirdi return nil } +// groupNamesToIDs map list of NetBird group names to group IDs func (r *NBPolicyReconciler) groupNamesToIDs(ctx context.Context, groupNames []string, logger logr.Logger) ([]string, error) { groups, err := r.netbird.Groups.List(ctx) if err != nil { diff --git a/internal/controller/nbresource_controller.go b/internal/controller/nbresource_controller.go index cf15e3b..666febe 100644 --- a/internal/controller/nbresource_controller.go +++ b/internal/controller/nbresource_controller.go @@ -99,6 +99,7 @@ func (r *NBResourceReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, nil } +// handlePolicy update NBPolicy if defined to add self reference to policy status func (r *NBResourceReconciler) handlePolicy(ctx context.Context, req ctrl.Request, nbResource *netbirdiov1.NBResource, groupIDs []string, logger logr.Logger) error { if nbResource.Status.PolicyName == nil && nbResource.Spec.PolicyName == "" { return nil @@ -108,6 +109,7 @@ func (r *NBResourceReconciler) handlePolicy(ctx context.Context, req ctrl.Reques var nbPolicy netbirdiov1.NBPolicy if nbResource.Spec.PolicyName == "" && nbResource.Status.PolicyName != nil { + // Remove self reference from policy status nbResource.Status.PolicyName = nil err := r.Client.Get(ctx, types.NamespacedName{Name: *nbResource.Status.PolicyName}, &nbPolicy) if err != nil { @@ -120,6 +122,8 @@ func (r *NBResourceReconciler) handlePolicy(ctx context.Context, req ctrl.Reques updatePolicyStatus = true } } else { + // Update policy settings if any difference is found + // TODO: Handle updated policy name by removing reference from old policy name in status.policyName err := r.Client.Get(ctx, types.NamespacedName{Name: nbResource.Spec.PolicyName}, &nbPolicy) if err != nil { logger.Error(errKubernetesAPI, "error getting NBPolicy", "err", err, "policyName", nbResource.Spec.PolicyName) @@ -166,6 +170,7 @@ func (r *NBResourceReconciler) handlePolicy(ctx context.Context, req ctrl.Reques return nil } +// handleGroupUpdate update network resource groups func (r *NBResourceReconciler) handleGroupUpdate(ctx context.Context, nbResource *netbirdiov1.NBResource, groupIDs []string, resource *api.NetworkResource, logger logr.Logger) error { // Handle possible updated group IDs groupIDMap := make(map[string]interface{}) @@ -198,6 +203,7 @@ func (r *NBResourceReconciler) handleGroupUpdate(ctx context.Context, nbResource return nil } +// handleNetBirdResource sync NetBird Network Resource func (r *NBResourceReconciler) handleNetBirdResource(ctx context.Context, nbResource *netbirdiov1.NBResource, groupIDs []string, logger logr.Logger) (*api.NetworkResource, error) { var resource *api.NetworkResource var err error @@ -208,6 +214,8 @@ func (r *NBResourceReconciler) handleNetBirdResource(ctx context.Context, nbReso return nil, err } } + + // Create/Update upstream network resource if nbResource.Status.NetworkResourceID == nil && resource == nil { resource, err := r.netbird.Networks.Resources(nbResource.Spec.NetworkID).Create(ctx, api.NetworkResourceRequest{ Address: nbResource.Spec.Address, @@ -255,6 +263,7 @@ func (r *NBResourceReconciler) handleNetBirdResource(ctx context.Context, nbReso return resource, nil } +// handleGroups create NBGroup objects for each group specified in NBResource func (r *NBResourceReconciler) handleGroups(ctx context.Context, req ctrl.Request, nbResource *netbirdiov1.NBResource, logger logr.Logger) ([]string, *ctrl.Result, error) { var groupIDs []string @@ -267,6 +276,7 @@ func (r *NBResourceReconciler) handleGroups(ctx context.Context, req ctrl.Reques logger.Error(errKubernetesAPI, "error getting NBGroup", "err", err) return nil, &ctrl.Result{}, err } else if errors.IsNotFound(err) { + // Create NBGroup nbGroup = netbirdiov1.NBGroup{ ObjectMeta: v1.ObjectMeta{ Name: groupNameRFC, @@ -295,6 +305,7 @@ func (r *NBResourceReconciler) handleGroups(ctx context.Context, req ctrl.Reques continue } else { + // Add NBResource as owner to NBGroup if not already done ownerExists := false for _, o := range nbGroup.OwnerReferences { if o.UID == nbResource.UID { @@ -324,6 +335,7 @@ func (r *NBResourceReconciler) handleGroups(ctx context.Context, req ctrl.Reques } } + // if not all groups are ready, requeue if len(groupIDs) != len(nbResource.Spec.Groups) { return nil, &ctrl.Result{RequeueAfter: 5 * time.Second}, nil } @@ -368,6 +380,7 @@ func (r *NBResourceReconciler) handleDelete(ctx context.Context, req ctrl.Reques } for _, g := range nbGroupList.Items { + // TODO: Handle multiple owners if len(g.OwnerReferences) > 0 && g.OwnerReferences[0].UID == nbResource.UID { g.Finalizers = util.Without(g.Finalizers, "netbird.io/resource-cleanup") err = r.Client.Update(ctx, &g) diff --git a/internal/controller/nbroutingpeer_controller.go b/internal/controller/nbroutingpeer_controller.go index c4a3923..eaac7ae 100644 --- a/internal/controller/nbroutingpeer_controller.go +++ b/internal/controller/nbroutingpeer_controller.go @@ -106,6 +106,7 @@ func (r *NBRoutingPeerReconciler) Reconcile(ctx context.Context, req ctrl.Reques return ctrl.Result{}, nil } +// handleDeployment reconcile routing peer Deployment func (r *NBRoutingPeerReconciler) handleDeployment(ctx context.Context, req ctrl.Request, nbrp *netbirdiov1.NBRoutingPeer, logger logr.Logger) error { routingPeerDeployment := appsv1.Deployment{} err := r.Client.Get(ctx, req.NamespacedName, &routingPeerDeployment) @@ -115,6 +116,7 @@ func (r *NBRoutingPeerReconciler) handleDeployment(ctx context.Context, req ctrl return err } + // Create deployment if errors.IsNotFound(err) { var replicas int32 = 3 if nbrp.Spec.Replicas != nil { @@ -253,6 +255,7 @@ func (r *NBRoutingPeerReconciler) handleDeployment(ctx context.Context, req ctrl patch := client.StrategicMergeFrom(&routingPeerDeployment) bs, _ := patch.Data(updatedDeployment) + // To ensure no useless patching is done to the deployment being watched // Minimum patch size is 2 for "{}" if len(bs) <= 2 { return nil @@ -268,6 +271,7 @@ func (r *NBRoutingPeerReconciler) handleDeployment(ctx context.Context, req ctrl return nil } +// handleRouter reconcile network routing peer in NetBird management API func (r *NBRoutingPeerReconciler) handleRouter(ctx context.Context, nbrp *netbirdiov1.NBRoutingPeer, nbGroup netbirdiov1.NBGroup, logger logr.Logger) error { // Check NetworkRouter exists routers, err := r.netbird.Networks.Routers(*nbrp.Status.NetworkID).List(ctx) @@ -280,8 +284,10 @@ func (r *NBRoutingPeerReconciler) handleRouter(ctx context.Context, nbrp *netbir if nbrp.Status.RouterID == nil || len(routers) == 0 { if len(routers) > 0 { + // Router exists but isn't saved to status nbrp.Status.RouterID = &routers[0].Id } else { + // Create network router router, err := r.netbird.Networks.Routers(*nbrp.Status.NetworkID).Create(ctx, api.NetworkRouterRequest{ Enabled: true, Masquerade: true, @@ -298,6 +304,7 @@ func (r *NBRoutingPeerReconciler) handleRouter(ctx context.Context, nbrp *netbir nbrp.Status.RouterID = &router.Id } } else { + // Ensure network router settings are correct if !routers[0].Enabled || !routers[0].Masquerade || routers[0].Metric != 9999 || len(*routers[0].PeerGroups) != 1 || (*routers[0].PeerGroups)[0] != *nbGroup.Status.GroupID { _, err = r.netbird.Networks.Routers(*nbrp.Status.NetworkID).Update(ctx, routers[0].Id, api.NetworkRouterRequest{ Enabled: true, @@ -317,6 +324,7 @@ func (r *NBRoutingPeerReconciler) handleRouter(ctx context.Context, nbrp *netbir return nil } +// handleSetupKey reconcile setup key and regenerate if invalid func (r *NBRoutingPeerReconciler) handleSetupKey(ctx context.Context, req ctrl.Request, nbrp *netbirdiov1.NBRoutingPeer, nbGroup netbirdiov1.NBGroup, logger logr.Logger) (*ctrl.Result, error) { networkName := r.ClusterName if r.NamespacedNetworks { @@ -381,10 +389,11 @@ func (r *NBRoutingPeerReconciler) handleSetupKey(ctx context.Context, req ctrl.R if (err != nil && strings.Contains(err.Error(), "not found")) || setupKey.Revoked { nbrp.Status.SetupKeyID = nil + // Requeue to avoid repeating code return &ctrl.Result{Requeue: true}, nil } - // Check if valid setup key exists + // Check if secret is valid skSecret := corev1.Secret{} err = r.Client.Get(ctx, req.NamespacedName, &skSecret) if err != nil && !errors.IsNotFound(err) { @@ -415,6 +424,7 @@ func (r *NBRoutingPeerReconciler) handleSetupKey(ctx context.Context, req ctrl.R return nil, nil } +// handleGroup creates/updates NBGroup for routing peer func (r *NBRoutingPeerReconciler) handleGroup(ctx context.Context, req ctrl.Request, nbrp *netbirdiov1.NBRoutingPeer, logger logr.Logger) (*netbirdiov1.NBGroup, *ctrl.Result, error) { networkName := r.ClusterName if r.NamespacedNetworks { @@ -471,6 +481,7 @@ func (r *NBRoutingPeerReconciler) handleGroup(ctx context.Context, req ctrl.Requ return &nbGroup, nil, nil } +// handleNetwork Create/Update NetBird Network func (r *NBRoutingPeerReconciler) handleNetwork(ctx context.Context, req ctrl.Request, nbrp *netbirdiov1.NBRoutingPeer, logger logr.Logger) error { networkName := r.ClusterName if r.NamespacedNetworks { diff --git a/internal/controller/service_controller.go b/internal/controller/service_controller.go index 247f063..0285275 100644 --- a/internal/controller/service_controller.go +++ b/internal/controller/service_controller.go @@ -70,6 +70,7 @@ func (r *ServiceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct return r.hideService(ctx, req, svc, logger) } +// hideService deletes NBResource for Service func (r *ServiceReconciler) hideService(ctx context.Context, req ctrl.Request, svc corev1.Service, logger logr.Logger) (ctrl.Result, error) { var nbResource netbirdiov1.NBResource err := r.Client.Get(ctx, req.NamespacedName, &nbResource) @@ -98,6 +99,7 @@ func (r *ServiceReconciler) hideService(ctx context.Context, req ctrl.Request, s return ctrl.Result{}, nil } +// exposeService creates/updates NBResource for Service func (r *ServiceReconciler) exposeService(ctx context.Context, req ctrl.Request, svc corev1.Service, logger logr.Logger) (ctrl.Result, error) { routerNamespace := r.ControllerNamespace if r.NamespacedNetworks { @@ -177,6 +179,7 @@ func (r *ServiceReconciler) exposeService(ctx context.Context, req ctrl.Request, return ctrl.Result{}, nil } +// reconcileNBResource ensures NBResource settings are in-line with Service definition and annotations func (r *ServiceReconciler) reconcileNBResource(nbResource *netbirdiov1.NBResource, req ctrl.Request, svc corev1.Service, routingPeer netbirdiov1.NBRoutingPeer) error { groups := []string{fmt.Sprintf("%s-%s-%s", r.ClusterName, req.Namespace, req.Name)} if v, ok := svc.Annotations[serviceGroupsAnnotation]; ok { @@ -242,6 +245,7 @@ func (r *ServiceReconciler) reconcileNBResource(nbResource *netbirdiov1.NBResour } } } + // TODO: Handle removed policy name return nil } From 61af2cd508425ed8bb58d007847b1422b157ebc7 Mon Sep 17 00:00:00 2001 From: Maycon Santos Date: Wed, 5 Mar 2025 15:01:46 +0100 Subject: [PATCH 06/11] update wording, links and adding examples --- README.md | 17 +++++----- docs/usage.md | 90 +++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 78 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 1c34577..7340909 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ https://github.com/user-attachments/assets/5472a499-e63d-4301-a513-ad84cfe5ca7b ## Description -This operator enables easily provisioning NetBird access on kubernetes clusters, allowing users to access internal resources directly. +This operator easily provides NetBird access on Kubernetes clusters, allowing users to access internal resources directly. ## Getting Started @@ -18,7 +18,7 @@ This operator enables easily provisioning NetBird access on kubernetes clusters, ### Deployment > [!NOTE] -> Helm Installation method is recommended due to automation of multiple settings within the deployment. +> Helm Installation method is recommended due to the automation of multiple settings within the deployment. #### Using Helm @@ -27,14 +27,15 @@ This operator enables easily provisioning NetBird access on kubernetes clusters, helm repo add netbirdio https://netbirdio.github.io/kubernetes-operator ``` 2. (Recommended) Install [cert-manager](https://cert-manager.io/docs/installation/#default-static-install). -1. (Recommended) Create a values.yaml file, check `helm show values netbirdio/kubernetes-operator` for more info. -1. Install using `helm install --create-namespace -f values.yaml -n netbird netbird-operator netbirdio/kubernetes-operator`. +3. (Recommended) Create a values.yaml file, check `helm show values netbirdio/kubernetes-operator` for more info. +4. Install using `helm install --create-namespace -f values.yaml -n netbird netbird-operator netbirdio/kubernetes-operator`. +> Learn more about the values.yaml options [here](helm/netbird-operator/values.yaml) and [Granting controller access to NetBird Management](docs/values.md#granting-controller-access-to-netbird-management). #### Using install.yaml > [!IMPORTANT] -> install.yaml only includes a very basic template for deploying a stripped down version of kubernetes-operator. -> This excludes any and all configuration for ingress capabilities, and requires cert-manager to be installed. +> install.yaml only includes a very basic template for deploying a stripped-down version of Kubernetes-operator. +> This excludes any and all configurations for ingress capabilities and requires the cert-manager to be installed. ```sh kubectl create namespace netbird @@ -43,13 +44,13 @@ kubectl apply -n netbird -f https://raw.githubusercontent.com/netbirdio/kubernet ### Usage -Checks [usage.md](docs/usage.md). +Check the usage of [usage.md](docs/usage.md) and examples. ## Contributing ### Prerequisites -To be able to develop on this project, you need to have the following tools installed: +To be able to develop this project, you need to have the following tools installed: - [Git](https://git-scm.com/). - [Make](https://www.gnu.org/software/make/). diff --git a/docs/usage.md b/docs/usage.md index 8e68da6..229318d 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -47,41 +47,89 @@ spec: ### Granting controller access to NetBird Management > [!IMPORTANT] -> NetBird kubernetes operator generates configurations using NetBird API, editing or deleting these configurations in the NetBird console may cause temporary network disconnection until the operator reconciles the configuration. +> The NetBird Kubernetes operator generates configurations using NetBird API; editing or deleting these configurations in the NetBird console may cause temporary network disconnection until the operator reconciles the configuration. 1. Create a Service User on your NetBird dashboard (Must be Admin). [Doc](https://docs.netbird.io/how-to/access-netbird-public-api#creating-a-service-user). -1. Create access token for the Service User (Must be Admin). [Doc](https://docs.netbird.io/how-to/access-netbird-public-api#creating-a-service-user). +1. Create an access token for the Service User (Must be Admin). [Doc](https://docs.netbird.io/how-to/access-netbird-public-api#creating-a-service-user). 1. Add access token to your helm values file under `netbirdAPI.key`. 1. Alternatively, provision secret in the same namespace as the operator and set the key `NB_API_KEY` to the access token generated. 1. Set `netbirdAPI.keyFromSecret` to the name of the secret created. 1. Set `ingress.enabled` to `true`. - 1. Optionally, to provision network immediately, set `ingress.router.enabled` to `true`. - 1. Optionally, to provision 1 network per kubernetes namespace, set `ingress.namespacedNetworks` to `true`. + 1. Optionally, to provision the network immediately, set `ingress.router.enabled` to `true`. + 1. Optionally, to provision 1 network per > The NetBird Kubernetes operator generates configurations using NetBird API; editing or deleting these configurations in the NetBird console may cause temporary network disconnection until the operator reconciles the configuration. namespace, set `ingress.namespacedNetworks` to `true`. 1. Run `helm install` or `helm upgrade`. +Minimum values.yaml example: +```yaml +netbirdAPI: + key: "nbp_XXxxxxxxXXXXxxxxXXXXXxxx" + +ingress: + enabled: true +``` + ### Exposing a Service > [!IMPORTANT] -> Ingress DNS Resolution requires DNS Wildcard Routing to be enabled, and at least one DNS Nameserver configured for clients. +> Ingress DNS Resolution requires DNS Wildcard Routing to be enabled and at least one DNS Nameserver configured for clients. |Annotation|Description|Default|Valid Values| |---|---|---|---| -|`netbird.io/expose`|Expose service using NetBird Network Resource||(`null`, `true`)| -|`netbird.io/groups`|Comma-separated list of group names to assign to Network Resource|`{ClusterName}-{Namespace}-{Service}`|Any comma-separated list of strings.| -|`netbird.io/resource-name`|Network Resource name|`{Namespace}-{Service}`|Any valid network resource name, make sure they're unique!| -|`netbird.io/policy`|Name of NBPolicy to propagate service ports as destination.||Name of any NBPolicy resource| -|`netbird.io/policy-ports`|Narrow down exposed ports in policy, leave empty for all ports.||Comma-separated integer list, integers must be between 0-65535| -|`netbird.io/policy-protocol`|Narrow down protocol for use in policy, leave empty for all protocols.||(`tcp`,`udp`)| +|`netbird.io/expose`| Expose service using NetBird Network Resource ||(`null`, `true`)| +|`netbird.io/groups`| Comma-separated list of group names to assign to Network Resource |`{ClusterName}-{Namespace}-{Service}`|Any comma-separated list of strings.| +|`netbird.io/resource-name`| Network Resource name |`{Namespace}-{Service}`|Any valid network resource name, make sure they're unique!| +|`netbird.io/policy`| Name of NBPolicy to propagate service ports as destination. ||Name of any NBPolicy resource| +|`netbird.io/policy-ports`| Narrow down exposed ports in a policy. Leave empty for all ports. ||Comma-separated integer list, integers must be between 0-65535| +|`netbird.io/policy-protocol`| Narrow down protocol for use in a policy. Leave empty for all protocols. ||(`tcp`,`udp`)| + +Example service: +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 0 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:latest + ports: + - containerPort: 80 +--- +apiVersion: v1 +kind: Service +metadata: + name: nginx-service + annotations: + netbird.io/expose: "true" + netbird.io/groups: "groupA,groupB" +spec: + selector: + app: nginx + ports: + - protocol: TCP + port: 8080 + targetPort: 80 + type: ClusterIP +``` ### Notes -* `netbird.io/expose` will interpret any string as true value, the only false value is `null`. +* `netbird.io/expose` will interpret any string as a `true` value; the only `false` value is `null`. * The operator does **not** handle duplicate resource names within the same network, it is up to you to ensure resource names are unique within the same network. * While the NetBird console will allow group names to contain commas, this is not allowed in `netbird.io/groups` annotation as commas are used as separators. * If a group already exists on NetBird console with the same name, NetBird Operator will use that group ID instead of creating a new group. * NetBird Operator will attempt to clean up any resources created, including groups created for resources. - * In case a group is used by resources that cannot be cleaned up by the operator, the operator will eventually ignore the group in NetBird. - * It's recommended to use unique groups per NetBird Operator installation to remove any possible conflicts. -* NetBird Operator does not validate service annotations on update, as this may cause unnecessary overhead on any Service update. + * If a group is used by resources that the operator cannot clean up, the operator will eventually ignore the group in NetBird. + * It's recommended that unique groups be used per NetBird Operator installation to remove any possible conflicts. +* The Operator does not validate service annotations on updates, as this may cause unnecessary overhead on any Service update. ### Managing Policies @@ -102,11 +150,11 @@ ingress: - udp bidirectional: true # Optional, defaults to true ``` -2. Reference policy in Services using `netbird.io/policy=default`, this will add relevant ports and destination groups to Policy. -3. (Optional) limit specific ports in exposed service by adding `netbird.io/policy-ports=443`. -4. (Optional) limit specific protocol in exposed service by adding `netbird.io/policy-protocol=tcp`. +2. Reference policy in Services using `netbird.io/policy=default`, this will add relevant ports and destination groups to policy. +3. (Optional) Limit specific ports in exposed service by adding `netbird.io/policy-ports=443`. +4. (Optional) Limit specific protocol in exposed service by adding `netbird.io/policy-protocol=tcp`. #### Notes -* Each NBPolicy will only create policies in NetBird console when information provided is enough to create one. If there are no services acting as destination, or specified services do not conform to the protocol(s) defined, policy will not be created. -* Each NBPolicy will create one Policy in NetBird console per protocol specified as long as protocol has destinations, this ensures better secured policies by separating ports for TCP and UDP. -* Policies currently do not support ICMP protocol, as ICMP is not supported in kubernetes services, and there are [no current plans to support it](https://discuss.kubernetes.io/t/icmp-support-for-kubernetes-service/21738). +* Each NBPolicy will only create policies in the NetBird console when the information provided is enough to create one. If no services act as a destination or specified services do not conform to the protocol(s) defined, the policy will not be created. +* Each NBPolicy will create one policy in the NetBird console per protocol specified as long as the protocol has destinations; this ensures better-secured policies by separating ports for TCP and UDP. +* Policies currently do not support ICMP protocol, as ICMP is not supported in Kubernetes services, and there are [no current plans to support it](https://discuss.kubernetes.io/t/icmp-support-for-kubernetes-service/21738). From 547f285e47e70bc423cd96402d3e22747b85adfe Mon Sep 17 00:00:00 2001 From: Maycon Santos Date: Wed, 5 Mar 2025 15:03:33 +0100 Subject: [PATCH 07/11] fix link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7340909..dcf2029 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ helm repo add netbirdio https://netbirdio.github.io/kubernetes-operator 3. (Recommended) Create a values.yaml file, check `helm show values netbirdio/kubernetes-operator` for more info. 4. Install using `helm install --create-namespace -f values.yaml -n netbird netbird-operator netbirdio/kubernetes-operator`. -> Learn more about the values.yaml options [here](helm/netbird-operator/values.yaml) and [Granting controller access to NetBird Management](docs/values.md#granting-controller-access-to-netbird-management). +> Learn more about the values.yaml options [here](helm/kubernetes-operator/values.yaml) and [Granting controller access to NetBird Management](docs/usage.md#granting-controller-access-to-netbird-management). #### Using install.yaml > [!IMPORTANT] From 58c283116a4d1327a59a2196310040ae772a000c Mon Sep 17 00:00:00 2001 From: Maycon Santos Date: Wed, 5 Mar 2025 15:05:02 +0100 Subject: [PATCH 08/11] add cluster name --- docs/usage.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/usage.md b/docs/usage.md index 229318d..84ff0a7 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -66,6 +66,8 @@ netbirdAPI: ingress: enabled: true +cluster: + name: kubernetes ``` ### Exposing a Service From fc6b801350050018895b96edd6d8bb230c70d499 Mon Sep 17 00:00:00 2001 From: Maycon Santos Date: Thu, 6 Mar 2025 09:42:17 +0100 Subject: [PATCH 09/11] add extra labels example and link to full values --- docs/usage.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 84ff0a7..9d36e10 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -1,6 +1,6 @@ # Usage -## Provision pods with NetBird access +## Provision pods with NetBird access using side-cars 1. Create a Setup Key in your [NetBird console](https://docs.netbird.io/how-to/register-machines-using-setup-keys#using-setup-keys). 1. Create a Secret object in the namespace where you need to provision NetBird access (secret name and field can be anything). @@ -42,6 +42,12 @@ spec: ... ``` +Since 0.27.0 NetBird supports extra DNS labels, which extends the DNS names that you can link to peers by grouping them and load balancing access using DNS round-robin. To enable this feature, add the following annotation to the pod: +```yaml + netbird.io/extra-dns-labels: "label1,label2" +``` +With this setup, all peers with the same extra label would be used in a DNS round-robin fashion. + ## Provisioning Networks (Ingress Functionality) ### Granting controller access to NetBird Management @@ -69,7 +75,7 @@ ingress: cluster: name: kubernetes ``` - +> Learn more about the values.yaml options [here](../helm/kubernetes-operator/values.yaml). ### Exposing a Service > [!IMPORTANT] From 0bb91dfad97a4d9f6a1e48f4810e0c77339b6128 Mon Sep 17 00:00:00 2001 From: Maycon Santos Date: Thu, 6 Mar 2025 09:43:39 +0100 Subject: [PATCH 10/11] minor text --- docs/usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage.md b/docs/usage.md index 9d36e10..5cca9c6 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -42,7 +42,7 @@ spec: ... ``` -Since 0.27.0 NetBird supports extra DNS labels, which extends the DNS names that you can link to peers by grouping them and load balancing access using DNS round-robin. To enable this feature, add the following annotation to the pod: +Since v0.27.0, NetBird supports extra DNS labels, which extends the DNS names that you can link to peers by grouping them and load balancing access using DNS round-robin. To enable this feature, add the following annotation to the pod: ```yaml netbird.io/extra-dns-labels: "label1,label2" ``` From beab7a27a99bd8d72b7da6a4245d45c785f01612 Mon Sep 17 00:00:00 2001 From: Maycon Santos Date: Thu, 6 Mar 2025 09:51:08 +0100 Subject: [PATCH 11/11] add version and test matrix --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index dcf2029..46cf0ee 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,19 @@ kubectl create namespace netbird kubectl apply -n netbird -f https://raw.githubusercontent.com/netbirdio/kubernetes-operator/refs/heads/main/manifests/install.yaml ``` +### Version +Latest version: v0.1.0 + +Tested against: +|Distribution|Test status| +|---|---| +|Google GKE|Pass| +|AWS EKS|Pass| +|Azure AKS|Not tested| +|OpenShift|Not tested| + +> We would love community feedback to improve the test matrix. Please submit a PR with your test results. + ### Usage Check the usage of [usage.md](docs/usage.md) and examples.