From 166091b8e0252dc34a63ce498f82f193ebb31bd7 Mon Sep 17 00:00:00 2001 From: "M. Essam" Date: Thu, 6 Mar 2025 10:57:45 +0200 Subject: [PATCH] Add ingress feature to controller (#5) Co-authored-by: Maycon Santos --- PROJECT | 46 ++ README.md | 116 +-- 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 | 554 +++++++++++++- cmd/main.go | 125 ++- docs/usage.md | 168 +++++ examples/ingress/exposed-nginx.yaml | 47 ++ examples/ingress/values.yaml | 12 + examples/setup-keys/example.yaml | 49 ++ go.mod | 14 +- go.sum | 275 ++++++- helm/kubernetes-operator/Chart.yaml | 4 +- .../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/_helpers.tpl | 2 +- .../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 | 127 +++- helm/kubernetes-operator/values.yaml | 64 +- internal/controller/nbgroup_controller.go | 197 +++++ .../controller/nbgroup_controller_test.go | 70 ++ internal/controller/nbpolicy_controller.go | 378 ++++++++++ .../controller/nbpolicy_controller_test.go | 70 ++ internal/controller/nbresource_controller.go | 413 ++++++++++ .../controller/nbresource_controller_test.go | 69 ++ .../controller/nbroutingpeer_controller.go | 641 ++++++++++++++++ .../nbroutingpeer_controller_test.go | 70 ++ internal/controller/nbsetupkey_controller.go | 38 +- internal/controller/service_controller.go | 259 +++++++ .../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 +++++++++++++++++- 54 files changed, 5992 insertions(+), 189 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 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 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/README.md b/README.md index 3c46956..46cf0ee 100644 --- a/README.md +++ b/README.md @@ -1,107 +1,69 @@ # 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. +This operator easily provides NetBird access on Kubernetes clusters, allowing users to access internal resources directly. ## 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. +- (Recommended) Cert Manager. -### To Deploy on the cluster -**Using the install.yaml** +### Deployment +> [!NOTE] +> Helm Installation method is recommended due to the automation of multiple settings within the deployment. + +#### Using Helm + +1. Add helm repository. +```sh +helm repo add netbirdio https://netbirdio.github.io/kubernetes-operator +``` +2. (Recommended) Install [cert-manager](https://cert-manager.io/docs/installation/#default-static-install). +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/kubernetes-operator/values.yaml) and [Granting controller access to NetBird Management](docs/usage.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 configurations for ingress capabilities and requires the cert-manager to be installed. ```sh kubectl create namespace netbird -kubectl apply -n netbird -f https://github.com/netbirdio/kubernetes-operator/releases/latest/manifests/install.yaml +kubectl apply -n netbird -f https://raw.githubusercontent.com/netbirdio/kubernetes-operator/refs/heads/main/manifests/install.yaml ``` -**Using the Helm Chart** +### Version +Latest version: v0.1.0 -```sh -helm repo add netbirdio https://netbirdio.github.io/kubernetes-operator -helm install -n netbird kubernetes-operator netbirdio/kubernetes-operator -``` +Tested against: +|Distribution|Test status| +|---|---| +|Google GKE|Pass| +|AWS EKS|Pass| +|Azure AKS|Not tested| +|OpenShift|Not tested| -For more options, check the default values by running -```sh -helm show values netbirdio/kubernetes-operator -``` +> We would love community feedback to improve the test matrix. Please submit a PR with your test results. -### To Uninstall -**Using install.yaml** +### Usage -```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 -``` - -### 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 - -``` +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/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,9 +5,544 @@ 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 *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) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// 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(NBPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NBPolicy) 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 *NBPolicyList) DeepCopyInto(out *NBPolicyList) { + *out = *in + 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 NBRoutingPeerSpec. +func (in *NBRoutingPeerSpec) DeepCopy() *NBRoutingPeerSpec { + if in == nil { + return nil + } + 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 @@ -35,23 +570,6 @@ func (in *NBSetupKey) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NBSetupKeyCondition) DeepCopyInto(out *NBSetupKeyCondition) { - *out = *in - in.LastProbeTime.DeepCopyInto(&out.LastProbeTime) - in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBSetupKeyCondition. -func (in *NBSetupKeyCondition) DeepCopy() *NBSetupKeyCondition { - if in == nil { - return nil - } - out := new(NBSetupKeyCondition) - in.DeepCopyInto(out) - return out -} - // 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/docs/usage.md b/docs/usage.md new file mode 100644 index 0000000..5cca9c6 --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,168 @@ +# Usage + +## 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). +```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: +... +``` + +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" +``` +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 + +> [!IMPORTANT] +> 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 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 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 +cluster: + name: kubernetes +``` +> Learn more about the values.yaml options [here](../helm/kubernetes-operator/values.yaml). +### 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 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 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. + * 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 + +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 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). 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/go.mod b/go.mod index 7548b0f..9034512 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,9 @@ 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 github.com/onsi/gomega v1.35.1 k8s.io/api v0.32.0 @@ -22,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 @@ -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..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.0 -appVersion: "v0.1.0" +version: 0.1.2 +appVersion: "v0.1.0-rc.3" 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/_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/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..4ba96f3 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 @@ -116,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 bf3c61a..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 @@ -128,3 +129,56 @@ operator: tolerations: [] 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: + # requests: + # cpu: 100m + # memory: 100Mi + # limits: + # cpu: 100m + # memory: 100Mi + # labels: {} + # annotations: {} + # nodeSelector: {} + # tolerations: [] + # Only needed if namespacedNetworks is set to true + namespaces: {} + # default: + # replicas: 3 + # resources: + # requests: + # cpu: 100m + # memory: 100Mi + # limits: + # cpu: 100m + # memory: 100Mi + # labels: {} + # annotations: {} + # nodeSelector: {} + # tolerations: [] + # NetBird Policies for use with exposed services + policies: {} + # default: + # name: Kubernetes Default Policy + # sourceGroups: + # - All + +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 + +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 new file mode 100644 index 0000000..a7d2b3a --- /dev/null +++ b/internal/controller/nbgroup_controller.go @@ -0,0 +1,197 @@ +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" + + "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" + "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 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 +) + +// 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) { + 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) { + logger.Error(errKubernetesAPI, "error getting NBGroup", "err", err) + } + 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, nbGroup, logger) + } + + 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) + return ctrl.Result{}, err + } + var group *api.Group + for _, g := range groups { + if g.Name == nbGroup.Spec.Name { + 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{ + Name: nbGroup.Spec.Name, + }) + if err != nil { + nbGroup.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("NetBird API Error: %v", err)) + 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 { + 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 { + 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 + } 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, 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) + if err != nil { + logger.Error(errKubernetesAPI, "error updating NBGroup", "err", err) + 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") { + logger.Error(errNetBirdAPI, "error deleting group", "err", err) + return err + } + + if err != nil && strings.Contains(err.Error(), "linked") { + 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 { + logger.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 + 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 { + logger.Error(errKubernetesAPI, "error updating NBGroup", "err", err) + return err + } + 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 + } + + nbGroup.Finalizers = util.Without(nbGroup.Finalizers, "netbird.io/group-cleanup") + err = r.Client.Update(ctx, &nbGroup) + if err != nil { + logger.Error(errKubernetesAPI, "error updating NBGroup", "err", err) + 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..4621d05 --- /dev/null +++ b/internal/controller/nbpolicy_controller.go @@ -0,0 +1,378 @@ +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" + + "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" + "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") +) + +// 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 + 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) { + 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 + } + if err == nil && resource.DeletionTimestamp == nil { + updatedManagedServiceList = append(updatedManagedServiceList, rss) + resourceList = append(resourceList, resource) + } + } + + nbPolicy.Status.ManagedServiceList = updatedManagedServiceList + + 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{}), + "udp": make(map[int32]interface{}), + } + groups, err := r.groupNamesToIDs(ctx, nbPolicy.Spec.DestinationGroups, logger) + 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 +} + +// 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) + 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 { + logger.Error(errNetBirdAPI, "Error creating Policy", "err", err) + nbPolicy.Status.Conditions = netbirdiov1.NBConditionFalse("APIError", fmt.Sprintf("Error creating policy: %v", err)) + return nil, err + } + + 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) + 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") { + 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 + } + + requeue := false + + if err != nil && strings.Contains(err.Error(), "not found") { + 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") + } + + 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) { + 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) + if err != nil { + if errors.IsNotFound(err) { + err = nil + } + if err != nil { + logger.Error(errKubernetesAPI, "error getting NBPolicy", "err", err) + } + 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, nbPolicy, logger) + } + + resourceList, err := r.getResources(ctx, &nbPolicy, logger) + if err != nil { + return ctrl.Result{}, err + } + + portMapping, destGroups, err := r.mapResources(ctx, &nbPolicy, resourceList, logger) + if err != nil { + return ctrl.Result{}, err + } + + 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.syncPolicy(ctx, &nbPolicy, sourceGroupIDs, destGroups, portMapping, logger) + + if requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err + } + + nbPolicy.Status.Conditions = netbirdiov1.NBConditionTrue() + + return ctrl.Result{}, nil +} + +// 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 { + var policyID *string + switch protocol { + case "tcp": + policyID = nbPolicy.Status.TCPPolicyID + case "udp": + policyID = nbPolicy.Status.UDPPolicyID + default: + 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 { + 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)) + return requeue, err + } + policyID = nil + + } else { + logger.Info("Ignoring protocol as NBPolicy has restricted protocols", "protocol", protocol) + } + } else if len(ports) == 0 && policyID == nil { + logger.Info("0 ports found for protocol in policy", "protocol", protocol) + continue + } else if len(destGroups) == 0 && policyID == nil { + logger.Info("no destinations found for protocol in policy", "protocol", protocol) + continue + } else if len(sourceGroups) == 0 && policyID == nil { + 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 + 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)) + 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, nbPolicy, protocol, sourceGroups, destGroups, stringPorts, logger) + } else { + policyID, requeue, err = r.updatePolicy(ctx, policyID, nbPolicy, protocol, sourceGroups, destGroups, stringPorts, logger) + } + if err != nil { + return requeue, err + } + } + + switch protocol { + case "tcp": + nbPolicy.Status.TCPPolicyID = policyID + case "udp": + nbPolicy.Status.UDPPolicyID = policyID + default: + logger.Error(errKubernetesAPI, "Unknown protocol", "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, nbPolicy netbirdiov1.NBPolicy, logger logr.Logger) 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 && !strings.Contains("not found", err.Error()) { + 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 { + logger.Error(errKubernetesAPI, "Error updating NBPolicy", "err", err) + return err + } + } + 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 { + logger.Error(errNetBirdAPI, "Error listing Groups", "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..666febe --- /dev/null +++ b/internal/controller/nbresource_controller.go @@ -0,0 +1,413 @@ +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" + + "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" + "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) { + 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) { + logger.Error(errKubernetesAPI, "error getting NBResource", "err", err) + } + 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, logger) + } + + 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, 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 + } + + // resource is only nil if requeue is expected + if resource == nil { + return ctrl.Result{Requeue: true}, nil + } + + 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, logger) + 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 +} + +// 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 + } + + updatePolicyStatus := false + + 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 { + logger.Error(errKubernetesAPI, "error getting NBPolicy", "err", err, "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 { + // 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) + 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 { + logger.Error(errKubernetesAPI, "error updating NBPolicy", "err", err, "policyName", nbResource.Spec.PolicyName) + return err + } + } + + 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{}) + 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 { + logger.Error(errNetBirdAPI, "error updating resource", "err", err) + return err + } + } + + 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 + 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") { + logger.Error(errNetBirdAPI, "error getting network resource", "err", err) + 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, + Enabled: true, + Groups: groupIDs, + Description: &networkDescription, + Name: nbResource.Spec.Name, + }) + + if err != nil { + logger.Error(errNetBirdAPI, "error creating resource", "err", err) + 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 +} + +// 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 + + 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) { + 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, + 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 { + logger.Error(errKubernetesAPI, "error creating NBGroup", "err", err) + return nil, &ctrl.Result{}, err + } + + continue + } else { + // Add NBResource as owner to NBGroup if not already done + 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 { + logger.Error(errKubernetesAPI, "error updating NBGroup", "err", err) + return nil, &ctrl.Result{}, err + } + } + } + + if nbGroup.Status.GroupID != nil { + groupIDs = append(groupIDs, *nbGroup.Status.GroupID) + } + } + + // if not all groups are ready, requeue + 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, 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) { + logger.Error(errKubernetesAPI, "error getting NBPolicy", "err", err, "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") { + logger.Error(errNetBirdAPI, "error deleting resource", "err", err) + return err + } + + nbResource.Status.NetworkResourceID = nil + } + + nbGroupList := netbirdiov1.NBGroupList{} + err := r.Client.List(ctx, &nbGroupList, &client.ListOptions{Namespace: req.Namespace}) + if err != nil { + logger.Error(errKubernetesAPI, "error listing NBGroup", "err", err) + return err + } + + 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) + if err != nil && !errors.IsNotFound(err) { + logger.Error(errKubernetesAPI, "error updating NBGroup", "err", err) + return err + } + } + } + + nbResource.Finalizers = nil + err = r.Client.Update(ctx, nbResource) + if err != nil { + logger.Error(errKubernetesAPI, "error updating NBGroup", "err", err) + 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..eaac7ae --- /dev/null +++ b/internal/controller/nbroutingpeer_controller.go @@ -0,0 +1,641 @@ +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" + + "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" + "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) { + 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) { + logger.Error(errKubernetesAPI, "error getting NBRoutingPeer", "err", err) + } + 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 { + logger.Error(errKubernetesAPI, "error updating NBRoutingPeer Status", "err", err) + } + } + 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, logger) + } + + logger.Info("NBRoutingPeer: Checking network") + err = r.handleNetwork(ctx, req, nbrp, logger) + if err != nil { + return ctrl.Result{}, err + } + + logger.Info("NBRoutingPeer: Checking groups") + nbGroup, result, err := r.handleGroup(ctx, req, nbrp, logger) + if nbGroup == nil { + return *result, err + } + + logger.Info("NBRoutingPeer: Checking setup keys") + result, err = r.handleSetupKey(ctx, req, nbrp, *nbGroup, logger) + if result != nil { + return *result, err + } + + logger.Info("NBRoutingPeer: Checking network router") + err = r.handleRouter(ctx, nbrp, *nbGroup, logger) + if err != nil { + return ctrl.Result{}, err + } + + logger.Info("NBRoutingPeer: Checking deployment") + err = r.handleDeployment(ctx, req, nbrp, logger) + if err != nil { + return ctrl.Result{}, err + } + + nbrp.Status.Conditions = netbirdiov1.NBConditionTrue() + 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) + if err != nil && !errors.IsNotFound(err) { + logger.Error(errKubernetesAPI, "error getting Deployment", "err", err) + nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("error getting Deployment: %v", err)) + return err + } + + // Create deployment + 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 { + logger.Error(errKubernetesAPI, "error creating Deployment", "err", err) + 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) + // To ensure no useless patching is done to the deployment being watched + // Minimum patch size is 2 for "{}" + if len(bs) <= 2 { + return nil + } + err = r.Client.Patch(ctx, updatedDeployment, patch) + if err != nil { + logger.Error(errKubernetesAPI, "error updating Deployment", "err", err) + nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("error updating Deployment: %v", err)) + return err + } + } + + 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) + + if err != nil { + 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 + } + + 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, + Metric: 9999, + PeerGroups: &[]string{*nbGroup.Status.GroupID}, + }) + + if err != nil { + 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 + } + + 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, + Masquerade: true, + Metric: 9999, + PeerGroups: &[]string{*nbGroup.Status.GroupID}, + }) + + if err != nil { + 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 + } + } + } + + 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 { + 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 { + 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 + } + + 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 { + 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 + } + } 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") { + 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 + } + + 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 secret is valid + skSecret := corev1.Secret{} + err = r.Client.Get(ctx, req.NamespacedName, &skSecret) + if err != nil && !errors.IsNotFound(err) { + 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 + } + + 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 { + 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 + } + + 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 +} + +// 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 { + 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) { + 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 + } + + 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 { + 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 + } + + // 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 +} + +// 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 { + networkName += "-" + req.Namespace + } + + if nbrp.Status.NetworkID == nil { + // Check if network exists + networks, err := r.netbird.Networks.List(ctx) + if err != nil { + 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 { + logger.Info("network already exists", "network-id", n.Id) + network = &n + } + } + + if network != nil { + nbrp.Status.NetworkID = &network.Id + } else { + logger.Info("creating network", "name", networkName) + network, err := r.netbird.Networks.Create(ctx, api.NetworkRequest{ + Name: networkName, + Description: &networkDescription, + }) + if err != nil { + logger.Error(errNetBirdAPI, "error creating network", "err", err) + 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, logger logr.Logger) (ctrl.Result, error) { + nbDeployment := appsv1.Deployment{} + err := r.Client.Get(ctx, req.NamespacedName, &nbDeployment) + if err != nil && !errors.IsNotFound(err) { + logger.Error(errKubernetesAPI, "error getting Deployment", "err", err) + return ctrl.Result{}, err + } + if err == nil { + err = r.Client.Delete(ctx, &nbDeployment) + if err != nil { + logger.Error(errKubernetesAPI, "error deleting Deployment", "err", err) + return ctrl.Result{}, err + } + } + + if nbrp.Status.SetupKeyID != nil { + 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") { + logger.Error(errNetBirdAPI, "error deleting setupKey", "err", err) + return ctrl.Result{}, err + } + + setupKeyID := *nbrp.Status.SetupKeyID + nbrp.Status.SetupKeyID = nil + 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") { + logger.Error(errNetBirdAPI, "error deleting Network Router", "err", err) + 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) { + logger.Error(errKubernetesAPI, "error getting NBGroup", "err", err) + return ctrl.Result{}, err + } + + if nbrp.Status.NetworkID != nil { + nbResourceList := netbirdiov1.NBResourceList{} + err = r.Client.List(ctx, &nbResourceList) + if err != nil { + logger.Error(errKubernetesAPI, "error listing NBResource", "err", err) + return ctrl.Result{}, err + } + + for _, nbrs := range nbResourceList.Items { + if nbrs.Spec.NetworkID == *nbrp.Status.NetworkID { + logger.Info("Deleting NBResource", "namespace", nbrs.Namespace, "name", nbrs.Name) + err = r.Client.Delete(ctx, &nbrs) + if err != nil { + logger.Error(errKubernetesAPI, "error deleting NBResource", "err", err) + return ctrl.Result{}, err + } + } + } + + if len(nbResourceList.Items) == 0 { + 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") { + logger.Error(errNetBirdAPI, "error deleting Network", "err", err) + 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") + 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 { + logger.Error(errKubernetesAPI, "error deleting NBGroup", "err", err) + return ctrl.Result{}, err + } + } + + if nbrp.Status.NetworkID != nil { + return ctrl.Result{RequeueAfter: 5 * time.Second}, nil + } + + if len(nbrp.Finalizers) > 0 { + logger.Info("Removing finalizers", "namespace", nbrp.Namespace, "name", nbrp.Name) + nbrp.Finalizers = nil + err = r.Client.Update(ctx, nbrp) + if err != nil { + logger.Error(errKubernetesAPI, "error updating NBRoutingPeer finalizers", "err", err) + 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..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,21 +44,22 @@ 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.NBSetupKeyCondition{ + Conditions: []netbirdiov1.NBCondition{ { - Type: netbirdiov1.Ready, + Type: netbirdiov1.NBSetupKeyReady, Status: corev1.ConditionFalse, LastProbeTime: v1.Now(), Reason: "InvalidConfig", @@ -82,12 +82,12 @@ 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) - return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{Conditions: []netbirdiov1.NBSetupKeyCondition{{ - Type: netbirdiov1.Ready, + 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, LastProbeTime: v1.Now(), Reason: "SecretNotExists", @@ -97,9 +97,9 @@ 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, + 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, LastProbeTime: v1.Now(), Reason: "SecretKeyNotExists", @@ -109,17 +109,17 @@ 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, + 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, 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..0285275 --- /dev/null +++ b/internal/controller/service_controller.go @@ -0,0 +1,259 @@ +package controller + +import ( + "context" + "fmt" + "strconv" + "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" + "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" +) + +// 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) { + 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) { + logger.Error(errKubernetesAPI, "error getting Service", "err", err) + } + 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, logger) + } + + 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) + if err != nil && !errors.IsNotFound(err) { + 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 { + logger.Error(errKubernetesAPI, "error deleting NBResource", "err", err) + 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 { + logger.Error(errKubernetesAPI, "error updating Service", "err", err) + return ctrl.Result{}, err + } + } + + 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 { + 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 { + logger.Error(errKubernetesAPI, "error updating Service", "err", err) + 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) { + logger.Error(errKubernetesAPI, "error getting NBRoutingPeer", "err", err) + 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 { + logger.Error(errKubernetesAPI, "error creating NBRoutingPeer", "err", err) + return ctrl.Result{}, err + } + + 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 { + 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) { + logger.Error(errKubernetesAPI, "error getting NBResource", "err", err) + 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 { + logger.Error(errKubernetesAPI, "error creating NBResource", "err", err) + return ctrl.Result{}, err + } + } else { + err = r.Client.Update(ctx, &nbResource) + if err != nil { + logger.Error(errKubernetesAPI, "error updating NBResource", "err", err) + return ctrl.Result{}, err + } + } + + 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 { + 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 + } + } + } + // TODO: Handle removed policy name + + 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