@@ -23,6 +23,7 @@ import (
2323 "log"
2424 "os"
2525 "regexp"
26+ "slices"
2627 "strings"
2728
2829 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@@ -136,7 +137,7 @@ func runGenerator(args ...string) {
136137 if channel == StandardChannel && strings .Contains (version .Name , "alpha" ) {
137138 channelCrd .Spec .Versions [i ].Served = false
138139 }
139- version .Schema .OpenAPIV3Schema .Properties = opconTweaksMap (channel , version .Schema .OpenAPIV3Schema . Properties )
140+ version .Schema .OpenAPIV3Schema .Properties = opconTweaksMap (channel , version .Schema .OpenAPIV3Schema )
140141 }
141142
142143 conv , err := crd .AsVersion (* channelCrd , apiextensionsv1 .SchemeGroupVersion )
@@ -179,10 +180,11 @@ func runGenerator(args ...string) {
179180 }
180181}
181182
182- func opconTweaksMap (channel string , props map [string ]apiextensionsv1.JSONSchemaProps ) map [string ]apiextensionsv1.JSONSchemaProps {
183+ func opconTweaksMap (channel string , obj * apiextensionsv1.JSONSchemaProps ) map [string ]apiextensionsv1.JSONSchemaProps {
184+ props := obj .Properties
183185 for name := range props {
184186 jsonProps := props [name ]
185- p := opconTweaks (channel , name , jsonProps )
187+ p := opconTweaks (channel , name , obj , jsonProps )
186188 if p == nil {
187189 delete (props , name )
188190 } else {
@@ -194,7 +196,7 @@ func opconTweaksMap(channel string, props map[string]apiextensionsv1.JSONSchemaP
194196
195197// Custom Opcon API Tweaks for tags prefixed with `<opcon:` that get past
196198// the limitations of Kubebuilder annotations.
197- func opconTweaks (channel string , name string , jsonProps apiextensionsv1.JSONSchemaProps ) * apiextensionsv1.JSONSchemaProps {
199+ func opconTweaks (channel string , name string , parent * apiextensionsv1. JSONSchemaProps , jsonProps apiextensionsv1.JSONSchemaProps ) * apiextensionsv1.JSONSchemaProps {
198200 if channel == StandardChannel {
199201 if strings .Contains (jsonProps .Description , "<opcon:experimental>" ) {
200202 return nil
@@ -210,6 +212,24 @@ func opconTweaks(channel string, name string, jsonProps apiextensionsv1.JSONSche
210212 numExpressions := strings .Count (jsonProps .Description , validationPrefix )
211213 numValid := 0
212214 if numExpressions > 0 {
215+ requiredRe := regexp .MustCompile (validationPrefix + "Required>" )
216+ requiredMatches := requiredRe .FindAllStringSubmatch (jsonProps .Description , - 1 )
217+ for _ , _ = range requiredMatches {
218+ numValid += 1
219+ if ! slices .Contains (parent .Required , name ) {
220+ parent .Required = append (parent .Required , name )
221+ }
222+ slices .Sort (parent .Required )
223+ }
224+
225+ optionalRe := regexp .MustCompile (validationPrefix + "Optional>" )
226+ optionalMatches := optionalRe .FindAllStringSubmatch (jsonProps .Description , - 1 )
227+ for _ , _ = range optionalMatches {
228+ numValid += 1
229+ parent .Required = slices .DeleteFunc (parent .Required , func (s string ) bool { return s == name })
230+ slices .Sort (parent .Required )
231+ }
232+
213233 enumRe := regexp .MustCompile (validationPrefix + "Enum=([A-Za-z;]*)>" )
214234 enumMatches := enumRe .FindAllStringSubmatch (jsonProps .Description , 64 )
215235 for _ , enumMatch := range enumMatches {
@@ -246,36 +266,51 @@ func opconTweaks(channel string, name string, jsonProps apiextensionsv1.JSONSche
246266 jsonProps .Description = formatDescription (jsonProps .Description , channel , name )
247267
248268 if len (jsonProps .Properties ) > 0 {
249- jsonProps .Properties = opconTweaksMap (channel , jsonProps . Properties )
269+ jsonProps .Properties = opconTweaksMap (channel , & jsonProps )
250270 } else if jsonProps .Items != nil && jsonProps .Items .Schema != nil {
251- jsonProps .Items .Schema = opconTweaks (channel , name , * jsonProps .Items .Schema )
271+ jsonProps .Items .Schema = opconTweaks (channel , name , & jsonProps , * jsonProps .Items .Schema )
252272 }
253273
254274 return & jsonProps
255275}
256276
257277func formatDescription (description string , channel string , name string ) string {
258- startTag := "<opcon:experimental:description>"
259- endTag := "</opcon:experimental:description>"
260- if channel == StandardChannel && strings .Contains (description , startTag ) {
261- regexPattern := `\n*` + regexp .QuoteMeta (startTag ) + `(?s:(.*?))` + regexp .QuoteMeta (endTag ) + `\n*`
278+ startTagStandard := "<opcon:standard:description>"
279+ endTagStandard := "</opcon:standard:description>"
280+ if channel == ExperimentalChannel && strings .Contains (description , startTagStandard ) {
281+ regexPattern := `\n*` + regexp .QuoteMeta (startTagStandard ) + `(?s:(.*?))` + regexp .QuoteMeta (endTagStandard ) + `\n*`
282+ re := regexp .MustCompile (regexPattern )
283+ match := re .FindStringSubmatch (description )
284+ if len (match ) != 2 {
285+ log .Fatalf ("Invalid <opcon:standard:description> tag for %s" , name )
286+ }
287+ description = re .ReplaceAllString (description , "\n \n " )
288+ } else {
289+ description = strings .ReplaceAll (description , startTagStandard , "" )
290+ description = strings .ReplaceAll (description , endTagStandard , "" )
291+ }
292+
293+ startTagExperimental := "<opcon:experimental:description>"
294+ endTagExperimental := "</opcon:experimental:description>"
295+ if channel == StandardChannel && strings .Contains (description , startTagExperimental ) {
296+ regexPattern := `\n*` + regexp .QuoteMeta (startTagExperimental ) + `(?s:(.*?))` + regexp .QuoteMeta (endTagExperimental ) + `\n*`
262297 re := regexp .MustCompile (regexPattern )
263298 match := re .FindStringSubmatch (description )
264299 if len (match ) != 2 {
265300 log .Fatalf ("Invalid <opcon:experimental:description> tag for %s" , name )
266301 }
267302 description = re .ReplaceAllString (description , "\n \n " )
268303 } else {
269- description = strings .ReplaceAll (description , startTag , "" )
270- description = strings .ReplaceAll (description , endTag , "" )
304+ description = strings .ReplaceAll (description , startTagExperimental , "" )
305+ description = strings .ReplaceAll (description , endTagExperimental , "" )
271306 }
272307
273308 // Comments within "opcon:util:excludeFromCRD" tag are not included in the generated CRD and all trailing \n operators before
274309 // and after the tags are removed and replaced with three \n operators.
275- startTag = "<opcon:util:excludeFromCRD>"
276- endTag = "</opcon:util:excludeFromCRD>"
277- if strings .Contains (description , startTag ) {
278- regexPattern := `\n*` + regexp .QuoteMeta (startTag ) + `(?s:(.*?))` + regexp .QuoteMeta (endTag ) + `\n*`
310+ startTagExclude : = "<opcon:util:excludeFromCRD>"
311+ endTagExclude : = "</opcon:util:excludeFromCRD>"
312+ if strings .Contains (description , startTagExclude ) {
313+ regexPattern := `\n*` + regexp .QuoteMeta (startTagExclude ) + `(?s:(.*?))` + regexp .QuoteMeta (endTagExclude ) + `\n*`
279314 re := regexp .MustCompile (regexPattern )
280315 match := re .FindStringSubmatch (description )
281316 if len (match ) != 2 {
0 commit comments