remove webhook conversion logic

Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com>
This commit is contained in:
Tim Ramlot 2024-02-02 11:19:08 +01:00
parent 6a9c402c61
commit 899d55ae57
No known key found for this signature in database
GPG Key ID: 47428728E0C2878D
11 changed files with 1 additions and 732 deletions

View File

@ -1,64 +0,0 @@
/*
Copyright 2021 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 apideprecation
import (
"context"
"fmt"
admissionv1 "k8s.io/api/admission/v1"
"k8s.io/apimachinery/pkg/runtime"
"github.com/cert-manager/cert-manager/pkg/apis/acme"
"github.com/cert-manager/cert-manager/pkg/apis/certmanager"
"github.com/cert-manager/cert-manager/pkg/webhook/admission"
)
const PluginName = "APIDeprecation"
type apiDeprecation struct{}
// Register registers a plugin
func Register(plugins *admission.Plugins) {
plugins.Register(PluginName, func() (admission.Interface, error) {
return NewPlugin(), nil
})
}
var _ admission.ValidationInterface = &apiDeprecation{}
func (p apiDeprecation) Handles(_ admissionv1.Operation) bool {
return true
}
func (p apiDeprecation) Validate(ctx context.Context, request admissionv1.AdmissionRequest, oldObj, obj runtime.Object) (warnings []string, err error) {
// Only generate warning messages for cert-manager.io and acme.cert-manager.io APIs
if request.RequestResource.Group != certmanager.GroupName &&
request.RequestResource.Group != acme.GroupName {
return nil, nil
}
// All non-v1 API resources in cert-manager.io and acme.cert-manager.io are now deprecated
if request.RequestResource.Version == "v1" {
return nil, nil
}
return []string{fmt.Sprintf("%s.%s/%s is deprecated in v1.4+, unavailable in v1.6+; use %s.%s/v1", request.RequestResource.Resource, request.RequestResource.Group, request.RequestResource.Version, request.RequestResource.Resource, request.RequestResource.Group)}, nil
}
func NewPlugin() admission.Interface {
return new(apiDeprecation)
}

View File

@ -1,75 +0,0 @@
/*
Copyright 2021 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 apideprecation
import (
"context"
"reflect"
"testing"
admissionv1 "k8s.io/api/admission/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestAPIDeprecation(t *testing.T) {
tests := map[string]struct {
req *admissionv1.AdmissionRequest
warnings []string
}{
"should print warnings for all non-v1 cert-manager.io types": {
req: &admissionv1.AdmissionRequest{
RequestResource: &metav1.GroupVersionResource{
Group: "cert-manager.io",
Version: "something-not-v1",
Resource: "somethings",
},
},
warnings: []string{"somethings.cert-manager.io/something-not-v1 is deprecated in v1.4+, unavailable in v1.6+; use somethings.cert-manager.io/v1"},
},
"should print warnings for all non-v1 acme.cert-manager.io types": {
req: &admissionv1.AdmissionRequest{
RequestResource: &metav1.GroupVersionResource{
Group: "acme.cert-manager.io",
Version: "something-not-v1",
Resource: "somethings",
},
},
warnings: []string{"somethings.acme.cert-manager.io/something-not-v1 is deprecated in v1.4+, unavailable in v1.6+; use somethings.acme.cert-manager.io/v1"},
},
"should not print warnings for non-v1 types in other groups": {
req: &admissionv1.AdmissionRequest{
RequestResource: &metav1.GroupVersionResource{
Group: "some-other-group-name",
Version: "something-not-v1",
Resource: "somethings",
},
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
p := NewPlugin().(*apiDeprecation)
warnings, err := p.Validate(context.Background(), *test.req, nil, nil)
if err != nil {
t.Errorf("unexpected error")
}
if !reflect.DeepEqual(warnings, test.warnings) {
t.Errorf("unexpected warnings, exp=%q, got=%q", test.warnings, warnings)
}
})
}
}

View File

@ -17,7 +17,6 @@ limitations under the License.
package plugin
import (
"github.com/cert-manager/cert-manager/internal/plugin/admission/apideprecation"
certificaterequestapproval "github.com/cert-manager/cert-manager/internal/plugin/admission/certificaterequest/approval"
certificaterequestidentity "github.com/cert-manager/cert-manager/internal/plugin/admission/certificaterequest/identity"
"github.com/cert-manager/cert-manager/internal/plugin/admission/resourcevalidation"
@ -26,14 +25,12 @@ import (
)
var AllOrderedPlugins = []string{
apideprecation.PluginName,
resourcevalidation.PluginName,
certificaterequestidentity.PluginName,
certificaterequestapproval.PluginName,
}
func RegisterAllPlugins(plugins *admission.Plugins) {
apideprecation.Register(plugins)
certificaterequestidentity.Register(plugins)
certificaterequestapproval.Register(plugins)
resourcevalidation.Register(plugins)
@ -41,7 +38,6 @@ func RegisterAllPlugins(plugins *admission.Plugins) {
func DefaultOnAdmissionPlugins() sets.Set[string] {
return sets.New[string](
apideprecation.PluginName,
resourcevalidation.PluginName,
certificaterequestidentity.PluginName,
certificaterequestapproval.PluginName,

View File

@ -38,20 +38,9 @@ import (
logf "github.com/cert-manager/cert-manager/pkg/logs"
"github.com/cert-manager/cert-manager/pkg/webhook/admission"
"github.com/cert-manager/cert-manager/pkg/webhook/admission/initializer"
"github.com/cert-manager/cert-manager/pkg/webhook/handlers"
"github.com/cert-manager/cert-manager/pkg/webhook/server"
)
var conversionHook handlers.ConversionHook = handlers.NewSchemeBackedConverter(logf.Log, Scheme)
// WithConversionHandler allows you to override the handler for the `/convert`
// endpoint in tests.
func WithConversionHandler(handler handlers.ConversionHook) func(*server.Server) {
return func(s *server.Server) {
s.ConversionWebhook = handler
}
}
// NewCertManagerWebhookServer creates a new webhook server configured with all cert-manager
// resource types, validation, defaulting and conversion functions.
func NewCertManagerWebhookServer(log logr.Logger, opts config.WebhookConfiguration, optionFunctions ...func(*server.Server)) (*server.Server, error) {
@ -81,7 +70,6 @@ func NewCertManagerWebhookServer(log logr.Logger, opts config.WebhookConfigurati
MinTLSVersion: opts.TLSConfig.MinTLSVersion,
ValidationWebhook: admissionHandler,
MutationWebhook: admissionHandler,
ConversionWebhook: conversionHook,
}
for _, fn := range optionFunctions {
fn(s)

View File

@ -1,103 +0,0 @@
/*
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 handlers
import (
"bytes"
"fmt"
"net/http"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"github.com/go-logr/logr"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
apijson "k8s.io/apimachinery/pkg/runtime/serializer/json"
"k8s.io/apimachinery/pkg/runtime/serializer/versioning"
logf "github.com/cert-manager/cert-manager/pkg/logs"
)
type SchemeBackedConverter struct {
log logr.Logger
scheme *runtime.Scheme
serializer *apijson.Serializer
}
var _ ConversionHook = &SchemeBackedConverter{}
func NewSchemeBackedConverter(log logr.Logger, scheme *runtime.Scheme) *SchemeBackedConverter {
serializer := apijson.NewSerializerWithOptions(apijson.DefaultMetaFactory, scheme, scheme, apijson.SerializerOptions{})
return &SchemeBackedConverter{
log: log,
scheme: scheme,
serializer: serializer,
}
}
func (c *SchemeBackedConverter) convertObjects(desiredAPIVersion string, objects []runtime.RawExtension) ([]runtime.RawExtension, error) {
desiredGV, err := schema.ParseGroupVersion(desiredAPIVersion)
if err != nil {
return nil, fmt.Errorf("Failed to parse desired apiVersion: %v", err)
}
c.log.V(logf.DebugLevel).Info("Parsed desired groupVersion", "desired_group_version", desiredGV)
groupVersioner := schema.GroupVersions([]schema.GroupVersion{desiredGV})
codec := versioning.NewCodec(
c.serializer,
c.serializer,
runtime.UnsafeObjectConvertor(c.scheme),
c.scheme,
c.scheme,
nil,
groupVersioner,
runtime.InternalGroupVersioner, c.scheme.Name(),
)
convertedObjects := make([]runtime.RawExtension, len(objects))
for i, raw := range objects {
decodedObject, currentGVK, err := codec.Decode(raw.Raw, nil, nil)
if err != nil {
return nil, fmt.Errorf("Failed to decode into apiVersion: %v", err)
}
c.log.V(logf.DebugLevel).Info("Decoded resource", "decoded_group_version_kind", currentGVK)
buf := bytes.Buffer{}
if err := codec.Encode(decodedObject, &buf); err != nil {
return nil, fmt.Errorf("Failed to convert to desired apiVersion: %v", err)
}
convertedObjects[i] = runtime.RawExtension{Raw: buf.Bytes()}
}
return convertedObjects, nil
}
func (c *SchemeBackedConverter) Convert(conversionSpec *apiextensionsv1.ConversionRequest) *apiextensionsv1.ConversionResponse {
result := metav1.Status{Status: metav1.StatusSuccess}
convertedObjects, err := c.convertObjects(conversionSpec.DesiredAPIVersion, conversionSpec.Objects)
if err != nil {
result.Status = metav1.StatusFailure
result.Code = http.StatusBadRequest
result.Reason = metav1.StatusReasonBadRequest
result.Message = err.Error()
}
return &apiextensionsv1.ConversionResponse{
UID: conversionSpec.UID,
ConvertedObjects: convertedObjects,
Result: result,
}
}

View File

@ -1,227 +0,0 @@
/*
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 handlers
import (
"reflect"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/klog/v2/ktesting"
"k8s.io/utils/diff"
"github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup"
"github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup/install"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
)
func TestConvertTestType(t *testing.T) {
scheme := runtime.NewScheme()
install.Install(scheme)
log := ktesting.NewLogger(t, ktesting.NewConfig())
c := NewSchemeBackedConverter(log, scheme)
type conversionTestT struct {
inputRequest apiextensionsv1.ConversionRequest
expectedResponse apiextensionsv1.ConversionResponse
}
tests := map[string]conversionTestT{
"correctly handles requests with multiple input items": {
inputRequest: apiextensionsv1.ConversionRequest{
DesiredAPIVersion: testgroup.GroupName + "/v1",
Objects: []runtime.RawExtension{
{
Raw: []byte(`
{
"apiVersion": "testgroup.testing.cert-manager.io/v1",
"kind": "TestType",
"metadata": {
"name": "testing",
"namespace": "abc",
"creationTimestamp": null
}
}
`),
},
{
Raw: []byte(`
{
"apiVersion": "testgroup.testing.cert-manager.io/v1",
"kind": "TestType",
"metadata": {
"name": "testing",
"namespace": "abc",
"creationTimestamp": null
}
}
`),
},
},
},
expectedResponse: apiextensionsv1.ConversionResponse{
Result: metav1.Status{
Status: metav1.StatusSuccess,
},
ConvertedObjects: []runtime.RawExtension{
{
Raw: []byte(`{"kind":"TestType","apiVersion":"testgroup.testing.cert-manager.io/v1","metadata":{"name":"testing","namespace":"abc","creationTimestamp":null},"testField":"","testFieldImmutable":""}
`),
},
{
Raw: []byte(`{"kind":"TestType","apiVersion":"testgroup.testing.cert-manager.io/v1","metadata":{"name":"testing","namespace":"abc","creationTimestamp":null},"testField":"","testFieldImmutable":""}
`),
},
},
},
},
"succeeds when handling requests with no input items": {
inputRequest: apiextensionsv1.ConversionRequest{
DesiredAPIVersion: testgroup.GroupName + "/v1",
Objects: []runtime.RawExtension{},
},
expectedResponse: apiextensionsv1.ConversionResponse{
Result: metav1.Status{
Status: metav1.StatusSuccess,
},
ConvertedObjects: []runtime.RawExtension{},
},
},
"copies across request UID to the response field": {
inputRequest: apiextensionsv1.ConversionRequest{
DesiredAPIVersion: testgroup.GroupName + "/v1",
Objects: []runtime.RawExtension{},
UID: types.UID("abc"),
},
expectedResponse: apiextensionsv1.ConversionResponse{
Result: metav1.Status{
Status: metav1.StatusSuccess,
},
UID: types.UID("abc"),
ConvertedObjects: []runtime.RawExtension{},
},
},
"converts from v1 to v1 without applying defaults": {
inputRequest: apiextensionsv1.ConversionRequest{
DesiredAPIVersion: testgroup.GroupName + "/v1",
Objects: []runtime.RawExtension{
{
Raw: []byte(`
{
"apiVersion": "testgroup.testing.cert-manager.io/v1",
"kind": "TestType",
"metadata": {
"name": "testing",
"namespace": "abc",
"creationTimestamp": null
}
}
`),
},
},
},
expectedResponse: apiextensionsv1.ConversionResponse{
Result: metav1.Status{
Status: metav1.StatusSuccess,
},
ConvertedObjects: []runtime.RawExtension{
{
Raw: []byte(`{"kind":"TestType","apiVersion":"testgroup.testing.cert-manager.io/v1","metadata":{"name":"testing","namespace":"abc","creationTimestamp":null},"testField":"","testFieldImmutable":""}
`),
},
},
},
},
"converts from v1 to v2 without applying defaults": {
inputRequest: apiextensionsv1.ConversionRequest{
DesiredAPIVersion: testgroup.GroupName + "/v2",
Objects: []runtime.RawExtension{
{
Raw: []byte(`
{
"apiVersion": "testgroup.testing.cert-manager.io/v1",
"kind": "TestType",
"metadata": {
"name": "testing",
"namespace": "abc",
"creationTimestamp": null
}
}
`),
},
},
},
expectedResponse: apiextensionsv1.ConversionResponse{
Result: metav1.Status{
Status: metav1.StatusSuccess,
},
ConvertedObjects: []runtime.RawExtension{
{
Raw: []byte(`{"kind":"TestType","apiVersion":"testgroup.testing.cert-manager.io/v2","metadata":{"name":"testing","namespace":"abc","creationTimestamp":null},"testField":"","testFieldImmutable":""}
`),
},
},
},
},
"converts from v1 to v2": {
inputRequest: apiextensionsv1.ConversionRequest{
DesiredAPIVersion: testgroup.GroupName + "/v2",
Objects: []runtime.RawExtension{
{
Raw: []byte(`
{
"apiVersion": "testgroup.testing.cert-manager.io/v1",
"kind": "TestType",
"metadata": {
"name": "testing",
"namespace": "abc",
"creationTimestamp": null
},
"testField": "atest",
"testFieldPtr": "something"
}
`),
},
},
},
expectedResponse: apiextensionsv1.ConversionResponse{
Result: metav1.Status{
Status: metav1.StatusSuccess,
},
ConvertedObjects: []runtime.RawExtension{
{
Raw: []byte(`{"kind":"TestType","apiVersion":"testgroup.testing.cert-manager.io/v2","metadata":{"name":"testing","namespace":"abc","creationTimestamp":null},"testField":"atest","testFieldPtrAlt":"something","testFieldImmutable":""}
`),
},
},
},
},
}
for n, test := range tests {
test := test // G601: Remove after Go 1.22. https://go.dev/wiki/LoopvarExperiment
t.Run(n, func(t *testing.T) {
resp := c.Convert(&test.inputRequest)
if !reflect.DeepEqual(&test.expectedResponse, resp) {
t.Errorf("Response was not as expected: %v", diff.ObjectGoPrintSideBySide(&test.expectedResponse, resp))
}
})
}
}

View File

@ -20,7 +20,6 @@ import (
"context"
admissionv1 "k8s.io/api/admission/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
)
type ValidatingAdmissionHook interface {
@ -34,8 +33,3 @@ type MutatingAdmissionHook interface {
// use the Patch field to mutate the object from the passed AdmissionRequest.
Mutate(ctx context.Context, admissionSpec *admissionv1.AdmissionRequest) *admissionv1.AdmissionResponse
}
type ConversionHook interface {
// Convert is called to convert a resource in one version into a different version.
Convert(conversionSpec *apiextensionsv1.ConversionRequest) *apiextensionsv1.ConversionResponse
}

View File

@ -29,7 +29,6 @@ import (
"golang.org/x/sync/errgroup"
admissionv1 "k8s.io/api/admission/v1"
apiextensionsinstall "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
@ -112,7 +111,6 @@ type Server struct {
ValidationWebhook handlers.ValidatingAdmissionHook
MutationWebhook handlers.MutatingAdmissionHook
ConversionWebhook handlers.ConversionHook
log logr.Logger
@ -229,7 +227,6 @@ func (s *Server) Run(ctx context.Context) error {
serverMux := http.NewServeMux()
serverMux.HandleFunc("/validate", s.handle(s.validate))
serverMux.HandleFunc("/mutate", s.handle(s.mutate))
serverMux.HandleFunc("/convert", s.handle(s.convert))
server := &http.Server{
Handler: serverMux,
ReadHeaderTimeout: defaultReadHeaderTimeout, // Mitigation for G112: Potential slowloris attack
@ -307,20 +304,6 @@ func (s *Server) logAdmissionReview(review *admissionv1.AdmissionReview, prefix
}
}
func (s *Server) convert(_ context.Context, obj runtime.Object) (runtime.Object, error) {
switch review := obj.(type) {
case *apiextensionsv1.ConversionReview:
if review.Request == nil {
return nil, errors.New("review.request was nil")
}
review.Response = s.ConversionWebhook.Convert(review.Request)
s.log.V(logf.DebugLevel).Info("request received by converting webhook", "kind", review.Kind, "request uid", review.Request.UID, "response uid", review.Response.UID)
return review, nil
default:
return nil, fmt.Errorf("unsupported conversion review type: %T", review)
}
}
func (s *Server) handle(inner handleFunc) func(w http.ResponseWriter, req *http.Request) {
return func(w http.ResponseWriter, req *http.Request) {
defer runtimeutil.HandleCrash(func(_ interface{}) {

View File

@ -26,76 +26,14 @@ import (
admissionv1 "k8s.io/api/admission/v1"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/klog/v2"
logf "github.com/cert-manager/cert-manager/pkg/logs"
"github.com/cert-manager/cert-manager/pkg/webhook/handlers"
"k8s.io/klog/v2/ktesting"
)
func TestConvert(t *testing.T) {
type testCase struct {
name string
in runtime.Object
err string
log string
}
tests := []testCase{
{
name: "unsupported conversion review type",
in: &apiextensionsv1.CustomResourceDefinition{},
err: "unsupported conversion review type: *v1.CustomResourceDefinition",
},
{
name: "unsupported conversion review version",
in: &apiextensionsv1beta1.ConversionReview{
Request: &apiextensionsv1beta1.ConversionRequest{},
},
err: "unsupported conversion review type: *v1beta1.ConversionReview",
},
{
name: "v1 conversion review",
in: &apiextensionsv1.ConversionReview{
Request: &apiextensionsv1.ConversionRequest{},
},
log: "request received by converting webhook",
},
{
name: "v1 conversion review with nil Request",
in: &apiextensionsv1.ConversionReview{},
err: "review.request was nil",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
var bufWriter = bytes.NewBuffer(nil)
klog.SetOutput(bufWriter)
klog.LogToStderr(false)
log := ktesting.NewLogger(t, ktesting.NewConfig())
s := &Server{
ConversionWebhook: handlers.NewSchemeBackedConverter(log, defaultScheme),
log: log,
}
out, err := s.convert(context.TODO(), tc.in)
if tc.err != "" {
assert.EqualError(t, err, tc.err)
assert.Nil(t, out)
return
}
require.NoError(t, err)
assert.NotNil(t, out)
if klog.V(logf.DebugLevel).Enabled() {
assert.Contains(t, bufWriter.String(), tc.log)
}
})
}
}
type validation struct {
responseUID types.UID
responseAllowed bool

View File

@ -1,129 +0,0 @@
/*
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 conversion
import (
"context"
"testing"
"time"
logtesting "github.com/go-logr/logr/testing"
"k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/utils/diff"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/cert-manager/cert-manager/integration-tests/framework"
"github.com/cert-manager/cert-manager/pkg/webhook/handlers"
testapi "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup/install"
testv1 "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup/v1"
testv2 "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup/v2"
)
func TestConversion(t *testing.T) {
tests := map[string]struct {
input client.Object
targetGVK schema.GroupVersionKind
output client.Object
}{
"should convert from v1 to v2": {
input: &testv1.TestType{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
},
TestFieldPtr: ptr.To("test1"),
},
targetGVK: testv2.SchemeGroupVersion.WithKind("TestType"),
output: &testv2.TestType{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
},
TestFieldPtrAlt: ptr.To("test1"),
},
},
"should convert from v2 to v1": {
input: &testv2.TestType{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
},
TestFieldPtrAlt: ptr.To("test1"),
},
targetGVK: testv1.SchemeGroupVersion.WithKind("TestType"),
output: &testv1.TestType{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
},
TestFieldPtr: ptr.To("test1"),
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
log := logtesting.NewTestLogger(t)
scheme := runtime.NewScheme()
testapi.Install(scheme)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*40)
defer cancel()
config, stop := framework.RunControlPlane(
t, ctx,
framework.WithCRDDirectory("../../../pkg/webhook/handlers/testdata/apis/testgroup/crds"),
framework.WithWebhookConversionHandler(handlers.NewSchemeBackedConverter(log, scheme)),
)
defer stop()
cl, err := client.New(config, client.Options{Scheme: scheme})
if err != nil {
t.Fatal(err)
}
if err := cl.Create(ctx, test.input); err != nil {
t.Fatal(err)
}
meta := test.input.(metav1.ObjectMetaAccessor)
convertedObj, err := scheme.New(test.targetGVK)
if err != nil {
t.Fatal(err)
}
if err := cl.Get(ctx, client.ObjectKey{Name: meta.GetObjectMeta().GetName(), Namespace: meta.GetObjectMeta().GetNamespace()}, convertedObj.(client.Object)); err != nil {
t.Fatalf("failed to fetch object in expected API version: %v", err)
}
convertedObjMeta := convertedObj.(metav1.ObjectMetaAccessor)
convertedObjMeta.GetObjectMeta().SetCreationTimestamp(metav1.Time{})
convertedObjMeta.GetObjectMeta().SetGeneration(0)
convertedObjMeta.GetObjectMeta().SetUID("")
convertedObjMeta.GetObjectMeta().SetSelfLink("")
convertedObjMeta.GetObjectMeta().SetResourceVersion("")
convertedObjMeta.GetObjectMeta().SetManagedFields([]metav1.ManagedFieldsEntry{})
if !equality.Semantic.DeepEqual(test.output, convertedObj) {
t.Errorf("unexpected output: %s", diff.ObjectReflectDiff(test.output, convertedObj))
}
})
}
}

View File

@ -40,8 +40,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/envtest"
"github.com/cert-manager/cert-manager/internal/test/paths"
"github.com/cert-manager/cert-manager/internal/webhook"
"github.com/cert-manager/cert-manager/pkg/webhook/handlers"
"github.com/cert-manager/cert-manager/test/apiserver"
webhooktesting "github.com/cert-manager/cert-manager/test/webhook"
)
@ -51,8 +49,7 @@ type StopFunc func()
// controlPlaneOptions has parameters for the control plane of the integration
// test framework which can be overridden in tests.
type controlPlaneOptions struct {
crdsDir *string
webhookConversionHandler handlers.ConversionHook
crdsDir *string
}
type RunControlPlaneOption func(*controlPlaneOptions)
@ -65,14 +62,6 @@ func WithCRDDirectory(directory string) RunControlPlaneOption {
}
}
// WithWebhookConversionHandler allows the webhook handler for the `/convert`
// endpoint to be overridden in tests.
func WithWebhookConversionHandler(handler handlers.ConversionHook) RunControlPlaneOption {
return func(o *controlPlaneOptions) {
o.webhookConversionHandler = handler
}
}
func RunControlPlane(t *testing.T, ctx context.Context, optionFunctions ...RunControlPlaneOption) (*rest.Config, StopFunc) {
crdDirectoryPath, err := paths.CRDDirectory()
if err != nil {
@ -112,14 +101,12 @@ func RunControlPlane(t *testing.T, ctx context.Context, optionFunctions ...RunCo
webhookOpts, stopWebhook := webhooktesting.StartWebhookServer(
t, ctx, []string{"--kubeconfig", f.Name()},
webhook.WithConversionHandler(options.webhookConversionHandler),
)
crds := readCustomResourcesAtPath(t, *options.crdsDir)
for _, crd := range crds {
t.Logf("Found CRD with name %q", crd.Name)
}
patchCRDConversion(crds, webhookOpts.URL, webhookOpts.CAPEM)
if _, err := envtest.InstallCRDs(env.Config, envtest.CRDInstallOptions{
CRDs: crds,
@ -159,25 +146,6 @@ func init() {
apiextensionsinstall.Install(internalScheme)
}
// patchCRDConversion overrides the conversion configuration of the CRDs that
// are loaded in to the integration test API server,
// configuring the conversion to be handled by the local webhook server.
func patchCRDConversion(crds []*apiextensionsv1.CustomResourceDefinition, url string, caPEM []byte) {
for i := range crds {
url := fmt.Sprintf("%s%s", url, "/convert")
crds[i].Spec.Conversion = &apiextensionsv1.CustomResourceConversion{
Strategy: apiextensionsv1.WebhookConverter,
Webhook: &apiextensionsv1.WebhookConversion{
ClientConfig: &apiextensionsv1.WebhookClientConfig{
URL: &url,
CABundle: caPEM,
},
ConversionReviewVersions: []string{"v1"},
},
}
}
}
func readCustomResourcesAtPath(t *testing.T, path string) []*apiextensionsv1.CustomResourceDefinition {
serializer := jsonserializer.NewSerializerWithOptions(jsonserializer.DefaultMetaFactory, internalScheme, internalScheme, jsonserializer.SerializerOptions{
Yaml: true,