Merge pull request #106181 from ulucinar/aru/consume-kube-openapi

Bump k8s.io/kube-openapi to commit ee342a809c29
This commit is contained in:
Kubernetes Prow Robot
2021-11-09 16:31:26 -08:00
committed by GitHub
47 changed files with 406 additions and 189 deletions

152
vendor/k8s.io/kube-openapi/pkg/generators/enum.go generated vendored Normal file
View File

@@ -0,0 +1,152 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package generators
import (
"fmt"
"regexp"
"sort"
"strings"
"k8s.io/gengo/generator"
"k8s.io/gengo/types"
)
const tagEnumType = "enum"
const enumTypeDescriptionHeader = "Possible enum values:"
type enumValue struct {
Name string
Value string
Comment string
}
type enumType struct {
Name types.Name
Values []*enumValue
}
// enumMap is a map from the name to the matching enum type.
type enumMap map[types.Name]*enumType
type enumContext struct {
enumTypes enumMap
}
func newEnumContext(c *generator.Context) *enumContext {
return &enumContext{enumTypes: parseEnums(c)}
}
// EnumType checks and finds the enumType for a given type.
// If the given type is a known enum type, returns the enumType, true
// Otherwise, returns nil, false
func (ec *enumContext) EnumType(t *types.Type) (enum *enumType, isEnum bool) {
enum, ok := ec.enumTypes[t.Name]
return enum, ok
}
// ValueStrings returns all possible values of the enum type as strings
// the results are sorted and quoted as Go literals.
func (et *enumType) ValueStrings() []string {
var values []string
for _, value := range et.Values {
// use "%q" format to generate a Go literal of the string const value
values = append(values, fmt.Sprintf("%q", value.Value))
}
sort.Strings(values)
return values
}
// DescriptionLines returns a description of the enum in this format:
//
// Possible enum values:
// - `value1`: description 1
// - `value2`: description 2
func (et *enumType) DescriptionLines() []string {
var lines []string
for _, value := range et.Values {
lines = append(lines, value.Description())
}
sort.Strings(lines)
// Prepend a empty string to initiate a new paragraph.
return append([]string{"", enumTypeDescriptionHeader}, lines...)
}
func parseEnums(c *generator.Context) enumMap {
// First, find the builtin "string" type
stringType := c.Universe.Type(types.Name{Name: "string"})
enumTypes := make(enumMap)
for _, p := range c.Universe {
// find all enum types.
for _, t := range p.Types {
if isEnumType(stringType, t) {
if _, ok := enumTypes[t.Name]; !ok {
enumTypes[t.Name] = &enumType{
Name: t.Name,
}
}
}
}
// find all enum values from constants, and try to match each with its type.
for _, c := range p.Constants {
enumType := c.Underlying
if _, ok := enumTypes[enumType.Name]; ok {
value := &enumValue{
Name: c.Name.Name,
Value: *c.ConstValue,
Comment: strings.Join(c.CommentLines, " "),
}
enumTypes[enumType.Name].appendValue(value)
}
}
}
return enumTypes
}
func (et *enumType) appendValue(value *enumValue) {
et.Values = append(et.Values, value)
}
// Description returns the description line for the enumValue
// with the format:
// - `FooValue`: is the Foo value
func (ev *enumValue) Description() string {
comment := strings.TrimSpace(ev.Comment)
// The comment should starts with the type name, trim it first.
comment = strings.TrimPrefix(comment, ev.Name)
// Trim the possible space after previous step.
comment = strings.TrimSpace(comment)
// The comment may be multiline, cascade all consecutive whitespaces.
comment = whitespaceRegex.ReplaceAllString(comment, " ")
return fmt.Sprintf(" - `%s`: %s", ev.Value, comment)
}
// isEnumType checks if a given type is an enum by the definition
// An enum type should be an alias of string and has tag '+enum' in its comment.
// Additionally, pass the type of builtin 'string' to check against.
func isEnumType(stringType *types.Type, t *types.Type) bool {
return t.Kind == types.Alias && t.Underlying == stringType && hasEnumTag(t)
}
func hasEnumTag(t *types.Type) bool {
return types.ExtractCommentTags("+", t.CommentLines)[tagEnumType] != nil
}
// whitespaceRegex is the regex for consecutive whitespaces.
var whitespaceRegex = regexp.MustCompile(`\s+`)

View File

@@ -66,6 +66,10 @@ var tagToExtension = map[string]extensionAttributes{
kind: types.Struct,
allowedValues: sets.NewString("atomic", "granular"),
},
"validations": {
xName: "x-kubernetes-validations",
kind: types.Slice,
},
}
// Extension encapsulates information necessary to generate an OpenAPI extension.

View File

@@ -225,6 +225,7 @@ type openAPITypeWriter struct {
*generator.SnippetWriter
context *generator.Context
refTypes map[string]*types.Type
enumContext *enumContext
GetDefinitionInterface *types.Type
}
@@ -233,6 +234,7 @@ func newOpenAPITypeWriter(sw *generator.SnippetWriter, c *generator.Context) ope
SnippetWriter: sw,
context: c,
refTypes: map[string]*types.Type{},
enumContext: newEnumContext(c),
}
}
@@ -625,7 +627,11 @@ func (g openAPITypeWriter) generateProperty(m *types.Member, parent *types.Type)
return err
}
g.Do("SchemaProps: spec.SchemaProps{\n", nil)
g.generateDescription(m.CommentLines)
var extraComments []string
if enumType, isEnum := g.enumContext.EnumType(m.Type); isEnum {
extraComments = enumType.DescriptionLines()
}
g.generateDescription(append(m.CommentLines, extraComments...))
jsonTags := getJsonTags(m)
if len(jsonTags) > 1 && jsonTags[1] == "string" {
g.generateSimpleProperty("string", "")
@@ -641,6 +647,10 @@ func (g openAPITypeWriter) generateProperty(m *types.Member, parent *types.Type)
typeString, format := openapi.OpenAPITypeFormat(t.String())
if typeString != "" {
g.generateSimpleProperty(typeString, format)
if enumType, isEnum := g.enumContext.EnumType(m.Type); isEnum {
// original type is an enum, add "Enum: " and the values
g.Do("Enum: []interface{}{$.$}", strings.Join(enumType.ValueStrings(), ", "))
}
g.Do("},\n},\n", nil)
return nil
}

View File

@@ -20,6 +20,7 @@ import (
"bytes"
"compress/gzip"
"crypto/sha512"
"encoding/json"
"fmt"
"mime"
"net/http"
@@ -30,9 +31,9 @@ import (
"github.com/emicklei/go-restful"
"github.com/golang/protobuf/proto"
openapi_v2 "github.com/googleapis/gnostic/openapiv2"
jsoniter "github.com/json-iterator/go"
"github.com/munnerz/goautoneg"
"gopkg.in/yaml.v2"
klog "k8s.io/klog/v2"
"k8s.io/kube-openapi/pkg/builder"
"k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/validation/spec"
@@ -55,13 +56,40 @@ type OpenAPIService struct {
lastModified time.Time
specBytes []byte
specPb []byte
specPbGz []byte
jsonCache cache
protoCache cache
}
specBytesETag string
specPbETag string
specPbGzETag string
type cache struct {
BuildCache func() ([]byte, error)
once sync.Once
bytes []byte
etag string
err error
}
func (c *cache) Get() ([]byte, string, error) {
c.once.Do(func() {
bytes, err := c.BuildCache()
// if there is an error updating the cache, there can be situations where
// c.bytes contains a valid value (carried over from the previous update)
// but c.err is also not nil; the cache user is expected to check for this
c.err = err
if c.err == nil {
// don't override previous spec if we had an error
c.bytes = bytes
c.etag = computeETag(c.bytes)
}
})
return c.bytes, c.etag, c.err
}
func (c *cache) New(cacheBuilder func() ([]byte, error)) cache {
return cache{
bytes: c.bytes,
etag: c.etag,
BuildCache: cacheBuilder,
}
}
func init() {
@@ -71,6 +99,9 @@ func init() {
}
func computeETag(data []byte) string {
if data == nil {
return ""
}
return fmt.Sprintf("\"%X\"", sha512.Sum512(data))
}
@@ -83,51 +114,40 @@ func NewOpenAPIService(spec *spec.Swagger) (*OpenAPIService, error) {
return o, nil
}
func (o *OpenAPIService) getSwaggerBytes() ([]byte, string, time.Time) {
func (o *OpenAPIService) getSwaggerBytes() ([]byte, string, time.Time, error) {
o.rwMutex.RLock()
defer o.rwMutex.RUnlock()
return o.specBytes, o.specBytesETag, o.lastModified
specBytes, etag, err := o.jsonCache.Get()
if err != nil {
return nil, "", time.Time{}, err
}
return specBytes, etag, o.lastModified, nil
}
func (o *OpenAPIService) getSwaggerPbBytes() ([]byte, string, time.Time) {
func (o *OpenAPIService) getSwaggerPbBytes() ([]byte, string, time.Time, error) {
o.rwMutex.RLock()
defer o.rwMutex.RUnlock()
return o.specPb, o.specPbETag, o.lastModified
}
func (o *OpenAPIService) getSwaggerPbGzBytes() ([]byte, string, time.Time) {
o.rwMutex.RLock()
defer o.rwMutex.RUnlock()
return o.specPbGz, o.specPbGzETag, o.lastModified
specPb, etag, err := o.protoCache.Get()
if err != nil {
return nil, "", time.Time{}, err
}
return specPb, etag, o.lastModified, nil
}
func (o *OpenAPIService) UpdateSpec(openapiSpec *spec.Swagger) (err error) {
specBytes, err := jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(openapiSpec)
if err != nil {
return err
}
specPb, err := ToProtoBinary(specBytes)
if err != nil {
return err
}
specPbGz := toGzip(specPb)
specBytesETag := computeETag(specBytes)
specPbETag := computeETag(specPb)
specPbGzETag := computeETag(specPbGz)
lastModified := time.Now()
o.rwMutex.Lock()
defer o.rwMutex.Unlock()
o.specBytes = specBytes
o.specPb = specPb
o.specPbGz = specPbGz
o.specBytesETag = specBytesETag
o.specPbETag = specPbETag
o.specPbGzETag = specPbGzETag
o.lastModified = lastModified
o.jsonCache = o.jsonCache.New(func() ([]byte, error) {
return json.Marshal(openapiSpec)
})
o.protoCache = o.protoCache.New(func() ([]byte, error) {
json, _, err := o.jsonCache.Get()
if err != nil {
return nil, err
}
return ToProtoBinary(json)
})
o.lastModified = time.Now()
return nil
}
@@ -206,7 +226,7 @@ func (o *OpenAPIService) RegisterOpenAPIVersionedService(servePath string, handl
accepted := []struct {
Type string
SubType string
GetDataAndETag func() ([]byte, string, time.Time)
GetDataAndETag func() ([]byte, string, time.Time, error)
}{
{"application", "json", o.getSwaggerBytes},
{"application", "com.github.proto-openapi.spec.v2@v1.0+protobuf", o.getSwaggerPbBytes},
@@ -230,7 +250,15 @@ func (o *OpenAPIService) RegisterOpenAPIVersionedService(servePath string, handl
}
// serve the first matching media type in the sorted clause list
data, etag, lastModified := accepts.GetDataAndETag()
data, etag, lastModified, err := accepts.GetDataAndETag()
if err != nil {
klog.Errorf("Error in OpenAPI handler: %s", err)
// only return a 503 if we have no older cache data to serve
if data == nil {
w.WriteHeader(http.StatusServiceUnavailable)
return
}
}
w.Header().Set("Etag", etag)
// ServeContent will take care of caching using eTag.
http.ServeContent(w, r, servePath, lastModified, bytes.NewReader(data))

View File

@@ -68,6 +68,25 @@ func (e Extensions) GetStringSlice(key string) ([]string, bool) {
return nil, false
}
// GetObject gets the object value from the extensions.
// out must be a json serializable type; the json go struct
// tags of out are used to populate it.
func (e Extensions) GetObject(key string, out interface{}) error {
// This json serialization/deserialization could be replaced with
// an approach using reflection if the optimization becomes justified.
if v, ok := e[strings.ToLower(key)]; ok {
b, err := json.Marshal(v)
if err != nil {
return err
}
err = json.Unmarshal(b, out)
if err != nil {
return err
}
}
return nil
}
// VendorExtensible composition block.
type VendorExtensible struct {
Extensions Extensions

View File

@@ -91,7 +91,7 @@ func NewSchemaValidator(schema *spec.Schema, rootSchema interface{}, root string
return &s
}
// SetPath sets the path for this schema valdiator
// SetPath sets the path for this schema validator
func (s *SchemaValidator) SetPath(path string) {
s.Path = path
}

View File

@@ -16,6 +16,7 @@ package validate
// SchemaValidatorOptions defines optional rules for schema validation
type SchemaValidatorOptions struct {
validationRulesEnabled bool
}
// Option sets optional rules for schema validation

View File

@@ -21,15 +21,17 @@ import (
"k8s.io/kube-openapi/pkg/validation/spec"
)
// An EntityValidator is an interface for things that can validate entities
type EntityValidator interface {
Validate(interface{}) *Result
}
// valueValidator validates the values it applies to.
type valueValidator interface {
// SetPath sets the exact path of the validator prior to calling Validate.
// The exact path contains the map keys and array indices to locate the
// value to be validated from the root data element.
SetPath(path string)
Applies(interface{}, reflect.Kind) bool
Validate(interface{}) *Result
// Applies returns true if the validator applies to the valueKind
// from source. Validate will be called if and only if Applies returns true.
Applies(source interface{}, valueKind reflect.Kind) bool
// Validate validates the value.
Validate(value interface{}) *Result
}
type basicCommonValidator struct {

4
vendor/modules.txt vendored
View File

@@ -2059,7 +2059,7 @@ k8s.io/kube-aggregator/pkg/registry/apiservice/rest
# k8s.io/kube-controller-manager v0.0.0 => ./staging/src/k8s.io/kube-controller-manager
## explicit
k8s.io/kube-controller-manager/config/v1alpha1
# k8s.io/kube-openapi v0.0.0-20210817084001-7fbd8d59e5b8 => k8s.io/kube-openapi v0.0.0-20210817084001-7fbd8d59e5b8
# k8s.io/kube-openapi v0.0.0-20211105084753-ee342a809c29 => k8s.io/kube-openapi v0.0.0-20211105084753-ee342a809c29
## explicit
k8s.io/kube-openapi/cmd/openapi-gen
k8s.io/kube-openapi/cmd/openapi-gen/args
@@ -2772,7 +2772,7 @@ sigs.k8s.io/yaml
# k8s.io/klog/v2 => k8s.io/klog/v2 v2.30.0
# k8s.io/kube-aggregator => ./staging/src/k8s.io/kube-aggregator
# k8s.io/kube-controller-manager => ./staging/src/k8s.io/kube-controller-manager
# k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20210817084001-7fbd8d59e5b8
# k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20211105084753-ee342a809c29
# k8s.io/kube-proxy => ./staging/src/k8s.io/kube-proxy
# k8s.io/kube-scheduler => ./staging/src/k8s.io/kube-scheduler
# k8s.io/kubectl => ./staging/src/k8s.io/kubectl