Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
0734ea4
Restrict membercluster name to a maximum of 63 characters
jamyct Nov 7, 2024
14522cd
remove validation from v1alpha1 API
jamyct Nov 8, 2024
f6f30e9
add validation to APIs
jamyct Nov 8, 2024
8a76b17
fix validation
jamyct Nov 8, 2024
fedc8ec
fix validation
jamyct Nov 8, 2024
a51aaa6
remove new type
jamyct Nov 8, 2024
780a121
switch restrictions to metav1.ObjectMeta identities
jamyct Nov 13, 2024
d745eae
latest version
jamyct Nov 14, 2024
3e33720
Use self.metadata.name instead of self.name
jamyct Nov 14, 2024
fd3bc48
update CEL
jamyct Nov 14, 2024
e64d259
fix CEL line
jamyct Nov 14, 2024
035b19c
create e2e test file for name validation
jamyct Nov 14, 2024
1c5690e
fix issues
jamyct Nov 15, 2024
4c89154
add regex e2e tests
jamyct Nov 15, 2024
177ffe9
remove testing example
jamyct Nov 15, 2024
10faa95
fix k8serrors capital
jamyct Nov 15, 2024
73e5320
move from e2e to integration
jamyct Nov 15, 2024
1bd78f9
run goimports
jamyct Nov 15, 2024
259762b
move back to e2e for timebeing
jamyct Nov 15, 2024
79cddc6
add positive e2e cases
jamyct Nov 15, 2024
f994e61
run goimports
jamyct Nov 15, 2024
a7e2773
fix import order & replace impersonateHubClient
jamyct Nov 18, 2024
4f55580
remove line to delete mc
jamyct Nov 18, 2024
9005721
separate tests in two types: deny or allow
jamyct Nov 18, 2024
5ee5c5f
shorten error string
jamyct Nov 19, 2024
35d2061
remove unnecessary get validation
jamyct Nov 19, 2024
82a376c
test v1 not v1beta1
jamyct Nov 19, 2024
fa327e1
move tests to apis placement directory
jamyct Nov 19, 2024
625c65c
Add context
jamyct Nov 19, 2024
929f0fc
removed unused variables from utils test file
jamyct Nov 19, 2024
2ee4d9b
run goimports
jamyct Nov 19, 2024
2028c0e
run goimports on suite test
jamyct Nov 19, 2024
9170c95
fix import order arrangement
jamyct Nov 19, 2024
e870537
fix issues
jamyct Nov 19, 2024
b9fcc08
remove unused file
jamyct Nov 19, 2024
6579319
remove unused constants
jamyct Nov 19, 2024
06bb3f7
run goimports
jamyct Nov 19, 2024
55658bc
switch import from placement to cluster directory
jamyct Nov 20, 2024
e00034a
add copyright banner to suit_test.go
jamyct Nov 20, 2024
16eeb9e
add integration tests for v1beta
jamyct Nov 22, 2024
4f8ee20
remove irelevant edit
jamyct Dec 2, 2024
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
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
Expand All @@ -339,7 +340,12 @@ gopkg.in/dnaeon/go-vcr.v3 v3.2.0 h1:Rltp0Vf+Aq0u4rQXgmXgtgoRDStTnFN83cWgSGSoRzM=
gopkg.in/dnaeon/go-vcr.v3 v3.2.0/go.mod h1:2IMOnnlx9I6u9x+YBsM3tAMx6AlOxnJ0pWxQAzZ79Ag=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
217 changes: 217 additions & 0 deletions test/apis/cluster/v1/api_validation_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
/*
Copyright (c) Microsoft Corporation.
Licensed under the MIT license.
*/
package v1

import (
"errors"
"fmt"
"reflect"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
rbacv1 "k8s.io/api/rbac/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

clusterv1 "go.goms.io/fleet/apis/cluster/v1"
)

var _ = Describe("Test cluster v1 API validation", func() {
Context("Test MemberCluster API validation - invalid cases", func() {
It("should deny creating API with invalid name size", func() {
var name = "abcdef-123456789-123456789-123456789-123456789-123456789-123456789-123456789"
// Create the API.
memberClusterName := &clusterv1.MemberCluster{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: clusterv1.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))
var 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 deny creating API with invalid name starting with non-alphanumeric character", func() {
var name = "-abcdef-123456789-123456789-123456789-123456789-123456789"
// Create the API.
memberClusterName := &clusterv1.MemberCluster{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: clusterv1.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"))
})

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 := &clusterv1.MemberCluster{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: clusterv1.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"))
})

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 := &clusterv1.MemberCluster{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: clusterv1.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"))
})
})

Context("Test Member Cluster creation API validation - valid cases", func() {
It("should allow creating API with valid name size", func() {
var name = "abc-123456789-123456789-123456789-123456789-123456789-123456789"
// Create the API.
memberClusterName := &clusterv1.MemberCluster{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: clusterv1.MemberClusterSpec{
Identity: rbacv1.Subject{
Name: "fleet-member-agent-cluster-1",
Kind: "ServiceAccount",
Namespace: "fleet-system",
APIGroup: "",
},
},
}
Expect(hubClient.Create(ctx, memberClusterName)).Should(Succeed())
Expect(hubClient.Delete(ctx, memberClusterName)).Should(Succeed())
})

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

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

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

It("should allow creating API with valid name ending with numeric character", func() {
var name = "123456789-123"
// Create the API.
memberClusterName := &clusterv1.MemberCluster{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: clusterv1.MemberClusterSpec{
Identity: rbacv1.Subject{
Name: "fleet-member-agent-cluster-1",
Kind: "ServiceAccount",
Namespace: "fleet-system",
APIGroup: "",
},
},
}
Expect(hubClient.Create(ctx, memberClusterName)).Should(Succeed())
Expect(hubClient.Delete(ctx, memberClusterName)).Should(Succeed())
})
})
})
92 changes: 92 additions & 0 deletions test/apis/cluster/v1/suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
Copyright (c) Microsoft Corporation.
Licensed under the MIT license.
*/
package v1

import (
"context"
"flag"
"path/filepath"
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/klog/v2"
"k8s.io/klog/v2/textlogger"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"

clusterv1 "go.goms.io/fleet/apis/cluster/v1"
)

var (
hubTestEnv *envtest.Environment
hubClient client.Client
ctx context.Context
cancel context.CancelFunc
)

func TestAPIs(t *testing.T) {
RegisterFailHandler(Fail)

RunSpecs(t, "ClusterResourcePlacement Controller Suite")
}

var _ = BeforeSuite(func() {
By("Setup klog")
fs := flag.NewFlagSet("klog", flag.ContinueOnError)
klog.InitFlags(fs)
Expect(fs.Parse([]string{"--v", "5", "-add_dir_header", "true"})).Should(Succeed())

ctx, cancel = context.WithCancel(context.TODO())

By("bootstrap the test environment")
// Start the cluster.
hubTestEnv = &envtest.Environment{
CRDDirectoryPaths: []string{
filepath.Join("..", "..", "..", "..", "config", "crd", "bases"),
},
ErrorIfCRDPathMissing: true,
}
hubCfg, err := hubTestEnv.Start()
Expect(err).NotTo(HaveOccurred())
Expect(hubCfg).NotTo(BeNil())

Expect(clusterv1.AddToScheme(scheme.Scheme)).Should(Succeed())

klog.InitFlags(flag.CommandLine)
flag.Parse()
// Create the hub controller manager.
hubCtrlMgr, err := ctrl.NewManager(hubCfg, ctrl.Options{
Scheme: scheme.Scheme,
Metrics: metricsserver.Options{
BindAddress: "0",
},
Logger: textlogger.NewLogger(textlogger.NewConfig(textlogger.Verbosity(4))),
})
Expect(err).NotTo(HaveOccurred())

// Set up the client.
// The client must be one with cache (i.e. configured by the controller manager) to make
// use of the cache indexes.
hubClient = hubCtrlMgr.GetClient()
Expect(hubClient).NotTo(BeNil())

go func() {
defer GinkgoRecover()
err = hubCtrlMgr.Start(ctx)
Expect(err).ToNot(HaveOccurred(), "failed to start manager for hub")
}()
})

var _ = AfterSuite(func() {
defer klog.Flush()
cancel()

By("tearing down the test environment")
Expect(hubTestEnv.Stop()).Should(Succeed())
})
Loading
Loading