k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/generators/openapi.go (about)

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package generators
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/json"
    22  	"fmt"
    23  	"io"
    24  	"path"
    25  	"reflect"
    26  	"regexp"
    27  	"sort"
    28  	"strings"
    29  
    30  	"k8s.io/gengo/v2"
    31  	"k8s.io/gengo/v2/generator"
    32  	"k8s.io/gengo/v2/namer"
    33  	"k8s.io/gengo/v2/types"
    34  	openapi "k8s.io/kube-openapi/pkg/common"
    35  	"k8s.io/kube-openapi/pkg/validation/spec"
    36  
    37  	"k8s.io/klog/v2"
    38  )
    39  
    40  // This is the comment tag that carries parameters for open API generation.
    41  const tagName = "k8s:openapi-gen"
    42  const markerPrefix = "+k8s:validation:"
    43  const tagOptional = "optional"
    44  const tagRequired = "required"
    45  const tagDefault = "default"
    46  
    47  // Known values for the tag.
    48  const (
    49  	tagValueTrue  = "true"
    50  	tagValueFalse = "false"
    51  )
    52  
    53  // Used for temporary validation of patch struct tags.
    54  // TODO: Remove patch struct tag validation because they we are now consuming OpenAPI on server.
    55  var tempPatchTags = [...]string{
    56  	"patchMergeKey",
    57  	"patchStrategy",
    58  }
    59  
    60  func getOpenAPITagValue(comments []string) []string {
    61  	return gengo.ExtractCommentTags("+", comments)[tagName]
    62  }
    63  
    64  func getSingleTagsValue(comments []string, tag string) (string, error) {
    65  	tags, ok := gengo.ExtractCommentTags("+", comments)[tag]
    66  	if !ok || len(tags) == 0 {
    67  		return "", nil
    68  	}
    69  	if len(tags) > 1 {
    70  		return "", fmt.Errorf("multiple values are not allowed for tag %s", tag)
    71  	}
    72  	return tags[0], nil
    73  }
    74  
    75  func hasOpenAPITagValue(comments []string, value string) bool {
    76  	tagValues := getOpenAPITagValue(comments)
    77  	for _, val := range tagValues {
    78  		if val == value {
    79  			return true
    80  		}
    81  	}
    82  	return false
    83  }
    84  
    85  // isOptional returns error if the member has +optional and +required in
    86  // its comments. If +optional is present it returns true. If +required is present
    87  // it returns false. Otherwise, it returns true if `omitempty` JSON tag is present
    88  func isOptional(m *types.Member) (bool, error) {
    89  	hasOptionalCommentTag := gengo.ExtractCommentTags(
    90  		"+", m.CommentLines)[tagOptional] != nil
    91  	hasRequiredCommentTag := gengo.ExtractCommentTags(
    92  		"+", m.CommentLines)[tagRequired] != nil
    93  	if hasOptionalCommentTag && hasRequiredCommentTag {
    94  		return false, fmt.Errorf("member %s cannot be both optional and required", m.Name)
    95  	} else if hasRequiredCommentTag {
    96  		return false, nil
    97  	} else if hasOptionalCommentTag {
    98  		return true, nil
    99  	}
   100  
   101  	// If neither +optional nor +required is present in the comments,
   102  	// infer optional from the json tags.
   103  	return strings.Contains(reflect.StructTag(m.Tags).Get("json"), "omitempty"), nil
   104  }
   105  
   106  func apiTypeFilterFunc(c *generator.Context, t *types.Type) bool {
   107  	// There is a conflict between this codegen and codecgen, we should avoid types generated for codecgen
   108  	if strings.HasPrefix(t.Name.Name, "codecSelfer") {
   109  		return false
   110  	}
   111  	pkg := c.Universe.Package(t.Name.Package)
   112  	if hasOpenAPITagValue(pkg.Comments, tagValueTrue) {
   113  		return !hasOpenAPITagValue(t.CommentLines, tagValueFalse)
   114  	}
   115  	if hasOpenAPITagValue(t.CommentLines, tagValueTrue) {
   116  		return true
   117  	}
   118  	return false
   119  }
   120  
   121  const (
   122  	specPackagePath          = "k8s.io/kube-openapi/pkg/validation/spec"
   123  	openAPICommonPackagePath = "k8s.io/kube-openapi/pkg/common"
   124  )
   125  
   126  // openApiGen produces a file with auto-generated OpenAPI functions.
   127  type openAPIGen struct {
   128  	generator.GoGenerator
   129  	// TargetPackage is the package that will get GetOpenAPIDefinitions function returns all open API definitions.
   130  	targetPackage string
   131  	imports       namer.ImportTracker
   132  }
   133  
   134  func newOpenAPIGen(outputFilename string, targetPackage string) generator.Generator {
   135  	return &openAPIGen{
   136  		GoGenerator: generator.GoGenerator{
   137  			OutputFilename: outputFilename,
   138  		},
   139  		imports:       generator.NewImportTrackerForPackage(targetPackage),
   140  		targetPackage: targetPackage,
   141  	}
   142  }
   143  
   144  const nameTmpl = "schema_$.type|private$"
   145  
   146  func (g *openAPIGen) Namers(c *generator.Context) namer.NameSystems {
   147  	// Have the raw namer for this file track what it imports.
   148  	return namer.NameSystems{
   149  		"raw": namer.NewRawNamer(g.targetPackage, g.imports),
   150  		"private": &namer.NameStrategy{
   151  			Join: func(pre string, in []string, post string) string {
   152  				return strings.Join(in, "_")
   153  			},
   154  			PrependPackageNames: 4, // enough to fully qualify from k8s.io/api/...
   155  		},
   156  	}
   157  }
   158  
   159  func (g *openAPIGen) Imports(c *generator.Context) []string {
   160  	importLines := []string{}
   161  	for _, singleImport := range g.imports.ImportLines() {
   162  		importLines = append(importLines, singleImport)
   163  	}
   164  	return importLines
   165  }
   166  
   167  func argsFromType(t *types.Type) generator.Args {
   168  	return generator.Args{
   169  		"type":              t,
   170  		"ReferenceCallback": types.Ref(openAPICommonPackagePath, "ReferenceCallback"),
   171  		"OpenAPIDefinition": types.Ref(openAPICommonPackagePath, "OpenAPIDefinition"),
   172  		"SpecSchemaType":    types.Ref(specPackagePath, "Schema"),
   173  	}
   174  }
   175  
   176  func (g *openAPIGen) Init(c *generator.Context, w io.Writer) error {
   177  	sw := generator.NewSnippetWriter(w, c, "$", "$")
   178  	sw.Do("func GetOpenAPIDefinitions(ref $.ReferenceCallback|raw$) map[string]$.OpenAPIDefinition|raw$ {\n", argsFromType(nil))
   179  	sw.Do("return map[string]$.OpenAPIDefinition|raw${\n", argsFromType(nil))
   180  
   181  	for _, t := range c.Order {
   182  		err := newOpenAPITypeWriter(sw, c).generateCall(t)
   183  		if err != nil {
   184  			return err
   185  		}
   186  	}
   187  
   188  	sw.Do("}\n", nil)
   189  	sw.Do("}\n\n", nil)
   190  
   191  	return sw.Error()
   192  }
   193  
   194  func (g *openAPIGen) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
   195  	klog.V(5).Infof("generating for type %v", t)
   196  	sw := generator.NewSnippetWriter(w, c, "$", "$")
   197  	err := newOpenAPITypeWriter(sw, c).generate(t)
   198  	if err != nil {
   199  		return err
   200  	}
   201  	return sw.Error()
   202  }
   203  
   204  func getJsonTags(m *types.Member) []string {
   205  	jsonTag := reflect.StructTag(m.Tags).Get("json")
   206  	if jsonTag == "" {
   207  		return []string{}
   208  	}
   209  	return strings.Split(jsonTag, ",")
   210  }
   211  
   212  func getReferableName(m *types.Member) string {
   213  	jsonTags := getJsonTags(m)
   214  	if len(jsonTags) > 0 {
   215  		if jsonTags[0] == "-" {
   216  			return ""
   217  		} else {
   218  			return jsonTags[0]
   219  		}
   220  	} else {
   221  		return m.Name
   222  	}
   223  }
   224  
   225  func shouldInlineMembers(m *types.Member) bool {
   226  	jsonTags := getJsonTags(m)
   227  	return len(jsonTags) > 1 && jsonTags[1] == "inline"
   228  }
   229  
   230  type openAPITypeWriter struct {
   231  	*generator.SnippetWriter
   232  	context                *generator.Context
   233  	refTypes               map[string]*types.Type
   234  	enumContext            *enumContext
   235  	GetDefinitionInterface *types.Type
   236  }
   237  
   238  func newOpenAPITypeWriter(sw *generator.SnippetWriter, c *generator.Context) openAPITypeWriter {
   239  	return openAPITypeWriter{
   240  		SnippetWriter: sw,
   241  		context:       c,
   242  		refTypes:      map[string]*types.Type{},
   243  		enumContext:   newEnumContext(c),
   244  	}
   245  }
   246  
   247  func methodReturnsValue(mt *types.Type, pkg, name string) bool {
   248  	if len(mt.Signature.Parameters) != 0 || len(mt.Signature.Results) != 1 {
   249  		return false
   250  	}
   251  	r := mt.Signature.Results[0]
   252  	return r.Name.Name == name && r.Name.Package == pkg
   253  }
   254  
   255  func hasOpenAPIV3DefinitionMethod(t *types.Type) bool {
   256  	for mn, mt := range t.Methods {
   257  		if mn != "OpenAPIV3Definition" {
   258  			continue
   259  		}
   260  		return methodReturnsValue(mt, openAPICommonPackagePath, "OpenAPIDefinition")
   261  	}
   262  	return false
   263  }
   264  
   265  func hasOpenAPIDefinitionMethod(t *types.Type) bool {
   266  	for mn, mt := range t.Methods {
   267  		if mn != "OpenAPIDefinition" {
   268  			continue
   269  		}
   270  		return methodReturnsValue(mt, openAPICommonPackagePath, "OpenAPIDefinition")
   271  	}
   272  	return false
   273  }
   274  
   275  func hasOpenAPIDefinitionMethods(t *types.Type) bool {
   276  	var hasSchemaTypeMethod, hasOpenAPISchemaFormat bool
   277  	for mn, mt := range t.Methods {
   278  		switch mn {
   279  		case "OpenAPISchemaType":
   280  			hasSchemaTypeMethod = methodReturnsValue(mt, "", "[]string")
   281  		case "OpenAPISchemaFormat":
   282  			hasOpenAPISchemaFormat = methodReturnsValue(mt, "", "string")
   283  		}
   284  	}
   285  	return hasSchemaTypeMethod && hasOpenAPISchemaFormat
   286  }
   287  
   288  func hasOpenAPIV3OneOfMethod(t *types.Type) bool {
   289  	for mn, mt := range t.Methods {
   290  		if mn != "OpenAPIV3OneOfTypes" {
   291  			continue
   292  		}
   293  		return methodReturnsValue(mt, "", "[]string")
   294  	}
   295  	return false
   296  }
   297  
   298  // typeShortName returns short package name (e.g. the name x appears in package x definition) dot type name.
   299  func typeShortName(t *types.Type) string {
   300  	// `path` vs. `filepath` because packages use '/'
   301  	return path.Base(t.Name.Package) + "." + t.Name.Name
   302  }
   303  
   304  func (g openAPITypeWriter) generateMembers(t *types.Type, required []string) ([]string, error) {
   305  	var err error
   306  	for t.Kind == types.Pointer { // fast-forward to effective type containing members
   307  		t = t.Elem
   308  	}
   309  	for _, m := range t.Members {
   310  		if hasOpenAPITagValue(m.CommentLines, tagValueFalse) {
   311  			continue
   312  		}
   313  		if shouldInlineMembers(&m) {
   314  			required, err = g.generateMembers(m.Type, required)
   315  			if err != nil {
   316  				return required, err
   317  			}
   318  			continue
   319  		}
   320  		name := getReferableName(&m)
   321  		if name == "" {
   322  			continue
   323  		}
   324  		if isOptional, err := isOptional(&m); err != nil {
   325  			klog.Errorf("Error when generating: %v, %v\n", name, m)
   326  			return required, err
   327  		} else if !isOptional {
   328  			required = append(required, name)
   329  		}
   330  		if err = g.generateProperty(&m, t); err != nil {
   331  			klog.Errorf("Error when generating: %v, %v\n", name, m)
   332  			return required, err
   333  		}
   334  	}
   335  	return required, nil
   336  }
   337  
   338  func (g openAPITypeWriter) generateCall(t *types.Type) error {
   339  	// Only generate for struct type and ignore the rest
   340  	switch t.Kind {
   341  	case types.Struct:
   342  		args := argsFromType(t)
   343  		g.Do("\"$.$\": ", t.Name)
   344  
   345  		hasV2Definition := hasOpenAPIDefinitionMethod(t)
   346  		hasV2DefinitionTypeAndFormat := hasOpenAPIDefinitionMethods(t)
   347  		hasV3Definition := hasOpenAPIV3DefinitionMethod(t)
   348  
   349  		switch {
   350  		case hasV2DefinitionTypeAndFormat:
   351  			g.Do(nameTmpl+"(ref),\n", args)
   352  		case hasV2Definition && hasV3Definition:
   353  			g.Do("common.EmbedOpenAPIDefinitionIntoV2Extension($.type|raw${}.OpenAPIV3Definition(), $.type|raw${}.OpenAPIDefinition()),\n", args)
   354  		case hasV2Definition:
   355  			g.Do("$.type|raw${}.OpenAPIDefinition(),\n", args)
   356  		case hasV3Definition:
   357  			g.Do("$.type|raw${}.OpenAPIV3Definition(),\n", args)
   358  		default:
   359  			g.Do(nameTmpl+"(ref),\n", args)
   360  		}
   361  	}
   362  	return g.Error()
   363  }
   364  
   365  func (g openAPITypeWriter) generateValueValidations(vs *spec.SchemaProps) error {
   366  
   367  	if vs == nil {
   368  		return nil
   369  	}
   370  	args := generator.Args{
   371  		"ptrTo": &types.Type{
   372  			Name: types.Name{
   373  				Package: "k8s.io/utils/ptr",
   374  				Name:    "To",
   375  			}},
   376  		"spec": vs,
   377  	}
   378  	if vs.Minimum != nil {
   379  		g.Do("Minimum: $.ptrTo|raw$[float64]($.spec.Minimum$),\n", args)
   380  	}
   381  	if vs.Maximum != nil {
   382  		g.Do("Maximum: $.ptrTo|raw$[float64]($.spec.Maximum$),\n", args)
   383  	}
   384  	if vs.ExclusiveMinimum {
   385  		g.Do("ExclusiveMinimum: true,\n", args)
   386  	}
   387  	if vs.ExclusiveMaximum {
   388  		g.Do("ExclusiveMaximum: true,\n", args)
   389  	}
   390  	if vs.MinLength != nil {
   391  		g.Do("MinLength: $.ptrTo|raw$[int64]($.spec.MinLength$),\n", args)
   392  	}
   393  	if vs.MaxLength != nil {
   394  		g.Do("MaxLength: $.ptrTo|raw$[int64]($.spec.MaxLength$),\n", args)
   395  	}
   396  
   397  	if vs.MinProperties != nil {
   398  		g.Do("MinProperties: $.ptrTo|raw$[int64]($.spec.MinProperties$),\n", args)
   399  	}
   400  	if vs.MaxProperties != nil {
   401  		g.Do("MaxProperties: $.ptrTo|raw$[int64]($.spec.MaxProperties$),\n", args)
   402  	}
   403  	if len(vs.Pattern) > 0 {
   404  		p, err := json.Marshal(vs.Pattern)
   405  		if err != nil {
   406  			return err
   407  		}
   408  		g.Do("Pattern: $.$,\n", string(p))
   409  	}
   410  	if vs.MultipleOf != nil {
   411  		g.Do("MultipleOf: $.ptrTo|raw$[float64]($.spec.MultipleOf$),\n", args)
   412  	}
   413  	if vs.MinItems != nil {
   414  		g.Do("MinItems: $.ptrTo|raw$[int64]($.spec.MinItems$),\n", args)
   415  	}
   416  	if vs.MaxItems != nil {
   417  		g.Do("MaxItems: $.ptrTo|raw$[int64]($.spec.MaxItems$),\n", args)
   418  	}
   419  	if vs.UniqueItems {
   420  		g.Do("UniqueItems: true,\n", nil)
   421  	}
   422  
   423  	return nil
   424  }
   425  
   426  func (g openAPITypeWriter) generate(t *types.Type) error {
   427  	// Only generate for struct type and ignore the rest
   428  	switch t.Kind {
   429  	case types.Struct:
   430  		validationSchema, err := ParseCommentTags(t, t.CommentLines, markerPrefix)
   431  		if err != nil {
   432  			return err
   433  		}
   434  
   435  		hasV2Definition := hasOpenAPIDefinitionMethod(t)
   436  		hasV2DefinitionTypeAndFormat := hasOpenAPIDefinitionMethods(t)
   437  		hasV3OneOfTypes := hasOpenAPIV3OneOfMethod(t)
   438  		hasV3Definition := hasOpenAPIV3DefinitionMethod(t)
   439  
   440  		if hasV2Definition || (hasV3Definition && !hasV2DefinitionTypeAndFormat) {
   441  			// already invoked directly
   442  			return nil
   443  		}
   444  
   445  		args := argsFromType(t)
   446  		g.Do("func "+nameTmpl+"(ref $.ReferenceCallback|raw$) $.OpenAPIDefinition|raw$ {\n", args)
   447  		switch {
   448  		case hasV2DefinitionTypeAndFormat && hasV3Definition:
   449  			g.Do("return common.EmbedOpenAPIDefinitionIntoV2Extension($.type|raw${}.OpenAPIV3Definition(), $.OpenAPIDefinition|raw${\n"+
   450  				"Schema: spec.Schema{\n"+
   451  				"SchemaProps: spec.SchemaProps{\n", args)
   452  			g.generateDescription(t.CommentLines)
   453  			g.Do("Type:$.type|raw${}.OpenAPISchemaType(),\n"+
   454  				"Format:$.type|raw${}.OpenAPISchemaFormat(),\n", args)
   455  			err = g.generateValueValidations(&validationSchema.SchemaProps)
   456  			if err != nil {
   457  				return err
   458  			}
   459  			g.Do("},\n", nil)
   460  			if err := g.generateStructExtensions(t, validationSchema.Extensions); err != nil {
   461  				return err
   462  			}
   463  			g.Do("},\n", nil)
   464  			g.Do("})\n}\n\n", args)
   465  			return nil
   466  		case hasV2DefinitionTypeAndFormat && hasV3OneOfTypes:
   467  			// generate v3 def.
   468  			g.Do("return common.EmbedOpenAPIDefinitionIntoV2Extension($.OpenAPIDefinition|raw${\n"+
   469  				"Schema: spec.Schema{\n"+
   470  				"SchemaProps: spec.SchemaProps{\n", args)
   471  			g.generateDescription(t.CommentLines)
   472  			g.Do("OneOf:common.GenerateOpenAPIV3OneOfSchema($.type|raw${}.OpenAPIV3OneOfTypes()),\n"+
   473  				"Format:$.type|raw${}.OpenAPISchemaFormat(),\n", args)
   474  			err = g.generateValueValidations(&validationSchema.SchemaProps)
   475  			if err != nil {
   476  				return err
   477  			}
   478  			g.Do("},\n", nil)
   479  			if err := g.generateStructExtensions(t, validationSchema.Extensions); err != nil {
   480  				return err
   481  			}
   482  			g.Do("},\n", nil)
   483  			g.Do("},", args)
   484  			// generate v2 def.
   485  			g.Do("$.OpenAPIDefinition|raw${\n"+
   486  				"Schema: spec.Schema{\n"+
   487  				"SchemaProps: spec.SchemaProps{\n", args)
   488  			g.generateDescription(t.CommentLines)
   489  			g.Do("Type:$.type|raw${}.OpenAPISchemaType(),\n"+
   490  				"Format:$.type|raw${}.OpenAPISchemaFormat(),\n", args)
   491  			err = g.generateValueValidations(&validationSchema.SchemaProps)
   492  			if err != nil {
   493  				return err
   494  			}
   495  			g.Do("},\n", nil)
   496  			if err := g.generateStructExtensions(t, validationSchema.Extensions); err != nil {
   497  				return err
   498  			}
   499  			g.Do("},\n", nil)
   500  			g.Do("})\n}\n\n", args)
   501  			return nil
   502  		case hasV2DefinitionTypeAndFormat:
   503  			g.Do("return $.OpenAPIDefinition|raw${\n"+
   504  				"Schema: spec.Schema{\n"+
   505  				"SchemaProps: spec.SchemaProps{\n", args)
   506  			g.generateDescription(t.CommentLines)
   507  			g.Do("Type:$.type|raw${}.OpenAPISchemaType(),\n"+
   508  				"Format:$.type|raw${}.OpenAPISchemaFormat(),\n", args)
   509  			err = g.generateValueValidations(&validationSchema.SchemaProps)
   510  			if err != nil {
   511  				return err
   512  			}
   513  			g.Do("},\n", nil)
   514  			if err := g.generateStructExtensions(t, validationSchema.Extensions); err != nil {
   515  				return err
   516  			}
   517  			g.Do("},\n", nil)
   518  			g.Do("}\n}\n\n", args)
   519  			return nil
   520  		case hasV3OneOfTypes:
   521  			// having v3 oneOf types without custom v2 type or format does not make sense.
   522  			return fmt.Errorf("type %q has v3 one of types but not v2 type or format", t.Name)
   523  		}
   524  
   525  		g.Do("return $.OpenAPIDefinition|raw${\nSchema: spec.Schema{\nSchemaProps: spec.SchemaProps{\n", args)
   526  		g.generateDescription(t.CommentLines)
   527  		g.Do("Type: []string{\"object\"},\n", nil)
   528  		err = g.generateValueValidations(&validationSchema.SchemaProps)
   529  		if err != nil {
   530  			return err
   531  		}
   532  
   533  		// write members into a temporary buffer, in order to postpone writing out the Properties field. We only do
   534  		// that if it is not empty.
   535  		propertiesBuf := bytes.Buffer{}
   536  		bsw := g
   537  		bsw.SnippetWriter = generator.NewSnippetWriter(&propertiesBuf, g.context, "$", "$")
   538  		required, err := bsw.generateMembers(t, []string{})
   539  		if err != nil {
   540  			return err
   541  		}
   542  		if propertiesBuf.Len() > 0 {
   543  			g.Do("Properties: map[string]$.SpecSchemaType|raw${\n", args)
   544  			g.Do(strings.Replace(propertiesBuf.String(), "$", "$\"$\"$", -1), nil) // escape $ (used as delimiter of the templates)
   545  			g.Do("},\n", nil)
   546  		}
   547  
   548  		if len(required) > 0 {
   549  			g.Do("Required: []string{\"$.$\"},\n", strings.Join(required, "\",\""))
   550  		}
   551  		g.Do("},\n", nil)
   552  		if err := g.generateStructExtensions(t, validationSchema.Extensions); err != nil {
   553  			return err
   554  		}
   555  		g.Do("},\n", nil)
   556  
   557  		// Map order is undefined, sort them or we may get a different file generated each time.
   558  		keys := []string{}
   559  		for k := range g.refTypes {
   560  			keys = append(keys, k)
   561  		}
   562  		sort.Strings(keys)
   563  		deps := []string{}
   564  		for _, k := range keys {
   565  			v := g.refTypes[k]
   566  			if t, _ := openapi.OpenAPITypeFormat(v.String()); t != "" {
   567  				// This is a known type, we do not need a reference to it
   568  				// Will eliminate special case of time.Time
   569  				continue
   570  			}
   571  			deps = append(deps, k)
   572  		}
   573  		if len(deps) > 0 {
   574  			g.Do("Dependencies: []string{\n", args)
   575  			for _, k := range deps {
   576  				g.Do("\"$.$\",", k)
   577  			}
   578  			g.Do("},\n", nil)
   579  		}
   580  		g.Do("}\n}\n\n", nil)
   581  	}
   582  	return nil
   583  }
   584  
   585  func (g openAPITypeWriter) generateStructExtensions(t *types.Type, otherExtensions map[string]interface{}) error {
   586  	extensions, errors := parseExtensions(t.CommentLines)
   587  	// Initially, we will only log struct extension errors.
   588  	if len(errors) > 0 {
   589  		for _, e := range errors {
   590  			klog.Errorf("[%s]: %s\n", t.String(), e)
   591  		}
   592  	}
   593  	unions, errors := parseUnions(t)
   594  	if len(errors) > 0 {
   595  		for _, e := range errors {
   596  			klog.Errorf("[%s]: %s\n", t.String(), e)
   597  		}
   598  	}
   599  
   600  	// TODO(seans3): Validate struct extensions here.
   601  	g.emitExtensions(extensions, unions, otherExtensions)
   602  	return nil
   603  }
   604  
   605  func (g openAPITypeWriter) generateMemberExtensions(m *types.Member, parent *types.Type, otherExtensions map[string]interface{}) error {
   606  	extensions, parseErrors := parseExtensions(m.CommentLines)
   607  	validationErrors := validateMemberExtensions(extensions, m)
   608  	errors := append(parseErrors, validationErrors...)
   609  	// Initially, we will only log member extension errors.
   610  	if len(errors) > 0 {
   611  		errorPrefix := fmt.Sprintf("[%s] %s:", parent.String(), m.String())
   612  		for _, e := range errors {
   613  			klog.V(2).Infof("%s %s\n", errorPrefix, e)
   614  		}
   615  	}
   616  	g.emitExtensions(extensions, nil, otherExtensions)
   617  	return nil
   618  }
   619  
   620  func (g openAPITypeWriter) emitExtensions(extensions []extension, unions []union, otherExtensions map[string]interface{}) {
   621  	// If any extensions exist, then emit code to create them.
   622  	if len(extensions) == 0 && len(unions) == 0 && len(otherExtensions) == 0 {
   623  		return
   624  	}
   625  	g.Do("VendorExtensible: spec.VendorExtensible{\nExtensions: spec.Extensions{\n", nil)
   626  	for _, extension := range extensions {
   627  		g.Do("\"$.$\": ", extension.xName)
   628  		if extension.hasMultipleValues() || extension.isAlwaysArrayFormat() {
   629  			g.Do("[]interface{}{\n", nil)
   630  		}
   631  		for _, value := range extension.values {
   632  			g.Do("\"$.$\",\n", value)
   633  		}
   634  		if extension.hasMultipleValues() || extension.isAlwaysArrayFormat() {
   635  			g.Do("},\n", nil)
   636  		}
   637  	}
   638  	if len(unions) > 0 {
   639  		g.Do("\"x-kubernetes-unions\": []interface{}{\n", nil)
   640  		for _, u := range unions {
   641  			u.emit(g)
   642  		}
   643  		g.Do("},\n", nil)
   644  	}
   645  
   646  	if len(otherExtensions) > 0 {
   647  		for k, v := range otherExtensions {
   648  			g.Do("$.key$: $.value$,\n", map[string]interface{}{
   649  				"key":   fmt.Sprintf("%#v", k),
   650  				"value": fmt.Sprintf("%#v", v),
   651  			})
   652  		}
   653  	}
   654  
   655  	g.Do("},\n},\n", nil)
   656  }
   657  
   658  // TODO(#44005): Move this validation outside of this generator (probably to policy verifier)
   659  func (g openAPITypeWriter) validatePatchTags(m *types.Member, parent *types.Type) error {
   660  	// TODO: Remove patch struct tag validation because they we are now consuming OpenAPI on server.
   661  	for _, tagKey := range tempPatchTags {
   662  		structTagValue := reflect.StructTag(m.Tags).Get(tagKey)
   663  		commentTagValue, err := getSingleTagsValue(m.CommentLines, tagKey)
   664  		if err != nil {
   665  			return err
   666  		}
   667  		if structTagValue != commentTagValue {
   668  			return fmt.Errorf("Tags in comment and struct should match for member (%s) of (%s)",
   669  				m.Name, parent.Name.String())
   670  		}
   671  	}
   672  	return nil
   673  }
   674  
   675  func defaultFromComments(comments []string, commentPath string, t *types.Type) (interface{}, *types.Name, error) {
   676  	var tag string
   677  
   678  	for {
   679  		var err error
   680  		tag, err = getSingleTagsValue(comments, tagDefault)
   681  		if err != nil {
   682  			return nil, nil, err
   683  		}
   684  
   685  		if t == nil || len(tag) > 0 {
   686  			break
   687  		}
   688  
   689  		comments = t.CommentLines
   690  		commentPath = t.Name.Package
   691  		switch t.Kind {
   692  		case types.Pointer:
   693  			t = t.Elem
   694  		case types.Alias:
   695  			t = t.Underlying
   696  		default:
   697  			t = nil
   698  		}
   699  	}
   700  
   701  	if tag == "" {
   702  		return nil, nil, nil
   703  	}
   704  
   705  	var i interface{}
   706  	if id, ok := parseSymbolReference(tag, commentPath); ok {
   707  		klog.Errorf("%v, %v", id, commentPath)
   708  		return nil, &id, nil
   709  	} else if err := json.Unmarshal([]byte(tag), &i); err != nil {
   710  		return nil, nil, fmt.Errorf("failed to unmarshal default: %v", err)
   711  	}
   712  	return i, nil, nil
   713  }
   714  
   715  var refRE = regexp.MustCompile(`^ref\((?P<reference>[^"]+)\)$`)
   716  var refREIdentIndex = refRE.SubexpIndex("reference")
   717  
   718  // parseSymbolReference looks for strings that match one of the following:
   719  //   - ref(Ident)
   720  //   - ref(pkgpath.Ident)
   721  //     If the input string matches either of these, it will return the (optional)
   722  //     pkgpath, the Ident, and true.  Otherwise it will return empty strings and
   723  //     false.
   724  //
   725  // This is borrowed from k8s.io/code-generator.
   726  func parseSymbolReference(s, sourcePackage string) (types.Name, bool) {
   727  	matches := refRE.FindStringSubmatch(s)
   728  	if len(matches) < refREIdentIndex || matches[refREIdentIndex] == "" {
   729  		return types.Name{}, false
   730  	}
   731  
   732  	contents := matches[refREIdentIndex]
   733  	name := types.ParseFullyQualifiedName(contents)
   734  	if len(name.Package) == 0 {
   735  		name.Package = sourcePackage
   736  	}
   737  	return name, true
   738  }
   739  
   740  func implementsCustomUnmarshalling(t *types.Type) bool {
   741  	switch t.Kind {
   742  	case types.Pointer:
   743  		unmarshaller, isUnmarshaller := t.Elem.Methods["UnmarshalJSON"]
   744  		return isUnmarshaller && unmarshaller.Signature.Receiver.Kind == types.Pointer
   745  	case types.Struct:
   746  		_, isUnmarshaller := t.Methods["UnmarshalJSON"]
   747  		return isUnmarshaller
   748  	default:
   749  		return false
   750  	}
   751  }
   752  
   753  func mustEnforceDefault(t *types.Type, omitEmpty bool) (interface{}, error) {
   754  	// Treat types with custom unmarshalling as a value
   755  	// (Can be alias, struct, or pointer)
   756  	if implementsCustomUnmarshalling(t) {
   757  		// Since Go JSON deserializer always feeds `null` when present
   758  		// to structs with custom UnmarshalJSON, the zero value for
   759  		// these structs is also null.
   760  		//
   761  		// In general, Kubernetes API types with custom marshalling should
   762  		// marshal their empty values to `null`.
   763  		return nil, nil
   764  	}
   765  
   766  	switch t.Kind {
   767  	case types.Alias:
   768  		return mustEnforceDefault(t.Underlying, omitEmpty)
   769  	case types.Pointer, types.Map, types.Slice, types.Array, types.Interface:
   770  		return nil, nil
   771  	case types.Struct:
   772  		if len(t.Members) == 1 && t.Members[0].Embedded {
   773  			// Treat a struct with a single embedded member the same as an alias
   774  			return mustEnforceDefault(t.Members[0].Type, omitEmpty)
   775  		}
   776  
   777  		return map[string]interface{}{}, nil
   778  	case types.Builtin:
   779  		if !omitEmpty {
   780  			if zero, ok := openapi.OpenAPIZeroValue(t.String()); ok {
   781  				return zero, nil
   782  			} else {
   783  				return nil, fmt.Errorf("please add type %v to getOpenAPITypeFormat function", t)
   784  			}
   785  		}
   786  		return nil, nil
   787  	default:
   788  		return nil, fmt.Errorf("not sure how to enforce default for %v", t.Kind)
   789  	}
   790  }
   791  
   792  func (g openAPITypeWriter) generateDefault(comments []string, t *types.Type, omitEmpty bool, commentOwningType *types.Type) error {
   793  	def, ref, err := defaultFromComments(comments, commentOwningType.Name.Package, t)
   794  	if err != nil {
   795  		return err
   796  	}
   797  	if enforced, err := mustEnforceDefault(t, omitEmpty); err != nil {
   798  		return err
   799  	} else if enforced != nil {
   800  		if def == nil {
   801  			def = enforced
   802  		} else if !reflect.DeepEqual(def, enforced) {
   803  			enforcedJson, _ := json.Marshal(enforced)
   804  			return fmt.Errorf("invalid default value (%#v) for non-pointer/non-omitempty. If specified, must be: %v", def, string(enforcedJson))
   805  		}
   806  	}
   807  	if def != nil {
   808  		g.Do("Default: $.$,\n", fmt.Sprintf("%#v", def))
   809  	} else if ref != nil {
   810  		g.Do("Default: $.|raw$,\n", &types.Type{Name: *ref})
   811  	}
   812  	return nil
   813  }
   814  
   815  func (g openAPITypeWriter) generateDescription(CommentLines []string) {
   816  	var buffer bytes.Buffer
   817  	delPrevChar := func() {
   818  		if buffer.Len() > 0 {
   819  			buffer.Truncate(buffer.Len() - 1) // Delete the last " " or "\n"
   820  		}
   821  	}
   822  
   823  	for _, line := range CommentLines {
   824  		// Ignore all lines after ---
   825  		if line == "---" {
   826  			break
   827  		}
   828  		line = strings.TrimRight(line, " ")
   829  		leading := strings.TrimLeft(line, " ")
   830  		switch {
   831  		case len(line) == 0: // Keep paragraphs
   832  			delPrevChar()
   833  			buffer.WriteString("\n\n")
   834  		case strings.HasPrefix(leading, "TODO"): // Ignore one line TODOs
   835  		case strings.HasPrefix(leading, "+"): // Ignore instructions to go2idl
   836  		default:
   837  			if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") {
   838  				delPrevChar()
   839  				line = "\n" + line + "\n" // Replace it with newline. This is useful when we have a line with: "Example:\n\tJSON-something..."
   840  			} else {
   841  				line += " "
   842  			}
   843  			buffer.WriteString(line)
   844  		}
   845  	}
   846  
   847  	postDoc := strings.TrimLeft(buffer.String(), "\n")
   848  	postDoc = strings.TrimRight(postDoc, "\n")
   849  	postDoc = strings.Replace(postDoc, "\\\"", "\"", -1) // replace user's \" to "
   850  	postDoc = strings.Replace(postDoc, "\"", "\\\"", -1) // Escape "
   851  	postDoc = strings.Replace(postDoc, "\n", "\\n", -1)
   852  	postDoc = strings.Replace(postDoc, "\t", "\\t", -1)
   853  	postDoc = strings.Trim(postDoc, " ")
   854  	if postDoc != "" {
   855  		g.Do("Description: \"$.$\",\n", postDoc)
   856  	}
   857  }
   858  
   859  func (g openAPITypeWriter) generateProperty(m *types.Member, parent *types.Type) error {
   860  	name := getReferableName(m)
   861  	if name == "" {
   862  		return nil
   863  	}
   864  	validationSchema, err := ParseCommentTags(m.Type, m.CommentLines, markerPrefix)
   865  	if err != nil {
   866  		return err
   867  	}
   868  	if err := g.validatePatchTags(m, parent); err != nil {
   869  		return err
   870  	}
   871  	g.Do("\"$.$\": {\n", name)
   872  	if err := g.generateMemberExtensions(m, parent, validationSchema.Extensions); err != nil {
   873  		return err
   874  	}
   875  	g.Do("SchemaProps: spec.SchemaProps{\n", nil)
   876  	var extraComments []string
   877  	if enumType, isEnum := g.enumContext.EnumType(m.Type); isEnum {
   878  		extraComments = enumType.DescriptionLines()
   879  	}
   880  	g.generateDescription(append(m.CommentLines, extraComments...))
   881  	jsonTags := getJsonTags(m)
   882  	if len(jsonTags) > 1 && jsonTags[1] == "string" {
   883  		g.generateSimpleProperty("string", "")
   884  		g.Do("},\n},\n", nil)
   885  		return nil
   886  	}
   887  	omitEmpty := strings.Contains(reflect.StructTag(m.Tags).Get("json"), "omitempty")
   888  	if err := g.generateDefault(m.CommentLines, m.Type, omitEmpty, parent); err != nil {
   889  		return fmt.Errorf("failed to generate default in %v: %v: %v", parent, m.Name, err)
   890  	}
   891  	err = g.generateValueValidations(&validationSchema.SchemaProps)
   892  	if err != nil {
   893  		return err
   894  	}
   895  	t := resolveAliasAndPtrType(m.Type)
   896  	// If we can get a openAPI type and format for this type, we consider it to be simple property
   897  	typeString, format := openapi.OpenAPITypeFormat(t.String())
   898  	if typeString != "" {
   899  		g.generateSimpleProperty(typeString, format)
   900  		if enumType, isEnum := g.enumContext.EnumType(m.Type); isEnum {
   901  			// original type is an enum, add "Enum: " and the values
   902  			g.Do("Enum: []interface{}{$.$},\n", strings.Join(enumType.ValueStrings(), ", "))
   903  		}
   904  		g.Do("},\n},\n", nil)
   905  		return nil
   906  	}
   907  	switch t.Kind {
   908  	case types.Builtin:
   909  		return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", t)
   910  	case types.Map:
   911  		if err := g.generateMapProperty(t); err != nil {
   912  			return fmt.Errorf("failed to generate map property in %v: %v: %v", parent, m.Name, err)
   913  		}
   914  	case types.Slice, types.Array:
   915  		if err := g.generateSliceProperty(t); err != nil {
   916  			return fmt.Errorf("failed to generate slice property in %v: %v: %v", parent, m.Name, err)
   917  		}
   918  	case types.Struct, types.Interface:
   919  		g.generateReferenceProperty(t)
   920  	default:
   921  		return fmt.Errorf("cannot generate spec for type %v", t)
   922  	}
   923  	g.Do("},\n},\n", nil)
   924  	return g.Error()
   925  }
   926  
   927  func (g openAPITypeWriter) generateSimpleProperty(typeString, format string) {
   928  	g.Do("Type: []string{\"$.$\"},\n", typeString)
   929  	g.Do("Format: \"$.$\",\n", format)
   930  }
   931  
   932  func (g openAPITypeWriter) generateReferenceProperty(t *types.Type) {
   933  	g.refTypes[t.Name.String()] = t
   934  	g.Do("Ref: ref(\"$.$\"),\n", t.Name.String())
   935  }
   936  
   937  func resolveAliasAndPtrType(t *types.Type) *types.Type {
   938  	var prev *types.Type
   939  	for prev != t {
   940  		prev = t
   941  		if t.Kind == types.Alias {
   942  			t = t.Underlying
   943  		}
   944  		if t.Kind == types.Pointer {
   945  			t = t.Elem
   946  		}
   947  	}
   948  	return t
   949  }
   950  
   951  func (g openAPITypeWriter) generateMapProperty(t *types.Type) error {
   952  	keyType := resolveAliasAndPtrType(t.Key)
   953  	elemType := resolveAliasAndPtrType(t.Elem)
   954  
   955  	// According to OpenAPI examples, only map from string is supported
   956  	if keyType.Name.Name != "string" {
   957  		return fmt.Errorf("map with non-string keys are not supported by OpenAPI in %v", t)
   958  	}
   959  
   960  	g.Do("Type: []string{\"object\"},\n", nil)
   961  	g.Do("AdditionalProperties: &spec.SchemaOrBool{\nAllows: true,\nSchema: &spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
   962  	if err := g.generateDefault(t.Elem.CommentLines, t.Elem, false, t.Elem); err != nil {
   963  		return err
   964  	}
   965  	typeString, format := openapi.OpenAPITypeFormat(elemType.String())
   966  	if typeString != "" {
   967  		g.generateSimpleProperty(typeString, format)
   968  		if enumType, isEnum := g.enumContext.EnumType(t.Elem); isEnum {
   969  			// original type is an enum, add "Enum: " and the values
   970  			g.Do("Enum: []interface{}{$.$},\n", strings.Join(enumType.ValueStrings(), ", "))
   971  		}
   972  		g.Do("},\n},\n},\n", nil)
   973  		return nil
   974  	}
   975  	switch elemType.Kind {
   976  	case types.Builtin:
   977  		return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", elemType)
   978  	case types.Struct:
   979  		g.generateReferenceProperty(elemType)
   980  	case types.Slice, types.Array:
   981  		if err := g.generateSliceProperty(elemType); err != nil {
   982  			return err
   983  		}
   984  	case types.Map:
   985  		if err := g.generateMapProperty(elemType); err != nil {
   986  			return err
   987  		}
   988  	default:
   989  		return fmt.Errorf("map Element kind %v is not supported in %v", elemType.Kind, t.Name)
   990  	}
   991  	g.Do("},\n},\n},\n", nil)
   992  	return nil
   993  }
   994  
   995  func (g openAPITypeWriter) generateSliceProperty(t *types.Type) error {
   996  	elemType := resolveAliasAndPtrType(t.Elem)
   997  	g.Do("Type: []string{\"array\"},\n", nil)
   998  	g.Do("Items: &spec.SchemaOrArray{\nSchema: &spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
   999  	if err := g.generateDefault(t.Elem.CommentLines, t.Elem, false, t.Elem); err != nil {
  1000  		return err
  1001  	}
  1002  	typeString, format := openapi.OpenAPITypeFormat(elemType.String())
  1003  	if typeString != "" {
  1004  		g.generateSimpleProperty(typeString, format)
  1005  		if enumType, isEnum := g.enumContext.EnumType(t.Elem); isEnum {
  1006  			// original type is an enum, add "Enum: " and the values
  1007  			g.Do("Enum: []interface{}{$.$},\n", strings.Join(enumType.ValueStrings(), ", "))
  1008  		}
  1009  		g.Do("},\n},\n},\n", nil)
  1010  		return nil
  1011  	}
  1012  	switch elemType.Kind {
  1013  	case types.Builtin:
  1014  		return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", elemType)
  1015  	case types.Struct:
  1016  		g.generateReferenceProperty(elemType)
  1017  	case types.Slice, types.Array:
  1018  		if err := g.generateSliceProperty(elemType); err != nil {
  1019  			return err
  1020  		}
  1021  	case types.Map:
  1022  		if err := g.generateMapProperty(elemType); err != nil {
  1023  			return err
  1024  		}
  1025  	default:
  1026  		return fmt.Errorf("slice Element kind %v is not supported in %v", elemType.Kind, t)
  1027  	}
  1028  	g.Do("},\n},\n},\n", nil)
  1029  	return nil
  1030  }