k8s.io/kube-openapi@v0.0.0-20240826222958-65a50c78dec5/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.Type.Name.Name == name && r.Type.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  // Generates Go code to represent an OpenAPI schema. May be refactored in
   366  // the future to take more responsibility as we transition from an on-line
   367  // approach to parsing the comments to spec.Schema
   368  func (g openAPITypeWriter) generateSchema(s *spec.Schema) error {
   369  	if !reflect.DeepEqual(s.SchemaProps, spec.SchemaProps{}) {
   370  		g.Do("SchemaProps: spec.SchemaProps{\n", nil)
   371  		err := g.generateValueValidations(&s.SchemaProps)
   372  		if err != nil {
   373  			return err
   374  		}
   375  
   376  		if len(s.Properties) > 0 {
   377  			g.Do("Properties: map[string]spec.Schema{\n", nil)
   378  
   379  			// Sort property names to generate deterministic output
   380  			keys := []string{}
   381  			for k := range s.Properties {
   382  				keys = append(keys, k)
   383  			}
   384  			sort.Strings(keys)
   385  
   386  			for _, k := range keys {
   387  				v := s.Properties[k]
   388  				g.Do("$.$: {\n", fmt.Sprintf("%#v", k))
   389  				err := g.generateSchema(&v)
   390  				if err != nil {
   391  					return err
   392  				}
   393  				g.Do("},\n", nil)
   394  			}
   395  			g.Do("},\n", nil)
   396  		}
   397  
   398  		if s.AdditionalProperties != nil && s.AdditionalProperties.Schema != nil {
   399  			g.Do("AdditionalProperties: &spec.SchemaOrBool{\n", nil)
   400  			g.Do("Allows: true,\n", nil)
   401  			g.Do("Schema: &spec.Schema{\n", nil)
   402  			err := g.generateSchema(s.AdditionalProperties.Schema)
   403  			if err != nil {
   404  				return err
   405  			}
   406  			g.Do("},\n", nil)
   407  			g.Do("},\n", nil)
   408  		}
   409  
   410  		if s.Items != nil && s.Items.Schema != nil {
   411  			g.Do("Items: &spec.SchemaOrArray{\n", nil)
   412  			g.Do("Schema: &spec.Schema{\n", nil)
   413  			err := g.generateSchema(s.Items.Schema)
   414  			if err != nil {
   415  				return err
   416  			}
   417  			g.Do("},\n", nil)
   418  			g.Do("},\n", nil)
   419  		}
   420  
   421  		g.Do("},\n", nil)
   422  	}
   423  
   424  	if len(s.Extensions) > 0 {
   425  		g.Do("VendorExtensible: spec.VendorExtensible{\nExtensions: spec.Extensions{\n", nil)
   426  
   427  		// Sort extension keys to generate deterministic output
   428  		keys := []string{}
   429  		for k := range s.Extensions {
   430  			keys = append(keys, k)
   431  		}
   432  		sort.Strings(keys)
   433  
   434  		for _, k := range keys {
   435  			v := s.Extensions[k]
   436  			g.Do("$.key$: $.value$,\n", map[string]interface{}{
   437  				"key":   fmt.Sprintf("%#v", k),
   438  				"value": fmt.Sprintf("%#v", v),
   439  			})
   440  		}
   441  		g.Do("},\n},\n", nil)
   442  	}
   443  
   444  	return nil
   445  }
   446  
   447  func (g openAPITypeWriter) generateValueValidations(vs *spec.SchemaProps) error {
   448  
   449  	if vs == nil {
   450  		return nil
   451  	}
   452  	args := generator.Args{
   453  		"ptrTo": &types.Type{
   454  			Name: types.Name{
   455  				Package: "k8s.io/utils/ptr",
   456  				Name:    "To",
   457  			}},
   458  		"spec": vs,
   459  	}
   460  	if vs.Minimum != nil {
   461  		g.Do("Minimum: $.ptrTo|raw$[float64]($.spec.Minimum$),\n", args)
   462  	}
   463  	if vs.Maximum != nil {
   464  		g.Do("Maximum: $.ptrTo|raw$[float64]($.spec.Maximum$),\n", args)
   465  	}
   466  	if vs.ExclusiveMinimum {
   467  		g.Do("ExclusiveMinimum: true,\n", args)
   468  	}
   469  	if vs.ExclusiveMaximum {
   470  		g.Do("ExclusiveMaximum: true,\n", args)
   471  	}
   472  	if vs.MinLength != nil {
   473  		g.Do("MinLength: $.ptrTo|raw$[int64]($.spec.MinLength$),\n", args)
   474  	}
   475  	if vs.MaxLength != nil {
   476  		g.Do("MaxLength: $.ptrTo|raw$[int64]($.spec.MaxLength$),\n", args)
   477  	}
   478  
   479  	if vs.MinProperties != nil {
   480  		g.Do("MinProperties: $.ptrTo|raw$[int64]($.spec.MinProperties$),\n", args)
   481  	}
   482  	if vs.MaxProperties != nil {
   483  		g.Do("MaxProperties: $.ptrTo|raw$[int64]($.spec.MaxProperties$),\n", args)
   484  	}
   485  	if len(vs.Pattern) > 0 {
   486  		p, err := json.Marshal(vs.Pattern)
   487  		if err != nil {
   488  			return err
   489  		}
   490  		g.Do("Pattern: $.$,\n", string(p))
   491  	}
   492  	if vs.MultipleOf != nil {
   493  		g.Do("MultipleOf: $.ptrTo|raw$[float64]($.spec.MultipleOf$),\n", args)
   494  	}
   495  	if vs.MinItems != nil {
   496  		g.Do("MinItems: $.ptrTo|raw$[int64]($.spec.MinItems$),\n", args)
   497  	}
   498  	if vs.MaxItems != nil {
   499  		g.Do("MaxItems: $.ptrTo|raw$[int64]($.spec.MaxItems$),\n", args)
   500  	}
   501  	if vs.UniqueItems {
   502  		g.Do("UniqueItems: true,\n", nil)
   503  	}
   504  
   505  	if len(vs.AllOf) > 0 {
   506  		g.Do("AllOf: []spec.Schema{\n", nil)
   507  		for _, s := range vs.AllOf {
   508  			g.Do("{\n", nil)
   509  			if err := g.generateSchema(&s); err != nil {
   510  				return err
   511  			}
   512  			g.Do("},\n", nil)
   513  		}
   514  		g.Do("},\n", nil)
   515  	}
   516  
   517  	return nil
   518  }
   519  
   520  func (g openAPITypeWriter) generate(t *types.Type) error {
   521  	// Only generate for struct type and ignore the rest
   522  	switch t.Kind {
   523  	case types.Struct:
   524  		validationSchema, err := ParseCommentTags(t, t.CommentLines, markerPrefix)
   525  		if err != nil {
   526  			return fmt.Errorf("failed parsing comment tags for %v: %w", t.String(), err)
   527  		}
   528  
   529  		hasV2Definition := hasOpenAPIDefinitionMethod(t)
   530  		hasV2DefinitionTypeAndFormat := hasOpenAPIDefinitionMethods(t)
   531  		hasV3OneOfTypes := hasOpenAPIV3OneOfMethod(t)
   532  		hasV3Definition := hasOpenAPIV3DefinitionMethod(t)
   533  
   534  		if hasV2Definition || (hasV3Definition && !hasV2DefinitionTypeAndFormat) {
   535  			// already invoked directly
   536  			return nil
   537  		}
   538  
   539  		args := argsFromType(t)
   540  		g.Do("func "+nameTmpl+"(ref $.ReferenceCallback|raw$) $.OpenAPIDefinition|raw$ {\n", args)
   541  		switch {
   542  		case hasV2DefinitionTypeAndFormat && hasV3Definition:
   543  			g.Do("return common.EmbedOpenAPIDefinitionIntoV2Extension($.type|raw${}.OpenAPIV3Definition(), $.OpenAPIDefinition|raw${\n"+
   544  				"Schema: spec.Schema{\n"+
   545  				"SchemaProps: spec.SchemaProps{\n", args)
   546  			g.generateDescription(t.CommentLines)
   547  			g.Do("Type:$.type|raw${}.OpenAPISchemaType(),\n"+
   548  				"Format:$.type|raw${}.OpenAPISchemaFormat(),\n", args)
   549  			err = g.generateValueValidations(&validationSchema.SchemaProps)
   550  			if err != nil {
   551  				return err
   552  			}
   553  			g.Do("},\n", nil)
   554  			if err := g.generateStructExtensions(t, validationSchema.Extensions); err != nil {
   555  				return err
   556  			}
   557  			g.Do("},\n", nil)
   558  			g.Do("})\n}\n\n", args)
   559  			return nil
   560  		case hasV2DefinitionTypeAndFormat && hasV3OneOfTypes:
   561  			// generate v3 def.
   562  			g.Do("return common.EmbedOpenAPIDefinitionIntoV2Extension($.OpenAPIDefinition|raw${\n"+
   563  				"Schema: spec.Schema{\n"+
   564  				"SchemaProps: spec.SchemaProps{\n", args)
   565  			g.generateDescription(t.CommentLines)
   566  			g.Do("OneOf:common.GenerateOpenAPIV3OneOfSchema($.type|raw${}.OpenAPIV3OneOfTypes()),\n"+
   567  				"Format:$.type|raw${}.OpenAPISchemaFormat(),\n", args)
   568  			err = g.generateValueValidations(&validationSchema.SchemaProps)
   569  			if err != nil {
   570  				return err
   571  			}
   572  			g.Do("},\n", nil)
   573  			if err := g.generateStructExtensions(t, validationSchema.Extensions); err != nil {
   574  				return err
   575  			}
   576  			g.Do("},\n", nil)
   577  			g.Do("},", args)
   578  			// generate v2 def.
   579  			g.Do("$.OpenAPIDefinition|raw${\n"+
   580  				"Schema: spec.Schema{\n"+
   581  				"SchemaProps: spec.SchemaProps{\n", args)
   582  			g.generateDescription(t.CommentLines)
   583  			g.Do("Type:$.type|raw${}.OpenAPISchemaType(),\n"+
   584  				"Format:$.type|raw${}.OpenAPISchemaFormat(),\n", args)
   585  			err = g.generateValueValidations(&validationSchema.SchemaProps)
   586  			if err != nil {
   587  				return err
   588  			}
   589  			g.Do("},\n", nil)
   590  			if err := g.generateStructExtensions(t, validationSchema.Extensions); err != nil {
   591  				return err
   592  			}
   593  			g.Do("},\n", nil)
   594  			g.Do("})\n}\n\n", args)
   595  			return nil
   596  		case hasV2DefinitionTypeAndFormat:
   597  			g.Do("return $.OpenAPIDefinition|raw${\n"+
   598  				"Schema: spec.Schema{\n"+
   599  				"SchemaProps: spec.SchemaProps{\n", args)
   600  			g.generateDescription(t.CommentLines)
   601  			g.Do("Type:$.type|raw${}.OpenAPISchemaType(),\n"+
   602  				"Format:$.type|raw${}.OpenAPISchemaFormat(),\n", args)
   603  			err = g.generateValueValidations(&validationSchema.SchemaProps)
   604  			if err != nil {
   605  				return err
   606  			}
   607  			g.Do("},\n", nil)
   608  			if err := g.generateStructExtensions(t, validationSchema.Extensions); err != nil {
   609  				return err
   610  			}
   611  			g.Do("},\n", nil)
   612  			g.Do("}\n}\n\n", args)
   613  			return nil
   614  		case hasV3OneOfTypes:
   615  			// having v3 oneOf types without custom v2 type or format does not make sense.
   616  			return fmt.Errorf("type %q has v3 one of types but not v2 type or format", t.Name)
   617  		}
   618  
   619  		g.Do("return $.OpenAPIDefinition|raw${\nSchema: spec.Schema{\nSchemaProps: spec.SchemaProps{\n", args)
   620  		g.generateDescription(t.CommentLines)
   621  		g.Do("Type: []string{\"object\"},\n", nil)
   622  		err = g.generateValueValidations(&validationSchema.SchemaProps)
   623  		if err != nil {
   624  			return err
   625  		}
   626  
   627  		// write members into a temporary buffer, in order to postpone writing out the Properties field. We only do
   628  		// that if it is not empty.
   629  		propertiesBuf := bytes.Buffer{}
   630  		bsw := g
   631  		bsw.SnippetWriter = generator.NewSnippetWriter(&propertiesBuf, g.context, "$", "$")
   632  		required, err := bsw.generateMembers(t, []string{})
   633  		if err != nil {
   634  			return err
   635  		}
   636  		if propertiesBuf.Len() > 0 {
   637  			g.Do("Properties: map[string]$.SpecSchemaType|raw${\n", args)
   638  			g.Do(strings.Replace(propertiesBuf.String(), "$", "$\"$\"$", -1), nil) // escape $ (used as delimiter of the templates)
   639  			g.Do("},\n", nil)
   640  		}
   641  
   642  		if len(required) > 0 {
   643  			g.Do("Required: []string{\"$.$\"},\n", strings.Join(required, "\",\""))
   644  		}
   645  		g.Do("},\n", nil)
   646  		if err := g.generateStructExtensions(t, validationSchema.Extensions); err != nil {
   647  			return err
   648  		}
   649  		g.Do("},\n", nil)
   650  
   651  		// Map order is undefined, sort them or we may get a different file generated each time.
   652  		keys := []string{}
   653  		for k := range g.refTypes {
   654  			keys = append(keys, k)
   655  		}
   656  		sort.Strings(keys)
   657  		deps := []string{}
   658  		for _, k := range keys {
   659  			v := g.refTypes[k]
   660  			if t, _ := openapi.OpenAPITypeFormat(v.String()); t != "" {
   661  				// This is a known type, we do not need a reference to it
   662  				// Will eliminate special case of time.Time
   663  				continue
   664  			}
   665  			deps = append(deps, k)
   666  		}
   667  		if len(deps) > 0 {
   668  			g.Do("Dependencies: []string{\n", args)
   669  			for _, k := range deps {
   670  				g.Do("\"$.$\",", k)
   671  			}
   672  			g.Do("},\n", nil)
   673  		}
   674  		g.Do("}\n}\n\n", nil)
   675  	}
   676  	return nil
   677  }
   678  
   679  func (g openAPITypeWriter) generateStructExtensions(t *types.Type, otherExtensions map[string]interface{}) error {
   680  	extensions, errors := parseExtensions(t.CommentLines)
   681  	// Initially, we will only log struct extension errors.
   682  	if len(errors) > 0 {
   683  		for _, e := range errors {
   684  			klog.Errorf("[%s]: %s\n", t.String(), e)
   685  		}
   686  	}
   687  	unions, errors := parseUnions(t)
   688  	if len(errors) > 0 {
   689  		for _, e := range errors {
   690  			klog.Errorf("[%s]: %s\n", t.String(), e)
   691  		}
   692  	}
   693  
   694  	// TODO(seans3): Validate struct extensions here.
   695  	g.emitExtensions(extensions, unions, otherExtensions)
   696  	return nil
   697  }
   698  
   699  func (g openAPITypeWriter) generateMemberExtensions(m *types.Member, parent *types.Type, otherExtensions map[string]interface{}) error {
   700  	extensions, parseErrors := parseExtensions(m.CommentLines)
   701  	validationErrors := validateMemberExtensions(extensions, m)
   702  	errors := append(parseErrors, validationErrors...)
   703  	// Initially, we will only log member extension errors.
   704  	if len(errors) > 0 {
   705  		errorPrefix := fmt.Sprintf("[%s] %s:", parent.String(), m.String())
   706  		for _, e := range errors {
   707  			klog.V(2).Infof("%s %s\n", errorPrefix, e)
   708  		}
   709  	}
   710  	g.emitExtensions(extensions, nil, otherExtensions)
   711  	return nil
   712  }
   713  
   714  func (g openAPITypeWriter) emitExtensions(extensions []extension, unions []union, otherExtensions map[string]interface{}) {
   715  	// If any extensions exist, then emit code to create them.
   716  	if len(extensions) == 0 && len(unions) == 0 && len(otherExtensions) == 0 {
   717  		return
   718  	}
   719  	g.Do("VendorExtensible: spec.VendorExtensible{\nExtensions: spec.Extensions{\n", nil)
   720  	for _, extension := range extensions {
   721  		g.Do("\"$.$\": ", extension.xName)
   722  		if extension.hasMultipleValues() || extension.isAlwaysArrayFormat() {
   723  			g.Do("[]interface{}{\n", nil)
   724  		}
   725  		for _, value := range extension.values {
   726  			g.Do("\"$.$\",\n", value)
   727  		}
   728  		if extension.hasMultipleValues() || extension.isAlwaysArrayFormat() {
   729  			g.Do("},\n", nil)
   730  		}
   731  	}
   732  	if len(unions) > 0 {
   733  		g.Do("\"x-kubernetes-unions\": []interface{}{\n", nil)
   734  		for _, u := range unions {
   735  			u.emit(g)
   736  		}
   737  		g.Do("},\n", nil)
   738  	}
   739  
   740  	if len(otherExtensions) > 0 {
   741  		// Sort extension keys to generate deterministic output
   742  		keys := []string{}
   743  		for k := range otherExtensions {
   744  			keys = append(keys, k)
   745  		}
   746  		sort.Strings(keys)
   747  
   748  		for _, k := range keys {
   749  			v := otherExtensions[k]
   750  			g.Do("$.key$: $.value$,\n", map[string]interface{}{
   751  				"key":   fmt.Sprintf("%#v", k),
   752  				"value": fmt.Sprintf("%#v", v),
   753  			})
   754  		}
   755  	}
   756  
   757  	g.Do("},\n},\n", nil)
   758  }
   759  
   760  // TODO(#44005): Move this validation outside of this generator (probably to policy verifier)
   761  func (g openAPITypeWriter) validatePatchTags(m *types.Member, parent *types.Type) error {
   762  	// TODO: Remove patch struct tag validation because they we are now consuming OpenAPI on server.
   763  	for _, tagKey := range tempPatchTags {
   764  		structTagValue := reflect.StructTag(m.Tags).Get(tagKey)
   765  		commentTagValue, err := getSingleTagsValue(m.CommentLines, tagKey)
   766  		if err != nil {
   767  			return err
   768  		}
   769  		if structTagValue != commentTagValue {
   770  			return fmt.Errorf("Tags in comment and struct should match for member (%s) of (%s)",
   771  				m.Name, parent.Name.String())
   772  		}
   773  	}
   774  	return nil
   775  }
   776  
   777  func defaultFromComments(comments []string, commentPath string, t *types.Type) (interface{}, *types.Name, error) {
   778  	var tag string
   779  
   780  	for {
   781  		var err error
   782  		tag, err = getSingleTagsValue(comments, tagDefault)
   783  		if err != nil {
   784  			return nil, nil, err
   785  		}
   786  
   787  		if t == nil || len(tag) > 0 {
   788  			break
   789  		}
   790  
   791  		comments = t.CommentLines
   792  		commentPath = t.Name.Package
   793  		switch t.Kind {
   794  		case types.Pointer:
   795  			t = t.Elem
   796  		case types.Alias:
   797  			t = t.Underlying
   798  		default:
   799  			t = nil
   800  		}
   801  	}
   802  
   803  	if tag == "" {
   804  		return nil, nil, nil
   805  	}
   806  
   807  	var i interface{}
   808  	if id, ok := parseSymbolReference(tag, commentPath); ok {
   809  		klog.V(5).Infof("%v, %v", id, commentPath)
   810  		return nil, &id, nil
   811  	} else if err := json.Unmarshal([]byte(tag), &i); err != nil {
   812  		return nil, nil, fmt.Errorf("failed to unmarshal default: %v", err)
   813  	}
   814  	return i, nil, nil
   815  }
   816  
   817  var refRE = regexp.MustCompile(`^ref\((?P<reference>[^"]+)\)$`)
   818  var refREIdentIndex = refRE.SubexpIndex("reference")
   819  
   820  // parseSymbolReference looks for strings that match one of the following:
   821  //   - ref(Ident)
   822  //   - ref(pkgpath.Ident)
   823  //     If the input string matches either of these, it will return the (optional)
   824  //     pkgpath, the Ident, and true.  Otherwise it will return empty strings and
   825  //     false.
   826  //
   827  // This is borrowed from k8s.io/code-generator.
   828  func parseSymbolReference(s, sourcePackage string) (types.Name, bool) {
   829  	matches := refRE.FindStringSubmatch(s)
   830  	if len(matches) < refREIdentIndex || matches[refREIdentIndex] == "" {
   831  		return types.Name{}, false
   832  	}
   833  
   834  	contents := matches[refREIdentIndex]
   835  	name := types.ParseFullyQualifiedName(contents)
   836  	if len(name.Package) == 0 {
   837  		name.Package = sourcePackage
   838  	}
   839  	return name, true
   840  }
   841  
   842  func implementsCustomUnmarshalling(t *types.Type) bool {
   843  	switch t.Kind {
   844  	case types.Pointer:
   845  		unmarshaller, isUnmarshaller := t.Elem.Methods["UnmarshalJSON"]
   846  		return isUnmarshaller && unmarshaller.Signature.Receiver.Kind == types.Pointer
   847  	case types.Struct:
   848  		_, isUnmarshaller := t.Methods["UnmarshalJSON"]
   849  		return isUnmarshaller
   850  	default:
   851  		return false
   852  	}
   853  }
   854  
   855  func mustEnforceDefault(t *types.Type, omitEmpty bool) (interface{}, error) {
   856  	// Treat types with custom unmarshalling as a value
   857  	// (Can be alias, struct, or pointer)
   858  	if implementsCustomUnmarshalling(t) {
   859  		// Since Go JSON deserializer always feeds `null` when present
   860  		// to structs with custom UnmarshalJSON, the zero value for
   861  		// these structs is also null.
   862  		//
   863  		// In general, Kubernetes API types with custom marshalling should
   864  		// marshal their empty values to `null`.
   865  		return nil, nil
   866  	}
   867  
   868  	switch t.Kind {
   869  	case types.Alias:
   870  		return mustEnforceDefault(t.Underlying, omitEmpty)
   871  	case types.Pointer, types.Map, types.Slice, types.Array, types.Interface:
   872  		return nil, nil
   873  	case types.Struct:
   874  		if len(t.Members) == 1 && t.Members[0].Embedded {
   875  			// Treat a struct with a single embedded member the same as an alias
   876  			return mustEnforceDefault(t.Members[0].Type, omitEmpty)
   877  		}
   878  
   879  		return map[string]interface{}{}, nil
   880  	case types.Builtin:
   881  		if !omitEmpty {
   882  			if zero, ok := openapi.OpenAPIZeroValue(t.String()); ok {
   883  				return zero, nil
   884  			} else {
   885  				return nil, fmt.Errorf("please add type %v to getOpenAPITypeFormat function", t)
   886  			}
   887  		}
   888  		return nil, nil
   889  	default:
   890  		return nil, fmt.Errorf("not sure how to enforce default for %v", t.Kind)
   891  	}
   892  }
   893  
   894  func (g openAPITypeWriter) generateDefault(comments []string, t *types.Type, omitEmpty bool, commentOwningType *types.Type) error {
   895  	def, ref, err := defaultFromComments(comments, commentOwningType.Name.Package, t)
   896  	if err != nil {
   897  		return err
   898  	}
   899  	if enforced, err := mustEnforceDefault(t, omitEmpty); err != nil {
   900  		return err
   901  	} else if enforced != nil {
   902  		if def == nil {
   903  			def = enforced
   904  		} else if !reflect.DeepEqual(def, enforced) {
   905  			enforcedJson, _ := json.Marshal(enforced)
   906  			return fmt.Errorf("invalid default value (%#v) for non-pointer/non-omitempty. If specified, must be: %v", def, string(enforcedJson))
   907  		}
   908  	}
   909  	if def != nil {
   910  		g.Do("Default: $.$,\n", fmt.Sprintf("%#v", def))
   911  	} else if ref != nil {
   912  		g.Do("Default: $.|raw$,\n", &types.Type{Name: *ref})
   913  	}
   914  	return nil
   915  }
   916  
   917  func (g openAPITypeWriter) generateDescription(CommentLines []string) {
   918  	var buffer bytes.Buffer
   919  	delPrevChar := func() {
   920  		if buffer.Len() > 0 {
   921  			buffer.Truncate(buffer.Len() - 1) // Delete the last " " or "\n"
   922  		}
   923  	}
   924  
   925  	for _, line := range CommentLines {
   926  		// Ignore all lines after ---
   927  		if line == "---" {
   928  			break
   929  		}
   930  		line = strings.TrimRight(line, " ")
   931  		leading := strings.TrimLeft(line, " ")
   932  		switch {
   933  		case len(line) == 0: // Keep paragraphs
   934  			delPrevChar()
   935  			buffer.WriteString("\n\n")
   936  		case strings.HasPrefix(leading, "TODO"): // Ignore one line TODOs
   937  		case strings.HasPrefix(leading, "+"): // Ignore instructions to go2idl
   938  		default:
   939  			if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") {
   940  				delPrevChar()
   941  				line = "\n" + line + "\n" // Replace it with newline. This is useful when we have a line with: "Example:\n\tJSON-something..."
   942  			} else {
   943  				line += " "
   944  			}
   945  			buffer.WriteString(line)
   946  		}
   947  	}
   948  
   949  	postDoc := strings.TrimSpace(buffer.String())
   950  	if len(postDoc) > 0 {
   951  		g.Do("Description: $.$,\n", fmt.Sprintf("%#v", postDoc))
   952  	}
   953  }
   954  
   955  func (g openAPITypeWriter) generateProperty(m *types.Member, parent *types.Type) error {
   956  	name := getReferableName(m)
   957  	if name == "" {
   958  		return nil
   959  	}
   960  	validationSchema, err := ParseCommentTags(m.Type, m.CommentLines, markerPrefix)
   961  	if err != nil {
   962  		return err
   963  	}
   964  	if err := g.validatePatchTags(m, parent); err != nil {
   965  		return err
   966  	}
   967  	g.Do("\"$.$\": {\n", name)
   968  	if err := g.generateMemberExtensions(m, parent, validationSchema.Extensions); err != nil {
   969  		return err
   970  	}
   971  	g.Do("SchemaProps: spec.SchemaProps{\n", nil)
   972  	var extraComments []string
   973  	if enumType, isEnum := g.enumContext.EnumType(m.Type); isEnum {
   974  		extraComments = enumType.DescriptionLines()
   975  	}
   976  	g.generateDescription(append(m.CommentLines, extraComments...))
   977  	jsonTags := getJsonTags(m)
   978  	if len(jsonTags) > 1 && jsonTags[1] == "string" {
   979  		g.generateSimpleProperty("string", "")
   980  		g.Do("},\n},\n", nil)
   981  		return nil
   982  	}
   983  	omitEmpty := strings.Contains(reflect.StructTag(m.Tags).Get("json"), "omitempty")
   984  	if err := g.generateDefault(m.CommentLines, m.Type, omitEmpty, parent); err != nil {
   985  		return fmt.Errorf("failed to generate default in %v: %v: %v", parent, m.Name, err)
   986  	}
   987  	err = g.generateValueValidations(&validationSchema.SchemaProps)
   988  	if err != nil {
   989  		return err
   990  	}
   991  	t := resolveAliasAndPtrType(m.Type)
   992  	// If we can get a openAPI type and format for this type, we consider it to be simple property
   993  	typeString, format := openapi.OpenAPITypeFormat(t.String())
   994  	if typeString != "" {
   995  		g.generateSimpleProperty(typeString, format)
   996  		if enumType, isEnum := g.enumContext.EnumType(m.Type); isEnum {
   997  			// original type is an enum, add "Enum: " and the values
   998  			g.Do("Enum: []interface{}{$.$},\n", strings.Join(enumType.ValueStrings(), ", "))
   999  		}
  1000  		g.Do("},\n},\n", nil)
  1001  		return nil
  1002  	}
  1003  	switch t.Kind {
  1004  	case types.Builtin:
  1005  		return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", t)
  1006  	case types.Map:
  1007  		if err := g.generateMapProperty(t); err != nil {
  1008  			return fmt.Errorf("failed to generate map property in %v: %v: %v", parent, m.Name, err)
  1009  		}
  1010  	case types.Slice, types.Array:
  1011  		if err := g.generateSliceProperty(t); err != nil {
  1012  			return fmt.Errorf("failed to generate slice property in %v: %v: %v", parent, m.Name, err)
  1013  		}
  1014  	case types.Struct, types.Interface:
  1015  		g.generateReferenceProperty(t)
  1016  	default:
  1017  		return fmt.Errorf("cannot generate spec for type %v", t)
  1018  	}
  1019  	g.Do("},\n},\n", nil)
  1020  	return g.Error()
  1021  }
  1022  
  1023  func (g openAPITypeWriter) generateSimpleProperty(typeString, format string) {
  1024  	g.Do("Type: []string{\"$.$\"},\n", typeString)
  1025  	g.Do("Format: \"$.$\",\n", format)
  1026  }
  1027  
  1028  func (g openAPITypeWriter) generateReferenceProperty(t *types.Type) {
  1029  	g.refTypes[t.Name.String()] = t
  1030  	g.Do("Ref: ref(\"$.$\"),\n", t.Name.String())
  1031  }
  1032  
  1033  func resolvePtrType(t *types.Type) *types.Type {
  1034  	var prev *types.Type
  1035  	for prev != t {
  1036  		prev = t
  1037  		if t.Kind == types.Pointer {
  1038  			t = t.Elem
  1039  		}
  1040  	}
  1041  	return t
  1042  }
  1043  
  1044  func resolveAliasAndPtrType(t *types.Type) *types.Type {
  1045  	var prev *types.Type
  1046  	for prev != t {
  1047  		prev = t
  1048  		if t.Kind == types.Alias {
  1049  			t = t.Underlying
  1050  		}
  1051  		if t.Kind == types.Pointer {
  1052  			t = t.Elem
  1053  		}
  1054  	}
  1055  	return t
  1056  }
  1057  
  1058  func (g openAPITypeWriter) generateMapProperty(t *types.Type) error {
  1059  	keyType := resolveAliasAndPtrType(t.Key)
  1060  	elemType := resolveAliasAndPtrType(t.Elem)
  1061  
  1062  	// According to OpenAPI examples, only map from string is supported
  1063  	if keyType.Name.Name != "string" {
  1064  		return fmt.Errorf("map with non-string keys are not supported by OpenAPI in %v", t)
  1065  	}
  1066  
  1067  	g.Do("Type: []string{\"object\"},\n", nil)
  1068  	g.Do("AdditionalProperties: &spec.SchemaOrBool{\nAllows: true,\nSchema: &spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
  1069  	if err := g.generateDefault(t.Elem.CommentLines, t.Elem, false, t.Elem); err != nil {
  1070  		return err
  1071  	}
  1072  	typeString, format := openapi.OpenAPITypeFormat(elemType.String())
  1073  	if typeString != "" {
  1074  		g.generateSimpleProperty(typeString, format)
  1075  		if enumType, isEnum := g.enumContext.EnumType(t.Elem); isEnum {
  1076  			// original type is an enum, add "Enum: " and the values
  1077  			g.Do("Enum: []interface{}{$.$},\n", strings.Join(enumType.ValueStrings(), ", "))
  1078  		}
  1079  		g.Do("},\n},\n},\n", nil)
  1080  		return nil
  1081  	}
  1082  	switch elemType.Kind {
  1083  	case types.Builtin:
  1084  		return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", elemType)
  1085  	case types.Struct:
  1086  		g.generateReferenceProperty(elemType)
  1087  	case types.Slice, types.Array:
  1088  		if err := g.generateSliceProperty(elemType); err != nil {
  1089  			return err
  1090  		}
  1091  	case types.Map:
  1092  		if err := g.generateMapProperty(elemType); err != nil {
  1093  			return err
  1094  		}
  1095  	default:
  1096  		return fmt.Errorf("map Element kind %v is not supported in %v", elemType.Kind, t.Name)
  1097  	}
  1098  	g.Do("},\n},\n},\n", nil)
  1099  	return nil
  1100  }
  1101  
  1102  func (g openAPITypeWriter) generateSliceProperty(t *types.Type) error {
  1103  	elemType := resolveAliasAndPtrType(t.Elem)
  1104  	g.Do("Type: []string{\"array\"},\n", nil)
  1105  	g.Do("Items: &spec.SchemaOrArray{\nSchema: &spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
  1106  	if err := g.generateDefault(t.Elem.CommentLines, t.Elem, false, t.Elem); err != nil {
  1107  		return err
  1108  	}
  1109  	typeString, format := openapi.OpenAPITypeFormat(elemType.String())
  1110  	if typeString != "" {
  1111  		g.generateSimpleProperty(typeString, format)
  1112  		if enumType, isEnum := g.enumContext.EnumType(t.Elem); isEnum {
  1113  			// original type is an enum, add "Enum: " and the values
  1114  			g.Do("Enum: []interface{}{$.$},\n", strings.Join(enumType.ValueStrings(), ", "))
  1115  		}
  1116  		g.Do("},\n},\n},\n", nil)
  1117  		return nil
  1118  	}
  1119  	switch elemType.Kind {
  1120  	case types.Builtin:
  1121  		return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", elemType)
  1122  	case types.Struct:
  1123  		g.generateReferenceProperty(elemType)
  1124  	case types.Slice, types.Array:
  1125  		if err := g.generateSliceProperty(elemType); err != nil {
  1126  			return err
  1127  		}
  1128  	case types.Map:
  1129  		if err := g.generateMapProperty(elemType); err != nil {
  1130  			return err
  1131  		}
  1132  	default:
  1133  		return fmt.Errorf("slice Element kind %v is not supported in %v", elemType.Kind, t)
  1134  	}
  1135  	g.Do("},\n},\n},\n", nil)
  1136  	return nil
  1137  }