cert-manager/pkg/internal/api/mutation/registry_test.go
joshvanl 4dd6d19011 Adds review comment suggestions/cleanup
Signed-off-by: joshvanl <vleeuwenjoshua@gmail.com>
2021-03-11 19:12:02 +00:00

386 lines
11 KiB
Go

/*
Copyright 2020 The cert-manager Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package mutation_test
import (
"bytes"
"encoding/json"
"testing"
admissionv1 "k8s.io/api/admission/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"github.com/jetstack/cert-manager/pkg/apis/certmanager"
"github.com/jetstack/cert-manager/pkg/internal/api/mutation"
cminternal "github.com/jetstack/cert-manager/pkg/internal/apis/certmanager"
"github.com/jetstack/cert-manager/pkg/webhook"
"github.com/jetstack/cert-manager/test/unit/gen"
)
var (
// use the webhook's Scheme during test fixtures as it has all internal and
// external cert-manager kinds registered
scheme = webhook.Scheme
)
func TestMutate(t *testing.T) {
crGVK := &metav1.GroupVersionKind{
Group: certmanager.GroupName,
Version: "v1",
Kind: "CertificateRequest",
}
testCR := gen.CertificateRequest("test-cr",
gen.SetCertificateRequestTypeMeta(metav1.TypeMeta{
Kind: "CertificateRequest",
APIVersion: "cert-manager.io/v1",
}),
)
testCRBytes, err := json.Marshal(testCR)
if err != nil {
t.Fatal(err)
}
testNotRegistered := gen.CertificateRequest("test-cr",
gen.SetCertificateRequestTypeMeta(metav1.TypeMeta{
Kind: "NotRegistered",
APIVersion: "not-registered.io/v1",
}),
)
testNotRegisteredBytes, err := json.Marshal(testNotRegistered)
if err != nil {
t.Fatal(err)
}
type mutationsEntry struct {
obj runtime.Object
fn func(t *testing.T) mutation.MutateFunc
}
type mutationUpdatesEntry struct {
obj runtime.Object
fn func(t *testing.T) mutation.MutateUpdateFunc
}
tests := map[string]struct {
mutations []mutationsEntry
mutationUpdates []mutationUpdatesEntry
req *admissionv1.AdmissionRequest
expErr bool
expPatch []byte
}{
"exit early if operation is not UPDATE or CREATE": {
mutations: nil,
mutationUpdates: nil,
req: &admissionv1.AdmissionRequest{
RequestKind: crGVK.DeepCopy(),
Operation: admissionv1.Delete,
Object: runtime.RawExtension{
Raw: testCRBytes,
},
},
expErr: false,
expPatch: nil,
},
"if no functions registered, expect only default patch on CREATE": {
mutations: nil,
mutationUpdates: nil,
req: &admissionv1.AdmissionRequest{
RequestKind: crGVK.DeepCopy(),
Operation: admissionv1.Create,
Object: runtime.RawExtension{
Raw: testCRBytes,
},
},
expErr: false,
expPatch: []byte("[]"),
},
"if no functions registered, expect only default patch on UPDATE": {
mutations: nil,
mutationUpdates: nil,
req: &admissionv1.AdmissionRequest{
RequestKind: crGVK.DeepCopy(),
Operation: admissionv1.Update,
OldObject: runtime.RawExtension{
Raw: testCRBytes,
},
Object: runtime.RawExtension{
Raw: testCRBytes,
},
},
expErr: false,
expPatch: []byte("[]"),
},
"if kind presented for mutation hasn't been registered for CREATE, error": {
mutations: nil,
mutationUpdates: nil,
req: &admissionv1.AdmissionRequest{
RequestKind: crGVK.DeepCopy(),
Operation: admissionv1.Create,
Object: runtime.RawExtension{
Raw: testNotRegisteredBytes,
},
},
expErr: true,
expPatch: nil,
},
"if kind presented for mutation hasn't been registered for UPDATE, error": {
mutations: nil,
mutationUpdates: nil,
req: &admissionv1.AdmissionRequest{
RequestKind: crGVK.DeepCopy(),
Operation: admissionv1.Update,
OldObject: runtime.RawExtension{
Raw: testNotRegisteredBytes,
},
Object: runtime.RawExtension{
Raw: testNotRegisteredBytes,
},
},
expErr: true,
expPatch: nil,
},
"if update mutation function registered for different kind, ignore": {
mutations: []mutationsEntry{
{
obj: new(cminternal.Certificate),
fn: func(t *testing.T) mutation.MutateFunc {
return func(_ *admissionv1.AdmissionRequest, _ runtime.Object) {
t.Error("unexpected call")
}
},
},
},
mutationUpdates: nil,
req: &admissionv1.AdmissionRequest{
RequestKind: crGVK.DeepCopy(),
Operation: admissionv1.Create,
Object: runtime.RawExtension{
Raw: testCRBytes,
},
},
expErr: false,
expPatch: []byte("[]"),
},
"if create mutation function registered for different kind, ignore": {
mutations: nil,
mutationUpdates: []mutationUpdatesEntry{
{
obj: new(cminternal.Certificate),
fn: func(t *testing.T) mutation.MutateUpdateFunc {
return func(_ *admissionv1.AdmissionRequest, _, _ runtime.Object) {
t.Error("unexpected call")
}
},
},
},
req: &admissionv1.AdmissionRequest{
RequestKind: crGVK.DeepCopy(),
Operation: admissionv1.Create,
Object: runtime.RawExtension{
Raw: testCRBytes,
},
},
expErr: false,
expPatch: []byte("[]"),
},
"if create mutation function registered for kind, run mutation": {
mutations: []mutationsEntry{
{
obj: new(cminternal.CertificateRequest),
fn: func(t *testing.T) mutation.MutateFunc {
return func(_ *admissionv1.AdmissionRequest, obj runtime.Object) {
cr := obj.(*cminternal.CertificateRequest)
cr.Spec.Request = []byte("mutation called")
}
},
},
},
mutationUpdates: []mutationUpdatesEntry{
{
obj: new(cminternal.Certificate),
fn: func(t *testing.T) mutation.MutateUpdateFunc {
return func(_ *admissionv1.AdmissionRequest, _, _ runtime.Object) {
t.Error("unexpected call")
}
},
},
},
req: &admissionv1.AdmissionRequest{
RequestKind: crGVK.DeepCopy(),
Operation: admissionv1.Create,
Object: runtime.RawExtension{
Raw: testCRBytes,
},
},
expErr: false,
expPatch: []byte(`[{"op":"replace","path":"/spec/request","value":"bXV0YXRpb24gY2FsbGVk"}]`),
},
"if update mutation function registered for kind, run mutation": {
mutations: []mutationsEntry{
{
obj: new(cminternal.Certificate),
fn: func(t *testing.T) mutation.MutateFunc {
return func(_ *admissionv1.AdmissionRequest, _ runtime.Object) {
t.Error("unexpected call")
}
},
},
},
mutationUpdates: []mutationUpdatesEntry{
{
obj: new(cminternal.CertificateRequest),
fn: func(t *testing.T) mutation.MutateUpdateFunc {
return func(_ *admissionv1.AdmissionRequest, _, obj runtime.Object) {
cr := obj.(*cminternal.CertificateRequest)
cr.Spec.Request = []byte("mutation called")
}
},
},
},
req: &admissionv1.AdmissionRequest{
RequestKind: crGVK.DeepCopy(),
Operation: admissionv1.Update,
Object: runtime.RawExtension{
Raw: testCRBytes,
},
OldObject: runtime.RawExtension{
Raw: testCRBytes,
},
},
expErr: false,
expPatch: []byte(`[{"op":"replace","path":"/spec/request","value":"bXV0YXRpb24gY2FsbGVk"}]`),
},
"if multiple create mutation functions registered for kind, run mutation": {
mutations: []mutationsEntry{
{
obj: new(cminternal.CertificateRequest),
fn: func(t *testing.T) mutation.MutateFunc {
return func(_ *admissionv1.AdmissionRequest, obj runtime.Object) {
cr := obj.(*cminternal.CertificateRequest)
cr.Spec.Request = []byte("mutation called")
}
},
},
{
obj: new(cminternal.CertificateRequest),
fn: func(t *testing.T) mutation.MutateFunc {
return func(_ *admissionv1.AdmissionRequest, obj runtime.Object) {
cr := obj.(*cminternal.CertificateRequest)
if cr.Annotations == nil {
cr.Annotations = make(map[string]string)
}
cr.Annotations["second-mutation"] = "called"
}
},
},
},
mutationUpdates: []mutationUpdatesEntry{
{
obj: new(cminternal.Certificate),
fn: func(t *testing.T) mutation.MutateUpdateFunc {
return func(_ *admissionv1.AdmissionRequest, _, _ runtime.Object) {
t.Error("unexpected call")
}
},
},
},
req: &admissionv1.AdmissionRequest{
RequestKind: crGVK.DeepCopy(),
Operation: admissionv1.Create,
Object: runtime.RawExtension{
Raw: testCRBytes,
},
},
expErr: false,
expPatch: []byte(`[{"op":"add","path":"/metadata/annotations","value":{"second-mutation":"called"}},{"op":"replace","path":"/spec/request","value":"bXV0YXRpb24gY2FsbGVk"}]`),
},
"if multiple update mutation function registered for kind, run mutation": {
mutations: []mutationsEntry{
{
obj: new(cminternal.Certificate),
fn: func(t *testing.T) mutation.MutateFunc {
return func(_ *admissionv1.AdmissionRequest, _ runtime.Object) {
t.Error("unexpected call")
}
},
},
},
mutationUpdates: []mutationUpdatesEntry{
{
obj: new(cminternal.CertificateRequest),
fn: func(t *testing.T) mutation.MutateUpdateFunc {
return func(_ *admissionv1.AdmissionRequest, _, obj runtime.Object) {
cr := obj.(*cminternal.CertificateRequest)
cr.Spec.Request = []byte("mutation called")
}
},
},
{
obj: new(cminternal.CertificateRequest),
fn: func(t *testing.T) mutation.MutateUpdateFunc {
return func(_ *admissionv1.AdmissionRequest, _, obj runtime.Object) {
cr := obj.(*cminternal.CertificateRequest)
if cr.Annotations == nil {
cr.Annotations = make(map[string]string)
}
cr.Annotations["second-mutation"] = "called"
}
},
},
},
req: &admissionv1.AdmissionRequest{
RequestKind: crGVK.DeepCopy(),
Operation: admissionv1.Update,
Object: runtime.RawExtension{
Raw: testCRBytes,
},
OldObject: runtime.RawExtension{
Raw: testCRBytes,
},
},
expErr: false,
expPatch: []byte(`[{"op":"add","path":"/metadata/annotations","value":{"second-mutation":"called"}},{"op":"replace","path":"/spec/request","value":"bXV0YXRpb24gY2FsbGVk"}]`),
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
reg := mutation.NewRegistry(scheme)
// Add mutation functions to registry
for _, m := range test.mutations {
reg.AddMutateFunc(m.obj, m.fn(t))
}
for _, m := range test.mutationUpdates {
reg.AddMutateUpdateFunc(m.obj, m.fn(t))
}
patch, err := reg.Mutate(test.req)
if test.expErr != (err != nil) {
t.Errorf("unexpected error, exp=%t got=%v",
test.expErr, err)
}
if !bytes.Equal(test.expPatch, patch) {
t.Errorf("unexpected patch, exp=%s got=%s",
test.expPatch, patch)
}
})
}
}