diff --git a/docs/book/src/multiversion-tutorial/conversion.md b/docs/book/src/multiversion-tutorial/conversion.md index 443b2a79284..cef38e79645 100644 --- a/docs/book/src/multiversion-tutorial/conversion.md +++ b/docs/book/src/multiversion-tutorial/conversion.md @@ -1,8 +1,16 @@ # Implementing conversion With our model for conversion in place, it's time to actually implement -the conversion functions. We'll put them in a file called -`cronjob_conversion.go` next to our `cronjob_types.go` file, to avoid +the conversion functions. We'll create a conversion webhook +for our CronJob API version `v1` (Hub) to Spoke our CronJob API version +`v2` see: + +```go +kubebuilder create webhook --group batch --version v1 --kind CronJob --conversion--spoke v2 +``` + +The above command will generate the `cronjob_conversion.go` next to our +`cronjob_types.go` file, to avoid cluttering up our main types file with extra functions. ## Hub... diff --git a/docs/book/src/multiversion-tutorial/testdata/project/PROJECT b/docs/book/src/multiversion-tutorial/testdata/project/PROJECT index 867776e2af8..08b9037e095 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/PROJECT +++ b/docs/book/src/multiversion-tutorial/testdata/project/PROJECT @@ -18,6 +18,7 @@ resources: path: tutorial.kubebuilder.io/project/api/v1 version: v1 webhooks: + conversion: true defaulting: true validation: true webhookVersion: v1 @@ -30,7 +31,6 @@ resources: path: tutorial.kubebuilder.io/project/api/v2 version: v2 webhooks: - conversion: true defaulting: true validation: true webhookVersion: v1 diff --git a/docs/book/src/multiversion-tutorial/testdata/project/api/v1/cronjob_conversion.go b/docs/book/src/multiversion-tutorial/testdata/project/api/v1/cronjob_conversion.go index 10524383e34..e7747661252 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/api/v1/cronjob_conversion.go +++ b/docs/book/src/multiversion-tutorial/testdata/project/api/v1/cronjob_conversion.go @@ -1,4 +1,6 @@ /* +Copyright 2024 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 diff --git a/docs/book/src/multiversion-tutorial/testdata/project/api/v1/cronjob_types.go b/docs/book/src/multiversion-tutorial/testdata/project/api/v1/cronjob_types.go index 8f3e38935bf..4c61fc2d485 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/api/v1/cronjob_types.go +++ b/docs/book/src/multiversion-tutorial/testdata/project/api/v1/cronjob_types.go @@ -127,6 +127,8 @@ type CronJobStatus struct { */ // +kubebuilder:object:root=true +// +kubebuilder:storageversion +// +kubebuilder:conversion:hub // +kubebuilder:subresource:status // +versionName=v1 // +kubebuilder:storageversion diff --git a/docs/book/src/multiversion-tutorial/testdata/project/api/v2/cronjob_conversion.go b/docs/book/src/multiversion-tutorial/testdata/project/api/v2/cronjob_conversion.go index 28fa9d6520b..515f1bb5b25 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/api/v2/cronjob_conversion.go +++ b/docs/book/src/multiversion-tutorial/testdata/project/api/v2/cronjob_conversion.go @@ -1,4 +1,6 @@ /* +Copyright 2024 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 @@ -21,16 +23,17 @@ For imports, we'll need the controller-runtime package, plus the API version for our hub type (v1), and finally some of the standard packages. */ + import ( "fmt" "strings" - "sigs.k8s.io/controller-runtime/pkg/conversion" + "log" - v1 "tutorial.kubebuilder.io/project/api/v1" -) + "sigs.k8s.io/controller-runtime/pkg/conversion" -// +kubebuilder:docs-gen:collapse=Imports + batchv1 "tutorial.kubebuilder.io/project/api/v1" +) // +kubebuilder:docs-gen:collapse=Imports /* Our "spoke" versions need to implement the @@ -43,9 +46,11 @@ methods to convert to/from the hub version. ConvertTo is expected to modify its argument to contain the converted object. Most of the conversion is straightforward copying, except for converting our changed field. */ + // ConvertTo converts this CronJob to the Hub version (v1). func (src *CronJob) ConvertTo(dstRaw conversion.Hub) error { - dst := dstRaw.(*v1.CronJob) + dst := dstRaw.(*batchv1.CronJob) + log.Printf("Converting from %T to %T", dst.APIVersion, src.APIVersion) sched := src.Spec.Schedule scheduleParts := []string{"*", "*", "*", "*", "*"} @@ -74,7 +79,7 @@ func (src *CronJob) ConvertTo(dstRaw conversion.Hub) error { // Spec dst.Spec.StartingDeadlineSeconds = src.Spec.StartingDeadlineSeconds - dst.Spec.ConcurrencyPolicy = v1.ConcurrencyPolicy(src.Spec.ConcurrencyPolicy) + dst.Spec.ConcurrencyPolicy = batchv1.ConcurrencyPolicy(src.Spec.ConcurrencyPolicy) dst.Spec.Suspend = src.Spec.Suspend dst.Spec.JobTemplate = src.Spec.JobTemplate dst.Spec.SuccessfulJobsHistoryLimit = src.Spec.SuccessfulJobsHistoryLimit @@ -85,6 +90,7 @@ func (src *CronJob) ConvertTo(dstRaw conversion.Hub) error { dst.Status.LastScheduleTime = src.Status.LastScheduleTime // +kubebuilder:docs-gen:collapse=rote conversion + return nil } @@ -93,9 +99,10 @@ ConvertFrom is expected to modify its receiver to contain the converted object. Most of the conversion is straightforward copying, except for converting our changed field. */ -// ConvertFrom converts from the Hub version (v1) to this version. +// ConvertFrom converts the Hub version (v1) to this CronJob (v2). func (dst *CronJob) ConvertFrom(srcRaw conversion.Hub) error { - src := srcRaw.(*v1.CronJob) + src := srcRaw.(*batchv1.CronJob) + log.Printf("Converting from %T to %T", src.APIVersion, dst.APIVersion) schedParts := strings.Split(src.Spec.Schedule, " ") if len(schedParts) != 5 { @@ -133,5 +140,6 @@ func (dst *CronJob) ConvertFrom(srcRaw conversion.Hub) error { dst.Status.LastScheduleTime = src.Status.LastScheduleTime // +kubebuilder:docs-gen:collapse=rote conversion + return nil } diff --git a/docs/book/src/multiversion-tutorial/testdata/project/config/crd/kustomization.yaml b/docs/book/src/multiversion-tutorial/testdata/project/config/crd/kustomization.yaml index fe5ceed15a8..0eb7fd5f65f 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/config/crd/kustomization.yaml +++ b/docs/book/src/multiversion-tutorial/testdata/project/config/crd/kustomization.yaml @@ -10,6 +10,7 @@ patches: # patches here are for enabling the conversion webhook for each CRD - path: patches/webhook_in_cronjobs.yaml - path: patches/webhook_in_cronjobs.yaml +- path: patches/webhook_in_cronjobs.yaml # +kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. diff --git a/docs/book/src/multiversion-tutorial/testdata/project/internal/webhook/v1/cronjob_webhook.go b/docs/book/src/multiversion-tutorial/testdata/project/internal/webhook/v1/cronjob_webhook.go index 9c96522aa58..0ae648962cf 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/internal/webhook/v1/cronjob_webhook.go +++ b/docs/book/src/multiversion-tutorial/testdata/project/internal/webhook/v1/cronjob_webhook.go @@ -44,11 +44,7 @@ Next, we'll setup a logger for the webhooks. var cronjoblog = logf.Log.WithName("cronjob-resource") /* -This setup doubles as setup for our conversion webhooks: as long as our -types implement the -[Hub](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/conversion?tab=doc#Hub) and -[Convertible](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/conversion?tab=doc#Convertible) -interfaces, a conversion webhook will be registered. +Then, we set up the webhook with the manager. */ // SetupCronJobWebhookWithManager registers the webhook for CronJob in the manager. diff --git a/docs/book/src/multiversion-tutorial/testdata/project/internal/webhook/v2/cronjob_webhook_test.go b/docs/book/src/multiversion-tutorial/testdata/project/internal/webhook/v2/cronjob_webhook_test.go index 13664e9e0bf..a8d445c7b6b 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/internal/webhook/v2/cronjob_webhook_test.go +++ b/docs/book/src/multiversion-tutorial/testdata/project/internal/webhook/v2/cronjob_webhook_test.go @@ -84,14 +84,4 @@ var _ = Describe("CronJob Webhook", func() { // }) }) - Context("When creating CronJob under Conversion Webhook", func() { - // TODO (user): Add logic to convert the object to the desired version and verify the conversion - // Example: - // It("Should convert the object correctly", func() { - // convertedObj := &batchv2.CronJob{} - // Expect(obj.ConvertTo(convertedObj)).To(Succeed()) - // Expect(convertedObj).ToNot(BeNil()) - // }) - }) - }) diff --git a/docs/book/src/multiversion-tutorial/webhooks.md b/docs/book/src/multiversion-tutorial/webhooks.md index 6b383c31c52..ef3283aeafd 100644 --- a/docs/book/src/multiversion-tutorial/webhooks.md +++ b/docs/book/src/multiversion-tutorial/webhooks.md @@ -3,15 +3,6 @@ Our conversion is in place, so all that's left is to tell controller-runtime about our conversion. -Normally, we'd run - -```shell -kubebuilder create webhook --group batch --version v1 --kind CronJob --conversion -``` - -to scaffold out the webhook setup. However, we've already got webhook -setup, from when we built our defaulting and validating webhooks! - ## Webhook setup... {{#literatego ./testdata/project/internal/webhook/v1/cronjob_webhook.go}} diff --git a/hack/docs/internal/multiversion-tutorial/generate_multiversion.go b/hack/docs/internal/multiversion-tutorial/generate_multiversion.go index 79896073ca1..dc5ed219c85 100644 --- a/hack/docs/internal/multiversion-tutorial/generate_multiversion.go +++ b/hack/docs/internal/multiversion-tutorial/generate_multiversion.go @@ -17,7 +17,6 @@ limitations under the License. package multiversion import ( - "os" "os/exec" "path/filepath" @@ -57,6 +56,16 @@ func (sp *Sample) GenerateSampleProject() { ) hackutils.CheckError("Creating the v2 API without controller", err) + log.Infof("Creating conversion webhook for v1") + err = sp.ctx.CreateWebhook( + "--group", "batch", + "--version", "v1", + "--kind", "CronJob", + "--conversion", + "--spoke", "v2", + ) + hackutils.CheckError("Creating conversion webhook for v1", err) + log.Infof("Creating defaulting and validation webhook for v2") err = sp.ctx.CreateWebhook( "--group", "batch", @@ -64,7 +73,6 @@ func (sp *Sample) GenerateSampleProject() { "--kind", "CronJob", "--defaulting", "--programmatic-validation", - "--conversion", ) hackutils.CheckError("Creating defaulting and validation webhook for v2", err) } @@ -75,25 +83,12 @@ func (sp *Sample) UpdateTutorial() { // Update files according to the multiversion sp.updateApiV1() sp.updateApiV2() - sp.updateWebhookV1() sp.updateWebhookV2() - sp.createHubFiles() + sp.updateConversionFiles() sp.updateSampleV2() sp.updateMain() } -func (sp *Sample) updateWebhookV1() { - err := pluginutil.ReplaceInFile( - filepath.Join(sp.ctx.Dir, "internal/webhook/v1/cronjob_webhook.go"), - "Then, we set up the webhook with the manager.", - `This setup doubles as setup for our conversion webhooks: as long as our -types implement the -[Hub](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/conversion?tab=doc#Hub) and -[Convertible](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/conversion?tab=doc#Convertible) -interfaces, a conversion webhook will be registered.`, - ) - hackutils.CheckError("replace webhook setup text", err) -} func (sp *Sample) updateSampleV2() { path := filepath.Join(sp.ctx.Dir, "config/samples/batch_v2_cronjob.yaml") oldText := `# TODO(user): Add fields here` @@ -106,29 +101,82 @@ func (sp *Sample) updateSampleV2() { hackutils.CheckError("replacing TODO with sampleV2Code in batch_v2_cronjob.yaml", err) } -func (sp *Sample) createHubFiles() { +func (sp *Sample) updateConversionFiles() { path := filepath.Join(sp.ctx.Dir, "api/v1/cronjob_conversion.go") - _, err := os.Create(path) - hackutils.CheckError("creating conversion file v1", err) + err := pluginutil.InsertCodeIfNotExist(path, + "limitations under the License.\n*/", + "\n// +kubebuilder:docs-gen:collapse=Apache License") + hackutils.CheckError("appending into hub v1 collapse docs", err) - err = pluginutil.AppendCodeAtTheEnd(path, "") - hackutils.CheckError("creating empty conversion file v1", err) - - err = pluginutil.AppendCodeAtTheEnd(path, hubV1Code) - hackutils.CheckError("appending hubV1Code to cronjob_conversion.go", err) + err = pluginutil.ReplaceInFile(path, + "// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!", + hubV1CodeComment) + hackutils.CheckError("adding comment to hub v1", err) path = filepath.Join(sp.ctx.Dir, "api/v2/cronjob_conversion.go") - _, err = os.Create(path) - hackutils.CheckError("creating conversion file v2", err) + err = pluginutil.InsertCodeIfNotExist(path, + "limitations under the License.\n*/", + "\n// +kubebuilder:docs-gen:collapse=Apache License") + hackutils.CheckError("appending into hub v2 collapse docs", err) - err = pluginutil.AppendCodeAtTheEnd(path, "") - hackutils.CheckError("creating empty conversion file v2", err) + err = pluginutil.InsertCode(path, + "import (", + ` + "fmt" + "strings" - err = pluginutil.AppendCodeAtTheEnd(path, hubV2Code) - hackutils.CheckError("appending hubV2Code to cronjob_conversion.go", err) +`) + hackutils.CheckError("adding imports to hub v2", err) + + err = pluginutil.InsertCodeIfNotExist(path, + "batchv1 \"tutorial.kubebuilder.io/project/api/v1\"\n)", + `// +kubebuilder:docs-gen:collapse=Imports + +/* +Our "spoke" versions need to implement the +[`+"`"+`Convertible`+"`"+`](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/conversion?tab=doc#Convertible) +interface. Namely, they'll need `+"`"+`ConvertTo()`+"`"+` and `+"`"+`ConvertFrom()`+"`"+` +methods to convert to/from the hub version. +*/ +`) + hackutils.CheckError("appending into hub v2 collapse docs", err) + + err = pluginutil.ReplaceInFile(path, + "package v2", + hubV2CodeComment) + hackutils.CheckError("adding comment to hub v2", err) + + err = pluginutil.ReplaceInFile(path, + "// TODO: Implement conversion logic from v2 to v1", + hubV2CovertTo) + hackutils.CheckError("replace covertTo at hub v2", err) + + err = pluginutil.ReplaceInFile(path, + "// TODO: Implement conversion logic from v1 to v2", + hubV2ConvertFromCode) + hackutils.CheckError("replace covert from at hub v2", err) + + err = pluginutil.ReplaceInFile(path, + "// ConvertFrom converts the Hub version (v1) to this CronJob (v2).", + `/* +ConvertFrom is expected to modify its receiver to contain the converted object. +Most of the conversion is straightforward copying, except for converting our changed field. +*/ + +// ConvertFrom converts the Hub version (v1) to this CronJob (v2).`) + hackutils.CheckError("replace covert from info at hub v2", err) + + err = pluginutil.ReplaceInFile(path, + "// ConvertTo converts this CronJob to the Hub version (v1).", + `/* +ConvertTo is expected to modify its argument to contain the converted object. +Most of the conversion is straightforward copying, except for converting our changed field. +*/ +// ConvertTo converts this CronJob to the Hub version (v1).`) + hackutils.CheckError("replace covert info at hub v2", err) } func (sp *Sample) updateApiV1() { diff --git a/hack/docs/internal/multiversion-tutorial/hub.go b/hack/docs/internal/multiversion-tutorial/hub.go index e22e4f698ae..696fd5e095d 100644 --- a/hack/docs/internal/multiversion-tutorial/hub.go +++ b/hack/docs/internal/multiversion-tutorial/hub.go @@ -17,50 +17,16 @@ limitations under the License. package multiversion -const hubV1Code = `/* -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. -*/ -// +kubebuilder:docs-gen:collapse=Apache License - -package v1 - +const hubV1CodeComment = ` /* Implementing the hub method is pretty easy -- we just have to add an empty method called ` + "`" + `Hub()` + "`" + `to serve as a [marker](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/conversion?tab=doc#Hub). We could also just put this inline in our cronjob_types.go file. */ - -// Hub marks this type as a conversion hub. -func (*CronJob) Hub() {} ` -const hubV2Code = `/* -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. -*/ -// +kubebuilder:docs-gen:collapse=Apache License - -package v2 +const hubV2CodeComment = `package v2 /* For imports, we'll need the controller-runtime @@ -68,33 +34,9 @@ For imports, we'll need the controller-runtime package, plus the API version for our hub type (v1), and finally some of the standard packages. */ -import ( - "fmt" - "strings" - - "sigs.k8s.io/controller-runtime/pkg/conversion" - - v1 "tutorial.kubebuilder.io/project/api/v1" -) - -// +kubebuilder:docs-gen:collapse=Imports - -/* -Our "spoke" versions need to implement the -[` + "`" + `Convertible` + "`" + `](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/conversion?tab=doc#Convertible) -interface. Namely, they'll need ` + "`" + `ConvertTo()` + "`" + ` and ` + "`" + `ConvertFrom()` + "`" + ` -methods to convert to/from the hub version. -*/ - -/* -ConvertTo is expected to modify its argument to contain the converted object. -Most of the conversion is straightforward copying, except for converting our changed field. -*/ -// ConvertTo converts this CronJob to the Hub version (v1). -func (src *CronJob) ConvertTo(dstRaw conversion.Hub) error { - dst := dstRaw.(*v1.CronJob) +` - sched := src.Spec.Schedule +const hubV2CovertTo = `sched := src.Spec.Schedule scheduleParts := []string{"*", "*", "*", "*", "*"} if sched.Minute != nil { scheduleParts[0] = string(*sched.Minute) @@ -121,7 +63,7 @@ func (src *CronJob) ConvertTo(dstRaw conversion.Hub) error { // Spec dst.Spec.StartingDeadlineSeconds = src.Spec.StartingDeadlineSeconds - dst.Spec.ConcurrencyPolicy = v1.ConcurrencyPolicy(src.Spec.ConcurrencyPolicy) + dst.Spec.ConcurrencyPolicy = batchv1.ConcurrencyPolicy(src.Spec.ConcurrencyPolicy) dst.Spec.Suspend = src.Spec.Suspend dst.Spec.JobTemplate = src.Spec.JobTemplate dst.Spec.SuccessfulJobsHistoryLimit = src.Spec.SuccessfulJobsHistoryLimit @@ -131,20 +73,9 @@ func (src *CronJob) ConvertTo(dstRaw conversion.Hub) error { dst.Status.Active = src.Status.Active dst.Status.LastScheduleTime = src.Status.LastScheduleTime - // +kubebuilder:docs-gen:collapse=rote conversion - return nil -} - -/* -ConvertFrom is expected to modify its receiver to contain the converted object. -Most of the conversion is straightforward copying, except for converting our changed field. -*/ - -// ConvertFrom converts from the Hub version (v1) to this version. -func (dst *CronJob) ConvertFrom(srcRaw conversion.Hub) error { - src := srcRaw.(*v1.CronJob) + // +kubebuilder:docs-gen:collapse=rote conversion` - schedParts := strings.Split(src.Spec.Schedule, " ") +const hubV2ConvertFromCode = `schedParts := strings.Split(src.Spec.Schedule, " ") if len(schedParts) != 5 { return fmt.Errorf("invalid schedule: not a standard 5-field schedule") } @@ -179,6 +110,4 @@ func (dst *CronJob) ConvertFrom(srcRaw conversion.Hub) error { dst.Status.Active = src.Status.Active dst.Status.LastScheduleTime = src.Status.LastScheduleTime - // +kubebuilder:docs-gen:collapse=rote conversion - return nil -}` + // +kubebuilder:docs-gen:collapse=rote conversion` diff --git a/pkg/model/resource/webhooks.go b/pkg/model/resource/webhooks.go index 6d8bc0378aa..05ee920a2d6 100644 --- a/pkg/model/resource/webhooks.go +++ b/pkg/model/resource/webhooks.go @@ -33,6 +33,8 @@ type Webhooks struct { // Conversion specifies if a conversion webhook is associated to the resource. Conversion bool `json:"conversion,omitempty"` + + Spoke string `json:"spoke,omitempty"` } // Validate checks that the Webhooks is valid. diff --git a/pkg/plugins/common/kustomize/v2/scaffolds/webhook.go b/pkg/plugins/common/kustomize/v2/scaffolds/webhook.go index d919b3088e1..fdce4affaf3 100644 --- a/pkg/plugins/common/kustomize/v2/scaffolds/webhook.go +++ b/pkg/plugins/common/kustomize/v2/scaffolds/webhook.go @@ -20,15 +20,14 @@ import ( "fmt" log "github.com/sirupsen/logrus" - pluginutil "sigs.k8s.io/kubebuilder/v4/pkg/plugin/util" - "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/crd" - "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/crd/patches" - "sigs.k8s.io/kubebuilder/v4/pkg/config" "sigs.k8s.io/kubebuilder/v4/pkg/machinery" "sigs.k8s.io/kubebuilder/v4/pkg/model/resource" + pluginutil "sigs.k8s.io/kubebuilder/v4/pkg/plugin/util" "sigs.k8s.io/kubebuilder/v4/pkg/plugins" "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/certmanager" + "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/crd" + "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/crd/patches" "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/kdefault" network_policy "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/network-policy" "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/webhook" diff --git a/pkg/plugins/golang/options.go b/pkg/plugins/golang/options.go index ea55db3eac4..676712792e7 100644 --- a/pkg/plugins/golang/options.go +++ b/pkg/plugins/golang/options.go @@ -72,6 +72,9 @@ type Options struct { DoDefaulting bool DoValidation bool DoConversion bool + + // Spoke version for conversion webhook + Spoke string } // UpdateResource updates the provided resource with the options @@ -108,6 +111,7 @@ func (opts Options) UpdateResource(res *resource.Resource, c config.Config) { } if opts.DoConversion { res.Webhooks.Conversion = true + res.Webhooks.Spoke = opts.Spoke } } diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/hub.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/hub.go new file mode 100644 index 00000000000..df2e4d4e1e0 --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/hub.go @@ -0,0 +1,72 @@ +/* +Copyright 2022 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 api + +import ( + "path/filepath" + + log "github.com/sirupsen/logrus" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" +) + +var _ machinery.Template = &Hub{} + +// Hub scaffolds the file that defines hub +// nolint:maligned +type Hub struct { + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin + + Force bool +} + +// SetTemplateDefaults implements file.Template +func (f *Hub) SetTemplateDefaults() error { + if f.Path == "" { + if f.MultiGroup && f.Resource.Group != "" { + f.Path = filepath.Join("api", "%[group]", "%[version]", "%[kind]_conversion.go") + } else { + f.Path = filepath.Join("api", "%[version]", "%[kind]_conversion.go") + } + } + + f.Path = f.Resource.Replacer().Replace(f.Path) + log.Println(f.Path) + + f.TemplateBody = hubTemplate + + if f.Force { + f.IfExistsAction = machinery.OverwriteFile + } else { + f.IfExistsAction = machinery.SkipFile + } + + return nil +} + +const hubTemplate = `{{ .Boilerplate }} + +package {{ .Resource.Version }} + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! + +// Hub marks this type as a conversion hub. +func (*{{ .Resource.Kind }}) Hub() {} +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/spoke.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/spoke.go new file mode 100644 index 00000000000..6c417d707b4 --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/spoke.go @@ -0,0 +1,91 @@ +/* +Copyright 2022 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 api + +import ( + log "github.com/sirupsen/logrus" + "path/filepath" + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" +) + +var _ machinery.Template = &Spoke{} + +// Spoke scaffolds the file that defines spoke version conversion +type Spoke struct { + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin + + Force bool +} + +// SetTemplateDefaults implements file.Template +func (f *Spoke) SetTemplateDefaults() error { + if f.Path == "" { + if f.MultiGroup && f.Resource.Group != "" { + f.Path = filepath.Join("api", "%[group]", f.Resource.Webhooks.Spoke, "%[kind]_conversion.go") + } else { + f.Path = filepath.Join("api", f.Resource.Webhooks.Spoke, "%[kind]_conversion.go") + } + } + + f.Path = f.Resource.Replacer().Replace(f.Path) + log.Println(f.Path) + + f.TemplateBody = spokeTemplate + + if f.Force { + f.IfExistsAction = machinery.OverwriteFile + } else { + f.IfExistsAction = machinery.SkipFile + } + + return nil +} + +const spokeTemplate = `{{ .Boilerplate }} + +package {{ .Resource.Webhooks.Spoke }} + +import ( + "log" + + "sigs.k8s.io/controller-runtime/pkg/conversion" + {{ .Resource.ImportAlias }} "{{ .Resource.Path }}" +) + +// ConvertTo converts this {{ .Resource.Kind }} to the Hub version ({{ .Resource.Version }}). +func (src *{{ .Resource.Kind }}) ConvertTo(dstRaw conversion.Hub) error { + dst := dstRaw.(*{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}) + log.Printf("Converting from %T to %T", dst.APIVersion, src.APIVersion) + + // TODO: Implement conversion logic from {{ .Resource.Webhooks.Spoke }} to {{ .Resource.Version }} + + return nil +} + +// ConvertFrom converts the Hub version ({{ .Resource.Version }}) to this {{ .Resource.Kind }} ({{ .Resource.Webhooks.Spoke }}). +func (dst *{{ .Resource.Kind }}) ConvertFrom(srcRaw conversion.Hub) error { + src := srcRaw.(*{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}) + log.Printf("Converting from %T to %T", src.APIVersion, dst.APIVersion) + + // TODO: Implement conversion logic from {{ .Resource.Version }} to {{ .Resource.Webhooks.Spoke }} + + return nil +} +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook.go index a5ac16564bd..0849eff0824 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook.go @@ -81,7 +81,7 @@ func (f *Webhook) SetTemplateDefaults() error { if f.Force { f.IfExistsAction = machinery.OverwriteFile } else { - f.IfExistsAction = machinery.Error + f.IfExistsAction = machinery.SkipFile } f.AdmissionReviewVersions = "v1" diff --git a/pkg/plugins/golang/v4/scaffolds/webhook.go b/pkg/plugins/golang/v4/scaffolds/webhook.go index bcfb74d18b1..cb91efc9d70 100644 --- a/pkg/plugins/golang/v4/scaffolds/webhook.go +++ b/pkg/plugins/golang/v4/scaffolds/webhook.go @@ -18,6 +18,8 @@ package scaffolds import ( "fmt" + "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/api" + "strings" log "github.com/sirupsen/logrus" "github.com/spf13/afero" @@ -102,8 +104,30 @@ func (s *webhookScaffolder) Scaffold() error { } if doConversion { + resourceFilePath := fmt.Sprintf("api/%s/%s_types.go", s.resource.Version, strings.ToLower(s.resource.Kind)) + if s.config.IsMultiGroup() { + resourceFilePath = fmt.Sprintf("api/%s/%s/%s_types.go", s.resource.Group, s.resource.Version, strings.ToLower(s.resource.Kind)) + } + + err = pluginutil.InsertCodeIfNotExist(resourceFilePath, + "// +kubebuilder:object:root=true", + "\n// +kubebuilder:storageversion\n// +kubebuilder:conversion:hub") + if err != nil { + log.Errorf("Unable to insert storage version marker "+ + "(// +kubebuilder:storageversion) and the hub conversion (// +kubebuilder:conversion:hub) "+ + "in file %s: %v", resourceFilePath, err) + } + + if err := scaffold.Execute( + &api.Hub{Force: s.force}, + &api.Spoke{Force: s.force}, + ); err != nil { + return err + } + log.Println(`Webhook server has been set up for you. You need to implement the conversion.Hub and conversion.Convertible interfaces for your CRD types.`) + } // TODO: Add test suite for conversion webhook after #1664 has been merged & conversion tests supported in envtest. diff --git a/pkg/plugins/golang/v4/webhook.go b/pkg/plugins/golang/v4/webhook.go index 685b216db9d..3109bd2f327 100644 --- a/pkg/plugins/golang/v4/webhook.go +++ b/pkg/plugins/golang/v4/webhook.go @@ -65,7 +65,7 @@ validating and/or conversion webhooks. # Create conversion webhook for Group: ship, Version: v1beta1 # and Kind: Frigate - %[1]s create webhook --group ship --version v1beta1 --kind Frigate --conversion + %[1]s create webhook --group ship --version v1beta1 --kind Frigate --conversion --spoke v1 `, cliMeta.CommandName) } @@ -83,6 +83,8 @@ func (p *createWebhookSubcommand) BindFlags(fs *pflag.FlagSet) { fs.BoolVar(&p.options.DoConversion, "conversion", false, "if set, scaffold the conversion webhook") + fs.StringVar(&p.options.Spoke, "spoke", "", "spoke version for conversion webhook") + // TODO: remove for go/v5 fs.BoolVar(&p.isLegacyPath, "legacy", false, "[DEPRECATED] Attempts to create resource under the API directory (legacy path). "+ @@ -108,31 +110,46 @@ func (p *createWebhookSubcommand) InjectConfig(c config.Config) error { func (p *createWebhookSubcommand) InjectResource(res *resource.Resource) error { p.resource = res + // Check for legacy path usage with external APIs if len(p.options.ExternalAPIPath) != 0 && len(p.options.ExternalAPIDomain) != 0 && p.isLegacyPath { - return errors.New("You cannot scaffold webhooks for external types " + - "using the legacy path") + return errors.New("you cannot scaffold webhooks for external types using the legacy path") } + // Validate conversion webhook requirements if --conversion is specified + if p.options.DoConversion { + if p.options.Spoke == "" { + return fmt.Errorf("conversion webhook requires --spoke version to be specified") + } + } + + // Update resource with options p.options.UpdateResource(p.resource, p.config) + // Validate the resource itself if err := p.resource.Validate(); err != nil { return err } + // Ensure at least one webhook type is specified if !p.resource.HasDefaultingWebhook() && !p.resource.HasValidationWebhook() && !p.resource.HasConversionWebhook() { - return fmt.Errorf("%s create webhook requires at least one of --defaulting,"+ - " --programmatic-validation and --conversion to be true", p.commandName) + return fmt.Errorf("%s create webhook requires at least one of --defaulting, --programmatic-validation, and --conversion to be true", p.commandName) } - // check if resource exist to create webhook + // Check if resource already exists to create webhook resValue, err := p.config.GetResource(p.resource.GVK) res = &resValue if err != nil { + // Resource must be previously created for non-external and non-core resources if !p.resource.External && !p.resource.Core { - return fmt.Errorf("%s create webhook requires a previously created API ", p.commandName) + return fmt.Errorf("%s create webhook requires a previously created API", p.commandName) } } else if res.Webhooks != nil && !res.Webhooks.IsEmpty() && !p.force { - return fmt.Errorf("webhook resource already exists") + // Only block if the exact same webhook type already exists + if (p.options.DoDefaulting && res.HasDefaultingWebhook()) || + (p.options.DoValidation && res.HasValidationWebhook()) || + (p.options.DoConversion && res.HasConversionWebhook()) { + return fmt.Errorf("webhook of this type already exists") + } } return nil diff --git a/test/testdata/generate.sh b/test/testdata/generate.sh index e445bdc7f5c..a09b11d2c75 100755 --- a/test/testdata/generate.sh +++ b/test/testdata/generate.sh @@ -41,16 +41,12 @@ function scaffold_test_project { $kb create api --group crew --version v1 --kind Captain --controller=true --resource=true --make=false $kb create api --group crew --version v1 --kind Captain --controller=true --resource=true --make=false --force $kb create webhook --group crew --version v1 --kind Captain --defaulting --programmatic-validation --make=false - + # Create API to test conversion from v1 to v2 $kb create api --group crew --version v1 --kind FirstMate --controller=true --resource=true --make=false $kb create api --group crew --version v2 --kind FirstMate --controller=false --resource=true --make=false - $kb create webhook --group crew --version v1 --kind FirstMate --conversion --make=false + $kb create webhook --group crew --version v1 --kind FirstMate --conversion --make=false --spoke v2 - # TODO: Remove it when we have the hub and spoke scaffolded by Kubebuilder - # Apply the sed command based on project type - insert_kubebuilder_annotations "api/v1/firstmate_types.go" - $kb create api --group crew --version v1 --kind Admiral --plural=admirales --controller=true --resource=true --namespaced=false --make=false $kb create webhook --group crew --version v1 --kind Admiral --plural=admirales --defaulting # Controller for External types @@ -100,16 +96,7 @@ function scaffold_test_project { # Create API to check webhook --conversion from v1 to v2 $kb create api --group example.com --version v1 --kind Wordpress --controller=true --resource=true --make=false $kb create api --group example.com --version v2 --kind Wordpress --controller=false --resource=true --make=false - $kb create webhook --group example.com --version v1 --kind Wordpress --conversion --make=false - - # TODO: Remove it when we have the hub and spoke scaffolded by Kubebuilder - # Apply the sed command based on project type - if [[ $project =~ multigroup ]]; then - insert_kubebuilder_annotations "api/example.com/v1/wordpress_types.go" - fi - if [[ $project =~ with-plugins ]]; then - insert_kubebuilder_annotations "api/v1/wordpress_types.go" - fi + $kb create webhook --group example.com --version v1 --kind Wordpress --conversion --make=false --spoke v2 header_text 'Editing project with Grafana plugin ...' $kb edit --plugins=grafana.kubebuilder.io/v1-alpha @@ -123,16 +110,6 @@ function scaffold_test_project { popd } -# TODO: Remove when hub and spoke be scaffolded by Kubebuilder -function insert_kubebuilder_annotations { - local file=$1 - local line=43 # The target line to insert text before - local annotations="// +kubebuilder:storageversion\n// +kubebuilder:conversion:hub" - - # Create a temporary file to avoid using -i flag, which varies between macOS and Linux - awk -v insert="$annotations" -v line=$line 'NR==line{print insert} 1' "$file" > "$file.tmp" && mv "$file.tmp" "$file" -} - build_kb scaffold_test_project project-v4 --plugins="go/v4" diff --git a/testdata/project-v4-multigroup/api/example.com/v1/wordpress_conversion.go b/testdata/project-v4-multigroup/api/example.com/v1/wordpress_conversion.go new file mode 100644 index 00000000000..1c00f708fe9 --- /dev/null +++ b/testdata/project-v4-multigroup/api/example.com/v1/wordpress_conversion.go @@ -0,0 +1,22 @@ +/* +Copyright 2024 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 v1 + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! + +// Hub marks this type as a conversion hub. +func (*Wordpress) Hub() {} diff --git a/testdata/project-v4-multigroup/api/example.com/v1/wordpress_types.go b/testdata/project-v4-multigroup/api/example.com/v1/wordpress_types.go index ff5263a7f60..885556fc7f2 100644 --- a/testdata/project-v4-multigroup/api/example.com/v1/wordpress_types.go +++ b/testdata/project-v4-multigroup/api/example.com/v1/wordpress_types.go @@ -39,9 +39,9 @@ type WordpressStatus struct { } // +kubebuilder:object:root=true -// +kubebuilder:subresource:status // +kubebuilder:storageversion // +kubebuilder:conversion:hub +// +kubebuilder:subresource:status // Wordpress is the Schema for the wordpresses API. type Wordpress struct { diff --git a/testdata/project-v4-multigroup/api/example.com/v2/wordpress_conversion.go b/testdata/project-v4-multigroup/api/example.com/v2/wordpress_conversion.go new file mode 100644 index 00000000000..90a69ba3338 --- /dev/null +++ b/testdata/project-v4-multigroup/api/example.com/v2/wordpress_conversion.go @@ -0,0 +1,45 @@ +/* +Copyright 2024 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 v2 + +import ( + "log" + + "sigs.k8s.io/controller-runtime/pkg/conversion" + + examplecomv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1" +) + +// ConvertTo converts this Wordpress to the Hub version (v1). +func (src *Wordpress) ConvertTo(dstRaw conversion.Hub) error { + dst := dstRaw.(*examplecomv1.Wordpress) + log.Printf("Converting from %T to %T", dst.APIVersion, src.APIVersion) + + // TODO: Implement conversion logic from v2 to v1 + + return nil +} + +// ConvertFrom converts the Hub version (v1) to this Wordpress (v2). +func (dst *Wordpress) ConvertFrom(srcRaw conversion.Hub) error { + src := srcRaw.(*examplecomv1.Wordpress) + log.Printf("Converting from %T to %T", src.APIVersion, dst.APIVersion) + + // TODO: Implement conversion logic from v1 to v2 + + return nil +} diff --git a/testdata/project-v4-with-plugins/api/v1/wordpress_conversion.go b/testdata/project-v4-with-plugins/api/v1/wordpress_conversion.go new file mode 100644 index 00000000000..1c00f708fe9 --- /dev/null +++ b/testdata/project-v4-with-plugins/api/v1/wordpress_conversion.go @@ -0,0 +1,22 @@ +/* +Copyright 2024 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 v1 + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! + +// Hub marks this type as a conversion hub. +func (*Wordpress) Hub() {} diff --git a/testdata/project-v4-with-plugins/api/v1/wordpress_types.go b/testdata/project-v4-with-plugins/api/v1/wordpress_types.go index ff5263a7f60..885556fc7f2 100644 --- a/testdata/project-v4-with-plugins/api/v1/wordpress_types.go +++ b/testdata/project-v4-with-plugins/api/v1/wordpress_types.go @@ -39,9 +39,9 @@ type WordpressStatus struct { } // +kubebuilder:object:root=true -// +kubebuilder:subresource:status // +kubebuilder:storageversion // +kubebuilder:conversion:hub +// +kubebuilder:subresource:status // Wordpress is the Schema for the wordpresses API. type Wordpress struct { diff --git a/testdata/project-v4-with-plugins/api/v2/wordpress_conversion.go b/testdata/project-v4-with-plugins/api/v2/wordpress_conversion.go new file mode 100644 index 00000000000..1a85681b1c4 --- /dev/null +++ b/testdata/project-v4-with-plugins/api/v2/wordpress_conversion.go @@ -0,0 +1,45 @@ +/* +Copyright 2024 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 v2 + +import ( + "log" + + "sigs.k8s.io/controller-runtime/pkg/conversion" + + examplecomv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1" +) + +// ConvertTo converts this Wordpress to the Hub version (v1). +func (src *Wordpress) ConvertTo(dstRaw conversion.Hub) error { + dst := dstRaw.(*examplecomv1.Wordpress) + log.Printf("Converting from %T to %T", dst.APIVersion, src.APIVersion) + + // TODO: Implement conversion logic from v2 to v1 + + return nil +} + +// ConvertFrom converts the Hub version (v1) to this Wordpress (v2). +func (dst *Wordpress) ConvertFrom(srcRaw conversion.Hub) error { + src := srcRaw.(*examplecomv1.Wordpress) + log.Printf("Converting from %T to %T", src.APIVersion, dst.APIVersion) + + // TODO: Implement conversion logic from v1 to v2 + + return nil +} diff --git a/testdata/project-v4/api/v1/firstmate_conversion.go b/testdata/project-v4/api/v1/firstmate_conversion.go new file mode 100644 index 00000000000..ae43ee6da78 --- /dev/null +++ b/testdata/project-v4/api/v1/firstmate_conversion.go @@ -0,0 +1,22 @@ +/* +Copyright 2024 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 v1 + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! + +// Hub marks this type as a conversion hub. +func (*FirstMate) Hub() {} diff --git a/testdata/project-v4/api/v1/firstmate_types.go b/testdata/project-v4/api/v1/firstmate_types.go index 552dfc25988..74c6b1dc0d6 100644 --- a/testdata/project-v4/api/v1/firstmate_types.go +++ b/testdata/project-v4/api/v1/firstmate_types.go @@ -39,9 +39,9 @@ type FirstMateStatus struct { } // +kubebuilder:object:root=true -// +kubebuilder:subresource:status // +kubebuilder:storageversion // +kubebuilder:conversion:hub +// +kubebuilder:subresource:status // FirstMate is the Schema for the firstmates API. type FirstMate struct { diff --git a/testdata/project-v4/api/v2/firstmate_conversion.go b/testdata/project-v4/api/v2/firstmate_conversion.go new file mode 100644 index 00000000000..5e91dba4e57 --- /dev/null +++ b/testdata/project-v4/api/v2/firstmate_conversion.go @@ -0,0 +1,45 @@ +/* +Copyright 2024 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 v2 + +import ( + "log" + + "sigs.k8s.io/controller-runtime/pkg/conversion" + + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1" +) + +// ConvertTo converts this FirstMate to the Hub version (v1). +func (src *FirstMate) ConvertTo(dstRaw conversion.Hub) error { + dst := dstRaw.(*crewv1.FirstMate) + log.Printf("Converting from %T to %T", dst.APIVersion, src.APIVersion) + + // TODO: Implement conversion logic from v2 to v1 + + return nil +} + +// ConvertFrom converts the Hub version (v1) to this FirstMate (v2). +func (dst *FirstMate) ConvertFrom(srcRaw conversion.Hub) error { + src := srcRaw.(*crewv1.FirstMate) + log.Printf("Converting from %T to %T", src.APIVersion, dst.APIVersion) + + // TODO: Implement conversion logic from v1 to v2 + + return nil +}