Skip to content

Commit 9c4a292

Browse files
authored
feat: restrict the membercluster name length to 63 (#946)
* Restrict membercluster name to a maximum of 63 characters * remove validation from v1alpha1 API * add validation to APIs * fix validation * fix validation * remove new type * switch restrictions to metav1.ObjectMeta identities * latest version * Use self.metadata.name instead of self.name * update CEL * fix CEL line * create e2e test file for name validation * fix issues * add regex e2e tests * remove testing example * fix k8serrors capital * move from e2e to integration * run goimports * move back to e2e for timebeing * add positive e2e cases * run goimports * fix import order & replace impersonateHubClient * remove line to delete mc * separate tests in two types: deny or allow * shorten error string * remove unnecessary get validation * test v1 not v1beta1 * move tests to apis placement directory * Add context * removed unused variables from utils test file * run goimports * run goimports on suite test * fix import order arrangement * fix issues * remove unused file * remove unused constants * run goimports * switch import from placement to cluster directory * add copyright banner to suit_test.go
1 parent 00d16dd commit 9c4a292

File tree

5 files changed

+317
-0
lines changed

5 files changed

+317
-0
lines changed

apis/cluster/v1/membercluster_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
// +kubebuilder:printcolumn:JSONPath=`.status.resourceUsage.allocatable.memory`,name="Allocatable-Memory", priority=1, type=string
2626

2727
// MemberCluster is a resource created in the hub cluster to represent a member cluster within a fleet.
28+
// +kubebuilder:validation:XValidation:rule="size(self.metadata.name) < 64",message="metadata.name max length is 63"
2829
type MemberCluster struct {
2930
metav1.TypeMeta `json:",inline"`
3031
metav1.ObjectMeta `json:"metadata,omitempty"`

apis/cluster/v1beta1/membercluster_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
// +kubebuilder:printcolumn:JSONPath=`.status.resourceUsage.allocatable.memory`,name="Allocatable-Memory", priority=1, type=string
2727

2828
// MemberCluster is a resource created in the hub cluster to represent a member cluster within a fleet.
29+
// +kubebuilder:validation:XValidation:rule="size(self.metadata.name) < 64",message="metadata.name max length is 63"
2930
type MemberCluster struct {
3031
metav1.TypeMeta `json:",inline"`
3132
metav1.ObjectMeta `json:"metadata,omitempty"`

config/crd/bases/cluster.kubernetes-fleet.io_memberclusters.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,9 @@ spec:
408408
required:
409409
- spec
410410
type: object
411+
x-kubernetes-validations:
412+
- message: metadata.name max length is 63
413+
rule: size(self.metadata.name) < 64
411414
served: true
412415
storage: false
413416
subresources:
@@ -801,6 +804,9 @@ spec:
801804
required:
802805
- spec
803806
type: object
807+
x-kubernetes-validations:
808+
- message: metadata.name max length is 63
809+
rule: size(self.metadata.name) < 64
804810
served: true
805811
storage: true
806812
subresources:
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
/*
2+
Copyright (c) Microsoft Corporation.
3+
Licensed under the MIT license.
4+
*/
5+
package v1
6+
7+
import (
8+
"errors"
9+
"fmt"
10+
"reflect"
11+
12+
. "github.com/onsi/ginkgo/v2"
13+
. "github.com/onsi/gomega"
14+
rbacv1 "k8s.io/api/rbac/v1"
15+
k8serrors "k8s.io/apimachinery/pkg/api/errors"
16+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17+
18+
clusterv1 "go.goms.io/fleet/apis/cluster/v1"
19+
)
20+
21+
var _ = Describe("Test cluster v1 API validation", func() {
22+
Context("Test MemberCluster API validation - invalid cases", func() {
23+
It("should deny creating API with invalid name size", func() {
24+
var name = "abcdef-123456789-123456789-123456789-123456789-123456789-123456789-123456789"
25+
// Create the API.
26+
memberClusterName := &clusterv1.MemberCluster{
27+
ObjectMeta: metav1.ObjectMeta{
28+
Name: name,
29+
},
30+
Spec: clusterv1.MemberClusterSpec{
31+
Identity: rbacv1.Subject{
32+
Name: "fleet-member-agent-cluster-1",
33+
Kind: "ServiceAccount",
34+
Namespace: "fleet-system",
35+
APIGroup: "",
36+
},
37+
},
38+
}
39+
By(fmt.Sprintf("expecting denial of CREATE API %s", name))
40+
var err = hubClient.Create(ctx, memberClusterName)
41+
var statusErr *k8serrors.StatusError
42+
Expect(errors.As(err, &statusErr)).To(BeTrue(), fmt.Sprintf("Create API call produced error %s. Error type wanted is %s.", reflect.TypeOf(err), reflect.TypeOf(&k8serrors.StatusError{})))
43+
Expect(statusErr.Status().Message).Should(ContainSubstring("metadata.name max length is 63"))
44+
})
45+
46+
It("should deny creating API with invalid name starting with non-alphanumeric character", func() {
47+
var name = "-abcdef-123456789-123456789-123456789-123456789-123456789"
48+
// Create the API.
49+
memberClusterName := &clusterv1.MemberCluster{
50+
ObjectMeta: metav1.ObjectMeta{
51+
Name: name,
52+
},
53+
Spec: clusterv1.MemberClusterSpec{
54+
Identity: rbacv1.Subject{
55+
Name: "fleet-member-agent-cluster-1",
56+
Kind: "ServiceAccount",
57+
Namespace: "fleet-system",
58+
APIGroup: "",
59+
},
60+
},
61+
}
62+
By(fmt.Sprintf("expecting denial of CREATE API %s", name))
63+
err := hubClient.Create(ctx, memberClusterName)
64+
var statusErr *k8serrors.StatusError
65+
Expect(errors.As(err, &statusErr)).To(BeTrue(), fmt.Sprintf("Create API call produced error %s. Error type wanted is %s.", reflect.TypeOf(err), reflect.TypeOf(&k8serrors.StatusError{})))
66+
Expect(statusErr.Status().Message).Should(ContainSubstring("a lowercase RFC 1123 subdomain"))
67+
})
68+
69+
It("should deny creating API with invalid name ending with non-alphanumeric character", func() {
70+
var name = "abcdef-123456789-123456789-123456789-123456789-123456789-"
71+
// Create the API.
72+
memberClusterName := &clusterv1.MemberCluster{
73+
ObjectMeta: metav1.ObjectMeta{
74+
Name: name,
75+
},
76+
Spec: clusterv1.MemberClusterSpec{
77+
Identity: rbacv1.Subject{
78+
Name: "fleet-member-agent-cluster-1",
79+
Kind: "ServiceAccount",
80+
Namespace: "fleet-system",
81+
APIGroup: "",
82+
},
83+
},
84+
}
85+
By(fmt.Sprintf("expecting denial of CREATE API %s", name))
86+
err := hubClient.Create(ctx, memberClusterName)
87+
var statusErr *k8serrors.StatusError
88+
Expect(errors.As(err, &statusErr)).To(BeTrue(), fmt.Sprintf("Create API call produced error %s. Error type wanted is %s.", reflect.TypeOf(err), reflect.TypeOf(&k8serrors.StatusError{})))
89+
Expect(statusErr.Status().Message).Should(ContainSubstring("a lowercase RFC 1123 subdomain"))
90+
})
91+
92+
It("should deny creating API with invalid name containing character that is not alphanumeric and not -", func() {
93+
var name = "a_bcdef-123456789-123456789-123456789-123456789-123456789-123456789-123456789"
94+
// Create the API.
95+
memberClusterName := &clusterv1.MemberCluster{
96+
ObjectMeta: metav1.ObjectMeta{
97+
Name: name,
98+
},
99+
Spec: clusterv1.MemberClusterSpec{
100+
Identity: rbacv1.Subject{
101+
Name: "fleet-member-agent-cluster-1",
102+
Kind: "ServiceAccount",
103+
Namespace: "fleet-system",
104+
APIGroup: "",
105+
},
106+
},
107+
}
108+
By(fmt.Sprintf("expecting denial of CREATE API %s", name))
109+
err := hubClient.Create(ctx, memberClusterName)
110+
var statusErr *k8serrors.StatusError
111+
Expect(errors.As(err, &statusErr)).To(BeTrue(), fmt.Sprintf("Create API call produced error %s. Error type wanted is %s.", reflect.TypeOf(err), reflect.TypeOf(&k8serrors.StatusError{})))
112+
Expect(statusErr.Status().Message).Should(ContainSubstring("a lowercase RFC 1123 subdomain"))
113+
})
114+
})
115+
116+
Context("Test Member Cluster creation API validation - valid cases", func() {
117+
It("should allow creating API with valid name size", func() {
118+
var name = "abc-123456789-123456789-123456789-123456789-123456789-123456789"
119+
// Create the API.
120+
memberClusterName := &clusterv1.MemberCluster{
121+
ObjectMeta: metav1.ObjectMeta{
122+
Name: name,
123+
},
124+
Spec: clusterv1.MemberClusterSpec{
125+
Identity: rbacv1.Subject{
126+
Name: "fleet-member-agent-cluster-1",
127+
Kind: "ServiceAccount",
128+
Namespace: "fleet-system",
129+
APIGroup: "",
130+
},
131+
},
132+
}
133+
Expect(hubClient.Create(ctx, memberClusterName)).Should(Succeed())
134+
Expect(hubClient.Delete(ctx, memberClusterName)).Should(Succeed())
135+
})
136+
137+
It("should allow creating API with valid name starting with alphabet character", func() {
138+
var name = "abc-123456789"
139+
// Create the API.
140+
memberClusterName := &clusterv1.MemberCluster{
141+
ObjectMeta: metav1.ObjectMeta{
142+
Name: name,
143+
},
144+
Spec: clusterv1.MemberClusterSpec{
145+
Identity: rbacv1.Subject{
146+
Name: "fleet-member-agent-cluster-1",
147+
Kind: "ServiceAccount",
148+
Namespace: "fleet-system",
149+
APIGroup: "",
150+
},
151+
},
152+
}
153+
Expect(hubClient.Create(ctx, memberClusterName)).Should(Succeed())
154+
Expect(hubClient.Delete(ctx, memberClusterName)).Should(Succeed())
155+
})
156+
157+
It("should allow creating API with valid name starting with numeric character", func() {
158+
var name = "123-123456789"
159+
// Create the API.
160+
memberClusterName := &clusterv1.MemberCluster{
161+
ObjectMeta: metav1.ObjectMeta{
162+
Name: name,
163+
},
164+
Spec: clusterv1.MemberClusterSpec{
165+
Identity: rbacv1.Subject{
166+
Name: "fleet-member-agent-cluster-1",
167+
Kind: "ServiceAccount",
168+
Namespace: "fleet-system",
169+
APIGroup: "",
170+
},
171+
},
172+
}
173+
Expect(hubClient.Create(ctx, memberClusterName)).Should(Succeed())
174+
Expect(hubClient.Delete(ctx, memberClusterName)).Should(Succeed())
175+
})
176+
177+
It("should allow creating API with valid name ending with alphabet character", func() {
178+
var name = "123456789-abc"
179+
// Create the API.
180+
memberClusterName := &clusterv1.MemberCluster{
181+
ObjectMeta: metav1.ObjectMeta{
182+
Name: name,
183+
},
184+
Spec: clusterv1.MemberClusterSpec{
185+
Identity: rbacv1.Subject{
186+
Name: "fleet-member-agent-cluster-1",
187+
Kind: "ServiceAccount",
188+
Namespace: "fleet-system",
189+
APIGroup: "",
190+
},
191+
},
192+
}
193+
Expect(hubClient.Create(ctx, memberClusterName)).Should(Succeed())
194+
Expect(hubClient.Delete(ctx, memberClusterName)).Should(Succeed())
195+
})
196+
197+
It("should allow creating API with valid name ending with numeric character", func() {
198+
var name = "123456789-123"
199+
// Create the API.
200+
memberClusterName := &clusterv1.MemberCluster{
201+
ObjectMeta: metav1.ObjectMeta{
202+
Name: name,
203+
},
204+
Spec: clusterv1.MemberClusterSpec{
205+
Identity: rbacv1.Subject{
206+
Name: "fleet-member-agent-cluster-1",
207+
Kind: "ServiceAccount",
208+
Namespace: "fleet-system",
209+
APIGroup: "",
210+
},
211+
},
212+
}
213+
Expect(hubClient.Create(ctx, memberClusterName)).Should(Succeed())
214+
Expect(hubClient.Delete(ctx, memberClusterName)).Should(Succeed())
215+
})
216+
})
217+
})

test/apis/cluster/v1/suite_test.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
Copyright (c) Microsoft Corporation.
3+
Licensed under the MIT license.
4+
*/
5+
package v1
6+
7+
import (
8+
"context"
9+
"flag"
10+
"path/filepath"
11+
"testing"
12+
13+
. "github.com/onsi/ginkgo/v2"
14+
. "github.com/onsi/gomega"
15+
"k8s.io/client-go/kubernetes/scheme"
16+
"k8s.io/klog/v2"
17+
"k8s.io/klog/v2/textlogger"
18+
ctrl "sigs.k8s.io/controller-runtime"
19+
"sigs.k8s.io/controller-runtime/pkg/client"
20+
"sigs.k8s.io/controller-runtime/pkg/envtest"
21+
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
22+
23+
clusterv1 "go.goms.io/fleet/apis/cluster/v1"
24+
)
25+
26+
var (
27+
hubTestEnv *envtest.Environment
28+
hubClient client.Client
29+
ctx context.Context
30+
cancel context.CancelFunc
31+
)
32+
33+
func TestAPIs(t *testing.T) {
34+
RegisterFailHandler(Fail)
35+
36+
RunSpecs(t, "ClusterResourcePlacement Controller Suite")
37+
}
38+
39+
var _ = BeforeSuite(func() {
40+
By("Setup klog")
41+
fs := flag.NewFlagSet("klog", flag.ContinueOnError)
42+
klog.InitFlags(fs)
43+
Expect(fs.Parse([]string{"--v", "5", "-add_dir_header", "true"})).Should(Succeed())
44+
45+
ctx, cancel = context.WithCancel(context.TODO())
46+
47+
By("bootstrap the test environment")
48+
// Start the cluster.
49+
hubTestEnv = &envtest.Environment{
50+
CRDDirectoryPaths: []string{
51+
filepath.Join("..", "..", "..", "..", "config", "crd", "bases"),
52+
},
53+
ErrorIfCRDPathMissing: true,
54+
}
55+
hubCfg, err := hubTestEnv.Start()
56+
Expect(err).NotTo(HaveOccurred())
57+
Expect(hubCfg).NotTo(BeNil())
58+
59+
Expect(clusterv1.AddToScheme(scheme.Scheme)).Should(Succeed())
60+
61+
klog.InitFlags(flag.CommandLine)
62+
flag.Parse()
63+
// Create the hub controller manager.
64+
hubCtrlMgr, err := ctrl.NewManager(hubCfg, ctrl.Options{
65+
Scheme: scheme.Scheme,
66+
Metrics: metricsserver.Options{
67+
BindAddress: "0",
68+
},
69+
Logger: textlogger.NewLogger(textlogger.NewConfig(textlogger.Verbosity(4))),
70+
})
71+
Expect(err).NotTo(HaveOccurred())
72+
73+
// Set up the client.
74+
// The client must be one with cache (i.e. configured by the controller manager) to make
75+
// use of the cache indexes.
76+
hubClient = hubCtrlMgr.GetClient()
77+
Expect(hubClient).NotTo(BeNil())
78+
79+
go func() {
80+
defer GinkgoRecover()
81+
err = hubCtrlMgr.Start(ctx)
82+
Expect(err).ToNot(HaveOccurred(), "failed to start manager for hub")
83+
}()
84+
})
85+
86+
var _ = AfterSuite(func() {
87+
defer klog.Flush()
88+
cancel()
89+
90+
By("tearing down the test environment")
91+
Expect(hubTestEnv.Stop()).Should(Succeed())
92+
})

0 commit comments

Comments
 (0)