@@ -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-
216212const (
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).
222222func 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
299306func 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
0 commit comments