Add ingress feature to controller (#5)

Co-authored-by: Maycon Santos <mlsmaycon@gmail.com>
This commit is contained in:
M. Essam
2025-03-06 09:57:45 +01:00
committed by GitHub
co-authored by Maycon Santos
parent cea60745d2
commit 166091b8e0
54 changed files with 5992 additions and 189 deletions
+46
View File
@@ -29,4 +29,50 @@ resources:
webhooks: webhooks:
defaulting: true defaulting: true
webhookVersion: v1 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" version: "3"
+39 -77
View File
@@ -1,107 +1,69 @@
# NetBird Kubernetes Operator # NetBird Kubernetes Operator
For easily provisioning access to Kubernetes resources using NetBird. For easily provisioning access to Kubernetes resources using NetBird.
https://github.com/user-attachments/assets/5472a499-e63d-4301-a513-ad84cfe5ca7b
## Description ## 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 ## Getting Started
### Prerequisites ### Prerequisites
- helm version 3+ - (Recommended) helm version 3+
- kubectl version v1.11.3+. - kubectl version v1.11.3+.
- Access to a Kubernetes v1.11.3+ cluster. - 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 ```sh
kubectl create namespace netbird 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 Tested against:
helm repo add netbirdio https://netbirdio.github.io/kubernetes-operator |Distribution|Test status|
helm install -n netbird kubernetes-operator netbirdio/kubernetes-operator |---|---|
``` |Google GKE|Pass|
|AWS EKS|Pass|
|Azure AKS|Not tested|
|OpenShift|Not tested|
For more options, check the default values by running > We would love community feedback to improve the test matrix. Please submit a PR with your test results.
```sh
helm show values netbirdio/kubernetes-operator
```
### To Uninstall ### Usage
**Using install.yaml**
```sh Check the usage of [usage.md](docs/usage.md) and examples.
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
```
## Contributing ## Contributing
### Prerequisites ### 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/). - [Git](https://git-scm.com/).
- [Make](https://www.gnu.org/software/make/). - [Make](https://www.gnu.org/software/make/).
+52
View File
@@ -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{})
}
+81
View File
@@ -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{})
}
+76
View File
@@ -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{})
}
+69
View File
@@ -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{})
}
+40 -13
View File
@@ -21,13 +21,13 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
// NBSetupKeyConditionType is a valid value for PodCondition.Type // NBConditionType is a valid value for PodCondition.Type
type NBSetupKeyConditionType string type NBConditionType string
// These are built-in conditions of pod. An application may use a custom condition not listed here. // These are built-in conditions of pod. An application may use a custom condition not listed here.
const ( const (
// Ready indicates whether NBSetupKey is valid and ready to use. // NBSetupKeyReady indicates whether NBSetupKey is valid and ready to use.
Ready NBSetupKeyConditionType = "Ready" NBSetupKeyReady NBConditionType = "Ready"
) )
// NBSetupKeySpec defines the desired state of NBSetupKey. // NBSetupKeySpec defines the desired state of NBSetupKey.
@@ -40,28 +40,55 @@ type NBSetupKeySpec struct {
// NBSetupKeyStatus defines the observed state of NBSetupKey. // NBSetupKeyStatus defines the observed state of NBSetupKey.
type NBSetupKeyStatus struct { 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. // NBCondition defines a condition in NBSetupKey status.
type NBSetupKeyCondition struct { type NBCondition struct {
// Type is the type of the condition. // 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. // Status is the status of the condition.
// Can be True, False, Unknown. // 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. // Last time we probed the condition.
// +optional // +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. // Last time the condition transitioned from one status to another.
// +optional // +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. // Unique, one-word, CamelCase reason for the condition's last transition.
// +optional // +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. // Human-readable message indicating details about last transition.
// +optional // +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 // +kubebuilder:object:root=true
+536 -18
View File
@@ -5,9 +5,544 @@
package v1 package v1
import ( import (
corev1 "k8s.io/api/core/v1"
runtime "k8s.io/apimachinery/pkg/runtime" 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. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NBSetupKey) DeepCopyInto(out *NBSetupKey) { func (in *NBSetupKey) DeepCopyInto(out *NBSetupKey) {
*out = *in *out = *in
@@ -35,23 +570,6 @@ func (in *NBSetupKey) DeepCopyObject() runtime.Object {
return nil 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. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NBSetupKeyList) DeepCopyInto(out *NBSetupKeyList) { func (in *NBSetupKeyList) DeepCopyInto(out *NBSetupKeyList) {
*out = *in *out = *in
@@ -105,7 +623,7 @@ func (in *NBSetupKeyStatus) DeepCopyInto(out *NBSetupKeyStatus) {
*out = *in *out = *in
if in.Conditions != nil { if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions in, out := &in.Conditions, &out.Conditions
*out = make([]NBSetupKeyCondition, len(*in)) *out = make([]NBCondition, len(*in))
for i := range *in { for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i]) (*in)[i].DeepCopyInto(&(*out)[i])
} }
+123 -2
View File
@@ -19,6 +19,7 @@ package main
import ( import (
"crypto/tls" "crypto/tls"
"flag" "flag"
"fmt"
"os" "os"
"path/filepath" "path/filepath"
@@ -45,6 +46,10 @@ import (
// +kubebuilder:scaffold:imports // +kubebuilder:scaffold:imports
) )
const (
inClusterNamespacePath = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"
)
var ( var (
scheme = runtime.NewScheme() scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup") setupLog = ctrl.Log.WithName("setup")
@@ -62,11 +67,29 @@ func init() {
func main() { func main() {
// NB Specific flags // NB Specific flags
var ( var (
managementURL string managementURL string
clientImage 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(&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(&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 // Controller generic flags
var ( var (
@@ -177,6 +200,87 @@ func main() {
os.Exit(1) 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 // +kubebuilder:scaffold:builder
if webhookCertWatcher != nil { if webhookCertWatcher != nil {
@@ -202,3 +306,20 @@ func main() {
os.Exit(1) 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
}
+168
View File
@@ -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).
+47
View File
@@ -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
+12
View File
@@ -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
+49
View File
@@ -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
+8 -6
View File
@@ -5,7 +5,9 @@ go 1.23.0
godebug default=go1.23 godebug default=go1.23
require ( require (
github.com/go-logr/logr v1.4.2
github.com/google/uuid v1.6.0 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/ginkgo/v2 v2.21.0
github.com/onsi/gomega v1.35.1 github.com/onsi/gomega v1.35.1
k8s.io/api v0.32.0 k8s.io/api v0.32.0
@@ -22,7 +24,6 @@ require (
github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.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-logr/zapr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // 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/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // 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/spf13/pflag v1.0.5 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.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/oauth2 v0.23.0 // indirect
golang.org/x/sync v0.8.0 // indirect golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.26.0 // indirect golang.org/x/sys v0.29.0 // indirect
golang.org/x/term v0.25.0 // indirect golang.org/x/term v0.28.0 // indirect
golang.org/x/text v0.19.0 // indirect golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.7.0 // indirect golang.org/x/time v0.7.0 // indirect
golang.org/x/tools v0.26.0 // indirect golang.org/x/tools v0.26.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
+265 -10
View File
@@ -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 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 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 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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/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.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.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 h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 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 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k=
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= 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 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= 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 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 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 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= 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 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 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 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= 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.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 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 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.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 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 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 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 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 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 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 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= 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/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 h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 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 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 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/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 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.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 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.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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 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-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 h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 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 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 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 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 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 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= 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 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= 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 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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.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 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= 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= 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/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 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 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 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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.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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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.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.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.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 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 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.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/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 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 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 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 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 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 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-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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.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-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-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-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.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= 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 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 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-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-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.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/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.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 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 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 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= 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-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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= 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 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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= 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/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 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE=
k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0= k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0=
k8s.io/apiextensions-apiserver v0.32.0 h1:S0Xlqt51qzzqjKPxfgX1xh4HBZE+p8KKBq+k2SWNOE0= k8s.io/apiextensions-apiserver v0.32.0 h1:S0Xlqt51qzzqjKPxfgX1xh4HBZE+p8KKBq+k2SWNOE0=
+2 -2
View File
@@ -2,5 +2,5 @@ apiVersion: v2
name: kubernetes-operator name: kubernetes-operator
description: A Helm chart for Kubernetes description: A Helm chart for Kubernetes
type: application type: application
version: 0.1.0 version: 0.1.2
appVersion: "v0.1.0" appVersion: "v0.1.0-rc.3"
@@ -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: {}
@@ -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: {}
@@ -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: {}
@@ -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 <key,value,effect> using the matching operator <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: {}
@@ -75,8 +75,7 @@ spec:
properties: properties:
conditions: conditions:
items: items:
description: NBSetupKeyCondition defines a condition in NBSetupKey description: NBCondition defines a condition in NBSetupKey status.
status.
properties: properties:
lastProbeTime: lastProbeTime:
description: Last time we probed the condition. description: Last time we probed the condition.
@@ -92,7 +92,7 @@ caCert: {{ index $secret.data "ca.crt" }}
clientCert: {{ index $secret.data "tls.crt" }} clientCert: {{ index $secret.data "tls.crt" }}
clientKey: {{ index $secret.data "tls.key" }} clientKey: {{ index $secret.data "tls.key" }}
{{- else -}} {{- 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 -}} {{- $ca := genCA "kubernetes-operator-ca" 3650 -}}
{{- $cert := genSignedCert (include "kubernetes-operator.fullname" .) nil $altNames 3650 $ca -}} {{- $cert := genSignedCert (include "kubernetes-operator.fullname" .) nil $altNames 3650 $ca -}}
caCert: {{ $ca.Cert | b64enc }} caCert: {{ $ca.Cert | b64enc }}
@@ -48,6 +48,18 @@ spec:
{{- if .Values.managementURL }} {{- if .Values.managementURL }}
- --netbird-management-url={{.Values.managementURL}} - --netbird-management-url={{.Values.managementURL}}
{{- end }} {{- 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: ports:
- name: webhook-server - name: webhook-server
containerPort: {{ .Values.webhook.service.port }} containerPort: {{ .Values.webhook.service.port }}
@@ -62,6 +74,15 @@ spec:
periodSeconds: {{ .Values.operator.livenessProbe.periodSeconds }} periodSeconds: {{ .Values.operator.livenessProbe.periodSeconds }}
successThreshold: {{ .Values.operator.livenessProbe.successThreshold }} successThreshold: {{ .Values.operator.livenessProbe.successThreshold }}
timeoutSeconds: {{ .Values.operator.livenessProbe.timeoutSeconds }} 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: readinessProbe:
failureThreshold: 3 failureThreshold: 3
httpGet: httpGet:
@@ -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 }}
@@ -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 }}
@@ -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
---
+80 -1
View File
@@ -27,6 +27,79 @@ rules:
- get - get
- patch - patch
- update - 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: - apiGroups:
- "" - ""
resources: resources:
@@ -35,7 +108,7 @@ rules:
- get - get
- list - list
- watch - watch
{{- if .Values.clusterSecretsPermissions.allowAllSecrets }} {{- if or .Values.ingress.enabled .Values.clusterSecretsPermissions.allowAllSecrets }}
- apiGroups: - apiGroups:
- "" - ""
resources: resources:
@@ -44,6 +117,12 @@ rules:
- get - get
- list - list
- watch - watch
{{- if .Values.ingress.enabled }}
- patch
- update
- create
- delete
{{- end }}
{{- end }} {{- end }}
--- ---
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
@@ -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 }}
+123 -4
View File
@@ -12,7 +12,7 @@ metadata:
{{- include "kubernetes-operator.labels" . | nindent 4 }} {{- include "kubernetes-operator.labels" . | nindent 4 }}
webhooks: webhooks:
- clientConfig: - clientConfig:
{{- if not $.Values.webhook.enableCertManager -}} {{- if not $.Values.webhook.enableCertManager }}
caBundle: {{ $tls.caCert }} caBundle: {{ $tls.caCert }}
{{ end }} {{ end }}
service: service:
@@ -64,7 +64,7 @@ metadata:
{{- include "kubernetes-operator.labels" . | nindent 4 }} {{- include "kubernetes-operator.labels" . | nindent 4 }}
webhooks: webhooks:
- clientConfig: - clientConfig:
{{- if not $.Values.webhook.enableCertManager -}} {{- if not $.Values.webhook.enableCertManager }}
caBundle: {{ $tls.caCert }} caBundle: {{ $tls.caCert }}
{{ end }} {{ end }}
service: service:
@@ -89,8 +89,127 @@ webhooks:
- CREATE - CREATE
- UPDATE - UPDATE
resources: resources:
- "*/*" - "nbsetupkeys"
sideEffects: None 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 }} {{- if not $.Values.webhook.enableCertManager }}
apiVersion: v1 apiVersion: v1
@@ -116,7 +235,7 @@ metadata:
spec: spec:
dnsNames: dnsNames:
- {{ template "kubernetes-operator.webhookService" . }}.{{ .Release.Namespace }}.svc - {{ 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: issuerRef:
kind: Issuer kind: Issuer
name: {{ template "kubernetes-operator.fullname" . }}-selfsigned-issuer name: {{ template "kubernetes-operator.fullname" . }}-selfsigned-issuer
+59 -5
View File
@@ -1,4 +1,6 @@
clusterSecretsPermissions: clusterSecretsPermissions:
# Required for NBSetupKey validation
# Required for Ingress functionality to create and validate secrets for routing peers
allowAllSecrets: true allowAllSecrets: true
webhook: webhook:
@@ -7,22 +9,21 @@ webhook:
port: 443 port: 443
targetPort: 9443 targetPort: 9443
cluster:
# Cluster DNS domain (required for requesting TLS certificates)
dnsDomain: cluster.local
# TLS configuration for webhook # TLS configuration for webhook
# Optional, unused if webhook.enableCertManager is set to true
tls: {} tls: {}
# Use cert-manager to provision webhook certificates # Use cert-manager to provision webhook certificates (recommended)
enableCertManager: true enableCertManager: true
# Narrow down validation and mutation webhooks namespaces
namespaceSelectors: [] namespaceSelectors: []
# - key: foo # - key: foo
# operator: In # operator: In
# values: # values:
# - bar # - bar
# Narrow down validation and mutation webhooks objects
objectSelector: objectSelector:
matchExpressions: [] matchExpressions: []
# - key: app.kubernetes.io/name # - key: app.kubernetes.io/name
@@ -128,3 +129,56 @@ operator:
tolerations: [] tolerations: []
affinity: {} 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"
+197
View File
@@ -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)
}
@@ -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.
})
})
})
+378
View File
@@ -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)
}
@@ -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.
})
})
})
@@ -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)
}
@@ -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.
})
})
})
@@ -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)
}
@@ -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.
})
})
})
+19 -19
View File
@@ -29,7 +29,6 @@ import (
ctrl "sigs.k8s.io/controller-runtime" ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/reconcile"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" 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 // Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state. // 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) { 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{} nbSetupKey := netbirdiov1.NBSetupKey{}
err := r.Get(ctx, req.NamespacedName, &nbSetupKey) err := r.Get(ctx, req.NamespacedName, &nbSetupKey)
if err != nil { 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 return ctrl.Result{}, nil
} }
if nbSetupKey.Spec.SecretKeyRef.Name == "" || nbSetupKey.Spec.SecretKeyRef.Key == "" { 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{ return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{
Conditions: []netbirdiov1.NBSetupKeyCondition{ Conditions: []netbirdiov1.NBCondition{
{ {
Type: netbirdiov1.Ready, Type: netbirdiov1.NBSetupKeyReady,
Status: corev1.ConditionFalse, Status: corev1.ConditionFalse,
LastProbeTime: v1.Now(), LastProbeTime: v1.Now(),
Reason: "InvalidConfig", 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) err = r.Get(ctx, types.NamespacedName{Namespace: nbSetupKey.Namespace, Name: nbSetupKey.Spec.SecretKeyRef.Name}, &secret)
if err != nil { if err != nil {
if !errors.IsNotFound(err) { 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 return ctrl.Result{}, err
} }
ctrl.Log.Error(fmt.Errorf("invalid NBSetupKey"), "secret referenced not found", "err", err, "namespace", req.Namespace, "name", req.Name) logger.Error(fmt.Errorf("invalid NBSetupKey"), "secret referenced not found", "err", err)
return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{Conditions: []netbirdiov1.NBSetupKeyCondition{{ return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{Conditions: []netbirdiov1.NBCondition{{
Type: netbirdiov1.Ready, Type: netbirdiov1.NBSetupKeyReady,
Status: corev1.ConditionFalse, Status: corev1.ConditionFalse,
LastProbeTime: v1.Now(), LastProbeTime: v1.Now(),
Reason: "SecretNotExists", Reason: "SecretNotExists",
@@ -97,9 +97,9 @@ func (r *NBSetupKeyReconciler) Reconcile(ctx context.Context, req ctrl.Request)
uuidBytes, ok := secret.Data[nbSetupKey.Spec.SecretKeyRef.Key] uuidBytes, ok := secret.Data[nbSetupKey.Spec.SecretKeyRef.Key]
if !ok { if !ok {
ctrl.Log.Error(fmt.Errorf("invalid NBSetupKey"), "secret key referenced not found", "namespace", req.Namespace, "name", req.Name) logger.Error(fmt.Errorf("invalid NBSetupKey"), "secret key referenced not found")
return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{Conditions: []netbirdiov1.NBSetupKeyCondition{{ return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{Conditions: []netbirdiov1.NBCondition{{
Type: netbirdiov1.Ready, Type: netbirdiov1.NBSetupKeyReady,
Status: corev1.ConditionFalse, Status: corev1.ConditionFalse,
LastProbeTime: v1.Now(), LastProbeTime: v1.Now(),
Reason: "SecretKeyNotExists", Reason: "SecretKeyNotExists",
@@ -109,17 +109,17 @@ func (r *NBSetupKeyReconciler) Reconcile(ctx context.Context, req ctrl.Request)
_, err = uuid.Parse(string(uuidBytes)) _, err = uuid.Parse(string(uuidBytes))
if err != nil { if err != nil {
ctrl.Log.Error(fmt.Errorf("invalid NBSetupKey"), "setupKey is not a valid UUID", "err", err, "namespace", req.Namespace, "name", req.Name) logger.Error(fmt.Errorf("invalid NBSetupKey"), "setupKey is not a valid UUID", "err", err)
return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{Conditions: []netbirdiov1.NBSetupKeyCondition{{ return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{Conditions: []netbirdiov1.NBCondition{{
Type: netbirdiov1.Ready, Type: netbirdiov1.NBSetupKeyReady,
Status: corev1.ConditionFalse, Status: corev1.ConditionFalse,
LastProbeTime: v1.Now(), LastProbeTime: v1.Now(),
Reason: "InvalidSetupKey", Reason: "InvalidSetupKey",
Message: "Referenced secret is not a valid SetupKey", Message: "Referenced secret is not a valid SetupKey",
}}}) }}})
} }
return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{Conditions: []netbirdiov1.NBSetupKeyCondition{{ return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{Conditions: []netbirdiov1.NBCondition{{
Type: netbirdiov1.Ready, Type: netbirdiov1.NBSetupKeyReady,
Status: corev1.ConditionTrue, Status: corev1.ConditionTrue,
LastProbeTime: v1.Now(), LastProbeTime: v1.Now(),
}}}) }}})
@@ -151,6 +151,6 @@ func (r *NBSetupKeyReconciler) SetupWithManager(mgr ctrl.Manager) error {
return nil return nil
}), }),
). // Trigger reconciliation when the labeled Busybox resource changes ). // Trigger reconciliation when a referenced secret changes
Complete(r) Complete(r)
} }
+259
View File
@@ -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)
}
@@ -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.
})
})
})
+5
View File
@@ -32,6 +32,8 @@ import (
logf "sigs.k8s.io/controller-runtime/pkg/log" logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/log/zap"
foobarv1 "k8s.io/api/core/v1"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
// +kubebuilder:scaffold:imports // +kubebuilder:scaffold:imports
) )
@@ -62,6 +64,9 @@ var _ = BeforeSuite(func() {
err = netbirdiov1.AddToScheme(scheme.Scheme) err = netbirdiov1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
err = foobarv1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
// +kubebuilder:scaffold:scheme // +kubebuilder:scaffold:scheme
By("bootstrapping test environment") By("bootstrapping test environment")
+6
View File
@@ -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
}
+41
View File
@@ -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
}
+82
View File
@@ -0,0 +1,82 @@
package v1
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
netbird "github.com/netbirdio/netbird/management/client/rest"
)
// nolint:unused
// log is for logging in this package.
var nbgrouplog = logf.Log.WithName("nbgroup-resource")
// SetupNBGroupWebhookWithManager registers the webhook for NBGroup in the manager.
func SetupNBGroupWebhookWithManager(mgr ctrl.Manager, managementURL, apiKey string) error {
return ctrl.NewWebhookManagedBy(mgr).For(&netbirdiov1.NBGroup{}).
WithValidator(&NBGroupCustomValidator{netbird: netbird.New(managementURL, apiKey), client: mgr.GetClient()}).
Complete()
}
// NBGroupCustomValidator struct is responsible for validating the NBGroup resource
// when it is created, updated, or deleted.
type NBGroupCustomValidator struct {
netbird *netbird.Client
client client.Client
}
var _ webhook.CustomValidator = &NBGroupCustomValidator{}
// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type NBGroup.
func (v *NBGroupCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
return nil, nil
}
// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type NBGroup.
func (v *NBGroupCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
return nil, nil
}
// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type NBGroup.
func (v *NBGroupCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
nbgroup, ok := obj.(*netbirdiov1.NBGroup)
if !ok {
return nil, fmt.Errorf("expected a NBGroup object but got %T", obj)
}
nbgrouplog.Info("Validation for NBGroup upon deletion", "name", nbgroup.GetName())
for _, o := range nbgroup.OwnerReferences {
if o.Kind == (&netbirdiov1.NBResource{}).Kind {
var nbResource netbirdiov1.NBResource
err := v.client.Get(ctx, types.NamespacedName{Namespace: nbgroup.Namespace, Name: o.Name}, &nbResource)
if err != nil && !errors.IsNotFound(err) {
return nil, err
}
if err == nil && nbResource.DeletionTimestamp == nil {
return nil, fmt.Errorf("group attached to NBResource %s/%s", nbgroup.Namespace, o.Name)
}
}
if o.Kind == (&netbirdiov1.NBRoutingPeer{}).Kind {
var nbResource netbirdiov1.NBRoutingPeer
err := v.client.Get(ctx, types.NamespacedName{Namespace: nbgroup.Namespace, Name: o.Name}, &nbResource)
if err != nil && !errors.IsNotFound(err) {
return nil, err
}
if err == nil && nbResource.DeletionTimestamp == nil {
return nil, fmt.Errorf("group attached to NBRoutingPeer %s/%s", nbgroup.Namespace, o.Name)
}
}
}
return nil, nil
}
@@ -0,0 +1,56 @@
package v1
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
// TODO (user): Add any additional imports if needed
)
var _ = Describe("NBGroup Webhook", func() {
var (
obj *netbirdiov1.NBGroup
oldObj *netbirdiov1.NBGroup
validator NBGroupCustomValidator
)
BeforeEach(func() {
Skip("Not implemented yet")
obj = &netbirdiov1.NBGroup{}
oldObj = &netbirdiov1.NBGroup{}
validator = NBGroupCustomValidator{}
Expect(validator).NotTo(BeNil(), "Expected validator to be initialized")
Expect(oldObj).NotTo(BeNil(), "Expected oldObj to be initialized")
Expect(obj).NotTo(BeNil(), "Expected obj to be initialized")
// TODO (user): Add any setup logic common to all tests
})
AfterEach(func() {
// TODO (user): Add any teardown logic common to all tests
})
Context("When creating or updating NBGroup under Validating Webhook", func() {
// TODO (user): Add logic for validating webhooks
// Example:
// It("Should deny creation if a required field is missing", func() {
// By("simulating an invalid creation scenario")
// obj.SomeRequiredField = ""
// Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred())
// })
//
// It("Should admit creation if all required fields are present", func() {
// By("simulating an invalid creation scenario")
// obj.SomeRequiredField = "valid_value"
// Expect(validator.ValidateCreate(ctx, obj)).To(BeNil())
// })
//
// It("Should validate updates correctly", func() {
// By("simulating a valid update scenario")
// oldObj.SomeRequiredField = "updated_value"
// obj.SomeRequiredField = "updated_value"
// Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil())
// })
})
})
+70
View File
@@ -0,0 +1,70 @@
package v1
import (
"context"
"fmt"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
"github.com/netbirdio/kubernetes-operator/internal/controller"
netbird "github.com/netbirdio/netbird/management/client/rest"
)
// nolint:unused
// log is for logging in this package.
var nbresourcelog = logf.Log.WithName("nbresource-resource")
// SetupNBResourceWebhookWithManager registers the webhook for NBResource in the manager.
func SetupNBResourceWebhookWithManager(mgr ctrl.Manager, managementURL, apiKey string) error {
return ctrl.NewWebhookManagedBy(mgr).For(&netbirdiov1.NBResource{}).
WithValidator(&NBResourceCustomValidator{netbird: netbird.New(managementURL, apiKey), client: mgr.GetClient()}).
Complete()
}
// NBResourceCustomValidator struct is responsible for validating the NBResource resource
// when it is created, updated, or deleted.
type NBResourceCustomValidator struct {
netbird *netbird.Client
client client.Client
}
var _ webhook.CustomValidator = &NBResourceCustomValidator{}
// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type NBResource.
func (v *NBResourceCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
return nil, nil
}
// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type NBResource.
func (v *NBResourceCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
return nil, nil
}
// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type NBResource.
func (v *NBResourceCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
nbresource, ok := obj.(*netbirdiov1.NBResource)
if !ok {
return nil, fmt.Errorf("expected a NBResource object but got %T", obj)
}
nbresourcelog.Info("Validation for NBResource upon deletion", "name", nbresource.GetName())
var svc corev1.Service
err := v.client.Get(ctx, types.NamespacedName{Namespace: nbresource.Namespace, Name: nbresource.Name}, &svc)
if err != nil {
return nil, err
}
if _, ok := svc.Annotations[controller.ServiceExposeAnnotation]; ok && svc.DeletionTimestamp == nil {
return nil, fmt.Errorf("service %s/%s still has netbird.io/expose annotation", svc.Namespace, svc.Name)
}
return nil, nil
}
@@ -0,0 +1,56 @@
package v1
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
// TODO (user): Add any additional imports if needed
)
var _ = Describe("NBResource Webhook", func() {
var (
obj *netbirdiov1.NBResource
oldObj *netbirdiov1.NBResource
validator NBResourceCustomValidator
)
BeforeEach(func() {
Skip("Not implemented yet")
obj = &netbirdiov1.NBResource{}
oldObj = &netbirdiov1.NBResource{}
validator = NBResourceCustomValidator{}
Expect(validator).NotTo(BeNil(), "Expected validator to be initialized")
Expect(oldObj).NotTo(BeNil(), "Expected oldObj to be initialized")
Expect(obj).NotTo(BeNil(), "Expected obj to be initialized")
// TODO (user): Add any setup logic common to all tests
})
AfterEach(func() {
// TODO (user): Add any teardown logic common to all tests
})
Context("When creating or updating NBResource under Validating Webhook", func() {
// TODO (user): Add logic for validating webhooks
// Example:
// It("Should deny creation if a required field is missing", func() {
// By("simulating an invalid creation scenario")
// obj.SomeRequiredField = ""
// Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred())
// })
//
// It("Should admit creation if all required fields are present", func() {
// By("simulating an invalid creation scenario")
// obj.SomeRequiredField = "valid_value"
// Expect(validator.ValidateCreate(ctx, obj)).To(BeNil())
// })
//
// It("Should validate updates correctly", func() {
// By("simulating a valid update scenario")
// oldObj.SomeRequiredField = "updated_value"
// obj.SomeRequiredField = "updated_value"
// Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil())
// })
})
})
@@ -0,0 +1,76 @@
package v1
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
netbird "github.com/netbirdio/netbird/management/client/rest"
)
// nolint:unused
// log is for logging in this package.
var nbroutingpeerlog = logf.Log.WithName("nbroutingpeer-resource")
// SetupNBRoutingPeerWebhookWithManager registers the webhook for NBRoutingPeer in the manager.
func SetupNBRoutingPeerWebhookWithManager(mgr ctrl.Manager, managementURL, apiKey string) error {
return ctrl.NewWebhookManagedBy(mgr).For(&netbirdiov1.NBRoutingPeer{}).
WithValidator(&NBRoutingPeerCustomValidator{netbird: netbird.New(managementURL, apiKey), client: mgr.GetClient()}).
Complete()
}
// NBRoutingPeerCustomValidator struct is responsible for validating the NBRoutingPeer resource
// when it is created, updated, or deleted.
type NBRoutingPeerCustomValidator struct {
netbird *netbird.Client
client client.Client
}
var _ webhook.CustomValidator = &NBRoutingPeerCustomValidator{}
// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type NBRoutingPeer.
func (v *NBRoutingPeerCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
return nil, nil
}
// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type NBRoutingPeer.
func (v *NBRoutingPeerCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
return nil, nil
}
// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type NBRoutingPeer.
func (v *NBRoutingPeerCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
nbroutingpeer, ok := obj.(*netbirdiov1.NBRoutingPeer)
if !ok {
return nil, fmt.Errorf("expected a NBRoutingPeer object but got %T", obj)
}
nbroutingpeerlog.Info("Validation for NBRoutingPeer upon deletion", "name", nbroutingpeer.GetName())
if nbroutingpeer.Status.NetworkID == nil {
return nil, nil
}
var nbResources netbirdiov1.NBResourceList
err := v.client.List(ctx, &nbResources)
if err != nil {
return nil, err
}
for _, r := range nbResources.Items {
if r.Spec.NetworkID == *nbroutingpeer.Status.NetworkID {
err = v.client.Delete(ctx, &r, client.DryRunAll)
if err != nil {
return nil, fmt.Errorf("%s/%s: %w", r.Namespace, r.Name, err)
}
}
}
return nil, nil
}
@@ -0,0 +1,56 @@
package v1
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
// TODO (user): Add any additional imports if needed
)
var _ = Describe("NBRoutingPeer Webhook", func() {
var (
obj *netbirdiov1.NBRoutingPeer
oldObj *netbirdiov1.NBRoutingPeer
validator NBRoutingPeerCustomValidator
)
BeforeEach(func() {
Skip("Not implemented yet")
obj = &netbirdiov1.NBRoutingPeer{}
oldObj = &netbirdiov1.NBRoutingPeer{}
validator = NBRoutingPeerCustomValidator{}
Expect(validator).NotTo(BeNil(), "Expected validator to be initialized")
Expect(oldObj).NotTo(BeNil(), "Expected oldObj to be initialized")
Expect(obj).NotTo(BeNil(), "Expected obj to be initialized")
// TODO (user): Add any setup logic common to all tests
})
AfterEach(func() {
// TODO (user): Add any teardown logic common to all tests
})
Context("When creating or updating NBRoutingPeer under Validating Webhook", func() {
// TODO (user): Add logic for validating webhooks
// Example:
// It("Should deny creation if a required field is missing", func() {
// By("simulating an invalid creation scenario")
// obj.SomeRequiredField = ""
// Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred())
// })
//
// It("Should admit creation if all required fields are present", func() {
// By("simulating an invalid creation scenario")
// obj.SomeRequiredField = "valid_value"
// Expect(validator.ValidateCreate(ctx, obj)).To(BeNil())
// })
//
// It("Should validate updates correctly", func() {
// By("simulating a valid update scenario")
// oldObj.SomeRequiredField = "updated_value"
// obj.SomeRequiredField = "updated_value"
// Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil())
// })
})
})
+1 -1
View File
@@ -83,7 +83,7 @@ func (d *PodNetbirdInjector) Default(ctx context.Context, obj runtime.Object) er
// ensure the NBSetupKey is ready. // ensure the NBSetupKey is ready.
ready := false ready := false
for _, c := range nbSetupKey.Status.Conditions { for _, c := range nbSetupKey.Status.Conditions {
if c.Type == netbirdiov1.Ready { if c.Type == netbirdiov1.NBSetupKeyReady {
ready = c.Status == corev1.ConditionTrue ready = c.Status == corev1.ConditionTrue
} }
} }
+2 -2
View File
@@ -108,9 +108,9 @@ var _ = Describe("Pod Webhook", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
sk.Status = netbirdiov1.NBSetupKeyStatus{ sk.Status = netbirdiov1.NBSetupKeyStatus{
Conditions: []netbirdiov1.NBSetupKeyCondition{ Conditions: []netbirdiov1.NBCondition{
{ {
Type: netbirdiov1.Ready, Type: netbirdiov1.NBSetupKeyReady,
Status: corev1.ConditionTrue, Status: corev1.ConditionTrue,
}, },
}, },
@@ -124,6 +124,15 @@ var _ = BeforeSuite(func() {
err = SetupNBSetupKeyWebhookWithManager(mgr) err = SetupNBSetupKeyWebhookWithManager(mgr)
Expect(err).NotTo(HaveOccurred()) 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 // +kubebuilder:scaffold:webhook
go func() { go func() {
+683 -26
View File
@@ -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 <key,value,effect> using the matching operator <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 # Source: kubernetes-operator/crds/netbird.io_nbsetupkeys.yaml
--- ---
@@ -77,8 +654,7 @@ spec:
properties: properties:
conditions: conditions:
items: items:
description: NBSetupKeyCondition defines a condition in NBSetupKey description: NBCondition defines a condition in NBSetupKey status.
status.
properties: properties:
lastProbeTime: lastProbeTime:
description: Last time we probed the condition. description: Last time we probed the condition.
@@ -124,7 +700,7 @@ kind: ServiceAccount
metadata: metadata:
name: kubernetes-operator name: kubernetes-operator
labels: 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/name: kubernetes-operator
app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator
app.kubernetes.io/version: "v0.1.0" app.kubernetes.io/version: "v0.1.0"
@@ -137,7 +713,7 @@ kind: ClusterRole
metadata: metadata:
name: kubernetes-operator name: kubernetes-operator
labels: 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/name: kubernetes-operator
app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator
app.kubernetes.io/version: "v0.1.0" app.kubernetes.io/version: "v0.1.0"
@@ -188,7 +764,7 @@ kind: ClusterRoleBinding
metadata: metadata:
name: kubernetes-operator name: kubernetes-operator
labels: 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/name: kubernetes-operator
app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator
app.kubernetes.io/version: "v0.1.0" app.kubernetes.io/version: "v0.1.0"
@@ -200,7 +776,7 @@ roleRef:
subjects: subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: kubernetes-operator name: kubernetes-operator
namespace: default namespace: production
--- ---
# Source: kubernetes-operator/templates/rbac.yaml # Source: kubernetes-operator/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
@@ -208,7 +784,7 @@ kind: Role
metadata: metadata:
name: kubernetes-operator name: kubernetes-operator
labels: 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/name: kubernetes-operator
app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator
app.kubernetes.io/version: "v0.1.0" app.kubernetes.io/version: "v0.1.0"
@@ -252,7 +828,7 @@ kind: RoleBinding
metadata: metadata:
name: kubernetes-operator name: kubernetes-operator
labels: 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/name: kubernetes-operator
app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator
app.kubernetes.io/version: "v0.1.0" app.kubernetes.io/version: "v0.1.0"
@@ -264,7 +840,7 @@ roleRef:
subjects: subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: kubernetes-operator name: kubernetes-operator
namespace: default namespace: production
--- ---
# Source: kubernetes-operator/templates/service.yaml # Source: kubernetes-operator/templates/service.yaml
apiVersion: v1 apiVersion: v1
@@ -272,7 +848,7 @@ kind: Service
metadata: metadata:
name: kubernetes-operator-metrics name: kubernetes-operator-metrics
labels: 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/name: kubernetes-operator
app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator
app.kubernetes.io/version: "v0.1.0" app.kubernetes.io/version: "v0.1.0"
@@ -294,7 +870,7 @@ kind: Service
metadata: metadata:
name: kubernetes-operator-webhook-service name: kubernetes-operator-webhook-service
labels: 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/name: kubernetes-operator
app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator
app.kubernetes.io/version: "v0.1.0" app.kubernetes.io/version: "v0.1.0"
@@ -317,7 +893,7 @@ metadata:
name: kubernetes-operator name: kubernetes-operator
labels: labels:
app.kubernetes.io/component: operator 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/name: kubernetes-operator
app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator
app.kubernetes.io/version: "v0.1.0" app.kubernetes.io/version: "v0.1.0"
@@ -332,7 +908,7 @@ spec:
metadata: metadata:
labels: labels:
app.kubernetes.io/component: operator 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/name: kubernetes-operator
app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator
app.kubernetes.io/version: "v0.1.0" app.kubernetes.io/version: "v0.1.0"
@@ -400,17 +976,17 @@ apiVersion: cert-manager.io/v1
kind: Certificate kind: Certificate
metadata: metadata:
name: kubernetes-operator-serving-cert name: kubernetes-operator-serving-cert
namespace: default namespace: production
labels: 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/name: kubernetes-operator
app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator
app.kubernetes.io/version: "v0.1.0" app.kubernetes.io/version: "v0.1.0"
app.kubernetes.io/managed-by: Helm app.kubernetes.io/managed-by: Helm
spec: spec:
dnsNames: dnsNames:
- kubernetes-operator-webhook-service.default.svc - kubernetes-operator-webhook-service.production.svc
- kubernetes-operator-webhook-service.default.svc.cluster.local - kubernetes-operator-webhook-service.production.svc.cluster.local
issuerRef: issuerRef:
kind: Issuer kind: Issuer
name: kubernetes-operator-selfsigned-issuer name: kubernetes-operator-selfsigned-issuer
@@ -421,9 +997,9 @@ apiVersion: cert-manager.io/v1
kind: Issuer kind: Issuer
metadata: metadata:
name: kubernetes-operator-selfsigned-issuer name: kubernetes-operator-selfsigned-issuer
namespace: default namespace: production
labels: 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/name: kubernetes-operator
app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator
app.kubernetes.io/version: "v0.1.0" app.kubernetes.io/version: "v0.1.0"
@@ -436,10 +1012,10 @@ apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration kind: MutatingWebhookConfiguration
metadata: metadata:
annotations: 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 name: kubernetes-operator-mpod-webhook
labels: 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/name: kubernetes-operator
app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator
app.kubernetes.io/version: "v0.1.0" app.kubernetes.io/version: "v0.1.0"
@@ -448,7 +1024,7 @@ webhooks:
- clientConfig: - clientConfig:
service: service:
name: kubernetes-operator-webhook-service name: kubernetes-operator-webhook-service
namespace: default namespace: production
path: /mutate--v1-pod path: /mutate--v1-pod
failurePolicy: Fail failurePolicy: Fail
name: mpod-v1.netbird.io name: mpod-v1.netbird.io
@@ -476,10 +1052,10 @@ apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration kind: ValidatingWebhookConfiguration
metadata: metadata:
annotations: 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 name: kubernetes-operator-vnbsetupkey-webhook
labels: 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/name: kubernetes-operator
app.kubernetes.io/instance: kubernetes-operator app.kubernetes.io/instance: kubernetes-operator
app.kubernetes.io/version: "v0.1.0" app.kubernetes.io/version: "v0.1.0"
@@ -488,7 +1064,7 @@ webhooks:
- clientConfig: - clientConfig:
service: service:
name: kubernetes-operator-webhook-service name: kubernetes-operator-webhook-service
namespace: default namespace: production
path: /validate-netbird-io-v1-nbsetupkey path: /validate-netbird-io-v1-nbsetupkey
failurePolicy: Fail failurePolicy: Fail
name: vnbsetupkey-v1.netbird.io name: vnbsetupkey-v1.netbird.io
@@ -503,5 +1079,86 @@ webhooks:
- CREATE - CREATE
- UPDATE - UPDATE
resources: resources:
- "*/*" - "nbsetupkeys"
sideEffects: None 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