@@ -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,22 @@ 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+ parent .Required = append (parent .Required , name )
220+ slices .Sort (parent .Required )
221+ }
222+
223+ optionalRe := regexp .MustCompile (validationPrefix + "Optional>" )
224+ optionalMatches := optionalRe .FindAllStringSubmatch (jsonProps .Description , - 1 )
225+ for _ , _ = range optionalMatches {
226+ numValid += 1
227+ parent .Required = slices .DeleteFunc (parent .Required , func (s string ) bool { return s == name })
228+ slices .Sort (parent .Required )
229+ }
230+
213231 enumRe := regexp .MustCompile (validationPrefix + "Enum=([A-Za-z;]*)>" )
214232 enumMatches := enumRe .FindAllStringSubmatch (jsonProps .Description , 64 )
215233 for _ , enumMatch := range enumMatches {
@@ -246,36 +264,51 @@ func opconTweaks(channel string, name string, jsonProps apiextensionsv1.JSONSche
246264 jsonProps .Description = formatDescription (jsonProps .Description , channel , name )
247265
248266 if len (jsonProps .Properties ) > 0 {
249- jsonProps .Properties = opconTweaksMap (channel , jsonProps . Properties )
267+ jsonProps .Properties = opconTweaksMap (channel , & jsonProps )
250268 } else if jsonProps .Items != nil && jsonProps .Items .Schema != nil {
251- jsonProps .Items .Schema = opconTweaks (channel , name , * jsonProps .Items .Schema )
269+ jsonProps .Items .Schema = opconTweaks (channel , name , & jsonProps , * jsonProps .Items .Schema )
252270 }
253271
254272 return & jsonProps
255273}
256274
257275func 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*`
276+ startTagStandard := "<opcon:standard:description>"
277+ endTagStandard := "</opcon:standard:description>"
278+ if channel == ExperimentalChannel && strings .Contains (description , startTagStandard ) {
279+ regexPattern := `\n*` + regexp .QuoteMeta (startTagStandard ) + `(?s:(.*?))` + regexp .QuoteMeta (endTagStandard ) + `\n*`
280+ re := regexp .MustCompile (regexPattern )
281+ match := re .FindStringSubmatch (description )
282+ if len (match ) != 2 {
283+ log .Fatalf ("Invalid <opcon:standard:description> tag for %s" , name )
284+ }
285+ description = re .ReplaceAllString (description , "\n \n " )
286+ } else {
287+ description = strings .ReplaceAll (description , startTagStandard , "" )
288+ description = strings .ReplaceAll (description , endTagStandard , "" )
289+ }
290+
291+ startTagExperimental := "<opcon:experimental:description>"
292+ endTagExperimental := "</opcon:experimental:description>"
293+ if channel == StandardChannel && strings .Contains (description , startTagExperimental ) {
294+ regexPattern := `\n*` + regexp .QuoteMeta (startTagExperimental ) + `(?s:(.*?))` + regexp .QuoteMeta (endTagExperimental ) + `\n*`
262295 re := regexp .MustCompile (regexPattern )
263296 match := re .FindStringSubmatch (description )
264297 if len (match ) != 2 {
265298 log .Fatalf ("Invalid <opcon:experimental:description> tag for %s" , name )
266299 }
267300 description = re .ReplaceAllString (description , "\n \n " )
268301 } else {
269- description = strings .ReplaceAll (description , startTag , "" )
270- description = strings .ReplaceAll (description , endTag , "" )
302+ description = strings .ReplaceAll (description , startTagExperimental , "" )
303+ description = strings .ReplaceAll (description , endTagExperimental , "" )
271304 }
272305
273306 // Comments within "opcon:util:excludeFromCRD" tag are not included in the generated CRD and all trailing \n operators before
274307 // 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*`
308+ startTagExclude : = "<opcon:util:excludeFromCRD>"
309+ endTagExclude : = "</opcon:util:excludeFromCRD>"
310+ if strings .Contains (description , startTagExclude ) {
311+ regexPattern := `\n*` + regexp .QuoteMeta (startTagExclude ) + `(?s:(.*?))` + regexp .QuoteMeta (endTagExclude ) + `\n*`
279312 re := regexp .MustCompile (regexPattern )
280313 match := re .FindStringSubmatch (description )
281314 if len (match ) != 2 {
0 commit comments