add prune and remove unused packages

This commit is contained in:
Michelle Au
2019-03-08 14:54:43 -08:00
parent f59b58d164
commit 8c0accad66
17240 changed files with 27 additions and 4750030 deletions

View File

@@ -1,20 +0,0 @@
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
# Intellij IDEA files
.idea/
*.iml
.vscode

View File

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

View File

@@ -1,9 +0,0 @@
# 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

@@ -1,401 +0,0 @@
{
"ImportPath": "k8s.io/kube-openapi",
"GoVersion": "go1.8",
"GodepVersion": "v79",
"Packages": [
"./..."
],
"Deps": [
{
"ImportPath": "bitbucket.org/ww/goautoneg",
"Comment": "null-5",
"Rev": "75cd24fc2f2c2a2088577d12123ddee5f54e0675"
},
{
"ImportPath": "github.com/NYTimes/gziphandler",
"Rev": "56545f4a5d46df9a6648819d1664c3a03a13ffdb"
},
{
"ImportPath": "github.com/PuerkitoBio/purell",
"Comment": "v1.0.0",
"Rev": "8a290539e2e8629dbc4e6bad948158f790ec31f4"
},
{
"ImportPath": "github.com/PuerkitoBio/urlesc",
"Rev": "5bd2802263f21d8788851d5305584c82a5c75d7e"
},
{
"ImportPath": "github.com/davecgh/go-spew/spew",
"Rev": "5215b55f46b2b919f50a1df0eaa5886afe4e3b3d"
},
{
"ImportPath": "github.com/emicklei/go-restful",
"Comment": "2.2.0-4-gff4f55a",
"Rev": "ff4f55a206334ef123e4f79bbf348980da81ca46"
},
{
"ImportPath": "github.com/emicklei/go-restful/log",
"Comment": "2.2.0-4-gff4f55a",
"Rev": "ff4f55a206334ef123e4f79bbf348980da81ca46"
},
{
"ImportPath": "github.com/ghodss/yaml",
"Rev": "73d445a93680fa1a78ae23a5839bad48f32ba1ee"
},
{
"ImportPath": "github.com/go-openapi/jsonpointer",
"Rev": "46af16f9f7b149af66e5d1bd010e3574dc06de98"
},
{
"ImportPath": "github.com/go-openapi/jsonreference",
"Rev": "13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272"
},
{
"ImportPath": "github.com/go-openapi/spec",
"Rev": "6aced65f8501fe1217321abf0749d354824ba2ff"
},
{
"ImportPath": "github.com/go-openapi/swag",
"Rev": "1d0bd113de87027671077d3c71eb3ac5d7dbba72"
},
{
"ImportPath": "github.com/golang/protobuf/proto",
"Rev": "4bd1920723d7b7c925de087aa32e2187708897f7"
},
{
"ImportPath": "github.com/golang/protobuf/ptypes",
"Rev": "4bd1920723d7b7c925de087aa32e2187708897f7"
},
{
"ImportPath": "github.com/golang/protobuf/ptypes/any",
"Rev": "4bd1920723d7b7c925de087aa32e2187708897f7"
},
{
"ImportPath": "github.com/golang/protobuf/ptypes/duration",
"Rev": "4bd1920723d7b7c925de087aa32e2187708897f7"
},
{
"ImportPath": "github.com/golang/protobuf/ptypes/timestamp",
"Rev": "4bd1920723d7b7c925de087aa32e2187708897f7"
},
{
"ImportPath": "github.com/googleapis/gnostic/OpenAPIv2",
"Rev": "68f4ded48ba9414dab2ae69b3f0d69971da73aa5"
},
{
"ImportPath": "github.com/googleapis/gnostic/compiler",
"Rev": "68f4ded48ba9414dab2ae69b3f0d69971da73aa5"
},
{
"ImportPath": "github.com/googleapis/gnostic/extensions",
"Rev": "68f4ded48ba9414dab2ae69b3f0d69971da73aa5"
},
{
"ImportPath": "github.com/mailru/easyjson/buffer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
},
{
"ImportPath": "github.com/mailru/easyjson/jlexer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
},
{
"ImportPath": "github.com/mailru/easyjson/jwriter",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
},
{
"ImportPath": "github.com/onsi/ginkgo",
"Comment": "v1.4.0-4-g11459a8",
"Rev": "11459a886d9cd66b319dac7ef1e917ee221372c9"
},
{
"ImportPath": "github.com/onsi/ginkgo/config",
"Comment": "v1.4.0-4-g11459a8",
"Rev": "11459a886d9cd66b319dac7ef1e917ee221372c9"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/codelocation",
"Comment": "v1.4.0-4-g11459a8",
"Rev": "11459a886d9cd66b319dac7ef1e917ee221372c9"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/containernode",
"Comment": "v1.4.0-4-g11459a8",
"Rev": "11459a886d9cd66b319dac7ef1e917ee221372c9"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/failer",
"Comment": "v1.4.0-4-g11459a8",
"Rev": "11459a886d9cd66b319dac7ef1e917ee221372c9"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/leafnodes",
"Comment": "v1.4.0-4-g11459a8",
"Rev": "11459a886d9cd66b319dac7ef1e917ee221372c9"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/remote",
"Comment": "v1.4.0-4-g11459a8",
"Rev": "11459a886d9cd66b319dac7ef1e917ee221372c9"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/spec",
"Comment": "v1.4.0-4-g11459a8",
"Rev": "11459a886d9cd66b319dac7ef1e917ee221372c9"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/spec_iterator",
"Comment": "v1.4.0-4-g11459a8",
"Rev": "11459a886d9cd66b319dac7ef1e917ee221372c9"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/specrunner",
"Comment": "v1.4.0-4-g11459a8",
"Rev": "11459a886d9cd66b319dac7ef1e917ee221372c9"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/suite",
"Comment": "v1.4.0-4-g11459a8",
"Rev": "11459a886d9cd66b319dac7ef1e917ee221372c9"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/testingtproxy",
"Comment": "v1.4.0-4-g11459a8",
"Rev": "11459a886d9cd66b319dac7ef1e917ee221372c9"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/writer",
"Comment": "v1.4.0-4-g11459a8",
"Rev": "11459a886d9cd66b319dac7ef1e917ee221372c9"
},
{
"ImportPath": "github.com/onsi/ginkgo/reporters",
"Comment": "v1.4.0-4-g11459a8",
"Rev": "11459a886d9cd66b319dac7ef1e917ee221372c9"
},
{
"ImportPath": "github.com/onsi/ginkgo/reporters/stenographer",
"Comment": "v1.4.0-4-g11459a8",
"Rev": "11459a886d9cd66b319dac7ef1e917ee221372c9"
},
{
"ImportPath": "github.com/onsi/ginkgo/reporters/stenographer/support/go-colorable",
"Comment": "v1.4.0-4-g11459a8",
"Rev": "11459a886d9cd66b319dac7ef1e917ee221372c9"
},
{
"ImportPath": "github.com/onsi/ginkgo/reporters/stenographer/support/go-isatty",
"Comment": "v1.4.0-4-g11459a8",
"Rev": "11459a886d9cd66b319dac7ef1e917ee221372c9"
},
{
"ImportPath": "github.com/onsi/ginkgo/types",
"Comment": "v1.4.0-4-g11459a8",
"Rev": "11459a886d9cd66b319dac7ef1e917ee221372c9"
},
{
"ImportPath": "github.com/onsi/gomega",
"Comment": "v1.2.0-2-gdcabb60",
"Rev": "dcabb60a477c2b6f456df65037cb6708210fbb02"
},
{
"ImportPath": "github.com/onsi/gomega/format",
"Comment": "v1.2.0-2-gdcabb60",
"Rev": "dcabb60a477c2b6f456df65037cb6708210fbb02"
},
{
"ImportPath": "github.com/onsi/gomega/internal/assertion",
"Comment": "v1.2.0-2-gdcabb60",
"Rev": "dcabb60a477c2b6f456df65037cb6708210fbb02"
},
{
"ImportPath": "github.com/onsi/gomega/internal/asyncassertion",
"Comment": "v1.2.0-2-gdcabb60",
"Rev": "dcabb60a477c2b6f456df65037cb6708210fbb02"
},
{
"ImportPath": "github.com/onsi/gomega/internal/oraclematcher",
"Comment": "v1.2.0-2-gdcabb60",
"Rev": "dcabb60a477c2b6f456df65037cb6708210fbb02"
},
{
"ImportPath": "github.com/onsi/gomega/internal/testingtsupport",
"Comment": "v1.2.0-2-gdcabb60",
"Rev": "dcabb60a477c2b6f456df65037cb6708210fbb02"
},
{
"ImportPath": "github.com/onsi/gomega/matchers",
"Comment": "v1.2.0-2-gdcabb60",
"Rev": "dcabb60a477c2b6f456df65037cb6708210fbb02"
},
{
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/bipartitegraph",
"Comment": "v1.2.0-2-gdcabb60",
"Rev": "dcabb60a477c2b6f456df65037cb6708210fbb02"
},
{
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/edge",
"Comment": "v1.2.0-2-gdcabb60",
"Rev": "dcabb60a477c2b6f456df65037cb6708210fbb02"
},
{
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/node",
"Comment": "v1.2.0-2-gdcabb60",
"Rev": "dcabb60a477c2b6f456df65037cb6708210fbb02"
},
{
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/util",
"Comment": "v1.2.0-2-gdcabb60",
"Rev": "dcabb60a477c2b6f456df65037cb6708210fbb02"
},
{
"ImportPath": "github.com/onsi/gomega/types",
"Comment": "v1.2.0-2-gdcabb60",
"Rev": "dcabb60a477c2b6f456df65037cb6708210fbb02"
},
{
"ImportPath": "github.com/pmezard/go-difflib/difflib",
"Rev": "d8ed2627bdf02c080bf22230dbb337003b7aba2d"
},
{
"ImportPath": "github.com/spf13/pflag",
"Rev": "9ff6c6923cfffbcd502984b8e0c80539a94968b7"
},
{
"ImportPath": "github.com/stretchr/testify/assert",
"Comment": "v1.0-88-ge3a8ff8",
"Rev": "e3a8ff8ce36581f87a15341206f205b1da467059"
},
{
"ImportPath": "golang.org/x/net/html",
"Rev": "f2499483f923065a842d38eb4c7f1927e6fc6e6d"
},
{
"ImportPath": "golang.org/x/net/html/atom",
"Rev": "f2499483f923065a842d38eb4c7f1927e6fc6e6d"
},
{
"ImportPath": "golang.org/x/net/html/charset",
"Rev": "f2499483f923065a842d38eb4c7f1927e6fc6e6d"
},
{
"ImportPath": "golang.org/x/net/idna",
"Rev": "f2499483f923065a842d38eb4c7f1927e6fc6e6d"
},
{
"ImportPath": "golang.org/x/sys/unix",
"Rev": "bb24a47a89eac6c1227fbcb2ae37a8b9ed323366"
},
{
"ImportPath": "golang.org/x/text/cases",
"Rev": "2910a502d2bf9e43193af9d68ca516529614eed3"
},
{
"ImportPath": "golang.org/x/text/encoding",
"Rev": "2910a502d2bf9e43193af9d68ca516529614eed3"
},
{
"ImportPath": "golang.org/x/text/encoding/charmap",
"Rev": "2910a502d2bf9e43193af9d68ca516529614eed3"
},
{
"ImportPath": "golang.org/x/text/encoding/htmlindex",
"Rev": "2910a502d2bf9e43193af9d68ca516529614eed3"
},
{
"ImportPath": "golang.org/x/text/encoding/internal",
"Rev": "2910a502d2bf9e43193af9d68ca516529614eed3"
},
{
"ImportPath": "golang.org/x/text/encoding/internal/identifier",
"Rev": "2910a502d2bf9e43193af9d68ca516529614eed3"
},
{
"ImportPath": "golang.org/x/text/encoding/japanese",
"Rev": "2910a502d2bf9e43193af9d68ca516529614eed3"
},
{
"ImportPath": "golang.org/x/text/encoding/korean",
"Rev": "2910a502d2bf9e43193af9d68ca516529614eed3"
},
{
"ImportPath": "golang.org/x/text/encoding/simplifiedchinese",
"Rev": "2910a502d2bf9e43193af9d68ca516529614eed3"
},
{
"ImportPath": "golang.org/x/text/encoding/traditionalchinese",
"Rev": "2910a502d2bf9e43193af9d68ca516529614eed3"
},
{
"ImportPath": "golang.org/x/text/encoding/unicode",
"Rev": "2910a502d2bf9e43193af9d68ca516529614eed3"
},
{
"ImportPath": "golang.org/x/text/internal/tag",
"Rev": "2910a502d2bf9e43193af9d68ca516529614eed3"
},
{
"ImportPath": "golang.org/x/text/internal/utf8internal",
"Rev": "2910a502d2bf9e43193af9d68ca516529614eed3"
},
{
"ImportPath": "golang.org/x/text/language",
"Rev": "2910a502d2bf9e43193af9d68ca516529614eed3"
},
{
"ImportPath": "golang.org/x/text/runes",
"Rev": "2910a502d2bf9e43193af9d68ca516529614eed3"
},
{
"ImportPath": "golang.org/x/text/secure/bidirule",
"Rev": "2910a502d2bf9e43193af9d68ca516529614eed3"
},
{
"ImportPath": "golang.org/x/text/secure/precis",
"Rev": "2910a502d2bf9e43193af9d68ca516529614eed3"
},
{
"ImportPath": "golang.org/x/text/transform",
"Rev": "2910a502d2bf9e43193af9d68ca516529614eed3"
},
{
"ImportPath": "golang.org/x/text/unicode/bidi",
"Rev": "2910a502d2bf9e43193af9d68ca516529614eed3"
},
{
"ImportPath": "golang.org/x/text/unicode/norm",
"Rev": "2910a502d2bf9e43193af9d68ca516529614eed3"
},
{
"ImportPath": "golang.org/x/text/width",
"Rev": "2910a502d2bf9e43193af9d68ca516529614eed3"
},
{
"ImportPath": "gopkg.in/yaml.v2",
"Rev": "53feefa2559fb8dfa8d81baad31be332c97d6c77"
},
{
"ImportPath": "k8s.io/gengo/args",
"Rev": "75356185a9af8f0464efa792e2e9508d5b4be83c"
},
{
"ImportPath": "k8s.io/gengo/generator",
"Rev": "75356185a9af8f0464efa792e2e9508d5b4be83c"
},
{
"ImportPath": "k8s.io/gengo/namer",
"Rev": "75356185a9af8f0464efa792e2e9508d5b4be83c"
},
{
"ImportPath": "k8s.io/gengo/parser",
"Rev": "75356185a9af8f0464efa792e2e9508d5b4be83c"
},
{
"ImportPath": "k8s.io/gengo/types",
"Rev": "75356185a9af8f0464efa792e2e9508d5b4be83c"
},
{
"ImportPath": "k8s.io/klog",
"Rev": "b9b56d5dfc9208f60ea747056670942d8b0afdc8"
}
]
}

View File

@@ -1,5 +0,0 @@
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.

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

@@ -1,12 +0,0 @@
reviewers:
- yujuhong
- gmarek
- mbohlool
- philips
- seans3
- apelisse
- roycaihw
approvers:
- mbohlool
- lavalamp
- seans3

18
vendor/k8s.io/kube-openapi/README.md generated vendored
View File

@@ -1,18 +0,0 @@
# Kube OpenAPI
This repo is the home for Kubernetes OpenAPI discovery spec generation. The goal
is to support a subset of OpenAPI features to satisfy kubernetes use-cases but
implement that subset with little to no assumption about the structure of the
code or routes. Thus, there should be no kubernetes specific code in this repo.
There are two main parts:
- A model generator that goes through .go files, find and generate model
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

@@ -1,73 +0,0 @@
/*
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,58 +0,0 @@
/*
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.
*/
// 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 (
"flag"
"log"
generatorargs "k8s.io/kube-openapi/cmd/openapi-gen/args"
"k8s.io/kube-openapi/pkg/generators"
"github.com/spf13/pflag"
"k8s.io/klog"
)
func main() {
klog.InitFlags(nil)
genericArgs, customArgs := generatorargs.NewDefaults()
genericArgs.AddFlags(pflag.CommandLine)
customArgs.AddFlags(pflag.CommandLine)
flag.Set("logtostderr", "true")
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
pflag.Parse()
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 {
log.Fatalf("OpenAPI code generation error: %v", err)
}
log.Println("Code for OpenAPI definitions generated")
}

View File

@@ -1,60 +0,0 @@
/*
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

@@ -1,3 +0,0 @@
# Kubernetes Community Code of Conduct
Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md)

View File

@@ -1,391 +0,0 @@
/*
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 aggregator
import (
"encoding/json"
"fmt"
"reflect"
"strings"
"github.com/go-openapi/spec"
"k8s.io/kube-openapi/pkg/util"
)
const (
definitionPrefix = "#/definitions/"
)
// Run a walkRefCallback method on all references of an OpenAPI spec
type referenceWalker struct {
// walkRefCallback will be called on each reference and the return value
// will replace that reference. This will allow the callers to change
// all/some references of an spec (e.g. useful in renaming definitions).
walkRefCallback func(ref spec.Ref) spec.Ref
// The spec to walk through.
root *spec.Swagger
// Keep track of visited references
alreadyVisited map[string]bool
}
func walkOnAllReferences(walkRef func(ref spec.Ref) spec.Ref, sp *spec.Swagger) {
walker := &referenceWalker{walkRefCallback: walkRef, root: sp, alreadyVisited: map[string]bool{}}
walker.Start()
}
func (s *referenceWalker) walkRef(ref spec.Ref) spec.Ref {
refStr := ref.String()
// References that start with #/definitions/ has a definition
// inside the same spec file. If that is the case, walk through
// those definitions too.
// We do not support external references yet.
if !s.alreadyVisited[refStr] && strings.HasPrefix(refStr, definitionPrefix) {
s.alreadyVisited[refStr] = true
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)
}
func (s *referenceWalker) walkSchema(schema *spec.Schema) {
if schema == nil {
return
}
schema.Ref = s.walkRef(schema.Ref)
for k, v := range schema.Definitions {
s.walkSchema(&v)
schema.Definitions[k] = v
}
for k, v := range schema.Properties {
s.walkSchema(&v)
schema.Properties[k] = v
}
for k, v := range schema.PatternProperties {
s.walkSchema(&v)
schema.PatternProperties[k] = v
}
for i := range schema.AllOf {
s.walkSchema(&schema.AllOf[i])
}
for i := range schema.AnyOf {
s.walkSchema(&schema.AnyOf[i])
}
for i := range schema.OneOf {
s.walkSchema(&schema.OneOf[i])
}
if schema.Not != nil {
s.walkSchema(schema.Not)
}
if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil {
s.walkSchema(schema.AdditionalProperties.Schema)
}
if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil {
s.walkSchema(schema.AdditionalItems.Schema)
}
if schema.Items != nil {
if schema.Items.Schema != nil {
s.walkSchema(schema.Items.Schema)
}
for i := range schema.Items.Schemas {
s.walkSchema(&schema.Items.Schemas[i])
}
}
}
func (s *referenceWalker) walkParams(params []spec.Parameter) {
if params == nil {
return
}
for _, param := range params {
param.Ref = s.walkRef(param.Ref)
s.walkSchema(param.Schema)
if param.Items != nil {
param.Items.Ref = s.walkRef(param.Items.Ref)
}
}
}
func (s *referenceWalker) walkResponse(resp *spec.Response) {
if resp == nil {
return
}
resp.Ref = s.walkRef(resp.Ref)
s.walkSchema(resp.Schema)
}
func (s *referenceWalker) walkOperation(op *spec.Operation) {
if op == nil {
return
}
s.walkParams(op.Parameters)
if op.Responses == nil {
return
}
s.walkResponse(op.Responses.Default)
for _, r := range op.Responses.StatusCodeResponses {
s.walkResponse(&r)
}
}
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)
s.walkOperation(pathItem.Get)
s.walkOperation(pathItem.Head)
s.walkOperation(pathItem.Options)
s.walkOperation(pathItem.Patch)
s.walkOperation(pathItem.Post)
s.walkOperation(pathItem.Put)
}
}
// 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 {
if refStr := ref.String(); refStr != "" && strings.HasPrefix(refStr, definitionPrefix) {
usedDefinitions[refStr[len(definitionPrefix):]] = true
}
return ref
}, sp)
return usedDefinitions
}
// FilterSpecByPaths removes unnecessary paths and definitions used by those paths.
// 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
// want to only deal with unused definitions resulted from filtering paths.
// Thus a definition will be removed only if it has been used before but
// it is unused because of a path prune.
initialUsedDefinitions := usedDefinitionForSpec(sp)
// First remove unwanted paths
prefixes := util.NewTrie(keepPathPrefixes)
orgPaths := sp.Paths
if orgPaths == nil {
return
}
sp.Paths = &spec.Paths{
VendorExtensible: orgPaths.VendorExtensible,
Paths: map[string]spec.PathItem{},
}
for path, pathItem := range orgPaths.Paths {
if !prefixes.HasPrefix(path) {
continue
}
sp.Paths.Paths[path] = pathItem
}
// Walk all references to find all definition references.
usedDefinitions := usedDefinitionForSpec(sp)
// Remove unused definitions
orgDefinitions := sp.Definitions
sp.Definitions = spec.Definitions{}
for k, v := range orgDefinitions {
if usedDefinitions[k] || !initialUsedDefinitions[k] {
sp.Definitions[k] = v
}
}
}
func renameDefinition(s *spec.Swagger, old, new string) {
oldRef := definitionPrefix + old
newRef := definitionPrefix + new
walkOnAllReferences(func(ref spec.Ref) spec.Ref {
if ref.String() == oldRef {
return spec.MustCreateRef(newRef)
}
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)
}
// MergeSpecsIgnorePathConflict is the same as MergeSpecs except it will ignore any path
// conflicts by keeping the paths of destination. It will rename definition conflicts.
func MergeSpecsIgnorePathConflict(dest, source *spec.Swagger) error {
return mergeSpecs(dest, source, true, true)
}
// MergeSpecsFailOnDefinitionConflict is differ from MergeSpecs as it fails if there is
// a definition conflict.
func MergeSpecsFailOnDefinitionConflict(dest, source *spec.Swagger) error {
return mergeSpecs(dest, source, false, false)
}
// MergeSpecs copies paths and definitions from source to dest, rename definitions if needed.
// dest will be mutated, and source will not be changed. It will fail on path conflicts.
func MergeSpecs(dest, source *spec.Swagger) error {
return mergeSpecs(dest, source, true, false)
}
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
for k := range source.Paths.Paths {
if _, found := dest.Paths.Paths[k]; !found {
keepPaths = append(keepPaths, k)
} else {
hasConflictingPath = true
}
}
if len(keepPaths) == 0 {
// There is nothing to merge. All paths are conflicting.
return nil
}
if hasConflictingPath {
source, err = CloneSpec(source)
if err != nil {
return err
}
specCloned = true
FilterSpecByPaths(source, keepPaths)
}
}
// Check for model conflicts
conflicts := false
for k, v := range source.Definitions {
v2, found := dest.Definitions[k]
if found && !reflect.DeepEqual(v, v2) {
if !renameModelConflicts {
return fmt.Errorf("model name conflict in merging OpenAPI spec: %s", k)
}
conflicts = true
break
}
}
if conflicts {
if !specCloned {
source, err = CloneSpec(source)
if err != nil {
return err
}
}
specCloned = true
usedNames := map[string]bool{}
for k := range dest.Definitions {
usedNames[k] = true
}
type Rename struct {
from, to string
}
renames := []Rename{}
OUTERLOOP:
for k, v := range source.Definitions {
if usedNames[k] {
v2, found := dest.Definitions[k]
// Reuse model if they are exactly the same.
if found && reflect.DeepEqual(v, v2) {
continue
}
// Reuse previously renamed model if one exists
var newName string
i := 1
for found {
i++
newName = fmt.Sprintf("%s_v%d", k, i)
v2, found = dest.Definitions[newName]
if found && reflect.DeepEqual(v, v2) {
renames = append(renames, Rename{from: k, to: newName})
continue OUTERLOOP
}
}
_, foundInSource := source.Definitions[newName]
for usedNames[newName] || foundInSource {
i++
newName = fmt.Sprintf("%s_v%d", k, i)
_, foundInSource = source.Definitions[newName]
}
renames = append(renames, Rename{from: k, to: newName})
usedNames[newName] = true
}
}
for _, r := range renames {
renameDefinition(source, r.from, r.to)
}
}
for k, v := range source.Definitions {
if _, found := dest.Definitions[k]; !found {
if dest.Definitions == nil {
dest.Definitions = spec.Definitions{}
}
dest.Definitions[k] = v
}
}
// Check for path conflicts
for k, v := range source.Paths.Paths {
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
}
// CloneSpec clones OpenAPI spec
func CloneSpec(source *spec.Swagger) (*spec.Swagger, error) {
// TODO(mehdy): Find a faster way to clone an spec
bytes, err := json.Marshal(source)
if err != nil {
return nil, err
}
var ret spec.Swagger
err = json.Unmarshal(bytes, &ret)
if err != nil {
return nil, err
}
return &ret, nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +0,0 @@
/*
Copyright 2016 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 builder contains code to generate OpenAPI discovery spec (which
// initial version of it also known as Swagger 2.0).
// For more details: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md
package builder

View File

@@ -1,442 +0,0 @@
/*
Copyright 2016 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 builder
import (
"encoding/json"
"fmt"
"net/http"
"strings"
restful "github.com/emicklei/go-restful"
"github.com/go-openapi/spec"
"k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/util"
)
const (
OpenAPIVersion = "2.0"
// TODO: Make this configurable.
extensionPrefix = "x-kubernetes-"
)
type openAPI struct {
config *common.Config
swagger *spec.Swagger
protocolList []string
definitions map[string]common.OpenAPIDefinition
}
// 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,
},
},
}
if o.config.GetOperationIDAndTags == nil {
o.config.GetOperationIDAndTags = func(r *restful.Route) (string, []string, error) {
return r.Operation, nil, nil
}
}
if o.config.GetDefinitionName == nil {
o.config.GetDefinitionName = func(name string) (string, spec.Extensions) {
return name[strings.LastIndex(name, "/")+1:], nil
}
}
o.definitions = o.config.GetDefinitions(func(name string) spec.Ref {
defName, _ := o.config.GetDefinitionName(name)
return spec.MustCreateRef("#/definitions/" + common.EscapeJsonPointer(defName))
})
if o.config.CommonResponses == nil {
o.config.CommonResponses = map[int]spec.Response{}
}
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 nil, err
}
}
return o.swagger, nil
}
func (o *openAPI) buildDefinitionRecursively(name string) error {
uniqueName, extensions := o.config.GetDefinitionName(name)
if _, ok := o.swagger.Definitions[uniqueName]; ok {
return nil
}
if item, ok := o.definitions[name]; ok {
schema := spec.Schema{
VendorExtensible: item.Schema.VendorExtensible,
SchemaProps: item.Schema.SchemaProps,
SwaggerSchemaProps: item.Schema.SwaggerSchemaProps,
}
if extensions != nil {
if schema.Extensions == nil {
schema.Extensions = spec.Extensions{}
}
for k, v := range extensions {
schema.Extensions[k] = v
}
}
o.swagger.Definitions[uniqueName] = schema
for _, v := range item.Dependencies {
if err := o.buildDefinitionRecursively(v); err != nil {
return err
}
}
} else {
return fmt.Errorf("cannot find model definition for %v. If you added a new type, you may need to add +k8s:openapi-gen=true to the package or type and run code-gen again", name)
}
return nil
}
// 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(name string) (string, error) {
if err := o.buildDefinitionRecursively(name); err != nil {
return "", err
}
defName, _ := o.config.GetDefinitionName(name)
return "#/definitions/" + common.EscapeJsonPointer(defName), nil
}
// buildPaths builds OpenAPI paths using go-restful's web services.
func (o *openAPI) buildPaths(webServices []*restful.WebService) error {
pathsToIgnore := util.NewTrie(o.config.IgnorePrefixes)
duplicateOpId := make(map[string]string)
for _, w := range webServices {
rootPath := w.RootPath()
if pathsToIgnore.HasPrefix(rootPath) {
continue
}
commonParams, err := o.buildParameters(w.PathParameters())
if err != nil {
return err
}
for path, routes := range groupRoutesByPath(w.Routes()) {
// go-swagger has special variable definition {$NAME:*} that can only be
// used at the end of the path and it is not recognized by OpenAPI.
if strings.HasSuffix(path, ":*}") {
path = path[:len(path)-3] + "}"
}
if pathsToIgnore.HasPrefix(path) {
continue
}
// Aggregating common parameters make API spec (and generated clients) simpler
inPathCommonParamsMap, err := o.findCommonParameters(routes)
if err != nil {
return err
}
pathItem, exists := o.swagger.Paths.Paths[path]
if exists {
return fmt.Errorf("duplicate webservice route has been found for path: %v", path)
}
pathItem = spec.PathItem{
PathItemProps: spec.PathItemProps{
Parameters: make([]spec.Parameter, 0),
},
}
// add web services's parameters as well as any parameters appears in all ops, as common parameters
pathItem.Parameters = append(pathItem.Parameters, commonParams...)
for _, p := range inPathCommonParamsMap {
pathItem.Parameters = append(pathItem.Parameters, p)
}
sortParameters(pathItem.Parameters)
for _, route := range routes {
op, err := o.buildOperations(route, inPathCommonParamsMap)
sortParameters(op.Parameters)
if err != nil {
return err
}
dpath, exists := duplicateOpId[op.ID]
if exists {
return fmt.Errorf("duplicate Operation ID %v for path %v and %v", op.ID, dpath, path)
} else {
duplicateOpId[op.ID] = path
}
switch strings.ToUpper(route.Method) {
case "GET":
pathItem.Get = op
case "POST":
pathItem.Post = op
case "HEAD":
pathItem.Head = op
case "PUT":
pathItem.Put = op
case "DELETE":
pathItem.Delete = op
case "OPTIONS":
pathItem.Options = op
case "PATCH":
pathItem.Patch = op
}
}
o.swagger.Paths.Paths[path] = pathItem
}
}
return nil
}
// buildOperations builds operations for each webservice path
func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map[interface{}]spec.Parameter) (ret *spec.Operation, err error) {
ret = &spec.Operation{
OperationProps: spec.OperationProps{
Description: route.Doc,
Consumes: route.Consumes,
Produces: route.Produces,
Schemes: o.config.ProtocolList,
Responses: &spec.Responses{
ResponsesProps: spec.ResponsesProps{
StatusCodeResponses: make(map[int]spec.Response),
},
},
},
}
for k, v := range route.Metadata {
if strings.HasPrefix(k, extensionPrefix) {
if ret.Extensions == nil {
ret.Extensions = spec.Extensions{}
}
ret.Extensions.Add(k, v)
}
}
if ret.ID, ret.Tags, err = o.config.GetOperationIDAndTags(&route); err != nil {
return ret, err
}
// Build responses
for _, resp := range route.ResponseErrors {
ret.Responses.StatusCodeResponses[resp.Code], err = o.buildResponse(resp.Model, resp.Message)
if err != nil {
return ret, err
}
}
// If there is no response but a write sample, assume that write sample is an http.StatusOK response.
if len(ret.Responses.StatusCodeResponses) == 0 && route.WriteSample != nil {
ret.Responses.StatusCodeResponses[http.StatusOK], err = o.buildResponse(route.WriteSample, "OK")
if err != nil {
return ret, err
}
}
for code, resp := range o.config.CommonResponses {
if _, exists := ret.Responses.StatusCodeResponses[code]; !exists {
ret.Responses.StatusCodeResponses[code] = resp
}
}
// If there is still no response, use default response provided.
if len(ret.Responses.StatusCodeResponses) == 0 {
ret.Responses.Default = o.config.DefaultResponse
}
// Build non-common Parameters
ret.Parameters = make([]spec.Parameter, 0)
for _, param := range route.ParameterDocs {
if _, isCommon := inPathCommonParamsMap[mapKeyFromParam(param)]; !isCommon {
openAPIParam, err := o.buildParameter(param.Data(), route.ReadSample)
if err != nil {
return ret, err
}
ret.Parameters = append(ret.Parameters, openAPIParam)
}
}
return ret, nil
}
func (o *openAPI) buildResponse(model interface{}, description string) (spec.Response, error) {
schema, err := o.toSchema(util.GetCanonicalTypeName(model))
if err != nil {
return spec.Response{}, err
}
return spec.Response{
ResponseProps: spec.ResponseProps{
Description: description,
Schema: schema,
},
}, nil
}
func (o *openAPI) findCommonParameters(routes []restful.Route) (map[interface{}]spec.Parameter, error) {
commonParamsMap := make(map[interface{}]spec.Parameter, 0)
paramOpsCountByName := make(map[interface{}]int, 0)
paramNameKindToDataMap := make(map[interface{}]restful.ParameterData, 0)
for _, route := range routes {
routeParamDuplicateMap := make(map[interface{}]bool)
s := ""
for _, param := range route.ParameterDocs {
m, _ := json.Marshal(param.Data())
s += string(m) + "\n"
key := mapKeyFromParam(param)
if routeParamDuplicateMap[key] {
msg, _ := json.Marshal(route.ParameterDocs)
return commonParamsMap, fmt.Errorf("duplicate parameter %v for route %v, %v", param.Data().Name, string(msg), s)
}
routeParamDuplicateMap[key] = true
paramOpsCountByName[key]++
paramNameKindToDataMap[key] = param.Data()
}
}
for key, count := range paramOpsCountByName {
paramData := paramNameKindToDataMap[key]
if count == len(routes) && paramData.Kind != restful.BodyParameterKind {
openAPIParam, err := o.buildParameter(paramData, nil)
if err != nil {
return commonParamsMap, err
}
commonParamsMap[key] = openAPIParam
}
}
return commonParamsMap, nil
}
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},
Format: openAPIFormat,
},
}, nil
} else {
ref, err := o.buildDefinitionForType(name)
if err != nil {
return nil, err
}
return &spec.Schema{
SchemaProps: spec.SchemaProps{
Ref: spec.MustCreateRef(ref),
},
}, nil
}
}
func (o *openAPI) buildParameter(restParam restful.ParameterData, bodySample interface{}) (ret spec.Parameter, err error) {
ret = spec.Parameter{
ParamProps: spec.ParamProps{
Name: restParam.Name,
Description: restParam.Description,
Required: restParam.Required,
},
}
switch restParam.Kind {
case restful.BodyParameterKind:
if bodySample != nil {
ret.In = "body"
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.
// Body parameter has a data type that is a short name but we need full package name
// of the type to create a definition.
return ret, fmt.Errorf("restful body parameters are not supported: %v", restParam.DataType)
}
case restful.PathParameterKind:
ret.In = "path"
if !restParam.Required {
return ret, fmt.Errorf("path parameters should be marked at required for parameter %v", restParam)
}
case restful.QueryParameterKind:
ret.In = "query"
case restful.HeaderParameterKind:
ret.In = "header"
case restful.FormParameterKind:
ret.In = "formData"
default:
return ret, fmt.Errorf("unknown restful operation kind : %v", restParam.Kind)
}
openAPIType, openAPIFormat := common.GetOpenAPITypeFormat(restParam.DataType)
if openAPIType == "" {
return ret, fmt.Errorf("non-body Restful parameter type should be a simple type, but got : %v", restParam.DataType)
}
ret.Type = openAPIType
ret.Format = openAPIFormat
ret.UniqueItems = !restParam.AllowMultiple
return ret, nil
}
func (o *openAPI) buildParameters(restParam []*restful.Parameter) (ret []spec.Parameter, err error) {
ret = make([]spec.Parameter, len(restParam))
for i, v := range restParam {
ret[i], err = o.buildParameter(v.Data(), nil)
if err != nil {
return ret, err
}
}
return ret, nil
}

View File

@@ -1,475 +0,0 @@
/*
Copyright 2016 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 builder
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"testing"
"github.com/emicklei/go-restful"
"github.com/go-openapi/spec"
"github.com/stretchr/testify/assert"
openapi "k8s.io/kube-openapi/pkg/common"
)
// setUp is a convenience function for setting up for (most) tests.
func setUp(t *testing.T, fullMethods bool) (*openapi.Config, *restful.Container, *assert.Assertions) {
assert := assert.New(t)
config, container := getConfig(fullMethods)
return config, container, assert
}
func noOp(request *restful.Request, response *restful.Response) {}
// Test input
type TestInput struct {
// Name of the input
Name string `json:"name,omitempty"`
// ID of the input
ID int `json:"id,omitempty"`
Tags []string `json:"tags,omitempty"`
}
// Test output
type TestOutput struct {
// Name of the output
Name string `json:"name,omitempty"`
// Number of outputs
Count int `json:"count,omitempty"`
}
func (_ TestInput) OpenAPIDefinition() *openapi.OpenAPIDefinition {
schema := spec.Schema{}
schema.Description = "Test input"
schema.Properties = map[string]spec.Schema{
"name": {
SchemaProps: spec.SchemaProps{
Description: "Name of the input",
Type: []string{"string"},
Format: "",
},
},
"id": {
SchemaProps: spec.SchemaProps{
Description: "ID of the input",
Type: []string{"integer"},
Format: "int32",
},
},
"tags": {
SchemaProps: spec.SchemaProps{
Description: "",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
},
},
},
}
schema.Extensions = spec.Extensions{"x-test": "test"}
return &openapi.OpenAPIDefinition{
Schema: schema,
Dependencies: []string{},
}
}
func (_ TestOutput) OpenAPIDefinition() *openapi.OpenAPIDefinition {
schema := spec.Schema{}
schema.Description = "Test output"
schema.Properties = map[string]spec.Schema{
"name": {
SchemaProps: spec.SchemaProps{
Description: "Name of the output",
Type: []string{"string"},
Format: "",
},
},
"count": {
SchemaProps: spec.SchemaProps{
Description: "Number of outputs",
Type: []string{"integer"},
Format: "int32",
},
},
}
return &openapi.OpenAPIDefinition{
Schema: schema,
Dependencies: []string{},
}
}
var _ openapi.OpenAPIDefinitionGetter = TestInput{}
var _ openapi.OpenAPIDefinitionGetter = TestOutput{}
func getTestRoute(ws *restful.WebService, method string, additionalParams bool, opPrefix string) *restful.RouteBuilder {
ret := ws.Method(method).
Path("/test/{path:*}").
Doc(fmt.Sprintf("%s test input", method)).
Operation(fmt.Sprintf("%s%sTestInput", method, opPrefix)).
Produces(restful.MIME_JSON).
Consumes(restful.MIME_JSON).
Param(ws.PathParameter("path", "path to the resource").DataType("string")).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
Reads(TestInput{}).
Returns(200, "OK", TestOutput{}).
Writes(TestOutput{}).
To(noOp)
if additionalParams {
ret.Param(ws.HeaderParameter("hparam", "a test head parameter").DataType("integer"))
ret.Param(ws.FormParameter("fparam", "a test form parameter").DataType("number"))
}
return ret
}
func getConfig(fullMethods bool) (*openapi.Config, *restful.Container) {
mux := http.NewServeMux()
container := restful.NewContainer()
container.ServeMux = mux
ws := new(restful.WebService)
ws.Path("/foo")
ws.Route(getTestRoute(ws, "get", true, "foo"))
if fullMethods {
ws.Route(getTestRoute(ws, "post", false, "foo")).
Route(getTestRoute(ws, "put", false, "foo")).
Route(getTestRoute(ws, "head", false, "foo")).
Route(getTestRoute(ws, "patch", false, "foo")).
Route(getTestRoute(ws, "options", false, "foo")).
Route(getTestRoute(ws, "delete", false, "foo"))
}
ws.Path("/bar")
ws.Route(getTestRoute(ws, "get", true, "bar"))
if fullMethods {
ws.Route(getTestRoute(ws, "post", false, "bar")).
Route(getTestRoute(ws, "put", false, "bar")).
Route(getTestRoute(ws, "head", false, "bar")).
Route(getTestRoute(ws, "patch", false, "bar")).
Route(getTestRoute(ws, "options", false, "bar")).
Route(getTestRoute(ws, "delete", false, "bar"))
}
container.Add(ws)
return &openapi.Config{
ProtocolList: []string{"https"},
Info: &spec.Info{
InfoProps: spec.InfoProps{
Title: "TestAPI",
Description: "Test API",
Version: "unversioned",
},
},
GetDefinitions: func(_ openapi.ReferenceCallback) map[string]openapi.OpenAPIDefinition {
return map[string]openapi.OpenAPIDefinition{
"k8s.io/kube-openapi/pkg/builder.TestInput": *TestInput{}.OpenAPIDefinition(),
"k8s.io/kube-openapi/pkg/builder.TestOutput": *TestOutput{}.OpenAPIDefinition(),
// Bazel changes the package name, this is ok for testing, but we need to fix it if it happened
// in the main code.
"k8s.io/kube-openapi/pkg/builder/go_default_test.TestInput": *TestInput{}.OpenAPIDefinition(),
"k8s.io/kube-openapi/pkg/builder/go_default_test.TestOutput": *TestOutput{}.OpenAPIDefinition(),
}
},
GetDefinitionName: func(name string) (string, spec.Extensions) {
friendlyName := name[strings.LastIndex(name, "/")+1:]
if strings.HasPrefix(friendlyName, "go_default_test") {
friendlyName = "builder" + friendlyName[len("go_default_test"):]
}
return friendlyName, spec.Extensions{"x-test2": "test2"}
},
}, container
}
func getTestOperation(method string, opPrefix string) *spec.Operation {
return &spec.Operation{
OperationProps: spec.OperationProps{
Description: fmt.Sprintf("%s test input", method),
Consumes: []string{"application/json"},
Produces: []string{"application/json"},
Schemes: []string{"https"},
Parameters: []spec.Parameter{},
Responses: getTestResponses(),
ID: fmt.Sprintf("%s%sTestInput", method, opPrefix),
},
}
}
func getTestPathItem(allMethods bool, opPrefix string) spec.PathItem {
ret := spec.PathItem{
PathItemProps: spec.PathItemProps{
Get: getTestOperation("get", opPrefix),
Parameters: getTestCommonParameters(),
},
}
ret.Get.Parameters = getAdditionalTestParameters()
if allMethods {
ret.Put = getTestOperation("put", opPrefix)
ret.Put.Parameters = getTestParameters()
ret.Post = getTestOperation("post", opPrefix)
ret.Post.Parameters = getTestParameters()
ret.Head = getTestOperation("head", opPrefix)
ret.Head.Parameters = getTestParameters()
ret.Patch = getTestOperation("patch", opPrefix)
ret.Patch.Parameters = getTestParameters()
ret.Delete = getTestOperation("delete", opPrefix)
ret.Delete.Parameters = getTestParameters()
ret.Options = getTestOperation("options", opPrefix)
ret.Options.Parameters = getTestParameters()
}
return ret
}
func getRefSchema(ref string) *spec.Schema {
return &spec.Schema{
SchemaProps: spec.SchemaProps{
Ref: spec.MustCreateRef(ref),
},
}
}
func getTestResponses() *spec.Responses {
ret := spec.Responses{
ResponsesProps: spec.ResponsesProps{
StatusCodeResponses: map[int]spec.Response{},
},
}
ret.StatusCodeResponses[200] = spec.Response{
ResponseProps: spec.ResponseProps{
Description: "OK",
Schema: getRefSchema("#/definitions/builder.TestOutput"),
},
}
return &ret
}
func getTestCommonParameters() []spec.Parameter {
ret := make([]spec.Parameter, 2)
ret[0] = spec.Parameter{
SimpleSchema: spec.SimpleSchema{
Type: "string",
},
ParamProps: spec.ParamProps{
Description: "path to the resource",
Name: "path",
In: "path",
Required: true,
},
CommonValidations: spec.CommonValidations{
UniqueItems: true,
},
}
ret[1] = spec.Parameter{
SimpleSchema: spec.SimpleSchema{
Type: "string",
},
ParamProps: spec.ParamProps{
Description: "If 'true', then the output is pretty printed.",
Name: "pretty",
In: "query",
},
CommonValidations: spec.CommonValidations{
UniqueItems: true,
},
}
return ret
}
func getTestParameters() []spec.Parameter {
ret := make([]spec.Parameter, 1)
ret[0] = spec.Parameter{
ParamProps: spec.ParamProps{
Name: "body",
In: "body",
Required: true,
Schema: getRefSchema("#/definitions/builder.TestInput"),
},
}
return ret
}
func getAdditionalTestParameters() []spec.Parameter {
ret := make([]spec.Parameter, 3)
ret[0] = spec.Parameter{
ParamProps: spec.ParamProps{
Name: "body",
In: "body",
Required: true,
Schema: getRefSchema("#/definitions/builder.TestInput"),
},
}
ret[1] = spec.Parameter{
ParamProps: spec.ParamProps{
Name: "fparam",
Description: "a test form parameter",
In: "formData",
},
SimpleSchema: spec.SimpleSchema{
Type: "number",
},
CommonValidations: spec.CommonValidations{
UniqueItems: true,
},
}
ret[2] = spec.Parameter{
SimpleSchema: spec.SimpleSchema{
Type: "integer",
},
ParamProps: spec.ParamProps{
Description: "a test head parameter",
Name: "hparam",
In: "header",
},
CommonValidations: spec.CommonValidations{
UniqueItems: true,
},
}
return ret
}
func getTestInputDefinition() spec.Schema {
return spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "Test input",
Properties: map[string]spec.Schema{
"id": {
SchemaProps: spec.SchemaProps{
Description: "ID of the input",
Type: spec.StringOrArray{"integer"},
Format: "int32",
},
},
"name": {
SchemaProps: spec.SchemaProps{
Description: "Name of the input",
Type: spec.StringOrArray{"string"},
},
},
"tags": {
SchemaProps: spec.SchemaProps{
Type: spec.StringOrArray{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: spec.StringOrArray{"string"},
},
},
},
},
},
},
},
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-test": "test",
"x-test2": "test2",
},
},
}
}
func getTestOutputDefinition() spec.Schema {
return spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "Test output",
Properties: map[string]spec.Schema{
"count": {
SchemaProps: spec.SchemaProps{
Description: "Number of outputs",
Type: spec.StringOrArray{"integer"},
Format: "int32",
},
},
"name": {
SchemaProps: spec.SchemaProps{
Description: "Name of the output",
Type: spec.StringOrArray{"string"},
},
},
},
},
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-test2": "test2",
},
},
}
}
func TestBuildOpenAPISpec(t *testing.T) {
config, container, assert := setUp(t, true)
expected := &spec.Swagger{
SwaggerProps: spec.SwaggerProps{
Info: &spec.Info{
InfoProps: spec.InfoProps{
Title: "TestAPI",
Description: "Test API",
Version: "unversioned",
},
},
Swagger: "2.0",
Paths: &spec.Paths{
Paths: map[string]spec.PathItem{
"/foo/test/{path}": getTestPathItem(true, "foo"),
"/bar/test/{path}": getTestPathItem(true, "bar"),
},
},
Definitions: spec.Definitions{
"builder.TestInput": getTestInputDefinition(),
"builder.TestOutput": getTestOutputDefinition(),
},
},
}
swagger, err := BuildOpenAPISpec(container.RegisteredWebServices(), 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
}
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
}
assert.Equal(string(expected_json), string(actual_json))
}

View File

@@ -1,61 +0,0 @@
/*
Copyright 2016 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 builder
import (
"sort"
"github.com/emicklei/go-restful"
"github.com/go-openapi/spec"
)
type parameters []spec.Parameter
func (s parameters) Len() int { return len(s) }
func (s parameters) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// byNameIn used in sorting parameters by Name and In fields.
type byNameIn struct {
parameters
}
func (s byNameIn) Less(i, j int) bool {
return s.parameters[i].Name < s.parameters[j].Name || (s.parameters[i].Name == s.parameters[j].Name && s.parameters[i].In < s.parameters[j].In)
}
// SortParameters sorts parameters by Name and In fields.
func sortParameters(p []spec.Parameter) {
sort.Sort(byNameIn{p})
}
func groupRoutesByPath(routes []restful.Route) map[string][]restful.Route {
pathToRoutes := make(map[string][]restful.Route)
for _, r := range routes {
pathToRoutes[r.Path] = append(pathToRoutes[r.Path], r)
}
return pathToRoutes
}
func mapKeyFromParam(param *restful.Parameter) interface{} {
return struct {
Name string
Kind int
}{
Name: param.Data().Name,
Kind: param.Data().Kind,
}
}

View File

@@ -1,174 +0,0 @@
/*
Copyright 2016 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 common
import (
"net/http"
"strings"
"github.com/emicklei/go-restful"
"github.com/go-openapi/spec"
)
// OpenAPIDefinition describes single type. Normally these definitions are auto-generated using gen-openapi.
type OpenAPIDefinition struct {
Schema spec.Schema
Dependencies []string
}
type ReferenceCallback func(path string) spec.Ref
// 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,
// the definition returned by it will be used, otherwise the auto-generated definitions will be used. See
// GetOpenAPITypeFormat for more information about trade-offs of using this interface or GetOpenAPITypeFormat method when
// possible.
type OpenAPIDefinitionGetter interface {
OpenAPIDefinition() *OpenAPIDefinition
}
type PathHandler interface {
Handle(path string, handler http.Handler)
}
// Config is set of configuration for openAPI spec generation.
type Config struct {
// List of supported protocols such as https, http, etc.
ProtocolList []string
// Info is general information about the API.
Info *spec.Info
// DefaultResponse will be used if an operation does not have any responses listed. It
// 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
// List of webservice's path prefixes to ignore
IgnorePrefixes []string
// OpenAPIDefinitions should provide definition for all models used by routes. Failure to provide this map
// or any of the models will result in spec generation failure.
GetDefinitions GetOpenAPIDefinitions
// GetOperationIDAndTags returns operation id and tags for a restful route. It is an optional function to customize operation IDs.
GetOperationIDAndTags func(r *restful.Route) (string, []string, error)
// GetDefinitionName returns a friendly name for a definition base on the serving path. parameter `name` is the full name of the definition.
// It is an optional function to customize model names.
GetDefinitionName func(name string) (string, spec.Extensions)
// PostProcessSpec runs after the spec is ready to serve. It allows a final modification to the spec before serving.
PostProcessSpec func(*spec.Swagger) (*spec.Swagger, error)
// SecurityDefinitions is list of all security definitions for OpenAPI service. If this is not nil, the user of config
// is responsible to provide DefaultSecurity and (maybe) add unauthorized response to CommonResponses.
SecurityDefinitions *spec.SecurityDefinitions
// DefaultSecurity for all operations. This will pass as spec.SwaggerProps.Security to OpenAPI.
// For most cases, this will be list of acceptable definitions in SecurityDefinitions.
DefaultSecurity []map[string][]string
}
var schemaTypeFormatMap = map[string][]string{
"uint": {"integer", "int32"},
"uint8": {"integer", "byte"},
"uint16": {"integer", "int32"},
"uint32": {"integer", "int64"},
"uint64": {"integer", "int64"},
"int": {"integer", "int32"},
"int8": {"integer", "byte"},
"int16": {"integer", "int32"},
"int32": {"integer", "int32"},
"int64": {"integer", "int64"},
"byte": {"integer", "byte"},
"float64": {"number", "double"},
"float32": {"number", "float"},
"bool": {"boolean", ""},
"time.Time": {"string", "date-time"},
"string": {"string", ""},
"integer": {"integer", ""},
"number": {"number", ""},
"boolean": {"boolean", ""},
"[]byte": {"string", "byte"}, // base64 encoded characters
"interface{}": {"object", ""},
}
// This function is a reference for converting go (or any custom type) to a simple open API type,format pair. There are
// two ways to customize spec for a type. If you add it here, a type will be converted to a simple type and the type
// comment (the comment that is added before type definition) will be lost. The spec will still have the property
// comment. The second way is to implement OpenAPIDefinitionGetter interface. That function can customize the spec (so
// the spec does not need to be simple type,format) or can even return a simple type,format (e.g. IntOrString). For simple
// type formats, the benefit of adding OpenAPIDefinitionGetter interface is to keep both type and property documentation.
// Example:
// type Sample struct {
// ...
// // port of the server
// port IntOrString
// ...
// }
// // IntOrString documentation...
// type IntOrString { ... }
//
// Adding IntOrString to this function:
// "port" : {
// format: "string",
// type: "int-or-string",
// Description: "port of the server"
// }
//
// Implement OpenAPIDefinitionGetter for IntOrString:
//
// "port" : {
// $Ref: "#/definitions/IntOrString"
// Description: "port of the server"
// }
// ...
// definitions:
// {
// "IntOrString": {
// format: "string",
// type: "int-or-string",
// Description: "IntOrString documentation..." // new
// }
// }
//
func GetOpenAPITypeFormat(typeName string) (string, string) {
mapped, ok := schemaTypeFormatMap[typeName]
if !ok {
return "", ""
}
return mapped[0], mapped[1]
}
func EscapeJsonPointer(p string) string {
// Escaping reference name using rfc6901
p = strings.Replace(p, "~", "~0", -1)
p = strings.Replace(p, "/", "~1", -1)
return p
}

View File

@@ -1,19 +0,0 @@
/*
Copyright 2016 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 common holds shared code and types between open API code
// generator and spec generator.
package common

View File

@@ -1,49 +0,0 @@
# Generate OpenAPI definitions
- 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 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
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
being used in a client's fluent interface.
# Custom OpenAPI type definitions
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"
// ...
type Time struct {
time.Time
}
func (_ Time) OpenAPIDefinition() openapi.OpenAPIDefinition {
return openapi.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "date-time",
},
},
}
}
```
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" }
```

View File

@@ -1,219 +0,0 @@
/*
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
}

View File

@@ -1,91 +0,0 @@
/*
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,
},
}
}

View File

@@ -1,182 +0,0 @@
/*
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

@@ -1,454 +0,0 @@
/*
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

@@ -1,607 +0,0 @@
/*
Copyright 2016 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"
"path/filepath"
"reflect"
"sort"
"strings"
"k8s.io/gengo/generator"
"k8s.io/gengo/namer"
"k8s.io/gengo/types"
openapi "k8s.io/kube-openapi/pkg/common"
"k8s.io/klog"
)
// This is the comment tag that carries parameters for open API generation.
const tagName = "k8s:openapi-gen"
const tagOptional = "optional"
// Known values for the tag.
const (
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]
}
func getSingleTagsValue(comments []string, tag string) (string, error) {
tags, ok := types.ExtractCommentTags("+", comments)[tag]
if !ok || len(tags) == 0 {
return "", nil
}
if len(tags) > 1 {
return "", fmt.Errorf("multiple values are not allowed for tag %s", tag)
}
return tags[0], nil
}
func hasOpenAPITagValue(comments []string, value string) bool {
tagValues := getOpenAPITagValue(comments)
for _, val := range tagValues {
if val == value {
return true
}
}
return false
}
// hasOptionalTag returns true if the member has +optional in its comments or
// omitempty in its json tags.
func hasOptionalTag(m *types.Member) bool {
hasOptionalCommentTag := types.ExtractCommentTags(
"+", m.CommentLines)[tagOptional] != nil
hasOptionalJsonTag := strings.Contains(
reflect.StructTag(m.Tags).Get("json"), "omitempty")
return hasOptionalCommentTag || hasOptionalJsonTag
}
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
}
pkg := c.Universe.Package(t.Name.Package)
if hasOpenAPITagValue(pkg.Comments, tagValueTrue) {
return !hasOpenAPITagValue(t.CommentLines, tagValueFalse)
}
if hasOpenAPITagValue(t.CommentLines, tagValueTrue) {
return true
}
return false
}
const (
specPackagePath = "github.com/go-openapi/spec"
openAPICommonPackagePath = "k8s.io/kube-openapi/pkg/common"
)
// openApiGen produces a file with auto-generated OpenAPI functions.
type openAPIGen struct {
generator.DefaultGen
// TargetPackage is the package that will get GetOpenAPIDefinitions function returns all open API definitions.
targetPackage string
imports namer.ImportTracker
}
func newOpenAPIGen(sanitizedName string, targetPackage string) generator.Generator {
return &openAPIGen{
DefaultGen: generator.DefaultGen{
OptionalName: sanitizedName,
},
imports: generator.NewImportTracker(),
targetPackage: targetPackage,
}
}
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) isOtherPackage(pkg string) bool {
if pkg == g.targetPackage {
return false
}
if strings.HasSuffix(pkg, "\""+g.targetPackage+"\"") {
return false
}
return true
}
func (g *openAPIGen) Imports(c *generator.Context) []string {
importLines := []string{}
for _, singleImport := range g.imports.ImportLines() {
importLines = append(importLines, singleImport)
}
return importLines
}
func argsFromType(t *types.Type) generator.Args {
return generator.Args{
"type": t,
"ReferenceCallback": types.Ref(openAPICommonPackagePath, "ReferenceCallback"),
"OpenAPIDefinition": types.Ref(openAPICommonPackagePath, "OpenAPIDefinition"),
"SpecSchemaType": types.Ref(specPackagePath, "Schema"),
}
}
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))
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 {
klog.V(5).Infof("generating for type %v", t)
sw := generator.NewSnippetWriter(w, c, "$", "$")
err := newOpenAPITypeWriter(sw).generate(t)
if err != nil {
return err
}
return sw.Error()
}
func getJsonTags(m *types.Member) []string {
jsonTag := reflect.StructTag(m.Tags).Get("json")
if jsonTag == "" {
return []string{}
}
return strings.Split(jsonTag, ",")
}
func getReferableName(m *types.Member) string {
jsonTags := getJsonTags(m)
if len(jsonTags) > 0 {
if jsonTags[0] == "-" {
return ""
} else {
return jsonTags[0]
}
} else {
return m.Name
}
}
func shouldInlineMembers(m *types.Member) bool {
jsonTags := getJsonTags(m)
return len(jsonTags) > 1 && jsonTags[1] == "inline"
}
type openAPITypeWriter struct {
*generator.SnippetWriter
refTypes map[string]*types.Type
GetDefinitionInterface *types.Type
}
func newOpenAPITypeWriter(sw *generator.SnippetWriter) openAPITypeWriter {
return openAPITypeWriter{
SnippetWriter: sw,
refTypes: map[string]*types.Type{},
}
}
func methodReturnsValue(mt *types.Type, pkg, name string) bool {
if len(mt.Signature.Parameters) != 0 || len(mt.Signature.Results) != 1 {
return false
}
r := mt.Signature.Results[0]
return r.Name.Name == name && r.Name.Package == pkg
}
func hasOpenAPIDefinitionMethod(t *types.Type) bool {
for mn, mt := range t.Methods {
if mn != "OpenAPIDefinition" {
continue
}
return methodReturnsValue(mt, openAPICommonPackagePath, "OpenAPIDefinition")
}
return false
}
func hasOpenAPIDefinitionMethods(t *types.Type) bool {
var hasSchemaTypeMethod, hasOpenAPISchemaFormat bool
for mn, mt := range t.Methods {
switch mn {
case "OpenAPISchemaType":
hasSchemaTypeMethod = methodReturnsValue(mt, "", "[]string")
case "OpenAPISchemaFormat":
hasOpenAPISchemaFormat = methodReturnsValue(mt, "", "string")
}
}
return hasSchemaTypeMethod && hasOpenAPISchemaFormat
}
// typeShortName returns short package name (e.g. the name x appears in package x definition) dot type name.
func typeShortName(t *types.Type) string {
return filepath.Base(t.Name.Package) + "." + t.Name.Name
}
func (g openAPITypeWriter) generateMembers(t *types.Type, required []string) ([]string, error) {
var err error
for _, m := range t.Members {
if hasOpenAPITagValue(m.CommentLines, tagValueFalse) {
continue
}
if shouldInlineMembers(&m) {
required, err = g.generateMembers(m.Type, required)
if err != nil {
return required, err
}
continue
}
name := getReferableName(&m)
if name == "" {
continue
}
if !hasOptionalTag(&m) {
required = append(required, name)
}
if err = g.generateProperty(&m, t); err != nil {
klog.Errorf("Error when generating: %v, %v\n", name, m)
return required, err
}
}
return required, nil
}
func (g openAPITypeWriter) generateCall(t *types.Type) error {
// Only generate for struct type and ignore the rest
switch t.Kind {
case types.Struct:
args := argsFromType(t)
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) {
g.Do("return $.OpenAPIDefinition|raw${\n"+
"Schema: spec.Schema{\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}\n\n", args)
return 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{})
if err != nil {
return err
}
g.Do("},\n", nil)
if len(required) > 0 {
g.Do("Required: []string{\"$.$\"},\n", strings.Join(required, "\",\""))
}
g.Do("},\n", nil)
if err := g.generateStructExtensions(t); err != nil {
return err
}
g.Do("},\n", nil)
g.Do("Dependencies: []string{\n", args)
// Map order is undefined, sort them or we may get a different file generated each time.
keys := []string{}
for k := range g.refTypes {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
v := g.refTypes[k]
if t, _ := openapi.GetOpenAPITypeFormat(v.String()); t != "" {
// This is a known type, we do not need a reference to it
// Will eliminate special case of time.Time
continue
}
g.Do("\"$.$\",", k)
}
g.Do("},\n}\n}\n\n", nil)
}
return nil
}
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)
}
}
// 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
}
g.Do("VendorExtensible: spec.VendorExtensible{\nExtensions: spec.Extensions{\n", nil)
for _, extension := range extensions {
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)
}
// TODO(#44005): Move this validation outside of this generator (probably to policy verifier)
func (g openAPITypeWriter) validatePatchTags(m *types.Member, parent *types.Type) error {
// 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
}
func (g openAPITypeWriter) generateDescription(CommentLines []string) {
var buffer bytes.Buffer
delPrevChar := func() {
if buffer.Len() > 0 {
buffer.Truncate(buffer.Len() - 1) // Delete the last " " or "\n"
}
}
for _, line := range CommentLines {
// Ignore all lines after ---
if line == "---" {
break
}
line = strings.TrimRight(line, " ")
leading := strings.TrimLeft(line, " ")
switch {
case len(line) == 0: // Keep paragraphs
delPrevChar()
buffer.WriteString("\n\n")
case strings.HasPrefix(leading, "TODO"): // Ignore one line TODOs
case strings.HasPrefix(leading, "+"): // Ignore instructions to go2idl
default:
if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") {
delPrevChar()
line = "\n" + line + "\n" // Replace it with newline. This is useful when we have a line with: "Example:\n\tJSON-someting..."
} else {
line += " "
}
buffer.WriteString(line)
}
}
postDoc := strings.TrimRight(buffer.String(), "\n")
postDoc = strings.Replace(postDoc, "\\\"", "\"", -1) // replace user's \" to "
postDoc = strings.Replace(postDoc, "\"", "\\\"", -1) // Escape "
postDoc = strings.Replace(postDoc, "\n", "\\n", -1)
postDoc = strings.Replace(postDoc, "\t", "\\t", -1)
postDoc = strings.Trim(postDoc, " ")
if postDoc != "" {
g.Do("Description: \"$.$\",\n", postDoc)
}
}
func (g openAPITypeWriter) generateProperty(m *types.Member, parent *types.Type) error {
name := getReferableName(m)
if name == "" {
return nil
}
if err := g.validatePatchTags(m, parent); err != nil {
return err
}
g.Do("\"$.$\": {\n", name)
if err := g.generateMemberExtensions(m, parent); err != nil {
return err
}
g.Do("SchemaProps: spec.SchemaProps{\n", nil)
g.generateDescription(m.CommentLines)
jsonTags := getJsonTags(m)
if len(jsonTags) > 1 && jsonTags[1] == "string" {
g.generateSimpleProperty("string", "")
g.Do("},\n},\n", nil)
return nil
}
t := resolveAliasAndPtrType(m.Type)
// If we can get a openAPI type and format for this type, we consider it to be simple property
typeString, format := openapi.GetOpenAPITypeFormat(t.String())
if typeString != "" {
g.generateSimpleProperty(typeString, format)
g.Do("},\n},\n", nil)
return nil
}
switch t.Kind {
case types.Builtin:
return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", t)
case types.Map:
if err := g.generateMapProperty(t); err != nil {
return err
}
case types.Slice, types.Array:
if err := g.generateSliceProperty(t); err != nil {
return err
}
case types.Struct, types.Interface:
g.generateReferenceProperty(t)
default:
return fmt.Errorf("cannot generate spec for type %v", t)
}
g.Do("},\n},\n", nil)
return g.Error()
}
func (g openAPITypeWriter) generateSimpleProperty(typeString, format string) {
g.Do("Type: []string{\"$.$\"},\n", typeString)
g.Do("Format: \"$.$\",\n", format)
}
func (g openAPITypeWriter) generateReferenceProperty(t *types.Type) {
g.refTypes[t.Name.String()] = t
g.Do("Ref: ref(\"$.$\"),\n", t.Name.String())
}
func resolveAliasAndPtrType(t *types.Type) *types.Type {
var prev *types.Type
for prev != t {
prev = t
if t.Kind == types.Alias {
t = t.Underlying
}
if t.Kind == types.Pointer {
t = t.Elem
}
}
return t
}
func (g openAPITypeWriter) generateMapProperty(t *types.Type) error {
keyType := resolveAliasAndPtrType(t.Key)
elemType := resolveAliasAndPtrType(t.Elem)
// According to OpenAPI examples, only map from string is supported
if keyType.Name.Name != "string" {
return fmt.Errorf("map with non-string keys are not supported by OpenAPI in %v", t)
}
g.Do("Type: []string{\"object\"},\n", nil)
g.Do("AdditionalProperties: &spec.SchemaOrBool{\nSchema: &spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
typeString, format := openapi.GetOpenAPITypeFormat(elemType.String())
if typeString != "" {
g.generateSimpleProperty(typeString, format)
g.Do("},\n},\n},\n", nil)
return nil
}
switch elemType.Kind {
case types.Builtin:
return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", elemType)
case types.Struct:
g.generateReferenceProperty(elemType)
case types.Slice, types.Array:
g.generateSliceProperty(elemType)
default:
return fmt.Errorf("map Element kind %v is not supported in %v", elemType.Kind, t.Name)
}
g.Do("},\n},\n},\n", nil)
return nil
}
func (g openAPITypeWriter) generateSliceProperty(t *types.Type) error {
elemType := resolveAliasAndPtrType(t.Elem)
g.Do("Type: []string{\"array\"},\n", nil)
g.Do("Items: &spec.SchemaOrArray{\nSchema: &spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
typeString, format := openapi.GetOpenAPITypeFormat(elemType.String())
if typeString != "" {
g.generateSimpleProperty(typeString, format)
g.Do("},\n},\n},\n", nil)
return nil
}
switch elemType.Kind {
case types.Builtin:
return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", elemType)
case types.Struct:
g.generateReferenceProperty(elemType)
case types.Slice, types.Array:
g.generateSliceProperty(elemType)
default:
return fmt.Errorf("slice Element kind %v is not supported in %v", elemType.Kind, t)
}
g.Do("},\n},\n},\n", nil)
return nil
}

View File

@@ -1,625 +0,0 @@
/*
Copyright 2016 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"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/gengo/generator"
"k8s.io/gengo/namer"
"k8s.io/gengo/parser"
"k8s.io/gengo/types"
)
func construct(t *testing.T, files map[string]string, testNamer namer.Namer) (*parser.Builder, types.Universe, []*types.Type) {
b := parser.New()
for name, src := range files {
if err := b.AddFileForTest(filepath.Dir(name), name, []byte(src)); err != nil {
t.Fatal(err)
}
}
u, err := b.FindTypes()
if err != nil {
t.Fatal(err)
}
orderer := namer.Orderer{Namer: testNamer}
o := orderer.OrderUniverse(u)
return b, u, o
}
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,
}
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)
}
blahT := universe.Type(types.Name{Package: "base/foo", Name: "Blah"})
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) {
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 simple string
String string
// A simple int
Int int `+"`"+`json:",omitempty"`+"`"+`
// An int considered string simple int
IntString int `+"`"+`json:",string"`+"`"+`
// A simple int64
Int64 int64
// A simple int32
Int32 int32
// A simple int16
Int16 int16
// A simple int8
Int8 int8
// A simple int
Uint uint
// A simple int64
Uint64 uint64
// A simple int32
Uint32 uint32
// A simple int16
Uint16 uint16
// A simple int8
Uint8 uint8
// A simple byte
Byte byte
// A simple boolean
Bool bool
// A simple float64
Float64 float64
// A simple float32
Float32 float32
// a base64 encoded characters
ByteArray []byte
// a member with an extension
// +k8s:openapi-gen=x-kubernetes-member-tag:member_test
WithExtension string
// a member with struct tag as extension
// +patchStrategy=merge
// +patchMergeKey=pmk
WithStructTagExtension string `+"`"+`patchStrategy:"merge" patchMergeKey:"pmk"`+"`"+`
// a member with a list type
// +listType=atomic
WithListType []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{
"String": {
SchemaProps: spec.SchemaProps{
Description: "A simple string",
Type: []string{"string"},
Format: "",
},
},
"Int64": {
SchemaProps: spec.SchemaProps{
Description: "A simple int64",
Type: []string{"integer"},
Format: "int64",
},
},
"Int32": {
SchemaProps: spec.SchemaProps{
Description: "A simple int32",
Type: []string{"integer"},
Format: "int32",
},
},
"Int16": {
SchemaProps: spec.SchemaProps{
Description: "A simple int16",
Type: []string{"integer"},
Format: "int32",
},
},
"Int8": {
SchemaProps: spec.SchemaProps{
Description: "A simple int8",
Type: []string{"integer"},
Format: "byte",
},
},
"Uint": {
SchemaProps: spec.SchemaProps{
Description: "A simple int",
Type: []string{"integer"},
Format: "int32",
},
},
"Uint64": {
SchemaProps: spec.SchemaProps{
Description: "A simple int64",
Type: []string{"integer"},
Format: "int64",
},
},
"Uint32": {
SchemaProps: spec.SchemaProps{
Description: "A simple int32",
Type: []string{"integer"},
Format: "int64",
},
},
"Uint16": {
SchemaProps: spec.SchemaProps{
Description: "A simple int16",
Type: []string{"integer"},
Format: "int32",
},
},
"Uint8": {
SchemaProps: spec.SchemaProps{
Description: "A simple int8",
Type: []string{"integer"},
Format: "byte",
},
},
"Byte": {
SchemaProps: spec.SchemaProps{
Description: "A simple byte",
Type: []string{"integer"},
Format: "byte",
},
},
"Bool": {
SchemaProps: spec.SchemaProps{
Description: "A simple boolean",
Type: []string{"boolean"},
Format: "",
},
},
"Float64": {
SchemaProps: spec.SchemaProps{
Description: "A simple float64",
Type: []string{"number"},
Format: "double",
},
},
"Float32": {
SchemaProps: spec.SchemaProps{
Description: "A simple float32",
Type: []string{"number"},
Format: "float",
},
},
"ByteArray": {
SchemaProps: spec.SchemaProps{
Description: "a base64 encoded characters",
Type: []string{"string"},
Format: "byte",
},
},
"WithExtension": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-member-tag": "member_test",
},
},
SchemaProps: spec.SchemaProps{
Description: "a member with an extension",
Type: []string{"string"},
Format: "",
},
},
"WithStructTagExtension": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-patch-merge-key": "pmk",
"x-kubernetes-patch-strategy": "merge",
},
},
SchemaProps: spec.SchemaProps{
Description: "a member with struct tag as extension",
Type: []string{"string"},
Format: "",
},
},
"WithListType": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "atomic",
},
},
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{
"x-kubernetes-type-tag": "type_test",
},
},
},
Dependencies: []string{
},
}
}
`, funcBuffer.String())
}
func TestFailingSample1(t *testing.T) {
_, funcErr, assert, _, _ := testOpenAPITypeWriter(t, `
package foo
// Map sample tests openAPIGen.generateMapProperty method.
type Blah struct {
// A sample String to String map
StringToArray 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) {
_, funcErr, assert, _, _ := testOpenAPITypeWriter(t, `
package foo
// Map sample tests openAPIGen.generateMapProperty method.
type Blah struct {
// A sample String to String map
StringToArray 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) {
callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
package foo
import openapi "k8s.io/kube-openapi/pkg/common"
type Blah struct {
}
func (_ Blah) OpenAPIDefinition() openapi.OpenAPIDefinition {
return openapi.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "date-time",
},
},
}
}
`)
if callErr != nil {
t.Fatal(callErr)
}
if funcErr != nil {
t.Fatal(funcErr)
}
assert.Equal(`"base/foo.Blah": foo.Blah{}.OpenAPIDefinition(),
`, callBuffer.String())
assert.Equal(``, funcBuffer.String())
}
func TestCustomDefs(t *testing.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 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 custom type",
Type:foo.Blah{}.OpenAPISchemaType(),
Format:foo.Blah{}.OpenAPISchemaFormat(),
},
},
}
}
`, funcBuffer.String())
}
func TestPointer(t *testing.T) {
callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
package foo
// PointerSample demonstrate pointer's properties
type Blah struct {
// A string pointer
StringPointer *string
// A struct pointer
StructPointer *Blah
// A slice pointer
SlicePointer *[]string
// A map pointer
MapPointer *map[string]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: "PointerSample demonstrate pointer's properties",
Properties: map[string]spec.Schema{
"StringPointer": {
SchemaProps: spec.SchemaProps{
Description: "A string pointer",
Type: []string{"string"},
Format: "",
},
},
"StructPointer": {
SchemaProps: spec.SchemaProps{
Description: "A struct pointer",
Ref: ref("base/foo.Blah"),
},
},
"SlicePointer": {
SchemaProps: spec.SchemaProps{
Description: "A slice pointer",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
},
},
},
"MapPointer": {
SchemaProps: spec.SchemaProps{
Description: "A map pointer",
Type: []string{"object"},
AdditionalProperties: &spec.SchemaOrBool{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
Required: []string{"StringPointer","StructPointer","SlicePointer","MapPointer"},
},
},
Dependencies: []string{
"base/foo.Blah",},
}
}
`, funcBuffer.String())
}
func TestNestedLists(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 {
// Nested list
NestedList [][]int64
}
`)
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{
"NestedList": {
SchemaProps: spec.SchemaProps{
Description: "Nested list",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"integer"},
Format: "int64",
},
},
},
},
},
},
},
},
},
Required: []string{"NestedList"},
},
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-type-tag": "type_test",
},
},
},
Dependencies: []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())
}

View File

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

View File

@@ -1,23 +0,0 @@
/*
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

@@ -1,172 +0,0 @@
/*
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

@@ -1,359 +0,0 @@
/*
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

@@ -1,64 +0,0 @@
/*
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

@@ -1,110 +0,0 @@
/*
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

@@ -1,272 +0,0 @@
/*
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 handler
import (
"bytes"
"compress/gzip"
"crypto/sha512"
"encoding/json"
"fmt"
"mime"
"net/http"
"strings"
"sync"
"time"
"bitbucket.org/ww/goautoneg"
yaml "gopkg.in/yaml.v2"
"github.com/NYTimes/gziphandler"
restful "github.com/emicklei/go-restful"
"github.com/go-openapi/spec"
"github.com/golang/protobuf/proto"
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
"github.com/googleapis/gnostic/compiler"
"k8s.io/kube-openapi/pkg/builder"
"k8s.io/kube-openapi/pkg/common"
)
const (
jsonExt = ".json"
mimeJson = "application/json"
// TODO(mehdy): change @68f4ded to a version tag when gnostic add version tags.
mimePb = "application/com.github.googleapis.gnostic.OpenAPIv2@68f4ded+protobuf"
mimePbGz = "application/x-gzip"
)
// OpenAPIService is the service responsible for serving OpenAPI spec. It has
// the ability to safely change the spec while serving it.
type OpenAPIService struct {
// rwMutex protects All members of this service.
rwMutex sync.RWMutex
lastModified time.Time
specBytes []byte
specPb []byte
specPbGz []byte
specBytesETag string
specPbETag string
specPbGzETag string
}
func init() {
mime.AddExtensionType(".json", mimeJson)
mime.AddExtensionType(".pb-v1", mimePb)
mime.AddExtensionType(".gz", mimePbGz)
}
func computeETag(data []byte) string {
return fmt.Sprintf("\"%X\"", sha512.Sum512(data))
}
// NOTE: [DEPRECATION] We will announce deprecation for format-separated endpoints for OpenAPI spec,
// 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 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)
if err != nil {
return nil, err
}
return RegisterOpenAPIService(spec, servePath, handler)
}
// NOTE: [DEPRECATION] We will announce deprecation for format-separated endpoints for OpenAPI spec,
// 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 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) {
if !strings.HasSuffix(servePath, jsonExt) {
return nil, fmt.Errorf("serving path must end with \"%s\"", jsonExt)
}
servePathBase := strings.TrimSuffix(servePath, jsonExt)
o := OpenAPIService{}
if err := o.UpdateSpec(openapiSpec); err != nil {
return nil, err
}
type fileInfo struct {
ext string
getDataAndETag func() ([]byte, string, time.Time)
}
files := []fileInfo{
{".json", o.getSwaggerBytes},
{"-2.0.0.json", o.getSwaggerBytes},
{"-2.0.0.pb-v1", o.getSwaggerPbBytes},
{"-2.0.0.pb-v1.gz", o.getSwaggerPbGzBytes},
}
for _, file := range files {
path := servePathBase + file.ext
getDataAndETag := file.getDataAndETag
handler.Handle(path, gziphandler.GzipHandler(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
data, etag, lastModified := getDataAndETag()
w.Header().Set("Etag", etag)
// ServeContent will take care of caching using eTag.
http.ServeContent(w, r, path, lastModified, bytes.NewReader(data))
}),
))
}
return &o, nil
}
func (o *OpenAPIService) getSwaggerBytes() ([]byte, string, time.Time) {
o.rwMutex.RLock()
defer o.rwMutex.RUnlock()
return o.specBytes, o.specBytesETag, o.lastModified
}
func (o *OpenAPIService) getSwaggerPbBytes() ([]byte, string, time.Time) {
o.rwMutex.RLock()
defer o.rwMutex.RUnlock()
return o.specPb, o.specPbETag, o.lastModified
}
func (o *OpenAPIService) getSwaggerPbGzBytes() ([]byte, string, time.Time) {
o.rwMutex.RLock()
defer o.rwMutex.RUnlock()
return o.specPbGz, o.specPbGzETag, o.lastModified
}
func (o *OpenAPIService) UpdateSpec(openapiSpec *spec.Swagger) (err error) {
specBytes, err := json.MarshalIndent(openapiSpec, " ", " ")
if err != nil {
return err
}
specPb, err := toProtoBinary(specBytes)
if err != nil {
return err
}
specPbGz := toGzip(specPb)
specBytesETag := computeETag(specBytes)
specPbETag := computeETag(specPb)
specPbGzETag := computeETag(specPbGz)
lastModified := time.Now()
o.rwMutex.Lock()
defer o.rwMutex.Unlock()
o.specBytes = specBytes
o.specPb = specPb
o.specPbGz = specPbGz
o.specBytesETag = specBytesETag
o.specPbETag = specPbETag
o.specPbGzETag = specPbGzETag
o.lastModified = lastModified
return nil
}
func toProtoBinary(spec []byte) ([]byte, error) {
var info yaml.MapSlice
err := yaml.Unmarshal(spec, &info)
if err != nil {
return nil, err
}
document, err := openapi_v2.NewDocument(info, compiler.NewContext("$root", nil))
if err != nil {
return nil, err
}
return proto.Marshal(document)
}
func toGzip(data []byte) []byte {
var buf bytes.Buffer
zw := gzip.NewWriter(&buf)
zw.Write(data)
zw.Close()
return buf.Bytes()
}
// 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 {
return nil, err
}
accepted := []struct {
Type string
SubType string
GetDataAndETag func() ([]byte, string, time.Time)
}{
{"application", "json", o.getSwaggerBytes},
{"application", "com.github.proto-openapi.spec.v2@v1.0+protobuf", o.getSwaggerPbBytes},
}
handler.Handle(servePath, gziphandler.GzipHandler(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
decipherableFormats := r.Header.Get("Accept")
if decipherableFormats == "" {
decipherableFormats = "*/*"
}
clauses := goautoneg.ParseAccept(decipherableFormats)
w.Header().Add("Vary", "Accept")
for _, clause := range clauses {
for _, accepts := range accepted {
if clause.Type != accepts.Type && clause.Type != "*" {
continue
}
if clause.SubType != accepts.SubType && clause.SubType != "*" {
continue
}
// serve the first matching media type in the sorted clause list
data, etag, lastModified := accepts.GetDataAndETag()
w.Header().Set("Etag", etag)
// ServeContent will take care of caching using eTag.
http.ServeContent(w, r, servePath, lastModified, bytes.NewReader(data))
return
}
}
// Return 406 for not acceptable format
w.WriteHeader(406)
return
}),
))
return &o, nil
}
// 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)
if err != nil {
return nil, err
}
return RegisterOpenAPIVersionedService(spec, servePath, handler)
}

View File

@@ -1,89 +0,0 @@
package handler
import (
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"reflect"
"testing"
"github.com/go-openapi/spec"
)
var returnedSwagger = []byte(`{
"swagger": "2.0",
"info": {
"title": "Kubernetes",
"version": "v1.11.0"
}}`)
func TestRegisterOpenAPIVersionedService(t *testing.T) {
var s spec.Swagger
err := s.UnmarshalJSON(returnedSwagger)
if err != nil {
t.Errorf("Unexpected error in unmarshalling SwaggerJSON: %v", err)
}
returnedJSON, err := json.MarshalIndent(s, " ", " ")
if err != nil {
t.Errorf("Unexpected error in preparing returnedJSON: %v", err)
}
returnedPb, err := toProtoBinary(returnedJSON)
if err != nil {
t.Errorf("Unexpected error in preparing returnedPb: %v", err)
}
mux := http.NewServeMux()
_, err = RegisterOpenAPIVersionedService(&s, "/openapi/v2", mux)
if err != nil {
t.Errorf("Unexpected error in register OpenAPI versioned service: %v", err)
}
server := httptest.NewServer(mux)
defer server.Close()
client := server.Client()
tcs := []struct {
acceptHeader string
respStatus int
respBody []byte
}{
{"", 200, returnedJSON},
{"*/*", 200, returnedJSON},
{"application/*", 200, returnedJSON},
{"application/json", 200, returnedJSON},
{"test/test", 406, []byte{}},
{"application/test", 406, []byte{}},
{"application/test, */*", 200, returnedJSON},
{"application/test, application/json", 200, returnedJSON},
{"application/com.github.proto-openapi.spec.v2@v1.0+protobuf", 200, returnedPb},
{"application/json, application/com.github.proto-openapi.spec.v2@v1.0+protobuf", 200, returnedJSON},
{"application/com.github.proto-openapi.spec.v2@v1.0+protobuf, application/json", 200, returnedPb},
{"application/com.github.proto-openapi.spec.v2@v1.0+protobuf; q=0.5, application/json", 200, returnedJSON},
}
for _, tc := range tcs {
req, err := http.NewRequest("GET", server.URL+"/openapi/v2", nil)
if err != nil {
t.Errorf("Accept: %v: Unexpected error in creating new request: %v", tc.acceptHeader, err)
}
req.Header.Add("Accept", tc.acceptHeader)
resp, err := client.Do(req)
if err != nil {
t.Errorf("Accept: %v: Unexpected error in serving HTTP request: %v", tc.acceptHeader, err)
}
if resp.StatusCode != tc.respStatus {
t.Errorf("Accept: %v: Unexpected response status code, want: %v, got: %v", tc.acceptHeader, tc.respStatus, resp.StatusCode)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Errorf("Accept: %v: Unexpected error in reading response body: %v", tc.acceptHeader, err)
}
if !reflect.DeepEqual(body, tc.respBody) {
t.Errorf("Accept: %v: Response body mismatches, want: %v, got: %v", tc.acceptHeader, tc.respBody, body)
}
}
}

View File

@@ -1,140 +0,0 @@
/*
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

View File

@@ -1,56 +0,0 @@
/*
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
}
}

View File

@@ -1,26 +0,0 @@
/*
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
}
}

View File

@@ -1,30 +0,0 @@
/*
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
}
}

View File

@@ -1,242 +0,0 @@
/*
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
}

View File

@@ -1,68 +0,0 @@
/*
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

@@ -1,49 +0,0 @@
/*
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 proto_test
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

@@ -1,265 +0,0 @@
/*
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 proto_test
import (
"path/filepath"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kube-openapi/pkg/util/proto/testing"
)
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 v1.8 openAPIData", func() {
var models proto.Models
BeforeEach(func() {
s, err := fakeSchema.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())
})
It("should have a path", func() {
Expect(deployment.GetPath().Get()).To(Equal([]string{"io.k8s.api.apps.v1beta1.Deployment"}))
})
It("should have a kind key of type string", func() {
Expect(deployment.Fields).To(HaveKey("kind"))
key := deployment.Fields["kind"].(*proto.Primitive)
Expect(key).ToNot(BeNil())
Expect(key.Type).To(Equal("string"))
Expect(key.GetPath().Get()).To(Equal([]string{"io.k8s.api.apps.v1beta1.Deployment", ".kind"}))
})
It("should have a apiVersion key of type string", func() {
Expect(deployment.Fields).To(HaveKey("apiVersion"))
key := deployment.Fields["apiVersion"].(*proto.Primitive)
Expect(key).ToNot(BeNil())
Expect(key.Type).To(Equal("string"))
Expect(key.GetPath().Get()).To(Equal([]string{"io.k8s.api.apps.v1beta1.Deployment", ".apiVersion"}))
})
It("should have a metadata key of type Reference", func() {
Expect(deployment.Fields).To(HaveKey("metadata"))
key := deployment.Fields["metadata"].(proto.Reference)
Expect(key).ToNot(BeNil())
Expect(key.Reference()).To(Equal("io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"))
subSchema := key.SubSchema().(*proto.Kind)
Expect(subSchema).ToNot(BeNil())
})
var status *proto.Kind
It("should have a status key of type Reference", func() {
Expect(deployment.Fields).To(HaveKey("status"))
key := deployment.Fields["status"].(proto.Reference)
Expect(key).ToNot(BeNil())
Expect(key.Reference()).To(Equal("io.k8s.api.apps.v1beta1.DeploymentStatus"))
status = key.SubSchema().(*proto.Kind)
Expect(status).ToNot(BeNil())
})
It("should have a valid DeploymentStatus", func() {
By("having availableReplicas key")
Expect(status.Fields).To(HaveKey("availableReplicas"))
replicas := status.Fields["availableReplicas"].(*proto.Primitive)
Expect(replicas).ToNot(BeNil())
Expect(replicas.Type).To(Equal("integer"))
By("having conditions key")
Expect(status.Fields).To(HaveKey("conditions"))
conditions := status.Fields["conditions"].(*proto.Array)
Expect(conditions).ToNot(BeNil())
Expect(conditions.GetName()).To(Equal(`Array of Reference to "io.k8s.api.apps.v1beta1.DeploymentCondition"`))
Expect(conditions.GetExtensions()).To(Equal(map[string]interface{}{
"x-kubernetes-patch-merge-key": "type",
"x-kubernetes-patch-strategy": "merge",
}))
condition := conditions.SubType.(proto.Reference)
Expect(condition.Reference()).To(Equal("io.k8s.api.apps.v1beta1.DeploymentCondition"))
})
var spec *proto.Kind
It("should have a spec key of type Reference", func() {
Expect(deployment.Fields).To(HaveKey("spec"))
key := deployment.Fields["spec"].(proto.Reference)
Expect(key).ToNot(BeNil())
Expect(key.Reference()).To(Equal("io.k8s.api.apps.v1beta1.DeploymentSpec"))
spec = key.SubSchema().(*proto.Kind)
Expect(spec).ToNot(BeNil())
})
It("should have a spec with no gvk", func() {
_, found := spec.GetExtensions()["x-kubernetes-group-version-kind"]
Expect(found).To(BeFalse())
})
It("should have a spec with a PodTemplateSpec sub-field", func() {
Expect(spec.Fields).To(HaveKey("template"))
key := spec.Fields["template"].(proto.Reference)
Expect(key).ToNot(BeNil())
Expect(key.Reference()).To(Equal("io.k8s.api.core.v1.PodTemplateSpec"))
})
})
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() {
s, err := fakeSchema.OpenAPISchema()
Expect(err).To(BeNil())
models, err = proto.NewOpenAPIData(s)
Expect(err).To(BeNil())
})
model := "io.k8s.api.authorization.v1.LocalSubjectAccessReview"
var schema proto.Schema
It("should lookup the Schema by its model", func() {
schema = models.LookupModel(model)
Expect(schema).ToNot(BeNil())
})
var sarspec *proto.Kind
It("should be a Kind and have a spec", func() {
sar := schema.(*proto.Kind)
Expect(sar).ToNot(BeNil())
Expect(sar.Fields).To(HaveKey("spec"))
specRef := sar.Fields["spec"].(proto.Reference)
Expect(specRef).ToNot(BeNil())
Expect(specRef.Reference()).To(Equal("io.k8s.api.authorization.v1.SubjectAccessReviewSpec"))
sarspec = specRef.SubSchema().(*proto.Kind)
Expect(sarspec).ToNot(BeNil())
})
It("should have a valid SubjectAccessReviewSpec", func() {
Expect(sarspec.Fields).To(HaveKey("extra"))
extra := sarspec.Fields["extra"].(*proto.Map)
Expect(extra).ToNot(BeNil())
Expect(extra.GetName()).To(Equal("Map of Array of string"))
Expect(extra.GetPath().Get()).To(Equal([]string{"io.k8s.api.authorization.v1.SubjectAccessReviewSpec", ".extra"}))
array := extra.SubType.(*proto.Array)
Expect(array).ToNot(BeNil())
Expect(array.GetName()).To(Equal("Array of string"))
Expect(array.GetPath().Get()).To(Equal([]string{"io.k8s.api.authorization.v1.SubjectAccessReviewSpec", ".extra"}))
str := array.SubType.(*proto.Primitive)
Expect(str).ToNot(BeNil())
Expect(str.Type).To(Equal("string"))
Expect(str.GetName()).To(Equal("string"))
Expect(str.GetPath().Get()).To(Equal([]string{"io.k8s.api.authorization.v1.SubjectAccessReviewSpec", ".extra"}))
})
})
var _ = Describe("Path", func() {
It("can be created by NewPath", func() {
path := proto.NewPath("key")
Expect(path.String()).To(Equal("key"))
})
It("can create and print complex paths", func() {
key := proto.NewPath("key")
array := key.ArrayPath(12)
field := array.FieldPath("subKey")
Expect(field.String()).To(Equal("key[12].subKey"))
})
It("has a length", func() {
key := proto.NewPath("key")
array := key.ArrayPath(12)
field := array.FieldPath("subKey")
Expect(field.Len()).To(Equal(3))
})
It("can look like an array", func() {
key := proto.NewPath("key")
array := key.ArrayPath(12)
field := array.FieldPath("subKey")
Expect(field.Get()).To(Equal([]string{"key", "[12]", ".subKey"}))
})
})

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,68 +0,0 @@
/*
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 testing
import (
"io/ioutil"
"os"
"sync"
yaml "gopkg.in/yaml.v2"
"github.com/googleapis/gnostic/OpenAPIv2"
"github.com/googleapis/gnostic/compiler"
)
// Fake opens and returns a openapi swagger from a file Path. It will
// parse only once and then return the same copy everytime.
type Fake struct {
Path string
once sync.Once
document *openapi_v2.Document
err error
}
// OpenAPISchema returns the openapi document and a potential error.
func (f *Fake) OpenAPISchema() (*openapi_v2.Document, error) {
f.once.Do(func() {
_, err := os.Stat(f.Path)
if err != nil {
f.err = err
return
}
spec, err := ioutil.ReadFile(f.Path)
if err != nil {
f.err = err
return
}
var info yaml.MapSlice
err = yaml.Unmarshal(spec, &info)
if err != nil {
f.err = err
return
}
f.document, f.err = openapi_v2.NewDocument(info, compiler.NewContext("$root", nil))
})
return f.document, f.err
}
type Empty struct{}
func (Empty) OpenAPISchema() (*openapi_v2.Document, error) {
return nil, nil
}

View File

@@ -1,79 +0,0 @@
/*
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 (
"fmt"
)
type errors struct {
errors []error
}
func (e *errors) Errors() []error {
return e.errors
}
func (e *errors) AppendErrors(err ...error) {
e.errors = append(e.errors, err...)
}
type ValidationError struct {
Path string
Err error
}
func (e ValidationError) Error() string {
return fmt.Sprintf("ValidationError(%s): %v", e.Path, e.Err)
}
type InvalidTypeError struct {
Path string
Expected string
Actual string
}
func (e InvalidTypeError) Error() string {
return fmt.Sprintf("invalid type for %s: got %q, expected %q", e.Path, e.Actual, e.Expected)
}
type MissingRequiredFieldError struct {
Path string
Field string
}
func (e MissingRequiredFieldError) Error() string {
return fmt.Sprintf("missing required field %q in %s", e.Field, e.Path)
}
type UnknownFieldError struct {
Path string
Field string
}
func (e UnknownFieldError) Error() string {
return fmt.Sprintf("unknown field %q in %s", e.Field, e.Path)
}
type InvalidObjectTypeError struct {
Path string
Type string
}
func (e InvalidObjectTypeError) Error() string {
return fmt.Sprintf("unknown object type %q in %s", e.Type, e.Path)
}

View File

@@ -1,298 +0,0 @@
/*
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 (
"reflect"
"sort"
"k8s.io/kube-openapi/pkg/util/proto"
)
type validationItem interface {
proto.SchemaVisitor
Errors() []error
Path() *proto.Path
}
type baseItem struct {
errors errors
path proto.Path
}
// Errors returns the list of errors found for this item.
func (item *baseItem) Errors() []error {
return item.errors.Errors()
}
// AddValidationError wraps the given error into a ValidationError and
// attaches it to this item.
func (item *baseItem) AddValidationError(err error) {
item.errors.AppendErrors(ValidationError{Path: item.path.String(), Err: err})
}
// AddError adds a regular (non-validation related) error to the list.
func (item *baseItem) AddError(err error) {
item.errors.AppendErrors(err)
}
// CopyErrors adds a list of errors to this item. This is useful to copy
// errors from subitems.
func (item *baseItem) CopyErrors(errs []error) {
item.errors.AppendErrors(errs...)
}
// Path returns the path of this item, helps print useful errors.
func (item *baseItem) Path() *proto.Path {
return &item.path
}
// mapItem represents a map entry in the yaml.
type mapItem struct {
baseItem
Map map[string]interface{}
}
func (item *mapItem) sortedKeys() []string {
sortedKeys := []string{}
for key := range item.Map {
sortedKeys = append(sortedKeys, key)
}
sort.Strings(sortedKeys)
return sortedKeys
}
var _ validationItem = &mapItem{}
func (item *mapItem) VisitPrimitive(schema *proto.Primitive) {
item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: schema.Type, Actual: "map"})
}
func (item *mapItem) VisitArray(schema *proto.Array) {
item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "array", Actual: "map"})
}
func (item *mapItem) VisitMap(schema *proto.Map) {
for _, key := range item.sortedKeys() {
subItem, err := itemFactory(item.Path().FieldPath(key), item.Map[key])
if err != nil {
item.AddError(err)
continue
}
schema.SubType.Accept(subItem)
item.CopyErrors(subItem.Errors())
}
}
func (item *mapItem) VisitKind(schema *proto.Kind) {
// Verify each sub-field.
for _, key := range item.sortedKeys() {
if item.Map[key] == nil {
continue
}
subItem, err := itemFactory(item.Path().FieldPath(key), item.Map[key])
if err != nil {
item.AddError(err)
continue
}
if _, ok := schema.Fields[key]; !ok {
item.AddValidationError(UnknownFieldError{Path: schema.GetPath().String(), Field: key})
continue
}
schema.Fields[key].Accept(subItem)
item.CopyErrors(subItem.Errors())
}
// Verify that all required fields are present.
for _, required := range schema.RequiredFields {
if v, ok := item.Map[required]; !ok || v == nil {
item.AddValidationError(MissingRequiredFieldError{Path: schema.GetPath().String(), Field: required})
}
}
}
func (item *mapItem) VisitArbitrary(schema *proto.Arbitrary) {
}
func (item *mapItem) VisitReference(schema proto.Reference) {
// passthrough
schema.SubSchema().Accept(item)
}
// arrayItem represents a yaml array.
type arrayItem struct {
baseItem
Array []interface{}
}
var _ validationItem = &arrayItem{}
func (item *arrayItem) VisitPrimitive(schema *proto.Primitive) {
item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: schema.Type, Actual: "array"})
}
func (item *arrayItem) VisitArray(schema *proto.Array) {
for i, v := range item.Array {
path := item.Path().ArrayPath(i)
if v == nil {
item.AddValidationError(InvalidObjectTypeError{Type: "nil", Path: path.String()})
continue
}
subItem, err := itemFactory(path, v)
if err != nil {
item.AddError(err)
continue
}
schema.SubType.Accept(subItem)
item.CopyErrors(subItem.Errors())
}
}
func (item *arrayItem) VisitMap(schema *proto.Map) {
item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "map", Actual: "array"})
}
func (item *arrayItem) VisitKind(schema *proto.Kind) {
item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "map", Actual: "array"})
}
func (item *arrayItem) VisitArbitrary(schema *proto.Arbitrary) {
}
func (item *arrayItem) VisitReference(schema proto.Reference) {
// passthrough
schema.SubSchema().Accept(item)
}
// primitiveItem represents a yaml value.
type primitiveItem struct {
baseItem
Value interface{}
Kind string
}
var _ validationItem = &primitiveItem{}
func (item *primitiveItem) VisitPrimitive(schema *proto.Primitive) {
// Some types of primitives can match more than one (a number
// can be a string, but not the other way around). Return from
// the switch if we have a valid possible type conversion
// NOTE(apelisse): This logic is blindly copied from the
// existing swagger logic, and I'm not sure I agree with it.
switch schema.Type {
case proto.Boolean:
switch item.Kind {
case proto.Boolean:
return
}
case proto.Integer:
switch item.Kind {
case proto.Integer, proto.Number:
return
}
case proto.Number:
switch item.Kind {
case proto.Number:
return
}
case proto.String:
return
}
item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: schema.Type, Actual: item.Kind})
}
func (item *primitiveItem) VisitArray(schema *proto.Array) {
item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "array", Actual: item.Kind})
}
func (item *primitiveItem) VisitMap(schema *proto.Map) {
item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "map", Actual: item.Kind})
}
func (item *primitiveItem) VisitKind(schema *proto.Kind) {
item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "map", Actual: item.Kind})
}
func (item *primitiveItem) VisitArbitrary(schema *proto.Arbitrary) {
}
func (item *primitiveItem) VisitReference(schema proto.Reference) {
// passthrough
schema.SubSchema().Accept(item)
}
// itemFactory creates the relevant item type/visitor based on the current yaml type.
func itemFactory(path proto.Path, v interface{}) (validationItem, error) {
// We need to special case for no-type fields in yaml (e.g. empty item in list)
if v == nil {
return nil, InvalidObjectTypeError{Type: "nil", Path: path.String()}
}
kind := reflect.TypeOf(v).Kind()
switch kind {
case reflect.Bool:
return &primitiveItem{
baseItem: baseItem{path: path},
Value: v,
Kind: proto.Boolean,
}, nil
case reflect.Int,
reflect.Int8,
reflect.Int16,
reflect.Int32,
reflect.Int64,
reflect.Uint,
reflect.Uint8,
reflect.Uint16,
reflect.Uint32,
reflect.Uint64:
return &primitiveItem{
baseItem: baseItem{path: path},
Value: v,
Kind: proto.Integer,
}, nil
case reflect.Float32,
reflect.Float64:
return &primitiveItem{
baseItem: baseItem{path: path},
Value: v,
Kind: proto.Number,
}, nil
case reflect.String:
return &primitiveItem{
baseItem: baseItem{path: path},
Value: v,
Kind: proto.String,
}, nil
case reflect.Array,
reflect.Slice:
return &arrayItem{
baseItem: baseItem{path: path},
Array: v.([]interface{}),
}, nil
case reflect.Map:
return &mapItem{
baseItem: baseItem{path: path},
Map: v.(map[string]interface{}),
}, nil
}
return nil, InvalidObjectTypeError{Type: kind.String(), Path: path.String()}
}

View File

@@ -1,30 +0,0 @@
/*
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 (
"k8s.io/kube-openapi/pkg/util/proto"
)
func ValidateModel(obj interface{}, schema proto.Schema, name string) []error {
rootValidation, err := itemFactory(proto.NewPath(name), obj)
if err != nil {
return []error{err}
}
schema.Accept(rootValidation)
return rootValidation.Errors()
}

View File

@@ -1,49 +0,0 @@
/*
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

@@ -1,369 +0,0 @@
/*
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())
})
})

View File

@@ -1,27 +0,0 @@
/*
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{}

View File

@@ -1,207 +0,0 @@
/*
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

@@ -1,79 +0,0 @@
/*
Copyright 2016 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
// A simple trie implementation with Add and HasPrefix methods only.
type Trie struct {
children map[byte]*Trie
wordTail bool
word string
}
// NewTrie creates a Trie and add all strings in the provided list to it.
func NewTrie(list []string) Trie {
ret := Trie{
children: make(map[byte]*Trie),
wordTail: false,
}
for _, v := range list {
ret.Add(v)
}
return ret
}
// Add adds a word to this trie
func (t *Trie) Add(v string) {
root := t
for _, b := range []byte(v) {
child, exists := root.children[b]
if !exists {
child = &Trie{
children: make(map[byte]*Trie),
wordTail: false,
}
root.children[b] = child
}
root = child
}
root.wordTail = true
root.word = v
}
// HasPrefix returns true of v has any of the prefixes stored in this trie.
func (t *Trie) HasPrefix(v string) bool {
_, has := t.GetPrefix(v)
return has
}
// GetPrefix is like HasPrefix but return the prefix in case of match or empty string otherwise.
func (t *Trie) GetPrefix(v string) (string, bool) {
root := t
if root.wordTail {
return root.word, true
}
for _, b := range []byte(v) {
child, exists := root.children[b]
if !exists {
return "", false
}
if child.wordTail {
return child.word, true
}
root = child
}
return "", false
}

View File

@@ -1,59 +0,0 @@
/*
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 util
import (
"reflect"
"strings"
)
// ToCanonicalName converts Golang package/type name into canonical OpenAPI name.
// Examples:
// Input: k8s.io/api/core/v1.Pod
// Output: io.k8s.api.core.v1.Pod
//
// Input: k8s.io/api/core/v1
// Output: io.k8s.api.core.v1
func ToCanonicalName(name string) string {
nameParts := strings.Split(name, "/")
// Reverse first part. e.g., io.k8s... instead of k8s.io...
if len(nameParts) > 0 && strings.Contains(nameParts[0], ".") {
parts := strings.Split(nameParts[0], ".")
for i, j := 0, len(parts)-1; i < j; i, j = i+1, j-1 {
parts[i], parts[j] = parts[j], parts[i]
}
nameParts[0] = strings.Join(parts, ".")
}
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()
}

View File

@@ -1,58 +0,0 @@
/*
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)
}
}
}

View File

@@ -1,40 +0,0 @@
# 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

@@ -1,116 +0,0 @@
/*
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

@@ -1,159 +0,0 @@
/*
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

@@ -1,278 +0,0 @@
// +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

@@ -1,36 +0,0 @@
// 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

@@ -1,24 +0,0 @@
// 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

@@ -1,157 +0,0 @@
{
"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

@@ -1,14 +0,0 @@
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

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

View File

@@ -1,20 +0,0 @@
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

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