Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: restrict the membercluster name length to 63 #946

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apis/cluster/v1/membercluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
// +kubebuilder:printcolumn:JSONPath=`.status.resourceUsage.allocatable.memory`,name="Allocatable-Memory", priority=1, type=string

// MemberCluster is a resource created in the hub cluster to represent a member cluster within a fleet.
// +kubebuilder:validation:XValidation:rule="size(self.metadata.name) < 64",message="metadata.name max length is 63"
type MemberCluster struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Expand Down
1 change: 1 addition & 0 deletions apis/cluster/v1beta1/membercluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
// +kubebuilder:printcolumn:JSONPath=`.status.resourceUsage.allocatable.memory`,name="Allocatable-Memory", priority=1, type=string

// MemberCluster is a resource created in the hub cluster to represent a member cluster within a fleet.
// +kubebuilder:validation:XValidation:rule="size(self.metadata.name) < 64",message="metadata.name max length is 63"
type MemberCluster struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,9 @@ spec:
required:
- spec
type: object
x-kubernetes-validations:
- message: metadata.name max length is 63
rule: size(self.metadata.name) < 64
served: true
storage: false
subresources:
Expand Down Expand Up @@ -801,6 +804,9 @@ spec:
required:
- spec
type: object
x-kubernetes-validations:
- message: metadata.name max length is 63
rule: size(self.metadata.name) < 64
served: true
storage: true
subresources:
Expand Down
226 changes: 226 additions & 0 deletions test/e2e/resource_validation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
/*
Copyright (c) Microsoft Corporation.
Licensed under the MIT license.
*/
package e2e
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please move this to integration test

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi ryan, will the e2e tests be better here? The invalid MC is rejected by the CRD definition, while "The controller-runtime/pkg/envtest Go library helps write integration tests for your controllers by setting up and starting an instance of etcd and the Kubernetes API server, without kubelet, controller-manager or other components."

The ITs is better to test the controller behavior itself.


import (
"errors"
"fmt"

"k8s.io/apimachinery/pkg/types"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

"reflect"

clusterv1beta1 "go.goms.io/fleet/apis/cluster/v1beta1"
rbacv1 "k8s.io/api/rbac/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Comment on lines +8 to +21
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please group the imports correctly

)

var _ = Describe("Resource validation tests for Member Cluster", func() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you split the tests into two, one is to allow create and the other one is to deny to improve the readability.

It("should deny creating API with invalid name size", func() {
var name = "abcdef-123456789-123456789-123456789-123456789-123456789-123456789-123456789"
// Create the API.
memberClusterName := &clusterv1beta1.MemberCluster{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: clusterv1beta1.MemberClusterSpec{
Identity: rbacv1.Subject{
Name: "fleet-member-agent-cluster-1",
Kind: "ServiceAccount",
Namespace: "fleet-system",
APIGroup: "",
},
},
}
By(fmt.Sprintf("expecting denial of CREATE API %s", name))
err := hubClient.Create(ctx, memberClusterName)
var statusErr *k8serrors.StatusError
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{})))
Expect(statusErr.Status().Message).Should(ContainSubstring("metadata.name max length is 63"))
})

It("should allow creating API with valid name size", func() {
var name = "abc-123456789-123456789-123456789-123456789-123456789-123456789"
// Create the API.
memberClusterName := &clusterv1beta1.MemberCluster{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: clusterv1beta1.MemberClusterSpec{
Identity: rbacv1.Subject{
Name: "fleet-member-agent-cluster-1",
Kind: "ServiceAccount",
Namespace: "fleet-system",
APIGroup: "",
},
},
}
Expect(impersonateHubClient.Create(ctx, memberClusterName)).Should(Succeed())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Expect(impersonateHubClient.Create(ctx, memberClusterName)).Should(Succeed())
Expect(hubClient.Create(ctx, memberClusterName)).Should(Succeed())

plese use hubClient instead and the imerpsonateHubClient is used for other test purpose.

Expect(impersonateHubClient.Get(ctx, types.NamespacedName{Name: memberClusterName.Name}, memberClusterName)).Should(Succeed())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't need to validate the get, since the reject should fail the create request.

Expect(impersonateHubClient.Delete(ctx, memberClusterName)).Should(Succeed())
ensureMemberClusterAndRelatedResourcesDeletion(name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This func will delete mc. Please remove line 66.

})

It("should deny creating API with invalid name starting with non-alphanumeric character", func() {
var name = "-abcdef-123456789-123456789-123456789-123456789-123456789"
// Create the API.
memberClusterName := &clusterv1beta1.MemberCluster{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: clusterv1beta1.MemberClusterSpec{
Identity: rbacv1.Subject{
Name: "fleet-member-agent-cluster-1",
Kind: "ServiceAccount",
Namespace: "fleet-system",
APIGroup: "",
},
},
}
By(fmt.Sprintf("expecting denial of CREATE API %s", name))
err := hubClient.Create(ctx, memberClusterName)
var statusErr *k8serrors.StatusError
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{})))
Expect(statusErr.Status().Message).Should(ContainSubstring("a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to compare the full error strings and it's not reliable cause the package may change the error message.

Checking if have "a lowercase RFC 1123 subdomain" should be fine.

})

It("should allow creating API with valid name starting with alphabet character", func() {
var name = "abc-123456789"
// Create the API.
memberClusterName := &clusterv1beta1.MemberCluster{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: clusterv1beta1.MemberClusterSpec{
Identity: rbacv1.Subject{
Name: "fleet-member-agent-cluster-1",
Kind: "ServiceAccount",
Namespace: "fleet-system",
APIGroup: "",
},
},
}
Expect(impersonateHubClient.Create(ctx, memberClusterName)).Should(Succeed())
Expect(impersonateHubClient.Get(ctx, types.NamespacedName{Name: memberClusterName.Name}, memberClusterName)).Should(Succeed())
Expect(impersonateHubClient.Delete(ctx, memberClusterName)).Should(Succeed())
ensureMemberClusterAndRelatedResourcesDeletion(name)
})

It("should allow creating API with valid name starting with numeric character", func() {
var name = "123-123456789"
// Create the API.
memberClusterName := &clusterv1beta1.MemberCluster{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: clusterv1beta1.MemberClusterSpec{
Identity: rbacv1.Subject{
Name: "fleet-member-agent-cluster-1",
Kind: "ServiceAccount",
Namespace: "fleet-system",
APIGroup: "",
},
},
}
Expect(impersonateHubClient.Create(ctx, memberClusterName)).Should(Succeed())
Expect(impersonateHubClient.Get(ctx, types.NamespacedName{Name: memberClusterName.Name}, memberClusterName)).Should(Succeed())
Expect(impersonateHubClient.Delete(ctx, memberClusterName)).Should(Succeed())
ensureMemberClusterAndRelatedResourcesDeletion(name)
})

It("should deny creating API with invalid name ending with non-alphanumeric character", func() {
var name = "abcdef-123456789-123456789-123456789-123456789-123456789-"
// Create the API.
memberClusterName := &clusterv1beta1.MemberCluster{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: clusterv1beta1.MemberClusterSpec{
Identity: rbacv1.Subject{
Name: "fleet-member-agent-cluster-1",
Kind: "ServiceAccount",
Namespace: "fleet-system",
APIGroup: "",
},
},
}
By(fmt.Sprintf("expecting denial of CREATE API %s", name))
err := hubClient.Create(ctx, memberClusterName)
var statusErr *k8serrors.StatusError
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{})))
Expect(statusErr.Status().Message).Should(ContainSubstring("a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"))
})

It("should allow creating API with valid name ending with alphabet character", func() {
var name = "123456789-abc"
// Create the API.
memberClusterName := &clusterv1beta1.MemberCluster{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: clusterv1beta1.MemberClusterSpec{
Identity: rbacv1.Subject{
Name: "fleet-member-agent-cluster-1",
Kind: "ServiceAccount",
Namespace: "fleet-system",
APIGroup: "",
},
},
}
Expect(impersonateHubClient.Create(ctx, memberClusterName)).Should(Succeed())
Expect(impersonateHubClient.Get(ctx, types.NamespacedName{Name: memberClusterName.Name}, memberClusterName)).Should(Succeed())
Expect(impersonateHubClient.Delete(ctx, memberClusterName)).Should(Succeed())
ensureMemberClusterAndRelatedResourcesDeletion(name)
})

It("should allow creating API with valid name ending with numeric character", func() {
var name = "123456789-123"
// Create the API.
memberClusterName := &clusterv1beta1.MemberCluster{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: clusterv1beta1.MemberClusterSpec{
Identity: rbacv1.Subject{
Name: "fleet-member-agent-cluster-1",
Kind: "ServiceAccount",
Namespace: "fleet-system",
APIGroup: "",
},
},
}
Expect(impersonateHubClient.Create(ctx, memberClusterName)).Should(Succeed())
Expect(impersonateHubClient.Get(ctx, types.NamespacedName{Name: memberClusterName.Name}, memberClusterName)).Should(Succeed())
Expect(impersonateHubClient.Delete(ctx, memberClusterName)).Should(Succeed())
ensureMemberClusterAndRelatedResourcesDeletion(name)
})

It("should deny creating API with invalid name containing character that is not alphanumeric and not -", func() {
var name = "a_bcdef-123456789-123456789-123456789-123456789-123456789-123456789-123456789"
// Create the API.
memberClusterName := &clusterv1beta1.MemberCluster{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: clusterv1beta1.MemberClusterSpec{
Identity: rbacv1.Subject{
Name: "fleet-member-agent-cluster-1",
Kind: "ServiceAccount",
Namespace: "fleet-system",
APIGroup: "",
},
},
}
By(fmt.Sprintf("expecting denial of CREATE API %s", name))
err := hubClient.Create(ctx, memberClusterName)
var statusErr *k8serrors.StatusError
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{})))
Expect(statusErr.Status().Message).Should(ContainSubstring("a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"))
})
})
Loading