kubectl: Remove swagger 1.2 entirely.

This commit is contained in:
Antoine Pelisse 2017-09-28 15:58:53 -07:00
parent d6b18a96dd
commit d1ce36371e
23 changed files with 0 additions and 1217 deletions

View File

@ -24,7 +24,6 @@ go_library(
"//pkg/kubectl/resource:go_default_library",
"//pkg/kubectl/validation:go_default_library",
"//pkg/printers:go_default_library",
"//vendor/github.com/emicklei/go-restful-swagger12:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",

View File

@ -23,7 +23,6 @@ import (
"path/filepath"
"time"
swagger "github.com/emicklei/go-restful-swagger12"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@ -419,10 +418,6 @@ func (f *FakeFactory) Validator(validate bool) (validation.Schema, error) {
return f.tf.Validator, f.tf.Err
}
func (f *FakeFactory) SwaggerSchema(schema.GroupVersionKind) (*swagger.ApiDeclaration, error) {
return nil, nil
}
func (f *FakeFactory) OpenAPISchema() (openapi.Resources, error) {
return nil, nil
}
@ -765,10 +760,6 @@ func (f *fakeAPIFactory) SuggestedPodTemplateResources() []schema.GroupResource
return []schema.GroupResource{}
}
func (f *fakeAPIFactory) SwaggerSchema(schema.GroupVersionKind) (*swagger.ApiDeclaration, error) {
return nil, nil
}
func (f *fakeAPIFactory) OpenAPISchema() (openapi.Resources, error) {
if f.tf.OpenAPISchemaFunc != nil {
return f.tf.OpenAPISchemaFunc()

View File

@ -41,7 +41,6 @@ go_library(
"//pkg/printers:go_default_library",
"//pkg/printers/internalversion:go_default_library",
"//pkg/version:go_default_library",
"//vendor/github.com/emicklei/go-restful-swagger12:go_default_library",
"//vendor/github.com/evanphx/json-patch:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
@ -57,7 +56,6 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer/json:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
@ -105,8 +103,6 @@ go_test(
"//pkg/controller:go_default_library",
"//pkg/kubectl:go_default_library",
"//pkg/kubectl/resource:go_default_library",
"//pkg/kubectl/validation:go_default_library",
"//vendor/github.com/emicklei/go-restful-swagger12:go_default_library",
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",

View File

@ -24,13 +24,11 @@ import (
"sync"
"time"
"github.com/emicklei/go-restful-swagger12"
"github.com/golang/glog"
"github.com/googleapis/gnostic/OpenAPIv2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/discovery"
restclient "k8s.io/client-go/rest"
@ -233,10 +231,6 @@ func (d *CachedDiscoveryClient) ServerVersion() (*version.Info, error) {
return d.delegate.ServerVersion()
}
func (d *CachedDiscoveryClient) SwaggerSchema(version schema.GroupVersion) (*swagger.ApiDeclaration, error) {
return d.delegate.SwaggerSchema(version)
}
func (d *CachedDiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
return d.delegate.OpenAPISchema()
}

View File

@ -22,7 +22,6 @@ import (
"testing"
"time"
"github.com/emicklei/go-restful-swagger12"
"github.com/googleapis/gnostic/OpenAPIv2"
"github.com/stretchr/testify/assert"
@ -101,7 +100,6 @@ type fakeDiscoveryClient struct {
groupCalls int
resourceCalls int
versionCalls int
swaggerCalls int
openAPICalls int
serverResourcesHandler func() ([]*metav1.APIResourceList, error)
@ -166,11 +164,6 @@ func (c *fakeDiscoveryClient) ServerVersion() (*version.Info, error) {
return &version.Info{}, nil
}
func (c *fakeDiscoveryClient) SwaggerSchema(version schema.GroupVersion) (*swagger.ApiDeclaration, error) {
c.swaggerCalls = c.swaggerCalls + 1
return &swagger.ApiDeclaration{}, nil
}
func (c *fakeDiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
c.openAPICalls = c.openAPICalls + 1
return &openapi_v2.Document{}, nil

View File

@ -17,20 +17,13 @@ limitations under the License.
package util
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"os/user"
"path"
"sort"
"strconv"
"strings"
"time"
swagger "github.com/emicklei/go-restful-swagger12"
"github.com/golang/glog"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@ -39,7 +32,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer/json"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/discovery"
"k8s.io/client-go/kubernetes"
@ -233,8 +225,6 @@ type ObjectMappingFactory interface {
// Returns a schema that can validate objects stored on disk.
Validator(validate bool) (validation.Schema, error)
// SwaggerSchema returns the schema declaration for the provided group version kind.
SwaggerSchema(schema.GroupVersionKind) (*swagger.ApiDeclaration, error)
// OpenAPISchema returns the schema openapi schema definiton
OpenAPISchema() (openapi.Resources, error)
}
@ -389,145 +379,3 @@ func getServiceProtocols(spec api.ServiceSpec) map[string]string {
}
return result
}
type clientSwaggerSchema struct {
c restclient.Interface
cacheDir string
}
const schemaFileName = "schema.json"
type schemaClient interface {
Get() *restclient.Request
}
func recursiveSplit(dir string) []string {
parent, file := path.Split(dir)
if len(parent) == 0 {
return []string{file}
}
return append(recursiveSplit(parent[:len(parent)-1]), file)
}
func substituteUserHome(dir string) (string, error) {
if len(dir) == 0 || dir[0] != '~' {
return dir, nil
}
parts := recursiveSplit(dir)
if len(parts[0]) == 1 {
parts[0] = os.Getenv("HOME")
} else {
usr, err := user.Lookup(parts[0][1:])
if err != nil {
return "", err
}
parts[0] = usr.HomeDir
}
return path.Join(parts...), nil
}
func writeSchemaFile(schemaData []byte, cacheDir, cacheFile, prefix, groupVersion string) error {
if err := os.MkdirAll(path.Join(cacheDir, prefix, groupVersion), 0755); err != nil {
return err
}
tmpFile, err := ioutil.TempFile(cacheDir, "schema")
if err != nil {
// If we can't write, keep going.
if os.IsPermission(err) {
return nil
}
return err
}
if _, err := io.Copy(tmpFile, bytes.NewBuffer(schemaData)); err != nil {
return err
}
glog.V(4).Infof("Writing swagger cache (dir %v) file %v (from %v)", cacheDir, cacheFile, tmpFile.Name())
if err := os.Link(tmpFile.Name(), cacheFile); err != nil {
// If we can't write due to file existing, or permission problems, keep going.
if os.IsExist(err) || os.IsPermission(err) {
return nil
}
return err
}
return nil
}
func getSchemaAndValidate(c schemaClient, data []byte, prefix, groupVersion, cacheDir string, delegate validation.Schema) (err error) {
var schemaData []byte
var firstSeen bool
fullDir, err := substituteUserHome(cacheDir)
if err != nil {
return err
}
cacheFile := path.Join(fullDir, prefix, groupVersion, schemaFileName)
if len(cacheDir) != 0 {
if schemaData, err = ioutil.ReadFile(cacheFile); err != nil && !os.IsNotExist(err) {
return err
}
}
if schemaData == nil {
firstSeen = true
schemaData, err = downloadSchemaAndStore(c, cacheDir, fullDir, cacheFile, prefix, groupVersion)
if err != nil {
return err
}
}
schema, err := validation.NewSwaggerSchemaFromBytes(schemaData, delegate)
if err != nil {
return err
}
err = schema.ValidateBytes(data)
if _, ok := err.(validation.TypeNotFoundError); ok && !firstSeen {
// As a temporary hack, kubectl would re-get the schema if validation
// fails for type not found reason.
// TODO: runtime-config settings needs to make into the file's name
schemaData, err = downloadSchemaAndStore(c, cacheDir, fullDir, cacheFile, prefix, groupVersion)
if err != nil {
return err
}
schema, err := validation.NewSwaggerSchemaFromBytes(schemaData, delegate)
if err != nil {
return err
}
return schema.ValidateBytes(data)
}
return err
}
// Download swagger schema from apiserver and store it to file.
func downloadSchemaAndStore(c schemaClient, cacheDir, fullDir, cacheFile, prefix, groupVersion string) (schemaData []byte, err error) {
schemaData, err = c.Get().
AbsPath("/swaggerapi", prefix, groupVersion).
Do().
Raw()
if err != nil {
return
}
if len(cacheDir) != 0 {
if err = writeSchemaFile(schemaData, fullDir, cacheFile, prefix, groupVersion); err != nil {
return
}
}
return
}
func (c *clientSwaggerSchema) ValidateBytes(data []byte) error {
gvk, err := json.DefaultMetaFactory.Interpret(data)
if err != nil {
return err
}
if ok := api.Registry.IsEnabledVersion(gvk.GroupVersion()); !ok {
// if we don't have this in our scheme, just skip validation because its an object we don't recognize
return nil
}
switch gvk.Group {
case api.GroupName:
return getSchemaAndValidate(c.c, data, "api", gvk.GroupVersion().String(), c.cacheDir, c)
default:
return getSchemaAndValidate(c.c, data, "apis/", gvk.GroupVersion().String(), c.cacheDir, c)
}
}

View File

@ -26,14 +26,11 @@ import (
"sync"
"time"
swagger "github.com/emicklei/go-restful-swagger12"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
restclient "k8s.io/client-go/rest"
@ -423,15 +420,6 @@ func (f *ring1Factory) Validator(validate bool) (validation.Schema, error) {
}, nil
}
func (f *ring1Factory) SwaggerSchema(gvk schema.GroupVersionKind) (*swagger.ApiDeclaration, error) {
version := gvk.GroupVersion()
discovery, err := f.clientAccessFactory.DiscoveryClient()
if err != nil {
return nil, err
}
return discovery.SwaggerSchema(version)
}
// OpenAPISchema returns metadata and structural information about Kubernetes object definitions.
func (f *ring1Factory) OpenAPISchema() (openapi.Resources, error) {
discovery, err := f.clientAccessFactory.DiscoveryClient()

View File

@ -18,13 +18,7 @@ package util
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/user"
"path"
"sort"
"strings"
"testing"
@ -46,12 +40,10 @@ import (
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
"k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/validation"
)
func TestNewFactoryDefaultFlagBindings(t *testing.T) {
@ -230,208 +222,6 @@ func TestFlagUnderscoreRenaming(t *testing.T) {
}
}
func loadSchemaForTest() (validation.Schema, error) {
pathToSwaggerSpec := "../../../../api/swagger-spec/" + api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version + ".json"
data, err := ioutil.ReadFile(pathToSwaggerSpec)
if err != nil {
return nil, err
}
return validation.NewSwaggerSchemaFromBytes(data, nil)
}
func header() http.Header {
header := http.Header{}
header.Set("Content-Type", runtime.ContentTypeJSON)
return header
}
func TestRefetchSchemaWhenValidationFails(t *testing.T) {
schema, err := loadSchemaForTest()
if err != nil {
t.Errorf("Error loading schema: %v", err)
t.FailNow()
}
output, err := json.Marshal(schema)
if err != nil {
t.Errorf("Error serializing schema: %v", err)
t.FailNow()
}
requests := map[string]int{}
c := &manualfake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
Client: manualfake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case strings.HasPrefix(p, "/swaggerapi") && m == "GET":
requests[p] = requests[p] + 1
return &http.Response{StatusCode: 200, Header: header(), Body: ioutil.NopCloser(bytes.NewBuffer(output))}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
dir, err := ioutil.TempDir("", "schemaCache")
if err != nil {
t.Fatalf("Error getting tempDir: %v", err)
}
defer os.RemoveAll(dir)
fullDir, err := substituteUserHome(dir)
if err != nil {
t.Errorf("Error getting fullDir: %v", err)
t.FailNow()
}
cacheFile := path.Join(fullDir, "foo", "bar", schemaFileName)
err = writeSchemaFile(output, fullDir, cacheFile, "foo", "bar")
if err != nil {
t.Errorf("Error building old cache schema: %v", err)
t.FailNow()
}
obj := &extensions.Deployment{}
data, err := runtime.Encode(testapi.Extensions.Codec(), obj)
if err != nil {
t.Errorf("unexpected error: %v", err)
t.FailNow()
}
// Re-get request, should use HTTP and write
if getSchemaAndValidate(c, data, "foo", "bar", dir, nil); err != nil {
t.Errorf("unexpected error validating: %v", err)
}
if requests["/swaggerapi/foo/bar"] != 1 {
t.Errorf("expected 1 schema request, saw: %d", requests["/swaggerapi/foo/bar"])
}
}
func TestValidateCachesSchema(t *testing.T) {
schema, err := loadSchemaForTest()
if err != nil {
t.Errorf("Error loading schema: %v", err)
t.FailNow()
}
output, err := json.Marshal(schema)
if err != nil {
t.Errorf("Error serializing schema: %v", err)
t.FailNow()
}
requests := map[string]int{}
c := &manualfake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
Client: manualfake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case strings.HasPrefix(p, "/swaggerapi") && m == "GET":
requests[p] = requests[p] + 1
return &http.Response{StatusCode: 200, Header: header(), Body: ioutil.NopCloser(bytes.NewBuffer(output))}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
dir, err := ioutil.TempDir("", "schemaCache")
if err != nil {
t.Fatalf("Error getting tempDir: %v", err)
}
defer os.RemoveAll(dir)
obj := &api.Pod{}
data, err := runtime.Encode(testapi.Default.Codec(), obj)
if err != nil {
t.Errorf("unexpected error: %v", err)
t.FailNow()
}
// Initial request, should use HTTP and write
if getSchemaAndValidate(c, data, "foo", "bar", dir, nil); err != nil {
t.Errorf("unexpected error validating: %v", err)
}
if _, err := os.Stat(path.Join(dir, "foo", "bar", schemaFileName)); err != nil {
t.Errorf("unexpected missing cache file: %v", err)
}
if requests["/swaggerapi/foo/bar"] != 1 {
t.Errorf("expected 1 schema request, saw: %d", requests["/swaggerapi/foo/bar"])
}
// Same version and group, should skip HTTP
if getSchemaAndValidate(c, data, "foo", "bar", dir, nil); err != nil {
t.Errorf("unexpected error validating: %v", err)
}
if requests["/swaggerapi/foo/bar"] != 2 {
t.Errorf("expected 1 schema request, saw: %d", requests["/swaggerapi/foo/bar"])
}
// Different API group, should go to HTTP and write
if getSchemaAndValidate(c, data, "foo", "baz", dir, nil); err != nil {
t.Errorf("unexpected error validating: %v", err)
}
if _, err := os.Stat(path.Join(dir, "foo", "baz", schemaFileName)); err != nil {
t.Errorf("unexpected missing cache file: %v", err)
}
if requests["/swaggerapi/foo/baz"] != 1 {
t.Errorf("expected 1 schema request, saw: %d", requests["/swaggerapi/foo/baz"])
}
// Different version, should go to HTTP and write
if getSchemaAndValidate(c, data, "foo2", "bar", dir, nil); err != nil {
t.Errorf("unexpected error validating: %v", err)
}
if _, err := os.Stat(path.Join(dir, "foo2", "bar", schemaFileName)); err != nil {
t.Errorf("unexpected missing cache file: %v", err)
}
if requests["/swaggerapi/foo2/bar"] != 1 {
t.Errorf("expected 1 schema request, saw: %d", requests["/swaggerapi/foo2/bar"])
}
// No cache dir, should go straight to HTTP and not write
if getSchemaAndValidate(c, data, "foo", "blah", "", nil); err != nil {
t.Errorf("unexpected error validating: %v", err)
}
if requests["/swaggerapi/foo/blah"] != 1 {
t.Errorf("expected 1 schema request, saw: %d", requests["/swaggerapi/foo/blah"])
}
if _, err := os.Stat(path.Join(dir, "foo", "blah", schemaFileName)); err == nil || !os.IsNotExist(err) {
t.Errorf("unexpected cache file error: %v", err)
}
}
func TestSubstitueUser(t *testing.T) {
usr, err := user.Current()
if err != nil {
t.Logf("SKIPPING TEST: unexpected error: %v", err)
return
}
tests := []struct {
input string
expected string
expectErr bool
}{
{input: "~/foo", expected: path.Join(os.Getenv("HOME"), "foo")},
{input: "~" + usr.Username + "/bar", expected: usr.HomeDir + "/bar"},
{input: "/foo/bar", expected: "/foo/bar"},
{input: "~doesntexit/bar", expectErr: true},
}
for _, test := range tests {
output, err := substituteUserHome(test.input)
if test.expectErr {
if err == nil {
t.Error("unexpected non-error")
}
continue
}
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if output != test.expected {
t.Errorf("expected: %s, saw: %s", test.expected, output)
}
}
}
func newPodList(count, isUnready, isUnhealthy int, labels map[string]string) *api.PodList {
pods := []api.Pod{}
for i := 0; i < count; i++ {

View File

@ -61,7 +61,6 @@ go_test(
"//pkg/api:go_default_library",
"//pkg/api/testapi:go_default_library",
"//pkg/api/testing:go_default_library",
"//vendor/github.com/emicklei/go-restful-swagger12:go_default_library",
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",

View File

@ -20,8 +20,6 @@ import (
"reflect"
"testing"
swagger "github.com/emicklei/go-restful-swagger12"
"github.com/googleapis/gnostic/OpenAPIv2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
@ -183,10 +181,6 @@ func (c *fakeDiscoveryClient) ServerVersion() (*version.Info, error) {
return &version.Info{}, nil
}
func (c *fakeDiscoveryClient) SwaggerSchema(version schema.GroupVersion) (*swagger.ApiDeclaration, error) {
return &swagger.ApiDeclaration{}, nil
}
func (c *fakeDiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
return &openapi_v2.Document{}, nil
}

View File

@ -11,38 +11,13 @@ exports_files(
visibility = ["//build/visible_to:COMMON_testing"],
)
filegroup(
name = "testdata",
srcs = [
"testdata/v1/invalidPod.yaml",
"testdata/v1/invalidPod1.json",
"testdata/v1/invalidPod2.json",
"testdata/v1/invalidPod3.json",
"testdata/v1/invalidPod4.yaml",
"testdata/v1/validPod.yaml",
],
visibility = ["//build/visible_to:COMMON_testing"],
)
go_test(
name = "go_default_test",
srcs = ["schema_test.go"],
data = [
":testdata",
"//api/swagger-spec",
],
library = ":go_default_library",
deps = [
"//pkg/api:go_default_library",
"//pkg/api/testapi:go_default_library",
"//pkg/api/testing:go_default_library",
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/testing/fuzzer:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
],
)
go_library(
@ -50,13 +25,8 @@ go_library(
srcs = ["schema.go"],
visibility = ["//build/visible_to:pkg_kubectl_validation_CONSUMERS"],
deps = [
"//pkg/api/util:go_default_library",
"//vendor/github.com/emicklei/go-restful-swagger12:go_default_library",
"//vendor/github.com/exponent-io/jsonpath:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
],
)

View File

@ -20,43 +20,11 @@ import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"regexp"
"strings"
swagger "github.com/emicklei/go-restful-swagger12"
ejson "github.com/exponent-io/jsonpath"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/yaml"
apiutil "k8s.io/kubernetes/pkg/api/util"
)
// InvalidTypeError records information about an invalid type.
type InvalidTypeError struct {
ExpectedKind reflect.Kind
ObservedKind reflect.Kind
FieldName string
}
func (i *InvalidTypeError) Error() string {
return fmt.Sprintf("expected type %s, for field %s, got %s", i.ExpectedKind.String(), i.FieldName, i.ObservedKind.String())
}
// NewInvalidTypeError returns an instance of NewInvalidTypeError.
func NewInvalidTypeError(expected reflect.Kind, observed reflect.Kind, fieldName string) error {
return &InvalidTypeError{expected, observed, fieldName}
}
// TypeNotFoundError is returned when specified type
// can not found in schema
type TypeNotFoundError string
func (tnfe TypeNotFoundError) Error() string {
return fmt.Sprintf("couldn't find type: %s", string(tnfe))
}
// Schema is an interface that knows how to validate an API object serialized to a byte array.
type Schema interface {
ValidateBytes(data []byte) error
@ -133,314 +101,3 @@ func (c ConjunctiveSchema) ValidateBytes(data []byte) error {
}
return utilerrors.NewAggregate(list)
}
// SwaggerSchema is a schema based on an OpenAPI spec.
type SwaggerSchema struct {
api swagger.ApiDeclaration
delegate Schema // For delegating to other api groups
}
// NewSwaggerSchemaFromBytes creates an instance of SwaggerSchema from bytes.
func NewSwaggerSchemaFromBytes(data []byte, factory Schema) (Schema, error) {
schema := &SwaggerSchema{}
err := json.Unmarshal(data, &schema.api)
if err != nil {
return nil, err
}
schema.delegate = factory
return schema, nil
}
// validateList unpacks a list and validate every item in the list.
// It return nil if every item is ok.
// Otherwise it return an error list contain errors of every item.
func (s *SwaggerSchema) validateList(obj map[string]interface{}) []error {
items, exists := obj["items"]
if !exists {
return []error{fmt.Errorf("no items field in %#v", obj)}
}
return s.validateItems(items)
}
func (s *SwaggerSchema) validateItems(items interface{}) []error {
allErrs := []error{}
itemList, ok := items.([]interface{})
if !ok {
return append(allErrs, fmt.Errorf("items isn't a slice"))
}
for i, item := range itemList {
fields, ok := item.(map[string]interface{})
if !ok {
allErrs = append(allErrs, fmt.Errorf("items[%d] isn't a map[string]interface{}", i))
continue
}
groupVersion := fields["apiVersion"]
if groupVersion == nil {
allErrs = append(allErrs, fmt.Errorf("items[%d].apiVersion not set", i))
continue
}
itemVersion, ok := groupVersion.(string)
if !ok {
allErrs = append(allErrs, fmt.Errorf("items[%d].apiVersion isn't string type", i))
continue
}
if len(itemVersion) == 0 {
allErrs = append(allErrs, fmt.Errorf("items[%d].apiVersion is empty", i))
}
kind := fields["kind"]
if kind == nil {
allErrs = append(allErrs, fmt.Errorf("items[%d].kind not set", i))
continue
}
itemKind, ok := kind.(string)
if !ok {
allErrs = append(allErrs, fmt.Errorf("items[%d].kind isn't string type", i))
continue
}
if len(itemKind) == 0 {
allErrs = append(allErrs, fmt.Errorf("items[%d].kind is empty", i))
}
version := apiutil.GetVersion(itemVersion)
errs := s.ValidateObject(item, "", version+"."+itemKind)
if len(errs) >= 1 {
allErrs = append(allErrs, errs...)
}
}
return allErrs
}
// ValidateBytes validates bytes in a SwaggerSchema.
func (s *SwaggerSchema) ValidateBytes(data []byte) error {
var obj interface{}
out, err := yaml.ToJSON(data)
if err != nil {
return err
}
data = out
if err := json.Unmarshal(data, &obj); err != nil {
return err
}
fields, ok := obj.(map[string]interface{})
if !ok {
return fmt.Errorf("error in unmarshaling data %s", string(data))
}
groupVersion := fields["apiVersion"]
if groupVersion == nil {
return fmt.Errorf("apiVersion not set")
}
if _, ok := groupVersion.(string); !ok {
return fmt.Errorf("apiVersion isn't string type")
}
kind := fields["kind"]
if kind == nil {
return fmt.Errorf("kind not set")
}
if _, ok := kind.(string); !ok {
return fmt.Errorf("kind isn't string type")
}
if strings.HasSuffix(kind.(string), "List") {
return utilerrors.NewAggregate(s.validateList(fields))
}
version := apiutil.GetVersion(groupVersion.(string))
allErrs := s.ValidateObject(obj, "", version+"."+kind.(string))
if len(allErrs) == 1 {
return allErrs[0]
}
return utilerrors.NewAggregate(allErrs)
}
// ValidateObject returns no errors for a valid object.
func (s *SwaggerSchema) ValidateObject(obj interface{}, fieldName, typeName string) []error {
allErrs := []error{}
models := s.api.Models
model, ok := models.At(typeName)
// Verify the api version matches. This is required for nested types with differing api versions because
// s.api only has schema for 1 api version (the parent object type's version).
// e.g. an extensions/v1beta1 Template embedding a /v1 Service requires the schema for the extensions/v1beta1
// api to delegate to the schema for the /v1 api.
// Only do this for !ok objects so that cross APIVersion vendored types take precedence.
if !ok && s.delegate != nil {
fields, mapOk := obj.(map[string]interface{})
if !mapOk {
return append(allErrs, fmt.Errorf("field %s for %s: expected object of type map[string]interface{}, but the actual type is %T", fieldName, typeName, obj))
}
if delegated, err := s.delegateIfDifferentAPIVersion(&unstructured.Unstructured{Object: fields}); delegated {
if err != nil {
allErrs = append(allErrs, err)
}
return allErrs
}
}
if !ok {
return append(allErrs, TypeNotFoundError(typeName))
}
properties := model.Properties
if len(properties.List) == 0 {
// The object does not have any sub-fields.
return nil
}
fields, ok := obj.(map[string]interface{})
if !ok {
return append(allErrs, fmt.Errorf("field %s for %s: expected object of type map[string]interface{}, but the actual type is %T", fieldName, typeName, obj))
}
if len(fieldName) > 0 {
fieldName = fieldName + "."
}
// handle required fields
for _, requiredKey := range model.Required {
if _, ok := fields[requiredKey]; !ok {
allErrs = append(allErrs, fmt.Errorf("field %s%s for %s is required", fieldName, requiredKey, typeName))
}
}
for key, value := range fields {
details, ok := properties.At(key)
// Special case for runtime.RawExtension and runtime.Objects because they always fail to validate
// This is because the actual values will be of some sub-type (e.g. Deployment) not the expected
// super-type (RawExtension)
if s.isGenericArray(details) {
errs := s.validateItems(value)
if len(errs) > 0 {
allErrs = append(allErrs, errs...)
}
continue
}
if !ok {
allErrs = append(allErrs, fmt.Errorf("found invalid field %s for %s", key, typeName))
continue
}
if details.Type == nil && details.Ref == nil {
allErrs = append(allErrs, fmt.Errorf("could not find the type of %s%s from object %v", fieldName, key, details))
}
var fieldType string
if details.Type != nil {
fieldType = *details.Type
} else {
fieldType = *details.Ref
}
if value == nil {
glog.V(2).Infof("Skipping nil field: %s%s", fieldName, key)
continue
}
errs := s.validateField(value, fieldName+key, fieldType, &details)
if len(errs) > 0 {
allErrs = append(allErrs, errs...)
}
}
return allErrs
}
// delegateIfDifferentAPIVersion delegates the validation of an object if its ApiGroup does not match the
// current SwaggerSchema.
// First return value is true if the validation was delegated (by a different ApiGroup SwaggerSchema)
// Second return value is the result of the delegated validation if performed.
func (s *SwaggerSchema) delegateIfDifferentAPIVersion(obj *unstructured.Unstructured) (bool, error) {
// Never delegate objects in the same APIVersion or we will get infinite recursion
if !s.isDifferentAPIVersion(obj) {
return false, nil
}
// Convert the object back into bytes so that we can pass it to the ValidateBytes function
m, err := json.Marshal(obj.Object)
if err != nil {
return true, err
}
// Delegate validation of this object to the correct SwaggerSchema for its ApiGroup
return true, s.delegate.ValidateBytes(m)
}
// isDifferentAPIVersion Returns true if obj lives in a different APIVersion than the SwaggerSchema does.
// The SwaggerSchema will not be able to process objects in different APIVersions unless they are vendored.
func (s *SwaggerSchema) isDifferentAPIVersion(obj *unstructured.Unstructured) bool {
groupVersion := obj.GetAPIVersion()
return len(groupVersion) > 0 && s.api.ApiVersion != groupVersion
}
// isGenericArray Returns true if p is an array of generic Objects - either RawExtension or Object.
func (s *SwaggerSchema) isGenericArray(p swagger.ModelProperty) bool {
return p.DataTypeFields.Type != nil &&
*p.DataTypeFields.Type == "array" &&
p.Items != nil &&
p.Items.Ref != nil &&
(*p.Items.Ref == "runtime.RawExtension" || *p.Items.Ref == "runtime.Object")
}
// This matches type name in the swagger spec, such as "v1.Binding".
var versionRegexp = regexp.MustCompile(`^(v.+|unversioned|types)\..*`)
func (s *SwaggerSchema) validateField(value interface{}, fieldName, fieldType string, fieldDetails *swagger.ModelProperty) []error {
allErrs := []error{}
if reflect.TypeOf(value) == nil {
return append(allErrs, fmt.Errorf("unexpected nil value for field %v", fieldName))
}
// TODO: caesarxuchao: because we have multiple group/versions and objects
// may reference objects in other group, the commented out way of checking
// if a filedType is a type defined by us is outdated. We use a hacky way
// for now.
// TODO: the type name in the swagger spec is something like "v1.Binding",
// and the "v1" is generated from the package name, not the groupVersion of
// the type. We need to fix go-restful to embed the group name in the type
// name, otherwise we couldn't handle identically named types in different
// groups correctly.
if versionRegexp.MatchString(fieldType) {
// if strings.HasPrefix(fieldType, apiVersion) {
return s.ValidateObject(value, fieldName, fieldType)
}
switch fieldType {
case "string":
// Be loose about what we accept for 'string' since we use IntOrString in a couple of places
_, isString := value.(string)
_, isNumber := value.(float64)
_, isInteger := value.(int)
if !isString && !isNumber && !isInteger {
return append(allErrs, NewInvalidTypeError(reflect.String, reflect.TypeOf(value).Kind(), fieldName))
}
case "array":
arr, ok := value.([]interface{})
if !ok {
return append(allErrs, NewInvalidTypeError(reflect.Array, reflect.TypeOf(value).Kind(), fieldName))
}
var arrType string
if fieldDetails.Items.Ref == nil && fieldDetails.Items.Type == nil {
return append(allErrs, NewInvalidTypeError(reflect.Array, reflect.TypeOf(value).Kind(), fieldName))
}
if fieldDetails.Items.Ref != nil {
arrType = *fieldDetails.Items.Ref
} else {
arrType = *fieldDetails.Items.Type
}
for ix := range arr {
errs := s.validateField(arr[ix], fmt.Sprintf("%s[%d]", fieldName, ix), arrType, nil)
if len(errs) > 0 {
allErrs = append(allErrs, errs...)
}
}
case "uint64":
case "int64":
case "integer":
_, isNumber := value.(float64)
_, isInteger := value.(int)
if !isNumber && !isInteger {
return append(allErrs, NewInvalidTypeError(reflect.Int, reflect.TypeOf(value).Kind(), fieldName))
}
case "float64":
if _, ok := value.(float64); !ok {
return append(allErrs, NewInvalidTypeError(reflect.Float64, reflect.TypeOf(value).Kind(), fieldName))
}
case "boolean":
if _, ok := value.(bool); !ok {
return append(allErrs, NewInvalidTypeError(reflect.Bool, reflect.TypeOf(value).Kind(), fieldName))
}
// API servers before release 1.3 produce swagger spec with `type: "any"` as the fallback type, while newer servers produce spec with `type: "object"`.
// We have both here so that kubectl can work with both old and new api servers.
case "object":
case "any":
default:
return append(allErrs, fmt.Errorf("unexpected type: %v", fieldType))
}
return allErrs
}

View File

@ -17,297 +17,10 @@ limitations under the License.
package validation
import (
"encoding/json"
"fmt"
"io/ioutil"
"math/rand"
"strings"
"testing"
"github.com/ghodss/yaml"
"k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/api/testing/fuzzer"
"k8s.io/apimachinery/pkg/runtime"
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi"
kapitesting "k8s.io/kubernetes/pkg/api/testing"
)
func readPod(filename string) ([]byte, error) {
data, err := ioutil.ReadFile("testdata/" + api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version + "/" + filename)
return data, err
}
func readSwaggerFile() ([]byte, error) {
return readSwaggerAPIFile(testapi.Default)
}
func readSwaggerAPIFile(group testapi.TestGroup) ([]byte, error) {
// TODO: Figure out a better way of finding these files
var pathToSwaggerSpec string
if group.GroupVersion().Group == "" {
pathToSwaggerSpec = "../../../api/swagger-spec/" + group.GroupVersion().Version + ".json"
} else {
pathToSwaggerSpec = "../../../api/swagger-spec/" + group.GroupVersion().Group + "_" + group.GroupVersion().Version + ".json"
}
return ioutil.ReadFile(pathToSwaggerSpec)
}
// Mock delegating Schema. Not a full fake impl.
type Factory struct {
defaultSchema Schema
extensionsSchema Schema
}
var _ Schema = &Factory{}
// TODO: Consider using a mocking library instead or fully fleshing this out into a fake impl and putting it in some
// generally available location
func (f *Factory) ValidateBytes(data []byte) error {
var obj interface{}
out, err := k8syaml.ToJSON(data)
if err != nil {
return err
}
data = out
if err := json.Unmarshal(data, &obj); err != nil {
return err
}
fields, ok := obj.(map[string]interface{})
if !ok {
return fmt.Errorf("error in unmarshaling data %s", string(data))
}
// Note: This only supports the 2 api versions we expect from the test it is currently supporting.
groupVersion := fields["apiVersion"]
switch groupVersion {
case "v1":
return f.defaultSchema.ValidateBytes(data)
case "extensions/v1beta1":
return f.extensionsSchema.ValidateBytes(data)
default:
return fmt.Errorf("Unsupported API version %s", groupVersion)
}
}
func loadSchemaForTest() (Schema, error) {
data, err := readSwaggerFile()
if err != nil {
return nil, err
}
return NewSwaggerSchemaFromBytes(data, nil)
}
func loadSchemaForTestWithFactory(group testapi.TestGroup, factory Schema) (Schema, error) {
data, err := readSwaggerAPIFile(group)
if err != nil {
return nil, err
}
return NewSwaggerSchemaFromBytes(data, factory)
}
func NewFactory() (*Factory, error) {
f := &Factory{}
defaultSchema, err := loadSchemaForTestWithFactory(testapi.Default, f)
if err != nil {
return nil, err
}
f.defaultSchema = defaultSchema
extensionSchema, err := loadSchemaForTestWithFactory(testapi.Extensions, f)
if err != nil {
return nil, err
}
f.extensionsSchema = extensionSchema
return f, nil
}
func TestLoad(t *testing.T) {
_, err := loadSchemaForTest()
if err != nil {
t.Errorf("Failed to load: %v", err)
}
}
func TestValidateOk(t *testing.T) {
schema, err := loadSchemaForTest()
if err != nil {
t.Fatalf("Failed to load: %v", err)
}
tests := []struct {
obj runtime.Object
typeName string
}{
{obj: &api.Pod{}},
{obj: &api.Service{}},
{obj: &api.ReplicationController{}},
}
seed := rand.Int63()
apiObjectFuzzer := fuzzer.FuzzerFor(kapitesting.FuzzerFuncs, rand.NewSource(seed), api.Codecs)
for i := 0; i < 5; i++ {
for _, test := range tests {
testObj := test.obj
apiObjectFuzzer.Fuzz(testObj)
data, err := runtime.Encode(testapi.Default.Codec(), testObj)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
err = schema.ValidateBytes(data)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
}
}
}
func TestValidateDifferentApiVersions(t *testing.T) {
schema, err := loadSchemaForTest()
if err != nil {
t.Fatalf("Failed to load: %v", err)
}
pod := &v1.Pod{}
pod.APIVersion = "v1"
pod.Kind = "Pod"
deployment := &v1beta1.Deployment{}
deployment.APIVersion = "extensions/v1beta1"
deployment.Kind = "Deployment"
list := &v1.List{}
list.APIVersion = "v1"
list.Kind = "List"
list.Items = []runtime.RawExtension{{Object: pod}, {Object: deployment}}
bytes, err := json.Marshal(list)
if err != nil {
t.Error(err)
}
err = schema.ValidateBytes(bytes)
if err == nil {
t.Error(fmt.Errorf("expected error when validating different api version and no delegate exists"))
}
f, err := NewFactory()
if err != nil {
t.Error(fmt.Errorf("failed to create Schema factory %v", err))
}
err = f.ValidateBytes(bytes)
if err != nil {
t.Error(fmt.Errorf("failed to validate object with multiple ApiGroups: %v", err))
}
}
func TestInvalid(t *testing.T) {
schema, err := loadSchemaForTest()
if err != nil {
t.Fatalf("Failed to load: %v", err)
}
tests := []string{
"invalidPod1.json", // command is a string, instead of []string.
"invalidPod2.json", // hostPort if of type string, instead of int.
"invalidPod3.json", // volumes is not an array of objects.
"invalidPod4.yaml", // string list with empty string.
"invalidPod.yaml", // command is a string, instead of []string.
}
for _, test := range tests {
pod, err := readPod(test)
if err != nil {
t.Errorf("could not read file: %s, err: %v", test, err)
}
err = schema.ValidateBytes(pod)
if err == nil {
t.Errorf("unexpected non-error, err: %s for pod: %s", err, pod)
}
}
}
func TestValid(t *testing.T) {
schema, err := loadSchemaForTest()
if err != nil {
t.Fatalf("Failed to load: %v", err)
}
tests := []string{
"validPod.yaml",
}
for _, test := range tests {
pod, err := readPod(test)
if err != nil {
t.Errorf("could not read file: %s, err: %v", test, err)
}
err = schema.ValidateBytes(pod)
if err != nil {
t.Errorf("unexpected error: %s, for pod %s", err, pod)
}
}
}
func TestVersionRegex(t *testing.T) {
testCases := []struct {
typeName string
match bool
}{
{
typeName: "v1.Binding",
match: true,
},
{
typeName: "v1beta1.Binding",
match: true,
},
{
typeName: "Binding",
match: false,
},
}
for _, test := range testCases {
if versionRegexp.MatchString(test.typeName) && !test.match {
t.Errorf("unexpected error: expect %s not to match the regular expression", test.typeName)
}
if !versionRegexp.MatchString(test.typeName) && test.match {
t.Errorf("unexpected error: expect %s to match the regular expression", test.typeName)
}
}
}
// Tests that validation works fine when spec contains "type": "any" instead of "type": "object"
// Ref: https://github.com/kubernetes/kubernetes/issues/24309
func TestTypeAny(t *testing.T) {
data, err := readSwaggerFile()
if err != nil {
t.Errorf("failed to read swagger file: %v", err)
}
// Replace type: "any" in the spec by type: "object" and verify that the validation still passes.
newData := strings.Replace(string(data), `"type": "object"`, `"type": "any"`, -1)
schema, err := NewSwaggerSchemaFromBytes([]byte(newData), nil)
if err != nil {
t.Fatalf("Failed to load: %v", err)
}
tests := []string{
"validPod.yaml",
}
for _, test := range tests {
podBytes, err := readPod(test)
if err != nil {
t.Errorf("could not read file: %s, err: %v", test, err)
}
// Verify that pod has at least one label (labels are type "any")
var pod v1.Pod
err = yaml.Unmarshal(podBytes, &pod)
if err != nil {
t.Errorf("error in unmarshalling pod: %v", err)
}
if len(pod.Labels) == 0 {
t.Errorf("invalid test input: the pod should have at least one label")
}
err = schema.ValidateBytes(podBytes)
if err != nil {
t.Errorf("unexpected error: %s, for pod %s", err, string(podBytes))
}
}
}
func TestValidateDuplicateLabelsFailCases(t *testing.T) {
strs := []string{
`{

View File

@ -90,10 +90,6 @@
"ImportPath": "github.com/emicklei/go-restful",
"Rev": "ff4f55a206334ef123e4f79bbf348980da81ca46"
},
{
"ImportPath": "github.com/emicklei/go-restful-swagger12",
"Rev": "dcef7f55730566d41eae5db10e7d6981829720f6"
},
{
"ImportPath": "github.com/emicklei/go-restful/log",
"Rev": "ff4f55a206334ef123e4f79bbf348980da81ca46"

View File

@ -15,11 +15,9 @@ go_library(
"unstructured.go",
],
deps = [
"//vendor/github.com/emicklei/go-restful-swagger12:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/golang/protobuf/proto:go_default_library",
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
@ -41,7 +39,6 @@ go_test(
"restmapper_test.go",
],
deps = [
"//vendor/github.com/emicklei/go-restful-swagger12:go_default_library",
"//vendor/github.com/gogo/protobuf/proto:go_default_library",
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",

View File

@ -20,10 +20,8 @@ go_library(
name = "go_default_library",
srcs = ["memcache.go"],
deps = [
"//vendor/github.com/emicklei/go-restful-swagger12:go_default_library",
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
"//vendor/k8s.io/client-go/discovery:go_default_library",

View File

@ -21,11 +21,9 @@ import (
"fmt"
"sync"
"github.com/emicklei/go-restful-swagger12"
"github.com/googleapis/gnostic/OpenAPIv2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/discovery"
@ -116,10 +114,6 @@ func (d *memCacheClient) ServerVersion() (*version.Info, error) {
return d.delegate.ServerVersion()
}
func (d *memCacheClient) SwaggerSchema(version schema.GroupVersion) (*swagger.ApiDeclaration, error) {
return d.delegate.SwaggerSchema(version)
}
func (d *memCacheClient) OpenAPISchema() (*openapi_v2.Document, error) {
return d.delegate.OpenAPISchema()
}

View File

@ -23,11 +23,9 @@ import (
"sort"
"strings"
"github.com/emicklei/go-restful-swagger12"
"github.com/golang/protobuf/proto"
"github.com/googleapis/gnostic/OpenAPIv2"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@ -48,7 +46,6 @@ type DiscoveryInterface interface {
ServerGroupsInterface
ServerResourcesInterface
ServerVersionInterface
SwaggerSchemaInterface
OpenAPISchemaInterface
}
@ -92,12 +89,6 @@ type ServerVersionInterface interface {
ServerVersion() (*version.Info, error)
}
// SwaggerSchemaInterface has a method to retrieve the swagger schema.
type SwaggerSchemaInterface interface {
// SwaggerSchema retrieves and parses the swagger API schema the server supports.
SwaggerSchema(version schema.GroupVersion) (*swagger.ApiDeclaration, error)
}
// OpenAPISchemaInterface has a method to retrieve the open API schema.
type OpenAPISchemaInterface interface {
// OpenAPISchema retrieves and parses the swagger API schema the server supports.
@ -336,41 +327,6 @@ func (d *DiscoveryClient) ServerVersion() (*version.Info, error) {
return &info, nil
}
// SwaggerSchema retrieves and parses the swagger API schema the server supports.
// TODO: Replace usages with Open API. Tracked in https://github.com/kubernetes/kubernetes/issues/44589
func (d *DiscoveryClient) SwaggerSchema(version schema.GroupVersion) (*swagger.ApiDeclaration, error) {
if version.Empty() {
return nil, fmt.Errorf("groupVersion cannot be empty")
}
groupList, err := d.ServerGroups()
if err != nil {
return nil, err
}
groupVersions := metav1.ExtractGroupVersions(groupList)
// This check also takes care the case that kubectl is newer than the running endpoint
if stringDoesntExistIn(version.String(), groupVersions) {
return nil, fmt.Errorf("API version: %v is not supported by the server. Use one of: %v", version, groupVersions)
}
var path string
if len(d.LegacyPrefix) > 0 && version == v1.SchemeGroupVersion {
path = "/swaggerapi" + d.LegacyPrefix + "/" + version.Version
} else {
path = "/swaggerapi/apis/" + version.Group + "/" + version.Version
}
body, err := d.restClient.Get().AbsPath(path).Do().Raw()
if err != nil {
return nil, err
}
var schema swagger.ApiDeclaration
err = json.Unmarshal(body, &schema)
if err != nil {
return nil, fmt.Errorf("got '%s': %v", string(body), err)
}
return &schema, nil
}
// OpenAPISchema fetches the open api schema using a rest client and parses the proto.
func (d *DiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
data, err := d.restClient.Get().AbsPath("/swagger-2.0.0.pb-v1").Do().Raw()

View File

@ -25,11 +25,9 @@ import (
"reflect"
"testing"
"github.com/emicklei/go-restful-swagger12"
"github.com/gogo/protobuf/proto"
"github.com/googleapis/gnostic/OpenAPIv2"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
@ -267,68 +265,6 @@ func TestGetServerResources(t *testing.T) {
}
}
func swaggerSchemaFakeServer() (*httptest.Server, error) {
request := 1
var sErr error
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
var resp interface{}
if request == 1 {
resp = metav1.APIVersions{Versions: []string{"v1", "v2", "v3"}}
request++
} else {
resp = swagger.ApiDeclaration{}
}
output, err := json.Marshal(resp)
if err != nil {
sErr = err
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(output)
}))
return server, sErr
}
func TestGetSwaggerSchema(t *testing.T) {
expect := swagger.ApiDeclaration{}
server, err := swaggerSchemaFakeServer()
if err != nil {
t.Errorf("unexpected encoding error: %v", err)
}
defer server.Close()
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
got, err := client.SwaggerSchema(v1.SchemeGroupVersion)
if err != nil {
t.Fatalf("unexpected encoding error: %v", err)
}
if e, a := expect, *got; !reflect.DeepEqual(e, a) {
t.Errorf("expected %v, got %v", e, a)
}
}
func TestGetSwaggerSchemaFail(t *testing.T) {
expErr := "API version: api.group/v4 is not supported by the server. Use one of: [v1 v2 v3]"
server, err := swaggerSchemaFakeServer()
if err != nil {
t.Errorf("unexpected encoding error: %v", err)
}
defer server.Close()
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
got, err := client.SwaggerSchema(schema.GroupVersion{Group: "api.group", Version: "v4"})
if got != nil {
t.Fatalf("unexpected response: %v", got)
}
if err.Error() != expErr {
t.Errorf("expected an error, got %v", err)
}
}
var returnedOpenAPI = openapi_v2.Document{
Definitions: &openapi_v2.Definitions{
AdditionalProperties: []*openapi_v2.NamedSchema{

View File

@ -10,9 +10,7 @@ go_library(
name = "go_default_library",
srcs = ["discovery.go"],
deps = [
"//vendor/github.com/emicklei/go-restful-swagger12:go_default_library",
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library",

View File

@ -19,10 +19,8 @@ package fake
import (
"fmt"
"github.com/emicklei/go-restful-swagger12"
"github.com/googleapis/gnostic/OpenAPIv2"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/version"
@ -87,19 +85,6 @@ func (c *FakeDiscovery) ServerVersion() (*version.Info, error) {
return &versionInfo, nil
}
func (c *FakeDiscovery) SwaggerSchema(version schema.GroupVersion) (*swagger.ApiDeclaration, error) {
action := testing.ActionImpl{}
action.Verb = "get"
if version == v1.SchemeGroupVersion {
action.Resource = schema.GroupVersionResource{Resource: "/swaggerapi/api/" + version.Version}
} else {
action.Resource = schema.GroupVersionResource{Resource: "/swaggerapi/apis/" + version.Group + "/" + version.Version}
}
c.Invokes(action, nil)
return &swagger.ApiDeclaration{}, nil
}
func (c *FakeDiscovery) OpenAPISchema() (*openapi_v2.Document, error) {
return &openapi_v2.Document{}, nil
}

View File

@ -28,7 +28,6 @@ import (
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
"github.com/emicklei/go-restful-swagger12"
"github.com/googleapis/gnostic/OpenAPIv2"
"github.com/stretchr/testify/assert"
)
@ -380,10 +379,6 @@ func (c *fakeCachedDiscoveryInterface) ServerVersion() (*version.Info, error) {
return &version.Info{}, nil
}
func (c *fakeCachedDiscoveryInterface) SwaggerSchema(version schema.GroupVersion) (*swagger.ApiDeclaration, error) {
return &swagger.ApiDeclaration{}, nil
}
func (c *fakeCachedDiscoveryInterface) OpenAPISchema() (*openapi_v2.Document, error) {
return &openapi_v2.Document{}, nil
}

View File

@ -22,10 +22,6 @@
"ImportPath": "github.com/emicklei/go-restful",
"Rev": "ff4f55a206334ef123e4f79bbf348980da81ca46"
},
{
"ImportPath": "github.com/emicklei/go-restful-swagger12",
"Rev": "dcef7f55730566d41eae5db10e7d6981829720f6"
},
{
"ImportPath": "github.com/emicklei/go-restful/log",
"Rev": "ff4f55a206334ef123e4f79bbf348980da81ca46"