From 9838f0dccca94b2f113f0140dd704406e5806ab5 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Thu, 23 Apr 2026 19:17:53 +0200 Subject: [PATCH] Add sidecar profile (#192) This change adds a new SidecarProfile resource which allows configuring client sidecar injection into pods. It replaces the older annotation based solution. This removes any pod specific configuration from the setup key and puts it all in this side car configuration. Fixes #188 Signed-off-by: Philip Laine --- PROJECT | 8 + api/v1alpha1/sidecarprofile_types.go | 102 ++++ api/v1alpha1/zz_generated.deepcopy.go | 139 +++++ examples/refactor/sidecarprofile.yaml | 34 ++ .../crds/netbird.io_sidecarprofiles.yaml | 551 ++++++++++++++++++ helm/kubernetes-operator/templates/rbac.yaml | 17 +- internal/webhook/v1/pod_webhook.go | 162 ++++- internal/webhook/v1/pod_webhook_test.go | 65 ++- internal/webhook/v1/webhook_suite_test.go | 4 + .../api/v1alpha1/containeroverride.go | 48 ++ .../api/v1alpha1/sidecarprofile.go | 229 ++++++++ .../api/v1alpha1/sidecarprofilespec.go | 74 +++ .../api/v1alpha1/sidecarprofilestatus.go | 35 ++ pkg/applyconfigurations/utils.go | 8 + 14 files changed, 1447 insertions(+), 29 deletions(-) create mode 100644 api/v1alpha1/sidecarprofile_types.go create mode 100644 examples/refactor/sidecarprofile.yaml create mode 100644 helm/kubernetes-operator/crds/netbird.io_sidecarprofiles.yaml create mode 100644 pkg/applyconfigurations/api/v1alpha1/containeroverride.go create mode 100644 pkg/applyconfigurations/api/v1alpha1/sidecarprofile.go create mode 100644 pkg/applyconfigurations/api/v1alpha1/sidecarprofilespec.go create mode 100644 pkg/applyconfigurations/api/v1alpha1/sidecarprofilestatus.go diff --git a/PROJECT b/PROJECT index 7dfe0d1..68b0780 100644 --- a/PROJECT +++ b/PROJECT @@ -107,4 +107,12 @@ resources: kind: NetworkResource path: github.com/netbirdio/kubernetes-operator/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: netbird.io + kind: SidecarProfile + path: github.com/netbirdio/kubernetes-operator/api/v1alpha1 + version: v1alpha1 version: "3" diff --git a/api/v1alpha1/sidecarprofile_types.go b/api/v1alpha1/sidecarprofile_types.go new file mode 100644 index 0000000..f15f768 --- /dev/null +++ b/api/v1alpha1/sidecarprofile_types.go @@ -0,0 +1,102 @@ +package v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// InjectionMode defines how the sidecar is injected into the pod. +// +kubebuilder:validation:Enum=Sidecar;Container +type InjectionMode string + +const ( + // InjectionModeSidecar injects the client as a sidecar container. + InjectionModeSidecar InjectionMode = "Sidecar" + + // InjectionModeContainer injects the client as a regular container. + InjectionModeContainer InjectionMode = "Container" +) + +// SidecarProfileSpec defines the desired state of SidecarProfile. +type SidecarProfileSpec struct { + // SetupKeyRef is the reference to the setup key used in the client. + // +required + SetupKeyRef corev1.LocalObjectReference `json:"setupKeyRef"` + + // PodSelector determines which pods the profile should apply to. + // An empty slector means the profile will apply to all pods in the namespace. + // +optional + PodSelector *metav1.LabelSelector `json:"podSelector,omitempty"` + + // InjectionMode defines whether the sidecar is injected as a native Kubernetes sidecar container or as a regular container. + // +kubebuilder:default=Sidecar + // +optional + InjectionMode InjectionMode `json:"injectionMode,omitempty"` + + // ExtraDNSLabels assigns additional DNS names to peers beyond their default hostname. + // +optional + ExtraDNSLabels []string `json:"extraDNSLabels,omitempty"` + + // +optional + ContainerOverride *ContainerOverride `json:"containerOverride,omitempty"` +} + +type ContainerOverride struct { + // Image overrides the image used by the client. + // +optional + Image string `json:"image,omitempty"` + + // +optional + Env []corev1.EnvVar `json:"env,omitempty"` + + // +optional + SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"` +} + +// SidecarProfileStatus defines the observed state of SidecarProfile. +type SidecarProfileStatus struct { + // Conditions holds the conditions for the SidecarProfile. + // +listType=map + // +listMapKey=type + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource + +// SidecarProfile is the Schema for the sidecarprofiles API. +type SidecarProfile struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // +required + Spec SidecarProfileSpec `json:"spec"` + + // +kubebuilder:default={} + Status SidecarProfileStatus `json:"status,omitempty"` +} + +// GetConditions returns the status conditions of the object. +func (s *SidecarProfile) GetConditions() []metav1.Condition { + return s.Status.Conditions +} + +// SetConditions sets the status conditions on the object. +func (s *SidecarProfile) SetConditions(conditions []metav1.Condition) { + s.Status.Conditions = conditions +} + +// +kubebuilder:object:root=true + +// SidecarProfileList contains a list of SidecarProfile +type SidecarProfileList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitzero"` + Items []SidecarProfile `json:"items"` +} + +func init() { + SchemeBuilder.Register(&SidecarProfile{}, &SidecarProfileList{}) +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index ff93134..2f35b21 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -10,6 +10,33 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ContainerOverride) DeepCopyInto(out *ContainerOverride) { + *out = *in + if in.Env != nil { + in, out := &in.Env, &out.Env + *out = make([]v1.EnvVar, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.SecurityContext != nil { + in, out := &in.SecurityContext, &out.SecurityContext + *out = new(v1.SecurityContext) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerOverride. +func (in *ContainerOverride) DeepCopy() *ContainerOverride { + if in == nil { + return nil + } + out := new(ContainerOverride) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CrossNamespaceReference) DeepCopyInto(out *CrossNamespaceReference) { *out = *in @@ -481,6 +508,118 @@ func (in *SetupKeyStatus) DeepCopy() *SetupKeyStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SidecarProfile) DeepCopyInto(out *SidecarProfile) { + *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 SidecarProfile. +func (in *SidecarProfile) DeepCopy() *SidecarProfile { + if in == nil { + return nil + } + out := new(SidecarProfile) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SidecarProfile) 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 *SidecarProfileList) DeepCopyInto(out *SidecarProfileList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]SidecarProfile, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SidecarProfileList. +func (in *SidecarProfileList) DeepCopy() *SidecarProfileList { + if in == nil { + return nil + } + out := new(SidecarProfileList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SidecarProfileList) 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 *SidecarProfileSpec) DeepCopyInto(out *SidecarProfileSpec) { + *out = *in + out.SetupKeyRef = in.SetupKeyRef + if in.PodSelector != nil { + in, out := &in.PodSelector, &out.PodSelector + *out = new(metav1.LabelSelector) + (*in).DeepCopyInto(*out) + } + if in.ExtraDNSLabels != nil { + in, out := &in.ExtraDNSLabels, &out.ExtraDNSLabels + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ContainerOverride != nil { + in, out := &in.ContainerOverride, &out.ContainerOverride + *out = new(ContainerOverride) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SidecarProfileSpec. +func (in *SidecarProfileSpec) DeepCopy() *SidecarProfileSpec { + if in == nil { + return nil + } + out := new(SidecarProfileSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SidecarProfileStatus) DeepCopyInto(out *SidecarProfileStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SidecarProfileStatus. +func (in *SidecarProfileStatus) DeepCopy() *SidecarProfileStatus { + if in == nil { + return nil + } + out := new(SidecarProfileStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WorkloadOverride) DeepCopyInto(out *WorkloadOverride) { *out = *in diff --git a/examples/refactor/sidecarprofile.yaml b/examples/refactor/sidecarprofile.yaml new file mode 100644 index 0000000..f56cedb --- /dev/null +++ b/examples/refactor/sidecarprofile.yaml @@ -0,0 +1,34 @@ +apiVersion: netbird.io/v1alpha1 +kind: SetupKey +metadata: + name: sidecar + namespace: default +spec: + name: sidecar + ephemeral: true +--- +apiVersion: netbird.io/v1alpha1 +kind: SidecarProfile +metadata: + name: test + namespace: default +spec: + setupKeyRef: + name: sidecar + podSelector: + matchLabels: + app: ubuntu +--- +apiVersion: v1 +kind: Pod +metadata: + name: ubuntu + namespace: default + labels: + app: ubuntu +spec: + containers: + - name: ubuntu + image: ubuntu:latest + command: ["sleep", "infinity"] + diff --git a/helm/kubernetes-operator/crds/netbird.io_sidecarprofiles.yaml b/helm/kubernetes-operator/crds/netbird.io_sidecarprofiles.yaml new file mode 100644 index 0000000..d08db01 --- /dev/null +++ b/helm/kubernetes-operator/crds/netbird.io_sidecarprofiles.yaml @@ -0,0 +1,551 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.20.1 + name: sidecarprofiles.netbird.io +spec: + group: netbird.io + names: + kind: SidecarProfile + listKind: SidecarProfileList + plural: sidecarprofiles + singular: sidecarprofile + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: SidecarProfile is the Schema for the sidecarprofiles 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: SidecarProfileSpec defines the desired state of SidecarProfile. + properties: + containerOverride: + properties: + env: + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: |- + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: |- + The key within the env file. An invalid key will prevent the pod from starting. + The keys defined within a source may consist of any printable ASCII characters except '='. + During Alpha stage of the EnvFiles feature gate, the key size is limited to 128 characters. + type: string + optional: + default: false + description: |- + Specify whether the file or its key must be defined. If the file or key + does not exist, then the env var is not published. + If optional is set to true and the specified key does not exist, + the environment variable will not be set in the Pod's containers. + + If optional is set to false and the specified key does not exist, + an error will be returned during Pod creation. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '..' path or start with '..'. + type: string + volumeName: + description: The name of the volume mount containing + the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + image: + description: Image overrides the image used by the client. + type: string + securityContext: + description: |- + SecurityContext holds security configuration that will be applied to a container. + Some fields are present in both SecurityContext and PodSecurityContext. When both + are set, the values in SecurityContext take precedence. + properties: + allowPrivilegeEscalation: + description: |- + AllowPrivilegeEscalation controls whether a process can gain more + privileges than its parent process. This bool directly controls if + the no_new_privs flag will be set on the container process. + AllowPrivilegeEscalation is true always when the container is: + 1) run as Privileged + 2) has CAP_SYS_ADMIN + Note that this field cannot be set when spec.os.name is windows. + type: boolean + appArmorProfile: + description: |- + appArmorProfile is the AppArmor options to use by this container. If set, this profile + overrides the pod's appArmorProfile. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile loaded on the node that should be used. + The profile must be preconfigured on the node to work. + Must match the loaded name of the profile. + Must be set if and only if type is "Localhost". + type: string + type: + description: |- + type indicates which kind of AppArmor profile will be applied. + Valid options are: + Localhost - a profile pre-loaded on the node. + RuntimeDefault - the container runtime's default profile. + Unconfined - no AppArmor enforcement. + type: string + required: + - type + type: object + capabilities: + description: |- + The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the container runtime. + Note that this field cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + x-kubernetes-list-type: atomic + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + description: |- + Run container in privileged mode. + Processes in privileged containers are essentially equivalent to root on the host. + Defaults to false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: |- + procMount denotes the type of proc mount to use for the containers. + The default value is Default which uses the container runtime defaults for + readonly paths and masked paths. + This requires the ProcMountType feature flag to be enabled. + Note that this field cannot be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: |- + Whether this container has a read-only root filesystem. + Default is false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by this container. If seccomp options are + provided at both the pod & container level, the container options + override the pod options. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + windowsOptions: + description: |- + The Windows specific settings applied to all containers. + If unspecified, the options from the PodSecurityContext will be used. + If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: |- + GMSACredentialSpec is where the GMSA admission webhook + (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the + GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the + GMSA credential spec to use. + type: string + hostProcess: + description: |- + HostProcess determines if a container should be run as a 'Host Process' container. + All of a Pod's containers must have the same effective HostProcess value + (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). + In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: |- + The UserName in Windows to run the entrypoint of the container process. + Defaults to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + type: object + extraDNSLabels: + description: ExtraDNSLabels assigns additional DNS names to peers + beyond their default hostname. + items: + type: string + type: array + injectionMode: + default: Sidecar + description: InjectionMode defines whether the sidecar is injected + as a native Kubernetes sidecar container or as a regular container. + enum: + - Sidecar + - Container + type: string + podSelector: + description: |- + PodSelector determines which pods the profile should apply to. + An empty slector means the profile will apply to all pods in the namespace. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + setupKeyRef: + description: SetupKeyRef is the reference to the setup key used in + the client. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + required: + - setupKeyRef + type: object + status: + default: {} + description: SidecarProfileStatus defines the observed state of SidecarProfile. + properties: + conditions: + description: Conditions holds the conditions for the SidecarProfile. + 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 + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/kubernetes-operator/templates/rbac.yaml b/helm/kubernetes-operator/templates/rbac.yaml index ad12f47..9da9cc0 100644 --- a/helm/kubernetes-operator/templates/rbac.yaml +++ b/helm/kubernetes-operator/templates/rbac.yaml @@ -27,7 +27,6 @@ rules: - get - patch - update -{{- if or .Values.netbirdAPI.key .Values.netbirdAPI.keyFromSecret }} - apiGroups: - netbird.io resources: @@ -35,6 +34,11 @@ rules: - nbresources - nbroutingpeers - nbpolicies + - setupkeys + - groups + - networkrouters + - networkresources + - sidecarprofiles verbs: - get - patch @@ -50,6 +54,11 @@ rules: - nbresources/status - nbroutingpeers/status - nbpolicies/status + - setupkeys/status + - groups/status + - networkrouters/status + - networkresources/status + - sidecarprofiles/status verbs: - get - patch @@ -61,6 +70,11 @@ rules: - nbresources/finalizers - nbroutingpeers/finalizers - nbpolicies/finalizers + - setupkeys/finalizers + - groups/finalizers + - networkrouters/finalizers + - networkresources/finalizers + - sidecarprofiles/finalizers verbs: - update - apiGroups: @@ -99,7 +113,6 @@ rules: - watch - create - delete -{{- end }} - apiGroups: - "" resources: diff --git a/internal/webhook/v1/pod_webhook.go b/internal/webhook/v1/pod_webhook.go index 640d06d..73ce861 100644 --- a/internal/webhook/v1/pod_webhook.go +++ b/internal/webhook/v1/pod_webhook.go @@ -17,20 +17,31 @@ limitations under the License. package v1 import ( + "cmp" "context" + "encoding/json" "fmt" + "slices" + "strings" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/strategicpatch" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" + nbv1alpha1 "github.com/netbirdio/kubernetes-operator/api/v1alpha1" + "github.com/netbirdio/kubernetes-operator/internal/controller" ) const ( + SidecarProfileAnnotation = "netbird.io/sidecar-profile" + setupKeyAnnotation = "netbird.io/setup-key" sidecarAnnotation = "netbird.io/init-sidecar" ) @@ -60,14 +71,128 @@ type PodNetbirdInjector struct { var _ admission.Defaulter[*corev1.Pod] = &PodNetbirdInjector{} -// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Pod. func (d *PodNetbirdInjector) Default(ctx context.Context, pod *corev1.Pod) error { - podlog.Info("Defaulting for Pod", "name", pod.GetName()) + // If setup key annotations are set we do the legacy injection. + if pod.Annotations != nil && pod.Annotations[setupKeyAnnotation] != "" { + return d.legacyInjector(ctx, pod) + } - // if the setup key annotation is missing, do nothing. - if pod.Annotations == nil || pod.Annotations[setupKeyAnnotation] == "" { + // Find sidecar profiles matching pods labels. + sidecarProfileList := &nbv1alpha1.SidecarProfileList{} + err := d.client.List(ctx, sidecarProfileList, client.InNamespace(pod.Namespace)) + if err != nil { + return err + } + sidecarProfiles := []nbv1alpha1.SidecarProfile{} + for _, sidecarProfile := range sidecarProfileList.Items { + if sidecarProfile.Spec.PodSelector == nil || sidecarProfile.Spec.PodSelector.Size() == 0 { + sidecarProfiles = append(sidecarProfiles, sidecarProfile) + continue + } + selector, err := metav1.LabelSelectorAsSelector(sidecarProfile.Spec.PodSelector) + if err != nil { + return err + } + if selector.Matches(labels.Set(pod.Labels)) { + sidecarProfiles = append(sidecarProfiles, sidecarProfile) + } + } + // Do nothing if no profile matches. + if len(sidecarProfiles) == 0 { return nil } + // If two match we chose the first in alphabetical order. + if len(sidecarProfiles) > 1 { + slices.SortFunc(sidecarProfiles, func(a, b nbv1alpha1.SidecarProfile) int { + return cmp.Compare(a.Name, b.Name) + }) + } + sidecarProfile := sidecarProfiles[0] + + // Get setup key referenced by sidecar profile. + setupKey := &nbv1alpha1.SetupKey{ + ObjectMeta: metav1.ObjectMeta{ + Name: sidecarProfile.Spec.SetupKeyRef.Name, + Namespace: pod.Namespace, + }, + } + err = d.client.Get(ctx, client.ObjectKeyFromObject(setupKey), setupKey) + if err != nil { + return err + } + + // Add sidecar container. + envVars := []corev1.EnvVar{ + { + Name: "NB_SETUP_KEY", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: setupKey.SecretName(), + }, + Key: controller.SetupKeySecretKey, + }, + }, + }, + { + Name: "NB_MANAGEMENT_URL", + Value: d.managementURL, + }, + } + if len(sidecarProfile.Spec.ExtraDNSLabels) > 0 { + envVars = append(envVars, corev1.EnvVar{ + Name: "NB_EXTRA_DNS_LABELS", + Value: strings.Join(sidecarProfile.Spec.ExtraDNSLabels, ","), + }) + } + + container := corev1.Container{ + Name: "netbird", + Image: d.clientImage, + Env: envVars, + SecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{"NET_ADMIN"}, + }, + }, + } + if sidecarProfile.Spec.ContainerOverride != nil { + baseJSON, err := json.Marshal(&container) + if err != nil { + return err + } + overrideJSON, err := json.Marshal(sidecarProfile.Spec.ContainerOverride) + if err != nil { + return err + } + mergedJSON, err := strategicpatch.StrategicMergePatch(baseJSON, overrideJSON, corev1.Container{}) + if err != nil { + return err + } + err = json.Unmarshal(mergedJSON, &container) + if err != nil { + return err + } + } + + switch sidecarProfile.Spec.InjectionMode { + case nbv1alpha1.InjectionModeSidecar: + restartPolicy := corev1.ContainerRestartPolicyAlways + container.RestartPolicy = &restartPolicy + pod.Spec.InitContainers = append(pod.Spec.InitContainers, container) + case nbv1alpha1.InjectionModeContainer: + pod.Spec.Containers = append(pod.Spec.Containers, container) + default: + return fmt.Errorf("unknown injection mode %s", sidecarProfile.Spec.InjectionMode) + } + + pod.Annotations[SidecarProfileAnnotation] = sidecarProfile.Name + + return nil +} + +func (d *PodNetbirdInjector) legacyInjector(ctx context.Context, pod *corev1.Pod) error { + podlog.Info("Defaulting for Pod", "name", pod.GetName()) // retrieve the NBSetupKey resource var nbSetupKey netbirdiov1.NBSetupKey @@ -118,7 +243,17 @@ func (d *PodNetbirdInjector) Default(ctx context.Context, pod *corev1.Pod) error } // Build the netbird container spec. - nbContainer := d.buildNetbirdContainer(envVars, nbSetupKey.Spec.VolumeMounts) + nbContainer := corev1.Container{ + Name: "netbird", + Image: d.clientImage, + Env: envVars, + SecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{"NET_ADMIN"}, + }, + }, + VolumeMounts: nbSetupKey.Spec.VolumeMounts, + } // If sidecar mode is requested, inject as a sidecar (init container with restartPolicy: Always). if pod.Annotations[sidecarAnnotation] == "true" { @@ -130,22 +265,5 @@ func (d *PodNetbirdInjector) Default(ctx context.Context, pod *corev1.Pod) error } pod.Spec.Volumes = append(pod.Spec.Volumes, nbSetupKey.Spec.Volumes...) - return nil } - -// buildNetbirdContainer constructs the NetBird container spec with the given -// environment variables and volume mounts. -func (d *PodNetbirdInjector) buildNetbirdContainer(envVars []corev1.EnvVar, volumeMounts []corev1.VolumeMount) corev1.Container { - return corev1.Container{ - Name: "netbird", - Image: d.clientImage, - Env: envVars, - SecurityContext: &corev1.SecurityContext{ - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{"NET_ADMIN"}, - }, - }, - VolumeMounts: volumeMounts, - } -} diff --git a/internal/webhook/v1/pod_webhook_test.go b/internal/webhook/v1/pod_webhook_test.go index b67c80d..f74405d 100644 --- a/internal/webhook/v1/pod_webhook_test.go +++ b/internal/webhook/v1/pod_webhook_test.go @@ -20,11 +20,11 @@ import ( "context" netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" + nbv1alpha1 "github.com/netbirdio/kubernetes-operator/api/v1alpha1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - corev1 "k8s.io/api/core/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) var _ = Describe("Pod Webhook", func() { @@ -35,7 +35,7 @@ var _ = Describe("Pod Webhook", func() { BeforeEach(func() { obj = &corev1.Pod{ - ObjectMeta: v1.ObjectMeta{ + ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "test", Annotations: make(map[string]string), @@ -83,7 +83,7 @@ var _ = Describe("Pod Webhook", func() { When("NBSetupKey exists", Ordered, func() { BeforeAll(func() { sk := netbirdiov1.NBSetupKey{ - ObjectMeta: v1.ObjectMeta{ + ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "test", }, @@ -98,7 +98,7 @@ var _ = Describe("Pod Webhook", func() { } err := k8sClient.Create(context.Background(), &corev1.Namespace{ - ObjectMeta: v1.ObjectMeta{ + ObjectMeta: metav1.ObjectMeta{ Name: "test", }, }) @@ -154,4 +154,59 @@ var _ = Describe("Pod Webhook", func() { }) }) + + Context("When creating Pod with SidecarProfile", func() { + BeforeEach(func() { + sidecarProfile := &nbv1alpha1.SidecarProfile{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + Spec: nbv1alpha1.SidecarProfileSpec{ + SetupKeyRef: corev1.LocalObjectReference{ + Name: "test", + }, + InjectionMode: nbv1alpha1.InjectionModeContainer, + }, + } + Expect(k8sClient.Create(context.Background(), sidecarProfile)).To(Succeed()) + }) + + AfterEach(func() { + sidecarProfile := &nbv1alpha1.SidecarProfile{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + } + Expect(k8sClient.Delete(ctx, sidecarProfile)).To(Succeed()) + }) + + When("SetupKey doesn't exist", func() { + It("Should fail", func() { + Expect(defaulter.Default(context.Background(), obj)).To(HaveOccurred()) + Expect(obj.Spec.Containers).To(HaveLen(1)) + }) + }) + + When("SetupKey exists", func() { + It("Should succeed", func() { + setupKey := &nbv1alpha1.SetupKey{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + Spec: nbv1alpha1.SetupKeySpec{ + Name: "test", + Ephemeral: true, + }, + } + Expect(k8sClient.Create(context.Background(), setupKey)).To(Succeed()) + + Expect(defaulter.Default(context.Background(), obj)).NotTo(HaveOccurred()) + Expect(obj.Spec.Containers).To(HaveLen(2)) + Expect(obj.Spec.Containers[1].Name).To(Equal("netbird")) + }) + }) + }) }) diff --git a/internal/webhook/v1/webhook_suite_test.go b/internal/webhook/v1/webhook_suite_test.go index 59c3a8a..023b8f1 100644 --- a/internal/webhook/v1/webhook_suite_test.go +++ b/internal/webhook/v1/webhook_suite_test.go @@ -42,6 +42,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook" netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" + nbv1alpha1 "github.com/netbirdio/kubernetes-operator/api/v1alpha1" // +kubebuilder:scaffold:imports ) @@ -78,6 +79,9 @@ var _ = BeforeSuite(func() { err = netbirdiov1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) + err = nbv1alpha1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + // +kubebuilder:scaffold:scheme By("bootstrapping test environment") diff --git a/pkg/applyconfigurations/api/v1alpha1/containeroverride.go b/pkg/applyconfigurations/api/v1alpha1/containeroverride.go new file mode 100644 index 0000000..088ae89 --- /dev/null +++ b/pkg/applyconfigurations/api/v1alpha1/containeroverride.go @@ -0,0 +1,48 @@ +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1 "k8s.io/api/core/v1" +) + +// ContainerOverrideApplyConfiguration represents a declarative configuration of the ContainerOverride type for use +// with apply. +type ContainerOverrideApplyConfiguration struct { + // Image overrides the image used by the client. + Image *string `json:"image,omitempty"` + Env []v1.EnvVar `json:"env,omitempty"` + SecurityContext *v1.SecurityContext `json:"securityContext,omitempty"` +} + +// ContainerOverrideApplyConfiguration constructs a declarative configuration of the ContainerOverride type for use with +// apply. +func ContainerOverride() *ContainerOverrideApplyConfiguration { + return &ContainerOverrideApplyConfiguration{} +} + +// WithImage sets the Image 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 Image field is set to the value of the last call. +func (b *ContainerOverrideApplyConfiguration) WithImage(value string) *ContainerOverrideApplyConfiguration { + b.Image = &value + return b +} + +// WithEnv adds the given value to the Env 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 Env field. +func (b *ContainerOverrideApplyConfiguration) WithEnv(values ...v1.EnvVar) *ContainerOverrideApplyConfiguration { + for i := range values { + b.Env = append(b.Env, values[i]) + } + return b +} + +// WithSecurityContext sets the SecurityContext 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 SecurityContext field is set to the value of the last call. +func (b *ContainerOverrideApplyConfiguration) WithSecurityContext(value v1.SecurityContext) *ContainerOverrideApplyConfiguration { + b.SecurityContext = &value + return b +} diff --git a/pkg/applyconfigurations/api/v1alpha1/sidecarprofile.go b/pkg/applyconfigurations/api/v1alpha1/sidecarprofile.go new file mode 100644 index 0000000..eecbe58 --- /dev/null +++ b/pkg/applyconfigurations/api/v1alpha1/sidecarprofile.go @@ -0,0 +1,229 @@ +// 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" +) + +// SidecarProfileApplyConfiguration represents a declarative configuration of the SidecarProfile type for use +// with apply. +// +// SidecarProfile is the Schema for the sidecarprofiles API. +type SidecarProfileApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *SidecarProfileSpecApplyConfiguration `json:"spec,omitempty"` + Status *SidecarProfileStatusApplyConfiguration `json:"status,omitempty"` +} + +// SidecarProfile constructs a declarative configuration of the SidecarProfile type for use with +// apply. +func SidecarProfile(name, namespace string) *SidecarProfileApplyConfiguration { + b := &SidecarProfileApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("SidecarProfile") + b.WithAPIVersion("netbird.io/v1alpha1") + return b +} + +func (b SidecarProfileApplyConfiguration) 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 *SidecarProfileApplyConfiguration) WithKind(value string) *SidecarProfileApplyConfiguration { + 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 *SidecarProfileApplyConfiguration) WithAPIVersion(value string) *SidecarProfileApplyConfiguration { + 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 *SidecarProfileApplyConfiguration) WithName(value string) *SidecarProfileApplyConfiguration { + 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 *SidecarProfileApplyConfiguration) WithGenerateName(value string) *SidecarProfileApplyConfiguration { + 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 *SidecarProfileApplyConfiguration) WithNamespace(value string) *SidecarProfileApplyConfiguration { + 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 *SidecarProfileApplyConfiguration) WithUID(value types.UID) *SidecarProfileApplyConfiguration { + 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 *SidecarProfileApplyConfiguration) WithResourceVersion(value string) *SidecarProfileApplyConfiguration { + 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 *SidecarProfileApplyConfiguration) WithGeneration(value int64) *SidecarProfileApplyConfiguration { + 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 *SidecarProfileApplyConfiguration) WithCreationTimestamp(value metav1.Time) *SidecarProfileApplyConfiguration { + 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 *SidecarProfileApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *SidecarProfileApplyConfiguration { + 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 *SidecarProfileApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *SidecarProfileApplyConfiguration { + 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 *SidecarProfileApplyConfiguration) WithLabels(entries map[string]string) *SidecarProfileApplyConfiguration { + 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 *SidecarProfileApplyConfiguration) WithAnnotations(entries map[string]string) *SidecarProfileApplyConfiguration { + 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 *SidecarProfileApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *SidecarProfileApplyConfiguration { + 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 *SidecarProfileApplyConfiguration) WithFinalizers(values ...string) *SidecarProfileApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *SidecarProfileApplyConfiguration) 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 *SidecarProfileApplyConfiguration) WithSpec(value *SidecarProfileSpecApplyConfiguration) *SidecarProfileApplyConfiguration { + 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 *SidecarProfileApplyConfiguration) WithStatus(value *SidecarProfileStatusApplyConfiguration) *SidecarProfileApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *SidecarProfileApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *SidecarProfileApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *SidecarProfileApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *SidecarProfileApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/pkg/applyconfigurations/api/v1alpha1/sidecarprofilespec.go b/pkg/applyconfigurations/api/v1alpha1/sidecarprofilespec.go new file mode 100644 index 0000000..dcbf6fb --- /dev/null +++ b/pkg/applyconfigurations/api/v1alpha1/sidecarprofilespec.go @@ -0,0 +1,74 @@ +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/netbirdio/kubernetes-operator/api/v1alpha1" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// SidecarProfileSpecApplyConfiguration represents a declarative configuration of the SidecarProfileSpec type for use +// with apply. +// +// SidecarProfileSpec defines the desired state of SidecarProfile. +type SidecarProfileSpecApplyConfiguration struct { + // SetupKeyRef is the reference to the setup key used in the client. + SetupKeyRef *v1.LocalObjectReference `json:"setupKeyRef,omitempty"` + // PodSelector determines which pods the profile should apply to. + // An empty slector means the profile will apply to all pods in the namespace. + PodSelector *metav1.LabelSelectorApplyConfiguration `json:"podSelector,omitempty"` + // InjectionMode defines whether the sidecar is injected as a native Kubernetes sidecar container or as a regular container. + InjectionMode *apiv1alpha1.InjectionMode `json:"injectionMode,omitempty"` + // ExtraDNSLabels assigns additional DNS names to peers beyond their default hostname. + ExtraDNSLabels []string `json:"extraDNSLabels,omitempty"` + ContainerOverride *ContainerOverrideApplyConfiguration `json:"containerOverride,omitempty"` +} + +// SidecarProfileSpecApplyConfiguration constructs a declarative configuration of the SidecarProfileSpec type for use with +// apply. +func SidecarProfileSpec() *SidecarProfileSpecApplyConfiguration { + return &SidecarProfileSpecApplyConfiguration{} +} + +// WithSetupKeyRef sets the SetupKeyRef 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 SetupKeyRef field is set to the value of the last call. +func (b *SidecarProfileSpecApplyConfiguration) WithSetupKeyRef(value v1.LocalObjectReference) *SidecarProfileSpecApplyConfiguration { + b.SetupKeyRef = &value + return b +} + +// WithPodSelector sets the PodSelector 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 PodSelector field is set to the value of the last call. +func (b *SidecarProfileSpecApplyConfiguration) WithPodSelector(value *metav1.LabelSelectorApplyConfiguration) *SidecarProfileSpecApplyConfiguration { + b.PodSelector = value + return b +} + +// WithInjectionMode sets the InjectionMode 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 InjectionMode field is set to the value of the last call. +func (b *SidecarProfileSpecApplyConfiguration) WithInjectionMode(value apiv1alpha1.InjectionMode) *SidecarProfileSpecApplyConfiguration { + b.InjectionMode = &value + return b +} + +// WithExtraDNSLabels adds the given value to the ExtraDNSLabels 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 ExtraDNSLabels field. +func (b *SidecarProfileSpecApplyConfiguration) WithExtraDNSLabels(values ...string) *SidecarProfileSpecApplyConfiguration { + for i := range values { + b.ExtraDNSLabels = append(b.ExtraDNSLabels, values[i]) + } + return b +} + +// WithContainerOverride sets the ContainerOverride 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 ContainerOverride field is set to the value of the last call. +func (b *SidecarProfileSpecApplyConfiguration) WithContainerOverride(value *ContainerOverrideApplyConfiguration) *SidecarProfileSpecApplyConfiguration { + b.ContainerOverride = value + return b +} diff --git a/pkg/applyconfigurations/api/v1alpha1/sidecarprofilestatus.go b/pkg/applyconfigurations/api/v1alpha1/sidecarprofilestatus.go new file mode 100644 index 0000000..3581316 --- /dev/null +++ b/pkg/applyconfigurations/api/v1alpha1/sidecarprofilestatus.go @@ -0,0 +1,35 @@ +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// SidecarProfileStatusApplyConfiguration represents a declarative configuration of the SidecarProfileStatus type for use +// with apply. +// +// SidecarProfileStatus defines the observed state of SidecarProfile. +type SidecarProfileStatusApplyConfiguration struct { + // Conditions holds the conditions for the SidecarProfile. + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` +} + +// SidecarProfileStatusApplyConfiguration constructs a declarative configuration of the SidecarProfileStatus type for use with +// apply. +func SidecarProfileStatus() *SidecarProfileStatusApplyConfiguration { + return &SidecarProfileStatusApplyConfiguration{} +} + +// 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 *SidecarProfileStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *SidecarProfileStatusApplyConfiguration { + 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/utils.go b/pkg/applyconfigurations/utils.go index 073a785..6091592 100644 --- a/pkg/applyconfigurations/utils.go +++ b/pkg/applyconfigurations/utils.go @@ -16,6 +16,8 @@ import ( func ForKind(kind schema.GroupVersionKind) interface{} { switch kind { // Group=netbird.io, Version=v1alpha1 + case v1alpha1.SchemeGroupVersion.WithKind("ContainerOverride"): + return &apiv1alpha1.ContainerOverrideApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("CrossNamespaceReference"): return &apiv1alpha1.CrossNamespaceReferenceApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("DNSZoneReference"): @@ -46,6 +48,12 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.SetupKeySpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("SetupKeyStatus"): return &apiv1alpha1.SetupKeyStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("SidecarProfile"): + return &apiv1alpha1.SidecarProfileApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("SidecarProfileSpec"): + return &apiv1alpha1.SidecarProfileSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("SidecarProfileStatus"): + return &apiv1alpha1.SidecarProfileStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("WorkloadOverride"): return &apiv1alpha1.WorkloadOverrideApplyConfiguration{}