diff --git a/examples/conversion/pkg/apis/addtoscheme_jobs_v1.go b/examples/conversion/pkg/apis/addtoscheme_jobs_v1.go deleted file mode 100644 index eca32666cc..0000000000 --- a/examples/conversion/pkg/apis/addtoscheme_jobs_v1.go +++ /dev/null @@ -1,25 +0,0 @@ -/* - -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/examples/conversion/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/examples/conversion/pkg/apis/addtoscheme_jobs_v2.go b/examples/conversion/pkg/apis/addtoscheme_jobs_v2.go deleted file mode 100644 index 769e79230a..0000000000 --- a/examples/conversion/pkg/apis/addtoscheme_jobs_v2.go +++ /dev/null @@ -1,25 +0,0 @@ -/* - -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/examples/conversion/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/examples/conversion/pkg/apis/apis.go b/examples/conversion/pkg/apis/apis.go deleted file mode 100644 index 15ccbf9c8a..0000000000 --- a/examples/conversion/pkg/apis/apis.go +++ /dev/null @@ -1,32 +0,0 @@ -/* - -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/examples/conversion/pkg/apis/jobs/v1/doc.go b/examples/conversion/pkg/apis/jobs/v1/doc.go deleted file mode 100644 index 6b06db1687..0000000000 --- a/examples/conversion/pkg/apis/jobs/v1/doc.go +++ /dev/null @@ -1,22 +0,0 @@ -/* - -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/examples/conversion/pkg/apis/jobs/v2/doc.go b/examples/conversion/pkg/apis/jobs/v2/doc.go deleted file mode 100644 index 2a1052c95e..0000000000 --- a/examples/conversion/pkg/apis/jobs/v2/doc.go +++ /dev/null @@ -1,22 +0,0 @@ -/* - -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/builder/webhook.go b/pkg/builder/webhook.go index d807552a02..2126907c02 100644 --- a/pkg/builder/webhook.go +++ b/pkg/builder/webhook.go @@ -87,9 +87,9 @@ func (blder *WebhookBuilder) registerWebhooks() error { blder.registerDefaultingWebhook() blder.registerValidatingWebhook() - err = conversion.CheckConvertibility(blder.mgr.GetScheme(), blder.apiType) + err = blder.registerConversionWebhook() if err != nil { - log.Error(err, "conversion check failed", "GVK", blder.gvk) + return err } return nil } @@ -131,6 +131,22 @@ func (blder *WebhookBuilder) registerValidatingWebhook() { } } +func (blder *WebhookBuilder) registerConversionWebhook() error { + ok, err := conversion.IsConvertible(blder.mgr.GetScheme(), blder.apiType) + if err != nil { + log.Error(err, "conversion check failed", "object", blder.apiType) + return err + } + if ok { + if !blder.isAlreadyHandled("/convert") { + blder.mgr.GetWebhookServer().Register("/convert", &conversion.Webhook{}) + } + log.Info("conversion webhook enabled", "object", blder.apiType) + } + + return nil +} + func (blder *WebhookBuilder) isAlreadyHandled(path string) bool { h, p := blder.mgr.GetWebhookServer().WebhookMux.Handler(&http.Request{URL: &url.URL{Path: path}}) if p == path && h != nil { diff --git a/pkg/manager/internal.go b/pkg/manager/internal.go index 652ba3080f..19857bdd03 100644 --- a/pkg/manager/internal.go +++ b/pkg/manager/internal.go @@ -38,7 +38,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/recorder" "sigs.k8s.io/controller-runtime/pkg/runtime/inject" "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/conversion" ) const ( @@ -218,7 +217,6 @@ func (cm *controllerManager) GetWebhookServer() *webhook.Server { Port: cm.port, Host: cm.host, } - cm.webhookServer.Register("/convert", &conversion.Webhook{}) if err := cm.Add(cm.webhookServer); err != nil { panic("unable to add webhookServer to the controller manager") } diff --git a/pkg/webhook/conversion/conversion.go b/pkg/webhook/conversion/conversion.go index 7a74a14892..f9bd5cb2db 100644 --- a/pkg/webhook/conversion/conversion.go +++ b/pkg/webhook/conversion/conversion.go @@ -174,23 +174,24 @@ func (wh *Webhook) convertViaHub(src, dst conversion.Convertible) error { // 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) + gvks := objectGVKs(wh.scheme, obj) + if len(gvks) == 0 { + return nil, fmt.Errorf("error retrieving gvks for object : %v", obj) } var hub conversion.Hub - var isHub, hubFoundAlready bool + var hubFoundAlready bool for _, gvk := range gvks { instance, err := wh.scheme.New(gvk) if err != nil { return nil, fmt.Errorf("failed to allocate an instance for gvk %v %v", gvk, err) } - if hub, isHub = instance.(conversion.Hub); isHub { + if val, isHub := instance.(conversion.Hub); isHub { if hubFoundAlready { return nil, fmt.Errorf("multiple hub version defined for %T", obj) } hubFoundAlready = true + hub = val } } return hub, nil @@ -216,21 +217,21 @@ func (wh *Webhook) allocateDstObject(apiVersion, kind string) (runtime.Object, e return obj, nil } -// CheckConvertibility determines if given type is convertible or not. For a type +// IsConvertible determines if given type is convertible or not. For a type // to be convertible, the group-kind needs to have a Hub type defined and all // non-hub types must be able to convert to/from Hub. -func CheckConvertibility(scheme *runtime.Scheme, obj runtime.Object) error { +func IsConvertible(scheme *runtime.Scheme, obj runtime.Object) (bool, error) { var hubs, spokes, nonSpokes []runtime.Object - gvks, _, err := scheme.ObjectKinds(obj) - if err != nil { - return fmt.Errorf("error retriving object kinds for given object : %v", err) + gvks := objectGVKs(scheme, obj) + if len(gvks) == 0 { + return false, fmt.Errorf("error retrieving gvks for object : %v", obj) } for _, gvk := range gvks { instance, err := scheme.New(gvk) if err != nil { - return fmt.Errorf("failed to allocate an instance for gvk %v %v", gvk, err) + return false, fmt.Errorf("failed to allocate an instance for gvk %v %v", gvk, err) } if isHub(instance) { @@ -247,13 +248,13 @@ func CheckConvertibility(scheme *runtime.Scheme, obj runtime.Object) error { } if len(gvks) == 1 { - return nil // single version + return false, nil // single version } if len(hubs) == 0 && len(spokes) == 0 { // multiple version detected with no conversion implementation. This is // true for multi-version built-in types. - return nil + return false, nil } if len(hubs) == 1 && len(nonSpokes) == 0 { // convertible @@ -261,18 +262,31 @@ func CheckConvertibility(scheme *runtime.Scheme, obj runtime.Object) error { for _, sp := range spokes { spokeVersions = append(spokeVersions, sp.GetObjectKind().GroupVersionKind().String()) } - log.V(1).Info("conversion enabled for kind", "kind", - gvks[0].GroupKind(), "hub", hubs[0], "spokes", spokeVersions) - return nil + return true, nil } - return PartialImplementationError{ + return false, PartialImplementationError{ hubs: hubs, nonSpokes: nonSpokes, spokes: spokes, } } +// objectGVKs returns all (Group,Version,Kind) for the Group/Kind of given object. +func objectGVKs(scheme *runtime.Scheme, obj runtime.Object) []schema.GroupVersionKind { + var gvks []schema.GroupVersionKind + + objGVK := obj.GetObjectKind().GroupVersionKind() + knownTypes := scheme.AllKnownTypes() + + for gvk := range knownTypes { + if objGVK.GroupKind() == gvk.GroupKind() { + gvks = append(gvks, gvk) + } + } + return gvks +} + // PartialImplementationError represents an error due to partial conversion // implementation such as hub without spokes, multiple hubs or spokes without hub. type PartialImplementationError struct { diff --git a/pkg/webhook/conversion/conversion_test.go b/pkg/webhook/conversion/conversion_test.go index 6b08a1ae03..1f2e1dc0e3 100644 --- a/pkg/webhook/conversion/conversion_test.go +++ b/pkg/webhook/conversion/conversion_test.go @@ -32,9 +32,9 @@ import ( "k8s.io/apimachinery/pkg/runtime" kscheme "k8s.io/client-go/kubernetes/scheme" - jobsapis "sigs.k8s.io/controller-runtime/examples/conversion/pkg/apis" - jobsv1 "sigs.k8s.io/controller-runtime/examples/conversion/pkg/apis/jobs/v1" - jobsv2 "sigs.k8s.io/controller-runtime/examples/conversion/pkg/apis/jobs/v2" + jobsv1 "sigs.k8s.io/controller-runtime/pkg/webhook/conversion/testdata/api/v1" + jobsv2 "sigs.k8s.io/controller-runtime/pkg/webhook/conversion/testdata/api/v2" + jobsv3 "sigs.k8s.io/controller-runtime/pkg/webhook/conversion/testdata/api/v3" ) var _ = Describe("Conversion Webhook", func() { @@ -49,8 +49,11 @@ var _ = Describe("Conversion Webhook", func() { Body: bytes.NewBuffer(nil), } - scheme = kscheme.Scheme - Expect(jobsapis.AddToScheme(scheme)).To(Succeed()) + scheme = runtime.NewScheme() + Expect(kscheme.AddToScheme(scheme)).To(Succeed()) + Expect(jobsv1.AddToScheme(scheme)).To(Succeed()) + Expect(jobsv2.AddToScheme(scheme)).To(Succeed()) + Expect(jobsv3.AddToScheme(scheme)).To(Succeed()) Expect(webhook.InjectScheme(scheme)).To(Succeed()) var err error @@ -77,7 +80,7 @@ var _ = Describe("Conversion Webhook", func() { return &jobsv1.ExternalJob{ TypeMeta: metav1.TypeMeta{ Kind: "ExternalJob", - APIVersion: "jobs.example.org/v1", + APIVersion: "jobs.testprojects.kb.io/v1", }, ObjectMeta: metav1.ObjectMeta{ Namespace: "default", @@ -89,14 +92,30 @@ var _ = Describe("Conversion Webhook", func() { } } - It("should convert objects successfully", func() { + makeV2Obj := func() *jobsv2.ExternalJob { + return &jobsv2.ExternalJob{ + TypeMeta: metav1.TypeMeta{ + Kind: "ExternalJob", + APIVersion: "jobs.testprojects.kb.io/v2", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "obj-1", + }, + Spec: jobsv2.ExternalJobSpec{ + ScheduleAt: "every 2 seconds", + }, + } + } + + It("should convert spoke to hub successfully", func() { v1Obj := makeV1Obj() expected := &jobsv2.ExternalJob{ TypeMeta: metav1.TypeMeta{ Kind: "ExternalJob", - APIVersion: "jobs.example.org/v2", + APIVersion: "jobs.testprojects.kb.io/v2", }, ObjectMeta: metav1.ObjectMeta{ Namespace: "default", @@ -110,7 +129,85 @@ var _ = Describe("Conversion Webhook", func() { convReq := &apix.ConversionReview{ TypeMeta: metav1.TypeMeta{}, Request: &apix.ConversionRequest{ - DesiredAPIVersion: "jobs.example.org/v2", + DesiredAPIVersion: "jobs.testprojects.kb.io/v2", + Objects: []runtime.RawExtension{ + { + Object: v1Obj, + }, + }, + }, + } + + convReview := doRequest(convReq) + + Expect(convReview.Response.ConvertedObjects).To(HaveLen(1)) + Expect(convReview.Response.Result.Status).To(Equal(metav1.StatusSuccess)) + got, _, err := decoder.Decode(convReview.Response.ConvertedObjects[0].Raw) + Expect(err).NotTo(HaveOccurred()) + Expect(got).To(Equal(expected)) + }) + + It("should convert hub to spoke successfully", func() { + + v2Obj := makeV2Obj() + + expected := &jobsv1.ExternalJob{ + TypeMeta: metav1.TypeMeta{ + Kind: "ExternalJob", + APIVersion: "jobs.testprojects.kb.io/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.testprojects.kb.io/v1", + Objects: []runtime.RawExtension{ + { + Object: v2Obj, + }, + }, + }, + } + + convReview := doRequest(convReq) + + Expect(convReview.Response.ConvertedObjects).To(HaveLen(1)) + Expect(convReview.Response.Result.Status).To(Equal(metav1.StatusSuccess)) + got, _, err := decoder.Decode(convReview.Response.ConvertedObjects[0].Raw) + Expect(err).NotTo(HaveOccurred()) + Expect(got).To(Equal(expected)) + }) + + It("should convert spoke to spoke successfully", func() { + + v1Obj := makeV1Obj() + + expected := &jobsv3.ExternalJob{ + TypeMeta: metav1.TypeMeta{ + Kind: "ExternalJob", + APIVersion: "jobs.testprojects.kb.io/v3", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "obj-1", + }, + Spec: jobsv3.ExternalJobSpec{ + DeferredAt: "every 2 seconds", + }, + } + + convReq := &apix.ConversionReview{ + TypeMeta: metav1.TypeMeta{}, + Request: &apix.ConversionRequest{ + DesiredAPIVersion: "jobs.testprojects.kb.io/v3", Objects: []runtime.RawExtension{ { Object: v1Obj, @@ -156,7 +253,7 @@ var _ = Describe("Conversion Webhook", func() { convReq := &apix.ConversionReview{ TypeMeta: metav1.TypeMeta{}, Request: &apix.ConversionRequest{ - DesiredAPIVersion: "jobs.example.org/v1", + DesiredAPIVersion: "jobs.testprojects.kb.io/v1", Objects: []runtime.RawExtension{ { Object: v1Obj, @@ -202,30 +299,33 @@ var _ = Describe("Conversion Webhook", func() { }) -var _ = Describe("Convertibility Check", func() { +var _ = Describe("IsConvertible", func() { var scheme *runtime.Scheme BeforeEach(func() { + scheme = runtime.NewScheme() - scheme = kscheme.Scheme - Expect(jobsapis.AddToScheme(scheme)).To(Succeed()) - + Expect(kscheme.AddToScheme(scheme)).To(Succeed()) + Expect(jobsv1.AddToScheme(scheme)).To(Succeed()) + Expect(jobsv2.AddToScheme(scheme)).To(Succeed()) + Expect(jobsv3.AddToScheme(scheme)).To(Succeed()) }) - It("should not return error for convertible types", func() { + It("should return true for convertible types", func() { obj := &jobsv2.ExternalJob{ TypeMeta: metav1.TypeMeta{ Kind: "ExternalJob", - APIVersion: "jobs.example.org/v2", + APIVersion: "jobs.testprojects.kb.io/v2", }, } - err := CheckConvertibility(scheme, obj) + ok, err := IsConvertible(scheme, obj) Expect(err).NotTo(HaveOccurred()) + Expect(ok).To(BeTrue()) }) - It("should not return error for a built-in multi-version type", func() { + It("should return false for a non convertible type", func() { obj := &appsv1beta1.Deployment{ TypeMeta: metav1.TypeMeta{ Kind: "Deployment", @@ -233,7 +333,8 @@ var _ = Describe("Convertibility Check", func() { }, } - err := CheckConvertibility(scheme, obj) + ok, err := IsConvertible(scheme, obj) Expect(err).NotTo(HaveOccurred()) + Expect(ok).ToNot(BeTrue()) }) }) diff --git a/pkg/webhook/conversion/testdata/.gitignore b/pkg/webhook/conversion/testdata/.gitignore new file mode 100644 index 0000000000..d97ffc5159 --- /dev/null +++ b/pkg/webhook/conversion/testdata/.gitignore @@ -0,0 +1,24 @@ + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +bin + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Kubernetes Generated files - skip generated files, except for vendored files + +!vendor/**/zz_generated.* + +# editor and IDE paraphernalia +.idea +*.swp +*.swo +*~ diff --git a/pkg/webhook/conversion/testdata/Makefile b/pkg/webhook/conversion/testdata/Makefile new file mode 100644 index 0000000000..2d9d3dda15 --- /dev/null +++ b/pkg/webhook/conversion/testdata/Makefile @@ -0,0 +1,64 @@ + +# Image URL to use all building/pushing image targets +IMG ?= controller:latest +# Produce CRDs that work back to Kubernetes 1.11 (no version conversion) +CRD_OPTIONS ?= "crd:trivialVersions=true" + +all: manager + +# Run tests +test: generate fmt vet manifests + go test ./api/... ./controllers/... -coverprofile cover.out + +# Build manager binary +manager: generate fmt vet + go build -o bin/manager main.go + +# Run against the configured Kubernetes cluster in ~/.kube/config +run: generate fmt vet + go run ./main.go + +# Install CRDs into a cluster +install: manifests + kubectl apply -f config/crd/bases + +# Deploy controller in the configured Kubernetes cluster in ~/.kube/config +deploy: manifests + kubectl apply -f config/crd/bases + kustomize build config/default | kubectl apply -f - + +# Generate manifests e.g. CRD, RBAC etc. +manifests: controller-gen + $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases + +# Run go fmt against code +fmt: + go fmt ./... + +# Run go vet against code +vet: + go vet ./... + +# Generate code +generate: controller-gen + $(CONTROLLER_GEN) object:headerFile=./hack/boilerplate.go.txt paths=./api/... + +# Build the docker image +docker-build: test + docker build . -t ${IMG} + @echo "updating kustomize image patch file for manager resource" + sed -i'' -e 's@image: .*@image: '"${IMG}"'@' ./config/default/manager_image_patch.yaml + +# Push the docker image +docker-push: + docker push ${IMG} + +# find or download controller-gen +# download controller-gen if necessary +controller-gen: +ifeq (, $(shell which controller-gen)) + go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.2.0-beta.2 +CONTROLLER_GEN=$(shell go env GOPATH)/bin/controller-gen +else +CONTROLLER_GEN=$(shell which controller-gen) +endif diff --git a/pkg/webhook/conversion/testdata/PROJECT b/pkg/webhook/conversion/testdata/PROJECT new file mode 100644 index 0000000000..6b168dcbc1 --- /dev/null +++ b/pkg/webhook/conversion/testdata/PROJECT @@ -0,0 +1,13 @@ +version: "2" +domain: testprojects.kb.io +repo: sigs.k8s.io/controller-runtime/pkg/webhook/conversion/testdata +resources: +- group: jobs + version: v1 + kind: ExternalJob +- group: jobs + version: v2 + kind: ExternalJob +- group: jobs + version: v3 + kind: ExternalJob diff --git a/examples/conversion/pkg/apis/jobs/v1/externaljob_types.go b/pkg/webhook/conversion/testdata/api/v1/externaljob_types.go similarity index 91% rename from examples/conversion/pkg/apis/jobs/v1/externaljob_types.go rename to pkg/webhook/conversion/testdata/api/v1/externaljob_types.go index dcb137084d..bf99e2a204 100644 --- a/examples/conversion/pkg/apis/jobs/v1/externaljob_types.go +++ b/pkg/webhook/conversion/testdata/api/v1/externaljob_types.go @@ -17,10 +17,10 @@ package v1 import ( "fmt" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/examples/conversion/pkg/apis/jobs/v2" "sigs.k8s.io/controller-runtime/pkg/conversion" + + v2 "sigs.k8s.io/controller-runtime/pkg/webhook/conversion/testdata/api/v2" ) // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! @@ -39,11 +39,9 @@ type ExternalJobStatus struct { // Important: Run "make" to regenerate code after modifying this file } -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:object:root=true // ExternalJob is the Schema for the externaljobs API -// +k8s:openapi-gen=true type ExternalJob struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -52,7 +50,7 @@ type ExternalJob struct { Status ExternalJobStatus `json:"status,omitempty"` } -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:object:root=true // ExternalJobList contains a list of ExternalJob type ExternalJobList struct { diff --git a/examples/conversion/pkg/apis/jobs/v1/register.go b/pkg/webhook/conversion/testdata/api/v1/groupversion_info.go similarity index 54% rename from examples/conversion/pkg/apis/jobs/v1/register.go rename to pkg/webhook/conversion/testdata/api/v1/groupversion_info.go index 8181537beb..5bbef61786 100644 --- a/examples/conversion/pkg/apis/jobs/v1/register.go +++ b/pkg/webhook/conversion/testdata/api/v1/groupversion_info.go @@ -13,14 +13,9 @@ 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 +// +kubebuilder:object:generate=true +// +groupName=jobs.testprojects.kb.io package v1 import ( @@ -29,17 +24,12 @@ import ( ) var ( - // SchemeGroupVersion is group version used to register these objects - SchemeGroupVersion = schema.GroupVersion{Group: "jobs.example.org", Version: "v1"} + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "jobs.testprojects.kb.io", Version: "v1"} // SchemeBuilder is used to add go types to the GroupVersionKind scheme - SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion} + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} - // AddToScheme is required by pkg/client/... + // AddToScheme adds the types in this group-version to the given scheme. AddToScheme = SchemeBuilder.AddToScheme ) - -// Resource is required by pkg/client/listers/... -func Resource(resource string) schema.GroupResource { - return SchemeGroupVersion.WithResource(resource).GroupResource() -} diff --git a/examples/conversion/pkg/apis/jobs/v1/zz_generated.deepcopy.go b/pkg/webhook/conversion/testdata/api/v1/zz_generated.deepcopy.go similarity index 97% rename from examples/conversion/pkg/apis/jobs/v1/zz_generated.deepcopy.go rename to pkg/webhook/conversion/testdata/api/v1/zz_generated.deepcopy.go index dc2e649724..7208ba8c69 100644 --- a/examples/conversion/pkg/apis/jobs/v1/zz_generated.deepcopy.go +++ b/pkg/webhook/conversion/testdata/api/v1/zz_generated.deepcopy.go @@ -14,7 +14,8 @@ 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. + +// autogenerated by controller-gen object, do not modify manually package v1 @@ -29,7 +30,6 @@ func (in *ExternalJob) DeepCopyInto(out *ExternalJob) { 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. @@ -62,7 +62,6 @@ func (in *ExternalJobList) DeepCopyInto(out *ExternalJobList) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalJobList. @@ -86,7 +85,6 @@ func (in *ExternalJobList) DeepCopyObject() runtime.Object { // 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. @@ -102,7 +100,6 @@ func (in *ExternalJobSpec) DeepCopy() *ExternalJobSpec { // 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. diff --git a/examples/conversion/pkg/apis/jobs/v2/externaljob_types.go b/pkg/webhook/conversion/testdata/api/v2/externaljob_types.go similarity index 92% rename from examples/conversion/pkg/apis/jobs/v2/externaljob_types.go rename to pkg/webhook/conversion/testdata/api/v2/externaljob_types.go index c6e75ee0e8..de5a03a212 100644 --- a/examples/conversion/pkg/apis/jobs/v2/externaljob_types.go +++ b/pkg/webhook/conversion/testdata/api/v2/externaljob_types.go @@ -35,11 +35,9 @@ type ExternalJobStatus struct { // Important: Run "make" to regenerate code after modifying this file } -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:object:root=true // ExternalJob is the Schema for the externaljobs API -// +k8s:openapi-gen=true type ExternalJob struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -48,7 +46,7 @@ type ExternalJob struct { Status ExternalJobStatus `json:"status,omitempty"` } -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:object:root=true // ExternalJobList contains a list of ExternalJob type ExternalJobList struct { diff --git a/examples/conversion/pkg/apis/jobs/v2/register.go b/pkg/webhook/conversion/testdata/api/v2/groupversion_info.go similarity index 54% rename from examples/conversion/pkg/apis/jobs/v2/register.go rename to pkg/webhook/conversion/testdata/api/v2/groupversion_info.go index 70e26e15b0..5019111a00 100644 --- a/examples/conversion/pkg/apis/jobs/v2/register.go +++ b/pkg/webhook/conversion/testdata/api/v2/groupversion_info.go @@ -13,14 +13,9 @@ 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 +// +kubebuilder:object:generate=true +// +groupName=jobs.testprojects.kb.io package v2 import ( @@ -29,17 +24,12 @@ import ( ) var ( - // SchemeGroupVersion is group version used to register these objects - SchemeGroupVersion = schema.GroupVersion{Group: "jobs.example.org", Version: "v2"} + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "jobs.testprojects.kb.io", Version: "v2"} // SchemeBuilder is used to add go types to the GroupVersionKind scheme - SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion} + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} - // AddToScheme is required by pkg/client/... + // AddToScheme adds the types in this group-version to the given scheme. AddToScheme = SchemeBuilder.AddToScheme ) - -// Resource is required by pkg/client/listers/... -func Resource(resource string) schema.GroupResource { - return SchemeGroupVersion.WithResource(resource).GroupResource() -} diff --git a/examples/conversion/pkg/apis/jobs/v2/zz_generated.deepcopy.go b/pkg/webhook/conversion/testdata/api/v2/zz_generated.deepcopy.go similarity index 97% rename from examples/conversion/pkg/apis/jobs/v2/zz_generated.deepcopy.go rename to pkg/webhook/conversion/testdata/api/v2/zz_generated.deepcopy.go index cffbbf5577..53c9f758b1 100644 --- a/examples/conversion/pkg/apis/jobs/v2/zz_generated.deepcopy.go +++ b/pkg/webhook/conversion/testdata/api/v2/zz_generated.deepcopy.go @@ -14,7 +14,8 @@ 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. + +// autogenerated by controller-gen object, do not modify manually package v2 @@ -29,7 +30,6 @@ func (in *ExternalJob) DeepCopyInto(out *ExternalJob) { 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. @@ -62,7 +62,6 @@ func (in *ExternalJobList) DeepCopyInto(out *ExternalJobList) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalJobList. @@ -86,7 +85,6 @@ func (in *ExternalJobList) DeepCopyObject() runtime.Object { // 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. @@ -102,7 +100,6 @@ func (in *ExternalJobSpec) DeepCopy() *ExternalJobSpec { // 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. diff --git a/pkg/webhook/conversion/testdata/api/v3/externaljob_types.go b/pkg/webhook/conversion/testdata/api/v3/externaljob_types.go new file mode 100644 index 0000000000..15c438f68a --- /dev/null +++ b/pkg/webhook/conversion/testdata/api/v3/externaljob_types.go @@ -0,0 +1,92 @@ +/* + +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 v3 + +import ( + "fmt" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/conversion" + + v2 "sigs.k8s.io/controller-runtime/pkg/webhook/conversion/testdata/api/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 + DeferredAt string `json:"deferredAt"` +} + +// 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 +} + +// +kubebuilder:object:root=true + +// ExternalJob is the Schema for the externaljobs API +type ExternalJob struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ExternalJobSpec `json:"spec,omitempty"` + Status ExternalJobStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// 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.DeferredAt + 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.DeferredAt = jobv2.Spec.ScheduleAt + return nil + default: + return fmt.Errorf("unsupported type %v", t) + } +} diff --git a/pkg/webhook/conversion/testdata/api/v3/groupversion_info.go b/pkg/webhook/conversion/testdata/api/v3/groupversion_info.go new file mode 100644 index 0000000000..1ae8269614 --- /dev/null +++ b/pkg/webhook/conversion/testdata/api/v3/groupversion_info.go @@ -0,0 +1,35 @@ +/* + +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 v3 contains API Schema definitions for the jobs v3 API group +// +kubebuilder:object:generate=true +// +groupName=jobs.testprojects.kb.io +package v3 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "jobs.testprojects.kb.io", Version: "v3"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/pkg/webhook/conversion/testdata/api/v3/zz_generated.deepcopy.go b/pkg/webhook/conversion/testdata/api/v3/zz_generated.deepcopy.go new file mode 100644 index 0000000000..a90942b427 --- /dev/null +++ b/pkg/webhook/conversion/testdata/api/v3/zz_generated.deepcopy.go @@ -0,0 +1,113 @@ +// +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. +*/ + +// autogenerated by controller-gen object, do not modify manually + +package v3 + +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 +} + +// 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]) + } + } +} + +// 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 +} + +// 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 +} + +// 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/examples/conversion/pkg/apis/jobs/group.go b/pkg/webhook/conversion/testdata/hack/boilerplate.go.txt similarity index 89% rename from examples/conversion/pkg/apis/jobs/group.go rename to pkg/webhook/conversion/testdata/hack/boilerplate.go.txt index 02b401aeb6..b92001fb4e 100644 --- a/examples/conversion/pkg/apis/jobs/group.go +++ b/pkg/webhook/conversion/testdata/hack/boilerplate.go.txt @@ -11,7 +11,4 @@ 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 +*/ \ No newline at end of file diff --git a/pkg/webhook/conversion/testdata/main.go b/pkg/webhook/conversion/testdata/main.go new file mode 100644 index 0000000000..2291dac0c3 --- /dev/null +++ b/pkg/webhook/conversion/testdata/main.go @@ -0,0 +1,72 @@ +/* + +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 main + +import ( + "flag" + "os" + + "k8s.io/apimachinery/pkg/runtime" + _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + jobsv1 "testdata.kb.io/api/v1" + jobsv2 "testdata.kb.io/api/v2" + jobsv3 "testdata.kb.io/api/v3" + // +kubebuilder:scaffold:imports +) + +var ( + scheme = runtime.NewScheme() + setupLog = ctrl.Log.WithName("setup") +) + +func init() { + + jobsv1.AddToScheme(scheme) + jobsv2.AddToScheme(scheme) + jobsv3.AddToScheme(scheme) + // +kubebuilder:scaffold:scheme +} + +func main() { + var metricsAddr string + var enableLeaderElection bool + flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") + flag.BoolVar(&enableLeaderElection, "enable-leader-election", false, + "Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.") + flag.Parse() + + ctrl.SetLogger(zap.Logger(true)) + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: scheme, + MetricsBindAddress: metricsAddr, + LeaderElection: enableLeaderElection, + }) + if err != nil { + setupLog.Error(err, "unable to start manager") + os.Exit(1) + } + + // +kubebuilder:scaffold:builder + + setupLog.Info("starting manager") + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + setupLog.Error(err, "problem running manager") + os.Exit(1) + } +}