From 081b4c72b307129a0f4483a883df6a625405b9c2 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 30 Oct 2025 13:22:15 +0100 Subject: [PATCH] feat: additional service ports (#999) * feat: additional service ports Signed-off-by: Dario Tranchitella * docs: additional service ports Signed-off-by: Dario Tranchitella --------- Signed-off-by: Dario Tranchitella --- api/v1alpha1/tenantcontrolplane_types.go | 30 ++++++++ api/v1alpha1/zz_generated.deepcopy.go | 28 +++++++ ...i.clastix.io_tenantcontrolplanes_spec.yaml | 50 ++++++++++++ ...kamaji.clastix.io_tenantcontrolplanes.yaml | 50 ++++++++++++ docs/content/reference/api.md | 77 +++++++++++++++++++ internal/resources/k8s_service_resource.go | 33 ++++++-- 6 files changed, 263 insertions(+), 5 deletions(-) diff --git a/api/v1alpha1/tenantcontrolplane_types.go b/api/v1alpha1/tenantcontrolplane_types.go index 0a8a89b..666f185 100644 --- a/api/v1alpha1/tenantcontrolplane_types.go +++ b/api/v1alpha1/tenantcontrolplane_types.go @@ -7,6 +7,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" ) // NetworkProfileSpec defines the desired state of NetworkProfile. @@ -89,6 +90,32 @@ type KubernetesSpec struct { AdmissionControllers AdmissionControllers `json:"admissionControllers,omitempty"` } +type AdditionalPort struct { + // The name of this port within the Service created by Kamaji. + // This must be a DNS_LABEL, must have unique names, and cannot be `kube-apiserver`, or `konnectivity-server`. + Name string `json:"name"` + // The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + //+kubebuilder:validation:Enum=TCP;UDP;SCTP + //+kubebuilder:default=TCP + Protocol corev1.Protocol `json:"protocol,omitempty"` + // The application protocol for this port. + // This is used as a hint for implementations to offer richer behavior for protocols that they understand. + // This field follows standard Kubernetes label syntax. + // Valid values are either: + // + // * Un-prefixed protocol names - reserved for IANA standard service names (as per + // RFC-6335 and https://www.iana.org/assignments/service-names). + AppProtocol *string `json:"appProtocol,omitempty"` + // The port that will be exposed by this service. + Port int32 `json:"port"` + // Number or name of the port to access on the pods of the Tenant Control Plane. + // Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + // If this is a string, it will be looked up as a named port in the + // target Pod's container ports. If this is not specified, the value + // of the 'port' field is used (an identity map). + TargetPort intstr.IntOrString `json:"targetPort"` +} + // AdditionalMetadata defines which additional metadata, such as labels and annotations, must be attached to the created resource. type AdditionalMetadata struct { Labels map[string]string `json:"labels,omitempty"` @@ -198,6 +225,9 @@ type ControlPlaneExtraArgs struct { type ServiceSpec struct { AdditionalMetadata AdditionalMetadata `json:"additionalMetadata,omitempty"` + // AdditionalPorts allows adding additional ports to the Service generated Kamaji + // which targets the Tenant Control Plane pods. + AdditionalPorts []AdditionalPort `json:"additionalPorts,omitempty"` // ServiceType allows specifying how to expose the Tenant Control Plane. ServiceType ServiceType `json:"serviceType"` } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 72fd59f..cb32d93 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -57,6 +57,27 @@ func (in *AdditionalMetadata) DeepCopy() *AdditionalMetadata { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdditionalPort) DeepCopyInto(out *AdditionalPort) { + *out = *in + if in.AppProtocol != nil { + in, out := &in.AppProtocol, &out.AppProtocol + *out = new(string) + **out = **in + } + out.TargetPort = in.TargetPort +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalPort. +func (in *AdditionalPort) DeepCopy() *AdditionalPort { + if in == nil { + return nil + } + out := new(AdditionalPort) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AdditionalVolumeMounts) DeepCopyInto(out *AdditionalVolumeMounts) { *out = *in @@ -1351,6 +1372,13 @@ func (in *SecretReference) DeepCopy() *SecretReference { func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) { *out = *in in.AdditionalMetadata.DeepCopyInto(&out.AdditionalMetadata) + if in.AdditionalPorts != nil { + in, out := &in.AdditionalPorts, &out.AdditionalPorts + *out = make([]AdditionalPort, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceSpec. diff --git a/charts/kamaji-crds/hack/kamaji.clastix.io_tenantcontrolplanes_spec.yaml b/charts/kamaji-crds/hack/kamaji.clastix.io_tenantcontrolplanes_spec.yaml index 44a035c..aec5250 100644 --- a/charts/kamaji-crds/hack/kamaji.clastix.io_tenantcontrolplanes_spec.yaml +++ b/charts/kamaji-crds/hack/kamaji.clastix.io_tenantcontrolplanes_spec.yaml @@ -6738,6 +6738,56 @@ versions: type: string type: object type: object + additionalPorts: + description: |- + AdditionalPorts allows adding additional ports to the Service generated Kamaji + which targets the Tenant Control Plane pods. + items: + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + type: string + name: + description: |- + The name of this port within the Service created by Kamaji. + This must be a DNS_LABEL, must have unique names, and cannot be `kube-apiserver`, or `konnectivity-server`. + type: string + port: + description: The port that will be exposed by this service. + format: int32 + type: integer + protocol: + default: TCP + description: The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + enum: + - TCP + - UDP + - SCTP + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods of the Tenant Control Plane. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + x-kubernetes-int-or-string: true + required: + - name + - port + - targetPort + type: object + type: array serviceType: description: ServiceType allows specifying how to expose the Tenant Control Plane. enum: diff --git a/charts/kamaji/crds/kamaji.clastix.io_tenantcontrolplanes.yaml b/charts/kamaji/crds/kamaji.clastix.io_tenantcontrolplanes.yaml index 49abfd5..647e4c2 100644 --- a/charts/kamaji/crds/kamaji.clastix.io_tenantcontrolplanes.yaml +++ b/charts/kamaji/crds/kamaji.clastix.io_tenantcontrolplanes.yaml @@ -6746,6 +6746,56 @@ spec: type: string type: object type: object + additionalPorts: + description: |- + AdditionalPorts allows adding additional ports to the Service generated Kamaji + which targets the Tenant Control Plane pods. + items: + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + type: string + name: + description: |- + The name of this port within the Service created by Kamaji. + This must be a DNS_LABEL, must have unique names, and cannot be `kube-apiserver`, or `konnectivity-server`. + type: string + port: + description: The port that will be exposed by this service. + format: int32 + type: integer + protocol: + default: TCP + description: The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + enum: + - TCP + - UDP + - SCTP + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods of the Tenant Control Plane. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + x-kubernetes-int-or-string: true + required: + - name + - port + - targetPort + type: object + type: array serviceType: description: ServiceType allows specifying how to expose the Tenant Control Plane. enum: diff --git a/docs/content/reference/api.md b/docs/content/reference/api.md index 668a2c1..c81f9fe 100644 --- a/docs/content/reference/api.md +++ b/docs/content/reference/api.md @@ -28609,6 +28609,14 @@ Defining the options for the Tenant Control Plane Service resource. AdditionalMetadata defines which additional metadata, such as labels and annotations, must be attached to the created resource.
false + + additionalPorts + []object + + AdditionalPorts allows adding additional ports to the Service generated Kamaji +which targets the Tenant Control Plane pods.
+ + false @@ -28645,6 +28653,75 @@ AdditionalMetadata defines which additional metadata, such as labels and annotat +`TenantControlPlane.spec.controlPlane.service.additionalPorts[index]` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
namestring + The name of this port within the Service created by Kamaji. +This must be a DNS_LABEL, must have unique names, and cannot be `kube-apiserver`, or `konnectivity-server`.
+
true
portinteger + The port that will be exposed by this service.
+
+ Format: int32
+
true
targetPortint or string + Number or name of the port to access on the pods of the Tenant Control Plane. +Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. +If this is a string, it will be looked up as a named port in the +target Pod's container ports. If this is not specified, the value +of the 'port' field is used (an identity map).
+
true
appProtocolstring + The application protocol for this port. +This is used as a hint for implementations to offer richer behavior for protocols that they understand. +This field follows standard Kubernetes label syntax. +Valid values are either: + +* Un-prefixed protocol names - reserved for IANA standard service names (as per +RFC-6335 and https://www.iana.org/assignments/service-names).
+
false
protocolenum + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP".
+
+ Enum: TCP, UDP, SCTP
+ Default: TCP
+
false
+ + `TenantControlPlane.spec.controlPlane.deployment` diff --git a/internal/resources/k8s_service_resource.go b/internal/resources/k8s_service_resource.go index 35cad15..791059c 100644 --- a/internal/resources/k8s_service_resource.go +++ b/internal/resources/k8s_service_resource.go @@ -98,14 +98,37 @@ func (r *KubernetesServiceResource) mutate(ctx context.Context, tenantControlPla "kamaji.clastix.io/name": tenantControlPlane.GetName(), } - if len(r.resource.Spec.Ports) == 0 { + if r.resource.Spec.Ports == nil { r.resource.Spec.Ports = make([]corev1.ServicePort, 1) } - r.resource.Spec.Ports[0].Name = "kube-apiserver" - r.resource.Spec.Ports[0].Protocol = corev1.ProtocolTCP - r.resource.Spec.Ports[0].Port = tenantControlPlane.Spec.NetworkProfile.Port - r.resource.Spec.Ports[0].TargetPort = intstr.FromInt32(tenantControlPlane.Spec.NetworkProfile.Port) + var ports []corev1.ServicePort + for i, port := range r.resource.Spec.Ports { + switch { + case i == 0: + port.Name = "kube-apiserver" + port.Protocol = corev1.ProtocolTCP + port.Port = tenantControlPlane.Spec.NetworkProfile.Port + port.TargetPort = intstr.FromInt32(tenantControlPlane.Spec.NetworkProfile.Port) + + ports = append(ports, port) + case i == 1 && port.Name == "konnectivity-server": + ports = append(ports, port) + } + } + + for _, port := range tenantControlPlane.Spec.ControlPlane.Service.AdditionalPorts { + ports = append(ports, corev1.ServicePort{ + Name: port.Name, + Protocol: port.Protocol, + AppProtocol: port.AppProtocol, + Port: port.Port, + TargetPort: port.TargetPort, + NodePort: 0, + }) + } + + r.resource.Spec.Ports = ports switch tenantControlPlane.Spec.ControlPlane.Service.ServiceType { case kamajiv1alpha1.ServiceTypeLoadBalancer: