Skip to content

Commit e0806cf

Browse files
committed
review updates
Signed-off-by: grokspawn <jordan@nimblewidget.com>
1 parent 1e671c5 commit e0806cf

File tree

2 files changed

+38
-26
lines changed

2 files changed

+38
-26
lines changed

hack/tools/crd-generator/main.go

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ func runGenerator(args ...string) {
137137
if channel == StandardChannel && strings.Contains(version.Name, "alpha") {
138138
channelCrd.Spec.Versions[i].Served = false
139139
}
140-
version.Schema.OpenAPIV3Schema.Properties, version.Schema.OpenAPIV3Schema.Required = opconTweaksMap(channel, version.Schema.OpenAPIV3Schema.Properties, version.Schema.OpenAPIV3Schema.Required)
140+
channelCrd.Spec.Versions[i].Schema.OpenAPIV3Schema.Properties = opconTweaksMap(channel, channelCrd.Spec.Versions[i].Schema.OpenAPIV3Schema)
141141
}
142142

143143
conv, err := crd.AsVersion(*channelCrd, apiextensionsv1.SchemeGroupVersion)
@@ -180,10 +180,11 @@ func runGenerator(args ...string) {
180180
}
181181
}
182182

183-
// Apply Opcon specific tweaks to all properties in a map, and return a list of required fields according to opcon tags.
184-
// For opcon validation optional/required tags, the required list is updated accordingly.
185-
func opconTweaksMap(channel string, props map[string]apiextensionsv1.JSONSchemaProps, existingRequired []string) (map[string]apiextensionsv1.JSONSchemaProps, []string) {
186-
newRequired := slices.Clone(existingRequired)
183+
// Apply Opcon specific tweaks to all properties in a map, and update the parent schema's required list according to opcon tags.
184+
// For opcon validation optional/required tags, the parent schema's required list is mutated directly.
185+
// TODO: if we need to support other conditions from opconTweaks, it will likely be preferable to convey the parent schema to facilitate direct alteration.
186+
func opconTweaksMap(channel string, parentSchema *apiextensionsv1.JSONSchemaProps) map[string]apiextensionsv1.JSONSchemaProps {
187+
props := parentSchema.Properties
187188

188189
for name := range props {
189190
jsonProps := props[name]
@@ -195,30 +196,29 @@ func opconTweaksMap(channel string, props map[string]apiextensionsv1.JSONSchemaP
195196
// Update required list based on tag
196197
switch reqStatus {
197198
case statusRequired:
198-
if !slices.Contains(newRequired, name) {
199-
newRequired = append(newRequired, name)
199+
if !slices.Contains(parentSchema.Required, name) {
200+
parentSchema.Required = append(parentSchema.Required, name)
200201
}
201202
case statusOptional:
202-
newRequired = slices.DeleteFunc(newRequired, func(s string) bool { return s == name })
203+
parentSchema.Required = slices.DeleteFunc(parentSchema.Required, func(s string) bool { return s == name })
203204
default:
204205
// "" (unspecified) means keep existing status
205206
}
206207
}
207208
}
208-
return props, newRequired
209+
return props
209210
}
210211

211-
// Custom Opcon API Tweaks for tags prefixed with `<opcon:` that get past
212-
// the limitations of Kubebuilder annotations.
213-
// Returns the modified schema and a string indicating required status where indicated by opcon tags:
214-
// "required", "optional", or "" (no decision -- preserve any non-opcon required status).
215-
216212
const (
217213
statusRequired = "required"
218214
statusOptional = "optional"
219215
statusNoOpinion = ""
220216
)
221217

218+
// Custom Opcon API Tweaks for tags prefixed with `<opcon:` that get past
219+
// the limitations of Kubebuilder annotations.
220+
// Returns the modified schema and a string indicating required status where indicated by opcon tags:
221+
// "required", "optional", or "" (no decision -- preserve any non-opcon required status).
222222
func opconTweaks(channel string, name string, jsonProps apiextensionsv1.JSONSchemaProps) (*apiextensionsv1.JSONSchemaProps, string) {
223223
requiredStatus := statusNoOpinion
224224

@@ -266,6 +266,8 @@ func opconTweaks(channel string, name string, jsonProps apiextensionsv1.JSONSche
266266
}
267267
optReqRe := regexp.MustCompile(validationPrefix + "(Optional|Required)>")
268268
optReqMatches := optReqRe.FindAllStringSubmatch(jsonProps.Description, 64)
269+
hasOptional := false
270+
hasRequired := false
269271
for _, optReqMatch := range optReqMatches {
270272
if len(optReqMatch) != 2 {
271273
log.Fatalf("Invalid %s Optional/Required tag for %s", validationPrefix, name)
@@ -274,11 +276,16 @@ func opconTweaks(channel string, name string, jsonProps apiextensionsv1.JSONSche
274276
numValid++
275277
switch optReqMatch[1] {
276278
case "Optional":
279+
hasOptional = true
277280
requiredStatus = statusOptional
278281
case "Required":
282+
hasRequired = true
279283
requiredStatus = statusRequired
280284
}
281285
}
286+
if hasOptional && hasRequired {
287+
log.Fatalf("Field %s has both Optional and Required validation tags for channel %s", name, channel)
288+
}
282289
}
283290

284291
if numValid < numExpressions {
@@ -288,7 +295,7 @@ func opconTweaks(channel string, name string, jsonProps apiextensionsv1.JSONSche
288295
jsonProps.Description = formatDescription(jsonProps.Description, channel, name)
289296

290297
if len(jsonProps.Properties) > 0 {
291-
jsonProps.Properties, jsonProps.Required = opconTweaksMap(channel, jsonProps.Properties, jsonProps.Required)
298+
jsonProps.Properties = opconTweaksMap(channel, &jsonProps)
292299
} else if jsonProps.Items != nil && jsonProps.Items.Schema != nil {
293300
jsonProps.Items.Schema, _ = opconTweaks(channel, name, *jsonProps.Items.Schema)
294301
}
@@ -299,24 +306,25 @@ func opconTweaks(channel string, name string, jsonProps apiextensionsv1.JSONSche
299306
func formatDescription(description string, channel string, name string) string {
300307
tagset := []struct {
301308
channel string
302-
start string
303-
end string
309+
tag string
304310
}{
305-
{channel: ExperimentalChannel, start: "<opcon:standard:description>", end: "</opcon:standard:description>"},
306-
{channel: StandardChannel, start: "<opcon:experimental:description>", end: "</opcon:experimental:description>"},
311+
{channel: ExperimentalChannel, tag: "opcon:standard:description"},
312+
{channel: StandardChannel, tag: "opcon:experimental:description"},
307313
}
308314
for _, ts := range tagset {
309-
if channel == ts.channel && strings.Contains(description, ts.start) {
310-
regexPattern := `\n*` + regexp.QuoteMeta(ts.start) + `(?s:(.*?))` + regexp.QuoteMeta(ts.end) + `\n*`
315+
startTag := fmt.Sprintf("<%s>", ts.tag)
316+
endTag := fmt.Sprintf("</%s>", ts.tag)
317+
if channel == ts.channel && strings.Contains(description, ts.tag) {
318+
regexPattern := `\n*` + regexp.QuoteMeta(startTag) + `(?s:(.*?))` + regexp.QuoteMeta(endTag) + `\n*`
311319
re := regexp.MustCompile(regexPattern)
312320
match := re.FindStringSubmatch(description)
313321
if len(match) != 2 {
314-
log.Fatalf("Invalid <opcon:experimental:description> tag for %s", name)
322+
log.Fatalf("Invalid %s tag for %s", startTag, name)
315323
}
316324
description = re.ReplaceAllString(description, "\n\n")
317325
} else {
318-
description = strings.ReplaceAll(description, ts.start, "")
319-
description = strings.ReplaceAll(description, ts.end, "")
326+
description = strings.ReplaceAll(description, startTag, "")
327+
description = strings.ReplaceAll(description, endTag, "")
320328
}
321329
}
322330

hack/tools/crd-generator/main_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,8 +312,12 @@ func TestOpconTweaksMapRequiredList(t *testing.T) {
312312

313313
for _, tt := range tests {
314314
t.Run(tt.name, func(t *testing.T) {
315-
_, required := opconTweaksMap(tt.channel, tt.props, tt.existingRequired)
316-
require.ElementsMatch(t, tt.expectedRequired, required)
315+
parentSchema := &apiextensionsv1.JSONSchemaProps{
316+
Properties: tt.props,
317+
Required: tt.existingRequired,
318+
}
319+
opconTweaksMap(tt.channel, parentSchema)
320+
require.ElementsMatch(t, tt.expectedRequired, parentSchema.Required)
317321
})
318322
}
319323
}

0 commit comments

Comments
 (0)