Skip to content

Commit

Permalink
Add Hub and Spoke for conversion webhooks
Browse files Browse the repository at this point in the history
Introduces the required code to add the code implementation for
the interfaces.
  • Loading branch information
camilamacedo86 committed Nov 1, 2024
1 parent c331b70 commit 233645d
Show file tree
Hide file tree
Showing 39 changed files with 591 additions and 192 deletions.
12 changes: 10 additions & 2 deletions docs/book/src/multiversion-tutorial/conversion.md
Original file line number Diff line number Diff line change
@@ -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...
Expand Down
3 changes: 2 additions & 1 deletion docs/book/src/multiversion-tutorial/testdata/project/PROJECT
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ resources:
path: tutorial.kubebuilder.io/project/api/v1
version: v1
webhooks:
conversion: true
defaulting: true
spoke: v2
validation: true
webhookVersion: v1
- api:
Expand All @@ -30,7 +32,6 @@ resources:
path: tutorial.kubebuilder.io/project/api/v2
version: v2
webhooks:
conversion: true
defaulting: true
validation: true
webhookVersion: v1
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ type CronJobStatus struct {
*/

// +kubebuilder:object:root=true
// +kubebuilder:storageversion
// +kubebuilder:conversion:hub
// +kubebuilder:subresource:status
// +versionName=v1
// +kubebuilder:storageversion
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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{"*", "*", "*", "*", "*"}
Expand Down Expand Up @@ -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
Expand All @@ -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
}

Expand All @@ -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 {
Expand Down Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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())
// })
})

})
9 changes: 0 additions & 9 deletions docs/book/src/multiversion-tutorial/webhooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
Expand Down
1 change: 1 addition & 0 deletions docs/book/src/reference/project-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ Now let's check its layout fields definition:
| `resources.core` | It is `true` when the group used is from Kubernetes API and the API resource is not defined on the project. |
| `resources.external` | It is `true` when the flag `--external-api-path` was used to generated the scaffold for an [External Type][external-type]. |
| `resources.webhooks` | Store the webhooks data when the sub-command `create webhook` is used. |
| `resources.webhooks.spoke` | Store the API version that will act as the Spoke with the designated Hub version for conversion webhooks. |
| `resources.webhooks.webhookVersion` | The Kubernetes API version (`apiVersion`) used to scaffold the webhook resource. |
| `resources.webhooks.conversion` | It is `true` when the webhook was scaffold with the `--conversion` flag which means that is a conversion webhook. |
| `resources.webhooks.defaulting` | It is `true` when the webhook was scaffold with the `--defaulting` flag which means that is a defaulting webhook. |
Expand Down
108 changes: 78 additions & 30 deletions hack/docs/internal/multiversion-tutorial/generate_multiversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ limitations under the License.
package multiversion

import (
"os"
"os/exec"
"path/filepath"

Expand Down Expand Up @@ -57,14 +56,23 @@ 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",
"--version", "v2",
"--kind", "CronJob",
"--defaulting",
"--programmatic-validation",
"--conversion",
)
hackutils.CheckError("Creating defaulting and validation webhook for v2", err)
}
Expand All @@ -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`
Expand All @@ -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() {
Expand Down
Loading

0 comments on commit 233645d

Please sign in to comment.