From 04198652d51ca1d8952e15dd6312985885373305 Mon Sep 17 00:00:00 2001 From: Enrico Candino Date: Wed, 17 Sep 2025 10:39:55 +0200 Subject: [PATCH] check for single expose mode (#466) --- charts/k3k/crds/k3k.io_clusters.yaml | 5 +++++ pkg/apis/k3k.io/v1alpha1/types.go | 1 + pkg/controller/cluster/cluster_test.go | 20 ++++++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/charts/k3k/crds/k3k.io_clusters.yaml b/charts/k3k/crds/k3k.io_clusters.yaml index 10b6c270..76e33205 100644 --- a/charts/k3k/crds/k3k.io_clusters.yaml +++ b/charts/k3k/crds/k3k.io_clusters.yaml @@ -365,6 +365,11 @@ spec: type: integer type: object type: object + x-kubernetes-validations: + - message: ingress, loadbalancer and nodePort are mutually exclusive; + only one can be set + rule: '[has(self.ingress), has(self.loadbalancer), has(self.nodePort)].filter(x, + x).size() <= 1' mirrorHostNodes: description: |- MirrorHostNodes controls whether node objects from the host cluster diff --git a/pkg/apis/k3k.io/v1alpha1/types.go b/pkg/apis/k3k.io/v1alpha1/types.go index 559f4b83..aec48927 100644 --- a/pkg/apis/k3k.io/v1alpha1/types.go +++ b/pkg/apis/k3k.io/v1alpha1/types.go @@ -103,6 +103,7 @@ type ClusterSpec struct { // Expose specifies options for exposing the API server. // By default, it's only exposed as a ClusterIP. // + // +kubebuilder:validation:XValidation:rule="[has(self.ingress), has(self.loadbalancer), has(self.nodePort)].filter(x, x).size() <= 1",message="ingress, loadbalancer and nodePort are mutually exclusive; only one can be set" // +optional Expose *ExposeConfig `json:"expose,omitempty"` diff --git a/pkg/controller/cluster/cluster_test.go b/pkg/controller/cluster/cluster_test.go index 1201b3b4..bdd17b94 100644 --- a/pkg/controller/cluster/cluster_test.go +++ b/pkg/controller/cluster/cluster_test.go @@ -263,6 +263,26 @@ var _ = Describe("Cluster Controller", Label("controller"), Label("Cluster"), fu Expect(etcdPort.TargetPort.IntValue()).To(BeEquivalentTo(2379)) }) }) + + When("exposing the cluster with nodePort and loadbalancer", func() { + It("will fail", func() { + cluster := &v1alpha1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cluster-", + Namespace: namespace, + }, + Spec: v1alpha1.ClusterSpec{ + Expose: &v1alpha1.ExposeConfig{ + LoadBalancer: &v1alpha1.LoadBalancerConfig{}, + NodePort: &v1alpha1.NodePortConfig{}, + }, + }, + } + + err := k8sClient.Create(ctx, cluster) + Expect(err).To(HaveOccurred()) + }) + }) }) }) })