ContainerImage.Pinniped/internal/ownerref/ownerref_test.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
}
}