Skip to content

Commit

Permalink
(go/v4): Add hub and spoke implementation for conversion webhook
Browse files Browse the repository at this point in the history
This PR introduces the initial implementation of the hub-and-spoke model for handling conversion webhooks. The goal is to streamline the conversion process by utilizing a central hub to speak a specific version of the same Group and Kind.

- **Single Spoke Support (A to B, Same Kind and Group):**
  The system will allow only one spoke version for conversions (i.e., converting from Version A to Version B within the same kind and group).

- **Future Expansion (List of GKV Spokes):**
  In the future, based on user feedback or demand, we can expand to support a list of **GKV spokes**, allowing for greater flexibility in conversions across different versions, kinds, and groups.

- **Advanced Case Handling (Manual Steps):**
  For more advanced cases, users can proceed without specifying a spoke. In this scenario, the conversion process will still occur without the spoke, enabling users to continue. However, they will be required to complete specific advanced steps manually.

Closes; kubernetes-sigs#2589
  • Loading branch information
camilamacedo86 committed Nov 10, 2024
1 parent 40db2b8 commit 9a11598
Show file tree
Hide file tree
Showing 39 changed files with 785 additions and 233 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,12 @@ 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("ConvertTo: converts this CronJob to the Hub version (v1);"+
"source: %s/%s and target: %s/%s", src.Namespace, src.Name, dst.Namespace, dst.Name)

sched := src.Spec.Schedule
scheduleParts := []string{"*", "*", "*", "*", "*"}
Expand Down Expand Up @@ -74,7 +80,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 +91,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 +100,11 @@ 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("ConvertFrom: converts the Hub version (v1) to this CronJob (v2);"+
"source: %s/%s and target: %s/%s", src.Namespace, src.Name, dst.Namespace, dst.Name)

schedParts := strings.Split(src.Spec.Schedule, " ")
if len(schedParts) != 5 {
Expand Down Expand Up @@ -133,5 +142,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 @@ -49,6 +49,7 @@ 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.
*/

// SetupCronJobWebhookWithManager registers the webhook for CronJob in the manager.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,4 +164,14 @@ 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 := &batchv1.CronJob{}
// Expect(obj.ConvertTo(convertedObj)).To(Succeed())
// Expect(convertedObj).ToNot(BeNil())
// })
})

})
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
Loading

0 comments on commit 9a11598

Please sign in to comment.