remove webhook conversion logic
Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com>
This commit is contained in:
parent
6a9c402c61
commit
899d55ae57
@ -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)
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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{}) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user