diff --git a/.golangci.yml b/.golangci.yml index ed003c99..50ad8452 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -65,6 +65,8 @@ linters: alias: admissionv1 - pkg: k8s.io/api/policy/v1 alias: policyv1 + - pkg: "k8s.io/api/discovery/v1" + alias: discoveryv1 - pkg: k8s.io/client-go/applyconfigurations/meta/v1 alias: metav1ac - pkg: k8s.io/client-go/applyconfigurations/core/v1 @@ -75,6 +77,8 @@ linters: alias: appsv1ac - pkg: k8s.io/client-go/applyconfigurations/policy/v1 alias: policyv1ac + - pkg: k8s.io/client-go/applyconfigurations/discovery/v1 + alias: discoveryv1ac - pkg: sigs.k8s.io/gateway-api/apis/v1 alias: gwv1 diff --git a/PROJECT b/PROJECT index 5564e55e..463b61b3 100644 --- a/PROJECT +++ b/PROJECT @@ -123,4 +123,12 @@ resources: kind: ClusterProxy path: github.com/netbirdio/kubernetes-operator/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: netbird.io + kind: NetworkEgress + path: github.com/netbirdio/kubernetes-operator/api/v1alpha1 + version: v1alpha1 version: "3" diff --git a/README.md b/README.md index b294f601..250768f3 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,10 @@ helm upgrade --install --create-namespace -n netbird netbird-operator oci://ghcr | Kind | API Version | |------|-------------| +| [SetupKey](docs/api-reference.md#setupkey) | `netbird.io/v1alpha1` | | [Group](docs/api-reference.md#group) | `netbird.io/v1alpha1` | -| [NetworkResource](docs/api-reference.md#networkresource) | `netbird.io/v1alpha1` | | [NetworkRouter](docs/api-reference.md#networkrouter) | `netbird.io/v1alpha1` | -| [SetupKey](docs/api-reference.md#setupkey) | `netbird.io/v1alpha1` | +| [NetworkResource](docs/api-reference.md#networkresource) | `netbird.io/v1alpha1` | +| [NetworkEgress](docs/api-reference.md#networkegress) | `netbird.io/v1alpha1` | | [SidecarProfile](docs/api-reference.md#sidecarprofile) | `netbird.io/v1alpha1` | | [ClusterProxy](docs/api-reference.md#clusterproxy) | `netbird.io/v1alpha1` | diff --git a/api/v1alpha1/networkegress_types.go b/api/v1alpha1/networkegress_types.go new file mode 100644 index 00000000..595159d4 --- /dev/null +++ b/api/v1alpha1/networkegress_types.go @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// NetworkEgressSpec defines the desired state of NetworkEgress. +type NetworkEgressSpec struct { + // NetworkRouterRef is a reference to the network and router where the resource will be created. + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" + NetworkRouterRef CrossNamespaceReference `json:"networkRouterRef"` + + // Target for egress traffic. + Target NetworkEgressTarget `json:"target"` + + // Ports to the resource to route. + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:Required + Ports []NetworkEgressPort `json:"ports"` +} + +// NetworkEgressTarget describes a single allowed egress destination. +// Exactly one of IP or FQDN must be set. +// +kubebuilder:validation:XValidation:rule="(has(self.ip) ? 1 : 0) + (has(self.fqdn) ? 1 : 0) == 1",message="exactly one of ip or fqdn must be set" +type NetworkEgressTarget struct { + // IP targets a single specific IP address (not a CIDR range). + // +optional + IP *NetworkEgressIPTarget `json:"ip,omitempty"` + + // FQDN targets an exact domain name (no wildcards). + // +optional + FQDN *NetworkEgressFQDNTarget `json:"fqdn,omitempty"` +} + +// NetworkEgressIPTarget is a single IPv4 or IPv6 address. +type NetworkEgressIPTarget struct { + // Address is a single IP address. + // +kubebuilder:validation:Required + // +kubebuilder:validation:XValidation:rule="isIP(self)",message="address must be a valid IPv4 or IPv6 address" + Address string `json:"address"` +} + +// NetworkEgressFQDNTarget matches traffic by an exact domain name (no wildcards). +type NetworkEgressFQDNTarget struct { + // Hostname is a fully qualified domain name to match exactly. + // +kubebuilder:validation:Required + // +kubebuilder:validation:Pattern=`^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$` + Hostname string `json:"hostname"` +} + +type NetworkEgressPort struct { + // Name of the port. + // +required + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=15 + // +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?$` + Name string `json:"name,omitempty"` + + // The port that will be exposed by this service. + // +required + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=65535 + Port int32 `json:"port"` +} + +// NetworkEgressStatus defines the observed state of NetworkEgress. +type NetworkEgressStatus struct { + // ObservedGeneration is the last reconciled generation. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + + // Conditions holds the conditions for the NetworkEgress. + // +listType=map + // +listMapKey=type + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource +// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description="" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="" + +// NetworkEgress is the Schema for the networkegresses API. +type NetworkEgress struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // +required + Spec NetworkEgressSpec `json:"spec"` + + // +kubebuilder:default={"observedGeneration":-1} + Status NetworkEgressStatus `json:"status,omitempty"` +} + +// GetConditions returns the status conditions of the object. +func (n *NetworkEgress) GetConditions() []metav1.Condition { + return n.Status.Conditions +} + +// SetConditions sets the status conditions on the object. +func (n *NetworkEgress) SetConditions(conditions []metav1.Condition) { + n.Status.Conditions = conditions +} + +// +kubebuilder:object:root=true + +// NetworkEgressList contains a list of NetworkEgress +type NetworkEgressList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitzero"` + Items []NetworkEgress `json:"items"` +} + +func init() { + SchemeBuilder.Register(&NetworkEgress{}, &NetworkEgressList{}) +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index fbf0304b..2823b0d9 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -313,6 +313,179 @@ func (in *GroupStatus) DeepCopy() *GroupStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkEgress) DeepCopyInto(out *NetworkEgress) { + *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 NetworkEgress. +func (in *NetworkEgress) DeepCopy() *NetworkEgress { + if in == nil { + return nil + } + out := new(NetworkEgress) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NetworkEgress) 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 *NetworkEgressFQDNTarget) DeepCopyInto(out *NetworkEgressFQDNTarget) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkEgressFQDNTarget. +func (in *NetworkEgressFQDNTarget) DeepCopy() *NetworkEgressFQDNTarget { + if in == nil { + return nil + } + out := new(NetworkEgressFQDNTarget) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkEgressIPTarget) DeepCopyInto(out *NetworkEgressIPTarget) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkEgressIPTarget. +func (in *NetworkEgressIPTarget) DeepCopy() *NetworkEgressIPTarget { + if in == nil { + return nil + } + out := new(NetworkEgressIPTarget) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkEgressList) DeepCopyInto(out *NetworkEgressList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NetworkEgress, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkEgressList. +func (in *NetworkEgressList) DeepCopy() *NetworkEgressList { + if in == nil { + return nil + } + out := new(NetworkEgressList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NetworkEgressList) 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 *NetworkEgressPort) DeepCopyInto(out *NetworkEgressPort) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkEgressPort. +func (in *NetworkEgressPort) DeepCopy() *NetworkEgressPort { + if in == nil { + return nil + } + out := new(NetworkEgressPort) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkEgressSpec) DeepCopyInto(out *NetworkEgressSpec) { + *out = *in + out.NetworkRouterRef = in.NetworkRouterRef + in.Target.DeepCopyInto(&out.Target) + if in.Ports != nil { + in, out := &in.Ports, &out.Ports + *out = make([]NetworkEgressPort, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkEgressSpec. +func (in *NetworkEgressSpec) DeepCopy() *NetworkEgressSpec { + if in == nil { + return nil + } + out := new(NetworkEgressSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkEgressStatus) DeepCopyInto(out *NetworkEgressStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkEgressStatus. +func (in *NetworkEgressStatus) DeepCopy() *NetworkEgressStatus { + if in == nil { + return nil + } + out := new(NetworkEgressStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkEgressTarget) DeepCopyInto(out *NetworkEgressTarget) { + *out = *in + if in.IP != nil { + in, out := &in.IP, &out.IP + *out = new(NetworkEgressIPTarget) + **out = **in + } + if in.FQDN != nil { + in, out := &in.FQDN, &out.FQDN + *out = new(NetworkEgressFQDNTarget) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkEgressTarget. +func (in *NetworkEgressTarget) DeepCopy() *NetworkEgressTarget { + if in == nil { + return nil + } + out := new(NetworkEgressTarget) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NetworkResource) DeepCopyInto(out *NetworkResource) { *out = *in diff --git a/charts/netbird-operator/crds/netbird.io_networkegresses.yaml b/charts/netbird-operator/crds/netbird.io_networkegresses.yaml new file mode 100644 index 00000000..42a5350a --- /dev/null +++ b/charts/netbird-operator/crds/netbird.io_networkegresses.yaml @@ -0,0 +1,200 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.20.1 + name: networkegresses.netbird.io +spec: + group: netbird.io + names: + kind: NetworkEgress + listKind: NetworkEgressList + plural: networkegresses + singular: networkegress + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: NetworkEgress is the Schema for the networkegresses 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: NetworkEgressSpec defines the desired state of NetworkEgress. + properties: + networkRouterRef: + description: NetworkRouterRef is a reference to the network and router + where the resource will be created. + properties: + name: + description: Name of the referent. + type: string + namespace: + description: Namespace of the referent. + type: string + required: + - name + - namespace + type: object + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + ports: + description: Ports to the resource to route. + items: + properties: + name: + description: Name of the port. + maxLength: 15 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: The port that will be exposed by this service. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + - port + type: object + minItems: 1 + type: array + target: + description: Target for egress traffic. + properties: + fqdn: + description: FQDN targets an exact domain name (no wildcards). + properties: + hostname: + description: Hostname is a fully qualified domain name to + match exactly. + pattern: ^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$ + type: string + required: + - hostname + type: object + ip: + description: IP targets a single specific IP address (not a CIDR + range). + properties: + address: + description: Address is a single IP address. + type: string + x-kubernetes-validations: + - message: address must be a valid IPv4 or IPv6 address + rule: isIP(self) + required: + - address + type: object + type: object + x-kubernetes-validations: + - message: exactly one of ip or fqdn must be set + rule: '(has(self.ip) ? 1 : 0) + (has(self.fqdn) ? 1 : 0) == 1' + required: + - networkRouterRef + - ports + - target + type: object + status: + default: + observedGeneration: -1 + description: NetworkEgressStatus defines the observed state of NetworkEgress. + properties: + conditions: + description: Conditions holds the conditions for the NetworkEgress. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + observedGeneration: + description: ObservedGeneration is the last reconciled generation. + format: int64 + type: integer + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/netbird-operator/templates/rbac.yaml b/charts/netbird-operator/templates/rbac.yaml index 8909c436..2b2873b9 100644 --- a/charts/netbird-operator/templates/rbac.yaml +++ b/charts/netbird-operator/templates/rbac.yaml @@ -38,6 +38,7 @@ rules: - groups - networkrouters - networkresources + - networkegresses - sidecarprofiles - clusterproxies verbs: @@ -59,6 +60,7 @@ rules: - groups/status - networkrouters/status - networkresources/status + - networkregresses/status - sidecarprofiles/status - clusterproxies/status verbs: @@ -76,19 +78,60 @@ rules: - groups/finalizers - networkrouters/finalizers - networkresources/finalizers + - networkegresses/finalizers - sidecarprofiles/finalizers verbs: - update +- apiGroups: + - "discovery.k8s.io" + resources: + - endpointslices + verbs: + - get + - patch + - update + - list + - watch + - create + - delete +- apiGroups: + - "rbac.authorization.k8s.io" + resources: + - roles + - rolebindings + verbs: + - get + - list + - watch + - create + - update + - patch + - delete - apiGroups: - "" resources: - - services + - configmaps + - serviceaccounts verbs: - get - list - watch + - create - update - patch + - delete +- apiGroups: + - "" + resources: + - services + verbs: + - get + - patch + - update + - list + - watch + - create + - delete - apiGroups: - "" resources: @@ -197,18 +240,6 @@ metadata: labels: {{- include "netbird-operator.labels" . | nindent 4 }} rules: -- apiGroups: - - "" - resources: - - configmaps - verbs: - - get - - list - - watch - - create - - update - - patch - - delete - apiGroups: - coordination.k8s.io resources: diff --git a/cmd/main.go b/cmd/main.go index eb57c960..5ceaeda6 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -312,6 +312,19 @@ func main() { setupLog.Error(err, "Failed to create controller", "controller", "NetworkResource") os.Exit(1) } + if err := (&controller.NetworkEgressReconciler{ + Client: mgr.GetClient(), + Netbird: nbClient, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "Failed to create controller", "controller", "NetworkEgress") + os.Exit(1) + } + if err := (&controller.ForwarderServiceReconciler{ + Client: mgr.GetClient(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "Failed to create controller", "controller", "ForwarderService") + os.Exit(1) + } if err := (&controller.ClusterProxyReconciler{ Client: mgr.GetClient(), ApiKey: netbirdAPIKey, diff --git a/config/crd/bases/netbird.io_networkegresses.yaml b/config/crd/bases/netbird.io_networkegresses.yaml new file mode 100644 index 00000000..42a5350a --- /dev/null +++ b/config/crd/bases/netbird.io_networkegresses.yaml @@ -0,0 +1,200 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.20.1 + name: networkegresses.netbird.io +spec: + group: netbird.io + names: + kind: NetworkEgress + listKind: NetworkEgressList + plural: networkegresses + singular: networkegress + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: NetworkEgress is the Schema for the networkegresses 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: NetworkEgressSpec defines the desired state of NetworkEgress. + properties: + networkRouterRef: + description: NetworkRouterRef is a reference to the network and router + where the resource will be created. + properties: + name: + description: Name of the referent. + type: string + namespace: + description: Namespace of the referent. + type: string + required: + - name + - namespace + type: object + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + ports: + description: Ports to the resource to route. + items: + properties: + name: + description: Name of the port. + maxLength: 15 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: The port that will be exposed by this service. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + - port + type: object + minItems: 1 + type: array + target: + description: Target for egress traffic. + properties: + fqdn: + description: FQDN targets an exact domain name (no wildcards). + properties: + hostname: + description: Hostname is a fully qualified domain name to + match exactly. + pattern: ^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$ + type: string + required: + - hostname + type: object + ip: + description: IP targets a single specific IP address (not a CIDR + range). + properties: + address: + description: Address is a single IP address. + type: string + x-kubernetes-validations: + - message: address must be a valid IPv4 or IPv6 address + rule: isIP(self) + required: + - address + type: object + type: object + x-kubernetes-validations: + - message: exactly one of ip or fqdn must be set + rule: '(has(self.ip) ? 1 : 0) + (has(self.fqdn) ? 1 : 0) == 1' + required: + - networkRouterRef + - ports + - target + type: object + status: + default: + observedGeneration: -1 + description: NetworkEgressStatus defines the observed state of NetworkEgress. + properties: + conditions: + description: Conditions holds the conditions for the NetworkEgress. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + observedGeneration: + description: ObservedGeneration is the last reconciled generation. + format: int64 + type: integer + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 0e7f6bc3..4e95db70 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -12,4 +12,5 @@ resources: - bases/netbird.io_setupkeys.yaml - bases/netbird.io_sidecarprofiles.yaml - bases/netbird.io_clusterproxies.yaml +- bases/netbird.io_networkegresses.yaml # +kubebuilder:scaffold:crdkustomizeresource diff --git a/docs/api-reference.md b/docs/api-reference.md index cd40a0de..8f765a53 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -11,6 +11,7 @@ Package v1alpha1 contains API Schema definitions for the v1alpha1 API group. ### Resource Types - [ClusterProxy](#clusterproxy) - [Group](#group) +- [NetworkEgress](#networkegress) - [NetworkResource](#networkresource) - [NetworkRouter](#networkrouter) - [SetupKey](#setupkey) @@ -105,6 +106,7 @@ _Appears in:_ _Appears in:_ +- [NetworkEgressSpec](#networkegressspec) - [NetworkResourceSpec](#networkresourcespec) | Field | Description | Default | Validation | @@ -222,6 +224,129 @@ _Appears in:_ | `Container` | InjectionModeContainer injects the client as a regular container.
| +#### NetworkEgress + + + +NetworkEgress is the Schema for the networkegresses API. + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `netbird.io/v1alpha1` | | | +| `kind` _string_ | `NetworkEgress` | | | +| `kind` _string_ | 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 | | Optional: \{\}
| +| `apiVersion` _string_ | 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 | | Optional: \{\}
| +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `spec` _[NetworkEgressSpec](#networkegressspec)_ | | | Required: \{\}
| +| `status` _[NetworkEgressStatus](#networkegressstatus)_ | | \{ observedGeneration:-1 \} | | + + +#### NetworkEgressFQDNTarget + + + +NetworkEgressFQDNTarget matches traffic by an exact domain name (no wildcards). + + + +_Appears in:_ +- [NetworkEgressTarget](#networkegresstarget) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `hostname` _string_ | Hostname is a fully qualified domain name to match exactly. | | Pattern: `^([a-zA-Z0-9]([a-zA-Z0-9-]\{0,61\}[a-zA-Z0-9])?\.)+[a-zA-Z]\{2,\}$`
Required: \{\}
| + + +#### NetworkEgressIPTarget + + + +NetworkEgressIPTarget is a single IPv4 or IPv6 address. + + + +_Appears in:_ +- [NetworkEgressTarget](#networkegresstarget) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `address` _string_ | Address is a single IP address. | | Required: \{\}
| + + +#### NetworkEgressPort + + + + + + + +_Appears in:_ +- [NetworkEgressSpec](#networkegressspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _string_ | Name of the port. | | MaxLength: 15
MinLength: 1
Pattern: `^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`
Required: \{\}
| +| `port` _integer_ | The port that will be exposed by this service. | | Maximum: 65535
Minimum: 1
Required: \{\}
| + + +#### NetworkEgressSpec + + + +NetworkEgressSpec defines the desired state of NetworkEgress. + + + +_Appears in:_ +- [NetworkEgress](#networkegress) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `networkRouterRef` _[CrossNamespaceReference](#crossnamespacereference)_ | NetworkRouterRef is a reference to the network and router where the resource will be created. | | | +| `target` _[NetworkEgressTarget](#networkegresstarget)_ | Target for egress traffic. | | | +| `ports` _[NetworkEgressPort](#networkegressport) array_ | Ports to the resource to route. | | MinItems: 1
Required: \{\}
| + + +#### NetworkEgressStatus + + + +NetworkEgressStatus defines the observed state of NetworkEgress. + + + +_Appears in:_ +- [NetworkEgress](#networkegress) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `observedGeneration` _integer_ | ObservedGeneration is the last reconciled generation. | | Optional: \{\}
| +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#condition-v1-meta) array_ | Conditions holds the conditions for the NetworkEgress. | | Optional: \{\}
| + + +#### NetworkEgressTarget + + + +NetworkEgressTarget describes a single allowed egress destination. +Exactly one of IP or FQDN must be set. + + + +_Appears in:_ +- [NetworkEgressSpec](#networkegressspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `ip` _[NetworkEgressIPTarget](#networkegressiptarget)_ | IP targets a single specific IP address (not a CIDR range). | | Optional: \{\}
| +| `fqdn` _[NetworkEgressFQDNTarget](#networkegressfqdntarget)_ | FQDN targets an exact domain name (no wildcards). | | Optional: \{\}
| + + #### NetworkResource diff --git a/examples/network/networkegress.yaml b/examples/network/networkegress.yaml new file mode 100644 index 00000000..f5a9b08d --- /dev/null +++ b/examples/network/networkegress.yaml @@ -0,0 +1,15 @@ +apiVersion: netbird.io/v1alpha1 +kind: NetworkEgress +metadata: + name: nginx + namespace: default +spec: + networkRouterRef: + name: prod + namespace: netbird + target: + ip: + address: 10.96.92.40 + ports: + - name: http + port: 80 diff --git a/go.mod b/go.mod index 6a183d69..917f6328 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/go-logr/logr v1.4.3 github.com/go-openapi/testify/v2 v2.6.0 github.com/google/uuid v1.6.0 + github.com/netbirdio/kube-egress-forwarder v0.0.2 github.com/netbirdio/netbird v0.72.4 github.com/onsi/ginkgo/v2 v2.32.0 github.com/onsi/gomega v1.42.1 @@ -79,7 +80,7 @@ require ( golang.org/x/crypto v0.53.0 // indirect golang.org/x/net v0.56.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sync v0.21.0 // indirect + golang.org/x/sync v0.22.0 // indirect golang.org/x/sys v0.46.0 // indirect golang.org/x/term v0.44.0 // indirect golang.org/x/text v0.38.0 // indirect diff --git a/go.sum b/go.sum index 83e4b126..15f71acc 100644 --- a/go.sum +++ b/go.sum @@ -340,6 +340,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/netbirdio/dex v0.244.1-0.20260512110716-8d70ad8647c1 h1:4TaYr9O4xX0D2kszeOLclTiCbA3eHq3xWV+9ILJbIYs= github.com/netbirdio/dex v0.244.1-0.20260512110716-8d70ad8647c1/go.mod h1:IHH+H8vK2GfqtIt5u/5OdPh18yk0oDHuj2vz5+Goetg= +github.com/netbirdio/kube-egress-forwarder v0.0.2 h1:jUlgA8lSDKyqn9tUdRFtEadXdDCFA63ZFPenQrMGv7E= +github.com/netbirdio/kube-egress-forwarder v0.0.2/go.mod h1:Lyt9k/93DTWaeOiGQWM63WtQNj/9NnMTR/PivDgWXXE= github.com/netbirdio/management-integrations/integrations v0.0.0-20260416123949-2355d972be42 h1:F3zS5fT9xzD1OFLfcdAE+3FfyiwjGukF1hvj0jErgs8= github.com/netbirdio/management-integrations/integrations v0.0.0-20260416123949-2355d972be42/go.mod h1:n47r67ZSPgwSmT/Z1o48JjZQW9YJ6m/6Bd/uAXkL3Pg= github.com/netbirdio/netbird v0.72.4 h1:Pjs5PZqIvjJ4gqTW+RNh2sBMjsgOFd1Rf0SDoYsHqLM= @@ -509,8 +511,8 @@ golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= -golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= -golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= diff --git a/internal/controller/forwarderservice_controller.go b/internal/controller/forwarderservice_controller.go new file mode 100644 index 00000000..539dbbe5 --- /dev/null +++ b/internal/controller/forwarderservice_controller.go @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package controller + +import ( + "context" + "errors" + "fmt" + "maps" + "net/netip" + "strings" + "time" + + corev1 "k8s.io/api/core/v1" + discoveryv1 "k8s.io/api/discovery/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/types" + corev1ac "k8s.io/client-go/applyconfigurations/core/v1" + discoveryv1ac "k8s.io/client-go/applyconfigurations/discovery/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/netbirdio/kube-egress-forwarder/pkg/forwarder" + + nbv1alpha1 "github.com/netbirdio/kubernetes-operator/api/v1alpha1" + "github.com/netbirdio/kubernetes-operator/internal/k8sutil" +) + +const ( + ForwarderRouterNameLabel = "netbird.io/forwarder-router-name" + EgressRouterNameLabel = "netbird.io/egress-router-name" + EgressRouterNamespaceLabel = "netbird.io/egress-router-namespace" + LastUpdatedLabel = "netbird.io/last-updated" +) + +// ForwarderServiceReconciler reconciles a EndpointSlice object +type ForwarderServiceReconciler struct { + client.Client +} + +func (r *ForwarderServiceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + svc := &corev1.Service{} + err := r.Get(ctx, req.NamespacedName, svc) + if err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + if !svc.DeletionTimestamp.IsZero() { + return ctrl.Result{}, nil + } + + ownerRef, err := k8sutil.ControllerReference(svc, r.Scheme()) + if err != nil { + return ctrl.Result{}, err + } + + routerName, ok := svc.Labels[ForwarderRouterNameLabel] + if !ok { + return ctrl.Result{}, errors.New("missing forwarder router label") + } + + // Load port manager state. + ruleConfigmap := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: req.Name, + Namespace: req.Namespace, + }, + } + err = r.Client.Get(ctx, client.ObjectKeyFromObject(ruleConfigmap), ruleConfigmap) + if err != nil && !kerrors.IsNotFound(err) { + return ctrl.Result{}, err + } + ruleMgr, err := forwarder.NewRuleManager(ruleConfigmap.Data) + if err != nil { + return ctrl.Result{}, err + } + + // Get endpoint slices for forwarder service. + endpointSliceList := &discoveryv1.EndpointSliceList{} + err = r.Client.List(ctx, endpointSliceList, &client.MatchingLabels{discoveryv1.LabelServiceName: svc.Name}) + if err != nil { + return ctrl.Result{}, err + } + + // Get services for egress resources. + egressSvcList := &corev1.ServiceList{} + err = r.Client.List(ctx, egressSvcList, &client.MatchingLabels{EgressRouterNameLabel: routerName, EgressRouterNamespaceLabel: req.Namespace}) + if err != nil { + return ctrl.Result{}, err + } + + // Copy router endpoint slices to egress services. + lastUpdated := fmt.Sprintf("%d", time.Now().Unix()) + + for _, egressSvc := range egressSvcList.Items { + netEgress := &nbv1alpha1.NetworkEgress{ + ObjectMeta: metav1.ObjectMeta{ + Name: egressSvc.OwnerReferences[0].Name, + Namespace: egressSvc.Namespace, + }, + } + err = r.Get(ctx, client.ObjectKeyFromObject(netEgress), netEgress) + if err != nil { + return ctrl.Result{}, err + } + + targetPorts := []int32{} + for _, port := range egressSvc.Spec.Ports { + dest, err := func() (string, error) { + switch { + case netEgress.Spec.Target.IP != nil: + addr, err := netip.ParseAddr(netEgress.Spec.Target.IP.Address) + if err != nil { + return "", err + } + return netip.AddrPortFrom(addr, uint16(port.Port)).String(), nil + case netEgress.Spec.Target.FQDN != nil: + return fmt.Sprintf("%s:%d", netEgress.Spec.Target.FQDN.Hostname, port.Port), nil + } + return "", errors.New("egress target not found") + }() + if err != nil { + return ctrl.Result{}, err + } + rule := ruleMgr.Allocate(port.Protocol, dest) + targetPorts = append(targetPorts, rule.Port) + } + portACs := toPortApplyConfigurations(egressSvc.Spec.Ports, targetPorts) + + egressSvcOwnerRef, err := k8sutil.ControllerReference(&egressSvc, r.Scheme()) + if err != nil { + return ctrl.Result{}, err + } + for _, endpointSlice := range endpointSliceList.Items { + nameSuffx := strings.TrimPrefix(endpointSlice.Name, endpointSlice.GenerateName) + + labels := map[string]string{ + discoveryv1.LabelServiceName: egressSvc.Name, + discoveryv1.LabelManagedBy: "netbird-operator.netbird.io", + LastUpdatedLabel: lastUpdated, + } + maps.Copy(labels, egressSvc.Labels) + + endpointACs := toEndpointApplyConfigurations(endpointSlice.Endpoints) + endpointSliceAC := discoveryv1ac.EndpointSlice(fmt.Sprintf("%s-%s", egressSvc.Name, nameSuffx), egressSvc.Namespace). + WithLabels(labels). + WithOwnerReferences(egressSvcOwnerRef). + WithAddressType(endpointSlice.AddressType). + WithEndpoints(endpointACs...). + WithPorts(portACs...) + err = r.Client.Apply(ctx, endpointSliceAC, client.ForceOwnership) + if err != nil { + return ctrl.Result{}, err + } + } + } + + // Write the port config to the port conversion. + data, err := ruleMgr.Data() + if err != nil { + return ctrl.Result{}, err + } + cmAC := corev1ac.ConfigMap(req.Name, req.Namespace). + WithOwnerReferences(ownerRef). + WithData(data) + err = r.Client.Apply(ctx, cmAC, client.ForceOwnership) + if err != nil { + return ctrl.Result{}, err + } + + // Cleanup old endpoint slices. + updateReq, err := labels.NewRequirement(LastUpdatedLabel, selection.NotEquals, []string{lastUpdated}) + if err != nil { + return ctrl.Result{}, err + } + egressNameReq, err := labels.NewRequirement(EgressRouterNameLabel, selection.Equals, []string{routerName}) + if err != nil { + return ctrl.Result{}, err + } + egressNamespaceReq, err := labels.NewRequirement(EgressRouterNamespaceLabel, selection.Equals, []string{req.Namespace}) + if err != nil { + return ctrl.Result{}, err + } + sourceReq, err := labels.NewRequirement(discoveryv1.LabelServiceName, selection.NotEquals, []string{svc.Name}) + if err != nil { + return ctrl.Result{}, err + } + deleteSelector := labels.NewSelector().Add(*updateReq).Add(*egressNameReq).Add(*egressNamespaceReq).Add(*sourceReq) + + err = r.Client.List(ctx, endpointSliceList, client.MatchingLabelsSelector{Selector: deleteSelector}) + if err != nil { + return ctrl.Result{}, err + } + for _, item := range endpointSliceList.Items { + if err := r.Client.Delete(ctx, &item); err != nil && !kerrors.IsNotFound(err) { + return ctrl.Result{}, err + } + } + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *ForwarderServiceReconciler) SetupWithManager(mgr ctrl.Manager) error { + forwarderSvcSelector := metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: ForwarderRouterNameLabel, + Operator: metav1.LabelSelectorOpExists, + }, + }, + } + forwarderSvcPred, err := predicate.LabelSelectorPredicate(forwarderSvcSelector) + if err != nil { + return err + } + + egressSvcSelector := metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: EgressRouterNameLabel, + Operator: metav1.LabelSelectorOpExists, + }, + { + Key: EgressRouterNamespaceLabel, + Operator: metav1.LabelSelectorOpExists, + }, + }, + } + egressSvcPred, err := predicate.LabelSelectorPredicate(egressSvcSelector) + if err != nil { + return err + } + + return ctrl.NewControllerManagedBy(mgr). + Named("forwarderservice"). + For(&corev1.Service{}, builder.WithPredicates(forwarderSvcPred)). + Owns(&discoveryv1.EndpointSlice{}, builder.WithPredicates(forwarderSvcPred)). + Owns(&corev1.ConfigMap{}). + Watches(&nbv1alpha1.NetworkEgress{}, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { + imp, ok := obj.(*nbv1alpha1.NetworkEgress) + if !ok { + return nil + } + return []reconcile.Request{ + { + NamespacedName: types.NamespacedName{ + Name: fmt.Sprintf("networkrouter-%s-forwarder", imp.Spec.NetworkRouterRef.Name), + Namespace: imp.Spec.NetworkRouterRef.Namespace, + }, + }, + } + })). + Watches(&corev1.Service{}, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { + return []reconcile.Request{ + { + NamespacedName: types.NamespacedName{ + Name: fmt.Sprintf("networkrouter-%s-forwarder", obj.GetLabels()[EgressRouterNameLabel]), + Namespace: obj.GetLabels()[EgressRouterNamespaceLabel], + }, + }, + } + }), builder.WithPredicates(egressSvcPred)). + Watches(&discoveryv1.EndpointSlice{}, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { + return []reconcile.Request{ + { + NamespacedName: types.NamespacedName{ + Name: fmt.Sprintf("networkrouter-%s-forwarder", obj.GetLabels()[EgressRouterNameLabel]), + Namespace: obj.GetLabels()[EgressRouterNamespaceLabel], + }, + }, + } + }), builder.WithPredicates(egressSvcPred)). + Complete(r) +} + +func toEndpointApplyConfigurations(endpoints []discoveryv1.Endpoint) []*discoveryv1ac.EndpointApplyConfiguration { + endpointACs := make([]*discoveryv1ac.EndpointApplyConfiguration, 0, len(endpoints)) + for _, endpoint := range endpoints { + conditionAC := discoveryv1ac.EndpointConditions() + if endpoint.Conditions.Ready != nil { + conditionAC.WithReady(*endpoint.Conditions.Ready) + } + if endpoint.Conditions.Serving != nil { + conditionAC.WithServing(*endpoint.Conditions.Serving) + } + if endpoint.Conditions.Terminating != nil { + conditionAC.WithTerminating(*endpoint.Conditions.Terminating) + } + + endpointAC := discoveryv1ac.Endpoint(). + WithAddresses(endpoint.Addresses...). + WithConditions(conditionAC) + + if endpoint.NodeName != nil { + endpointAC = endpointAC.WithNodeName(*endpoint.NodeName) + } + if endpoint.Zone != nil { + endpointAC = endpointAC.WithZone(*endpoint.Zone) + } + if endpoint.Hints != nil { + hintAC := discoveryv1ac.EndpointHints() + for _, hint := range endpoint.Hints.ForNodes { + hintAC = hintAC.WithForNodes(discoveryv1ac.ForNode().WithName(hint.Name)) + } + for _, hint := range endpoint.Hints.ForZones { + hintAC = hintAC.WithForZones(discoveryv1ac.ForZone().WithName(hint.Name)) + } + endpointAC = endpointAC.WithHints(hintAC) + } + if endpoint.TargetRef != nil { + endpointAC = endpointAC.WithTargetRef(corev1ac.ObjectReference(). + WithResourceVersion(endpoint.TargetRef.ResourceVersion). + WithFieldPath(endpoint.TargetRef.FieldPath). + WithUID(endpoint.TargetRef.UID). + WithAPIVersion(endpoint.TargetRef.APIVersion). + WithKind(endpoint.TargetRef.Kind). + WithName(endpoint.TargetRef.Name). + WithNamespace(endpoint.TargetRef.Namespace), + ) + } + + endpointACs = append(endpointACs, endpointAC) + } + return endpointACs +} + +func toPortApplyConfigurations(ports []corev1.ServicePort, targetPorts []int32) []*discoveryv1ac.EndpointPortApplyConfiguration { + portACs := make([]*discoveryv1ac.EndpointPortApplyConfiguration, 0, len(ports)) + for i, port := range ports { + portAC := discoveryv1ac.EndpointPort().WithName(port.Name).WithPort(targetPorts[i]).WithProtocol(port.Protocol) + if port.AppProtocol != nil { + portAC = portAC.WithAppProtocol(*port.AppProtocol) + } + portACs = append(portACs, portAC) + } + return portACs +} diff --git a/internal/controller/networkegress_controller.go b/internal/controller/networkegress_controller.go new file mode 100644 index 00000000..e56551f3 --- /dev/null +++ b/internal/controller/networkegress_controller.go @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package controller + +import ( + "context" + "fmt" + + "github.com/fluxcd/pkg/runtime/conditions" + "github.com/fluxcd/pkg/runtime/patch" + corev1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + corev1ac "k8s.io/client-go/applyconfigurations/core/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + netbird "github.com/netbirdio/netbird/shared/management/client/rest" + + nbv1alpha1 "github.com/netbirdio/kubernetes-operator/api/v1alpha1" + "github.com/netbirdio/kubernetes-operator/internal/k8sutil" +) + +// NetworkEgressReconciler reconciles a NetworkEgress object +type NetworkEgressReconciler struct { + client.Client + + Netbird *netbird.Client +} + +// +kubebuilder:rbac:groups=netbird.io,resources=networkegresses,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=netbird.io,resources=networkegresses/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=netbird.io,resources=networkegresses/finalizers,verbs=update + +func (r *NetworkEgressReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + netEgress := &nbv1alpha1.NetworkEgress{} + err := r.Get(ctx, req.NamespacedName, netEgress) + if err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + sp := patch.NewSerialPatcher(netEgress, r.Client) + + if !netEgress.DeletionTimestamp.IsZero() { + return ctrl.Result{}, nil + } + + ownerRef, err := k8sutil.ControllerReference(netEgress, r.Scheme()) + if err != nil { + return ctrl.Result{}, err + } + + netRouter := &nbv1alpha1.NetworkRouter{ + ObjectMeta: metav1.ObjectMeta{ + Name: netEgress.Spec.NetworkRouterRef.Name, + Namespace: netEgress.Spec.NetworkRouterRef.Namespace, + }, + } + err = r.Get(ctx, client.ObjectKeyFromObject(netRouter), netRouter) + if err != nil { + if kerrors.IsNotFound(err) { + conditions.MarkFalse(netEgress, nbv1alpha1.ReadyCondition, nbv1alpha1.DependencyReason, "Referenced NetworkRouter cannot be found.") + err = sp.Patch(ctx, netEgress) + if err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + // Create service for egress resource to do port remapping. + ports := []*corev1ac.ServicePortApplyConfiguration{} + for _, port := range netEgress.Spec.Ports { + ports = append(ports, corev1ac.ServicePort().WithName(port.Name).WithPort(port.Port)) + } + routerSvcAC := corev1ac.Service(netEgress.Name, netEgress.Namespace). + WithLabels(map[string]string{EgressRouterNameLabel: netRouter.Name, EgressRouterNamespaceLabel: netRouter.Namespace}). + WithOwnerReferences(ownerRef). + WithSpec( + corev1ac.ServiceSpec(). + WithPorts(ports...), + ) + err = r.Client.Apply(ctx, routerSvcAC, client.ForceOwnership) + if err != nil { + return ctrl.Result{}, err + } + + conditions.MarkTrue(netEgress, nbv1alpha1.ReadyCondition, nbv1alpha1.ReconciledReason, "") + err = sp.Patch(ctx, netEgress, patch.WithStatusObservedGeneration{}) + if err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *NetworkEgressReconciler) SetupWithManager(mgr ctrl.Manager) error { + err := mgr.GetFieldIndexer().IndexField(context.Background(), &nbv1alpha1.NetworkEgress{}, ".spec.networkRouterRef", func(obj client.Object) []string { + netEgress := obj.(*nbv1alpha1.NetworkEgress) + ref := netEgress.Spec.NetworkRouterRef + if ref.Name == "" { + return nil + } + if ref.Namespace == "" { + ref.Namespace = netEgress.Namespace + } + return []string{fmt.Sprintf("%s/%s", ref.Name, ref.Namespace)} + }) + if err != nil { + return err + } + + return ctrl.NewControllerManagedBy(mgr). + For(&nbv1alpha1.NetworkEgress{}). + Owns(&corev1.Service{}). + Watches( + &nbv1alpha1.NetworkRouter{}, + handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { + netEgressList := &nbv1alpha1.NetworkEgressList{} + err := r.List(ctx, netEgressList, client.MatchingFields{".spec.networkRouterRef": fmt.Sprintf("%s/%s", obj.GetName(), obj.GetNamespace())}) + if err != nil { + return nil + } + + requests := make([]reconcile.Request, len(netEgressList.Items)) + for i, item := range netEgressList.Items { + requests[i] = reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: item.Name, + Namespace: item.Namespace, + }, + } + } + return requests + }), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). + Complete(r) +} diff --git a/internal/controller/networkegress_controller_test.go b/internal/controller/networkegress_controller_test.go new file mode 100644 index 00000000..d18e7e46 --- /dev/null +++ b/internal/controller/networkegress_controller_test.go @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package controller + +import ( + "context" + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + nbv1alpha1 "github.com/netbirdio/kubernetes-operator/api/v1alpha1" +) + +var _ = Describe("NetworkEgress Controller", func() { + Context("When reconciling a resource", func() { + ctx := context.Background() + + var netEgressRec *NetworkEgressReconciler + var forwarderRec *ForwarderServiceReconciler + + nn := client.ObjectKey{ + Name: "test-resource", + Namespace: "network-egress", + } + BeforeEach(func() { + netEgressRec = &NetworkEgressReconciler{ + Client: k8sClient, + } + forwarderRec = &ForwarderServiceReconciler{ + Client: k8sClient, + } + + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: nn.Namespace, + }, + } + Expect(k8sClient.Create(ctx, ns)).To(Succeed()) + }) + + AfterEach(func() { + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: nn.Namespace, + }, + } + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(ns), ns) + if kerrors.IsNotFound(err) { + return + } + Expect(err).ToNot(HaveOccurred()) + Expect(k8sClient.Delete(ctx, ns)).To(Succeed()) + }) + + It("creates a egress service with endpoint slices", func() { + netRouter := &nbv1alpha1.NetworkRouter{ + ObjectMeta: metav1.ObjectMeta{ + Name: "egress-router", + Namespace: nn.Namespace, + }, + Spec: nbv1alpha1.NetworkRouterSpec{ + DNSZoneRef: nbv1alpha1.DNSZoneReference{ + Name: "foo.bar", + }, + }, + } + Expect(k8sClient.Create(ctx, netRouter)).To(Succeed()) + + netEgress := &nbv1alpha1.NetworkEgress{ + ObjectMeta: metav1.ObjectMeta{ + Name: nn.Name, + Namespace: nn.Namespace, + }, + Spec: nbv1alpha1.NetworkEgressSpec{ + NetworkRouterRef: nbv1alpha1.CrossNamespaceReference{ + Name: netRouter.Name, + Namespace: nn.Namespace, + }, + Target: nbv1alpha1.NetworkEgressTarget{ + FQDN: &nbv1alpha1.NetworkEgressFQDNTarget{ + Hostname: "example.com", + }, + }, + Ports: []nbv1alpha1.NetworkEgressPort{ + { + Name: "http", + Port: 80, + }, + }, + }, + } + Expect(k8sClient.Create(ctx, netEgress)).To(Succeed()) + _, err := netEgressRec.Reconcile(ctx, reconcile.Request{NamespacedName: nn}) + Expect(err).NotTo(HaveOccurred()) + + egressSvc := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: nn.Name, + Namespace: nn.Namespace, + }, + } + err = k8sClient.Get(ctx, client.ObjectKeyFromObject(egressSvc), egressSvc) + Expect(err).NotTo(HaveOccurred()) + Expect(egressSvc.Labels[EgressRouterNameLabel]).To(Equal(netRouter.Name)) + Expect(egressSvc.Labels[EgressRouterNamespaceLabel]).To(Equal(netRouter.Namespace)) + Expect(egressSvc.Spec.Ports).To(HaveLen(1)) + Expect(egressSvc.Spec.Ports[0].TargetPort.String()).To(Equal("80")) + + _, err = forwarderRec.Reconcile(ctx, reconcile.Request{NamespacedName: client.ObjectKey{Name: fmt.Sprintf("networkrouter-%s-forwarder", netRouter.Name), Namespace: nn.Namespace}}) + Expect(err).NotTo(HaveOccurred()) + }) + }) +}) diff --git a/internal/controller/networkrouter_controller.go b/internal/controller/networkrouter_controller.go index 2d630d79..42c38cf4 100644 --- a/internal/controller/networkrouter_controller.go +++ b/internal/controller/networkrouter_controller.go @@ -15,6 +15,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" policyv1 "k8s.io/api/policy/v1" + rbacv1 "k8s.io/api/rbac/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -24,6 +25,7 @@ import ( corev1ac "k8s.io/client-go/applyconfigurations/core/v1" metav1ac "k8s.io/client-go/applyconfigurations/meta/v1" policyv1ac "k8s.io/client-go/applyconfigurations/policy/v1" + rbacv1ac "k8s.io/client-go/applyconfigurations/rbac/v1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -35,6 +37,7 @@ import ( "github.com/netbirdio/kubernetes-operator/internal/k8sutil" "github.com/netbirdio/kubernetes-operator/internal/netbirdutil" nbv1alpha1ac "github.com/netbirdio/kubernetes-operator/pkg/applyconfigurations/api/v1alpha1" + "github.com/netbirdio/kubernetes-operator/pkg/version" ) type NetworkRouterReconciler struct { @@ -191,7 +194,7 @@ func (r *NetworkRouterReconciler) Reconcile(ctx context.Context, req ctrl.Reques return ctrl.Result{}, err } - // Create the deployment. + // Setup router configuration. selectorLabels := map[string]string{ "app.kubernetes.io/name": "networkrouter", "app.kubernetes.io/instance": req.Name, @@ -207,6 +210,44 @@ func (r *NetworkRouterReconciler) Reconcile(ctx context.Context, req ctrl.Reques clientImage = netRouter.Spec.Image } + // Create forwarder resources. + forwarderName := fmt.Sprintf("networkrouter-%s-forwarder", req.Name) + forwarderSvcAC := corev1ac.Service(forwarderName, req.Namespace). + WithLabels(map[string]string{ForwarderRouterNameLabel: netRouter.Name}). + WithOwnerReferences(ownerRef). + WithSpec( + corev1ac.ServiceSpec(). + WithClusterIP("None"). + WithSelector(selectorLabels), + ) + err = r.Client.Apply(ctx, forwarderSvcAC, client.ForceOwnership) + if err != nil { + return ctrl.Result{}, err + } + + saAC := corev1ac.ServiceAccount(forwarderName, req.Namespace). + WithOwnerReferences(ownerRef) + err = r.Client.Apply(ctx, saAC, client.ForceOwnership) + if err != nil { + return ctrl.Result{}, err + } + roleAC := rbacv1ac.Role(forwarderName, req.Namespace). + WithOwnerReferences(ownerRef). + WithRules(rbacv1ac.PolicyRule().WithAPIGroups("").WithResources("configmaps").WithVerbs("get", "watch")) + err = r.Client.Apply(ctx, roleAC, client.ForceOwnership) + if err != nil { + return ctrl.Result{}, err + } + roleBindAC := rbacv1ac.RoleBinding(forwarderName, req.Namespace). + WithOwnerReferences(ownerRef). + WithSubjects(rbacv1ac.Subject().WithKind(*saAC.Kind).WithName(*saAC.Name)). + WithRoleRef(rbacv1ac.RoleRef().WithKind(*roleAC.Kind).WithName(*roleAC.Name)) + err = r.Client.Apply(ctx, roleBindAC, client.ForceOwnership) + if err != nil { + return ctrl.Result{}, err + } + + // Create the deployment. podTemplateSpecAC := corev1ac.PodTemplateSpec(). WithLabels(selectorLabels). WithSpec(corev1ac.PodSpec(). @@ -232,65 +273,84 @@ func (r *NetworkRouterReconciler) Reconcile(ctx context.Context, req ctrl.Reques WithReadOnlyRootFilesystem(true), ), ). - WithContainers(corev1ac.Container(). - WithName("netbird"). - WithImage(clientImage). - WithEnv( - corev1ac.EnvVar(). - WithName("NB_SETUP_KEY"). - WithValueFrom(corev1ac.EnvVarSource(). - WithSecretKeyRef(corev1ac.SecretKeySelector(). - WithName(setupKey.SecretName()). - WithKey(SetupKeySecretKey), + WithContainers( + corev1ac.Container(). + WithName("netbird"). + WithImage(clientImage). + WithEnv( + corev1ac.EnvVar(). + WithName("NB_SETUP_KEY"). + WithValueFrom(corev1ac.EnvVarSource(). + WithSecretKeyRef(corev1ac.SecretKeySelector(). + WithName(setupKey.SecretName()). + WithKey(SetupKeySecretKey), + ), ), - ), - corev1ac.EnvVar(). - WithName("NB_MANAGEMENT_URL"). - WithValue(r.ManagementURL), - corev1ac.EnvVar(). - WithName("NB_LOG_LEVEL"). - WithValue(logLevel), - corev1ac.EnvVar(). - WithName("NB_LOG_FILE"). - WithValue("console"), - corev1ac.EnvVar(). - WithName("NB_DISABLE_PROFILES"). - WithValue("true"), - corev1ac.EnvVar(). - WithName("NB_DISABLE_UPDATE_SETTINGS"). - WithValue("true"), - corev1ac.EnvVar(). - WithName("NB_DAEMON_ADDR"). - WithValue("unix:///var/run/netbird/netbird.sock"), - corev1ac.EnvVar(). - WithName("NB_ENTRYPOINT_SERVICE_TIMEOUT"). - WithValue("0"), - ). - WithStartupProbe(corev1ac.Probe().WithExec(corev1ac.ExecAction().WithCommand("netbird", "status", "--check", "startup"))). - WithReadinessProbe(corev1ac.Probe().WithExec(corev1ac.ExecAction().WithCommand("netbird", "status", "--check", "ready"))). - WithVolumeMounts( - corev1ac.VolumeMount().WithName("netbird-run").WithMountPath("/var/run/netbird"), - corev1ac.VolumeMount().WithName("netbird-lib").WithMountPath("/var/lib/netbird"), - corev1ac.VolumeMount().WithName("ssh-etc").WithMountPath("/etc/ssh"), - corev1ac.VolumeMount().WithName("resolv-conf").WithMountPath("/etc/resolv.conf").WithSubPath("resolv.conf"), - corev1ac.VolumeMount().WithName("resolv-conf").WithMountPath("/etc/resolv.conf.original.netbird").WithSubPath("resolv.conf.original.netbird"), - ). - WithSecurityContext(corev1ac.SecurityContext(). - WithReadOnlyRootFilesystem(true). - WithCapabilities(corev1ac.Capabilities(). - WithAdd("NET_ADMIN"). - WithAdd("SYS_RESOURCE"). - WithAdd("SYS_ADMIN"), + corev1ac.EnvVar(). + WithName("NB_MANAGEMENT_URL"). + WithValue(r.ManagementURL), + corev1ac.EnvVar(). + WithName("NB_LOG_LEVEL"). + WithValue(logLevel), + corev1ac.EnvVar(). + WithName("NB_LOG_FILE"). + WithValue("console"), + corev1ac.EnvVar(). + WithName("NB_DISABLE_PROFILES"). + WithValue("true"), + corev1ac.EnvVar(). + WithName("NB_DISABLE_UPDATE_SETTINGS"). + WithValue("true"), + corev1ac.EnvVar(). + WithName("NB_DAEMON_ADDR"). + WithValue("unix:///var/run/netbird/netbird.sock"), + corev1ac.EnvVar(). + WithName("NB_ENTRYPOINT_SERVICE_TIMEOUT"). + WithValue("0"), ). - WithPrivileged(true), - ). - WithResources(corev1ac.ResourceRequirements(). - WithRequests(corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("100m"), - corev1.ResourceMemory: resource.MustParse("128Mi"), - }), - ), + WithStartupProbe(corev1ac.Probe().WithExec(corev1ac.ExecAction().WithCommand("netbird", "status", "--check", "startup"))). + WithReadinessProbe(corev1ac.Probe().WithExec(corev1ac.ExecAction().WithCommand("netbird", "status", "--check", "ready"))). + WithVolumeMounts( + corev1ac.VolumeMount().WithName("netbird-run").WithMountPath("/var/run/netbird"), + corev1ac.VolumeMount().WithName("netbird-lib").WithMountPath("/var/lib/netbird"), + corev1ac.VolumeMount().WithName("ssh-etc").WithMountPath("/etc/ssh"), + corev1ac.VolumeMount().WithName("resolv-conf").WithMountPath("/etc/resolv.conf").WithSubPath("resolv.conf"), + corev1ac.VolumeMount().WithName("resolv-conf").WithMountPath("/etc/resolv.conf.original.netbird").WithSubPath("resolv.conf.original.netbird"), + ). + WithSecurityContext(corev1ac.SecurityContext(). + WithReadOnlyRootFilesystem(true). + WithCapabilities(corev1ac.Capabilities(). + WithAdd("NET_ADMIN"). + WithAdd("SYS_RESOURCE"). + WithAdd("SYS_ADMIN"), + ). + WithPrivileged(true), + ). + WithResources(corev1ac.ResourceRequirements(). + WithRequests(corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }), + ), + corev1ac.Container(). + WithName("kube-egress-forwarder"). + WithImage(version.KubeEgressForwarderImage). + WithArgs("--configmap-name", forwarderName, "--configmap-namespace", req.Namespace). + WithVolumeMounts( + corev1ac.VolumeMount().WithName("resolv-conf").WithMountPath("/etc/resolv.conf").WithSubPath("resolv.conf"), + ). + WithSecurityContext(corev1ac.SecurityContext(). + WithCapabilities(corev1ac.Capabilities().WithDrop("ALL")). + WithReadOnlyRootFilesystem(true), + ). + WithResources(corev1ac.ResourceRequirements(). + WithRequests(corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }), + ), ). + WithServiceAccountName(*saAC.Name). WithVolumes( corev1ac.Volume().WithName("netbird-run").WithEmptyDir(corev1ac.EmptyDirVolumeSource()), corev1ac.Volume().WithName("netbird-lib").WithEmptyDir(corev1ac.EmptyDirVolumeSource()), @@ -421,5 +481,9 @@ func (r *NetworkRouterReconciler) SetupWithManager(mgr ctrl.Manager) error { Owns(&nbv1alpha1.Group{}). Owns(&nbv1alpha1.SetupKey{}). Owns(&appsv1.Deployment{}). + Owns(&corev1.Service{}). + Owns(&corev1.ServiceAccount{}). + Owns(&rbacv1.Role{}). + Owns(&rbacv1.RoleBinding{}). Complete(r) } diff --git a/pkg/applyconfigurations/api/v1alpha1/fqdntarget.go b/pkg/applyconfigurations/api/v1alpha1/fqdntarget.go new file mode 100644 index 00000000..ed195588 --- /dev/null +++ b/pkg/applyconfigurations/api/v1alpha1/fqdntarget.go @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +// FQDNTargetApplyConfiguration represents a declarative configuration of the FQDNTarget type for use +// with apply. +// +// FQDNTarget matches traffic by an exact domain name (no wildcards). +type FQDNTargetApplyConfiguration struct { + // Hostname is a fully qualified domain name to match exactly. + Hostname *string `json:"hostname,omitempty"` +} + +// FQDNTargetApplyConfiguration constructs a declarative configuration of the FQDNTarget type for use with +// apply. +func FQDNTarget() *FQDNTargetApplyConfiguration { + return &FQDNTargetApplyConfiguration{} +} + +// WithHostname sets the Hostname field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Hostname field is set to the value of the last call. +func (b *FQDNTargetApplyConfiguration) WithHostname(value string) *FQDNTargetApplyConfiguration { + b.Hostname = &value + return b +} diff --git a/pkg/applyconfigurations/api/v1alpha1/ipaddresstarget.go b/pkg/applyconfigurations/api/v1alpha1/ipaddresstarget.go new file mode 100644 index 00000000..78409dbe --- /dev/null +++ b/pkg/applyconfigurations/api/v1alpha1/ipaddresstarget.go @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +// IPAddressTargetApplyConfiguration represents a declarative configuration of the IPAddressTarget type for use +// with apply. +// +// IPAddressTarget is a single IPv4 or IPv6 address. +type IPAddressTargetApplyConfiguration struct { + // Address is a single IP address, e.g. "10.0.0.5" or "2001:db8::1". + // CIDR notation (e.g. "10.0.0.0/24") is rejected. + Address *string `json:"address,omitempty"` +} + +// IPAddressTargetApplyConfiguration constructs a declarative configuration of the IPAddressTarget type for use with +// apply. +func IPAddressTarget() *IPAddressTargetApplyConfiguration { + return &IPAddressTargetApplyConfiguration{} +} + +// WithAddress sets the Address field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Address field is set to the value of the last call. +func (b *IPAddressTargetApplyConfiguration) WithAddress(value string) *IPAddressTargetApplyConfiguration { + b.Address = &value + return b +} diff --git a/pkg/applyconfigurations/api/v1alpha1/iptarget.go b/pkg/applyconfigurations/api/v1alpha1/iptarget.go new file mode 100644 index 00000000..2e1132ff --- /dev/null +++ b/pkg/applyconfigurations/api/v1alpha1/iptarget.go @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +// IPTargetApplyConfiguration represents a declarative configuration of the IPTarget type for use +// with apply. +// +// IPTarget is a single IPv4 or IPv6 address. +type IPTargetApplyConfiguration struct { + // Address is a single IP address. + Address *string `json:"address,omitempty"` +} + +// IPTargetApplyConfiguration constructs a declarative configuration of the IPTarget type for use with +// apply. +func IPTarget() *IPTargetApplyConfiguration { + return &IPTargetApplyConfiguration{} +} + +// WithAddress sets the Address field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Address field is set to the value of the last call. +func (b *IPTargetApplyConfiguration) WithAddress(value string) *IPTargetApplyConfiguration { + b.Address = &value + return b +} diff --git a/pkg/applyconfigurations/api/v1alpha1/networkegress.go b/pkg/applyconfigurations/api/v1alpha1/networkegress.go new file mode 100644 index 00000000..72b21a32 --- /dev/null +++ b/pkg/applyconfigurations/api/v1alpha1/networkegress.go @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// NetworkEgressApplyConfiguration represents a declarative configuration of the NetworkEgress type for use +// with apply. +// +// NetworkEgress is the Schema for the networkegresses API. +type NetworkEgressApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *NetworkEgressSpecApplyConfiguration `json:"spec,omitempty"` + Status *NetworkEgressStatusApplyConfiguration `json:"status,omitempty"` +} + +// NetworkEgress constructs a declarative configuration of the NetworkEgress type for use with +// apply. +func NetworkEgress(name, namespace string) *NetworkEgressApplyConfiguration { + b := &NetworkEgressApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("NetworkEgress") + b.WithAPIVersion("netbird.io/v1alpha1") + return b +} + +func (b NetworkEgressApplyConfiguration) IsApplyConfiguration() {} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *NetworkEgressApplyConfiguration) WithKind(value string) *NetworkEgressApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *NetworkEgressApplyConfiguration) WithAPIVersion(value string) *NetworkEgressApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *NetworkEgressApplyConfiguration) WithName(value string) *NetworkEgressApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *NetworkEgressApplyConfiguration) WithGenerateName(value string) *NetworkEgressApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *NetworkEgressApplyConfiguration) WithNamespace(value string) *NetworkEgressApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *NetworkEgressApplyConfiguration) WithUID(value types.UID) *NetworkEgressApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *NetworkEgressApplyConfiguration) WithResourceVersion(value string) *NetworkEgressApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *NetworkEgressApplyConfiguration) WithGeneration(value int64) *NetworkEgressApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *NetworkEgressApplyConfiguration) WithCreationTimestamp(value metav1.Time) *NetworkEgressApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *NetworkEgressApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *NetworkEgressApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *NetworkEgressApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *NetworkEgressApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *NetworkEgressApplyConfiguration) WithLabels(entries map[string]string) *NetworkEgressApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *NetworkEgressApplyConfiguration) WithAnnotations(entries map[string]string) *NetworkEgressApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *NetworkEgressApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *NetworkEgressApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *NetworkEgressApplyConfiguration) WithFinalizers(values ...string) *NetworkEgressApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *NetworkEgressApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *NetworkEgressApplyConfiguration) WithSpec(value *NetworkEgressSpecApplyConfiguration) *NetworkEgressApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *NetworkEgressApplyConfiguration) WithStatus(value *NetworkEgressStatusApplyConfiguration) *NetworkEgressApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *NetworkEgressApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *NetworkEgressApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *NetworkEgressApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *NetworkEgressApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/pkg/applyconfigurations/api/v1alpha1/networkegressfqdntarget.go b/pkg/applyconfigurations/api/v1alpha1/networkegressfqdntarget.go new file mode 100644 index 00000000..a60e9a8a --- /dev/null +++ b/pkg/applyconfigurations/api/v1alpha1/networkegressfqdntarget.go @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +// NetworkEgressFQDNTargetApplyConfiguration represents a declarative configuration of the NetworkEgressFQDNTarget type for use +// with apply. +// +// NetworkEgressFQDNTarget matches traffic by an exact domain name (no wildcards). +type NetworkEgressFQDNTargetApplyConfiguration struct { + // Hostname is a fully qualified domain name to match exactly. + Hostname *string `json:"hostname,omitempty"` +} + +// NetworkEgressFQDNTargetApplyConfiguration constructs a declarative configuration of the NetworkEgressFQDNTarget type for use with +// apply. +func NetworkEgressFQDNTarget() *NetworkEgressFQDNTargetApplyConfiguration { + return &NetworkEgressFQDNTargetApplyConfiguration{} +} + +// WithHostname sets the Hostname field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Hostname field is set to the value of the last call. +func (b *NetworkEgressFQDNTargetApplyConfiguration) WithHostname(value string) *NetworkEgressFQDNTargetApplyConfiguration { + b.Hostname = &value + return b +} diff --git a/pkg/applyconfigurations/api/v1alpha1/networkegressiptarget.go b/pkg/applyconfigurations/api/v1alpha1/networkegressiptarget.go new file mode 100644 index 00000000..aa8c110e --- /dev/null +++ b/pkg/applyconfigurations/api/v1alpha1/networkegressiptarget.go @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +// NetworkEgressIPTargetApplyConfiguration represents a declarative configuration of the NetworkEgressIPTarget type for use +// with apply. +// +// NetworkEgressIPTarget is a single IPv4 or IPv6 address. +type NetworkEgressIPTargetApplyConfiguration struct { + // Address is a single IP address. + Address *string `json:"address,omitempty"` +} + +// NetworkEgressIPTargetApplyConfiguration constructs a declarative configuration of the NetworkEgressIPTarget type for use with +// apply. +func NetworkEgressIPTarget() *NetworkEgressIPTargetApplyConfiguration { + return &NetworkEgressIPTargetApplyConfiguration{} +} + +// WithAddress sets the Address field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Address field is set to the value of the last call. +func (b *NetworkEgressIPTargetApplyConfiguration) WithAddress(value string) *NetworkEgressIPTargetApplyConfiguration { + b.Address = &value + return b +} diff --git a/pkg/applyconfigurations/api/v1alpha1/networkegressport.go b/pkg/applyconfigurations/api/v1alpha1/networkegressport.go new file mode 100644 index 00000000..fd0d4fc2 --- /dev/null +++ b/pkg/applyconfigurations/api/v1alpha1/networkegressport.go @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +// NetworkEgressPortApplyConfiguration represents a declarative configuration of the NetworkEgressPort type for use +// with apply. +type NetworkEgressPortApplyConfiguration struct { + // Name of the port. + Name *string `json:"name,omitempty"` + // The port that will be exposed by this service. + Port *int32 `json:"port,omitempty"` +} + +// NetworkEgressPortApplyConfiguration constructs a declarative configuration of the NetworkEgressPort type for use with +// apply. +func NetworkEgressPort() *NetworkEgressPortApplyConfiguration { + return &NetworkEgressPortApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *NetworkEgressPortApplyConfiguration) WithName(value string) *NetworkEgressPortApplyConfiguration { + b.Name = &value + return b +} + +// WithPort sets the Port field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Port field is set to the value of the last call. +func (b *NetworkEgressPortApplyConfiguration) WithPort(value int32) *NetworkEgressPortApplyConfiguration { + b.Port = &value + return b +} diff --git a/pkg/applyconfigurations/api/v1alpha1/networkegressspec.go b/pkg/applyconfigurations/api/v1alpha1/networkegressspec.go new file mode 100644 index 00000000..7b51b873 --- /dev/null +++ b/pkg/applyconfigurations/api/v1alpha1/networkegressspec.go @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +// NetworkEgressSpecApplyConfiguration represents a declarative configuration of the NetworkEgressSpec type for use +// with apply. +// +// NetworkEgressSpec defines the desired state of NetworkEgress. +type NetworkEgressSpecApplyConfiguration struct { + // NetworkRouterRef is a reference to the network and router where the resource will be created. + NetworkRouterRef *CrossNamespaceReferenceApplyConfiguration `json:"networkRouterRef,omitempty"` + // Target for egress traffic. + Target *NetworkEgressTargetApplyConfiguration `json:"target,omitempty"` + // Ports to the resource to route. + Ports []NetworkEgressPortApplyConfiguration `json:"ports,omitempty"` +} + +// NetworkEgressSpecApplyConfiguration constructs a declarative configuration of the NetworkEgressSpec type for use with +// apply. +func NetworkEgressSpec() *NetworkEgressSpecApplyConfiguration { + return &NetworkEgressSpecApplyConfiguration{} +} + +// WithNetworkRouterRef sets the NetworkRouterRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the NetworkRouterRef field is set to the value of the last call. +func (b *NetworkEgressSpecApplyConfiguration) WithNetworkRouterRef(value *CrossNamespaceReferenceApplyConfiguration) *NetworkEgressSpecApplyConfiguration { + b.NetworkRouterRef = value + return b +} + +// WithTarget sets the Target field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Target field is set to the value of the last call. +func (b *NetworkEgressSpecApplyConfiguration) WithTarget(value *NetworkEgressTargetApplyConfiguration) *NetworkEgressSpecApplyConfiguration { + b.Target = value + return b +} + +// WithPorts adds the given value to the Ports field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Ports field. +func (b *NetworkEgressSpecApplyConfiguration) WithPorts(values ...*NetworkEgressPortApplyConfiguration) *NetworkEgressSpecApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithPorts") + } + b.Ports = append(b.Ports, *values[i]) + } + return b +} diff --git a/pkg/applyconfigurations/api/v1alpha1/networkegressstatus.go b/pkg/applyconfigurations/api/v1alpha1/networkegressstatus.go new file mode 100644 index 00000000..25e65437 --- /dev/null +++ b/pkg/applyconfigurations/api/v1alpha1/networkegressstatus.go @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// NetworkEgressStatusApplyConfiguration represents a declarative configuration of the NetworkEgressStatus type for use +// with apply. +// +// NetworkEgressStatus defines the observed state of NetworkEgress. +type NetworkEgressStatusApplyConfiguration struct { + // ObservedGeneration is the last reconciled generation. + ObservedGeneration *int64 `json:"observedGeneration,omitempty"` + // Conditions holds the conditions for the NetworkEgress. + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` +} + +// NetworkEgressStatusApplyConfiguration constructs a declarative configuration of the NetworkEgressStatus type for use with +// apply. +func NetworkEgressStatus() *NetworkEgressStatusApplyConfiguration { + return &NetworkEgressStatusApplyConfiguration{} +} + +// WithObservedGeneration sets the ObservedGeneration field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ObservedGeneration field is set to the value of the last call. +func (b *NetworkEgressStatusApplyConfiguration) WithObservedGeneration(value int64) *NetworkEgressStatusApplyConfiguration { + b.ObservedGeneration = &value + return b +} + +// WithConditions adds the given value to the Conditions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Conditions field. +func (b *NetworkEgressStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *NetworkEgressStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithConditions") + } + b.Conditions = append(b.Conditions, *values[i]) + } + return b +} diff --git a/pkg/applyconfigurations/api/v1alpha1/networkegresstarget.go b/pkg/applyconfigurations/api/v1alpha1/networkegresstarget.go new file mode 100644 index 00000000..364e3faf --- /dev/null +++ b/pkg/applyconfigurations/api/v1alpha1/networkegresstarget.go @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +// NetworkEgressTargetApplyConfiguration represents a declarative configuration of the NetworkEgressTarget type for use +// with apply. +// +// NetworkEgressTarget describes a single allowed egress destination. +// Exactly one of IP or FQDN must be set. +type NetworkEgressTargetApplyConfiguration struct { + // IP targets a single specific IP address (not a CIDR range). + IP *NetworkEgressIPTargetApplyConfiguration `json:"ip,omitempty"` + // FQDN targets an exact domain name (no wildcards). + FQDN *NetworkEgressFQDNTargetApplyConfiguration `json:"fqdn,omitempty"` +} + +// NetworkEgressTargetApplyConfiguration constructs a declarative configuration of the NetworkEgressTarget type for use with +// apply. +func NetworkEgressTarget() *NetworkEgressTargetApplyConfiguration { + return &NetworkEgressTargetApplyConfiguration{} +} + +// WithIP sets the IP field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the IP field is set to the value of the last call. +func (b *NetworkEgressTargetApplyConfiguration) WithIP(value *NetworkEgressIPTargetApplyConfiguration) *NetworkEgressTargetApplyConfiguration { + b.IP = value + return b +} + +// WithFQDN sets the FQDN field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the FQDN field is set to the value of the last call. +func (b *NetworkEgressTargetApplyConfiguration) WithFQDN(value *NetworkEgressFQDNTargetApplyConfiguration) *NetworkEgressTargetApplyConfiguration { + b.FQDN = value + return b +} diff --git a/pkg/applyconfigurations/utils.go b/pkg/applyconfigurations/utils.go index 7a18c179..487364b2 100644 --- a/pkg/applyconfigurations/utils.go +++ b/pkg/applyconfigurations/utils.go @@ -38,6 +38,20 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.GroupSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("GroupStatus"): return &apiv1alpha1.GroupStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("NetworkEgress"): + return &apiv1alpha1.NetworkEgressApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("NetworkEgressFQDNTarget"): + return &apiv1alpha1.NetworkEgressFQDNTargetApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("NetworkEgressIPTarget"): + return &apiv1alpha1.NetworkEgressIPTargetApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("NetworkEgressPort"): + return &apiv1alpha1.NetworkEgressPortApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("NetworkEgressSpec"): + return &apiv1alpha1.NetworkEgressSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("NetworkEgressStatus"): + return &apiv1alpha1.NetworkEgressStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("NetworkEgressTarget"): + return &apiv1alpha1.NetworkEgressTargetApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("NetworkResource"): return &apiv1alpha1.NetworkResourceApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("NetworkResourceSpec"): diff --git a/pkg/version/version.go b/pkg/version/version.go index b46a167b..fe84930c 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -7,8 +7,9 @@ import ( ) const ( - NetbirdClientImage = "ghcr.io/netbirdio/netbird:0.72.4@sha256:6c6c20baffae4a3ec50f29ec9361608a420625185505e8cd6f0c44d71c5d4798" - KubeApiProxyImage = "ghcr.io/netbirdio/netbird-kubeapi-proxy:v0.0.4@sha256:bffa4f093abc19b4934ae37657bac76fa3b390cbd39aadac987634215eb750f5" + NetbirdClientImage = "ghcr.io/netbirdio/netbird:0.72.4@sha256:6c6c20baffae4a3ec50f29ec9361608a420625185505e8cd6f0c44d71c5d4798" + KubeApiProxyImage = "ghcr.io/netbirdio/netbird-kubeapi-proxy:v0.0.4@sha256:bffa4f093abc19b4934ae37657bac76fa3b390cbd39aadac987634215eb750f5" + KubeEgressForwarderImage = "ghcr.io/netbirdio/kube-egress-forwarder:v0.0.2@sha256:f3b4637122cbda3c1915d49e6f96edff7e3a3accfceb87b821c829056abe8a6f" ) func BuildVersion() string { diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 666c9bb3..a3a9c23b 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -25,7 +25,7 @@ import ( netbird "github.com/netbirdio/netbird/shared/management/client/rest" "github.com/netbirdio/netbird/shared/management/http/api" "helm.sh/helm/v4/pkg/action" - "helm.sh/helm/v4/pkg/chart/loader" + "helm.sh/helm/v4/pkg/chart/v2/loader" "helm.sh/helm/v4/pkg/downloader" "helm.sh/helm/v4/pkg/getter" "helm.sh/helm/v4/pkg/kube" @@ -38,6 +38,7 @@ import ( kruntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" kruntimeutil "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/client-go/tools/clientcmd" "sigs.k8s.io/controller-runtime/pkg/client" @@ -290,8 +291,8 @@ func TestE2E(t *testing.T) { } err = k8sClient.Create(t.Context(), secret) require.NoError(t, err) - installOperator(t, kcPath, false, managementURL) - installOperator(t, kcPath, true, managementURL) + installOperator(t, k8sClient, kcPath, false, managementURL) + installOperator(t, k8sClient, kcPath, true, managementURL) t.Run("cluster proxy", testClusterProxy(k8sClient, nbClient)) }) @@ -393,7 +394,7 @@ func testClusterProxy(k8sClient client.Client, nbClient *netbird.Client) func(*t } } -func installOperator(t *testing.T, kcPath string, dev bool, managementURL string) { +func installOperator(t *testing.T, k8sClient client.Client, kcPath string, dev bool, managementURL string) { t.Helper() regClient, err := registry.NewClient() @@ -455,6 +456,15 @@ func installOperator(t *testing.T, kcPath string, dev bool, managementURL string _, err = install.RunWithContext(t.Context(), charter, vals) require.NoError(t, err) } else { + for _, crd := range charter.CRDObjects() { + docs := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(crd.File.Data), 4096) + var obj unstructured.Unstructured + err := docs.Decode(&obj) + require.NoError(t, err) + err = k8sClient.Patch(t.Context(), &obj, client.Apply, client.ForceOwnership, client.FieldOwner("helm")) + require.NoError(t, err) + } + upgrade := action.NewUpgrade(actionCfg) upgrade.Namespace = netbirdNamespace upgrade.WaitStrategy = kube.StatusWatcherStrategy