Skip to content

Commit dd213e6

Browse files
(kustomize/v2, go/v4): Add Hub and Spoke for conversion webhooks
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; #2589
1 parent cc242e6 commit dd213e6

File tree

12 files changed

+32
-78
lines changed

12 files changed

+32
-78
lines changed

docs/book/src/multiversion-tutorial/testdata/project/PROJECT

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ resources:
2020
webhooks:
2121
conversion: true
2222
defaulting: true
23-
spoke:
24-
- v2
25-
- v2
23+
spoke: v2
2624
validation: true
2725
webhookVersion: v1
2826
- api:

pkg/cli/alpha/internal/generate.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -369,8 +369,8 @@ func getWebhookResourceFlags(resource resource.Resource) []string {
369369
}
370370
if resource.HasConversionWebhook() {
371371
args = append(args, "--conversion")
372-
for _, spoke := range resource.Webhooks.Spoke {
373-
args = append(args, "--spoke", spoke)
372+
if resource.Webhooks.Spoke != "" {
373+
args = append(args, "--spoke", resource.Webhooks.Spoke)
374374
}
375375
}
376376
return args

pkg/model/resource/webhooks.go

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ type Webhooks struct {
3434
// Conversion specifies if a conversion webhook is associated to the resource.
3535
Conversion bool `json:"conversion,omitempty"`
3636

37-
Spoke []string `json:"spoke,omitempty"`
37+
Spoke string `json:"spoke,omitempty"`
3838
}
3939

4040
// Validate checks that the Webhooks is valid.
@@ -80,7 +80,7 @@ func (webhooks *Webhooks) Update(other *Webhooks) error {
8080
webhooks.Conversion = webhooks.Conversion || other.Conversion
8181

8282
// Update Spoke.
83-
webhooks.Spoke = append(webhooks.Spoke, other.Spoke...)
83+
webhooks.Spoke = other.Spoke
8484

8585
return nil
8686
}
@@ -89,23 +89,3 @@ func (webhooks *Webhooks) Update(other *Webhooks) error {
8989
func (webhooks Webhooks) IsEmpty() bool {
9090
return webhooks.WebhookVersion == "" && !webhooks.Defaulting && !webhooks.Validation && !webhooks.Conversion
9191
}
92-
93-
// HasSpokeVersion returns true if the spoke version is present in the list of spoke versions.
94-
func (webhooks Webhooks) HasSpokeVersion(version string) bool {
95-
for _, v := range webhooks.Spoke {
96-
if v == version {
97-
return true
98-
}
99-
}
100-
return false
101-
}
102-
103-
// HasAnySpokeVersionFrom returns true if any spoke versions are present in the list of spoke versions.
104-
func (webhooks Webhooks) HasAnySpokeVersionFrom(values []string) bool {
105-
for _, v := range values {
106-
if webhooks.HasSpokeVersion(v) {
107-
return true
108-
}
109-
}
110-
return false
111-
}

pkg/model/resource/webhooks_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,14 @@ var _ = Describe("Webhooks", func() {
5252
Defaulting: true,
5353
Validation: true,
5454
Conversion: true,
55-
Spoke: []string{"v2"},
55+
Spoke: "v2",
5656
}
5757
Expect(webhook.Update(nil)).To(Succeed())
5858
Expect(webhook.WebhookVersion).To(Equal(v1))
5959
Expect(webhook.Defaulting).To(BeTrue())
6060
Expect(webhook.Validation).To(BeTrue())
6161
Expect(webhook.Conversion).To(BeTrue())
62-
Expect(webhook.Spoke).To(Equal([]string{"v2"}))
62+
Expect(webhook.Spoke).To(Equal("v2"))
6363
})
6464

6565
Context("webhooks version", func() {

pkg/plugins/golang/options.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ type Options struct {
7474
DoConversion bool
7575

7676
// Spoke version for conversion webhook
77-
Spoke []string
77+
Spoke string
7878
}
7979

8080
// UpdateResource updates the provided resource with the options

pkg/plugins/golang/v4/scaffolds/internal/templates/api/spoke.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,16 @@ type Spoke struct {
3232
machinery.BoilerplateMixin
3333
machinery.ResourceMixin
3434

35-
Force bool
36-
SpokeVersion string
35+
Force bool
3736
}
3837

3938
// SetTemplateDefaults implements file.Template
4039
func (f *Spoke) SetTemplateDefaults() error {
4140
if f.Path == "" {
4241
if f.MultiGroup && f.Resource.Group != "" {
43-
f.Path = filepath.Join("api", "%[group]", f.SpokeVersion, "%[kind]_conversion.go")
42+
f.Path = filepath.Join("api", "%[group]", f.Resource.Webhooks.Spoke, "%[kind]_conversion.go")
4443
} else {
45-
f.Path = filepath.Join("api", f.SpokeVersion, "%[kind]_conversion.go")
44+
f.Path = filepath.Join("api", f.Resource.Webhooks.Spoke, "%[kind]_conversion.go")
4645
}
4746
}
4847

@@ -63,7 +62,7 @@ func (f *Spoke) SetTemplateDefaults() error {
6362
// nolint:lll
6463
const spokeTemplate = `{{ .Boilerplate }}
6564
66-
package {{ .SpokeVersion }}
65+
package {{ .Resource.Webhooks.Spoke }}
6766
6867
import (
6968
"log"
@@ -78,18 +77,18 @@ func (src *{{ .Resource.Kind }}) ConvertTo(dstRaw conversion.Hub) error {
7877
log.Printf("ConvertTo: converts this {{ .Resource.Kind }} to the Hub version ({{ .Resource.Version }});" +
7978
"source: %s/%s and target: %s/%s", src.Namespace, src.Name, dst.Namespace, dst.Name)
8079
81-
// TODO(user): Implement conversion logic from {{ .SpokeVersion }} to {{ .Resource.Version }}
80+
// TODO(user): Implement conversion logic from {{ .Resource.Webhooks.Spoke }} to {{ .Resource.Version }}
8281
8382
return nil
8483
}
8584
86-
// ConvertFrom converts the Hub version ({{ .Resource.Version }}) to this {{ .Resource.Kind }} ({{ .SpokeVersion}}).
85+
// ConvertFrom converts the Hub version ({{ .Resource.Version }}) to this {{ .Resource.Kind }} ({{ .Resource.Webhooks.Spoke }}).
8786
func (dst *{{ .Resource.Kind }}) ConvertFrom(srcRaw conversion.Hub) error {
8887
src := srcRaw.(*{{ .Resource.ImportAlias }}.{{ .Resource.Kind }})
89-
log.Printf("ConvertFrom: converts the Hub version ({{ .Resource.Version }}) to this {{ .Resource.Kind }} ({{ .SpokeVersion}});" +
88+
log.Printf("ConvertFrom: converts the Hub version ({{ .Resource.Version }}) to this {{ .Resource.Kind }} ({{ .Resource.Webhooks.Spoke }});" +
9089
"source: %s/%s and target: %s/%s", src.Namespace, src.Name, dst.Namespace, dst.Name)
9190
92-
// TODO(user): Implement conversion logic from {{ .Resource.Version }} to {{ .SpokeVersion }}
91+
// TODO(user): Implement conversion logic from {{ .Resource.Version }} to {{ .Resource.Webhooks.Spoke }}
9392
9493
return nil
9594
}

pkg/plugins/golang/v4/scaffolds/webhook.go

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -124,18 +124,10 @@ func (s *webhookScaffolder) Scaffold() error {
124124

125125
if err := scaffold.Execute(
126126
&api.Hub{Force: s.force},
127+
&api.Spoke{Force: s.force},
127128
); err != nil {
128129
return err
129130
}
130-
131-
// Create the spoke version conversion file for each spoke version
132-
for _, spokeVersion := range s.resource.Webhooks.Spoke {
133-
if err := scaffold.Execute(
134-
&api.Spoke{Force: s.force, SpokeVersion: spokeVersion},
135-
); err != nil {
136-
return err
137-
}
138-
}
139131
log.Println(`Webhook server has been set up for you.
140132
You need to implement the conversion.Hub and conversion.Convertible interfaces for your CRD types.`)
141133
}

pkg/plugins/golang/v4/webhook.go

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ validating and/or conversion webhooks.
6666
6767
# Create conversion webhook for Group: ship, Version: v1beta1
6868
# and Kind: Frigate
69-
%[1]s create webhook --group ship --version v1beta1 --kind Frigate --conversion --spoke v1,v2,v3
69+
%[1]s create webhook --group ship --version v1beta1 --kind Frigate --conversion --spoke v1
7070
`, cliMeta.CommandName)
7171
}
7272

@@ -84,9 +84,9 @@ func (p *createWebhookSubcommand) BindFlags(fs *pflag.FlagSet) {
8484
fs.BoolVar(&p.options.DoConversion, "conversion", false,
8585
"if set, scaffold the conversion webhook")
8686

87-
fs.StringSliceVar(&p.options.Spoke, "spoke",
88-
[]string{},
89-
"comma-separated list of spoke versions for conversion webhook (i.e. --spoke v1,v2,v3)")
87+
fs.StringVar(&p.options.Spoke, "spoke",
88+
"",
89+
"if set, scaffold the spoke implementation (i.e. --spoke v1)")
9090

9191
// TODO: remove for go/v5
9292
fs.BoolVar(&p.isLegacyPath, "legacy", false,
@@ -149,21 +149,12 @@ func (p *createWebhookSubcommand) InjectResource(res *resource.Resource) error {
149149
}
150150

151151
if p.options.DoConversion {
152-
if res.HasConversionWebhook() &&
153-
len(p.options.Spoke) > 0 &&
154-
res.Webhooks.HasAnySpokeVersionFrom(p.options.Spoke) {
155-
return fmt.Errorf("conversion webhook already exists with one or more spoke versions informed")
156-
}
157-
158-
// Check if all spoke versions informed are api versions which exist in the resource
159-
for _, spokeVersion := range p.options.Spoke {
160-
spokeGKV := res.GVK
161-
spokeGKV.Version = spokeVersion
162-
_, err := p.config.GetResource(spokeGKV)
163-
if err != nil {
164-
return fmt.Errorf("resource does not have a version %s",
165-
spokeVersion)
166-
}
152+
spokeGKV := res.GVK
153+
spokeGKV.Version = p.options.Spoke
154+
_, err := p.config.GetResource(spokeGKV)
155+
if err != nil {
156+
return fmt.Errorf("resource does not have a version %s",
157+
p.options.Spoke)
167158
}
168159
}
169160
return nil

test/e2e/v4/generate_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -421,12 +421,12 @@ func scaffoldConversionWebhook(kbc *utils.TestContext) {
421421

422422
err = pluginutil.ReplaceInFile(filepath.Join(kbc.Dir, "api/v2/conversiontest_conversion.go"),
423423
"// TODO(user): Implement conversion logic from v1 to v2",
424-
`dst.Spec.Size = src.Spec.Replicas`)
424+
`src.Spec.Size = dst.Spec.Replicas`)
425425
Expect(err).NotTo(HaveOccurred(), "failed to implement conversion logic from v1 to v2")
426426

427427
err = pluginutil.ReplaceInFile(filepath.Join(kbc.Dir, "api/v2/conversiontest_conversion.go"),
428428
"// TODO(user): Implement conversion logic from v2 to v1",
429-
`dst.Spec.Replicas = src.Spec.Size`)
429+
`src.Spec.Replicas = dst.Spec.Size`)
430430
Expect(err).NotTo(HaveOccurred(), "failed to implement conversion logic from v2 to v1")
431431
}
432432

testdata/project-v4-multigroup/PROJECT

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,7 @@ resources:
183183
version: v1
184184
webhooks:
185185
conversion: true
186-
spoke:
187-
- v2
188-
- v2
186+
spoke: v2
189187
webhookVersion: v1
190188
- api:
191189
crdVersion: v1

0 commit comments

Comments
 (0)