0fc1f17866
This is a partial revert of 288d9c999e
. For some reason it didn't occur to me
that we could do it this way earlier. Whoops.
This also contains a middleware update: mutation funcs can return an error now
and short-circuit the rest of the request/response flow. The idea here is that
if someone is configuring their kubeclient to use middleware, they are agreeing
to a narrow-er client contract by doing so (e.g., their TokenCredentialRequest's
must have an Spec.Authenticator.APIGroup set).
I also updated some internal/groupsuffix tests to be more realistic.
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
261 lines
7.1 KiB
Go
261 lines
7.1 KiB
Go
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package ownerref
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
corev1 "k8s.io/api/core/v1"
|
|
rbacv1 "k8s.io/api/rbac/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
"go.pinniped.dev/internal/kubeclient"
|
|
"go.pinniped.dev/internal/testutil"
|
|
)
|
|
|
|
func TestOwnerReferenceMiddleware(t *testing.T) {
|
|
ref1 := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "earth", Namespace: "some-namespace", UID: "0x11"}}
|
|
ref2 := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "mars", Namespace: "some-namespace", UID: "0x12"}}
|
|
ref3 := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "sun", Namespace: "some-namespace", UID: "0x13"}}
|
|
clusterRef := &rbacv1.ClusterRole{ObjectMeta: metav1.ObjectMeta{Name: "bananas", UID: "0x13"}}
|
|
|
|
secret := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "twizzlers", Namespace: "some-namespace"}}
|
|
secretOtherNamespace := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "twizzlers", Namespace: "some-other-namespace"}}
|
|
configMap := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "pandas", Namespace: "some-namespace"}}
|
|
clusterRole := &rbacv1.ClusterRole{ObjectMeta: metav1.ObjectMeta{Name: "bananas"}}
|
|
|
|
secretWithOwner := withOwnerRef(t, &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "twizzlers", Namespace: "some-namespace"}}, ref3)
|
|
configMapWithOwner := withOwnerRef(t, &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "pandas", Namespace: "some-namespace"}}, ref3)
|
|
|
|
namespaceRef := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "solar-system", UID: "0x42"}}
|
|
secretInSameNamespaceAsNamespaceRef := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "venus", Namespace: "solar-system", UID: "0x11"}}
|
|
|
|
type args struct {
|
|
ref kubeclient.Object
|
|
httpMethod string
|
|
subresource string
|
|
obj kubeclient.Object
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
wantHandles, wantMutates bool
|
|
wantObj kubeclient.Object
|
|
}{
|
|
{
|
|
name: "on update",
|
|
args: args{
|
|
ref: ref1,
|
|
httpMethod: http.MethodPut,
|
|
obj: secret.DeepCopy(),
|
|
},
|
|
wantHandles: false,
|
|
wantMutates: false,
|
|
},
|
|
{
|
|
name: "on get",
|
|
args: args{
|
|
ref: ref1,
|
|
httpMethod: http.MethodGet,
|
|
obj: secret.DeepCopy(),
|
|
},
|
|
wantHandles: false,
|
|
wantMutates: false,
|
|
},
|
|
{
|
|
name: "on delete",
|
|
args: args{
|
|
ref: ref1,
|
|
httpMethod: http.MethodDelete,
|
|
obj: secret.DeepCopy(),
|
|
},
|
|
wantHandles: false,
|
|
wantMutates: false,
|
|
},
|
|
{
|
|
name: "on patch",
|
|
args: args{
|
|
ref: ref1,
|
|
httpMethod: http.MethodPatch,
|
|
obj: secret.DeepCopy(),
|
|
},
|
|
wantHandles: false,
|
|
wantMutates: false,
|
|
},
|
|
{
|
|
name: "on create",
|
|
args: args{
|
|
ref: ref1,
|
|
httpMethod: http.MethodPost,
|
|
obj: secret.DeepCopy(),
|
|
},
|
|
wantHandles: true,
|
|
wantMutates: true,
|
|
wantObj: withOwnerRef(t, secret, ref1),
|
|
},
|
|
{
|
|
name: "on create when the ref object is a namespace",
|
|
args: args{
|
|
ref: namespaceRef,
|
|
httpMethod: http.MethodPost,
|
|
obj: secretInSameNamespaceAsNamespaceRef.DeepCopy(),
|
|
},
|
|
wantHandles: true,
|
|
wantMutates: true,
|
|
wantObj: withOwnerRef(t, secretInSameNamespaceAsNamespaceRef, namespaceRef),
|
|
},
|
|
{
|
|
name: "on create config map",
|
|
args: args{
|
|
ref: ref2,
|
|
httpMethod: http.MethodPost,
|
|
obj: configMap.DeepCopy(),
|
|
},
|
|
wantHandles: true,
|
|
wantMutates: true,
|
|
wantObj: withOwnerRef(t, configMap, ref2),
|
|
},
|
|
{
|
|
name: "on create with cluster-scoped owner",
|
|
args: args{
|
|
ref: clusterRef,
|
|
httpMethod: http.MethodPost,
|
|
obj: secret.DeepCopy(),
|
|
},
|
|
wantHandles: true,
|
|
wantMutates: true,
|
|
wantObj: withOwnerRef(t, secret, clusterRef),
|
|
},
|
|
{
|
|
name: "on create of cluster-scoped resource with namespace-scoped owner",
|
|
args: args{
|
|
ref: ref1,
|
|
httpMethod: http.MethodPost,
|
|
obj: clusterRole.DeepCopy(),
|
|
},
|
|
wantHandles: false,
|
|
wantMutates: false,
|
|
},
|
|
{
|
|
name: "on create of cluster-scoped resource with cluster-scoped owner",
|
|
args: args{
|
|
ref: clusterRef,
|
|
httpMethod: http.MethodPost,
|
|
obj: clusterRole.DeepCopy(),
|
|
},
|
|
wantHandles: true,
|
|
wantMutates: true,
|
|
wantObj: withOwnerRef(t, clusterRole, clusterRef),
|
|
},
|
|
{
|
|
name: "on create with pre-existing ref",
|
|
args: args{
|
|
ref: ref1,
|
|
httpMethod: http.MethodPost,
|
|
obj: secretWithOwner.DeepCopyObject().(kubeclient.Object),
|
|
},
|
|
wantHandles: true,
|
|
wantMutates: false,
|
|
},
|
|
{
|
|
name: "on create with pre-existing ref config map",
|
|
args: args{
|
|
ref: ref2,
|
|
httpMethod: http.MethodPost,
|
|
obj: configMapWithOwner.DeepCopyObject().(kubeclient.Object),
|
|
},
|
|
wantHandles: true,
|
|
wantMutates: false,
|
|
},
|
|
{
|
|
name: "on create of subresource",
|
|
args: args{
|
|
ref: ref2,
|
|
httpMethod: http.MethodPost,
|
|
subresource: "some-subresource",
|
|
obj: configMapWithOwner.DeepCopyObject().(kubeclient.Object),
|
|
},
|
|
wantHandles: false,
|
|
wantMutates: false,
|
|
},
|
|
{
|
|
name: "on create with namespace mismatch",
|
|
args: args{
|
|
ref: ref2,
|
|
httpMethod: http.MethodPost,
|
|
obj: secretOtherNamespace.DeepCopyObject().(kubeclient.Object),
|
|
},
|
|
wantHandles: false,
|
|
wantMutates: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
middleware := New(tt.args.ref)
|
|
|
|
rt := (&testutil.RoundTrip{}).
|
|
WithVerb(verb(t, tt.args.httpMethod)).
|
|
WithNamespace(tt.args.obj.GetNamespace()).
|
|
WithSubresource(tt.args.subresource)
|
|
middleware.Handle(context.Background(), rt)
|
|
require.Empty(t, rt.MutateResponses, 1)
|
|
if !tt.wantHandles {
|
|
require.Empty(t, rt.MutateRequests)
|
|
return
|
|
}
|
|
require.Len(t, rt.MutateRequests, 1)
|
|
|
|
orig := tt.args.obj.DeepCopyObject().(kubeclient.Object)
|
|
for _, mutateRequest := range rt.MutateRequests {
|
|
mutateRequest := mutateRequest
|
|
require.NoError(t, mutateRequest(tt.args.obj))
|
|
}
|
|
if !tt.wantMutates {
|
|
require.Equal(t, orig, tt.args.obj)
|
|
} else {
|
|
require.NotEqual(t, orig, tt.args.obj)
|
|
require.Equal(t, tt.wantObj, tt.args.obj)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func withOwnerRef(t *testing.T, obj kubeclient.Object, ref kubeclient.Object) kubeclient.Object {
|
|
t.Helper()
|
|
|
|
ownerRef := metav1.OwnerReference{
|
|
Name: ref.GetName(),
|
|
UID: ref.GetUID(),
|
|
}
|
|
|
|
obj = obj.DeepCopyObject().(kubeclient.Object)
|
|
require.Len(t, obj.GetOwnerReferences(), 0)
|
|
obj.SetOwnerReferences([]metav1.OwnerReference{ownerRef})
|
|
|
|
return obj
|
|
}
|
|
|
|
func verb(t *testing.T, v string) kubeclient.Verb {
|
|
t.Helper()
|
|
switch v {
|
|
case http.MethodGet:
|
|
return kubeclient.VerbGet
|
|
case http.MethodPut:
|
|
return kubeclient.VerbUpdate
|
|
case http.MethodPost:
|
|
return kubeclient.VerbCreate
|
|
case http.MethodDelete:
|
|
return kubeclient.VerbDelete
|
|
case http.MethodPatch:
|
|
return kubeclient.VerbPatch
|
|
default:
|
|
require.FailNowf(t, "unknown verb", "unknown verb: %q", v)
|
|
return kubeclient.VerbGet // shouldn't get here
|
|
}
|
|
}
|