Add network egress resource (#357)

This change adds a new import resource which enables exposing Netbird
resources as Kubernetes services. This remove the need to add sidecars
to every pod.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a new `NetworkEgress` custom resource (`netbird.io/v1alpha1`)
with CRD, schema validation, and status/conditions.
* Extended controller functionality to create egress services and
translate egress rules into import `EndpointSlice` resources; egress
pods now include a kube-egress-forwarder sidecar.
* **Bug Fixes**
* Added missing deep-copy and declarative apply support for the new
`NetworkEgress` API types.
* **Documentation**
* Updated README/API reference and added example manifests for
`NetworkEgress` (including IP/FQDN target usage).
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Signed-off-by: Philip Laine <philip.laine@gmail.com>
This commit is contained in:
Philip Laine
2026-07-17 08:06:53 +02:00
committed by GitHub
parent 3c1c6675d9
commit ea2dafd5a3
31 changed files with 2220 additions and 83 deletions
+4
View File
@@ -65,6 +65,8 @@ linters:
alias: admissionv1 alias: admissionv1
- pkg: k8s.io/api/policy/v1 - pkg: k8s.io/api/policy/v1
alias: policyv1 alias: policyv1
- pkg: "k8s.io/api/discovery/v1"
alias: discoveryv1
- pkg: k8s.io/client-go/applyconfigurations/meta/v1 - pkg: k8s.io/client-go/applyconfigurations/meta/v1
alias: metav1ac alias: metav1ac
- pkg: k8s.io/client-go/applyconfigurations/core/v1 - pkg: k8s.io/client-go/applyconfigurations/core/v1
@@ -75,6 +77,8 @@ linters:
alias: appsv1ac alias: appsv1ac
- pkg: k8s.io/client-go/applyconfigurations/policy/v1 - pkg: k8s.io/client-go/applyconfigurations/policy/v1
alias: policyv1ac alias: policyv1ac
- pkg: k8s.io/client-go/applyconfigurations/discovery/v1
alias: discoveryv1ac
- pkg: sigs.k8s.io/gateway-api/apis/v1 - pkg: sigs.k8s.io/gateway-api/apis/v1
alias: gwv1 alias: gwv1
+8
View File
@@ -123,4 +123,12 @@ resources:
kind: ClusterProxy kind: ClusterProxy
path: github.com/netbirdio/kubernetes-operator/api/v1alpha1 path: github.com/netbirdio/kubernetes-operator/api/v1alpha1
version: 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" version: "3"
+4 -3
View File
@@ -23,9 +23,10 @@ helm upgrade --install --create-namespace -n netbird netbird-operator oci://ghcr
| Kind | API Version | | Kind | API Version |
|------|-------------| |------|-------------|
| [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` | | [SetupKey](docs/api-reference.md#setupkey) | `netbird.io/v1alpha1` |
| [Group](docs/api-reference.md#group) | `netbird.io/v1alpha1` |
| [NetworkRouter](docs/api-reference.md#networkrouter) | `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` | | [SidecarProfile](docs/api-reference.md#sidecarprofile) | `netbird.io/v1alpha1` |
| [ClusterProxy](docs/api-reference.md#clusterproxy) | `netbird.io/v1alpha1` | | [ClusterProxy](docs/api-reference.md#clusterproxy) | `netbird.io/v1alpha1` |
+120
View File
@@ -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{})
}
+173
View File
@@ -313,6 +313,179 @@ func (in *GroupStatus) DeepCopy() *GroupStatus {
return out 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. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NetworkResource) DeepCopyInto(out *NetworkResource) { func (in *NetworkResource) DeepCopyInto(out *NetworkResource) {
*out = *in *out = *in
@@ -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: {}
+45 -14
View File
@@ -38,6 +38,7 @@ rules:
- groups - groups
- networkrouters - networkrouters
- networkresources - networkresources
- networkegresses
- sidecarprofiles - sidecarprofiles
- clusterproxies - clusterproxies
verbs: verbs:
@@ -59,6 +60,7 @@ rules:
- groups/status - groups/status
- networkrouters/status - networkrouters/status
- networkresources/status - networkresources/status
- networkregresses/status
- sidecarprofiles/status - sidecarprofiles/status
- clusterproxies/status - clusterproxies/status
verbs: verbs:
@@ -76,19 +78,60 @@ rules:
- groups/finalizers - groups/finalizers
- networkrouters/finalizers - networkrouters/finalizers
- networkresources/finalizers - networkresources/finalizers
- networkegresses/finalizers
- sidecarprofiles/finalizers - sidecarprofiles/finalizers
verbs: verbs:
- update - 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:
- configmaps
- serviceaccounts
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- apiGroups: - apiGroups:
- "" - ""
resources: resources:
- services - services
verbs: verbs:
- get - get
- patch
- update
- list - list
- watch - watch
- update - create
- patch - delete
- apiGroups: - apiGroups:
- "" - ""
resources: resources:
@@ -197,18 +240,6 @@ metadata:
labels: labels:
{{- include "netbird-operator.labels" . | nindent 4 }} {{- include "netbird-operator.labels" . | nindent 4 }}
rules: rules:
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- apiGroups: - apiGroups:
- coordination.k8s.io - coordination.k8s.io
resources: resources:
+13
View File
@@ -312,6 +312,19 @@ func main() {
setupLog.Error(err, "Failed to create controller", "controller", "NetworkResource") setupLog.Error(err, "Failed to create controller", "controller", "NetworkResource")
os.Exit(1) 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{ if err := (&controller.ClusterProxyReconciler{
Client: mgr.GetClient(), Client: mgr.GetClient(),
ApiKey: netbirdAPIKey, ApiKey: netbirdAPIKey,
@@ -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: {}
+1
View File
@@ -12,4 +12,5 @@ resources:
- bases/netbird.io_setupkeys.yaml - bases/netbird.io_setupkeys.yaml
- bases/netbird.io_sidecarprofiles.yaml - bases/netbird.io_sidecarprofiles.yaml
- bases/netbird.io_clusterproxies.yaml - bases/netbird.io_clusterproxies.yaml
- bases/netbird.io_networkegresses.yaml
# +kubebuilder:scaffold:crdkustomizeresource # +kubebuilder:scaffold:crdkustomizeresource
+125
View File
@@ -11,6 +11,7 @@ Package v1alpha1 contains API Schema definitions for the v1alpha1 API group.
### Resource Types ### Resource Types
- [ClusterProxy](#clusterproxy) - [ClusterProxy](#clusterproxy)
- [Group](#group) - [Group](#group)
- [NetworkEgress](#networkegress)
- [NetworkResource](#networkresource) - [NetworkResource](#networkresource)
- [NetworkRouter](#networkrouter) - [NetworkRouter](#networkrouter)
- [SetupKey](#setupkey) - [SetupKey](#setupkey)
@@ -105,6 +106,7 @@ _Appears in:_
_Appears in:_ _Appears in:_
- [NetworkEgressSpec](#networkegressspec)
- [NetworkResourceSpec](#networkresourcespec) - [NetworkResourceSpec](#networkresourcespec)
| Field | Description | Default | Validation | | Field | Description | Default | Validation |
@@ -222,6 +224,129 @@ _Appears in:_
| `Container` | InjectionModeContainer injects the client as a regular container.<br /> | | `Container` | InjectionModeContainer injects the client as a regular container.<br /> |
#### 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.<br />Servers may infer this from the endpoint the client submits requests to.<br />Cannot be updated.<br />In CamelCase.<br />More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | Optional: \{\} <br /> |
| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.<br />Servers should convert recognized schemas to the latest internal value, and<br />may reject unrecognized values.<br />More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | Optional: \{\} <br /> |
| `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: \{\} <br /> |
| `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,\}$` <br />Required: \{\} <br /> |
#### 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: \{\} <br /> |
#### NetworkEgressPort
_Appears in:_
- [NetworkEgressSpec](#networkegressspec)
| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `name` _string_ | Name of the port. | | MaxLength: 15 <br />MinLength: 1 <br />Pattern: `^[a-z0-9]([-a-z0-9]*[a-z0-9])?$` <br />Required: \{\} <br /> |
| `port` _integer_ | The port that will be exposed by this service. | | Maximum: 65535 <br />Minimum: 1 <br />Required: \{\} <br /> |
#### 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 <br />Required: \{\} <br /> |
#### 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: \{\} <br /> |
| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#condition-v1-meta) array_ | Conditions holds the conditions for the NetworkEgress. | | Optional: \{\} <br /> |
#### 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: \{\} <br /> |
| `fqdn` _[NetworkEgressFQDNTarget](#networkegressfqdntarget)_ | FQDN targets an exact domain name (no wildcards). | | Optional: \{\} <br /> |
#### NetworkResource #### NetworkResource
+15
View File
@@ -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
+2 -1
View File
@@ -9,6 +9,7 @@ require (
github.com/go-logr/logr v1.4.3 github.com/go-logr/logr v1.4.3
github.com/go-openapi/testify/v2 v2.6.0 github.com/go-openapi/testify/v2 v2.6.0
github.com/google/uuid v1.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/netbirdio/netbird v0.72.4
github.com/onsi/ginkgo/v2 v2.32.0 github.com/onsi/ginkgo/v2 v2.32.0
github.com/onsi/gomega v1.42.1 github.com/onsi/gomega v1.42.1
@@ -79,7 +80,7 @@ require (
golang.org/x/crypto v0.53.0 // indirect golang.org/x/crypto v0.53.0 // indirect
golang.org/x/net v0.56.0 // indirect golang.org/x/net v0.56.0 // indirect
golang.org/x/oauth2 v0.36.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/sys v0.46.0 // indirect
golang.org/x/term v0.44.0 // indirect golang.org/x/term v0.44.0 // indirect
golang.org/x/text v0.38.0 // indirect golang.org/x/text v0.38.0 // indirect
+4 -2
View File
@@ -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/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 h1:4TaYr9O4xX0D2kszeOLclTiCbA3eHq3xWV+9ILJbIYs=
github.com/netbirdio/dex v0.244.1-0.20260512110716-8d70ad8647c1/go.mod h1:IHH+H8vK2GfqtIt5u/5OdPh18yk0oDHuj2vz5+Goetg= 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 h1:F3zS5fT9xzD1OFLfcdAE+3FfyiwjGukF1hvj0jErgs8=
github.com/netbirdio/management-integrations/integrations v0.0.0-20260416123949-2355d972be42/go.mod h1:n47r67ZSPgwSmT/Z1o48JjZQW9YJ6m/6Bd/uAXkL3Pg= 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= 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/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 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= 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.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= 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.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
@@ -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
}
@@ -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)
}
@@ -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())
})
})
})
@@ -15,6 +15,7 @@ import (
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
policyv1 "k8s.io/api/policy/v1" policyv1 "k8s.io/api/policy/v1"
rbacv1 "k8s.io/api/rbac/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors" kerrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -24,6 +25,7 @@ import (
corev1ac "k8s.io/client-go/applyconfigurations/core/v1" corev1ac "k8s.io/client-go/applyconfigurations/core/v1"
metav1ac "k8s.io/client-go/applyconfigurations/meta/v1" metav1ac "k8s.io/client-go/applyconfigurations/meta/v1"
policyv1ac "k8s.io/client-go/applyconfigurations/policy/v1" policyv1ac "k8s.io/client-go/applyconfigurations/policy/v1"
rbacv1ac "k8s.io/client-go/applyconfigurations/rbac/v1"
ctrl "sigs.k8s.io/controller-runtime" ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "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/k8sutil"
"github.com/netbirdio/kubernetes-operator/internal/netbirdutil" "github.com/netbirdio/kubernetes-operator/internal/netbirdutil"
nbv1alpha1ac "github.com/netbirdio/kubernetes-operator/pkg/applyconfigurations/api/v1alpha1" nbv1alpha1ac "github.com/netbirdio/kubernetes-operator/pkg/applyconfigurations/api/v1alpha1"
"github.com/netbirdio/kubernetes-operator/pkg/version"
) )
type NetworkRouterReconciler struct { type NetworkRouterReconciler struct {
@@ -191,7 +194,7 @@ func (r *NetworkRouterReconciler) Reconcile(ctx context.Context, req ctrl.Reques
return ctrl.Result{}, err return ctrl.Result{}, err
} }
// Create the deployment. // Setup router configuration.
selectorLabels := map[string]string{ selectorLabels := map[string]string{
"app.kubernetes.io/name": "networkrouter", "app.kubernetes.io/name": "networkrouter",
"app.kubernetes.io/instance": req.Name, "app.kubernetes.io/instance": req.Name,
@@ -207,6 +210,44 @@ func (r *NetworkRouterReconciler) Reconcile(ctx context.Context, req ctrl.Reques
clientImage = netRouter.Spec.Image 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(). podTemplateSpecAC := corev1ac.PodTemplateSpec().
WithLabels(selectorLabels). WithLabels(selectorLabels).
WithSpec(corev1ac.PodSpec(). WithSpec(corev1ac.PodSpec().
@@ -232,7 +273,8 @@ func (r *NetworkRouterReconciler) Reconcile(ctx context.Context, req ctrl.Reques
WithReadOnlyRootFilesystem(true), WithReadOnlyRootFilesystem(true),
), ),
). ).
WithContainers(corev1ac.Container(). WithContainers(
corev1ac.Container().
WithName("netbird"). WithName("netbird").
WithImage(clientImage). WithImage(clientImage).
WithEnv( WithEnv(
@@ -290,7 +332,25 @@ func (r *NetworkRouterReconciler) Reconcile(ctx context.Context, req ctrl.Reques
corev1.ResourceMemory: resource.MustParse("128Mi"), 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( WithVolumes(
corev1ac.Volume().WithName("netbird-run").WithEmptyDir(corev1ac.EmptyDirVolumeSource()), corev1ac.Volume().WithName("netbird-run").WithEmptyDir(corev1ac.EmptyDirVolumeSource()),
corev1ac.Volume().WithName("netbird-lib").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.Group{}).
Owns(&nbv1alpha1.SetupKey{}). Owns(&nbv1alpha1.SetupKey{}).
Owns(&appsv1.Deployment{}). Owns(&appsv1.Deployment{}).
Owns(&corev1.Service{}).
Owns(&corev1.ServiceAccount{}).
Owns(&rbacv1.Role{}).
Owns(&rbacv1.RoleBinding{}).
Complete(r) Complete(r)
} }
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
+14
View File
@@ -38,6 +38,20 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
return &apiv1alpha1.GroupSpecApplyConfiguration{} return &apiv1alpha1.GroupSpecApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("GroupStatus"): case v1alpha1.SchemeGroupVersion.WithKind("GroupStatus"):
return &apiv1alpha1.GroupStatusApplyConfiguration{} 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"): case v1alpha1.SchemeGroupVersion.WithKind("NetworkResource"):
return &apiv1alpha1.NetworkResourceApplyConfiguration{} return &apiv1alpha1.NetworkResourceApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("NetworkResourceSpec"): case v1alpha1.SchemeGroupVersion.WithKind("NetworkResourceSpec"):
+1
View File
@@ -9,6 +9,7 @@ import (
const ( const (
NetbirdClientImage = "ghcr.io/netbirdio/netbird:0.72.4@sha256:6c6c20baffae4a3ec50f29ec9361608a420625185505e8cd6f0c44d71c5d4798" NetbirdClientImage = "ghcr.io/netbirdio/netbird:0.72.4@sha256:6c6c20baffae4a3ec50f29ec9361608a420625185505e8cd6f0c44d71c5d4798"
KubeApiProxyImage = "ghcr.io/netbirdio/netbird-kubeapi-proxy:v0.0.4@sha256:bffa4f093abc19b4934ae37657bac76fa3b390cbd39aadac987634215eb750f5" 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 { func BuildVersion() string {
+14 -4
View File
@@ -25,7 +25,7 @@ import (
netbird "github.com/netbirdio/netbird/shared/management/client/rest" netbird "github.com/netbirdio/netbird/shared/management/client/rest"
"github.com/netbirdio/netbird/shared/management/http/api" "github.com/netbirdio/netbird/shared/management/http/api"
"helm.sh/helm/v4/pkg/action" "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/downloader"
"helm.sh/helm/v4/pkg/getter" "helm.sh/helm/v4/pkg/getter"
"helm.sh/helm/v4/pkg/kube" "helm.sh/helm/v4/pkg/kube"
@@ -38,6 +38,7 @@ import (
kruntime "k8s.io/apimachinery/pkg/runtime" kruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
kruntimeutil "k8s.io/apimachinery/pkg/util/runtime" kruntimeutil "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
@@ -290,8 +291,8 @@ func TestE2E(t *testing.T) {
} }
err = k8sClient.Create(t.Context(), secret) err = k8sClient.Create(t.Context(), secret)
require.NoError(t, err) require.NoError(t, err)
installOperator(t, kcPath, false, managementURL) installOperator(t, k8sClient, kcPath, false, managementURL)
installOperator(t, kcPath, true, managementURL) installOperator(t, k8sClient, kcPath, true, managementURL)
t.Run("cluster proxy", testClusterProxy(k8sClient, nbClient)) 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() t.Helper()
regClient, err := registry.NewClient() 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) _, err = install.RunWithContext(t.Context(), charter, vals)
require.NoError(t, err) require.NoError(t, err)
} else { } 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 := action.NewUpgrade(actionCfg)
upgrade.Namespace = netbirdNamespace upgrade.Namespace = netbirdNamespace
upgrade.WaitStrategy = kube.StatusWatcherStrategy upgrade.WaitStrategy = kube.StatusWatcherStrategy