From c5be06252ec2d035fec2495a9d5f4013cf0e98ed Mon Sep 17 00:00:00 2001 From: Sunil Arora Date: Mon, 4 Mar 2019 16:22:56 -0800 Subject: [PATCH] :sparkles: crd conversion webhook implementation --- pkg/conversion/conversion.go | 34 +++ pkg/webhook/conversion/conversion.go | 250 ++++++++++++++++++ .../conversion/conversion_suite_test.go | 38 +++ pkg/webhook/conversion/conversion_test.go | 192 ++++++++++++++ pkg/webhook/conversion/decoder.go | 30 +++ pkg/webhook/conversion/response.go | 34 +++ .../testData/pkg/apis/addtoscheme_jobs_v1.go | 25 ++ .../testData/pkg/apis/addtoscheme_jobs_v2.go | 25 ++ .../conversion/testData/pkg/apis/apis.go | 32 +++ .../testData/pkg/apis/jobs/group.go | 17 ++ .../testData/pkg/apis/jobs/v1/doc.go | 22 ++ .../pkg/apis/jobs/v1/externaljob_types.go | 94 +++++++ .../testData/pkg/apis/jobs/v1/register.go | 45 ++++ .../pkg/apis/jobs/v1/zz_generated.deepcopy.go | 116 ++++++++ .../testData/pkg/apis/jobs/v2/doc.go | 22 ++ .../pkg/apis/jobs/v2/externaljob_types.go | 70 +++++ .../testData/pkg/apis/jobs/v2/register.go | 45 ++++ .../pkg/apis/jobs/v2/zz_generated.deepcopy.go | 116 ++++++++ 18 files changed, 1207 insertions(+) create mode 100644 pkg/conversion/conversion.go create mode 100644 pkg/webhook/conversion/conversion.go create mode 100644 pkg/webhook/conversion/conversion_suite_test.go create mode 100644 pkg/webhook/conversion/conversion_test.go create mode 100644 pkg/webhook/conversion/decoder.go create mode 100644 pkg/webhook/conversion/response.go create mode 100644 pkg/webhook/conversion/testData/pkg/apis/addtoscheme_jobs_v1.go create mode 100644 pkg/webhook/conversion/testData/pkg/apis/addtoscheme_jobs_v2.go create mode 100644 pkg/webhook/conversion/testData/pkg/apis/apis.go create mode 100644 pkg/webhook/conversion/testData/pkg/apis/jobs/group.go create mode 100644 pkg/webhook/conversion/testData/pkg/apis/jobs/v1/doc.go create mode 100644 pkg/webhook/conversion/testData/pkg/apis/jobs/v1/externaljob_types.go create mode 100644 pkg/webhook/conversion/testData/pkg/apis/jobs/v1/register.go create mode 100644 pkg/webhook/conversion/testData/pkg/apis/jobs/v1/zz_generated.deepcopy.go create mode 100644 pkg/webhook/conversion/testData/pkg/apis/jobs/v2/doc.go create mode 100644 pkg/webhook/conversion/testData/pkg/apis/jobs/v2/externaljob_types.go create mode 100644 pkg/webhook/conversion/testData/pkg/apis/jobs/v2/register.go create mode 100644 pkg/webhook/conversion/testData/pkg/apis/jobs/v2/zz_generated.deepcopy.go diff --git a/pkg/conversion/conversion.go b/pkg/conversion/conversion.go new file mode 100644 index 0000000000..4269f6708f --- /dev/null +++ b/pkg/conversion/conversion.go @@ -0,0 +1,34 @@ +/* +Copyright 2019 The Kubernetes 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 "k8s.io/apimachinery/pkg/runtime" + +// Convertible defines capability of a type to convertible i.e. it can be converted to/from a hub type. +type Convertible interface { + runtime.Object + ConvertTo(dst Hub) error + ConvertFrom(src Hub) error +} + +// Hub defines capability to indicate whether a versioned type is a Hub or not. +// Default conversion handler will use this interface to implement spoke to +// spoke conversion. +type Hub interface { + runtime.Object + Hub() +} diff --git a/pkg/webhook/conversion/conversion.go b/pkg/webhook/conversion/conversion.go new file mode 100644 index 0000000000..0a130d78b0 --- /dev/null +++ b/pkg/webhook/conversion/conversion.go @@ -0,0 +1,250 @@ +/* +Copyright 2019 The Kubernetes 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 ( + "fmt" + "io/ioutil" + "net/http" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/conversion" + + "encoding/json" + + apix "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + logf "sigs.k8s.io/controller-runtime/pkg/log" +) + +var ( + log = logf.Log.WithName("conversion_webhook") +) + +// Webhook implements a CRD conversion webhook HTTP handler. +type Webhook struct { + scheme *runtime.Scheme + decoder *Decoder +} + +// InjectScheme injects a scheme into the webhook, in order to construct a Decoder. +func (wh *Webhook) InjectScheme(s *runtime.Scheme) error { + var err error + wh.scheme = s + wh.decoder, err = NewDecoder(s) + if err != nil { + return err + } + + // inject the decoder here too, just in case the order of calling this is not + // scheme first, then inject func + // if w.Handler != nil { + // if _, err := InjectDecoderInto(w.GetDecoder(), w.Handler); err != nil { + // return err + // } + // } + + return nil +} + +// ensure Webhook implements http.Handler +var _ http.Handler = &Webhook{} + +func (wh *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) { + convertReview, err := wh.readRequest(r) + if err != nil { + log.Error(err, "failed to read conversion request") + w.WriteHeader(http.StatusBadRequest) + return + } + + // TODO(droot): may be move the conversion logic to a separate module to + // decouple it from the http layer ? + resp, err := wh.handleConvertRequest(convertReview.Request) + if err != nil { + log.Error(err, "failed to convert", "request", convertReview.Request.UID) + convertReview.Response = errored(err) + convertReview.Response.UID = convertReview.Request.UID + } else { + convertReview.Response = resp + } + + err = json.NewEncoder(w).Encode(convertReview) + if err != nil { + log.Error(err, "failed to write response") + return + } +} + +func (wh *Webhook) readRequest(r *http.Request) (*apix.ConversionReview, error) { + + var body []byte + if r.Body == nil { + return nil, fmt.Errorf("nil request body") + } + data, err := ioutil.ReadAll(r.Body) + if err != nil { + return nil, err + } + body = data + + convertReview := &apix.ConversionReview{} + // TODO(droot): figure out if we want to split decoder for conversion + // request from the objects contained in the request + err = wh.decoder.DecodeInto(body, convertReview) + if err != nil { + return nil, err + } + return convertReview, nil +} + +// handles a version conversion request. +func (wh *Webhook) handleConvertRequest(req *apix.ConversionRequest) (*apix.ConversionResponse, error) { + if req == nil { + return nil, fmt.Errorf("conversion request is nil") + } + var objects []runtime.RawExtension + + for _, obj := range req.Objects { + src, gvk, err := wh.decoder.Decode(obj.Raw) + if err != nil { + return nil, err + } + dst, err := wh.allocateDstObject(req.DesiredAPIVersion, gvk.Kind) + if err != nil { + return nil, err + } + err = wh.convertObject(src, dst) + if err != nil { + return nil, err + } + objects = append(objects, runtime.RawExtension{Object: dst}) + } + return &apix.ConversionResponse{ + UID: req.UID, + ConvertedObjects: objects, + }, nil +} + +// convertObject will convert given a src object to dst object. +func (wh *Webhook) convertObject(src, dst runtime.Object) error { + srcGVK := src.GetObjectKind().GroupVersionKind() + dstGVK := dst.GetObjectKind().GroupVersionKind() + + if srcGVK.GroupKind().String() != dstGVK.GroupKind().String() { + return fmt.Errorf("src %T and dst %T does not belong to same API Group", src, dst) + } + + if srcGVK.String() == dstGVK.String() { + return fmt.Errorf("conversion is not allowed between same type %T", src) + } + + srcIsHub, dstIsHub := isHub(src), isHub(dst) + srcIsConvertible, dstIsConvertible := isConvertible(src), isConvertible(dst) + + if srcIsHub { + if dstIsConvertible { + return dst.(conversion.Convertible).ConvertFrom(src.(conversion.Hub)) + } + return fmt.Errorf("%T is not convertible to %T", src, dst) + } + + if dstIsHub { + if srcIsConvertible { + return src.(conversion.Convertible).ConvertTo(dst.(conversion.Hub)) + } + return fmt.Errorf("%T is not convertible %T", dst, src) + } + + // neither src nor dst are Hub, means both of them are spoke, so lets get the hub + // version type. + hub, err := wh.getHub(src) + if err != nil { + return err + } + + // src and dst needs to be convertable for it to work + if !srcIsConvertible || !dstIsConvertible { + return fmt.Errorf("%T and %T needs to be convertable", src, dst) + } + + err = src.(conversion.Convertible).ConvertTo(hub) + if err != nil { + return fmt.Errorf("%T failed to convert to hub version %T : %v", src, hub, err) + } + + err = dst.(conversion.Convertible).ConvertFrom(hub) + if err != nil { + return fmt.Errorf("%T failed to convert from hub version %T : %v", dst, hub, err) + } + + return nil +} + +// getHub returns an instance of the Hub for passed-in object's group/kind. +func (wh *Webhook) getHub(obj runtime.Object) (conversion.Hub, error) { + gvks, _, err := wh.scheme.ObjectKinds(obj) + if err != nil { + return nil, fmt.Errorf("error retriving object kinds for given object : %v", err) + } + + var hub conversion.Hub + var isHub, hubFoundAlready bool + for _, gvk := range gvks { + o, _ := wh.scheme.New(gvk) + if hub, isHub = o.(conversion.Hub); isHub { + if hubFoundAlready { + return nil, fmt.Errorf("multiple hub version defined for %T", obj) + } + hubFoundAlready = true + } + } + return hub, nil +} + +// allocateDstObject returns an instance for a given GVK. +func (wh *Webhook) allocateDstObject(apiVersion, kind string) (runtime.Object, error) { + gvk := schema.FromAPIVersionAndKind(apiVersion, kind) + + obj, err := wh.scheme.New(gvk) + if err != nil { + return obj, err + } + + t, err := meta.TypeAccessor(obj) + if err != nil { + return obj, err + } + + t.SetAPIVersion(apiVersion) + t.SetKind(kind) + + return obj, nil +} + +// isHub determines if passed-in object is a Hub or not. +func isHub(obj runtime.Object) bool { + _, yes := obj.(conversion.Hub) + return yes +} + +// isConvertible determines if passed-in object is a convertible. +func isConvertible(obj runtime.Object) bool { + _, yes := obj.(conversion.Convertible) + return yes +} diff --git a/pkg/webhook/conversion/conversion_suite_test.go b/pkg/webhook/conversion/conversion_suite_test.go new file mode 100644 index 0000000000..15f267209e --- /dev/null +++ b/pkg/webhook/conversion/conversion_suite_test.go @@ -0,0 +1,38 @@ +/* +Copyright 2018 The Kubernetes 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 ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +func TestConversionWebhook(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecsWithDefaultAndCustomReporters(t, "application Suite", []Reporter{envtest.NewlineReporter{}}) +} + +var _ = BeforeSuite(func(done Done) { + logf.SetLogger(zap.LoggerTo(GinkgoWriter, true)) + + close(done) +}, 60) diff --git a/pkg/webhook/conversion/conversion_test.go b/pkg/webhook/conversion/conversion_test.go new file mode 100644 index 0000000000..fd82bd2156 --- /dev/null +++ b/pkg/webhook/conversion/conversion_test.go @@ -0,0 +1,192 @@ +/* +Copyright 2018 The Kubernetes 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 ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" + "net/http/httptest" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + apix "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + jobsapis "sigs.k8s.io/controller-runtime/pkg/webhook/conversion/testData/pkg/apis" + jobsv1 "sigs.k8s.io/controller-runtime/pkg/webhook/conversion/testData/pkg/apis/jobs/v1" + jobsv2 "sigs.k8s.io/controller-runtime/pkg/webhook/conversion/testData/pkg/apis/jobs/v2" +) + +var _ = Describe("Conversion Webhook", func() { + + var respRecorder *httptest.ResponseRecorder + var decoder *Decoder + webhook := Webhook{} + + BeforeEach(func() { + respRecorder = &httptest.ResponseRecorder{ + Body: bytes.NewBuffer(nil), + } + + scheme := runtime.NewScheme() + Expect(jobsapis.AddToScheme(scheme)).Should(Succeed()) + + Expect(webhook.InjectScheme(scheme)).Should(Succeed()) + + var err error + decoder, err = NewDecoder(scheme) + Expect(err).NotTo(HaveOccurred()) + + }) + + doRequest := func(convReq *apix.ConversionReview) *apix.ConversionReview { + var payload bytes.Buffer + + Expect(json.NewEncoder(&payload).Encode(convReq)).Should(Succeed()) + + convReview := &apix.ConversionReview{} + req := &http.Request{ + Body: ioutil.NopCloser(bytes.NewReader(payload.Bytes())), + } + webhook.ServeHTTP(respRecorder, req) + Expect(json.NewDecoder(respRecorder.Result().Body).Decode(convReview)).Should(Succeed()) + return convReview + } + + It("should convert objects successfully", func() { + + v1Obj := &jobsv1.ExternalJob{ + TypeMeta: metav1.TypeMeta{ + Kind: "ExternalJob", + APIVersion: "jobs.example.org/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "obj-1", + }, + Spec: jobsv1.ExternalJobSpec{ + RunAt: "every 2 seconds", + }, + } + + expected := &jobsv2.ExternalJob{ + TypeMeta: metav1.TypeMeta{ + Kind: "ExternalJob", + APIVersion: "jobs.example.org/v2", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "obj-1", + }, + Spec: jobsv2.ExternalJobSpec{ + ScheduleAt: "every 2 seconds", + }, + } + + convReq := &apix.ConversionReview{ + TypeMeta: metav1.TypeMeta{}, + Request: &apix.ConversionRequest{ + DesiredAPIVersion: "jobs.example.org/v2", + Objects: []runtime.RawExtension{ + { + Object: v1Obj, + }, + }, + }, + } + + convReview := doRequest(convReq) + + Expect(convReview.Response.ConvertedObjects).Should(HaveLen(1)) + got, _, err := decoder.Decode(convReview.Response.ConvertedObjects[0].Raw) + Expect(err).NotTo(HaveOccurred()) + Expect(got).To(Equal(expected)) + }) + + It("should return error when dest/src objects belong to different API groups", func() { + v1Obj := &jobsv1.ExternalJob{ + TypeMeta: metav1.TypeMeta{ + Kind: "ExternalJob", + APIVersion: "jobs.example.org/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "obj-1", + }, + Spec: jobsv1.ExternalJobSpec{ + RunAt: "every 2 seconds", + }, + } + + convReq := &apix.ConversionReview{ + TypeMeta: metav1.TypeMeta{}, + Request: &apix.ConversionRequest{ + // request conversion for different group + DesiredAPIVersion: "jobss.example.org/v2", + Objects: []runtime.RawExtension{ + { + Object: v1Obj, + }, + }, + }, + } + + convReview := doRequest(convReq) + Expect(convReview.Response.Result.Message).Should( + ContainSubstring(`no kind "ExternalJob" is registered for version`)) + Expect(convReview.Response.ConvertedObjects).Should(BeEmpty()) + }) + + It("should return error when dest/src objects are of same type", func() { + v1Obj := &jobsv1.ExternalJob{ + TypeMeta: metav1.TypeMeta{ + Kind: "ExternalJob", + APIVersion: "jobs.example.org/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "obj-1", + }, + Spec: jobsv1.ExternalJobSpec{ + RunAt: "every 2 seconds", + }, + } + + convReq := &apix.ConversionReview{ + TypeMeta: metav1.TypeMeta{}, + Request: &apix.ConversionRequest{ + DesiredAPIVersion: "jobs.example.org/v1", + Objects: []runtime.RawExtension{ + { + Object: v1Obj, + }, + }, + }, + } + + convReview := doRequest(convReq) + + Expect(convReview.Response.Result.Message).To( + Equal("conversion is not allowed between same type *v1.ExternalJob")) + Expect(convReview.Response.ConvertedObjects).Should(BeEmpty()) + }) + +}) diff --git a/pkg/webhook/conversion/decoder.go b/pkg/webhook/conversion/decoder.go new file mode 100644 index 0000000000..2a7ca4ff81 --- /dev/null +++ b/pkg/webhook/conversion/decoder.go @@ -0,0 +1,30 @@ +package conversion + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" +) + +// Decoder knows how to decode the contents of a CRD version conversion +// request into a concrete object. +type Decoder struct { + codecs serializer.CodecFactory +} + +// NewDecoder creates a Decoder given the runtime.Scheme +func NewDecoder(scheme *runtime.Scheme) (*Decoder, error) { + return &Decoder{codecs: serializer.NewCodecFactory(scheme)}, nil +} + +// Decode decodes the inlined object. +func (d *Decoder) Decode(content []byte) (runtime.Object, *schema.GroupVersionKind, error) { + deserializer := d.codecs.UniversalDeserializer() + return deserializer.Decode(content, nil, nil) +} + +// DecodeInto decodes the inlined object in the into the passed-in runtime.Object. +func (d *Decoder) DecodeInto(content []byte, into runtime.Object) error { + deserializer := d.codecs.UniversalDeserializer() + return runtime.DecodeInto(deserializer, content, into) +} diff --git a/pkg/webhook/conversion/response.go b/pkg/webhook/conversion/response.go new file mode 100644 index 0000000000..4add8a21f1 --- /dev/null +++ b/pkg/webhook/conversion/response.go @@ -0,0 +1,34 @@ +/* +Copyright 2018 The Kubernetes 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 ( + "net/http" + + apix "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func errored(err error) *apix.ConversionResponse { + return &apix.ConversionResponse{ + Result: metav1.Status{ + Code: http.StatusOK, + Status: metav1.StatusFailure, + Message: err.Error(), + }, + } +} diff --git a/pkg/webhook/conversion/testData/pkg/apis/addtoscheme_jobs_v1.go b/pkg/webhook/conversion/testData/pkg/apis/addtoscheme_jobs_v1.go new file mode 100644 index 0000000000..fa39ba78af --- /dev/null +++ b/pkg/webhook/conversion/testData/pkg/apis/addtoscheme_jobs_v1.go @@ -0,0 +1,25 @@ +/* + +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 apis + +import ( + "sigs.k8s.io/controller-runtime/pkg/webhook/conversion/testData/pkg/apis/jobs/v1" +) + +func init() { + // Register the types with the Scheme so the components can map objects to GroupVersionKinds and back + AddToSchemes = append(AddToSchemes, v1.SchemeBuilder.AddToScheme) +} diff --git a/pkg/webhook/conversion/testData/pkg/apis/addtoscheme_jobs_v2.go b/pkg/webhook/conversion/testData/pkg/apis/addtoscheme_jobs_v2.go new file mode 100644 index 0000000000..09428aa6a5 --- /dev/null +++ b/pkg/webhook/conversion/testData/pkg/apis/addtoscheme_jobs_v2.go @@ -0,0 +1,25 @@ +/* + +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 apis + +import ( + "sigs.k8s.io/controller-runtime/pkg/webhook/conversion/testData/pkg/apis/jobs/v2" +) + +func init() { + // Register the types with the Scheme so the components can map objects to GroupVersionKinds and back + AddToSchemes = append(AddToSchemes, v2.SchemeBuilder.AddToScheme) +} diff --git a/pkg/webhook/conversion/testData/pkg/apis/apis.go b/pkg/webhook/conversion/testData/pkg/apis/apis.go new file mode 100644 index 0000000000..15ccbf9c8a --- /dev/null +++ b/pkg/webhook/conversion/testData/pkg/apis/apis.go @@ -0,0 +1,32 @@ +/* + +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. +*/ + +// Generate deepcopy for apis +//go:generate go run ../../vendor/k8s.io/code-generator/cmd/deepcopy-gen/main.go -O zz_generated.deepcopy -i ./... -h ../../hack/boilerplate.go.txt + +// Package apis contains Kubernetes API groups. +package apis + +import ( + "k8s.io/apimachinery/pkg/runtime" +) + +// AddToSchemes may be used to add all resources defined in the project to a Scheme +var AddToSchemes runtime.SchemeBuilder + +// AddToScheme adds all Resources to the Scheme +func AddToScheme(s *runtime.Scheme) error { + return AddToSchemes.AddToScheme(s) +} diff --git a/pkg/webhook/conversion/testData/pkg/apis/jobs/group.go b/pkg/webhook/conversion/testData/pkg/apis/jobs/group.go new file mode 100644 index 0000000000..02b401aeb6 --- /dev/null +++ b/pkg/webhook/conversion/testData/pkg/apis/jobs/group.go @@ -0,0 +1,17 @@ +/* + +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 jobs contains jobs API versions +package jobs diff --git a/pkg/webhook/conversion/testData/pkg/apis/jobs/v1/doc.go b/pkg/webhook/conversion/testData/pkg/apis/jobs/v1/doc.go new file mode 100644 index 0000000000..6b06db1687 --- /dev/null +++ b/pkg/webhook/conversion/testData/pkg/apis/jobs/v1/doc.go @@ -0,0 +1,22 @@ +/* + +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 v1 contains API Schema definitions for the jobs v1 API group +// +k8s:openapi-gen=true +// +k8s:deepcopy-gen=package,register +// +k8s:conversion-gen=github.com/droot/crd-conversion-example/pkg/apis/jobs +// +k8s:defaulter-gen=TypeMeta +// +groupName=jobs.example.org +package v1 diff --git a/pkg/webhook/conversion/testData/pkg/apis/jobs/v1/externaljob_types.go b/pkg/webhook/conversion/testData/pkg/apis/jobs/v1/externaljob_types.go new file mode 100644 index 0000000000..9ac20442f1 --- /dev/null +++ b/pkg/webhook/conversion/testData/pkg/apis/jobs/v1/externaljob_types.go @@ -0,0 +1,94 @@ +/* + +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 v1 + +import ( + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/conversion" + "sigs.k8s.io/controller-runtime/pkg/webhook/conversion/testData/pkg/apis/jobs/v2" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// ExternalJobSpec defines the desired state of ExternalJob +type ExternalJobSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + RunAt string `json:"runAt"` +} + +// ExternalJobStatus defines the observed state of ExternalJob +type ExternalJobStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ExternalJob is the Schema for the externaljobs API +// +k8s:openapi-gen=true +type ExternalJob struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ExternalJobSpec `json:"spec,omitempty"` + Status ExternalJobStatus `json:"status,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ExternalJobList contains a list of ExternalJob +type ExternalJobList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ExternalJob `json:"items"` +} + +func init() { + SchemeBuilder.Register(&ExternalJob{}, &ExternalJobList{}) +} + +// ConvertTo implements conversion logic to convert to Hub type (v2.ExternalJob +// in this case) +func (ej *ExternalJob) ConvertTo(dst conversion.Hub) error { + switch t := dst.(type) { + case *v2.ExternalJob: + jobv2 := dst.(*v2.ExternalJob) + jobv2.ObjectMeta = ej.ObjectMeta + jobv2.Spec.ScheduleAt = ej.Spec.RunAt + return nil + default: + return fmt.Errorf("unsupported type %v", t) + } +} + +// ConvertFrom implements conversion logic to convert from Hub type (v2.ExternalJob +// in this case) +func (ej *ExternalJob) ConvertFrom(src conversion.Hub) error { + switch t := src.(type) { + case *v2.ExternalJob: + jobv2 := src.(*v2.ExternalJob) + ej.ObjectMeta = jobv2.ObjectMeta + ej.Spec.RunAt = jobv2.Spec.ScheduleAt + return nil + default: + return fmt.Errorf("unsupported type %v", t) + } +} diff --git a/pkg/webhook/conversion/testData/pkg/apis/jobs/v1/register.go b/pkg/webhook/conversion/testData/pkg/apis/jobs/v1/register.go new file mode 100644 index 0000000000..3afa816eb6 --- /dev/null +++ b/pkg/webhook/conversion/testData/pkg/apis/jobs/v1/register.go @@ -0,0 +1,45 @@ +/* + +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. +*/ + +// NOTE: Boilerplate only. Ignore this file. + +// Package v1 contains API Schema definitions for the jobs v1 API group +// +k8s:openapi-gen=true +// +k8s:deepcopy-gen=package,register +// +k8s:conversion-gen=github.com/droot/crd-conversion-example/pkg/apis/jobs +// +k8s:defaulter-gen=TypeMeta +// +groupName=jobs.example.org +package v1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + controllers "sigs.k8s.io/controller-runtime" +) + +var ( + // SchemeGroupVersion is group version used to register these objects + SchemeGroupVersion = schema.GroupVersion{Group: "jobs.example.org", Version: "v1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &controllers.SchemeBuilder{GroupVersion: SchemeGroupVersion} + + // AddToScheme is required by pkg/client/... + AddToScheme = SchemeBuilder.AddToScheme +) + +// Resource is required by pkg/client/listers/... +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} diff --git a/pkg/webhook/conversion/testData/pkg/apis/jobs/v1/zz_generated.deepcopy.go b/pkg/webhook/conversion/testData/pkg/apis/jobs/v1/zz_generated.deepcopy.go new file mode 100644 index 0000000000..dc2e649724 --- /dev/null +++ b/pkg/webhook/conversion/testData/pkg/apis/jobs/v1/zz_generated.deepcopy.go @@ -0,0 +1,116 @@ +// +build !ignore_autogenerated + +/* + +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. +*/ +// Code generated by main. DO NOT EDIT. + +package v1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalJob) DeepCopyInto(out *ExternalJob) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalJob. +func (in *ExternalJob) DeepCopy() *ExternalJob { + if in == nil { + return nil + } + out := new(ExternalJob) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ExternalJob) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalJobList) DeepCopyInto(out *ExternalJobList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ExternalJob, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalJobList. +func (in *ExternalJobList) DeepCopy() *ExternalJobList { + if in == nil { + return nil + } + out := new(ExternalJobList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ExternalJobList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalJobSpec) DeepCopyInto(out *ExternalJobSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalJobSpec. +func (in *ExternalJobSpec) DeepCopy() *ExternalJobSpec { + if in == nil { + return nil + } + out := new(ExternalJobSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalJobStatus) DeepCopyInto(out *ExternalJobStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalJobStatus. +func (in *ExternalJobStatus) DeepCopy() *ExternalJobStatus { + if in == nil { + return nil + } + out := new(ExternalJobStatus) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/webhook/conversion/testData/pkg/apis/jobs/v2/doc.go b/pkg/webhook/conversion/testData/pkg/apis/jobs/v2/doc.go new file mode 100644 index 0000000000..2a1052c95e --- /dev/null +++ b/pkg/webhook/conversion/testData/pkg/apis/jobs/v2/doc.go @@ -0,0 +1,22 @@ +/* + +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 v2 contains API Schema definitions for the jobs v2 API group +// +k8s:openapi-gen=true +// +k8s:deepcopy-gen=package,register +// +k8s:conversion-gen=github.com/droot/crd-conversion-example/pkg/apis/jobs +// +k8s:defaulter-gen=TypeMeta +// +groupName=jobs.example.org +package v2 diff --git a/pkg/webhook/conversion/testData/pkg/apis/jobs/v2/externaljob_types.go b/pkg/webhook/conversion/testData/pkg/apis/jobs/v2/externaljob_types.go new file mode 100644 index 0000000000..c6e75ee0e8 --- /dev/null +++ b/pkg/webhook/conversion/testData/pkg/apis/jobs/v2/externaljob_types.go @@ -0,0 +1,70 @@ +/* + +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 v2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// ExternalJobSpec defines the desired state of ExternalJob +type ExternalJobSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + ScheduleAt string `json:"scheduleAt"` +} + +// ExternalJobStatus defines the observed state of ExternalJob +type ExternalJobStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ExternalJob is the Schema for the externaljobs API +// +k8s:openapi-gen=true +type ExternalJob struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ExternalJobSpec `json:"spec,omitempty"` + Status ExternalJobStatus `json:"status,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ExternalJobList contains a list of ExternalJob +type ExternalJobList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ExternalJob `json:"items"` +} + +func init() { + SchemeBuilder.Register(&ExternalJob{}, &ExternalJobList{}) +} + +// Hub is just a marker method to indicate that v2.ExternalJob is the Hub type +// in this case. +// v2.ExternalJob is the storage version so mark this as Hub. +// Storage version doesn't need to implement any conversion methods because +// default conversionHandler implements conversion logic for storage version. +// TODO(droot): Add comment annotation here to mark it as storage version +func (ej *ExternalJob) Hub() {} diff --git a/pkg/webhook/conversion/testData/pkg/apis/jobs/v2/register.go b/pkg/webhook/conversion/testData/pkg/apis/jobs/v2/register.go new file mode 100644 index 0000000000..ce4a2d3ba5 --- /dev/null +++ b/pkg/webhook/conversion/testData/pkg/apis/jobs/v2/register.go @@ -0,0 +1,45 @@ +/* + +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. +*/ + +// NOTE: Boilerplate only. Ignore this file. + +// Package v2 contains API Schema definitions for the jobs v2 API group +// +k8s:openapi-gen=true +// +k8s:deepcopy-gen=package,register +// +k8s:conversion-gen=github.com/droot/crd-conversion-example/pkg/apis/jobs +// +k8s:defaulter-gen=TypeMeta +// +groupName=jobs.example.org +package v2 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + controllers "sigs.k8s.io/controller-runtime" +) + +var ( + // SchemeGroupVersion is group version used to register these objects + SchemeGroupVersion = schema.GroupVersion{Group: "jobs.example.org", Version: "v2"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &controllers.SchemeBuilder{GroupVersion: SchemeGroupVersion} + + // AddToScheme is required by pkg/client/... + AddToScheme = SchemeBuilder.AddToScheme +) + +// Resource is required by pkg/client/listers/... +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} diff --git a/pkg/webhook/conversion/testData/pkg/apis/jobs/v2/zz_generated.deepcopy.go b/pkg/webhook/conversion/testData/pkg/apis/jobs/v2/zz_generated.deepcopy.go new file mode 100644 index 0000000000..cffbbf5577 --- /dev/null +++ b/pkg/webhook/conversion/testData/pkg/apis/jobs/v2/zz_generated.deepcopy.go @@ -0,0 +1,116 @@ +// +build !ignore_autogenerated + +/* + +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. +*/ +// Code generated by main. DO NOT EDIT. + +package v2 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalJob) DeepCopyInto(out *ExternalJob) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalJob. +func (in *ExternalJob) DeepCopy() *ExternalJob { + if in == nil { + return nil + } + out := new(ExternalJob) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ExternalJob) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalJobList) DeepCopyInto(out *ExternalJobList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ExternalJob, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalJobList. +func (in *ExternalJobList) DeepCopy() *ExternalJobList { + if in == nil { + return nil + } + out := new(ExternalJobList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ExternalJobList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalJobSpec) DeepCopyInto(out *ExternalJobSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalJobSpec. +func (in *ExternalJobSpec) DeepCopy() *ExternalJobSpec { + if in == nil { + return nil + } + out := new(ExternalJobSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalJobStatus) DeepCopyInto(out *ExternalJobStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalJobStatus. +func (in *ExternalJobStatus) DeepCopy() *ExternalJobStatus { + if in == nil { + return nil + } + out := new(ExternalJobStatus) + in.DeepCopyInto(out) + return out +}