Bumping k8s dependencies to 1.13

This commit is contained in:
Cheng Xing
2018-11-16 14:08:25 -08:00
parent 305407125c
commit b4c0b68ec7
8002 changed files with 884099 additions and 276228 deletions

View File

@@ -1,4 +1,4 @@
language: go
go_import_path: k8s.io/kube-openapi
script: go test ./pkg/...
script: go test ./pkg/... ./test/...

9
vendor/k8s.io/kube-openapi/CONTRIBUTING.md generated vendored Normal file
View File

@@ -0,0 +1,9 @@
# Contributing
Thanks for taking the time to join our community and start contributing!
The [Contributor Guide](https://github.com/kubernetes/community/blob/master/contributors/guide/README.md)
provides detailed instructions on how to get your ideas and bug fixes seen and accepted.
Please remember to sign the [CNCF CLA](https://github.com/kubernetes/community/blob/master/CLA.md) and
read and observe the [Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).

View File

@@ -58,10 +58,6 @@
"ImportPath": "github.com/go-openapi/swag",
"Rev": "1d0bd113de87027671077d3c71eb3ac5d7dbba72"
},
{
"ImportPath": "github.com/golang/glog",
"Rev": "44145f04b68cf362d9c4df2182967c2275eaefed"
},
{
"ImportPath": "github.com/golang/protobuf/proto",
"Rev": "4bd1920723d7b7c925de087aa32e2187708897f7"
@@ -396,6 +392,10 @@
{
"ImportPath": "k8s.io/gengo/types",
"Rev": "75356185a9af8f0464efa792e2e9508d5b4be83c"
},
{
"ImportPath": "k8s.io/klog",
"Rev": "b9b56d5dfc9208f60ea747056670942d8b0afdc8"
}
]
}

5
vendor/k8s.io/kube-openapi/OWNERS generated vendored
View File

@@ -3,5 +3,10 @@ reviewers:
- gmarek
- mbohlool
- philips
- seans3
- apelisse
- roycaihw
approvers:
- mbohlool
- lavalamp
- seans3

View File

@@ -12,3 +12,7 @@ definitions.
- The spec generator that is responsible for dynamically generate
the final OpenAPI spec using web service routes or combining other
OpenAPI/Json specs.
## Contributing
Please see [CONTRIBUTING.md](CONTRIBUTING.md) for instructions on how to contribute.

View File

@@ -0,0 +1,73 @@
/*
Copyright 2018 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 args
import (
"fmt"
"github.com/spf13/pflag"
"k8s.io/gengo/args"
)
// CustomArgs is used by the gengo framework to pass args specific to this generator.
type CustomArgs struct {
// ReportFilename is added to CustomArgs for specifying name of report file used
// by API linter. If specified, API rule violations will be printed to report file.
// Otherwise default value "-" will be used which indicates stdout.
ReportFilename string
}
// NewDefaults returns default arguments for the generator. Returning the arguments instead
// of using default flag parsing allows registering custom arguments afterwards
func NewDefaults() (*args.GeneratorArgs, *CustomArgs) {
// Default() sets a couple of flag default values for example the boilerplate.
// WithoutDefaultFlagParsing() disables implicit addition of command line flags and parsing,
// which allows registering custom arguments afterwards
genericArgs := args.Default().WithoutDefaultFlagParsing()
customArgs := &CustomArgs{}
genericArgs.CustomArgs = customArgs
// Default value for report filename is "-", which stands for stdout
customArgs.ReportFilename = "-"
// Default value for output file base name
genericArgs.OutputFileBaseName = "openapi_generated"
return genericArgs, customArgs
}
// AddFlags add the generator flags to the flag set.
func (c *CustomArgs) AddFlags(fs *pflag.FlagSet) {
fs.StringVarP(&c.ReportFilename, "report-filename", "r", c.ReportFilename, "Name of report file used by API linter to print API violations. Default \"-\" stands for standard output. NOTE that if valid filename other than \"-\" is specified, API linter won't return error on detected API violations. This allows further check of existing API violations without stopping the OpenAPI generation toolchain.")
}
// Validate checks the given arguments.
func Validate(genericArgs *args.GeneratorArgs) error {
c, ok := genericArgs.CustomArgs.(*CustomArgs)
if !ok {
return fmt.Errorf("input arguments don't contain valid custom arguments")
}
if len(c.ReportFilename) == 0 {
return fmt.Errorf("report filename cannot be empty. specify a valid filename or use \"-\" for stdout")
}
if len(genericArgs.OutputFileBaseName) == 0 {
return fmt.Errorf("output file base name cannot be empty")
}
if len(genericArgs.OutputPackagePath) == 0 {
return fmt.Errorf("output package cannot be empty")
}
return nil
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2016 The Kubernetes Authors.
Copyright 2018 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.
@@ -17,31 +17,42 @@ limitations under the License.
// This package generates openAPI definition file to be used in open API spec generation on API servers. To generate
// definition for a specific type or package add "+k8s:openapi-gen=true" tag to the type/package comment lines. To
// exclude a type from a tagged package, add "+k8s:openapi-gen=false" tag to the type comment lines.
package main
import (
"path/filepath"
"flag"
"log"
"k8s.io/gengo/args"
generatorargs "k8s.io/kube-openapi/cmd/openapi-gen/args"
"k8s.io/kube-openapi/pkg/generators"
"github.com/golang/glog"
"github.com/spf13/pflag"
"k8s.io/klog"
)
func main() {
arguments := args.Default()
klog.InitFlags(nil)
genericArgs, customArgs := generatorargs.NewDefaults()
// Override defaults.
arguments.OutputFileBaseName = "openapi_generated"
arguments.GoHeaderFilePath = filepath.Join(args.DefaultSourceTree(), "k8s.io/kubernetes/hack/boilerplate/boilerplate.go.txt")
genericArgs.AddFlags(pflag.CommandLine)
customArgs.AddFlags(pflag.CommandLine)
flag.Set("logtostderr", "true")
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
pflag.Parse()
// Run it.
if err := arguments.Execute(
if err := generatorargs.Validate(genericArgs); err != nil {
log.Fatalf("Arguments validation error: %v", err)
}
// Generates the code for the OpenAPIDefinitions.
if err := genericArgs.Execute(
generators.NameSystems(),
generators.DefaultNameSystem(),
generators.Packages,
); err != nil {
glog.Fatalf("Error: %v", err)
log.Fatalf("OpenAPI code generation error: %v", err)
}
glog.V(2).Info("Completed successfully.")
}
log.Println("Code for OpenAPI definitions generated")
}

View File

@@ -0,0 +1,60 @@
/*
Copyright 2018 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 main
import (
"log"
"os"
"github.com/googleapis/gnostic/OpenAPIv2"
"github.com/googleapis/gnostic/compiler"
yaml "gopkg.in/yaml.v2"
"k8s.io/kube-openapi/pkg/schemaconv"
"k8s.io/kube-openapi/pkg/util/proto"
)
func main() {
if len(os.Args) != 1 {
log.Fatal("this program takes input on stdin and writes output to stdout.")
}
var info yaml.MapSlice
if err := yaml.NewDecoder(os.Stdin).Decode(&info); err != nil {
log.Fatalf("error decoding stdin: %v", err)
}
document, err := openapi_v2.NewDocument(info, compiler.NewContext("$root", nil))
if err != nil {
log.Fatalf("error interpreting stdin: %v", err)
}
models, err := proto.NewOpenAPIData(document)
if err != nil {
log.Fatalf("error interpreting models: %v", err)
}
newSchema, err := schemaconv.ToSchema(models)
if err != nil {
log.Fatalf("error converting schema format: %v", err)
}
if err := yaml.NewEncoder(os.Stdout).Encode(newSchema); err != nil {
log.Fatalf("error writing new schema: %v", err)
}
}

View File

@@ -61,6 +61,10 @@ func (s *referenceWalker) walkRef(ref spec.Ref) spec.Ref {
k := refStr[len(definitionPrefix):]
def := s.root.Definitions[k]
s.walkSchema(&def)
// Make sure we don't assign to nil map
if s.root.Definitions == nil {
s.root.Definitions = spec.Definitions{}
}
s.root.Definitions[k] = def
}
return s.walkRefCallback(ref)
@@ -83,13 +87,13 @@ func (s *referenceWalker) walkSchema(schema *spec.Schema) {
s.walkSchema(&v)
schema.PatternProperties[k] = v
}
for i, _ := range schema.AllOf {
for i := range schema.AllOf {
s.walkSchema(&schema.AllOf[i])
}
for i, _ := range schema.AnyOf {
for i := range schema.AnyOf {
s.walkSchema(&schema.AnyOf[i])
}
for i, _ := range schema.OneOf {
for i := range schema.OneOf {
s.walkSchema(&schema.OneOf[i])
}
if schema.Not != nil {
@@ -105,7 +109,7 @@ func (s *referenceWalker) walkSchema(schema *spec.Schema) {
if schema.Items.Schema != nil {
s.walkSchema(schema.Items.Schema)
}
for i, _ := range schema.Items.Schemas {
for i := range schema.Items.Schemas {
s.walkSchema(&schema.Items.Schemas[i])
}
}
@@ -147,6 +151,9 @@ func (s *referenceWalker) walkOperation(op *spec.Operation) {
}
func (s *referenceWalker) Start() {
if s.root.Paths == nil {
return
}
for _, pathItem := range s.root.Paths.Paths {
s.walkParams(pathItem.Parameters)
s.walkOperation(pathItem.Delete)
@@ -159,7 +166,7 @@ func (s *referenceWalker) Start() {
}
}
// usedDefinitionForSpec returns a map with all used definition in the provided spec as keys and true as values.
// usedDefinitionForSpec returns a map with all used definitions in the provided spec as keys and true as values.
func usedDefinitionForSpec(sp *spec.Swagger) map[string]bool {
usedDefinitions := map[string]bool{}
walkOnAllReferences(func(ref spec.Ref) spec.Ref {
@@ -172,7 +179,7 @@ func usedDefinitionForSpec(sp *spec.Swagger) map[string]bool {
}
// FilterSpecByPaths removes unnecessary paths and definitions used by those paths.
// i.e. if a Path removed by this function, all definition used by it and not used
// i.e. if a Path removed by this function, all definitions used by it and not used
// anywhere else will also be removed.
func FilterSpecByPaths(sp *spec.Swagger, keepPathPrefixes []string) {
// Walk all references to find all used definitions. This function
@@ -220,6 +227,10 @@ func renameDefinition(s *spec.Swagger, old, new string) {
}
return ref
}, s)
// Make sure we don't assign to nil map
if s.Definitions == nil {
s.Definitions = spec.Definitions{}
}
s.Definitions[new] = s.Definitions[old]
delete(s.Definitions, old)
}
@@ -244,6 +255,15 @@ func MergeSpecs(dest, source *spec.Swagger) error {
func mergeSpecs(dest, source *spec.Swagger, renameModelConflicts, ignorePathConflicts bool) (err error) {
specCloned := false
// Paths may be empty, due to [ACL constraints](http://goo.gl/8us55a#securityFiltering).
if source.Paths == nil {
// When a source spec does not have any path, that means none of the definitions
// are used thus we should not do anything
return nil
}
if dest.Paths == nil {
dest.Paths = &spec.Paths{}
}
if ignorePathConflicts {
keepPaths := []string{}
hasConflictingPath := false
@@ -335,6 +355,9 @@ func mergeSpecs(dest, source *spec.Swagger, renameModelConflicts, ignorePathConf
}
for k, v := range source.Definitions {
if _, found := dest.Definitions[k]; !found {
if dest.Definitions == nil {
dest.Definitions = spec.Definitions{}
}
dest.Definitions[k] = v
}
}
@@ -343,6 +366,10 @@ func mergeSpecs(dest, source *spec.Swagger, renameModelConflicts, ignorePathConf
if _, found := dest.Paths.Paths[k]; found {
return fmt.Errorf("unable to merge: duplicated path %s", k)
}
// PathItem may be empty, due to [ACL constraints](http://goo.gl/8us55a#securityFiltering).
if dest.Paths.Paths == nil {
dest.Paths.Paths = map[string]spec.PathItem{}
}
dest.Paths.Paths[k] = v
}
return nil

View File

@@ -133,9 +133,9 @@ definitions:
format: "string"
`), &spec1_filtered)
assert := assert.New(t)
ast := assert.New(t)
FilterSpecByPaths(spec1, []string{"/test"})
assert.Equal(DebugSpec{spec1_filtered}, DebugSpec{spec1})
ast.Equal(DebugSpec{spec1_filtered}, DebugSpec{spec1})
}
func TestFilterSpecsWithUnusedDefinitions(t *testing.T) {
@@ -238,9 +238,9 @@ definitions:
type: "object"
`), &spec1Filtered)
assert := assert.New(t)
ast := assert.New(t)
FilterSpecByPaths(spec1, []string{"/test"})
assert.Equal(DebugSpec{spec1Filtered}, DebugSpec{spec1})
ast.Equal(DebugSpec{spec1Filtered}, DebugSpec{spec1})
}
func TestMergeSpecsSimple(t *testing.T) {
@@ -369,11 +369,211 @@ definitions:
type: "string"
`), &expected)
assert := assert.New(t)
if !assert.NoError(MergeSpecs(spec1, spec2)) {
ast := assert.New(t)
if !ast.NoError(MergeSpecs(spec1, spec2)) {
return
}
assert.Equal(DebugSpec{expected}, DebugSpec{spec1})
ast.Equal(DebugSpec{expected}, DebugSpec{spec1})
}
func TestMergeSpecsEmptyDefinitions(t *testing.T) {
var spec1, spec2, expected *spec.Swagger
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
responses:
405:
description: "Invalid input"
`), &spec1)
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/othertest:
post:
tags:
- "test2"
summary: "Test2 API"
operationId: "addTest2"
consumes:
- "application/json"
produces:
- "application/xml"
parameters:
- in: "body"
name: "body"
description: "test2 object"
required: true
schema:
$ref: "#/definitions/Test2"
definitions:
Test2:
type: "object"
properties:
other:
$ref: "#/definitions/Other"
Other:
type: "string"
`), &spec2)
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
responses:
405:
description: "Invalid input"
/othertest:
post:
tags:
- "test2"
summary: "Test2 API"
operationId: "addTest2"
consumes:
- "application/json"
produces:
- "application/xml"
parameters:
- in: "body"
name: "body"
description: "test2 object"
required: true
schema:
$ref: "#/definitions/Test2"
definitions:
Test2:
type: "object"
properties:
other:
$ref: "#/definitions/Other"
Other:
type: "string"
`), &expected)
ast := assert.New(t)
if !ast.NoError(MergeSpecs(spec1, spec2)) {
return
}
ast.Equal(DebugSpec{expected}, DebugSpec{spec1})
}
func TestMergeSpecsEmptyPaths(t *testing.T) {
var spec1, spec2, expected *spec.Swagger
yaml.Unmarshal([]byte(`
swagger: "2.0"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
InvalidInput:
type: "string"
format: "string"
`), &spec1)
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/othertest:
post:
tags:
- "test2"
summary: "Test2 API"
operationId: "addTest2"
consumes:
- "application/json"
produces:
- "application/xml"
parameters:
- in: "body"
name: "body"
description: "test2 object"
required: true
schema:
$ref: "#/definitions/Test2"
definitions:
Test2:
type: "object"
properties:
other:
$ref: "#/definitions/Other"
Other:
type: "string"
`), &spec2)
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/othertest:
post:
tags:
- "test2"
summary: "Test2 API"
operationId: "addTest2"
consumes:
- "application/json"
produces:
- "application/xml"
parameters:
- in: "body"
name: "body"
description: "test2 object"
required: true
schema:
$ref: "#/definitions/Test2"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
InvalidInput:
type: "string"
format: "string"
Test2:
type: "object"
properties:
other:
$ref: "#/definitions/Other"
Other:
type: "string"
`), &expected)
ast := assert.New(t)
if !ast.NoError(MergeSpecs(spec1, spec2)) {
return
}
ast.Equal(DebugSpec{expected}, DebugSpec{spec1})
}
func TestMergeSpecsReuseModel(t *testing.T) {
@@ -500,11 +700,11 @@ definitions:
format: "string"
`), &expected)
assert := assert.New(t)
if !assert.NoError(MergeSpecs(spec1, spec2)) {
ast := assert.New(t)
if !ast.NoError(MergeSpecs(spec1, spec2)) {
return
}
assert.Equal(DebugSpec{expected}, DebugSpec{spec1})
ast.Equal(DebugSpec{expected}, DebugSpec{spec1})
}
func TestMergeSpecsRenameModel(t *testing.T) {
@@ -636,11 +836,11 @@ definitions:
format: "string"
`), &expected)
assert := assert.New(t)
if !assert.NoError(MergeSpecs(spec1, spec2)) {
ast := assert.New(t)
if !ast.NoError(MergeSpecs(spec1, spec2)) {
return
}
assert.Equal(DebugSpec{expected}, DebugSpec{spec1})
ast.Equal(DebugSpec{expected}, DebugSpec{spec1})
}
func TestMergeSpecsRenameModelWithExistingV2InDestination(t *testing.T) {
@@ -715,11 +915,11 @@ definitions:
type: "object"
`), &expected)
assert := assert.New(t)
if !assert.NoError(MergeSpecs(spec1, spec2)) {
ast := assert.New(t)
if !ast.NoError(MergeSpecs(spec1, spec2)) {
return
}
assert.Equal(DebugSpec{expected}, DebugSpec{spec1})
ast.Equal(DebugSpec{expected}, DebugSpec{spec1})
}
func TestMergeSpecsRenameModelWithExistingV2InSource(t *testing.T) {
@@ -794,11 +994,11 @@ definitions:
type: "object"
`), &expected)
assert := assert.New(t)
if !assert.NoError(MergeSpecs(spec1, spec2)) {
ast := assert.New(t)
if !ast.NoError(MergeSpecs(spec1, spec2)) {
return
}
assert.Equal(DebugSpec{expected}, DebugSpec{spec1})
ast.Equal(DebugSpec{expected}, DebugSpec{spec1})
}
// This tests if there are three specs, where the first two use the same object definition,
@@ -881,14 +1081,14 @@ definitions:
type: "object"
`), &expected)
assert := assert.New(t)
if !assert.NoError(MergeSpecs(spec1, spec2)) {
ast := assert.New(t)
if !ast.NoError(MergeSpecs(spec1, spec2)) {
return
}
if !assert.NoError(MergeSpecs(spec1, spec3)) {
if !ast.NoError(MergeSpecs(spec1, spec3)) {
return
}
assert.Equal(DebugSpec{expected}, DebugSpec{spec1})
ast.Equal(DebugSpec{expected}, DebugSpec{spec1})
}
// This tests if there are three specs, where the last two use the same object definition,
@@ -969,14 +1169,14 @@ definitions:
type: "object"
`), &expected)
assert := assert.New(t)
if !assert.NoError(MergeSpecs(spec1, spec2)) {
ast := assert.New(t)
if !ast.NoError(MergeSpecs(spec1, spec2)) {
return
}
if !assert.NoError(MergeSpecs(spec1, spec3)) {
if !ast.NoError(MergeSpecs(spec1, spec3)) {
return
}
assert.Equal(DebugSpec{expected}, DebugSpec{spec1})
ast.Equal(DebugSpec{expected}, DebugSpec{spec1})
}
func TestSafeMergeSpecsSimple(t *testing.T) {
@@ -1079,15 +1279,15 @@ definitions:
format: "int64"
`), &expected)
assert := assert.New(t)
ast := assert.New(t)
actual, err := CloneSpec(fooSpec)
if !assert.NoError(err) {
if !ast.NoError(err) {
return
}
if !assert.NoError(MergeSpecsFailOnDefinitionConflict(actual, barSpec)) {
if !ast.NoError(MergeSpecsFailOnDefinitionConflict(actual, barSpec)) {
return
}
assert.Equal(DebugSpec{expected}, DebugSpec{actual})
ast.Equal(DebugSpec{expected}, DebugSpec{actual})
}
func TestSafeMergeSpecsReuseModel(t *testing.T) {
@@ -1184,15 +1384,15 @@ definitions:
format: "int64"
`), &expected)
assert := assert.New(t)
ast := assert.New(t)
actual, err := CloneSpec(fooSpec)
if !assert.NoError(err) {
if !ast.NoError(err) {
return
}
if !assert.NoError(MergeSpecsFailOnDefinitionConflict(actual, barSpec)) {
if !ast.NoError(MergeSpecsFailOnDefinitionConflict(actual, barSpec)) {
return
}
assert.Equal(DebugSpec{expected}, DebugSpec{actual})
ast.Equal(DebugSpec{expected}, DebugSpec{actual})
}
func TestSafeMergeSpecsReuseModelFails(t *testing.T) {
@@ -1291,12 +1491,12 @@ definitions:
format: "int64"
`), &expected)
assert := assert.New(t)
ast := assert.New(t)
actual, err := CloneSpec(fooSpec)
if !assert.NoError(err) {
if !ast.NoError(err) {
return
}
assert.Error(MergeSpecsFailOnDefinitionConflict(actual, barSpec))
ast.Error(MergeSpecsFailOnDefinitionConflict(actual, barSpec))
}
func TestMergeSpecsIgnorePathConflicts(t *testing.T) {
@@ -1402,22 +1602,22 @@ definitions:
format: "int64"
`), &expected)
assert := assert.New(t)
ast := assert.New(t)
actual, err := CloneSpec(fooSpec)
if !assert.NoError(err) {
if !ast.NoError(err) {
return
}
if !assert.Error(MergeSpecs(actual, barSpec)) {
if !ast.Error(MergeSpecs(actual, barSpec)) {
return
}
actual, err = CloneSpec(fooSpec)
if !assert.NoError(err) {
if !ast.NoError(err) {
return
}
if !assert.NoError(MergeSpecsIgnorePathConflict(actual, barSpec)) {
if !ast.NoError(MergeSpecsIgnorePathConflict(actual, barSpec)) {
return
}
assert.Equal(DebugSpec{expected}, DebugSpec{actual})
ast.Equal(DebugSpec{expected}, DebugSpec{actual})
}
func TestMergeSpecsIgnorePathConflictsAllConflicting(t *testing.T) {
@@ -1448,16 +1648,16 @@ definitions:
format: "int64"
`), &fooSpec)
assert := assert.New(t)
ast := assert.New(t)
foo2Spec, err := CloneSpec(fooSpec)
actual, err := CloneSpec(fooSpec)
if !assert.NoError(err) {
if !ast.NoError(err) {
return
}
if !assert.NoError(MergeSpecsIgnorePathConflict(actual, foo2Spec)) {
if !ast.NoError(MergeSpecsIgnorePathConflict(actual, foo2Spec)) {
return
}
assert.Equal(DebugSpec{fooSpec}, DebugSpec{actual})
ast.Equal(DebugSpec{fooSpec}, DebugSpec{actual})
}
func TestMergeSpecReplacesAllPossibleRefs(t *testing.T) {
@@ -1650,9 +1850,9 @@ definitions:
type: "object"
`), &expected)
assert := assert.New(t)
if !assert.NoError(MergeSpecs(spec1, spec2)) {
ast := assert.New(t)
if !ast.NoError(MergeSpecs(spec1, spec2)) {
return
}
assert.Equal(DebugSpec{expected}, DebugSpec{spec1})
ast.Equal(DebugSpec{expected}, DebugSpec{spec1})
}

View File

@@ -20,7 +20,6 @@ import (
"encoding/json"
"fmt"
"net/http"
"reflect"
"strings"
restful "github.com/emicklei/go-restful"
@@ -45,27 +44,59 @@ type openAPI struct {
// BuildOpenAPISpec builds OpenAPI spec given a list of webservices (containing routes) and common.Config to customize it.
func BuildOpenAPISpec(webServices []*restful.WebService, config *common.Config) (*spec.Swagger, error) {
o := newOpenAPI(config)
err := o.buildPaths(webServices)
if err != nil {
return nil, err
}
return o.finalizeSwagger()
}
// BuildOpenAPIDefinitionsForResource builds a partial OpenAPI spec given a sample object and common.Config to customize it.
func BuildOpenAPIDefinitionsForResource(model interface{}, config *common.Config) (*spec.Definitions, error) {
o := newOpenAPI(config)
// We can discard the return value of toSchema because all we care about is the side effect of calling it.
// All the models created for this resource get added to o.swagger.Definitions
_, err := o.toSchema(util.GetCanonicalTypeName(model))
if err != nil {
return nil, err
}
swagger, err := o.finalizeSwagger()
if err != nil {
return nil, err
}
return &swagger.Definitions, nil
}
// BuildOpenAPIDefinitionsForResources returns the OpenAPI spec which includes the definitions for the
// passed type names.
func BuildOpenAPIDefinitionsForResources(config *common.Config, names ...string) (*spec.Swagger, error) {
o := newOpenAPI(config)
// We can discard the return value of toSchema because all we care about is the side effect of calling it.
// All the models created for this resource get added to o.swagger.Definitions
for _, name := range names {
_, err := o.toSchema(name)
if err != nil {
return nil, err
}
}
return o.finalizeSwagger()
}
// newOpenAPI sets up the openAPI object so we can build the spec.
func newOpenAPI(config *common.Config) openAPI {
o := openAPI{
config: config,
swagger: &spec.Swagger{
SwaggerProps: spec.SwaggerProps{
Swagger: OpenAPIVersion,
Definitions: spec.Definitions{},
Responses: config.ResponseDefinitions,
Paths: &spec.Paths{Paths: map[string]spec.PathItem{}},
Info: config.Info,
},
},
}
err := o.init(webServices)
if err != nil {
return nil, err
}
return o.swagger, nil
}
func (o *openAPI) init(webServices []*restful.WebService) error {
if o.config.GetOperationIDAndTags == nil {
o.config.GetOperationIDAndTags = func(r *restful.Route) (string, []string, error) {
return r.Operation, nil, nil
@@ -83,33 +114,25 @@ func (o *openAPI) init(webServices []*restful.WebService) error {
if o.config.CommonResponses == nil {
o.config.CommonResponses = map[int]spec.Response{}
}
err := o.buildPaths(webServices)
if err != nil {
return err
}
return o
}
// finalizeSwagger is called after the spec is built and returns the final spec.
// NOTE: finalizeSwagger also make changes to the final spec, as specified in the config.
func (o *openAPI) finalizeSwagger() (*spec.Swagger, error) {
if o.config.SecurityDefinitions != nil {
o.swagger.SecurityDefinitions = *o.config.SecurityDefinitions
o.swagger.Security = o.config.DefaultSecurity
}
if o.config.PostProcessSpec != nil {
var err error
o.swagger, err = o.config.PostProcessSpec(o.swagger)
if err != nil {
return err
return nil, err
}
}
return nil
}
func getCanonicalizeTypeName(t reflect.Type) string {
if t.PkgPath() == "" {
return t.Name()
}
path := t.PkgPath()
if strings.Contains(path, "/vendor/") {
path = path[strings.Index(path, "/vendor/")+len("/vendor/"):]
}
return path + "." + t.Name()
return o.swagger, nil
}
func (o *openAPI) buildDefinitionRecursively(name string) error {
@@ -143,15 +166,10 @@ func (o *openAPI) buildDefinitionRecursively(name string) error {
return nil
}
// buildDefinitionForType build a definition for a given type and return a referable name to it's definition.
// buildDefinitionForType build a definition for a given type and return a referable name to its definition.
// This is the main function that keep track of definitions used in this spec and is depend on code generated
// by k8s.io/kubernetes/cmd/libs/go2idl/openapi-gen.
func (o *openAPI) buildDefinitionForType(sample interface{}) (string, error) {
t := reflect.TypeOf(sample)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
name := getCanonicalizeTypeName(t)
func (o *openAPI) buildDefinitionForType(name string) (string, error) {
if err := o.buildDefinitionRecursively(name); err != nil {
return "", err
}
@@ -302,7 +320,7 @@ func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map
}
func (o *openAPI) buildResponse(model interface{}, description string) (spec.Response, error) {
schema, err := o.toSchema(model)
schema, err := o.toSchema(util.GetCanonicalTypeName(model))
if err != nil {
return spec.Response{}, err
}
@@ -347,8 +365,8 @@ func (o *openAPI) findCommonParameters(routes []restful.Route) (map[interface{}]
return commonParamsMap, nil
}
func (o *openAPI) toSchema(model interface{}) (_ *spec.Schema, err error) {
if openAPIType, openAPIFormat := common.GetOpenAPITypeFormat(getCanonicalizeTypeName(reflect.TypeOf(model))); openAPIType != "" {
func (o *openAPI) toSchema(name string) (_ *spec.Schema, err error) {
if openAPIType, openAPIFormat := common.GetOpenAPITypeFormat(name); openAPIType != "" {
return &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{openAPIType},
@@ -356,7 +374,7 @@ func (o *openAPI) toSchema(model interface{}) (_ *spec.Schema, err error) {
},
}, nil
} else {
ref, err := o.buildDefinitionForType(model)
ref, err := o.buildDefinitionForType(name)
if err != nil {
return nil, err
}
@@ -380,7 +398,7 @@ func (o *openAPI) buildParameter(restParam restful.ParameterData, bodySample int
case restful.BodyParameterKind:
if bodySample != nil {
ret.In = "body"
ret.Schema, err = o.toSchema(bodySample)
ret.Schema, err = o.toSchema(util.GetCanonicalTypeName(bodySample))
return ret, err
} else {
// There is not enough information in the body parameter to build the definition.

View File

@@ -30,20 +30,10 @@ import (
)
// setUp is a convenience function for setting up for (most) tests.
func setUp(t *testing.T, fullMethods bool) (openAPI, *restful.Container, *assert.Assertions) {
func setUp(t *testing.T, fullMethods bool) (*openapi.Config, *restful.Container, *assert.Assertions) {
assert := assert.New(t)
config, container := getConfig(fullMethods)
return openAPI{
config: config,
swagger: &spec.Swagger{
SwaggerProps: spec.SwaggerProps{
Swagger: OpenAPIVersion,
Definitions: spec.Definitions{},
Paths: &spec.Paths{Paths: map[string]spec.PathItem{}},
Info: config.Info,
},
},
}, container, assert
return config, container, assert
}
func noOp(request *restful.Request, response *restful.Response) {}
@@ -425,8 +415,8 @@ func getTestOutputDefinition() spec.Schema {
}
}
func TestBuildSwaggerSpec(t *testing.T) {
o, container, assert := setUp(t, true)
func TestBuildOpenAPISpec(t *testing.T) {
config, container, assert := setUp(t, true)
expected := &spec.Swagger{
SwaggerProps: spec.SwaggerProps{
Info: &spec.Info{
@@ -449,7 +439,7 @@ func TestBuildSwaggerSpec(t *testing.T) {
},
},
}
err := o.init(container.RegisteredWebServices())
swagger, err := BuildOpenAPISpec(container.RegisteredWebServices(), config)
if !assert.NoError(err) {
return
}
@@ -457,7 +447,27 @@ func TestBuildSwaggerSpec(t *testing.T) {
if !assert.NoError(err) {
return
}
actual_json, err := json.Marshal(o.swagger)
actual_json, err := json.Marshal(swagger)
if !assert.NoError(err) {
return
}
assert.Equal(string(expected_json), string(actual_json))
}
func TestBuildOpenAPIDefinitionsForResource(t *testing.T) {
config, _, assert := setUp(t, true)
expected := &spec.Definitions{
"builder.TestInput": getTestInputDefinition(),
}
swagger, err := BuildOpenAPIDefinitionsForResource(TestInput{}, config)
if !assert.NoError(err) {
return
}
expected_json, err := json.Marshal(expected)
if !assert.NoError(err) {
return
}
actual_json, err := json.Marshal(swagger)
if !assert.NoError(err) {
return
}

View File

@@ -32,7 +32,7 @@ type OpenAPIDefinition struct {
type ReferenceCallback func(path string) spec.Ref
// OpenAPIDefinitions is collection of all definitions.
// GetOpenAPIDefinitions is collection of all definitions.
type GetOpenAPIDefinitions func(ReferenceCallback) map[string]OpenAPIDefinition
// OpenAPIDefinitionGetter gets openAPI definitions for a given type. If a type implements this interface,
@@ -59,6 +59,12 @@ type Config struct {
// will show up as ... "responses" : {"default" : $DefaultResponse} in the spec.
DefaultResponse *spec.Response
// ResponseDefinitions will be added to "responses" under the top-level swagger object. This is an object
// that holds responses definitions that can be used across operations. This property does not define
// global responses for all operations. For more info please refer:
// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#fixed-fields
ResponseDefinitions map[string]spec.Response
// CommonResponses will be added as a response to all operation specs. This is a good place to add common
// responses such as authorization failed.
CommonResponses map[int]spec.Response

View File

@@ -4,8 +4,9 @@
- To exclude a type or a member from a tagged package/type, add "+k8s:openapi-gen=false" tag to the comment lines.
# OpenAPI Extensions
OpenAPI spec can have extensions on types. To define one or more extensions on a type or its member
add `+k8s:openapi-gen=x-kubernetes-$NAME:`$VALUE`` to the comment lines before type/member. A type/member can
add `+k8s:openapi-gen=x-kubernetes-$NAME:$VALUE` to the comment lines before type/member. A type/member can
have multiple extensions. The rest of the line in the comment will be used as $VALUE so there is no need to
escape or quote the value string. Extensions can be used to pass more information to client generators or
documentation generators. For example a type might have a friendly name to be displayed in documentation or
@@ -17,6 +18,7 @@ Custom types which otherwise don't map directly to OpenAPI can override their
OpenAPI definition by implementing a function named "OpenAPIDefinition" with
the following signature:
```go
import openapi "k8s.io/kube-openapi/pkg/common"
// ...
@@ -35,12 +37,13 @@ the following signature:
},
}
}
```
Alternatively, the type can avoid the "openapi" import by defining the following
methods. The following example produces the same OpenAPI definition as the
example above:
```go
func (_ Time) OpenAPISchemaType() []string { return []string{"string"} }
func (_ Time) OpenAPISchemaFormat() string { return "date-time" }
TODO(mehdy): Make k8s:openapi-gen a parameter to the generator now that OpenAPI has its own repo.
```

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

@@ -0,0 +1,219 @@
/*
Copyright 2018 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 (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"sort"
"k8s.io/kube-openapi/pkg/generators/rules"
"k8s.io/gengo/generator"
"k8s.io/gengo/types"
"k8s.io/klog"
)
const apiViolationFileType = "api-violation"
type apiViolationFile struct {
// Since our file actually is unrelated to the package structure, use a
// path that hasn't been mangled by the framework.
unmangledPath string
}
func (a apiViolationFile) AssembleFile(f *generator.File, path string) error {
path = a.unmangledPath
klog.V(2).Infof("Assembling file %q", path)
if path == "-" {
_, err := io.Copy(os.Stdout, &f.Body)
return err
}
output, err := os.Create(path)
if err != nil {
return err
}
defer output.Close()
_, err = io.Copy(output, &f.Body)
return err
}
func (a apiViolationFile) VerifyFile(f *generator.File, path string) error {
if path == "-" {
// Nothing to verify against.
return nil
}
path = a.unmangledPath
formatted := f.Body.Bytes()
existing, err := ioutil.ReadFile(path)
if err != nil {
return fmt.Errorf("unable to read file %q for comparison: %v", path, err)
}
if bytes.Compare(formatted, existing) == 0 {
return nil
}
// Be nice and find the first place where they differ
// (Copied from gengo's default file type)
i := 0
for i < len(formatted) && i < len(existing) && formatted[i] == existing[i] {
i++
}
eDiff, fDiff := existing[i:], formatted[i:]
if len(eDiff) > 100 {
eDiff = eDiff[:100]
}
if len(fDiff) > 100 {
fDiff = fDiff[:100]
}
return fmt.Errorf("output for %q differs; first existing/expected diff: \n %q\n %q", path, string(eDiff), string(fDiff))
}
func newAPIViolationGen() *apiViolationGen {
return &apiViolationGen{
linter: newAPILinter(),
}
}
type apiViolationGen struct {
generator.DefaultGen
linter *apiLinter
}
func (v *apiViolationGen) FileType() string { return apiViolationFileType }
func (v *apiViolationGen) Filename() string {
return "this file is ignored by the file assembler"
}
func (v *apiViolationGen) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
klog.V(5).Infof("validating API rules for type %v", t)
if err := v.linter.validate(t); err != nil {
return err
}
return nil
}
// Finalize prints the API rule violations to report file (if specified from
// arguments) or stdout (default)
func (v *apiViolationGen) Finalize(c *generator.Context, w io.Writer) error {
// NOTE: we don't return error here because we assume that the report file will
// get evaluated afterwards to determine if error should be raised. For example,
// you can have make rules that compare the report file with existing known
// violations (whitelist) and determine no error if no change is detected.
v.linter.report(w)
return nil
}
// apiLinter is the framework hosting multiple API rules and recording API rule
// violations
type apiLinter struct {
// API rules that implement APIRule interface and output API rule violations
rules []APIRule
violations []apiViolation
}
// newAPILinter creates an apiLinter object with API rules in package rules. Please
// add APIRule here when new API rule is implemented.
func newAPILinter() *apiLinter {
return &apiLinter{
rules: []APIRule{
&rules.NamesMatch{},
&rules.OmitEmptyMatchCase{},
},
}
}
// apiViolation uniquely identifies single API rule violation
type apiViolation struct {
// Name of rule from APIRule.Name()
rule string
packageName string
typeName string
// Optional: name of field that violates API rule. Empty fieldName implies that
// the entire type violates the rule.
field string
}
// apiViolations implements sort.Interface for []apiViolation based on the fields: rule,
// packageName, typeName and field.
type apiViolations []apiViolation
func (a apiViolations) Len() int { return len(a) }
func (a apiViolations) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a apiViolations) Less(i, j int) bool {
if a[i].rule != a[j].rule {
return a[i].rule < a[j].rule
}
if a[i].packageName != a[j].packageName {
return a[i].packageName < a[j].packageName
}
if a[i].typeName != a[j].typeName {
return a[i].typeName < a[j].typeName
}
return a[i].field < a[j].field
}
// APIRule is the interface for validating API rule on Go types
type APIRule interface {
// Validate evaluates API rule on type t and returns a list of field names in
// the type that violate the rule. Empty field name [""] implies the entire
// type violates the rule.
Validate(t *types.Type) ([]string, error)
// Name returns the name of APIRule
Name() string
}
// validate runs all API rules on type t and records any API rule violation
func (l *apiLinter) validate(t *types.Type) error {
for _, r := range l.rules {
klog.V(5).Infof("validating API rule %v for type %v", r.Name(), t)
fields, err := r.Validate(t)
if err != nil {
return err
}
for _, field := range fields {
l.violations = append(l.violations, apiViolation{
rule: r.Name(),
packageName: t.Name.Package,
typeName: t.Name.Name,
field: field,
})
}
}
return nil
}
// report prints any API rule violation to writer w and returns error if violation exists
func (l *apiLinter) report(w io.Writer) error {
sort.Sort(apiViolations(l.violations))
for _, v := range l.violations {
fmt.Fprintf(w, "API rule violation: %s,%s,%s,%s\n", v.rule, v.packageName, v.typeName, v.field)
}
if len(l.violations) > 0 {
return fmt.Errorf("API rule violations exist")
}
return nil
}

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

@@ -0,0 +1,91 @@
/*
Copyright 2018 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"
"path/filepath"
"k8s.io/gengo/args"
"k8s.io/gengo/generator"
"k8s.io/gengo/namer"
"k8s.io/gengo/types"
"k8s.io/klog"
generatorargs "k8s.io/kube-openapi/cmd/openapi-gen/args"
)
type identityNamer struct{}
func (_ identityNamer) Name(t *types.Type) string {
return t.Name.String()
}
var _ namer.Namer = identityNamer{}
// NameSystems returns the name system used by the generators in this package.
func NameSystems() namer.NameSystems {
return namer.NameSystems{
"raw": namer.NewRawNamer("", nil),
"sorting_namer": identityNamer{},
}
}
// DefaultNameSystem returns the default name system for ordering the types to be
// processed by the generators in this package.
func DefaultNameSystem() string {
return "sorting_namer"
}
func Packages(context *generator.Context, arguments *args.GeneratorArgs) generator.Packages {
boilerplate, err := arguments.LoadGoBoilerplate()
if err != nil {
klog.Fatalf("Failed loading boilerplate: %v", err)
}
header := append([]byte(fmt.Sprintf("// +build !%s\n\n", arguments.GeneratedBuildTag)), boilerplate...)
header = append(header, []byte(
`
// This file was autogenerated by openapi-gen. Do not edit it manually!
`)...)
reportPath := "-"
if customArgs, ok := arguments.CustomArgs.(*generatorargs.CustomArgs); ok {
reportPath = customArgs.ReportFilename
}
context.FileTypes[apiViolationFileType] = apiViolationFile{
unmangledPath: reportPath,
}
return generator.Packages{
&generator.DefaultPackage{
PackageName: filepath.Base(arguments.OutputPackagePath),
PackagePath: arguments.OutputPackagePath,
HeaderText: header,
GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
return []generator.Generator{
newOpenAPIGen(
arguments.OutputFileBaseName,
arguments.OutputPackagePath,
),
newAPIViolationGen(),
}
},
FilterFunc: apiTypeFilterFunc,
},
}
}

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

@@ -0,0 +1,182 @@
/*
Copyright 2018 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"
"sort"
"strings"
"k8s.io/gengo/examples/set-gen/sets"
"k8s.io/gengo/types"
)
const extensionPrefix = "x-kubernetes-"
// extensionAttributes encapsulates common traits for particular extensions.
type extensionAttributes struct {
xName string
kind types.Kind
allowedValues sets.String
}
// Extension tag to openapi extension attributes
var tagToExtension = map[string]extensionAttributes{
"patchMergeKey": {
xName: "x-kubernetes-patch-merge-key",
kind: types.Slice,
},
"patchStrategy": {
xName: "x-kubernetes-patch-strategy",
kind: types.Slice,
allowedValues: sets.NewString("merge", "retainKeys"),
},
"listMapKey": {
xName: "x-kubernetes-list-map-keys",
kind: types.Slice,
},
"listType": {
xName: "x-kubernetes-list-type",
kind: types.Slice,
allowedValues: sets.NewString("atomic", "set", "map"),
},
}
// Extension encapsulates information necessary to generate an OpenAPI extension.
type extension struct {
idlTag string // Example: listType
xName string // Example: x-kubernetes-list-type
values []string // Example: [atomic]
}
func (e extension) hasAllowedValues() bool {
return tagToExtension[e.idlTag].allowedValues.Len() > 0
}
func (e extension) allowedValues() sets.String {
return tagToExtension[e.idlTag].allowedValues
}
func (e extension) hasKind() bool {
return len(tagToExtension[e.idlTag].kind) > 0
}
func (e extension) kind() types.Kind {
return tagToExtension[e.idlTag].kind
}
func (e extension) validateAllowedValues() error {
// allowedValues not set means no restrictions on values.
if !e.hasAllowedValues() {
return nil
}
// Check for missing value.
if len(e.values) == 0 {
return fmt.Errorf("%s needs a value, none given.", e.idlTag)
}
// For each extension value, validate that it is allowed.
allowedValues := e.allowedValues()
if !allowedValues.HasAll(e.values...) {
return fmt.Errorf("%v not allowed for %s. Allowed values: %v",
e.values, e.idlTag, allowedValues.List())
}
return nil
}
func (e extension) validateType(kind types.Kind) error {
// If this extension class has no kind, then don't validate the type.
if !e.hasKind() {
return nil
}
if kind != e.kind() {
return fmt.Errorf("tag %s on type %v; only allowed on type %v",
e.idlTag, kind, e.kind())
}
return nil
}
func (e extension) hasMultipleValues() bool {
return len(e.values) > 1
}
// Returns sorted list of map keys. Needed for deterministic testing.
func sortedMapKeys(m map[string][]string) []string {
keys := make([]string, len(m))
i := 0
for k := range m {
keys[i] = k
i++
}
sort.Strings(keys)
return keys
}
// Parses comments to return openapi extensions. Returns a list of
// extensions which parsed correctly, as well as a list of the
// parse errors. Validating extensions is performed separately.
// NOTE: Non-empty errors does not mean extensions is empty.
func parseExtensions(comments []string) ([]extension, []error) {
extensions := []extension{}
errors := []error{}
// First, generate extensions from "+k8s:openapi-gen=x-kubernetes-*" annotations.
values := getOpenAPITagValue(comments)
for _, val := range values {
// Example: x-kubernetes-member-tag:member_test
if strings.HasPrefix(val, extensionPrefix) {
parts := strings.SplitN(val, ":", 2)
if len(parts) != 2 {
errors = append(errors, fmt.Errorf("invalid extension value: %v", val))
continue
}
e := extension{
idlTag: tagName, // Example: k8s:openapi-gen
xName: parts[0], // Example: x-kubernetes-member-tag
values: []string{parts[1]}, // Example: member_test
}
extensions = append(extensions, e)
}
}
// Next, generate extensions from "idlTags" (e.g. +listType)
tagValues := types.ExtractCommentTags("+", comments)
for _, idlTag := range sortedMapKeys(tagValues) {
xAttrs, exists := tagToExtension[idlTag]
if !exists {
continue
}
values := tagValues[idlTag]
e := extension{
idlTag: idlTag, // listType
xName: xAttrs.xName, // x-kubernetes-list-type
values: values, // [atomic]
}
extensions = append(extensions, e)
}
return extensions, errors
}
func validateMemberExtensions(extensions []extension, m *types.Member) []error {
errors := []error{}
for _, e := range extensions {
if err := e.validateAllowedValues(); err != nil {
errors = append(errors, err)
}
if err := e.validateType(m.Type.Kind); err != nil {
errors = append(errors, err)
}
}
return errors
}

View File

@@ -0,0 +1,454 @@
/*
Copyright 2018 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 (
"reflect"
"strings"
"testing"
"k8s.io/gengo/examples/set-gen/sets"
"k8s.io/gengo/types"
)
func TestSingleTagExtension(t *testing.T) {
// Comments only contain one tag extension and one value.
var tests = []struct {
comments []string
extensionTag string
extensionName string
extensionValues []string
}{
{
comments: []string{"+patchMergeKey=name"},
extensionTag: "patchMergeKey",
extensionName: "x-kubernetes-patch-merge-key",
extensionValues: []string{"name"},
},
{
comments: []string{"+patchStrategy=merge"},
extensionTag: "patchStrategy",
extensionName: "x-kubernetes-patch-strategy",
extensionValues: []string{"merge"},
},
{
comments: []string{"+listType=atomic"},
extensionTag: "listType",
extensionName: "x-kubernetes-list-type",
extensionValues: []string{"atomic"},
},
{
comments: []string{"+listMapKey=port"},
extensionTag: "listMapKey",
extensionName: "x-kubernetes-list-map-keys",
extensionValues: []string{"port"},
},
{
comments: []string{"+k8s:openapi-gen=x-kubernetes-member-tag:member_test"},
extensionTag: "k8s:openapi-gen",
extensionName: "x-kubernetes-member-tag",
extensionValues: []string{"member_test"},
},
{
comments: []string{"+k8s:openapi-gen=x-kubernetes-member-tag:member_test:member_test2"},
extensionTag: "k8s:openapi-gen",
extensionName: "x-kubernetes-member-tag",
extensionValues: []string{"member_test:member_test2"},
},
{
// Test that poorly formatted extensions aren't added.
comments: []string{
"+k8s:openapi-gen=x-kubernetes-no-value",
"+k8s:openapi-gen=x-kubernetes-member-success:success",
"+k8s:openapi-gen=x-kubernetes-wrong-separator;error",
},
extensionTag: "k8s:openapi-gen",
extensionName: "x-kubernetes-member-success",
extensionValues: []string{"success"},
},
}
for _, test := range tests {
extensions, _ := parseExtensions(test.comments)
actual := extensions[0]
if actual.idlTag != test.extensionTag {
t.Errorf("Extension Tag: expected (%s), actual (%s)\n", test.extensionTag, actual.idlTag)
}
if actual.xName != test.extensionName {
t.Errorf("Extension Name: expected (%s), actual (%s)\n", test.extensionName, actual.xName)
}
if !reflect.DeepEqual(actual.values, test.extensionValues) {
t.Errorf("Extension Values: expected (%s), actual (%s)\n", test.extensionValues, actual.values)
}
if actual.hasMultipleValues() {
t.Errorf("%s: hasMultipleValues() should be false\n", actual.xName)
}
}
}
func TestMultipleTagExtensions(t *testing.T) {
var tests = []struct {
comments []string
extensionTag string
extensionName string
extensionValues []string
}{
{
comments: []string{
"+listMapKey=port",
"+listMapKey=protocol",
},
extensionTag: "listMapKey",
extensionName: "x-kubernetes-list-map-keys",
extensionValues: []string{"port", "protocol"},
},
}
for _, test := range tests {
extensions, errors := parseExtensions(test.comments)
if len(errors) > 0 {
t.Errorf("Unexpected errors: %v\n", errors)
}
actual := extensions[0]
if actual.idlTag != test.extensionTag {
t.Errorf("Extension Tag: expected (%s), actual (%s)\n", test.extensionTag, actual.idlTag)
}
if actual.xName != test.extensionName {
t.Errorf("Extension Name: expected (%s), actual (%s)\n", test.extensionName, actual.xName)
}
if !reflect.DeepEqual(actual.values, test.extensionValues) {
t.Errorf("Extension Values: expected (%s), actual (%s)\n", test.extensionValues, actual.values)
}
if !actual.hasMultipleValues() {
t.Errorf("%s: hasMultipleValues() should be true\n", actual.xName)
}
}
}
func TestExtensionParseErrors(t *testing.T) {
var tests = []struct {
comments []string
errorMessage string
}{
{
// Missing extension value should be an error.
comments: []string{
"+k8s:openapi-gen=x-kubernetes-no-value",
},
errorMessage: "x-kubernetes-no-value",
},
{
// Wrong separator should be an error.
comments: []string{
"+k8s:openapi-gen=x-kubernetes-wrong-separator;error",
},
errorMessage: "x-kubernetes-wrong-separator;error",
},
}
for _, test := range tests {
_, errors := parseExtensions(test.comments)
if len(errors) == 0 {
t.Errorf("Expected errors while parsing: %v\n", test.comments)
}
error := errors[0]
if !strings.Contains(error.Error(), test.errorMessage) {
t.Errorf("Error (%v) should contain substring (%s)\n", error, test.errorMessage)
}
}
}
func TestExtensionAllowedValues(t *testing.T) {
var methodTests = []struct {
e extension
allowedValues sets.String
}{
{
e: extension{
idlTag: "patchStrategy",
},
allowedValues: sets.NewString("merge", "retainKeys"),
},
{
e: extension{
idlTag: "patchMergeKey",
},
allowedValues: nil,
},
{
e: extension{
idlTag: "listType",
},
allowedValues: sets.NewString("atomic", "set", "map"),
},
{
e: extension{
idlTag: "listMapKey",
},
allowedValues: nil,
},
{
e: extension{
idlTag: "k8s:openapi-gen",
},
allowedValues: nil,
},
}
for _, test := range methodTests {
if test.allowedValues != nil {
if !test.e.hasAllowedValues() {
t.Errorf("hasAllowedValues() expected (true), but received: false")
}
if !reflect.DeepEqual(test.allowedValues, test.e.allowedValues()) {
t.Errorf("allowedValues() expected (%v), but received: %v",
test.allowedValues, test.e.allowedValues())
}
}
if test.allowedValues == nil && test.e.hasAllowedValues() {
t.Errorf("hasAllowedValues() expected (false), but received: true")
}
}
var successTests = []struct {
e extension
}{
{
e: extension{
idlTag: "patchStrategy",
xName: "x-kubernetes-patch-strategy",
values: []string{"merge"},
},
},
{
// Validate multiple values.
e: extension{
idlTag: "patchStrategy",
xName: "x-kubernetes-patch-strategy",
values: []string{"merge", "retainKeys"},
},
},
{
e: extension{
idlTag: "patchMergeKey",
xName: "x-kubernetes-patch-merge-key",
values: []string{"key1"},
},
},
{
e: extension{
idlTag: "listType",
xName: "x-kubernetes-list-type",
values: []string{"atomic"},
},
},
}
for _, test := range successTests {
actualErr := test.e.validateAllowedValues()
if actualErr != nil {
t.Errorf("Expected no error for (%v), but received: %v\n", test.e, actualErr)
}
}
var failureTests = []struct {
e extension
}{
{
// Every value must be allowed.
e: extension{
idlTag: "patchStrategy",
xName: "x-kubernetes-patch-strategy",
values: []string{"disallowed", "merge"},
},
},
{
e: extension{
idlTag: "patchStrategy",
xName: "x-kubernetes-patch-strategy",
values: []string{"foo"},
},
},
{
e: extension{
idlTag: "listType",
xName: "x-kubernetes-list-type",
values: []string{"not-allowed"},
},
},
}
for _, test := range failureTests {
actualErr := test.e.validateAllowedValues()
if actualErr == nil {
t.Errorf("Expected error, but received none: %v\n", test.e)
}
}
}
func TestExtensionKind(t *testing.T) {
var methodTests = []struct {
e extension
kind types.Kind
}{
{
e: extension{
idlTag: "patchStrategy",
},
kind: types.Slice,
},
{
e: extension{
idlTag: "patchMergeKey",
},
kind: types.Slice,
},
{
e: extension{
idlTag: "listType",
},
kind: types.Slice,
},
{
e: extension{
idlTag: "listMapKey",
},
kind: types.Slice,
},
{
e: extension{
idlTag: "k8s:openapi-gen",
},
kind: "",
},
}
for _, test := range methodTests {
if len(test.kind) > 0 {
if !test.e.hasKind() {
t.Errorf("%v: hasKind() expected (true), but received: false", test.e)
}
if test.kind != test.e.kind() {
t.Errorf("%v: kind() expected (%v), but received: %v", test.e, test.kind, test.e.kind())
}
} else {
if test.e.hasKind() {
t.Errorf("%v: hasKind() expected (false), but received: true", test.e)
}
}
}
}
func TestValidateMemberExtensions(t *testing.T) {
patchStrategyExtension := extension{
idlTag: "patchStrategy",
xName: "x-kubernetes-patch-strategy",
values: []string{"merge"},
}
patchMergeKeyExtension := extension{
idlTag: "patchMergeKey",
xName: "x-kubernetes-patch-merge-key",
values: []string{"key1", "key2"},
}
listTypeExtension := extension{
idlTag: "listType",
xName: "x-kubernetes-list-type",
values: []string{"atomic"},
}
listMapKeysExtension := extension{
idlTag: "listMapKey",
xName: "x-kubernetes-map-keys",
values: []string{"key1"},
}
genExtension := extension{
idlTag: "k8s:openapi-gen",
xName: "x-kubernetes-member-type",
values: []string{"value1"},
}
sliceField := types.Member{
Name: "Containers",
Type: &types.Type{
Kind: types.Slice,
},
}
mapField := types.Member{
Name: "Containers",
Type: &types.Type{
Kind: types.Map,
},
}
var successTests = []struct {
extensions []extension
member types.Member
}{
// Test single member extension
{
extensions: []extension{patchStrategyExtension},
member: sliceField,
},
// Test multiple member extensions
{
extensions: []extension{
patchMergeKeyExtension,
listTypeExtension,
listMapKeysExtension,
genExtension, // Should not generate errors during type validation
},
member: sliceField,
},
}
for _, test := range successTests {
errors := validateMemberExtensions(test.extensions, &test.member)
if len(errors) > 0 {
t.Errorf("validateMemberExtensions: %v should have produced no errors. Errors: %v",
test.extensions, errors)
}
}
var failureTests = []struct {
extensions []extension
member types.Member
}{
// Test single member extension
{
extensions: []extension{patchStrategyExtension},
member: mapField,
},
// Test multiple member extensions
{
extensions: []extension{
patchMergeKeyExtension,
listTypeExtension,
listMapKeysExtension,
},
member: mapField,
},
}
for _, test := range failureTests {
errors := validateMemberExtensions(test.extensions, &test.member)
if len(errors) != len(test.extensions) {
t.Errorf("validateMemberExtensions: %v should have produced all errors. Errors: %v",
test.extensions, errors)
}
}
}

View File

@@ -25,13 +25,12 @@ import (
"sort"
"strings"
"k8s.io/gengo/args"
"k8s.io/gengo/generator"
"k8s.io/gengo/namer"
"k8s.io/gengo/types"
openapi "k8s.io/kube-openapi/pkg/common"
"github.com/golang/glog"
"k8s.io/klog"
)
// This is the comment tag that carries parameters for open API generation.
@@ -40,15 +39,17 @@ const tagOptional = "optional"
// Known values for the tag.
const (
tagValueTrue = "true"
tagValueFalse = "false"
tagExtensionPrefix = "x-kubernetes-"
tagPatchStrategy = "patchStrategy"
tagPatchMergeKey = "patchMergeKey"
patchStrategyExtensionName = "patch-strategy"
patchMergeKeyExtensionName = "patch-merge-key"
tagValueTrue = "true"
tagValueFalse = "false"
)
// Used for temporary validation of patch struct tags.
// TODO: Remove patch struct tag validation because they we are now consuming OpenAPI on server.
var tempPatchTags = [...]string{
"patchMergeKey",
"patchStrategy",
}
func getOpenAPITagValue(comments []string) []string {
return types.ExtractCommentTags("+", comments)[tagName]
}
@@ -84,64 +85,19 @@ func hasOptionalTag(m *types.Member) bool {
return hasOptionalCommentTag || hasOptionalJsonTag
}
type identityNamer struct{}
func (_ identityNamer) Name(t *types.Type) string {
return t.Name.String()
}
var _ namer.Namer = identityNamer{}
// NameSystems returns the name system used by the generators in this package.
func NameSystems() namer.NameSystems {
return namer.NameSystems{
"raw": namer.NewRawNamer("", nil),
"sorting_namer": identityNamer{},
func apiTypeFilterFunc(c *generator.Context, t *types.Type) bool {
// There is a conflict between this codegen and codecgen, we should avoid types generated for codecgen
if strings.HasPrefix(t.Name.Name, "codecSelfer") {
return false
}
}
// DefaultNameSystem returns the default name system for ordering the types to be
// processed by the generators in this package.
func DefaultNameSystem() string {
return "sorting_namer"
}
func Packages(context *generator.Context, arguments *args.GeneratorArgs) generator.Packages {
boilerplate, err := arguments.LoadGoBoilerplate()
if err != nil {
glog.Fatalf("Failed loading boilerplate: %v", err)
pkg := c.Universe.Package(t.Name.Package)
if hasOpenAPITagValue(pkg.Comments, tagValueTrue) {
return !hasOpenAPITagValue(t.CommentLines, tagValueFalse)
}
header := append([]byte(fmt.Sprintf("// +build !%s\n\n", arguments.GeneratedBuildTag)), boilerplate...)
header = append(header, []byte(
`
// This file was autogenerated by openapi-gen. Do not edit it manually!
`)...)
return generator.Packages{
&generator.DefaultPackage{
PackageName: filepath.Base(arguments.OutputPackagePath),
PackagePath: arguments.OutputPackagePath,
HeaderText: header,
GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
return []generator.Generator{NewOpenAPIGen(arguments.OutputFileBaseName, arguments.OutputPackagePath, context)}
},
FilterFunc: func(c *generator.Context, t *types.Type) bool {
// There is a conflict between this codegen and codecgen, we should avoid types generated for codecgen
if strings.HasPrefix(t.Name.Name, "codecSelfer") {
return false
}
pkg := context.Universe.Package(t.Name.Package)
if hasOpenAPITagValue(pkg.Comments, tagValueTrue) {
return !hasOpenAPITagValue(t.CommentLines, tagValueFalse)
}
if hasOpenAPITagValue(t.CommentLines, tagValueTrue) {
return true
}
return false
},
},
if hasOpenAPITagValue(t.CommentLines, tagValueTrue) {
return true
}
return false
}
const (
@@ -155,35 +111,33 @@ type openAPIGen struct {
// TargetPackage is the package that will get GetOpenAPIDefinitions function returns all open API definitions.
targetPackage string
imports namer.ImportTracker
context *generator.Context
}
func NewOpenAPIGen(sanitizedName string, targetPackage string, context *generator.Context) generator.Generator {
func newOpenAPIGen(sanitizedName string, targetPackage string) generator.Generator {
return &openAPIGen{
DefaultGen: generator.DefaultGen{
OptionalName: sanitizedName,
},
imports: generator.NewImportTracker(),
targetPackage: targetPackage,
context: context,
}
}
const nameTmpl = "schema_$.type|private$"
func (g *openAPIGen) Namers(c *generator.Context) namer.NameSystems {
// Have the raw namer for this file track what it imports.
return namer.NameSystems{
"raw": namer.NewRawNamer(g.targetPackage, g.imports),
"private": &namer.NameStrategy{
Join: func(pre string, in []string, post string) string {
return strings.Join(in, "_")
},
PrependPackageNames: 4, // enough to fully qualify from k8s.io/api/...
},
}
}
func (g *openAPIGen) Filter(c *generator.Context, t *types.Type) bool {
// There is a conflict between this codegen and codecgen, we should avoid types generated for codecgen
if strings.HasPrefix(t.Name.Name, "codecSelfer") {
return false
}
return true
}
func (g *openAPIGen) isOtherPackage(pkg string) bool {
if pkg == g.targetPackage {
return false
@@ -215,18 +169,22 @@ func (g *openAPIGen) Init(c *generator.Context, w io.Writer) error {
sw := generator.NewSnippetWriter(w, c, "$", "$")
sw.Do("func GetOpenAPIDefinitions(ref $.ReferenceCallback|raw$) map[string]$.OpenAPIDefinition|raw$ {\n", argsFromType(nil))
sw.Do("return map[string]$.OpenAPIDefinition|raw${\n", argsFromType(nil))
return sw.Error()
}
func (g *openAPIGen) Finalize(c *generator.Context, w io.Writer) error {
sw := generator.NewSnippetWriter(w, c, "$", "$")
sw.Do("}\n", nil)
for _, t := range c.Order {
err := newOpenAPITypeWriter(sw).generateCall(t)
if err != nil {
return err
}
}
sw.Do("}\n", nil)
sw.Do("}\n\n", nil)
return sw.Error()
}
func (g *openAPIGen) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
glog.V(5).Infof("generating for type %v", t)
klog.V(5).Infof("generating for type %v", t)
sw := generator.NewSnippetWriter(w, c, "$", "$")
err := newOpenAPITypeWriter(sw).generate(t)
if err != nil {
@@ -243,10 +201,6 @@ func getJsonTags(m *types.Member) []string {
return strings.Split(jsonTag, ",")
}
func getPatchTags(m *types.Member) (string, string) {
return reflect.StructTag(m.Tags).Get(tagPatchMergeKey), reflect.StructTag(m.Tags).Get(tagPatchStrategy)
}
func getReferableName(m *types.Member) string {
jsonTags := getJsonTags(m)
if len(jsonTags) > 0 {
@@ -335,14 +289,14 @@ func (g openAPITypeWriter) generateMembers(t *types.Type, required []string) ([]
required = append(required, name)
}
if err = g.generateProperty(&m, t); err != nil {
glog.Errorf("Error when generating: %v, %v\n", name, m)
klog.Errorf("Error when generating: %v, %v\n", name, m)
return required, err
}
}
return required, nil
}
func (g openAPITypeWriter) generate(t *types.Type) error {
func (g openAPITypeWriter) generateCall(t *types.Type) error {
// Only generate for struct type and ignore the rest
switch t.Kind {
case types.Struct:
@@ -350,31 +304,37 @@ func (g openAPITypeWriter) generate(t *types.Type) error {
g.Do("\"$.$\": ", t.Name)
if hasOpenAPIDefinitionMethod(t) {
g.Do("$.type|raw${}.OpenAPIDefinition(),\n", args)
} else {
g.Do(nameTmpl+"(ref),\n", args)
}
}
return g.Error()
}
func (g openAPITypeWriter) generate(t *types.Type) error {
// Only generate for struct type and ignore the rest
switch t.Kind {
case types.Struct:
if hasOpenAPIDefinitionMethod(t) {
// already invoked directly
return nil
}
args := argsFromType(t)
g.Do("func "+nameTmpl+"(ref $.ReferenceCallback|raw$) $.OpenAPIDefinition|raw$ {\n", args)
if hasOpenAPIDefinitionMethods(t) {
// Since this generated snippet is part of a map:
//
// map[string]common.OpenAPIDefinition: {
// "TYPE_NAME": {
// Schema: spec.Schema{ ... },
// },
// }
//
// For compliance with gofmt -s it's important we elide the
// struct type. The type is implied by the map and will be
// removed otherwise.
g.Do("{\n"+
g.Do("return $.OpenAPIDefinition|raw${\n"+
"Schema: spec.Schema{\n"+
"SchemaProps: spec.SchemaProps{\n"+
"Type:$.type|raw${}.OpenAPISchemaType(),\n"+
"SchemaProps: spec.SchemaProps{\n", args)
g.generateDescription(t.CommentLines)
g.Do("Type:$.type|raw${}.OpenAPISchemaType(),\n"+
"Format:$.type|raw${}.OpenAPISchemaFormat(),\n"+
"},\n"+
"},\n"+
"},\n", args)
"}\n}\n\n", args)
return nil
}
g.Do("{\nSchema: spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
g.Do("return $.OpenAPIDefinition|raw${\nSchema: spec.Schema{\nSchemaProps: spec.SchemaProps{\n", args)
g.generateDescription(t.CommentLines)
g.Do("Properties: map[string]$.SpecSchemaType|raw${\n", args)
required, err := g.generateMembers(t, []string{})
@@ -386,7 +346,7 @@ func (g openAPITypeWriter) generate(t *types.Type) error {
g.Do("Required: []string{\"$.$\"},\n", strings.Join(required, "\",\""))
}
g.Do("},\n", nil)
if err := g.generateExtensions(t.CommentLines); err != nil {
if err := g.generateStructExtensions(t); err != nil {
return err
}
g.Do("},\n", nil)
@@ -406,70 +366,73 @@ func (g openAPITypeWriter) generate(t *types.Type) error {
}
g.Do("\"$.$\",", k)
}
g.Do("},\n},\n", nil)
g.Do("},\n}\n}\n\n", nil)
}
return nil
}
func (g openAPITypeWriter) generateExtensions(CommentLines []string) error {
tagValues := getOpenAPITagValue(CommentLines)
type NameValue struct {
Name, Value string
}
extensions := []NameValue{}
for _, val := range tagValues {
if strings.HasPrefix(val, tagExtensionPrefix) {
parts := strings.SplitN(val, ":", 2)
if len(parts) != 2 {
return fmt.Errorf("invalid extension value: %v", val)
}
extensions = append(extensions, NameValue{parts[0], parts[1]})
func (g openAPITypeWriter) generateStructExtensions(t *types.Type) error {
extensions, errors := parseExtensions(t.CommentLines)
// Initially, we will only log struct extension errors.
if len(errors) > 0 {
for _, e := range errors {
klog.V(2).Infof("[%s]: %s\n", t.String(), e)
}
}
patchMergeKeyTag, err := getSingleTagsValue(CommentLines, tagPatchMergeKey)
if err != nil {
return err
}
if len(patchMergeKeyTag) > 0 {
extensions = append(extensions, NameValue{tagExtensionPrefix + patchMergeKeyExtensionName, patchMergeKeyTag})
}
patchStrategyTag, err := getSingleTagsValue(CommentLines, tagPatchStrategy)
if err != nil {
return err
}
if len(patchStrategyTag) > 0 {
extensions = append(extensions, NameValue{tagExtensionPrefix + patchStrategyExtensionName, patchStrategyTag})
// TODO(seans3): Validate struct extensions here.
g.emitExtensions(extensions)
return nil
}
func (g openAPITypeWriter) generateMemberExtensions(m *types.Member, parent *types.Type) error {
extensions, parseErrors := parseExtensions(m.CommentLines)
validationErrors := validateMemberExtensions(extensions, m)
errors := append(parseErrors, validationErrors...)
// Initially, we will only log member extension errors.
if len(errors) > 0 {
errorPrefix := fmt.Sprintf("[%s] %s:", parent.String(), m.String())
for _, e := range errors {
klog.V(2).Infof("%s %s\n", errorPrefix, e)
}
}
g.emitExtensions(extensions)
return nil
}
func (g openAPITypeWriter) emitExtensions(extensions []extension) {
// If any extensions exist, then emit code to create them.
if len(extensions) == 0 {
return nil
return
}
g.Do("VendorExtensible: spec.VendorExtensible{\nExtensions: spec.Extensions{\n", nil)
for _, extension := range extensions {
g.Do("\"$.$\": ", extension.Name)
g.Do("\"$.$\",\n", extension.Value)
g.Do("\"$.$\": ", extension.xName)
if extension.hasMultipleValues() {
g.Do("[]string{\n", nil)
}
for _, value := range extension.values {
g.Do("\"$.$\",\n", value)
}
if extension.hasMultipleValues() {
g.Do("},\n", nil)
}
}
g.Do("},\n},\n", nil)
return nil
}
// TODO(#44005): Move this validation outside of this generator (probably to policy verifier)
func (g openAPITypeWriter) validatePatchTags(m *types.Member, parent *types.Type) error {
patchMergeKeyStructTag, patchStrategyStructTag := getPatchTags(m)
patchMergeKeyCommentTag, err := getSingleTagsValue(m.CommentLines, tagPatchMergeKey)
if err != nil {
return err
}
patchStrategyCommentTag, err := getSingleTagsValue(m.CommentLines, tagPatchStrategy)
if err != nil {
return err
}
if patchMergeKeyStructTag != patchMergeKeyCommentTag {
return fmt.Errorf("patchMergeKey in comment and struct tags should match for member (%s) of (%s)",
m.Name, parent.Name.String())
}
if patchStrategyStructTag != patchStrategyCommentTag {
return fmt.Errorf("patchStrategy in comment and struct tags should match for member (%s) of (%s)",
m.Name, parent.Name.String())
// TODO: Remove patch struct tag validation because they we are now consuming OpenAPI on server.
for _, tagKey := range tempPatchTags {
structTagValue := reflect.StructTag(m.Tags).Get(tagKey)
commentTagValue, err := getSingleTagsValue(m.CommentLines, tagKey)
if err != nil {
return err
}
if structTagValue != commentTagValue {
return fmt.Errorf("Tags in comment and struct should match for member (%s) of (%s)",
m.Name, parent.Name.String())
}
}
return nil
}
@@ -526,7 +489,7 @@ func (g openAPITypeWriter) generateProperty(m *types.Member, parent *types.Type)
return err
}
g.Do("\"$.$\": {\n", name)
if err := g.generateExtensions(m.CommentLines); err != nil {
if err := g.generateMemberExtensions(m, parent); err != nil {
return err
}
g.Do("SchemaProps: spec.SchemaProps{\n", nil)

View File

@@ -20,6 +20,7 @@ import (
"bytes"
"fmt"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
@@ -46,7 +47,7 @@ func construct(t *testing.T, files map[string]string, testNamer namer.Namer) (*p
return b, u, o
}
func testOpenAPITypeWritter(t *testing.T, code string) (error, *assert.Assertions, *bytes.Buffer) {
func testOpenAPITypeWriter(t *testing.T, code string) (error, error, *assert.Assertions, *bytes.Buffer, *bytes.Buffer) {
assert := assert.New(t)
var testFiles = map[string]string{
"base/foo/bar.go": code,
@@ -54,20 +55,33 @@ func testOpenAPITypeWritter(t *testing.T, code string) (error, *assert.Assertion
rawNamer := namer.NewRawNamer("o", nil)
namers := namer.NameSystems{
"raw": namer.NewRawNamer("", nil),
"private": &namer.NameStrategy{
Join: func(pre string, in []string, post string) string {
return strings.Join(in, "_")
},
PrependPackageNames: 4, // enough to fully qualify from k8s.io/api/...
},
}
builder, universe, _ := construct(t, testFiles, rawNamer)
context, err := generator.NewContext(builder, namers, "raw")
if err != nil {
t.Fatal(err)
}
buffer := &bytes.Buffer{}
sw := generator.NewSnippetWriter(buffer, context, "$", "$")
blahT := universe.Type(types.Name{Package: "base/foo", Name: "Blah"})
return newOpenAPITypeWriter(sw).generate(blahT), assert, buffer
callBuffer := &bytes.Buffer{}
callSW := generator.NewSnippetWriter(callBuffer, context, "$", "$")
callError := newOpenAPITypeWriter(callSW).generateCall(blahT)
funcBuffer := &bytes.Buffer{}
funcSW := generator.NewSnippetWriter(funcBuffer, context, "$", "$")
funcError := newOpenAPITypeWriter(funcSW).generate(blahT)
return callError, funcError, assert, callBuffer, funcBuffer
}
func TestSimple(t *testing.T) {
err, assert, buffer := testOpenAPITypeWritter(t, `
callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
package foo
// Blah is a test.
@@ -112,15 +126,24 @@ type Blah struct {
// +k8s:openapi-gen=x-kubernetes-member-tag:member_test
WithExtension string
// a member with struct tag as extension
// +patchStrategy=ps
// +patchStrategy=merge
// +patchMergeKey=pmk
WithStructTagExtension string `+"`"+`patchStrategy:"ps" patchMergeKey:"pmk"`+"`"+`
WithStructTagExtension string `+"`"+`patchStrategy:"merge" patchMergeKey:"pmk"`+"`"+`
// a member with a list type
// +listType=atomic
WithListType []string
}
`)
if err != nil {
t.Fatal(err)
if callErr != nil {
t.Fatal(callErr)
}
assert.Equal(`"base/foo.Blah": {
if funcErr != nil {
t.Fatal(funcErr)
}
assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref),
`, callBuffer.String())
assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "Blah is a test.",
@@ -246,7 +269,7 @@ Format: "",
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-patch-merge-key": "pmk",
"x-kubernetes-patch-strategy": "ps",
"x-kubernetes-patch-strategy": "merge",
},
},
SchemaProps: spec.SchemaProps{
@@ -255,8 +278,27 @@ Type: []string{"string"},
Format: "",
},
},
"WithListType": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "atomic",
},
Required: []string{"String","Int64","Int32","Int16","Int8","Uint","Uint64","Uint32","Uint16","Uint8","Byte","Bool","Float64","Float32","ByteArray","WithExtension","WithStructTagExtension"},
},
SchemaProps: spec.SchemaProps{
Description: "a member with a list type",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
Required: []string{"String","Int64","Int32","Int16","Int8","Uint","Uint64","Uint32","Uint16","Uint8","Byte","Bool","Float64","Float32","ByteArray","WithExtension","WithStructTagExtension","WithListType"},
},
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
@@ -266,12 +308,14 @@ Extensions: spec.Extensions{
},
Dependencies: []string{
},
},
`, buffer.String())
}
}
`, funcBuffer.String())
}
func TestFailingSample1(t *testing.T) {
err, assert, _ := testOpenAPITypeWritter(t, `
_, funcErr, assert, _, _ := testOpenAPITypeWriter(t, `
package foo
// Map sample tests openAPIGen.generateMapProperty method.
@@ -280,13 +324,13 @@ type Blah struct {
StringToArray map[string]map[string]string
}
`)
if assert.Error(err, "An error was expected") {
assert.Equal(err, fmt.Errorf("map Element kind Map is not supported in map[string]map[string]string"))
if assert.Error(funcErr, "An error was expected") {
assert.Equal(funcErr, fmt.Errorf("map Element kind Map is not supported in map[string]map[string]string"))
}
}
func TestFailingSample2(t *testing.T) {
err, assert, _ := testOpenAPITypeWritter(t, `
_, funcErr, assert, _, _ := testOpenAPITypeWriter(t, `
package foo
// Map sample tests openAPIGen.generateMapProperty method.
@@ -294,13 +338,13 @@ type Blah struct {
// A sample String to String map
StringToArray map[int]string
} `)
if assert.Error(err, "An error was expected") {
assert.Equal(err, fmt.Errorf("map with non-string keys are not supported by OpenAPI in map[int]string"))
if assert.Error(funcErr, "An error was expected") {
assert.Equal(funcErr, fmt.Errorf("map with non-string keys are not supported by OpenAPI in map[int]string"))
}
}
func TestCustomDef(t *testing.T) {
err, assert, buffer := testOpenAPITypeWritter(t, `
callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
package foo
import openapi "k8s.io/kube-openapi/pkg/common"
@@ -319,39 +363,53 @@ func (_ Blah) OpenAPIDefinition() openapi.OpenAPIDefinition {
}
}
`)
if err != nil {
t.Fatal(err)
if callErr != nil {
t.Fatal(callErr)
}
if funcErr != nil {
t.Fatal(funcErr)
}
assert.Equal(`"base/foo.Blah": foo.Blah{}.OpenAPIDefinition(),
`, buffer.String())
`, callBuffer.String())
assert.Equal(``, funcBuffer.String())
}
func TestCustomDefs(t *testing.T) {
err, assert, buffer := testOpenAPITypeWritter(t, `
callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
package foo
// Blah is a custom type
type Blah struct {
}
func (_ Blah) OpenAPISchemaType() []string { return []string{"string"} }
func (_ Blah) OpenAPISchemaFormat() string { return "date-time" }
`)
if err != nil {
t.Fatal(err)
if callErr != nil {
t.Fatal(callErr)
}
assert.Equal(`"base/foo.Blah": {
if funcErr != nil {
t.Fatal(funcErr)
}
assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref),
`, callBuffer.String())
assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "Blah is a custom type",
Type:foo.Blah{}.OpenAPISchemaType(),
Format:foo.Blah{}.OpenAPISchemaFormat(),
},
},
},
`, buffer.String())
}
}
`, funcBuffer.String())
}
func TestPointer(t *testing.T) {
err, assert, buffer := testOpenAPITypeWritter(t, `
callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
package foo
// PointerSample demonstrate pointer's properties
@@ -366,10 +424,16 @@ type Blah struct {
MapPointer *map[string]string
}
`)
if err != nil {
t.Fatal(err)
if callErr != nil {
t.Fatal(callErr)
}
assert.Equal(`"base/foo.Blah": {
if funcErr != nil {
t.Fatal(funcErr)
}
assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref),
`, callBuffer.String())
assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "PointerSample demonstrate pointer's properties",
@@ -421,12 +485,14 @@ Required: []string{"StringPointer","StructPointer","SlicePointer","MapPointer"},
},
Dependencies: []string{
"base/foo.Blah",},
},
`, buffer.String())
}
}
`, funcBuffer.String())
}
func TestNestedLists(t *testing.T) {
err, assert, buffer := testOpenAPITypeWritter(t, `
callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
package foo
// Blah is a test.
@@ -437,10 +503,16 @@ type Blah struct {
NestedList [][]int64
}
`)
if err != nil {
t.Fatal(err)
if callErr != nil {
t.Fatal(callErr)
}
assert.Equal(`"base/foo.Blah": {
if funcErr != nil {
t.Fatal(funcErr)
}
assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref),
`, callBuffer.String())
assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "Blah is a test.",
@@ -477,6 +549,77 @@ Extensions: spec.Extensions{
},
Dependencies: []string{
},
},
`, buffer.String())
}
}
`, funcBuffer.String())
}
func TestExtensions(t *testing.T) {
callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
package foo
// Blah is a test.
// +k8s:openapi-gen=true
// +k8s:openapi-gen=x-kubernetes-type-tag:type_test
type Blah struct {
// a member with a list type
// +listType=map
// +listMapKey=port
// +listMapKey=protocol
WithListField []string
}
`)
if callErr != nil {
t.Fatal(callErr)
}
if funcErr != nil {
t.Fatal(funcErr)
}
assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref),
`, callBuffer.String())
assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "Blah is a test.",
Properties: map[string]spec.Schema{
"WithListField": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-map-keys": []string{
"port",
"protocol",
},
"x-kubernetes-list-type": "map",
},
},
SchemaProps: spec.SchemaProps{
Description: "a member with a list type",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
Required: []string{"WithListField"},
},
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-type-tag": "type_test",
},
},
},
Dependencies: []string{
},
}
}
`, funcBuffer.String())
}

4
vendor/k8s.io/kube-openapi/pkg/generators/rules/OWNERS generated vendored Executable file
View File

@@ -0,0 +1,4 @@
reviewers:
- roycaihw
approvers:
- roycaihw

23
vendor/k8s.io/kube-openapi/pkg/generators/rules/doc.go generated vendored Normal file
View File

@@ -0,0 +1,23 @@
/*
Copyright 2018 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 rules contains API rules that are enforced in OpenAPI spec generation
// as part of the machinery. Files under this package implement APIRule interface
// which evaluates Go type and produces list of API rule violations.
//
// Implementations of APIRule should be added to API linter under openAPIGen code-
// generator to get integrated in the generation process.
package rules

View File

@@ -0,0 +1,172 @@
/*
Copyright 2018 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 rules
import (
"reflect"
"strings"
"k8s.io/kube-openapi/pkg/util/sets"
"k8s.io/gengo/types"
)
var (
// Blacklist of JSON tags that should skip match evaluation
jsonTagBlacklist = sets.NewString(
// Omitted field is ignored by the package
"-",
)
// Blacklist of JSON names that should skip match evaluation
jsonNameBlacklist = sets.NewString(
// Empty name is used for inline struct field (e.g. metav1.TypeMeta)
"",
// Special case for object and list meta
"metadata",
)
// List of substrings that aren't allowed in Go name and JSON name
disallowedNameSubstrings = sets.NewString(
// Underscore is not allowed in either name
"_",
// Dash is not allowed in either name. Note that since dash is a valid JSON tag, this should be checked
// after JSON tag blacklist check.
"-",
)
)
/*
NamesMatch implements APIRule interface.
Go field names must be CamelCase. JSON field names must be camelCase. Other than capitalization of the
initial letter, the two should almost always match. No underscores nor dashes in either.
This rule verifies the convention "Other than capitalization of the initial letter, the two should almost always match."
Examples (also in unit test):
Go name | JSON name | match
podSpec false
PodSpec podSpec true
PodSpec PodSpec false
podSpec podSpec false
PodSpec spec false
Spec podSpec false
JSONSpec jsonSpec true
JSONSpec jsonspec false
HTTPJSONSpec httpJSONSpec true
NOTE: this validator cannot tell two sequential all-capital words from one word, therefore the case below
is also considered matched.
HTTPJSONSpec httpjsonSpec true
NOTE: JSON names in jsonNameBlacklist should skip evaluation
true
podSpec true
podSpec - true
podSpec metadata true
*/
type NamesMatch struct{}
// Name returns the name of APIRule
func (n *NamesMatch) Name() string {
return "names_match"
}
// Validate evaluates API rule on type t and returns a list of field names in
// the type that violate the rule. Empty field name [""] implies the entire
// type violates the rule.
func (n *NamesMatch) Validate(t *types.Type) ([]string, error) {
fields := make([]string, 0)
// Only validate struct type and ignore the rest
switch t.Kind {
case types.Struct:
for _, m := range t.Members {
goName := m.Name
jsonTag, ok := reflect.StructTag(m.Tags).Lookup("json")
// Distinguish empty JSON tag and missing JSON tag. Empty JSON tag / name is
// allowed (in JSON name blacklist) but missing JSON tag is invalid.
if !ok {
fields = append(fields, goName)
continue
}
if jsonTagBlacklist.Has(jsonTag) {
continue
}
jsonName := strings.Split(jsonTag, ",")[0]
if !namesMatch(goName, jsonName) {
fields = append(fields, goName)
}
}
}
return fields, nil
}
// namesMatch evaluates if goName and jsonName match the API rule
// TODO: Use an off-the-shelf CamelCase solution instead of implementing this logic. The following existing
// packages have been tried out:
// github.com/markbates/inflect
// github.com/segmentio/go-camelcase
// github.com/iancoleman/strcase
// github.com/fatih/camelcase
// Please see https://github.com/kubernetes/kube-openapi/pull/83#issuecomment-400842314 for more details
// about why they don't satisfy our need. What we need can be a function that detects an acronym at the
// beginning of a string.
func namesMatch(goName, jsonName string) bool {
if jsonNameBlacklist.Has(jsonName) {
return true
}
if !isAllowedName(goName) || !isAllowedName(jsonName) {
return false
}
if strings.ToLower(goName) != strings.ToLower(jsonName) {
return false
}
// Go field names must be CamelCase. JSON field names must be camelCase.
if !isCapital(goName[0]) || isCapital(jsonName[0]) {
return false
}
for i := 0; i < len(goName); i++ {
if goName[i] == jsonName[i] {
// goName[0:i-1] is uppercase and jsonName[0:i-1] is lowercase, goName[i:]
// and jsonName[i:] should match;
// goName[i] should be lowercase if i is equal to 1, e.g.:
// goName | jsonName
// PodSpec podSpec
// or uppercase if i is greater than 1, e.g.:
// goname | jsonName
// JSONSpec jsonSpec
// This is to rule out cases like:
// goname | jsonName
// JSONSpec jsonspec
return goName[i:] == jsonName[i:] && (i == 1 || isCapital(goName[i]))
}
}
return true
}
// isCaptical returns true if one character is capital
func isCapital(b byte) bool {
return b >= 'A' && b <= 'Z'
}
// isAllowedName checks the list of disallowedNameSubstrings and returns true if name doesn't contain
// any disallowed substring.
func isAllowedName(name string) bool {
for _, substr := range disallowedNameSubstrings.UnsortedList() {
if strings.Contains(name, substr) {
return false
}
}
return true
}

View File

@@ -0,0 +1,359 @@
/*
Copyright 2018 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 rules
import (
"reflect"
"testing"
"k8s.io/gengo/types"
)
func TestNamesMatch(t *testing.T) {
tcs := []struct {
// name of test case
name string
t *types.Type
// expected list of violation fields
expected []string
}{
// The comments are in format of {goName, jsonName, match},
// {"PodSpec", "podSpec", true},
{
name: "simple",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "PodSpec",
Tags: `json:"podSpec"`,
},
},
},
expected: []string{},
},
// {"PodSpec", "podSpec", true},
{
name: "multiple_json_tags",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "PodSpec",
Tags: `json:"podSpec,omitempty"`,
},
},
},
expected: []string{},
},
// {"PodSpec", "podSpec", true},
{
name: "protobuf_tag",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "PodSpec",
Tags: `json:"podSpec,omitempty" protobuf:"bytes,1,opt,name=podSpec"`,
},
},
},
expected: []string{},
},
// {"", "podSpec", false},
{
name: "empty",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "",
Tags: `json:"podSpec"`,
},
},
},
expected: []string{""},
},
// {"PodSpec", "PodSpec", false},
{
name: "CamelCase_CamelCase",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "PodSpec",
Tags: `json:"PodSpec"`,
},
},
},
expected: []string{"PodSpec"},
},
// {"podSpec", "podSpec", false},
{
name: "camelCase_camelCase",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "podSpec",
Tags: `json:"podSpec"`,
},
},
},
expected: []string{"podSpec"},
},
// {"PodSpec", "spec", false},
{
name: "short_json_name",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "PodSpec",
Tags: `json:"spec"`,
},
},
},
expected: []string{"PodSpec"},
},
// {"Spec", "podSpec", false},
{
name: "long_json_name",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "Spec",
Tags: `json:"podSpec"`,
},
},
},
expected: []string{"Spec"},
},
// {"JSONSpec", "jsonSpec", true},
{
name: "acronym",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "JSONSpec",
Tags: `json:"jsonSpec"`,
},
},
},
expected: []string{},
},
// {"JSONSpec", "jsonspec", false},
{
name: "acronym_invalid",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "JSONSpec",
Tags: `json:"jsonspec"`,
},
},
},
expected: []string{"JSONSpec"},
},
// {"HTTPJSONSpec", "httpJSONSpec", true},
{
name: "multiple_acronym",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "HTTPJSONSpec",
Tags: `json:"httpJSONSpec"`,
},
},
},
expected: []string{},
},
// // NOTE: this validator cannot tell two sequential all-capital words from one word,
// // therefore the case below is also considered matched.
// {"HTTPJSONSpec", "httpjsonSpec", true},
{
name: "multiple_acronym_as_one",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "HTTPJSONSpec",
Tags: `json:"httpjsonSpec"`,
},
},
},
expected: []string{},
},
// NOTE: JSON tags in jsonTagBlacklist should skip evaluation
{
name: "blacklist_tag_dash",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "podSpec",
Tags: `json:"-"`,
},
},
},
expected: []string{},
},
// {"PodSpec", "-", false},
{
name: "invalid_json_name_dash",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "PodSpec",
Tags: `json:"-,"`,
},
},
},
expected: []string{"PodSpec"},
},
// NOTE: JSON names in jsonNameBlacklist should skip evaluation
// {"", "", true},
{
name: "unspecified",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "",
Tags: `json:""`,
},
},
},
expected: []string{},
},
// {"podSpec", "", true},
{
name: "blacklist_empty",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "podSpec",
Tags: `json:""`,
},
},
},
expected: []string{},
},
// {"podSpec", "metadata", true},
{
name: "blacklist_metadata",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "podSpec",
Tags: `json:"metadata"`,
},
},
},
expected: []string{},
},
{
name: "non_struct",
t: &types.Type{
Kind: types.Map,
},
expected: []string{},
},
{
name: "no_json_tag",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "PodSpec",
Tags: `podSpec`,
},
},
},
expected: []string{"PodSpec"},
},
// NOTE: this is to expand test coverage
// {"S", "s", true},
{
name: "single_character",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "S",
Tags: `json:"s"`,
},
},
},
expected: []string{},
},
// NOTE: names with disallowed substrings should fail evaluation
// {"Pod-Spec", "pod-Spec", false},
{
name: "disallowed_substring_dash",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "Pod-Spec",
Tags: `json:"pod-Spec"`,
},
},
},
expected: []string{"Pod-Spec"},
},
// {"Pod_Spec", "pod_Spec", false},
{
name: "disallowed_substring_underscore",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "Pod_Spec",
Tags: `json:"pod_Spec"`,
},
},
},
expected: []string{"Pod_Spec"},
},
}
n := &NamesMatch{}
for _, tc := range tcs {
if violations, _ := n.Validate(tc.t); !reflect.DeepEqual(violations, tc.expected) {
t.Errorf("unexpected validation result: test name %v, want: %v, got: %v",
tc.name, tc.expected, violations)
}
}
}
// TestRuleName tests the Name of API rule. This is to expand test coverage
func TestRuleName(t *testing.T) {
ruleName := "names_match"
n := &NamesMatch{}
if n.Name() != ruleName {
t.Errorf("unexpected API rule name: want: %v, got: %v", ruleName, n.Name())
}
}

View File

@@ -0,0 +1,64 @@
/*
Copyright 2018 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 rules
import (
"reflect"
"strings"
"k8s.io/gengo/types"
)
// OmitEmptyMatchCase implements APIRule interface.
// "omitempty" must appear verbatim (no case variants).
type OmitEmptyMatchCase struct{}
func (n *OmitEmptyMatchCase) Name() string {
return "omitempty_match_case"
}
func (n *OmitEmptyMatchCase) Validate(t *types.Type) ([]string, error) {
fields := make([]string, 0)
// Only validate struct type and ignore the rest
switch t.Kind {
case types.Struct:
for _, m := range t.Members {
goName := m.Name
jsonTag, ok := reflect.StructTag(m.Tags).Lookup("json")
if !ok {
continue
}
parts := strings.Split(jsonTag, ",")
if len(parts) < 2 {
// no tags other than name
continue
}
if parts[0] == "-" {
// not serialized
continue
}
for _, part := range parts[1:] {
if strings.EqualFold(part, "omitempty") && part != "omitempty" {
fields = append(fields, goName)
}
}
}
}
return fields, nil
}

View File

@@ -0,0 +1,110 @@
/*
Copyright 2018 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 rules
import (
"reflect"
"testing"
"k8s.io/gengo/types"
)
func TestOmitEmptyMatchCase(t *testing.T) {
tcs := []struct {
// name of test case
name string
t *types.Type
// expected list of violation fields
expected []string
}{
{
name: "simple",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "PodSpec",
Tags: `json:"podSpec"`,
},
},
},
expected: []string{},
},
{
name: "unserialized",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "PodSpec",
Tags: `json:"-,inline"`,
},
},
},
expected: []string{},
},
{
name: "named_omitEmpty",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "OmitEmpty",
Tags: `json:"omitEmpty,inline"`,
},
},
},
expected: []string{},
},
{
name: "valid",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "PodSpec",
Tags: `json:"podSpec,omitempty"`,
},
},
},
expected: []string{},
},
{
name: "invalid",
t: &types.Type{
Kind: types.Struct,
Members: []types.Member{
types.Member{
Name: "PodSpec",
Tags: `json:"podSpec,omitEmpty"`,
},
},
},
expected: []string{"PodSpec"},
},
}
n := &OmitEmptyMatchCase{}
for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
if violations, _ := n.Validate(tc.t); !reflect.DeepEqual(violations, tc.expected) {
t.Errorf("unexpected validation result: want: %v, got: %v", tc.expected, violations)
}
})
}
}

View File

@@ -58,7 +58,6 @@ type OpenAPIService struct {
// rwMutex protects All members of this service.
rwMutex sync.RWMutex
orgSpec *spec.Swagger
lastModified time.Time
specBytes []byte
@@ -84,7 +83,7 @@ func computeETag(data []byte) string {
// and switch to a single /openapi/v2 endpoint in Kubernetes 1.10. The design doc and deprecation process
// are tracked at: https://docs.google.com/document/d/19lEqE9lc4yHJ3WJAJxS_G7TcORIJXGHyq3wpwcH28nU.
//
// BuildAndRegisterOpenAPIService builds the spec and registers a handler to provides access to it.
// BuildAndRegisterOpenAPIService builds the spec and registers a handler to provide access to it.
// Use this method if your OpenAPI spec is static. If you want to update the spec, use BuildOpenAPISpec then RegisterOpenAPIService.
func BuildAndRegisterOpenAPIService(servePath string, webServices []*restful.WebService, config *common.Config, handler common.PathHandler) (*OpenAPIService, error) {
spec, err := builder.BuildOpenAPISpec(webServices, config)
@@ -98,7 +97,7 @@ func BuildAndRegisterOpenAPIService(servePath string, webServices []*restful.Web
// and switch to a single /openapi/v2 endpoint in Kubernetes 1.10. The design doc and deprecation process
// are tracked at: https://docs.google.com/document/d/19lEqE9lc4yHJ3WJAJxS_G7TcORIJXGHyq3wpwcH28nU.
//
// RegisterOpenAPIService registers a handler to provides access to provided swagger spec.
// RegisterOpenAPIService registers a handler to provide access to provided swagger spec.
// Note: servePath should end with ".json" as the RegisterOpenAPIService assume it is serving a
// json file and will also serve .pb and .gz files.
func RegisterOpenAPIService(openapiSpec *spec.Swagger, servePath string, handler common.PathHandler) (*OpenAPIService, error) {
@@ -161,7 +160,6 @@ func (o *OpenAPIService) getSwaggerPbGzBytes() ([]byte, string, time.Time) {
}
func (o *OpenAPIService) UpdateSpec(openapiSpec *spec.Swagger) (err error) {
orgSpec := openapiSpec
specBytes, err := json.MarshalIndent(openapiSpec, " ", " ")
if err != nil {
return err
@@ -181,7 +179,6 @@ func (o *OpenAPIService) UpdateSpec(openapiSpec *spec.Swagger) (err error) {
o.rwMutex.Lock()
defer o.rwMutex.Unlock()
o.orgSpec = orgSpec
o.specBytes = specBytes
o.specPb = specPb
o.specPbGz = specPbGz
@@ -214,7 +211,7 @@ func toGzip(data []byte) []byte {
return buf.Bytes()
}
// RegisterOpenAPIVersionedService registers a handler to provides access to provided swagger spec.
// RegisterOpenAPIVersionedService registers a handler to provide access to provided swagger spec.
func RegisterOpenAPIVersionedService(openapiSpec *spec.Swagger, servePath string, handler common.PathHandler) (*OpenAPIService, error) {
o := OpenAPIService{}
if err := o.UpdateSpec(openapiSpec); err != nil {
@@ -264,7 +261,7 @@ func RegisterOpenAPIVersionedService(openapiSpec *spec.Swagger, servePath string
return &o, nil
}
// BuildAndRegisterOpenAPIVersionedService builds the spec and registers a handler to provides access to it.
// BuildAndRegisterOpenAPIVersionedService builds the spec and registers a handler to provide access to it.
// Use this method if your OpenAPI spec is static. If you want to update the spec, use BuildOpenAPISpec then RegisterOpenAPIVersionedService.
func BuildAndRegisterOpenAPIVersionedService(servePath string, webServices []*restful.WebService, config *common.Config, handler common.PathHandler) (*OpenAPIService, error) {
spec, err := builder.BuildOpenAPISpec(webServices, config)

140
vendor/k8s.io/kube-openapi/pkg/idl/doc.go generated vendored Normal file
View File

@@ -0,0 +1,140 @@
/*
Copyright 2018 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.
*/
// The IDL package describes comment directives that may be applied to
// API types and fields.
package idl
// ListType annotates a list to further describe its topology. It may
// have 3 possible values: "atomic", "map", or "set". Note that there is
// no default, and the generation step will fail if a list is found that
// is missing the tag.
//
// This tag MUST only be used on lists, or the generation step will
// fail.
//
// Atomic
//
// Example:
// +listType=atomic
//
// Atomic lists will be entirely replaced when updated. This tag may be
// used on any type of list (struct, scalar, ...).
//
// Using this tag will generate the following OpenAPI extension:
// "x-kubernetes-list-type": "atomic"
//
// Map
//
// Example:
// +listType=map
//
// These lists are like maps in that their elements have a non-index key
// used to identify them. Order is preserved upon merge. Using the map
// tag on a list with non-struct elements will result in an error during
// the generation step.
//
// Using this tag will generate the following OpenAPI extension:
// "x-kubernetes-list-type": "map"
//
// Set
//
// Example:
// +listType=set
//
// Sets are lists that must not have multiple times the same value. Each
// value must be a scalar (or another atomic type).
//
// Using this tag will generate the following OpenAPI extension:
// "x-kubernetes-list-type": "set"
type ListType string
// ListMapKey annotates map lists by specifying the key used as the index of the map.
//
// This tag MUST only be used on lists that have the listType=map
// attribute, or the generation step will fail. Also, the value
// specified for this attribute must be a scalar typed field of the
// child structure (no nesting is supported).
//
// An example of how this can be used is shown in the ListType (map) example.
//
// Example:
// +listMapKey=name
//
// Using this tag will generate the following OpenAPI extension:
// "x-kubernetes-list-map-key": "name"
type ListMapKey string
// MapType annotates a map to further describe its topology. It may
// have only one value: "atomic". Atomic means that the entire map is
// considered as a whole, rather than as distinct values.
//
// By default, a map will be considered as a set of distinct values that
// can be updated individually. This default WILL NOT generate any
// openapi extension, as this will also be interpreted as the default
// behavior in the openapi definition.
//
// This tag MUST only be used on maps, or the generation step will fail.
//
// Atomic
//
// Example:
// +mapType=atomic
//
// Atomic maps will be entirely replaced when updated. This tag may be
// used on any map.
//
// Using this tag will generate the following OpenAPI extension:
// "x-kubernetes-map-type": "atomic"
type MapType string
// OpenAPIGen needs to be described.
type OpenAPIGen string
// Optional needs to be described.
type Optional string
// PatchMergeKey needs to be described.
type PatchMergeKey string
// PatchStrategy needs to be described.
type PatchStrategy string
// StructType annotates a struct to further describe its topology. It may
// have only one value: "atomic". Atomic means that the entire struct is
// considered as a whole, rather than as distinct values.
//
// By default, a struct will be considered as a set of distinct values that
// can be updated individually. This default WILL NOT generate any
// openapi extension, as this will also be interpreted as the default
// behavior in the openapi definition.
//
// This tag MUST only be used on structs, or the generation step will fail.
//
// Atomic
//
// Example:
// +structType=atomic
//
// Atomic structs will be entirely replaced when updated. This tag may be
// used on any struct.
//
// Using this tag will generate the following OpenAPI extension:
// "x-kubernetes-struct-type": "atomic"
type StructType string
// Union is TBD.
type Union string

56
vendor/k8s.io/kube-openapi/pkg/idl/listtype_test.go generated vendored Normal file
View File

@@ -0,0 +1,56 @@
/*
Copyright 2018 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 idl_test
// This example shows how to use the listType map attribute and how to
// specify a key to identify elements of the list. The listMapKey
// attribute is used to specify that Name is the key of the map.
func ExampleListType_map() {
type SomeStruct struct {
Name string
Value string
}
type SomeAPI struct {
// +listType=map
// +listMapKey=name
elements []SomeStruct
}
}
// This example shows how to use the listType set attribute to specify
// that this list should be treated as a set: items in the list can't be
// duplicated.
func ExampleListType_set() {
type SomeAPI struct {
// +listType=set
keys []string
}
}
// This example shows how to use the listType atomic attribute to
// specify that this list should be treated as a whole.
func ExampleListType_atomic() {
type SomeStruct struct {
Name string
Value string
}
type SomeAPI struct {
// +listType=atomic
elements []SomeStruct
}
}

26
vendor/k8s.io/kube-openapi/pkg/idl/maptype_test.go generated vendored Normal file
View File

@@ -0,0 +1,26 @@
/*
Copyright 2018 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 idl_test
// This example shows how to use the mapType atomic attribute to
// specify that this map should be treated as a whole.
func ExampleMapType_atomic() {
type SomeAPI struct {
// +mapType=atomic
elements map[string]string
}
}

30
vendor/k8s.io/kube-openapi/pkg/idl/structtype_test.go generated vendored Normal file
View File

@@ -0,0 +1,30 @@
/*
Copyright 2018 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 idl_test
// This example shows how to use the structType atomic attribute to
// specify that this struct should be treated as a whole.
func ExampleStructType_atomic() {
type SomeStruct struct {
Name string
Value string
}
type SomeAPI struct {
// +structType=atomic
elements SomeStruct
}
}

242
vendor/k8s.io/kube-openapi/pkg/schemaconv/smd.go generated vendored Normal file
View File

@@ -0,0 +1,242 @@
/*
Copyright 2017 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 schemaconv
import (
"errors"
"fmt"
"path"
"strings"
"k8s.io/kube-openapi/pkg/util/proto"
"sigs.k8s.io/structured-merge-diff/schema"
)
// ToSchema converts openapi definitions into a schema suitable for structured
// merge (i.e. kubectl apply v2).
func ToSchema(models proto.Models) (*schema.Schema, error) {
c := convert{
input: models,
output: &schema.Schema{},
}
if err := c.convertAll(); err != nil {
return nil, err
}
return c.output, nil
}
type convert struct {
input proto.Models
output *schema.Schema
currentName string
current *schema.Atom
errorMessages []string
}
func (c *convert) push(name string, a *schema.Atom) *convert {
return &convert{
input: c.input,
output: c.output,
currentName: name,
current: a,
}
}
func (c *convert) top() *schema.Atom { return c.current }
func (c *convert) pop(c2 *convert) {
c.errorMessages = append(c.errorMessages, c2.errorMessages...)
}
func (c *convert) convertAll() error {
for _, name := range c.input.ListModels() {
model := c.input.LookupModel(name)
c.insertTypeDef(name, model)
}
if len(c.errorMessages) > 0 {
return errors.New(strings.Join(c.errorMessages, "\n"))
}
return nil
}
func (c *convert) reportError(format string, args ...interface{}) {
c.errorMessages = append(c.errorMessages,
c.currentName+": "+fmt.Sprintf(format, args...),
)
}
func (c *convert) insertTypeDef(name string, model proto.Schema) {
def := schema.TypeDef{
Name: name,
}
c2 := c.push(name, &def.Atom)
model.Accept(c2)
c.pop(c2)
if def.Atom == (schema.Atom{}) {
// This could happen if there were a top-level reference.
return
}
c.output.Types = append(c.output.Types, def)
}
func (c *convert) makeRef(model proto.Schema) schema.TypeRef {
var tr schema.TypeRef
if r, ok := model.(*proto.Ref); ok {
// reference a named type
_, n := path.Split(r.Reference())
tr.NamedType = &n
} else {
// compute the type inline
c2 := c.push("inlined in "+c.currentName, &tr.Inlined)
model.Accept(c2)
c.pop(c2)
if tr == (schema.TypeRef{}) {
// emit warning?
tr.Inlined.Untyped = &schema.Untyped{}
}
}
return tr
}
func (c *convert) VisitKind(k *proto.Kind) {
a := c.top()
a.Struct = &schema.Struct{}
for _, name := range k.FieldOrder {
member := k.Fields[name]
tr := c.makeRef(member)
a.Struct.Fields = append(a.Struct.Fields, schema.StructField{
Name: name,
Type: tr,
})
}
// TODO: Get element relationship when we start adding it to the spec.
}
func toStringSlice(o interface{}) (out []string, ok bool) {
switch t := o.(type) {
case []interface{}:
for _, v := range t {
switch vt := v.(type) {
case string:
out = append(out, vt)
}
}
return out, true
}
return nil, false
}
func (c *convert) VisitArray(a *proto.Array) {
atom := c.top()
atom.List = &schema.List{
ElementRelationship: schema.Atomic,
}
l := atom.List
l.ElementType = c.makeRef(a.SubType)
ext := a.GetExtensions()
if val, ok := ext["x-kubernetes-list-type"]; ok {
if val == "atomic" {
l.ElementRelationship = schema.Atomic
} else if val == "set" {
l.ElementRelationship = schema.Associative
} else if val == "map" {
l.ElementRelationship = schema.Associative
if keys, ok := ext["x-kubernetes-list-map-keys"]; ok {
if keyNames, ok := toStringSlice(keys); ok {
l.Keys = keyNames
} else {
c.reportError("uninterpreted map keys: %#v", keys)
}
} else {
c.reportError("missing map keys")
}
} else {
c.reportError("unknown list type %v", val)
l.ElementRelationship = schema.Atomic
}
} else if val, ok := ext["x-kubernetes-patch-strategy"]; ok {
if val == "merge" || val == "merge,retainKeys" {
l.ElementRelationship = schema.Associative
if key, ok := ext["x-kubernetes-patch-merge-key"]; ok {
if keyName, ok := key.(string); ok {
l.Keys = []string{keyName}
} else {
c.reportError("uninterpreted merge key: %#v", key)
}
} else {
// It's not an error for this to be absent, it
// means it's a set.
}
} else if val == "retainKeys" {
} else {
c.reportError("unknown patch strategy %v", val)
l.ElementRelationship = schema.Atomic
}
}
}
func (c *convert) VisitMap(m *proto.Map) {
a := c.top()
a.Map = &schema.Map{}
a.Map.ElementType = c.makeRef(m.SubType)
// TODO: Get element relationship when we start putting it into the
// spec.
}
func (c *convert) VisitPrimitive(p *proto.Primitive) {
a := c.top()
ptr := func(s schema.Scalar) *schema.Scalar { return &s }
switch p.Type {
case proto.Integer:
a.Scalar = ptr(schema.Numeric)
case proto.Number:
a.Scalar = ptr(schema.Numeric)
case proto.String:
switch p.Format {
case "":
a.Scalar = ptr(schema.String)
case "byte":
// byte really means []byte and is encoded as a string.
a.Scalar = ptr(schema.String)
case "int-or-string":
a.Untyped = &schema.Untyped{}
case "date-time":
a.Untyped = &schema.Untyped{}
default:
a.Untyped = &schema.Untyped{}
}
case proto.Boolean:
a.Scalar = ptr(schema.Boolean)
default:
a.Untyped = &schema.Untyped{}
}
}
func (c *convert) VisitArbitrary(a *proto.Arbitrary) {
c.top().Untyped = &schema.Untyped{}
}
func (c *convert) VisitReference(proto.Reference) {
// Do nothing, we handle references specially
}

68
vendor/k8s.io/kube-openapi/pkg/schemaconv/smd_test.go generated vendored Normal file
View File

@@ -0,0 +1,68 @@
/*
Copyright 2017 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 schemaconv
import (
"io/ioutil"
"path/filepath"
"testing"
yaml "gopkg.in/yaml.v2"
"k8s.io/kube-openapi/pkg/util/proto"
prototesting "k8s.io/kube-openapi/pkg/util/proto/testing"
)
var (
openAPIPath = filepath.Join("testdata", "swagger.json")
fakeSchema = prototesting.Fake{Path: openAPIPath}
expectedNewSchemaPath = filepath.Join("testdata", "new-schema.yaml")
)
func TestToSchema(t *testing.T) {
s, err := fakeSchema.OpenAPISchema()
if err != nil {
t.Fatal(err)
}
models, err := proto.NewOpenAPIData(s)
if err != nil {
t.Fatal(err)
}
ns, err := ToSchema(models)
if err != nil {
t.Fatal(err)
}
got, err := yaml.Marshal(ns)
if err != nil {
t.Fatal(err)
}
expect, err := ioutil.ReadFile(expectedNewSchemaPath)
if err != nil {
t.Fatalf("Unable to read golden data file %q: %v", expectedNewSchemaPath, err)
}
if string(expect) != string(got) {
t.Errorf("Computed schema did not match %q.", expectedNewSchemaPath)
t.Logf("To recompute this file, run:\n\tgo run ./cmd/openapi2smd/openapi2smd.go < %q > %q",
filepath.Join("pkg", "schemaconv", openAPIPath),
filepath.Join("pkg", "schemaconv", expectedNewSchemaPath),
)
t.Log("You can then use `git diff` to see the changes.")
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -21,8 +21,8 @@ import (
"sort"
"strings"
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
yaml "gopkg.in/yaml.v2"
"github.com/googleapis/gnostic/OpenAPIv2"
"gopkg.in/yaml.v2"
)
func newSchemaError(path *Path, format string, a ...interface{}) error {
@@ -126,12 +126,17 @@ func (d *Definitions) parseMap(s *openapi_v2.Schema, path *Path) (Schema, error)
if len(s.GetType().GetValue()) != 0 && s.GetType().GetValue()[0] != object {
return nil, newSchemaError(path, "invalid object type")
}
var sub Schema
if s.GetAdditionalProperties().GetSchema() == nil {
return nil, newSchemaError(path, "invalid object doesn't have additional properties")
}
sub, err := d.ParseSchema(s.GetAdditionalProperties().GetSchema(), path)
if err != nil {
return nil, err
sub = &Arbitrary{
BaseSchema: d.parseBaseSchema(s, path),
}
} else {
var err error
sub, err = d.ParseSchema(s.GetAdditionalProperties().GetSchema(), path)
if err != nil {
return nil, err
}
}
return &Map{
BaseSchema: d.parseBaseSchema(s, path),
@@ -148,12 +153,10 @@ func (d *Definitions) parsePrimitive(s *openapi_v2.Schema, path *Path) (Schema,
t = s.GetType().GetValue()[0]
}
switch t {
case String:
case Number:
case Integer:
case Boolean:
case "": // Some models are completely empty, and can be safely ignored.
// Do nothing
case String: // do nothing
case Number: // do nothing
case Integer: // do nothing
case Boolean: // do nothing
default:
return nil, newSchemaError(path, "Unknown primitive type: %q", t)
}
@@ -193,20 +196,24 @@ func (d *Definitions) parseKind(s *openapi_v2.Schema, path *Path) (Schema, error
}
fields := map[string]Schema{}
fieldOrder := []string{}
for _, namedSchema := range s.GetProperties().GetAdditionalProperties() {
var err error
path := path.FieldPath(namedSchema.GetName())
fields[namedSchema.GetName()], err = d.ParseSchema(namedSchema.GetValue(), &path)
name := namedSchema.GetName()
path := path.FieldPath(name)
fields[name], err = d.ParseSchema(namedSchema.GetValue(), &path)
if err != nil {
return nil, err
}
fieldOrder = append(fieldOrder, name)
}
return &Kind{
BaseSchema: d.parseBaseSchema(s, path),
RequiredFields: s.GetRequired(),
Fields: fields,
FieldOrder: fieldOrder,
}, nil
}
@@ -219,27 +226,38 @@ func (d *Definitions) parseArbitrary(s *openapi_v2.Schema, path *Path) (Schema,
// ParseSchema creates a walkable Schema from an openapi schema. While
// this function is public, it doesn't leak through the interface.
func (d *Definitions) ParseSchema(s *openapi_v2.Schema, path *Path) (Schema, error) {
objectTypes := s.GetType().GetValue()
if len(objectTypes) == 1 {
t := objectTypes[0]
switch t {
case object:
return d.parseMap(s, path)
case array:
return d.parseArray(s, path)
}
}
if s.GetXRef() != "" {
return d.parseReference(s, path)
}
if s.GetProperties() != nil {
return d.parseKind(s, path)
objectTypes := s.GetType().GetValue()
switch len(objectTypes) {
case 0:
// in the OpenAPI schema served by older k8s versions, object definitions created from structs did not include
// the type:object property (they only included the "properties" property), so we need to handle this case
if s.GetProperties() != nil {
return d.parseKind(s, path)
} else {
// Definition has no type and no properties. Treat it as an arbitrary value
// TODO: what if it has additionalProperties or patternProperties?
return d.parseArbitrary(s, path)
}
case 1:
t := objectTypes[0]
switch t {
case object:
if s.GetProperties() != nil {
return d.parseKind(s, path)
} else {
return d.parseMap(s, path)
}
case array:
return d.parseArray(s, path)
}
return d.parsePrimitive(s, path)
default:
// the OpenAPI generator never generates (nor it ever did in the past) OpenAPI type definitions with multiple types
return nil, newSchemaError(path, "definitions with multiple types aren't supported")
}
if len(objectTypes) == 0 || (len(objectTypes) == 1 && objectTypes[0] == "") {
return d.parseArbitrary(s, path)
}
return d.parsePrimitive(s, path)
}
// LookupModel is public through the interface of Models. It

View File

@@ -59,7 +59,7 @@ type SchemaVisitor interface {
}
// SchemaVisitorArbitrary is an additional visitor interface which handles
// arbitrary types. For backwards compatability, it's a separate interface
// arbitrary types. For backwards compatibility, it's a separate interface
// which is checked for at runtime.
type SchemaVisitorArbitrary interface {
SchemaVisitor
@@ -173,6 +173,8 @@ type Kind struct {
RequiredFields []string
// Maps field names to types.
Fields map[string]Schema
// FieldOrder reports the canonical order for the fields.
FieldOrder []string
}
var _ Schema = &Kind{}

View File

@@ -26,9 +26,10 @@ import (
"k8s.io/kube-openapi/pkg/util/proto/testing"
)
var fakeSchema = testing.Fake{Path: filepath.Join("testing", "swagger.json")}
var fakeSchema = testing.Fake{Path: filepath.Join("testdata", "swagger.json")}
var fakeSchemaNext = testing.Fake{Path: filepath.Join("testdata", "swagger_next.json")}
var _ = Describe("Reading apps/v1beta1/Deployment from openAPIData", func() {
var _ = Describe("Reading apps/v1beta1/Deployment from v1.8 openAPIData", func() {
var models proto.Models
BeforeEach(func() {
s, err := fakeSchema.OpenAPISchema()
@@ -132,6 +133,63 @@ var _ = Describe("Reading apps/v1beta1/Deployment from openAPIData", func() {
})
})
var _ = Describe("Reading apps/v1beta1/Deployment from v1.11 openAPIData", func() {
var models proto.Models
BeforeEach(func() {
s, err := fakeSchemaNext.OpenAPISchema()
Expect(err).To(BeNil())
models, err = proto.NewOpenAPIData(s)
Expect(err).To(BeNil())
})
model := "io.k8s.api.apps.v1beta1.Deployment"
var schema proto.Schema
It("should lookup the Schema by its model name", func() {
schema = models.LookupModel(model)
Expect(schema).ToNot(BeNil())
})
var deployment *proto.Kind
It("should be a Kind", func() {
deployment = schema.(*proto.Kind)
Expect(deployment).ToNot(BeNil())
})
})
var _ = Describe("Reading apps/v1beta1/ControllerRevision from v1.11 openAPIData", func() {
var models proto.Models
BeforeEach(func() {
s, err := fakeSchemaNext.OpenAPISchema()
Expect(err).To(BeNil())
models, err = proto.NewOpenAPIData(s)
Expect(err).To(BeNil())
})
model := "io.k8s.api.apps.v1beta1.ControllerRevision"
var schema proto.Schema
It("should lookup the Schema by its model name", func() {
schema = models.LookupModel(model)
Expect(schema).ToNot(BeNil())
})
var cr *proto.Kind
It("data property should be map[string]Arbitrary", func() {
cr = schema.(*proto.Kind)
Expect(cr).ToNot(BeNil())
Expect(cr.Fields).To(HaveKey("data"))
data := cr.Fields["data"].(*proto.Map)
Expect(data).ToNot(BeNil())
Expect(data.GetName()).To(Equal("Map of Arbitrary value (primitive, object or array)"))
Expect(data.GetPath().Get()).To(Equal([]string{"io.k8s.api.apps.v1beta1.ControllerRevision", ".data"}))
arbitrary := data.SubType.(*proto.Arbitrary)
Expect(arbitrary).ToNot(BeNil())
Expect(arbitrary.GetName()).To(Equal("Arbitrary value (primitive, object or array)"))
Expect(arbitrary.GetPath().Get()).To(Equal([]string{"io.k8s.api.apps.v1beta1.ControllerRevision", ".data"}))
})
})
var _ = Describe("Reading authorization.k8s.io/v1/SubjectAccessReview from openAPIData", func() {
var models proto.Models
BeforeEach(func() {

View File

@@ -1112,7 +1112,7 @@
"type": "string"
},
"kind": {
"description": "Expected values Shared: mulitple blob disks per storage account Dedicated: single blob disk per storage account Managed: azure managed data disk (only in managed availability set). defaults to shared",
"description": "Expected values Shared: multiple blob disks per storage account Dedicated: single blob disk per storage account Managed: azure managed data disk (only in managed availability set). defaults to shared",
"type": "string"
},
"readOnly": {
@@ -1672,7 +1672,7 @@
],
"properties": {
"names": {
"description": "Names by which this image is known. e.g. [\"gcr.io/google_containers/hyperkube:v1.0.7\", \"dockerhub.io/google_containers/hyperkube:v1.0.7\"]",
"description": "Names by which this image is known. e.g. [\"k8s.gcr.io/hyperkube:v1.0.7\", \"dockerhub.io/google_containers/hyperkube:v1.0.7\"]",
"type": "array",
"items": {
"type": "string"

File diff suppressed because it is too large Load Diff

View File

@@ -60,3 +60,9 @@ func (f *Fake) OpenAPISchema() (*openapi_v2.Document, error) {
})
return f.document, f.err
}
type Empty struct{}
func (Empty) OpenAPISchema() (*openapi_v2.Document, error) {
return nil, nil
}

View File

@@ -0,0 +1,49 @@
/*
Copyright 2017 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 validation
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/config"
. "github.com/onsi/ginkgo/types"
. "github.com/onsi/gomega"
"fmt"
"testing"
)
func TestOpenapi(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecsWithDefaultAndCustomReporters(t, "Openapi Suite", []Reporter{newlineReporter{}})
}
// Print a newline after the default newlineReporter due to issue
// https://github.com/jstemmer/go-junit-report/issues/31
type newlineReporter struct{}
func (newlineReporter) SpecSuiteWillBegin(config GinkgoConfigType, summary *SuiteSummary) {}
func (newlineReporter) BeforeSuiteDidRun(setupSummary *SetupSummary) {}
func (newlineReporter) AfterSuiteDidRun(setupSummary *SetupSummary) {}
func (newlineReporter) SpecWillRun(specSummary *SpecSummary) {}
func (newlineReporter) SpecDidComplete(specSummary *SpecSummary) {}
// SpecSuiteDidEnd Prints a newline between "35 Passed | 0 Failed | 0 Pending | 0 Skipped" and "--- PASS:"
func (newlineReporter) SpecSuiteDidEnd(summary *SuiteSummary) { fmt.Printf("\n") }

View File

@@ -0,0 +1,369 @@
/*
Copyright 2017 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 validation_test
import (
"fmt"
"path/filepath"
"github.com/ghodss/yaml"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kube-openapi/pkg/util/proto/testing"
"k8s.io/kube-openapi/pkg/util/proto/validation"
)
var fakeSchema = testing.Fake{Path: filepath.Join("..", "testdata", "swagger.json")}
func Validate(models proto.Models, model string, data string) []error {
var obj interface{}
if err := yaml.Unmarshal([]byte(data), &obj); err != nil {
return []error{fmt.Errorf("pre-validation: failed to parse yaml: %v", err)}
}
schema := models.LookupModel(model)
if schema == nil {
return []error{fmt.Errorf("pre-validation: couldn't find model %s", model)}
}
return validation.ValidateModel(obj, schema, model)
}
var _ = Describe("resource validation using OpenAPI Schema", func() {
var models proto.Models
BeforeEach(func() {
s, err := fakeSchema.OpenAPISchema()
Expect(err).To(BeNil())
models, err = proto.NewOpenAPIData(s)
Expect(err).To(BeNil())
})
It("finds Deployment in Schema and validates it", func() {
err := Validate(models, "io.k8s.api.apps.v1beta1.Deployment", `
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
name: redis-master
name: name
spec:
replicas: 1
template:
metadata:
labels:
app: redis
spec:
containers:
- image: redis
name: redis
`)
Expect(err).To(BeNil())
})
It("validates a valid pod", func() {
err := Validate(models, "io.k8s.api.core.v1.Pod", `
apiVersion: v1
kind: Pod
metadata:
labels:
name: redis-master
name: name
spec:
containers:
- args:
- this
- is
- an
- ok
- command
image: gcr.io/fake_project/fake_image:fake_tag
name: master
`)
Expect(err).To(BeNil())
})
It("finds invalid command (string instead of []string) in Json Pod", func() {
err := Validate(models, "io.k8s.api.core.v1.Pod", `
{
"kind": "Pod",
"apiVersion": "v1",
"metadata": {
"name": "name",
"labels": {
"name": "redis-master"
}
},
"spec": {
"containers": [
{
"name": "master",
"image": "gcr.io/fake_project/fake_image:fake_tag",
"args": "this is a bad command"
}
]
}
}
`)
Expect(err).To(Equal([]error{
validation.ValidationError{
Path: "io.k8s.api.core.v1.Pod.spec.containers[0].args",
Err: validation.InvalidTypeError{
Path: "io.k8s.api.core.v1.Container.args",
Expected: "array",
Actual: "string",
},
},
}))
})
It("fails because hostPort is string instead of int", func() {
err := Validate(models, "io.k8s.api.core.v1.Pod", `
{
"kind": "Pod",
"apiVersion": "v1",
"metadata": {
"name": "apache-php",
"labels": {
"name": "apache-php"
}
},
"spec": {
"volumes": [{
"name": "shared-disk"
}],
"containers": [
{
"name": "apache-php",
"image": "gcr.io/fake_project/fake_image:fake_tag",
"ports": [
{
"name": "apache",
"hostPort": "13380",
"containerPort": 80,
"protocol": "TCP"
}
],
"volumeMounts": [
{
"name": "shared-disk",
"mountPath": "/var/www/html"
}
]
}
]
}
}
`)
Expect(err).To(Equal([]error{
validation.ValidationError{
Path: "io.k8s.api.core.v1.Pod.spec.containers[0].ports[0].hostPort",
Err: validation.InvalidTypeError{
Path: "io.k8s.api.core.v1.ContainerPort.hostPort",
Expected: "integer",
Actual: "string",
},
},
}))
})
It("fails because volume is not an array of object", func() {
err := Validate(models, "io.k8s.api.core.v1.Pod", `
{
"kind": "Pod",
"apiVersion": "v1",
"metadata": {
"name": "apache-php",
"labels": {
"name": "apache-php"
}
},
"spec": {
"volumes": [
"name": "shared-disk"
],
"containers": [
{
"name": "apache-php",
"image": "gcr.io/fake_project/fake_image:fake_tag",
"ports": [
{
"name": "apache",
"hostPort": 13380,
"containerPort": 80,
"protocol": "TCP"
}
],
"volumeMounts": [
{
"name": "shared-disk",
"mountPath": "/var/www/html"
}
]
}
]
}
}
`)
Expect(err).To(BeNil())
})
It("fails because some string lists have empty strings", func() {
err := Validate(models, "io.k8s.api.core.v1.Pod", `
apiVersion: v1
kind: Pod
metadata:
labels:
name: redis-master
name: name
spec:
containers:
- image: gcr.io/fake_project/fake_image:fake_tag
name: master
args:
-
command:
-
`)
Expect(err).To(Equal([]error{
validation.ValidationError{
Path: "io.k8s.api.core.v1.Pod.spec.containers[0].args",
Err: validation.InvalidObjectTypeError{
Path: "io.k8s.api.core.v1.Pod.spec.containers[0].args[0]",
Type: "nil",
},
},
validation.ValidationError{
Path: "io.k8s.api.core.v1.Pod.spec.containers[0].command",
Err: validation.InvalidObjectTypeError{
Path: "io.k8s.api.core.v1.Pod.spec.containers[0].command[0]",
Type: "nil",
},
},
}))
})
It("fails if required fields are missing", func() {
err := Validate(models, "io.k8s.api.core.v1.Pod", `
apiVersion: v1
kind: Pod
metadata:
labels:
name: redis-master
name: name
spec:
containers:
- command: ["my", "command"]
`)
Expect(err).To(Equal([]error{
validation.ValidationError{
Path: "io.k8s.api.core.v1.Pod.spec.containers[0]",
Err: validation.MissingRequiredFieldError{
Path: "io.k8s.api.core.v1.Container",
Field: "name",
},
},
validation.ValidationError{
Path: "io.k8s.api.core.v1.Pod.spec.containers[0]",
Err: validation.MissingRequiredFieldError{
Path: "io.k8s.api.core.v1.Container",
Field: "image",
},
},
}))
})
It("fails if required fields are empty", func() {
err := Validate(models, "io.k8s.api.core.v1.Pod", `
apiVersion: v1
kind: Pod
metadata:
labels:
name: redis-master
name: name
spec:
containers:
- image:
name:
`)
Expect(err).To(Equal([]error{
validation.ValidationError{
Path: "io.k8s.api.core.v1.Pod.spec.containers[0]",
Err: validation.MissingRequiredFieldError{
Path: "io.k8s.api.core.v1.Container",
Field: "name",
},
},
validation.ValidationError{
Path: "io.k8s.api.core.v1.Pod.spec.containers[0]",
Err: validation.MissingRequiredFieldError{
Path: "io.k8s.api.core.v1.Container",
Field: "image",
},
},
}))
})
It("is fine with empty non-mandatory fields", func() {
err := Validate(models, "io.k8s.api.core.v1.Pod", `
apiVersion: v1
kind: Pod
metadata:
labels:
name: redis-master
name: name
spec:
containers:
- image: image
name: name
command:
`)
Expect(err).To(BeNil())
})
It("fails because apiVersion is not provided", func() {
err := Validate(models, "io.k8s.api.core.v1.Pod", `
kind: Pod
metadata:
name: name
spec:
containers:
- name: name
image: image
`)
Expect(err).To(BeNil())
})
It("fails because apiVersion type is not string and kind is not provided", func() {
err := Validate(models, "io.k8s.api.core.v1.Pod", `
apiVersion: 1
metadata:
name: name
spec:
containers:
- name: name
image: image
`)
Expect(err).To(BeNil())
})
})

27
vendor/k8s.io/kube-openapi/pkg/util/sets/empty.go generated vendored Normal file
View File

@@ -0,0 +1,27 @@
/*
Copyright 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.
*/
// Code generated by set-gen. DO NOT EDIT.
// NOTE: This file is copied from k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/util/sets/empty.go
// because in Kubernetes we don't allowed vendor code to import staging code. See
// https://github.com/kubernetes/kube-openapi/pull/90 for more details.
package sets
// Empty is public since it is used by some internal API objects for conversions between external
// string arrays and internal sets, and conversion logic requires public types today.
type Empty struct{}

207
vendor/k8s.io/kube-openapi/pkg/util/sets/string.go generated vendored Normal file
View File

@@ -0,0 +1,207 @@
/*
Copyright 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.
*/
// Code generated by set-gen. DO NOT EDIT.
// NOTE: This file is copied from k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/util/sets/string.go
// because in Kubernetes we don't allowed vendor code to import staging code. See
// https://github.com/kubernetes/kube-openapi/pull/90 for more details.
package sets
import (
"reflect"
"sort"
)
// sets.String is a set of strings, implemented via map[string]struct{} for minimal memory consumption.
type String map[string]Empty
// NewString creates a String from a list of values.
func NewString(items ...string) String {
ss := String{}
ss.Insert(items...)
return ss
}
// StringKeySet creates a String from a keys of a map[string](? extends interface{}).
// If the value passed in is not actually a map, this will panic.
func StringKeySet(theMap interface{}) String {
v := reflect.ValueOf(theMap)
ret := String{}
for _, keyValue := range v.MapKeys() {
ret.Insert(keyValue.Interface().(string))
}
return ret
}
// Insert adds items to the set.
func (s String) Insert(items ...string) {
for _, item := range items {
s[item] = Empty{}
}
}
// Delete removes all items from the set.
func (s String) Delete(items ...string) {
for _, item := range items {
delete(s, item)
}
}
// Has returns true if and only if item is contained in the set.
func (s String) Has(item string) bool {
_, contained := s[item]
return contained
}
// HasAll returns true if and only if all items are contained in the set.
func (s String) HasAll(items ...string) bool {
for _, item := range items {
if !s.Has(item) {
return false
}
}
return true
}
// HasAny returns true if any items are contained in the set.
func (s String) HasAny(items ...string) bool {
for _, item := range items {
if s.Has(item) {
return true
}
}
return false
}
// Difference returns a set of objects that are not in s2
// For example:
// s1 = {a1, a2, a3}
// s2 = {a1, a2, a4, a5}
// s1.Difference(s2) = {a3}
// s2.Difference(s1) = {a4, a5}
func (s String) Difference(s2 String) String {
result := NewString()
for key := range s {
if !s2.Has(key) {
result.Insert(key)
}
}
return result
}
// Union returns a new set which includes items in either s1 or s2.
// For example:
// s1 = {a1, a2}
// s2 = {a3, a4}
// s1.Union(s2) = {a1, a2, a3, a4}
// s2.Union(s1) = {a1, a2, a3, a4}
func (s1 String) Union(s2 String) String {
result := NewString()
for key := range s1 {
result.Insert(key)
}
for key := range s2 {
result.Insert(key)
}
return result
}
// Intersection returns a new set which includes the item in BOTH s1 and s2
// For example:
// s1 = {a1, a2}
// s2 = {a2, a3}
// s1.Intersection(s2) = {a2}
func (s1 String) Intersection(s2 String) String {
var walk, other String
result := NewString()
if s1.Len() < s2.Len() {
walk = s1
other = s2
} else {
walk = s2
other = s1
}
for key := range walk {
if other.Has(key) {
result.Insert(key)
}
}
return result
}
// IsSuperset returns true if and only if s1 is a superset of s2.
func (s1 String) IsSuperset(s2 String) bool {
for item := range s2 {
if !s1.Has(item) {
return false
}
}
return true
}
// Equal returns true if and only if s1 is equal (as a set) to s2.
// Two sets are equal if their membership is identical.
// (In practice, this means same elements, order doesn't matter)
func (s1 String) Equal(s2 String) bool {
return len(s1) == len(s2) && s1.IsSuperset(s2)
}
type sortableSliceOfString []string
func (s sortableSliceOfString) Len() int { return len(s) }
func (s sortableSliceOfString) Less(i, j int) bool { return lessString(s[i], s[j]) }
func (s sortableSliceOfString) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// List returns the contents as a sorted string slice.
func (s String) List() []string {
res := make(sortableSliceOfString, 0, len(s))
for key := range s {
res = append(res, key)
}
sort.Sort(res)
return []string(res)
}
// UnsortedList returns the slice with contents in random order.
func (s String) UnsortedList() []string {
res := make([]string, 0, len(s))
for key := range s {
res = append(res, key)
}
return res
}
// Returns a single element from the set.
func (s String) PopAny() (string, bool) {
for key := range s {
s.Delete(key)
return key, true
}
var zeroValue string
return zeroValue, false
}
// Len returns the size of the set.
func (s String) Len() int {
return len(s)
}
func lessString(lhs, rhs string) bool {
return lhs < rhs
}

View File

@@ -16,7 +16,10 @@ limitations under the License.
package util
import "strings"
import (
"reflect"
"strings"
)
// ToCanonicalName converts Golang package/type name into canonical OpenAPI name.
// Examples:
@@ -37,3 +40,20 @@ func ToCanonicalName(name string) string {
}
return strings.Join(nameParts, ".")
}
// GetCanonicalTypeName will find the canonical type name of a sample object, removing
// the "vendor" part of the path
func GetCanonicalTypeName(model interface{}) string {
t := reflect.TypeOf(model)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.PkgPath() == "" {
return t.Name()
}
path := t.PkgPath()
if strings.Contains(path, "/vendor/") {
path = path[strings.Index(path, "/vendor/")+len("/vendor/"):]
}
return path + "." + t.Name()
}

58
vendor/k8s.io/kube-openapi/pkg/util/util_test.go generated vendored Normal file
View File

@@ -0,0 +1,58 @@
/*
Copyright 2018 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 util
import (
"reflect"
"testing"
)
func TestCanonicalName(t *testing.T) {
var tests = []struct {
input string
expected string
}{
{"k8s.io/api/core/v1.Pod", "io.k8s.api.core.v1.Pod"},
{"k8s.io/api/networking/v1/NetworkPolicy", "io.k8s.api.networking.v1.NetworkPolicy"},
{"k8s.io/api/apps/v1beta2.Scale", "io.k8s.api.apps.v1beta2.Scale"},
{"servicecatalog.k8s.io/foo/bar/v1alpha1.Baz", "io.k8s.servicecatalog.foo.bar.v1alpha1.Baz"},
}
for _, test := range tests {
if got := ToCanonicalName(test.input); got != test.expected {
t.Errorf("ToCanonicalName(%q) = %v", test.input, got)
}
}
}
type TestType struct{}
func TestGetCanonicalTypeName(t *testing.T) {
var tests = []struct {
input interface{}
expected string
}{
{TestType{}, "k8s.io/kube-openapi/pkg/util.TestType"},
{&TestType{}, "k8s.io/kube-openapi/pkg/util.TestType"},
}
for _, test := range tests {
if got := GetCanonicalTypeName(test.input); got != test.expected {
t.Errorf("GetCanonicalTypeName(%q) = %v", reflect.TypeOf(test.input), got)
}
}
}

40
vendor/k8s.io/kube-openapi/test/integration/README.md generated vendored Normal file
View File

@@ -0,0 +1,40 @@
# Kube OpenAPI Integration Tests
## Running the integration tests
Within the current directory:
```bash
$ go test -v .
```
## Generating the golden Swagger definition file and API rule violation report
First, run the generator to create `openapi_generated.go` file which specifies
the `OpenAPIDefinition` for each type, and generate the golden API rule
violation report file . Note that if you do not pass a report
filename (`./testdata/golden.report` in the command below) to let the generator
to print API rule violations to the file, the generator will return error to stderr
on API rule violations.
```bash
$ go run ../../cmd/openapi-gen/openapi-gen.go -i "./testdata/listtype,./testdata/dummytype" -o pkg -p generated -O openapi_generated -r ./testdata/golden.report
```
The generated file `pkg/generated/openapi_generated.go` should have been created.
Next, run the OpenAPI builder to create the Swagger file which includes
the definitions. The output file named `golden.json` will be output in
the current directory.
```bash
$ go run builder/main.go testdata/golden.json
```
After the golden spec is generated, please clean up the generated file
`pkg/generated/openapi_generated.go` before you commit. It's an intermediate product that doesn't need to be updated in kube-openapi repository. The checked-in file is kept minimum to make sure that `test/integration/builder` compiles. Please run:
```base
$ git checkout pkg/generated/openapi_generated.go
```
to discard any local change.

View File

@@ -0,0 +1,116 @@
/*
Copyright 2018 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 main
import (
"encoding/json"
"io/ioutil"
"log"
"os"
"github.com/emicklei/go-restful"
"github.com/go-openapi/spec"
"k8s.io/kube-openapi/pkg/builder"
"k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/test/integration/pkg/generated"
)
// TODO: Change this to output the generated swagger to stdout.
const defaultSwaggerFile = "generated.json"
func main() {
// Get the name of the generated swagger file from the args
// if it exists; otherwise use the default file name.
swaggerFilename := defaultSwaggerFile
if len(os.Args) > 1 {
swaggerFilename = os.Args[1]
}
// Generate the definition names from the map keys returned
// from GetOpenAPIDefinitions. Anonymous function returning empty
// Ref is not used.
var defNames []string
for name, _ := range generated.GetOpenAPIDefinitions(func(name string) spec.Ref {
return spec.Ref{}
}) {
defNames = append(defNames, name)
}
// Create a minimal builder config, then call the builder with the definition names.
config := createOpenAPIBuilderConfig()
config.GetDefinitions = generated.GetOpenAPIDefinitions
// Build the Paths using a simple WebService for the final spec
swagger, serr := builder.BuildOpenAPISpec(createWebServices(), config)
if serr != nil {
log.Fatalf("ERROR: %s", serr.Error())
}
// Generate the definitions for the passed type names to put in the final spec.
// Note that in reality one should run BuildOpenAPISpec to build the entire spec. We
// separate the steps of building Paths and building Definitions here, because we
// only have a simple WebService which doesn't wire the definitions up.
definitionSwagger, err := builder.BuildOpenAPIDefinitionsForResources(config, defNames...)
if err != nil {
log.Fatalf("ERROR: %s", err.Error())
}
// Copy the generated definitions into the final swagger.
swagger.Definitions = definitionSwagger.Definitions
// Marshal the swagger spec into JSON, then write it out.
specBytes, err := json.MarshalIndent(swagger, " ", " ")
if err != nil {
log.Fatalf("json marshal error: %s", err.Error())
}
err = ioutil.WriteFile(swaggerFilename, specBytes, 0644)
if err != nil {
log.Fatalf("stdout write error: %s", err.Error())
}
}
// CreateOpenAPIBuilderConfig hard-codes some values in the API builder
// config for testing.
func createOpenAPIBuilderConfig() *common.Config {
return &common.Config{
ProtocolList: []string{"https"},
IgnorePrefixes: []string{"/swaggerapi"},
Info: &spec.Info{
InfoProps: spec.InfoProps{
Title: "Integration Test",
Version: "1.0",
},
},
ResponseDefinitions: map[string]spec.Response{
"NotFound": spec.Response{
ResponseProps: spec.ResponseProps{
Description: "Entity not found.",
},
},
},
CommonResponses: map[int]spec.Response{
404: *spec.ResponseRef("#/responses/NotFound"),
},
}
}
// createWebServices hard-codes a simple WebService which only defines a GET path
// for testing.
func createWebServices() []*restful.WebService {
w := new(restful.WebService)
// Define a dummy GET /test endpoint
w = w.Route(w.GET("test").
To(func(*restful.Request, *restful.Response) {}))
return []*restful.WebService{w}
}

View File

@@ -0,0 +1,159 @@
/*
Copyright 2018 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 integration
import (
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
)
const (
testdataDir = "./testdata"
inputDir = testdataDir + "/listtype" + "," + testdataDir + "/dummytype"
outputBase = "pkg"
outputPackage = "generated"
outputBaseFileName = "openapi_generated"
generatedSwaggerFileName = "generated.json"
generatedReportFileName = "generated.report"
goldenSwaggerFileName = "golden.json"
goldenReportFileName = "golden.report"
timeoutSeconds = 5.0
)
func TestGenerators(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Integration Test Suite")
}
var _ = Describe("Open API Definitions Generation", func() {
var (
workingDirectory string
tempDir string
terr error
openAPIGenPath string
)
testdataFile := func(filename string) string { return filepath.Join(testdataDir, filename) }
generatedFile := func(filename string) string { return filepath.Join(tempDir, filename) }
BeforeSuite(func() {
// Explicitly manage working directory
abs, err := filepath.Abs("")
Expect(err).ShouldNot(HaveOccurred())
workingDirectory = abs
// Create a temporary directory for generated swagger files.
tempDir, terr = ioutil.TempDir("./", "openapi")
Expect(terr).ShouldNot(HaveOccurred())
// Build the OpenAPI code generator.
By("building openapi-gen")
binary_path, berr := gexec.Build("../../cmd/openapi-gen/openapi-gen.go")
Expect(berr).ShouldNot(HaveOccurred())
openAPIGenPath = binary_path
// Run the OpenAPI code generator, creating OpenAPIDefinition code
// to be compiled into builder.
By("processing go idl with openapi-gen")
gr := generatedFile(generatedReportFileName)
command := exec.Command(openAPIGenPath,
"-i", inputDir,
"-o", outputBase,
"-p", outputPackage,
"-O", outputBaseFileName,
"-r", gr,
)
command.Dir = workingDirectory
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
Expect(err).ShouldNot(HaveOccurred())
Eventually(session, timeoutSeconds).Should(gexec.Exit(0))
By("writing swagger")
// Create the OpenAPI swagger builder.
binary_path, berr = gexec.Build("./builder/main.go")
Expect(berr).ShouldNot(HaveOccurred())
// Execute the builder, generating an OpenAPI swagger file with definitions.
gs := generatedFile(generatedSwaggerFileName)
By("writing swagger to " + gs)
command = exec.Command(binary_path, gs)
command.Dir = workingDirectory
session, err = gexec.Start(command, GinkgoWriter, GinkgoWriter)
Expect(err).ShouldNot(HaveOccurred())
Eventually(session, timeoutSeconds).Should(gexec.Exit(0))
})
AfterSuite(func() {
os.RemoveAll(tempDir)
gexec.CleanupBuildArtifacts()
})
Describe("openapi-gen --verify", func() {
It("Verifies that the existing files are correct", func() {
command := exec.Command(openAPIGenPath,
"-i", inputDir,
"-o", outputBase,
"-p", outputPackage,
"-O", outputBaseFileName,
"-r", testdataFile(goldenReportFileName),
"--verify-only",
)
command.Dir = workingDirectory
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
Expect(err).ShouldNot(HaveOccurred())
Eventually(session, timeoutSeconds).Should(gexec.Exit(0))
})
})
Describe("Validating OpenAPI Definition Generation", func() {
It("Generated OpenAPI swagger definitions should match golden files", func() {
// Diff the generated swagger against the golden swagger. Exit code should be zero.
command := exec.Command(
"diff",
testdataFile(goldenSwaggerFileName),
generatedFile(generatedSwaggerFileName),
)
command.Dir = workingDirectory
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
Expect(err).ShouldNot(HaveOccurred())
Eventually(session, timeoutSeconds).Should(gexec.Exit(0))
})
})
Describe("Validating API Rule Violation Reporting", func() {
It("Generated API rule violations should match golden report files", func() {
// Diff the generated report against the golden report. Exit code should be zero.
command := exec.Command(
"diff",
testdataFile(goldenReportFileName),
generatedFile(generatedReportFileName),
)
command.Dir = workingDirectory
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
Expect(err).ShouldNot(HaveOccurred())
Eventually(session, timeoutSeconds).Should(gexec.Exit(0))
})
})
})

View File

@@ -0,0 +1,278 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by openapi-gen.go. DO NOT EDIT.
// This file was autogenerated by openapi-gen. Do not edit it manually!
package generated
import (
spec "github.com/go-openapi/spec"
common "k8s.io/kube-openapi/pkg/common"
)
func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
return map[string]common.OpenAPIDefinition{
"./testdata/dummytype.Bar": schema__testdata_dummytype_Bar(ref),
"./testdata/dummytype.Baz": schema__testdata_dummytype_Baz(ref),
"./testdata/dummytype.Foo": schema__testdata_dummytype_Foo(ref),
"./testdata/dummytype.Waldo": schema__testdata_dummytype_Waldo(ref),
"./testdata/listtype.AtomicList": schema__testdata_listtype_AtomicList(ref),
"./testdata/listtype.Item": schema__testdata_listtype_Item(ref),
"./testdata/listtype.MapList": schema__testdata_listtype_MapList(ref),
"./testdata/listtype.SetList": schema__testdata_listtype_SetList(ref),
}
}
func schema__testdata_dummytype_Bar(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Properties: map[string]spec.Schema{
"ViolationBehind": {
SchemaProps: spec.SchemaProps{
Type: []string{"boolean"},
Format: "",
},
},
"Violation": {
SchemaProps: spec.SchemaProps{
Type: []string{"boolean"},
Format: "",
},
},
},
Required: []string{"ViolationBehind", "Violation"},
},
},
Dependencies: []string{},
}
}
func schema__testdata_dummytype_Baz(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Properties: map[string]spec.Schema{
"Violation": {
SchemaProps: spec.SchemaProps{
Type: []string{"boolean"},
Format: "",
},
},
"ViolationBehind": {
SchemaProps: spec.SchemaProps{
Type: []string{"boolean"},
Format: "",
},
},
},
Required: []string{"Violation", "ViolationBehind"},
},
},
Dependencies: []string{},
}
}
func schema__testdata_dummytype_Foo(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Properties: map[string]spec.Schema{
"Second": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"First": {
SchemaProps: spec.SchemaProps{
Type: []string{"integer"},
Format: "int32",
},
},
},
Required: []string{"Second", "First"},
},
},
Dependencies: []string{},
}
}
func schema__testdata_dummytype_Waldo(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Properties: map[string]spec.Schema{
"First": {
SchemaProps: spec.SchemaProps{
Type: []string{"integer"},
Format: "int32",
},
},
"Second": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"First", "Second"},
},
},
Dependencies: []string{},
}
}
func schema__testdata_listtype_AtomicList(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Properties: map[string]spec.Schema{
"Field": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "atomic",
},
},
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
Required: []string{"Field"},
},
},
Dependencies: []string{},
}
}
func schema__testdata_listtype_Item(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Properties: map[string]spec.Schema{
"Protocol": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"Port": {
SchemaProps: spec.SchemaProps{
Type: []string{"integer"},
Format: "int32",
},
},
"a": {
SchemaProps: spec.SchemaProps{
Type: []string{"integer"},
Format: "int32",
},
},
"b": {
SchemaProps: spec.SchemaProps{
Type: []string{"integer"},
Format: "int32",
},
},
"c": {
SchemaProps: spec.SchemaProps{
Type: []string{"integer"},
Format: "int32",
},
},
},
Required: []string{"Protocol", "Port"},
},
},
Dependencies: []string{},
}
}
func schema__testdata_listtype_MapList(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Properties: map[string]spec.Schema{
"Field": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-map-keys": "port",
"x-kubernetes-list-type": "map",
},
},
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Ref: ref("./testdata/listtype.Item"),
},
},
},
},
},
},
Required: []string{"Field"},
},
},
Dependencies: []string{
"./testdata/listtype.Item"},
}
}
func schema__testdata_listtype_SetList(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Properties: map[string]spec.Schema{
"Field": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "set",
},
},
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
Required: []string{"Field"},
},
},
Dependencies: []string{},
}
}

View File

@@ -0,0 +1,36 @@
// The package is intended for testing the openapi-gen API rule
// checker. The API rule violations are in format of:
//
// `{rule-name},{package},{type},{(optional) field}`
//
// The checker should sort the violations before
// reporting to a file or stderr.
//
// We have the dummytype package separately from the listtype
// package to test the sorting behavior on package level, e.g.
//
// -i "./testdata/listtype,./testdata/dummytype"
// -i "./testdata/dummytype,./testdata/listtype"
//
// The violations from dummytype should always come first in
// report.
package dummytype
// +k8s:openapi-gen=true
type Foo struct {
Second string
First int
}
// +k8s:openapi-gen=true
type Bar struct {
ViolationBehind bool
Violation bool
}
// +k8s:openapi-gen=true
type Baz struct {
Violation bool
ViolationBehind bool
}

View File

@@ -0,0 +1,24 @@
// The package is intended for testing the openapi-gen API rule
// checker. The API rule violations are in format of:
//
// `{rule-name},{package},{type},{(optional) field}`
//
// The checker should sort the violations before
// reporting to a file or stderr.
//
// We have the dummytype package separately from the listtype
// package to test the sorting behavior on package level, e.g.
//
// -i "./testdata/listtype,./testdata/dummytype"
// -i "./testdata/dummytype,./testdata/listtype"
//
// The violations from dummytype should always come first in
// report.
package dummytype
// +k8s:openapi-gen=true
type Waldo struct {
First int
Second string
}

View File

@@ -0,0 +1,157 @@
{
"swagger": "2.0",
"info": {
"title": "Integration Test",
"version": "1.0"
},
"paths": {
"/test": {
"get": {
"schemes": [
"https"
],
"operationId": "func1",
"responses": {
"404": {
"$ref": "#/responses/NotFound"
}
}
}
}
},
"definitions": {
"dummytype.Bar": {
"required": [
"ViolationBehind",
"Violation"
],
"properties": {
"Violation": {
"type": "boolean"
},
"ViolationBehind": {
"type": "boolean"
}
}
},
"dummytype.Baz": {
"required": [
"Violation",
"ViolationBehind"
],
"properties": {
"Violation": {
"type": "boolean"
},
"ViolationBehind": {
"type": "boolean"
}
}
},
"dummytype.Foo": {
"required": [
"Second",
"First"
],
"properties": {
"First": {
"type": "integer",
"format": "int32"
},
"Second": {
"type": "string"
}
}
},
"dummytype.Waldo": {
"required": [
"First",
"Second"
],
"properties": {
"First": {
"type": "integer",
"format": "int32"
},
"Second": {
"type": "string"
}
}
},
"listtype.AtomicList": {
"required": [
"Field"
],
"properties": {
"Field": {
"type": "array",
"items": {
"type": "string"
},
"x-kubernetes-list-type": "atomic"
}
}
},
"listtype.Item": {
"required": [
"Protocol",
"Port"
],
"properties": {
"Port": {
"type": "integer",
"format": "int32"
},
"Protocol": {
"type": "string"
},
"a": {
"type": "integer",
"format": "int32"
},
"b": {
"type": "integer",
"format": "int32"
},
"c": {
"type": "integer",
"format": "int32"
}
}
},
"listtype.MapList": {
"required": [
"Field"
],
"properties": {
"Field": {
"type": "array",
"items": {
"$ref": "#/definitions/listtype.Item"
},
"x-kubernetes-list-map-keys": "port",
"x-kubernetes-list-type": "map"
}
}
},
"listtype.SetList": {
"required": [
"Field"
],
"properties": {
"Field": {
"type": "array",
"items": {
"type": "string"
},
"x-kubernetes-list-type": "set"
}
}
}
},
"responses": {
"NotFound": {
"description": "Entity not found."
}
}
}

View File

@@ -0,0 +1,14 @@
API rule violation: names_match,./testdata/dummytype,Bar,Violation
API rule violation: names_match,./testdata/dummytype,Bar,ViolationBehind
API rule violation: names_match,./testdata/dummytype,Baz,Violation
API rule violation: names_match,./testdata/dummytype,Baz,ViolationBehind
API rule violation: names_match,./testdata/dummytype,Foo,First
API rule violation: names_match,./testdata/dummytype,Foo,Second
API rule violation: names_match,./testdata/dummytype,Waldo,First
API rule violation: names_match,./testdata/dummytype,Waldo,Second
API rule violation: names_match,./testdata/listtype,AtomicList,Field
API rule violation: names_match,./testdata/listtype,Item,Port
API rule violation: names_match,./testdata/listtype,Item,Protocol
API rule violation: names_match,./testdata/listtype,MapList,Field
API rule violation: names_match,./testdata/listtype,SetList,Field
API rule violation: omitempty_match_case,./testdata/listtype,Item,C

View File

@@ -0,0 +1,7 @@
package listtype
// +k8s:openapi-gen=true
type AtomicList struct {
// +listType=atomic
Field []string
}

View File

@@ -0,0 +1,20 @@
package listtype
// +k8s:openapi-gen=true
type MapList struct {
// +listType=map
// +listMapKey=port
Field []Item
}
// +k8s:openapi-gen=true
type Item struct {
Protocol string
Port int
// +optional
A int `json:"a"`
// +optional
B int `json:"b,omitempty"`
// +optional
C int `json:"c,omitEmpty"`
}

View File

@@ -0,0 +1,7 @@
package listtype
// +k8s:openapi-gen=true
type SetList struct {
// +listType=set
Field []string
}