// Copyright 2021 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package groupsuffix import ( "context" "fmt" "strings" "testing" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" authv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1" loginv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/login/v1alpha1" configv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/config/v1alpha1" "go.pinniped.dev/internal/kubeclient" "go.pinniped.dev/internal/testutil" ) func ExampleReplace_loginv1alpha1() { s, _ := Replace(loginv1alpha1.GroupName, "tuna.fish.io") fmt.Println(s) // Output: login.concierge.tuna.fish.io } func ExampleReplace_string() { s, _ := Replace("idp.supervisor.pinniped.dev", "marlin.io") fmt.Println(s) // Output: idp.supervisor.marlin.io } func TestMiddlware(t *testing.T) { const newSuffix = "some.suffix.com" podWithoutOwner := &corev1.Pod{ TypeMeta: metav1.TypeMeta{ APIVersion: corev1.SchemeGroupVersion.String(), Kind: "Pod", }, ObjectMeta: metav1.ObjectMeta{ OwnerReferences: []metav1.OwnerReference{}, }, } nonPinnipedOwner := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "some-name", UID: "some-uid", }, } nonPinnipedOwnerGVK := corev1.SchemeGroupVersion.WithKind("Pod") podWithNonPinnipedOwner := &corev1.Pod{ TypeMeta: metav1.TypeMeta{ APIVersion: corev1.SchemeGroupVersion.String(), Kind: "Pod", }, ObjectMeta: metav1.ObjectMeta{ OwnerReferences: []metav1.OwnerReference{ *metav1.NewControllerRef(nonPinnipedOwner, nonPinnipedOwnerGVK), }, }, } var ok bool pinnipedOwner := &configv1alpha1.FederationDomain{ ObjectMeta: metav1.ObjectMeta{ Name: "some-name", UID: "some-uid", }, } pinnipedOwnerGVK := configv1alpha1.SchemeGroupVersion.WithKind("FederationDomain") pinnipedOwnerWithNewGroupGVK := configv1alpha1.SchemeGroupVersion.WithKind("FederationDomain") pinnipedOwnerWithNewGroupGVK.Group, ok = Replace(pinnipedOwnerWithNewGroupGVK.Group, newSuffix) require.True(t, ok) podWithPinnipedOwner := &corev1.Pod{ TypeMeta: metav1.TypeMeta{ APIVersion: corev1.SchemeGroupVersion.String(), Kind: "Pod", }, ObjectMeta: metav1.ObjectMeta{ OwnerReferences: []metav1.OwnerReference{ *metav1.NewControllerRef(pinnipedOwner, pinnipedOwnerGVK), // make sure we don't update the non-pinniped owner *metav1.NewControllerRef(nonPinnipedOwner, nonPinnipedOwnerGVK), }, }, } podWithPinnipedOwnerWithNewGroup := &corev1.Pod{ TypeMeta: metav1.TypeMeta{ APIVersion: corev1.SchemeGroupVersion.String(), Kind: "Pod", }, ObjectMeta: metav1.ObjectMeta{ OwnerReferences: []metav1.OwnerReference{ *metav1.NewControllerRef(pinnipedOwner, pinnipedOwnerWithNewGroupGVK), // make sure we don't update the non-pinniped owner *metav1.NewControllerRef(nonPinnipedOwner, nonPinnipedOwnerGVK), }, }, } federationDomainWithPinnipedOwner := &configv1alpha1.FederationDomain{ TypeMeta: metav1.TypeMeta{ APIVersion: configv1alpha1.SchemeGroupVersion.String(), Kind: "FederationDomain", }, ObjectMeta: metav1.ObjectMeta{ OwnerReferences: []metav1.OwnerReference{ *metav1.NewControllerRef(pinnipedOwner, pinnipedOwnerGVK), // make sure we don't update the non-pinniped owner *metav1.NewControllerRef(nonPinnipedOwner, nonPinnipedOwnerGVK), }, }, } federationDomainWithNewGroupAndPinnipedOwner := &configv1alpha1.FederationDomain{ TypeMeta: metav1.TypeMeta{ APIVersion: replaceGV(t, configv1alpha1.SchemeGroupVersion, newSuffix).String(), Kind: "FederationDomain", }, ObjectMeta: metav1.ObjectMeta{ OwnerReferences: []metav1.OwnerReference{ *metav1.NewControllerRef(pinnipedOwner, pinnipedOwnerGVK), // make sure we don't update the non-pinniped owner *metav1.NewControllerRef(nonPinnipedOwner, nonPinnipedOwnerGVK), }, }, } federationDomainWithNewGroupAndPinnipedOwnerWithNewGroup := &configv1alpha1.FederationDomain{ TypeMeta: metav1.TypeMeta{ APIVersion: replaceGV(t, configv1alpha1.SchemeGroupVersion, newSuffix).String(), Kind: "FederationDomain", }, ObjectMeta: metav1.ObjectMeta{ OwnerReferences: []metav1.OwnerReference{ *metav1.NewControllerRef(pinnipedOwner, pinnipedOwnerWithNewGroupGVK), // make sure we don't update the non-pinniped owner *metav1.NewControllerRef(nonPinnipedOwner, nonPinnipedOwnerGVK), }, }, } tokenCredentialRequest := with( &loginv1alpha1.TokenCredentialRequest{}, gvk(replaceGV(t, loginv1alpha1.SchemeGroupVersion, newSuffix).WithKind("TokenCredentialRequest")), ) tokenCredentialRequestWithPinnipedAuthenticator := with( tokenCredentialRequest, authenticatorAPIGroup(authv1alpha1.SchemeGroupVersion.Group), ) tokenCredentialRequestWithCustomAPIGroupAuthenticator := with( tokenCredentialRequest, authenticatorAPIGroup(replaceGV(t, authv1alpha1.SchemeGroupVersion, newSuffix).Group), ) tokenCredentialRequestWithNewGroup := with( tokenCredentialRequest, gvk(replaceGV(t, loginv1alpha1.SchemeGroupVersion, newSuffix).WithKind("TokenCredentialRequest")), ) tokenCredentialRequestWithNewGroupAndPinnipedAuthenticator := with( tokenCredentialRequestWithNewGroup, authenticatorAPIGroup(authv1alpha1.SchemeGroupVersion.Group), ) tokenCredentialRequestWithNewGroupAndCustomAPIGroupAuthenticator := with( tokenCredentialRequestWithNewGroup, authenticatorAPIGroup(replaceGV(t, authv1alpha1.SchemeGroupVersion, newSuffix).Group), ) tests := []struct { name string apiGroupSuffix string rt *testutil.RoundTrip requestObj, responseObj kubeclient.Object wantNilMiddleware bool wantMutateRequests, wantMutateResponses int wantMutateRequestErrors, wantMutateResponseErrors []string wantRequestObj, wantResponseObj kubeclient.Object }{ { name: "api group suffix is empty", apiGroupSuffix: "", rt: (&testutil.RoundTrip{}). WithVerb(kubeclient.VerbGet). WithNamespace("some-namespace"). WithResource(corev1.SchemeGroupVersion.WithResource("pods")), wantNilMiddleware: true, }, { name: "api group suffix is default", apiGroupSuffix: "pinniped.dev", rt: (&testutil.RoundTrip{}). WithVerb(kubeclient.VerbGet). WithNamespace("some-namespace"). WithResource(corev1.SchemeGroupVersion.WithResource("pods")), wantNilMiddleware: true, }, { name: "get resource without pinniped.dev", apiGroupSuffix: newSuffix, rt: (&testutil.RoundTrip{}). WithVerb(kubeclient.VerbGet). WithNamespace("some-namespace"). WithResource(corev1.SchemeGroupVersion.WithResource("pods")), responseObj: podWithoutOwner, wantMutateResponses: 1, wantResponseObj: podWithoutOwner, }, { name: "get resource without pinniped.dev with non-pinniped.dev owner ref", apiGroupSuffix: newSuffix, rt: (&testutil.RoundTrip{}). WithVerb(kubeclient.VerbGet). WithNamespace("some-namespace"). WithResource(corev1.SchemeGroupVersion.WithResource("pods")), responseObj: podWithNonPinnipedOwner, wantMutateResponses: 1, wantResponseObj: podWithNonPinnipedOwner, }, { name: "get resource without pinniped.dev with pinniped.dev owner ref", apiGroupSuffix: newSuffix, rt: (&testutil.RoundTrip{}). WithVerb(kubeclient.VerbGet). WithNamespace("some-namespace"). WithResource(corev1.SchemeGroupVersion.WithResource("pods")), responseObj: podWithPinnipedOwnerWithNewGroup, wantMutateResponses: 1, wantResponseObj: podWithPinnipedOwner, }, { name: "get resource with pinniped.dev", apiGroupSuffix: newSuffix, rt: (&testutil.RoundTrip{}). WithVerb(kubeclient.VerbGet). WithResource(loginv1alpha1.SchemeGroupVersion.WithResource("tokencredentialrequests")), requestObj: with(&metav1.PartialObjectMetadata{}, gvk(loginv1alpha1.SchemeGroupVersion.WithKind("TokenCredentialRequest"))), responseObj: tokenCredentialRequestWithNewGroup, wantMutateRequests: 1, wantMutateResponses: 1, wantRequestObj: with(&metav1.PartialObjectMetadata{}, gvk(replaceGV(t, loginv1alpha1.SchemeGroupVersion, newSuffix).WithKind("TokenCredentialRequest"))), wantResponseObj: tokenCredentialRequestWithNewGroup, // the middleware will reset object GVK for us }, { name: "create resource without pinniped.dev and without owner ref", apiGroupSuffix: newSuffix, rt: (&testutil.RoundTrip{}). WithVerb(kubeclient.VerbCreate). WithNamespace("some-namespace"). WithResource(corev1.SchemeGroupVersion.WithResource("pods")), requestObj: podWithoutOwner, responseObj: podWithoutOwner, wantMutateRequests: 1, wantMutateResponses: 1, wantRequestObj: podWithoutOwner, wantResponseObj: podWithoutOwner, }, { name: "create resource without pinniped.dev and with owner ref that has no pinniped.dev owner", apiGroupSuffix: newSuffix, rt: (&testutil.RoundTrip{}). WithVerb(kubeclient.VerbCreate). WithNamespace("some-namespace"). WithResource(corev1.SchemeGroupVersion.WithResource("pods")), requestObj: podWithNonPinnipedOwner, responseObj: podWithNonPinnipedOwner, wantMutateRequests: 1, wantMutateResponses: 1, wantRequestObj: podWithNonPinnipedOwner, wantResponseObj: podWithNonPinnipedOwner, }, { name: "create resource without pinniped.dev and with owner ref that has pinniped.dev owner", apiGroupSuffix: newSuffix, rt: (&testutil.RoundTrip{}). WithVerb(kubeclient.VerbCreate). WithNamespace("some-namespace"). WithResource(corev1.SchemeGroupVersion.WithResource("pods")), requestObj: podWithPinnipedOwner, responseObj: podWithPinnipedOwnerWithNewGroup, wantMutateRequests: 1, wantMutateResponses: 1, wantRequestObj: podWithPinnipedOwnerWithNewGroup, wantResponseObj: podWithPinnipedOwner, }, { name: "create subresource without pinniped.dev", apiGroupSuffix: newSuffix, rt: (&testutil.RoundTrip{}). WithVerb(kubeclient.VerbCreate). WithNamespace("some-namespace"). WithResource(corev1.SchemeGroupVersion.WithResource("pods")). WithSubresource("some-subresource"), responseObj: podWithPinnipedOwner, wantMutateResponses: 1, wantResponseObj: podWithPinnipedOwner, }, { // test that multiple of our middleware request mutations play nicely with each other name: "create resource with pinniped.dev and with owner ref that has pinniped.dev owner", apiGroupSuffix: newSuffix, rt: (&testutil.RoundTrip{}). WithVerb(kubeclient.VerbCreate). WithNamespace("some-namespace"). WithResource(configv1alpha1.SchemeGroupVersion.WithResource("federationdomains")), requestObj: federationDomainWithPinnipedOwner, responseObj: federationDomainWithNewGroupAndPinnipedOwnerWithNewGroup, wantMutateRequests: 2, wantMutateResponses: 1, wantRequestObj: federationDomainWithNewGroupAndPinnipedOwnerWithNewGroup, wantResponseObj: federationDomainWithNewGroupAndPinnipedOwner, // the middleware will reset object GVK for us }, { name: "update resource without pinniped.dev", apiGroupSuffix: newSuffix, rt: (&testutil.RoundTrip{}). WithVerb(kubeclient.VerbUpdate). WithNamespace("some-namespace"). WithResource(corev1.SchemeGroupVersion.WithResource("pods")), responseObj: podWithoutOwner, wantMutateResponses: 1, wantResponseObj: podWithoutOwner, }, { name: "update resource with pinniped.dev", apiGroupSuffix: newSuffix, rt: (&testutil.RoundTrip{}). WithVerb(kubeclient.VerbUpdate). WithResource(loginv1alpha1.SchemeGroupVersion.WithResource("tokencredentialrequests")), requestObj: tokenCredentialRequest, responseObj: tokenCredentialRequestWithNewGroup, wantMutateRequests: 1, wantMutateResponses: 1, wantRequestObj: tokenCredentialRequestWithNewGroup, wantResponseObj: tokenCredentialRequestWithNewGroup, // the middleware will reset object GVK for us }, { name: "list resource without pinniped.dev", apiGroupSuffix: newSuffix, rt: (&testutil.RoundTrip{}). WithVerb(kubeclient.VerbList). WithNamespace("some-namespace"). WithResource(corev1.SchemeGroupVersion.WithResource("pods")), responseObj: podWithoutOwner, wantMutateResponses: 1, wantResponseObj: podWithoutOwner, }, { name: "list resource with pinniped.dev", apiGroupSuffix: newSuffix, rt: (&testutil.RoundTrip{}). WithVerb(kubeclient.VerbList). WithResource(loginv1alpha1.SchemeGroupVersion.WithResource("tokencredentialrequests")), requestObj: tokenCredentialRequest, responseObj: tokenCredentialRequestWithNewGroup, wantMutateRequests: 1, wantMutateResponses: 1, wantRequestObj: tokenCredentialRequestWithNewGroup, wantResponseObj: tokenCredentialRequestWithNewGroup, // the middleware will reset object GVK for us }, { name: "watch resource without pinniped.dev", apiGroupSuffix: newSuffix, rt: (&testutil.RoundTrip{}). WithVerb(kubeclient.VerbWatch). WithNamespace("some-namespace"). WithResource(corev1.SchemeGroupVersion.WithResource("pods")), responseObj: podWithoutOwner, wantMutateResponses: 1, wantResponseObj: podWithoutOwner, }, { name: "watch resource with pinniped.dev", apiGroupSuffix: newSuffix, rt: (&testutil.RoundTrip{}). WithVerb(kubeclient.VerbList). WithResource(loginv1alpha1.SchemeGroupVersion.WithResource("tokencredentialrequests")), requestObj: tokenCredentialRequest, responseObj: tokenCredentialRequestWithNewGroup, wantMutateRequests: 1, wantMutateResponses: 1, wantRequestObj: tokenCredentialRequestWithNewGroup, wantResponseObj: tokenCredentialRequestWithNewGroup, // the middleware will reset object GVK for us }, { name: "patch resource without pinniped.dev", apiGroupSuffix: newSuffix, rt: (&testutil.RoundTrip{}). WithVerb(kubeclient.VerbPatch). WithNamespace("some-namespace"). WithResource(corev1.SchemeGroupVersion.WithResource("pods")), responseObj: podWithoutOwner, wantMutateResponses: 1, wantResponseObj: podWithoutOwner, }, { name: "patch resource with pinniped.dev", apiGroupSuffix: newSuffix, rt: (&testutil.RoundTrip{}). WithVerb(kubeclient.VerbPatch). WithResource(loginv1alpha1.SchemeGroupVersion.WithResource("tokencredentialrequests")), requestObj: tokenCredentialRequest, responseObj: tokenCredentialRequestWithNewGroup, wantMutateRequests: 1, wantMutateResponses: 1, wantRequestObj: tokenCredentialRequestWithNewGroup, wantResponseObj: tokenCredentialRequestWithNewGroup, // the middleware will reset object GVK for us }, { name: "create tokencredentialrequest with pinniped.dev authenticator api group", apiGroupSuffix: newSuffix, rt: (&testutil.RoundTrip{}). WithVerb(kubeclient.VerbCreate). WithResource(loginv1alpha1.SchemeGroupVersion.WithResource("tokencredentialrequests")), requestObj: tokenCredentialRequestWithPinnipedAuthenticator, responseObj: tokenCredentialRequestWithNewGroup, // a token credential response does not contain a spec wantMutateRequests: 3, wantMutateResponses: 1, wantRequestObj: tokenCredentialRequestWithNewGroupAndCustomAPIGroupAuthenticator, wantResponseObj: tokenCredentialRequestWithNewGroup, // the middleware will reset object GVK for us }, { name: "create tokencredentialrequest with custom authenticator api group fails because api group is expected to be pinniped.dev", apiGroupSuffix: newSuffix, rt: (&testutil.RoundTrip{}). WithVerb(kubeclient.VerbCreate). WithResource(loginv1alpha1.SchemeGroupVersion.WithResource("tokencredentialrequests")), requestObj: tokenCredentialRequestWithCustomAPIGroupAuthenticator, responseObj: tokenCredentialRequestWithNewGroup, // a token credential response does not contain a spec wantMutateRequests: 3, wantMutateResponses: 1, wantMutateRequestErrors: []string{`cannot replace token credential request "/" authenticator API group "authentication.concierge.some.suffix.com" with group suffix "some.suffix.com"`}, wantResponseObj: tokenCredentialRequestWithNewGroup, // the middleware will reset object GVK for us }, { name: "create tokencredentialrequest with pinniped.dev authenticator api group and subresource", apiGroupSuffix: newSuffix, rt: (&testutil.RoundTrip{}). WithVerb(kubeclient.VerbCreate). WithResource(loginv1alpha1.SchemeGroupVersion.WithResource("tokencredentialrequests")). WithSubresource("some-subresource"), requestObj: tokenCredentialRequestWithPinnipedAuthenticator, responseObj: tokenCredentialRequestWithNewGroup, // a token credential response does not contain a spec wantMutateRequests: 1, wantMutateResponses: 1, wantRequestObj: tokenCredentialRequestWithNewGroupAndPinnipedAuthenticator, wantResponseObj: tokenCredentialRequestWithNewGroup, // the middleware will reset object GVK for us }, { name: "non-create tokencredentialrequest with pinniped.dev authenticator api group", apiGroupSuffix: newSuffix, rt: (&testutil.RoundTrip{}). WithVerb(kubeclient.VerbList). WithResource(loginv1alpha1.SchemeGroupVersion.WithResource("tokencredentialrequests")), requestObj: tokenCredentialRequestWithPinnipedAuthenticator, responseObj: tokenCredentialRequestWithNewGroup, // a token credential response does not contain a spec wantMutateRequests: 1, wantMutateResponses: 1, wantRequestObj: tokenCredentialRequestWithNewGroupAndPinnipedAuthenticator, wantResponseObj: tokenCredentialRequestWithNewGroup, // the middleware will reset object GVK for us }, { name: "create tokencredentialrequest with nil authenticator api group", apiGroupSuffix: newSuffix, rt: (&testutil.RoundTrip{}). WithVerb(kubeclient.VerbCreate). WithResource(loginv1alpha1.SchemeGroupVersion.WithResource("tokencredentialrequests")), requestObj: tokenCredentialRequest, responseObj: tokenCredentialRequestWithNewGroup, // a token credential response does not contain a spec wantMutateRequests: 3, wantMutateResponses: 1, wantMutateRequestErrors: []string{`cannot replace token credential request "/" without authenticator API group`}, wantResponseObj: tokenCredentialRequestWithNewGroup, // the middleware will reset object GVK for us }, { name: "create tokencredentialrequest with non-*loginv1alpha1.TokenCredentialRequest", apiGroupSuffix: newSuffix, rt: (&testutil.RoundTrip{}). WithVerb(kubeclient.VerbCreate). WithResource(loginv1alpha1.SchemeGroupVersion.WithResource("tokencredentialrequests")), requestObj: podWithoutOwner, responseObj: podWithoutOwner, wantMutateRequests: 3, wantMutateResponses: 1, wantMutateRequestErrors: []string{`cannot cast obj of type *v1.Pod to *loginv1alpha1.TokenCredentialRequest`}, wantResponseObj: podWithoutOwner, }, } for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { m := New(test.apiGroupSuffix) if test.wantNilMiddleware { require.Nil(t, m, "wanted nil middleware") return } m.Handle(context.Background(), test.rt) require.Len(t, test.rt.MutateRequests, test.wantMutateRequests, "undesired request mutation count") require.Len(t, test.rt.MutateResponses, test.wantMutateResponses, "undesired response mutation count") if test.wantMutateRequests != 0 { require.NotNil(t, test.requestObj, "expected test.requestObj to be set") objMutated := test.requestObj.DeepCopyObject().(kubeclient.Object) var mutateRequestErrors []string for _, mutateRequest := range test.rt.MutateRequests { mutateRequest := mutateRequest if err := mutateRequest(objMutated); err != nil { mutateRequestErrors = append(mutateRequestErrors, err.Error()) } } if len(test.wantMutateRequestErrors) > 0 { require.Equal(t, test.wantMutateRequestErrors, mutateRequestErrors, "mutate request errors did not match") } else { require.Equal(t, test.wantRequestObj, objMutated, "request obj did not match") } } if test.wantMutateResponses != 0 { require.NotNil(t, test.responseObj, "expected test.responseObj to be set") objMutated := test.responseObj.DeepCopyObject().(kubeclient.Object) var mutateResponseErrors []string for _, mutateResponse := range test.rt.MutateResponses { mutateResponse := mutateResponse if err := mutateResponse(objMutated); err != nil { mutateResponseErrors = append(mutateResponseErrors, err.Error()) } } if len(test.wantMutateRequestErrors) > 0 { require.Equal(t, test.wantMutateResponseErrors, mutateResponseErrors, "mutate response errors did not match") } else { require.Equal(t, test.wantResponseObj, objMutated, "response obj did not match") } } }) } } func TestReplaceError(t *testing.T) { s, ok := Replace("bad-suffix-that-doesnt-end-in-pinniped-dot-dev", "shouldnt-matter.com") require.Equal(t, "", s) require.False(t, ok) s, ok = Replace("bad-suffix-that-end-in.prefixed-pinniped.dev", "shouldnt-matter.com") require.Equal(t, "", s) require.False(t, ok) } func TestReplaceSuffix(t *testing.T) { s, ok := Replace("something.pinniped.dev.something-else.pinniped.dev", "tuna.io") require.Equal(t, "something.pinniped.dev.something-else.tuna.io", s) require.True(t, ok) // When the replace wasn't actually needed, it still returns true. s, ok = Unreplace("something.pinniped.dev", "pinniped.dev") require.Equal(t, "something.pinniped.dev", s) require.True(t, ok) } func TestUnreplaceSuffix(t *testing.T) { s, ok := Unreplace("something.pinniped.dev.something-else.tuna.io", "tuna.io") require.Equal(t, "something.pinniped.dev.something-else.pinniped.dev", s) require.True(t, ok) // When the unreplace wasn't actually needed, it still returns true. s, ok = Unreplace("something.pinniped.dev", "pinniped.dev") require.Equal(t, "something.pinniped.dev", s) require.True(t, ok) // When the unreplace was needed but did not work, return false. s, ok = Unreplace("something.pinniped.dev.something-else.tuna.io", "salmon.io") require.Equal(t, "", s) require.False(t, ok) } func TestValidate(t *testing.T) { tests := []struct { apiGroupSuffix string wantErrorPrefix string }{ { apiGroupSuffix: "happy.suffix.com", }, { apiGroupSuffix: "no-dots", wantErrorPrefix: "must contain '.'", }, { apiGroupSuffix: ".starts.with.dot", wantErrorPrefix: "a lowercase RFC 1123 subdomain must consist", }, { apiGroupSuffix: "ends.with.dot.", wantErrorPrefix: "a lowercase RFC 1123 subdomain must consist", }, { apiGroupSuffix: ".multiple-issues.because-this-string-is-longer-than-the-253-character-limit-of-a-dns-1123-identifier-" + chars(253), wantErrorPrefix: "[must be no more than 253 characters, a lowercase RFC 1123 subdomain must consist", }, } for _, test := range tests { test := test t.Run(test.apiGroupSuffix, func(t *testing.T) { err := Validate(test.apiGroupSuffix) if test.wantErrorPrefix != "" { require.Error(t, err) require.Truef( t, strings.HasPrefix(err.Error(), test.wantErrorPrefix), "%q does not start with %q", err.Error(), test.wantErrorPrefix) } else { require.NoError(t, err) } }) } } type withFunc func(obj kubeclient.Object) func with(obj kubeclient.Object, withFuncs ...withFunc) kubeclient.Object { obj = obj.DeepCopyObject().(kubeclient.Object) for _, withFunc := range withFuncs { withFunc(obj) } return obj } func gvk(gvk schema.GroupVersionKind) withFunc { return func(obj kubeclient.Object) { obj.GetObjectKind().SetGroupVersionKind(gvk) } } func authenticatorAPIGroup(apiGroup string) withFunc { return func(obj kubeclient.Object) { tokenCredentialRequest := obj.(*loginv1alpha1.TokenCredentialRequest) tokenCredentialRequest.Spec.Authenticator.APIGroup = &apiGroup } } //nolint:unparam // the apiGroupSuffix parameter might always be the same, but this is nice for test readability func replaceGV(t *testing.T, baseGV schema.GroupVersion, apiGroupSuffix string) schema.GroupVersion { t.Helper() groupName, ok := Replace(baseGV.Group, apiGroupSuffix) require.True(t, ok, "expected to be able to replace %q's suffix with %q", baseGV.Group, apiGroupSuffix) return schema.GroupVersion{Group: groupName, Version: baseGV.Version} } func chars(count int) string { return strings.Repeat("a", count) }