Commit 9bcd83a8 authored by Darren Shepherd's avatar Darren Shepherd

Remove openapi/swagger

parent abe77762
...@@ -48,8 +48,6 @@ type GenericControllerManagerOptions struct { ...@@ -48,8 +48,6 @@ type GenericControllerManagerOptions struct {
SecureServing *apiserveroptions.SecureServingOptions SecureServing *apiserveroptions.SecureServingOptions
// TODO: remove insecure serving mode // TODO: remove insecure serving mode
InsecureServing *InsecureServingOptions InsecureServing *InsecureServingOptions
Authentication *apiserveroptions.DelegatingAuthenticationOptions
Authorization *apiserveroptions.DelegatingAuthorizationOptions
Master string Master string
Kubeconfig string Kubeconfig string
...@@ -77,8 +75,6 @@ func NewGenericControllerManagerOptions(componentConfig componentconfig.KubeCont ...@@ -77,8 +75,6 @@ func NewGenericControllerManagerOptions(componentConfig componentconfig.KubeCont
BindPort: int(componentConfig.Port), BindPort: int(componentConfig.Port),
BindNetwork: "tcp", BindNetwork: "tcp",
}, },
Authentication: nil, // TODO: enable with apiserveroptions.NewDelegatingAuthenticationOptions()
Authorization: nil, // TODO: enable with apiserveroptions.NewDelegatingAuthorizationOptions()
} }
// disable secure serving for now // disable secure serving for now
...@@ -179,8 +175,6 @@ func (o *GenericControllerManagerOptions) AddFlags(fs *pflag.FlagSet) { ...@@ -179,8 +175,6 @@ func (o *GenericControllerManagerOptions) AddFlags(fs *pflag.FlagSet) {
o.SecureServing.AddFlags(fs) o.SecureServing.AddFlags(fs)
o.InsecureServing.AddFlags(fs) o.InsecureServing.AddFlags(fs)
o.InsecureServing.AddDeprecatedFlags(fs) o.InsecureServing.AddDeprecatedFlags(fs)
o.Authentication.AddFlags(fs)
o.Authorization.AddFlags(fs)
} }
// ApplyTo fills up controller manager config with options and userAgent // ApplyTo fills up controller manager config with options and userAgent
...@@ -193,12 +187,6 @@ func (o *GenericControllerManagerOptions) ApplyTo(c *genericcontrollermanager.Co ...@@ -193,12 +187,6 @@ func (o *GenericControllerManagerOptions) ApplyTo(c *genericcontrollermanager.Co
if err := o.InsecureServing.ApplyTo(&c.InsecureServing, &c.ComponentConfig); err != nil { if err := o.InsecureServing.ApplyTo(&c.InsecureServing, &c.ComponentConfig); err != nil {
return err return err
} }
if err := o.Authentication.ApplyTo(&c.Authentication, c.SecureServing, nil); err != nil {
return err
}
if err := o.Authorization.ApplyTo(&c.Authorization); err != nil {
return err
}
var err error var err error
c.Kubeconfig, err = clientcmd.BuildConfigFromFlags(o.Master, o.Kubeconfig) c.Kubeconfig, err = clientcmd.BuildConfigFromFlags(o.Master, o.Kubeconfig)
...@@ -226,8 +214,6 @@ func (o *GenericControllerManagerOptions) Validate() []error { ...@@ -226,8 +214,6 @@ func (o *GenericControllerManagerOptions) Validate() []error {
errors := []error{} errors := []error{}
errors = append(errors, o.SecureServing.Validate()...) errors = append(errors, o.SecureServing.Validate()...)
errors = append(errors, o.InsecureServing.Validate()...) errors = append(errors, o.InsecureServing.Validate()...)
errors = append(errors, o.Authentication.Validate()...)
errors = append(errors, o.Authorization.Validate()...)
// TODO: validate component config, master and kubeconfig // TODO: validate component config, master and kubeconfig
......
...@@ -71,10 +71,6 @@ func createAggregatorConfig( ...@@ -71,10 +71,6 @@ func createAggregatorConfig(
aggregatorscheme.Scheme, aggregatorscheme.Scheme,
pluginInitializers...) pluginInitializers...)
// the aggregator doesn't wire these up. It just delegates them to the kubeapiserver
genericConfig.EnableSwaggerUI = false
genericConfig.SwaggerConfig = nil
// copy the etcd options so we don't mutate originals. // copy the etcd options so we don't mutate originals.
etcdOptions := *commandOptions.Etcd etcdOptions := *commandOptions.Etcd
etcdOptions.StorageConfig.Codec = aggregatorscheme.Codecs.LegacyCodec(v1beta1.SchemeGroupVersion, v1.SchemeGroupVersion) etcdOptions.StorageConfig.Codec = aggregatorscheme.Codecs.LegacyCodec(v1beta1.SchemeGroupVersion, v1.SchemeGroupVersion)
......
...@@ -76,8 +76,7 @@ func BuildAuthn(client authenticationclient.TokenReviewInterface, authn kubeletc ...@@ -76,8 +76,7 @@ func BuildAuthn(client authenticationclient.TokenReviewInterface, authn kubeletc
authenticatorConfig.TokenAccessReviewClient = client authenticatorConfig.TokenAccessReviewClient = client
} }
authenticator, _, err := authenticatorConfig.New() return authenticatorConfig.New()
return authenticator, err
} }
// BuildAuthz creates an authorizer compatible with the kubelet's needs // BuildAuthz creates an authorizer compatible with the kubelet's needs
...@@ -86,17 +85,6 @@ func BuildAuthz(client authorizationclient.SubjectAccessReviewInterface, authz k ...@@ -86,17 +85,6 @@ func BuildAuthz(client authorizationclient.SubjectAccessReviewInterface, authz k
case kubeletconfig.KubeletAuthorizationModeAlwaysAllow: case kubeletconfig.KubeletAuthorizationModeAlwaysAllow:
return authorizerfactory.NewAlwaysAllowAuthorizer(), nil return authorizerfactory.NewAlwaysAllowAuthorizer(), nil
case kubeletconfig.KubeletAuthorizationModeWebhook:
if client == nil {
return nil, errors.New("no client provided, cannot use webhook authorization")
}
authorizerConfig := authorizerfactory.DelegatingAuthorizerConfig{
SubjectAccessReviewClient: client,
AllowCacheTTL: authz.Webhook.CacheAuthorizedTTL.Duration,
DenyCacheTTL: authz.Webhook.CacheUnauthorizedTTL.Duration,
}
return authorizerConfig.New()
case "": case "":
return nil, fmt.Errorf("No authorization mode specified") return nil, fmt.Errorf("No authorization mode specified")
......
# doc.go is managed by kazel, so gazelle should ignore it.
# gazelle:exclude doc.go
package(default_visibility = ["//visibility:public"])
load("//pkg/generated/openapi:def.bzl", "openapi_library")
load("//build:openapi.bzl", "openapi_go_prefix", "openapi_vendor_prefix")
openapi_library(
name = "go_default_library",
srcs = ["doc.go"],
go_prefix = openapi_go_prefix,
openapi_targets = [
"pkg/apis/abac/v0",
"pkg/apis/abac/v1beta1",
"pkg/apis/componentconfig/v1alpha1",
"pkg/kubelet/apis/kubeletconfig/v1beta1",
"pkg/proxy/apis/kubeproxyconfig/v1alpha1",
"pkg/version",
],
tags = ["automanaged"],
vendor_prefix = openapi_vendor_prefix,
vendor_targets = [
"k8s.io/api/admission/v1beta1",
"k8s.io/api/admissionregistration/v1alpha1",
"k8s.io/api/admissionregistration/v1beta1",
"k8s.io/api/apps/v1",
"k8s.io/api/apps/v1beta1",
"k8s.io/api/apps/v1beta2",
"k8s.io/api/authentication/v1",
"k8s.io/api/authentication/v1beta1",
"k8s.io/api/authorization/v1",
"k8s.io/api/authorization/v1beta1",
"k8s.io/api/autoscaling/v1",
"k8s.io/api/autoscaling/v2beta1",
"k8s.io/api/batch/v1",
"k8s.io/api/batch/v1beta1",
"k8s.io/api/batch/v2alpha1",
"k8s.io/api/certificates/v1beta1",
"k8s.io/api/core/v1",
"k8s.io/api/events/v1beta1",
"k8s.io/api/extensions/v1beta1",
"k8s.io/api/imagepolicy/v1alpha1",
"k8s.io/api/networking/v1",
"k8s.io/api/policy/v1beta1",
"k8s.io/api/rbac/v1",
"k8s.io/api/rbac/v1alpha1",
"k8s.io/api/rbac/v1beta1",
"k8s.io/api/scheduling/v1alpha1",
"k8s.io/api/settings/v1alpha1",
"k8s.io/api/storage/v1",
"k8s.io/api/storage/v1alpha1",
"k8s.io/api/storage/v1beta1",
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1",
"k8s.io/apimachinery/pkg/api/resource",
"k8s.io/apimachinery/pkg/apis/meta/v1",
"k8s.io/apimachinery/pkg/apis/meta/v1beta1",
"k8s.io/apimachinery/pkg/apis/testapigroup/v1",
"k8s.io/apimachinery/pkg/runtime",
"k8s.io/apimachinery/pkg/util/intstr",
"k8s.io/apimachinery/pkg/version",
"k8s.io/apiserver/pkg/apis/audit/v1alpha1",
"k8s.io/apiserver/pkg/apis/audit/v1beta1",
"k8s.io/apiserver/pkg/apis/example/v1",
"k8s.io/apiserver/pkg/apis/example2/v1",
"k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1",
"k8s.io/client-go/pkg/version",
"k8s.io/kube-aggregator/pkg/apis/apiregistration/v1",
"k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1",
"k8s.io/metrics/pkg/apis/custom_metrics/v1beta1",
"k8s.io/metrics/pkg/apis/external_metrics/v1beta1",
"k8s.io/metrics/pkg/apis/metrics/v1alpha1",
"k8s.io/metrics/pkg/apis/metrics/v1beta1",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
# 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.
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("@io_kubernetes_build//defs:go.bzl", "go_genrule")
def openapi_library(name, tags, srcs, go_prefix, vendor_prefix="", openapi_targets=[], vendor_targets=[]):
deps = [
"//vendor/github.com/go-openapi/spec:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/common:go_default_library",
] + ["//%s:go_default_library" % target for target in openapi_targets] + ["//vendor/%s:go_default_library" % target for target in vendor_targets]
go_library(
name=name,
tags=tags,
srcs=srcs + [":zz_generated.openapi"],
deps=deps,
)
go_genrule(
name = "zz_generated.openapi",
srcs = srcs + ["//" + vendor_prefix + "hack/boilerplate:boilerplate.go.txt"],
outs = ["zz_generated.openapi.go"],
cmd = " ".join([
"$(location //vendor/k8s.io/code-generator/cmd/openapi-gen)",
"--v 1",
"--logtostderr",
"--go-header-file $(location //" + vendor_prefix + "hack/boilerplate:boilerplate.go.txt)",
"--output-file-base zz_generated.openapi",
"--output-package " + go_prefix + vendor_prefix + "pkg/generated/openapi",
"--input-dirs " + ",".join([go_prefix + target for target in openapi_targets] + [go_prefix + "vendor/" + target for target in vendor_targets]),
"&& cp " + vendor_prefix + "pkg/generated/openapi/zz_generated.openapi.go $(location :zz_generated.openapi.go)",
]),
go_deps = deps,
tools = ["//vendor/k8s.io/code-generator/cmd/openapi-gen"],
)
/*
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.
*/
// openapi generated definitions.
package openapi
...@@ -19,8 +19,6 @@ package authenticator ...@@ -19,8 +19,6 @@ package authenticator
import ( import (
"time" "time"
"github.com/go-openapi/spec"
"k8s.io/apiserver/pkg/authentication/authenticator" "k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/authenticatorfactory" "k8s.io/apiserver/pkg/authentication/authenticatorfactory"
"k8s.io/apiserver/pkg/authentication/group" "k8s.io/apiserver/pkg/authentication/group"
...@@ -74,11 +72,9 @@ type AuthenticatorConfig struct { ...@@ -74,11 +72,9 @@ type AuthenticatorConfig struct {
// New returns an authenticator.Request or an error that supports the standard // New returns an authenticator.Request or an error that supports the standard
// Kubernetes authentication mechanisms. // Kubernetes authentication mechanisms.
func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDefinitions, error) { func (config AuthenticatorConfig) New() (authenticator.Request, error) {
var authenticators []authenticator.Request var authenticators []authenticator.Request
var tokenAuthenticators []authenticator.Token var tokenAuthenticators []authenticator.Token
securityDefinitions := spec.SecurityDefinitions{}
hasBasicAuth := false
// front-proxy, BasicAuth methods, local first, then remote // front-proxy, BasicAuth methods, local first, then remote
// Add the front proxy authenticator if requested // Add the front proxy authenticator if requested
...@@ -91,7 +87,7 @@ func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDe ...@@ -91,7 +87,7 @@ func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDe
config.RequestHeaderConfig.ExtraHeaderPrefixes, config.RequestHeaderConfig.ExtraHeaderPrefixes,
) )
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
authenticators = append(authenticators, requestHeaderAuthenticator) authenticators = append(authenticators, requestHeaderAuthenticator)
} }
...@@ -99,17 +95,16 @@ func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDe ...@@ -99,17 +95,16 @@ func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDe
if len(config.BasicAuthFile) > 0 { if len(config.BasicAuthFile) > 0 {
basicAuth, err := newAuthenticatorFromBasicAuthFile(config.BasicAuthFile) basicAuth, err := newAuthenticatorFromBasicAuthFile(config.BasicAuthFile)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
authenticators = append(authenticators, basicAuth) authenticators = append(authenticators, basicAuth)
hasBasicAuth = true
} }
// X509 methods // X509 methods
if len(config.ClientCAFile) > 0 { if len(config.ClientCAFile) > 0 {
certAuth, err := newAuthenticatorFromClientCAFile(config.ClientCAFile) certAuth, err := newAuthenticatorFromClientCAFile(config.ClientCAFile)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
authenticators = append(authenticators, certAuth) authenticators = append(authenticators, certAuth)
} }
...@@ -118,14 +113,14 @@ func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDe ...@@ -118,14 +113,14 @@ func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDe
if len(config.TokenAuthFile) > 0 { if len(config.TokenAuthFile) > 0 {
tokenAuth, err := newAuthenticatorFromTokenFile(config.TokenAuthFile) tokenAuth, err := newAuthenticatorFromTokenFile(config.TokenAuthFile)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
tokenAuthenticators = append(tokenAuthenticators, tokenAuth) tokenAuthenticators = append(tokenAuthenticators, tokenAuth)
} }
if len(config.ServiceAccountKeyFiles) > 0 { if len(config.ServiceAccountKeyFiles) > 0 {
serviceAccountAuth, err := newLegacyServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.ServiceAccountTokenGetter) serviceAccountAuth, err := newLegacyServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.ServiceAccountTokenGetter)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth) tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
} }
...@@ -136,15 +131,6 @@ func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDe ...@@ -136,15 +131,6 @@ func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDe
} }
} }
if hasBasicAuth {
securityDefinitions["HTTPBasic"] = &spec.SecurityScheme{
SecuritySchemeProps: spec.SecuritySchemeProps{
Type: "basic",
Description: "HTTP Basic authentication",
},
}
}
if len(tokenAuthenticators) > 0 { if len(tokenAuthenticators) > 0 {
// Union the token authenticators // Union the token authenticators
tokenAuth := tokenunion.New(tokenAuthenticators...) tokenAuth := tokenunion.New(tokenAuthenticators...)
...@@ -153,25 +139,17 @@ func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDe ...@@ -153,25 +139,17 @@ func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDe
tokenAuth = tokencache.New(tokenAuth, config.TokenSuccessCacheTTL, config.TokenFailureCacheTTL) tokenAuth = tokencache.New(tokenAuth, config.TokenSuccessCacheTTL, config.TokenFailureCacheTTL)
} }
authenticators = append(authenticators, bearertoken.New(tokenAuth), websocket.NewProtocolAuthenticator(tokenAuth)) authenticators = append(authenticators, bearertoken.New(tokenAuth), websocket.NewProtocolAuthenticator(tokenAuth))
securityDefinitions["BearerToken"] = &spec.SecurityScheme{
SecuritySchemeProps: spec.SecuritySchemeProps{
Type: "apiKey",
Name: "authorization",
In: "header",
Description: "Bearer Token authentication",
},
}
} }
if len(authenticators) == 0 { if len(authenticators) == 0 {
if config.Anonymous { if config.Anonymous {
return anonymous.NewAuthenticator(), &securityDefinitions, nil return anonymous.NewAuthenticator(), nil
} }
} }
switch len(authenticators) { switch len(authenticators) {
case 0: case 0:
return nil, &securityDefinitions, nil return nil, nil
} }
authenticator := union.New(authenticators...) authenticator := union.New(authenticators...)
...@@ -184,7 +162,7 @@ func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDe ...@@ -184,7 +162,7 @@ func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDe
authenticator = union.NewFailOnError(authenticator, anonymous.NewAuthenticator()) authenticator = union.NewFailOnError(authenticator, anonymous.NewAuthenticator())
} }
return authenticator, &securityDefinitions, nil return authenticator, nil
} }
// IsValidServiceAccountKeyFile returns true if a valid public RSA key can be read from the given file // IsValidServiceAccountKeyFile returns true if a valid public RSA key can be read from the given file
......
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"factory.go",
"item.go",
"list_element.go",
"map_element.go",
"openapi.go",
"primitive_element.go",
"type_element.go",
"util.go",
"visitor.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/apply/parse",
visibility = ["//visibility:public"],
deps = [
"//pkg/kubectl/apply:go_default_library",
"//pkg/kubectl/cmd/util/openapi:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
],
)
go_test(
name = "go_default_xtest",
srcs = ["suite_test.go"],
data = [
"//api/openapi-spec:swagger-spec",
],
deps = [
"//vendor/github.com/onsi/ginkgo:go_default_library",
"//vendor/github.com/onsi/ginkgo/config:go_default_library",
"//vendor/github.com/onsi/ginkgo/types:go_default_library",
"//vendor/github.com/onsi/gomega:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
/*
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 parse
import (
"fmt"
"reflect"
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kubernetes/pkg/kubectl/apply"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
)
// Factory creates an Element by combining object values from recorded, local and remote sources with
// the metadata from an openapi schema.
type Factory struct {
// Resources contains the openapi field metadata for the object models
Resources openapi.Resources
}
// CreateElement returns an Element by collating the recorded, local and remote field values
func (b *Factory) CreateElement(recorded, local, remote map[string]interface{}) (apply.Element, error) {
// Create an Item from the 3 values. Use empty name for field
visitor := &ElementBuildingVisitor{b.Resources}
gvk, err := getCommonGroupVersionKind(recorded, local, remote)
if err != nil {
return nil, err
}
// Get the openapi object metadata
s := visitor.resources.LookupResource(gvk)
oapiKind, err := getKind(s)
if err != nil {
return nil, err
}
data := apply.NewRawElementData(recorded, local, remote)
fieldName := ""
item, err := visitor.getItem(oapiKind, fieldName, data)
if err != nil {
return nil, err
}
// Collate each field of the item into a combined Element
return item.CreateElement(visitor)
}
// getItem returns the appropriate Item based on the underlying type of the arguments
func (v *ElementBuildingVisitor) getItem(s proto.Schema, name string, data apply.RawElementData) (Item, error) {
kind, err := getType(data.GetRecorded(), data.GetLocal(), data.GetRemote())
if err != nil {
return nil, err
}
if kind == nil {
// All of the items values are nil.
return &emptyItem{Name: name}, nil
}
// Create an item matching the type
switch kind.Kind() {
case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint,
reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64,
reflect.String:
p, err := getPrimitive(s)
if err != nil {
return nil, fmt.Errorf("expected openapi Primitive, was %T for %v (%v)", s, kind, err)
}
return &primitiveItem{name, p, data}, nil
case reflect.Array, reflect.Slice:
a, err := getArray(s)
if err != nil {
return nil, fmt.Errorf("expected openapi Array, was %T for %v (%v)", s, kind, err)
}
return &listItem{
Name: name,
Array: a,
ListElementData: apply.ListElementData{
RawElementData: data,
},
}, nil
case reflect.Map:
if k, err := getKind(s); err == nil {
return &typeItem{
Name: name,
Type: k,
MapElementData: apply.MapElementData{
RawElementData: data,
},
}, nil
}
// If it looks like a map, and no openapi type is found, default to mapItem
m, err := getMap(s)
if err != nil {
return nil, fmt.Errorf("expected openapi Kind or Map, was %T for %v (%v)", s, kind, err)
}
return &mapItem{
Name: name,
Map: m,
MapElementData: apply.MapElementData{
RawElementData: data,
},
}, nil
}
return nil, fmt.Errorf("unsupported type type %v", kind)
}
/*
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 parse
import (
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kubernetes/pkg/kubectl/apply"
)
// Item wraps values from 3 sources (recorded, local, remote).
// The values are not collated
type Item interface {
// CreateElement merges the values in the item into a combined Element
CreateElement(ItemVisitor) (apply.Element, error)
}
// primitiveItem contains a recorded, local, and remote value
type primitiveItem struct {
Name string
Primitive *proto.Primitive
apply.RawElementData
}
func (i *primitiveItem) CreateElement(v ItemVisitor) (apply.Element, error) {
return v.CreatePrimitiveElement(i)
}
func (i *primitiveItem) GetMeta() proto.Schema {
// https://golang.org/doc/faq#nil_error
if i.Primitive != nil {
return i.Primitive
}
return nil
}
// listItem contains a recorded, local, and remote list
type listItem struct {
Name string
Array *proto.Array
apply.ListElementData
}
func (i *listItem) CreateElement(v ItemVisitor) (apply.Element, error) {
return v.CreateListElement(i)
}
func (i *listItem) GetMeta() proto.Schema {
// https://golang.org/doc/faq#nil_error
if i.Array != nil {
return i.Array
}
return nil
}
// mapItem contains a recorded, local, and remote map
type mapItem struct {
Name string
Map *proto.Map
apply.MapElementData
}
func (i *mapItem) CreateElement(v ItemVisitor) (apply.Element, error) {
return v.CreateMapElement(i)
}
func (i *mapItem) GetMeta() proto.Schema {
// https://golang.org/doc/faq#nil_error
if i.Map != nil {
return i.Map
}
return nil
}
// mapItem contains a recorded, local, and remote map
type typeItem struct {
Name string
Type *proto.Kind
apply.MapElementData
}
func (i *typeItem) GetMeta() proto.Schema {
// https://golang.org/doc/faq#nil_error
if i.Type != nil {
return i.Type
}
return nil
}
func (i *typeItem) CreateElement(v ItemVisitor) (apply.Element, error) {
return v.CreateTypeElement(i)
}
// emptyItem contains no values
type emptyItem struct {
Name string
}
func (i *emptyItem) CreateElement(v ItemVisitor) (apply.Element, error) {
e := &apply.EmptyElement{}
e.Name = i.Name
return e, nil
}
/*
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 parse
import (
"fmt"
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kubernetes/pkg/kubectl/apply"
)
// Contains the heavy lifting for finding tuples of matching elements in lists based on the merge key
// and then uses the canonical order derived from the orders in the recorded, local and remote lists.
// replaceListElement builds a ListElement for a listItem.
// Uses the "merge" strategy to identify "same" elements across lists by a "merge key"
func (v ElementBuildingVisitor) mergeListElement(meta apply.FieldMetaImpl, item *listItem) (*apply.ListElement, error) {
subtype := getSchemaType(item.Array.SubType)
switch subtype {
case "primitive":
return v.doPrimitiveList(meta, item)
case "map", "kind", "reference":
return v.doMapList(meta, item)
default:
return nil, fmt.Errorf("Cannot merge lists with subtype %s", subtype)
}
}
// doPrimitiveList merges 3 lists of primitives together
// tries to maintain ordering
func (v ElementBuildingVisitor) doPrimitiveList(meta apply.FieldMetaImpl, item *listItem) (*apply.ListElement, error) {
result := &apply.ListElement{
FieldMetaImpl: apply.FieldMetaImpl{
MergeType: apply.MergeStrategy,
Name: item.Name,
},
ListElementData: item.ListElementData,
Values: []apply.Element{},
}
// Use locally defined order, then add remote, then add recorded.
orderedKeys := &apply.CombinedPrimitiveSlice{}
// Locally defined items come first and retain their order
// as defined locally
for _, l := range item.GetLocalList() {
orderedKeys.UpsertLocal(l)
}
// Mixin remote values, adding any that are not present locally
for _, l := range item.GetRemoteList() {
orderedKeys.UpsertRemote(l)
}
// Mixin recorded values, adding any that are not present locally
// or remotely
for _, l := range item.GetRecordedList() {
orderedKeys.UpsertRecorded(l)
}
for i, l := range orderedKeys.Items {
var s proto.Schema
if item.Array != nil && item.Array.SubType != nil {
s = item.Array.SubType
}
subitem, err := v.getItem(s, fmt.Sprintf("%d", i), l.RawElementData)
if err != nil {
return nil, err
}
// Convert the Item to an Element
newelem, err := subitem.CreateElement(v)
if err != nil {
return nil, err
}
// Append the element to the list
result.Values = append(result.Values, newelem)
}
return result, nil
}
// doMapList merges 3 lists of maps together by collating their values.
// tries to retain ordering
func (v ElementBuildingVisitor) doMapList(meta apply.FieldMetaImpl, item *listItem) (*apply.ListElement, error) {
key := meta.GetFieldMergeKeys()
result := &apply.ListElement{
FieldMetaImpl: apply.FieldMetaImpl{
MergeType: apply.MergeStrategy,
MergeKeys: key,
Name: item.Name,
},
ListElementData: item.ListElementData,
Values: []apply.Element{},
}
// Use locally defined order, then add remote, then add recorded.
orderedKeys := &apply.CombinedMapSlice{}
// Locally defined items come first and retain their order
// as defined locally
for _, l := range item.GetLocalList() {
orderedKeys.UpsertLocal(key, l)
}
// Mixin remote values, adding any that are not present locally
for _, l := range item.GetRemoteList() {
orderedKeys.UpsertRemote(key, l)
}
// Mixin recorded values, adding any that are not present locally
// or remotely
for _, l := range item.GetRecordedList() {
orderedKeys.UpsertRecorded(key, l)
}
for i, l := range orderedKeys.Items {
var s proto.Schema
if item.Array != nil && item.Array.SubType != nil {
s = item.Array.SubType
}
subitem, err := v.getItem(s, fmt.Sprintf("%d", i), l.RawElementData)
if err != nil {
return nil, err
}
// Build the element fully
newelem, err := subitem.CreateElement(v)
if err != nil {
return nil, err
}
// Append the element to the list
result.Values = append(result.Values, newelem)
}
return result, nil
}
// replaceListElement builds a new ListElement from a listItem
// Uses the "replace" strategy and identify "same" elements across lists by their index
func (v ElementBuildingVisitor) replaceListElement(meta apply.FieldMetaImpl, item *listItem) (*apply.ListElement, error) {
meta.Name = item.Name
result := &apply.ListElement{
FieldMetaImpl: meta,
ListElementData: item.ListElementData,
Values: []apply.Element{},
}
// Use the max length to iterate over the slices
for i := 0; i < max(len(item.GetRecordedList()), len(item.GetLocalList()), len(item.GetRemoteList())); i++ {
// Lookup the item from each list
data := apply.RawElementData{}
if recorded, recordedSet := boundsSafeLookup(i, item.GetRecordedList()); recordedSet {
data.SetRecorded(recorded)
}
if local, localSet := boundsSafeLookup(i, item.GetLocalList()); localSet {
data.SetLocal(local)
}
if remote, remoteSet := boundsSafeLookup(i, item.GetRemoteList()); remoteSet {
data.SetRemote(remote)
}
// Create the Item
var s proto.Schema
if item.Array != nil && item.Array.SubType != nil {
s = item.Array.SubType
}
subitem, err := v.getItem(s, fmt.Sprintf("%d", i), data)
if err != nil {
return nil, err
}
// Build the element
newelem, err := subitem.CreateElement(v)
if err != nil {
return nil, err
}
// Append the element to the list
result.Values = append(result.Values, newelem)
}
return result, nil
}
/*
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 parse
import (
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kubernetes/pkg/kubectl/apply"
)
// mapElement builds a new mapElement from a mapItem
func (v ElementBuildingVisitor) mapElement(meta apply.FieldMetaImpl, item *mapItem) (*apply.MapElement, error) {
// Function to return schema type of the map values
var fn schemaFn = func(string) proto.Schema {
// All map values share the same schema
if item.Map != nil && item.Map.SubType != nil {
return item.Map.SubType
}
return nil
}
// Collect same fields from multiple maps into a map of elements
values, err := v.createMapValues(fn, meta, item.MapElementData)
if err != nil {
return nil, err
}
// Return the result
return &apply.MapElement{
FieldMetaImpl: meta,
MapElementData: item.MapElementData,
Values: values,
}, nil
}
// schemaFn returns the schema for a field or map value based on its name or key
type schemaFn func(key string) proto.Schema
// createMapValues combines the recorded, local and remote values from
// data into a map of elements.
func (v ElementBuildingVisitor) createMapValues(
schemaFn schemaFn,
meta apply.FieldMetaImpl,
data apply.MapElementData) (map[string]apply.Element, error) {
// Collate each key in the map
values := map[string]apply.Element{}
for _, key := range keysUnion(data.GetRecordedMap(), data.GetLocalMap(), data.GetRemoteMap()) {
combined := apply.RawElementData{}
if recorded, recordedSet := nilSafeLookup(key, data.GetRecordedMap()); recordedSet {
combined.SetRecorded(recorded)
}
if local, localSet := nilSafeLookup(key, data.GetLocalMap()); localSet {
combined.SetLocal(local)
}
if remote, remoteSet := nilSafeLookup(key, data.GetRemoteMap()); remoteSet {
combined.SetRemote(remote)
}
// Create an item for the field
field, err := v.getItem(schemaFn(key), key, combined)
if err != nil {
return nil, err
}
// Build the element for this field
element, err := field.CreateElement(v)
if err != nil {
return nil, err
}
// Add the field element to the map
values[key] = element
}
return values, nil
}
/*
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 parse
import (
"fmt"
"strings"
"k8s.io/kube-openapi/pkg/util/proto"
)
// Contains functions for casting openapi interfaces to their underlying types
// getSchemaType returns the string type of the schema - e.g. array, primitive, map, kind, reference
func getSchemaType(schema proto.Schema) string {
if schema == nil {
return ""
}
visitor := &baseSchemaVisitor{}
schema.Accept(visitor)
return visitor.Kind
}
// getKind converts schema to an *proto.Kind object
func getKind(schema proto.Schema) (*proto.Kind, error) {
if schema == nil {
return nil, nil
}
visitor := &kindSchemaVisitor{}
schema.Accept(visitor)
return visitor.Result, visitor.Err
}
// getArray converts schema to an *proto.Array object
func getArray(schema proto.Schema) (*proto.Array, error) {
if schema == nil {
return nil, nil
}
visitor := &arraySchemaVisitor{}
schema.Accept(visitor)
return visitor.Result, visitor.Err
}
// getMap converts schema to an *proto.Map object
func getMap(schema proto.Schema) (*proto.Map, error) {
if schema == nil {
return nil, nil
}
visitor := &mapSchemaVisitor{}
schema.Accept(visitor)
return visitor.Result, visitor.Err
}
// getPrimitive converts schema to an *proto.Primitive object
func getPrimitive(schema proto.Schema) (*proto.Primitive, error) {
if schema == nil {
return nil, nil
}
visitor := &primitiveSchemaVisitor{}
schema.Accept(visitor)
return visitor.Result, visitor.Err
}
type baseSchemaVisitor struct {
Err error
Kind string
}
// VisitArray implements openapi
func (v *baseSchemaVisitor) VisitArray(array *proto.Array) {
v.Kind = "array"
v.Err = fmt.Errorf("Array type not expected")
}
// MergeMap implements openapi
func (v *baseSchemaVisitor) VisitMap(*proto.Map) {
v.Kind = "map"
v.Err = fmt.Errorf("Map type not expected")
}
// MergePrimitive implements openapi
func (v *baseSchemaVisitor) VisitPrimitive(*proto.Primitive) {
v.Kind = "primitive"
v.Err = fmt.Errorf("Primitive type not expected")
}
// VisitKind implements openapi
func (v *baseSchemaVisitor) VisitKind(*proto.Kind) {
v.Kind = "kind"
v.Err = fmt.Errorf("Kind type not expected")
}
// VisitReference implements openapi
func (v *baseSchemaVisitor) VisitReference(reference proto.Reference) {
v.Kind = "reference"
v.Err = fmt.Errorf("Reference type not expected")
}
type kindSchemaVisitor struct {
baseSchemaVisitor
Result *proto.Kind
}
// VisitKind implements openapi
func (v *kindSchemaVisitor) VisitKind(result *proto.Kind) {
v.Result = result
v.Kind = "kind"
}
// VisitReference implements openapi
func (v *kindSchemaVisitor) VisitReference(reference proto.Reference) {
reference.SubSchema().Accept(v)
if v.Err == nil {
v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions)
}
}
func copyExtensions(field string, from, to map[string]interface{}) error {
// Copy extensions from field to type for references
for key, val := range from {
if curr, found := to[key]; found {
// Don't allow the same extension to be defined both on the field and on the type
return fmt.Errorf("Cannot override value for extension %s on field %s from %v to %v",
key, field, curr, val)
}
to[key] = val
}
return nil
}
type mapSchemaVisitor struct {
baseSchemaVisitor
Result *proto.Map
}
// MergeMap implements openapi
func (v *mapSchemaVisitor) VisitMap(result *proto.Map) {
v.Result = result
v.Kind = "map"
}
// VisitReference implements openapi
func (v *mapSchemaVisitor) VisitReference(reference proto.Reference) {
reference.SubSchema().Accept(v)
if v.Err == nil {
v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions)
}
}
type arraySchemaVisitor struct {
baseSchemaVisitor
Result *proto.Array
}
// VisitArray implements openapi
func (v *arraySchemaVisitor) VisitArray(result *proto.Array) {
v.Result = result
v.Kind = "array"
v.Err = copySubElementPatchStrategy(result.Path.String(), result.GetExtensions(), result.SubType.GetExtensions())
}
// copyPatchStrategy copies the strategies to subelements to the subtype
// e.g. PodTemplate.Volumes is a []Volume with "x-kubernetes-patch-strategy": "merge,retainKeys"
// the "retainKeys" strategy applies to merging Volumes, and must be copied to the sub element
func copySubElementPatchStrategy(field string, from, to map[string]interface{}) error {
// Check if the parent has a patch strategy extension
if ext, found := from["x-kubernetes-patch-strategy"]; found {
strategy, ok := ext.(string)
if !ok {
return fmt.Errorf("Expected string value for x-kubernetes-patch-strategy on %s, was %T",
field, ext)
}
// Check of the parent patch strategy has a sub patch strategy, and if so copy to the sub type
if strings.Contains(strategy, ",") {
strategies := strings.Split(strategy, ",")
if len(strategies) != 2 {
// Only 1 sub strategy is supported
return fmt.Errorf(
"Expected between 0 and 2 elements for x-kubernetes-patch-merge-strategy by got %v",
strategies)
}
to["x-kubernetes-patch-strategy"] = strategies[1]
}
}
return nil
}
// MergePrimitive implements openapi
func (v *arraySchemaVisitor) VisitReference(reference proto.Reference) {
reference.SubSchema().Accept(v)
if v.Err == nil {
v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions)
}
}
type primitiveSchemaVisitor struct {
baseSchemaVisitor
Result *proto.Primitive
}
// MergePrimitive implements openapi
func (v *primitiveSchemaVisitor) VisitPrimitive(result *proto.Primitive) {
v.Result = result
v.Kind = "primitive"
}
// VisitReference implements openapi
func (v *primitiveSchemaVisitor) VisitReference(reference proto.Reference) {
reference.SubSchema().Accept(v)
if v.Err == nil {
v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions)
}
}
/*
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 parse
import "k8s.io/kubernetes/pkg/kubectl/apply"
// primitiveElement builds a new primitiveElement from a PrimitiveItem
func (v ElementBuildingVisitor) primitiveElement(item *primitiveItem) (*apply.PrimitiveElement, error) {
meta := apply.FieldMetaImpl{Name: item.Name}
return &apply.PrimitiveElement{
FieldMetaImpl: meta,
RawElementData: item.RawElementData,
}, nil
}
/*
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 parse_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") }
/*
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 parse
import (
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kubernetes/pkg/kubectl/apply"
)
// typeElement builds a new mapElement from a typeItem
func (v ElementBuildingVisitor) typeElement(meta apply.FieldMetaImpl, item *typeItem) (*apply.TypeElement, error) {
// Function to get the schema of a field from its key
var fn schemaFn = func(key string) proto.Schema {
if item.Type != nil && item.Type.Fields != nil {
return item.Type.Fields[key]
}
return nil
}
// Collect same fields from multiple maps into a map of elements
values, err := v.createMapValues(fn, meta, item.MapElementData)
if err != nil {
return nil, err
}
// Return the result
return &apply.TypeElement{
FieldMetaImpl: meta,
MapElementData: item.MapElementData,
Values: values,
}, nil
}
/*
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 parse
import (
"fmt"
"reflect"
"strings"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kubernetes/pkg/kubectl/apply"
)
// nilSafeLookup returns the value from the map if the map is non-nil
func nilSafeLookup(key string, from map[string]interface{}) (interface{}, bool) {
if from != nil {
value, found := from[key]
return value, found
}
// Not present
return nil, false
}
// boundsSafeLookup returns the value from the slice if the slice is non-nil and
// the index is in bounds.
func boundsSafeLookup(index int, from []interface{}) (interface{}, bool) {
if from != nil && len(from) > index {
return from[index], true
}
return nil, false
}
// keysUnion returns a slice containing the union of the keys present in the arguments
func keysUnion(maps ...map[string]interface{}) []string {
keys := map[string]interface{}{}
for _, m := range maps {
for k := range m {
keys[k] = nil
}
}
result := []string{}
for key := range keys {
result = append(result, key)
}
return result
}
// max returns the argument with the highest value
func max(values ...int) int {
v := 0
for _, i := range values {
if i > v {
v = i
}
}
return v
}
// getType returns the type of the arguments. If the arguments don't have matching
// types, getType returns an error. Nil types matching everything.
func getType(args ...interface{}) (reflect.Type, error) {
var last interface{}
for _, next := range args {
// Skip nil values
if next == nil {
continue
}
// Set the first non-nil value we find and continue
if last == nil {
last = next
continue
}
// Verify the types of the values match
if reflect.TypeOf(last).Kind() != reflect.TypeOf(next).Kind() {
return nil, fmt.Errorf("missmatching non-nil types for the same field: %T %T", last, next)
}
}
return reflect.TypeOf(last), nil
}
// getFieldMeta parses the metadata about the field from the openapi spec
func getFieldMeta(s proto.Schema, name string) (apply.FieldMetaImpl, error) {
m := apply.FieldMetaImpl{}
if s != nil {
ext := s.GetExtensions()
if e, found := ext["x-kubernetes-patch-strategy"]; found {
strategy, ok := e.(string)
if !ok {
return apply.FieldMetaImpl{}, fmt.Errorf("Expected string for x-kubernetes-patch-strategy by got %T", s)
}
// Take the first strategy if there are substrategies.
// Sub strategies are copied to sub types in openapi.go
strategies := strings.Split(strategy, ",")
if len(strategies) > 2 {
return apply.FieldMetaImpl{}, fmt.Errorf("Expected between 0 and 2 elements for x-kubernetes-patch-merge-strategy by got %v", strategies)
}
// For lists, choose the strategy for this type, not the subtype
m.MergeType = strategies[0]
}
if k, found := ext["x-kubernetes-patch-merge-key"]; found {
key, ok := k.(string)
if !ok {
return apply.FieldMetaImpl{}, fmt.Errorf("Expected string for x-kubernetes-patch-merge-key by got %T", k)
}
m.MergeKeys = apply.MergeKeys(strings.Split(key, ","))
}
}
m.Name = name
return m, nil
}
// getCommonGroupVersionKind verifies that the recorded, local and remote all share
// the same GroupVersionKind and returns the value
func getCommonGroupVersionKind(recorded, local, remote map[string]interface{}) (schema.GroupVersionKind, error) {
recordedGVK, err := getGroupVersionKind(recorded)
if err != nil {
return schema.GroupVersionKind{}, err
}
localGVK, err := getGroupVersionKind(local)
if err != nil {
return schema.GroupVersionKind{}, err
}
remoteGVK, err := getGroupVersionKind(remote)
if err != nil {
return schema.GroupVersionKind{}, err
}
if !reflect.DeepEqual(recordedGVK, localGVK) || !reflect.DeepEqual(localGVK, remoteGVK) {
return schema.GroupVersionKind{},
fmt.Errorf("group version kinds do not match (recorded: %v local: %v remote: %v)",
recordedGVK, localGVK, remoteGVK)
}
return recordedGVK, nil
}
// getGroupVersionKind returns the GroupVersionKind of the object
func getGroupVersionKind(config map[string]interface{}) (schema.GroupVersionKind, error) {
gvk := schema.GroupVersionKind{}
if gv, found := config["apiVersion"]; found {
casted, ok := gv.(string)
if !ok {
return gvk, fmt.Errorf("Expected string for apiVersion, found %T", gv)
}
s := strings.Split(casted, "/")
if len(s) != 1 {
gvk.Group = s[0]
}
gvk.Version = s[len(s)-1]
} else {
return gvk, fmt.Errorf("Missing apiVersion in Kind %v", config)
}
if k, found := config["kind"]; found {
casted, ok := k.(string)
if !ok {
return gvk, fmt.Errorf("Expected string for kind, found %T", k)
}
gvk.Kind = casted
} else {
return gvk, fmt.Errorf("Missing kind in Kind %v", config)
}
return gvk, nil
}
/*
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 parse
import (
"k8s.io/kubernetes/pkg/kubectl/apply"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
)
// ItemVisitor provides an interface for Items to Accept and call
// the Visit function that corresponds to its actual type.
type ItemVisitor interface {
// CreatePrimitiveElement builds an Element for a primitiveItem
CreatePrimitiveElement(*primitiveItem) (apply.Element, error)
// CreateListElement builds an Element for a listItem
CreateListElement(*listItem) (apply.Element, error)
// CreateMapElement builds an Element for a mapItem
CreateMapElement(*mapItem) (apply.Element, error)
// CreateTypeElement builds an Element for a typeItem
CreateTypeElement(*typeItem) (apply.Element, error)
}
// ElementBuildingVisitor creates an Elements from Items
// An Element combines the values from the Item with the field metadata.
type ElementBuildingVisitor struct {
resources openapi.Resources
}
// CreatePrimitiveElement creates a primitiveElement
func (v ElementBuildingVisitor) CreatePrimitiveElement(item *primitiveItem) (apply.Element, error) {
return v.primitiveElement(item)
}
// CreateListElement creates a ListElement
func (v ElementBuildingVisitor) CreateListElement(item *listItem) (apply.Element, error) {
meta, err := getFieldMeta(item.GetMeta(), item.Name)
if err != nil {
return nil, err
}
if meta.GetFieldMergeType() == apply.MergeStrategy {
return v.mergeListElement(meta, item)
}
return v.replaceListElement(meta, item)
}
// CreateMapElement creates a mapElement
func (v ElementBuildingVisitor) CreateMapElement(item *mapItem) (apply.Element, error) {
meta, err := getFieldMeta(item.GetMeta(), item.Name)
if err != nil {
return nil, err
}
return v.mapElement(meta, item)
}
// CreateTypeElement creates a typeElement
func (v ElementBuildingVisitor) CreateTypeElement(item *typeItem) (apply.Element, error) {
meta, err := getFieldMeta(item.GetMeta(), item.Name)
if err != nil {
return nil, err
}
return v.typeElement(meta, item)
}
...@@ -34,11 +34,6 @@ func NewCmdAlpha(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cobra.Com ...@@ -34,11 +34,6 @@ func NewCmdAlpha(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cobra.Com
Long: templates.LongDesc(i18n.T("These commands correspond to alpha features that are not enabled in Kubernetes clusters by default.")), Long: templates.LongDesc(i18n.T("These commands correspond to alpha features that are not enabled in Kubernetes clusters by default.")),
} }
// Alpha commands should be added here. As features graduate from alpha they should move
// from here to the CommandGroups defined by NewKubeletCommand() in cmd.go.
//cmd.AddCommand(NewCmdDebug(f, in, out, err))
cmd.AddCommand(NewCmdDiff(f, out, err))
// NewKubeletCommand() will hide the alpha command if it has no subcommands. Overriding // NewKubeletCommand() will hide the alpha command if it has no subcommands. Overriding
// the help function ensures a reasonable message if someone types the hidden command anyway. // the help function ensures a reasonable message if someone types the hidden command anyway.
if !cmd.HasSubCommands() { if !cmd.HasSubCommands() {
......
...@@ -37,13 +37,11 @@ import ( ...@@ -37,13 +37,11 @@ import (
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
oapi "k8s.io/kube-openapi/pkg/util/proto"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
...@@ -200,14 +198,6 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti ...@@ -200,14 +198,6 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti
return err return err
} }
var openapiSchema openapi.Resources
if cmdutil.GetFlagBool(cmd, "openapi-patch") {
openapiSchema, err = f.OpenAPISchema()
if err != nil {
openapiSchema = nil
}
}
cmdNamespace, enforceNamespace, err := f.DefaultNamespace() cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil { if err != nil {
return err return err
...@@ -327,7 +317,6 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti ...@@ -327,7 +317,6 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti
cascade: options.Cascade, cascade: options.Cascade,
timeout: options.Timeout, timeout: options.Timeout,
gracePeriod: options.GracePeriod, gracePeriod: options.GracePeriod,
openapiSchema: openapiSchema,
} }
patchBytes, patchedObject, err := patcher.patch(info.Object, modified, info.Source, info.Namespace, info.Name, errOut) patchBytes, patchedObject, err := patcher.patch(info.Object, modified, info.Source, info.Namespace, info.Name, errOut)
...@@ -586,8 +575,6 @@ type patcher struct { ...@@ -586,8 +575,6 @@ type patcher struct {
cascade bool cascade bool
timeout time.Duration timeout time.Duration
gracePeriod int gracePeriod int
openapiSchema openapi.Resources
} }
func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, namespace, name string, errOut io.Writer) ([]byte, runtime.Object, error) { func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, namespace, name string, errOut io.Writer) ([]byte, runtime.Object, error) {
...@@ -606,7 +593,6 @@ func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, names ...@@ -606,7 +593,6 @@ func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, names
var patchType types.PatchType var patchType types.PatchType
var patch []byte var patch []byte
var lookupPatchMeta strategicpatch.LookupPatchMeta var lookupPatchMeta strategicpatch.LookupPatchMeta
var schema oapi.Schema
createPatchErrFormat := "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor:" createPatchErrFormat := "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor:"
// Create the versioned struct from the type defined in the restmapping // Create the versioned struct from the type defined in the restmapping
...@@ -631,20 +617,6 @@ func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, names ...@@ -631,20 +617,6 @@ func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, names
// Compute a three way strategic merge patch to send to server. // Compute a three way strategic merge patch to send to server.
patchType = types.StrategicMergePatchType patchType = types.StrategicMergePatchType
// Try to use openapi first if the openapi spec is available and can successfully calculate the patch.
// Otherwise, fall back to baked-in types.
if p.openapiSchema != nil {
if schema = p.openapiSchema.LookupResource(p.mapping.GroupVersionKind); schema != nil {
lookupPatchMeta = strategicpatch.PatchMetaFromOpenAPI{Schema: schema}
if openapiPatch, err := strategicpatch.CreateThreeWayMergePatch(original, modified, current, lookupPatchMeta, p.overwrite); err != nil {
fmt.Fprintf(errOut, "warning: error calculating patch from openapi spec: %v\n", err)
} else {
patchType = types.StrategicMergePatchType
patch = openapiPatch
}
}
}
if patch == nil { if patch == nil {
lookupPatchMeta, err = strategicpatch.NewPatchMetaFromStruct(versionedObject) lookupPatchMeta, err = strategicpatch.NewPatchMetaFromStruct(versionedObject)
if err != nil { if err != nil {
......
...@@ -259,7 +259,6 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob ...@@ -259,7 +259,6 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob
Message: "Basic Commands (Intermediate):", Message: "Basic Commands (Intermediate):",
Commands: []*cobra.Command{ Commands: []*cobra.Command{
resource.NewCmdGet(f, out, err), resource.NewCmdGet(f, out, err),
NewCmdExplain(f, out, err),
NewCmdEdit(f, out, err), NewCmdEdit(f, out, err),
NewCmdDelete(f, out, err), NewCmdDelete(f, out, err),
}, },
......
/*
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 cmd
import (
"bytes"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"testing"
"k8s.io/utils/exec"
)
type FakeObject struct {
name string
local map[string]interface{}
merged map[string]interface{}
live map[string]interface{}
last map[string]interface{}
}
var _ Object = &FakeObject{}
func (f *FakeObject) Name() string {
return f.name
}
func (f *FakeObject) Local() (map[string]interface{}, error) {
return f.local, nil
}
func (f *FakeObject) Merged() (map[string]interface{}, error) {
return f.merged, nil
}
func (f *FakeObject) Live() (map[string]interface{}, error) {
return f.live, nil
}
func (f *FakeObject) Last() (map[string]interface{}, error) {
return f.last, nil
}
func TestArguments(t *testing.T) {
tests := []struct {
// Input
args []string
// Outputs
from string
to string
err string
}{
// Defaults
{
args: []string{},
from: "LOCAL",
to: "LIVE",
err: "",
},
// One valid argument
{
args: []string{"MERGED"},
from: "MERGED",
to: "LIVE",
err: "",
},
// One invalid argument
{
args: []string{"WRONG"},
from: "",
to: "",
err: `Invalid parameter "WRONG", must be either "LOCAL", "LIVE", "LAST" or "MERGED"`,
},
// Two valid arguments
{
args: []string{"MERGED", "LAST"},
from: "MERGED",
to: "LAST",
err: "",
},
// Two same arguments is fine
{
args: []string{"MERGED", "MERGED"},
from: "MERGED",
to: "MERGED",
err: "",
},
// Second argument is invalid
{
args: []string{"MERGED", "WRONG"},
from: "",
to: "",
err: `Invalid parameter "WRONG", must be either "LOCAL", "LIVE", "LAST" or "MERGED"`,
},
// Three arguments
{
args: []string{"MERGED", "LIVE", "LAST"},
from: "",
to: "",
err: `Invalid number of arguments: expected at most 2.`,
},
}
for _, test := range tests {
from, to, e := parseDiffArguments(test.args)
err := ""
if e != nil {
err = e.Error()
}
if from != test.from || to != test.to || err != test.err {
t.Errorf("parseDiffArguments(%v) = (%v, %v, %v), expected (%v, %v, %v)",
test.args,
from, to, err,
test.from, test.to, test.err,
)
}
}
}
func TestDiffProgram(t *testing.T) {
os.Setenv("KUBERNETES_EXTERNAL_DIFF", "echo")
stdout := bytes.Buffer{}
diff := DiffProgram{
Stdout: &stdout,
Stderr: &bytes.Buffer{},
Exec: exec.New(),
}
err := diff.Run("one", "two")
if err != nil {
t.Fatal(err)
}
if output := stdout.String(); output != "one two\n" {
t.Fatalf(`stdout = %q, expected "one two\n"`, output)
}
}
func TestPrinter(t *testing.T) {
printer := Printer{}
obj := map[string]interface{}{
"string": "string",
"list": []int{1, 2, 3},
"int": 12,
}
buf := bytes.Buffer{}
printer.Print(obj, &buf)
want := `int: 12
list:
- 1
- 2
- 3
string: string
`
if buf.String() != want {
t.Errorf("Print() = %q, want %q", buf.String(), want)
}
}
func TestDiffVersion(t *testing.T) {
diff, err := NewDiffVersion("LOCAL")
if err != nil {
t.Fatal(err)
}
defer diff.Dir.Delete()
obj := FakeObject{
name: "bla",
local: map[string]interface{}{"local": true},
last: map[string]interface{}{"last": true},
live: map[string]interface{}{"live": true},
merged: map[string]interface{}{"merged": true},
}
err = diff.Print(&obj, Printer{})
if err != nil {
t.Fatal(err)
}
fcontent, err := ioutil.ReadFile(path.Join(diff.Dir.Name, obj.Name()))
if err != nil {
t.Fatal(err)
}
econtent := "local: true\n"
if string(fcontent) != econtent {
t.Fatalf("File has %q, expected %q", string(fcontent), econtent)
}
}
func TestDirectory(t *testing.T) {
dir, err := CreateDirectory("prefix")
defer dir.Delete()
if err != nil {
t.Fatal(err)
}
_, err = os.Stat(dir.Name)
if err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(filepath.Base(dir.Name), "prefix") {
t.Fatalf(`Directory doesn't start with "prefix": %q`, dir.Name)
}
entries, err := ioutil.ReadDir(dir.Name)
if err != nil {
t.Fatal(err)
}
if len(entries) != 0 {
t.Fatalf("Directory should be empty, has %d elements", len(entries))
}
_, err = dir.NewFile("ONE")
if err != nil {
t.Fatal(err)
}
_, err = dir.NewFile("TWO")
if err != nil {
t.Fatal(err)
}
entries, err = ioutil.ReadDir(dir.Name)
if err != nil {
t.Fatal(err)
}
if len(entries) != 2 {
t.Fatalf("ReadDir should have two elements, has %d elements", len(entries))
}
err = dir.Delete()
if err != nil {
t.Fatal(err)
}
_, err = os.Stat(dir.Name)
if err == nil {
t.Fatal("Directory should be gone, still present.")
}
}
func TestDiffer(t *testing.T) {
diff, err := NewDiffer("LOCAL", "LIVE")
if err != nil {
t.Fatal(err)
}
defer diff.TearDown()
obj := FakeObject{
name: "bla",
local: map[string]interface{}{"local": true},
last: map[string]interface{}{"last": true},
live: map[string]interface{}{"live": true},
merged: map[string]interface{}{"merged": true},
}
err = diff.Diff(&obj, Printer{})
if err != nil {
t.Fatal(err)
}
fcontent, err := ioutil.ReadFile(path.Join(diff.From.Dir.Name, obj.Name()))
if err != nil {
t.Fatal(err)
}
econtent := "local: true\n"
if string(fcontent) != econtent {
t.Fatalf("File has %q, expected %q", string(fcontent), econtent)
}
fcontent, err = ioutil.ReadFile(path.Join(diff.To.Dir.Name, obj.Name()))
if err != nil {
t.Fatal(err)
}
econtent = "live: true\n"
if string(fcontent) != econtent {
t.Fatalf("File has %q, expected %q", string(fcontent), econtent)
}
}
/*
Copyright 2014 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 cmd
import (
"fmt"
"io"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/explain"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
var (
explainLong = templates.LongDesc(`
List the fields for supported resources
This command describes the fields associated with each supported API resource.
Fields are identified via a simple JSONPath identifier:
<type>.<fieldName>[.<fieldName>]
Add the --recursive flag to display all of the fields at once without descriptions.
Information about each field is retrieved from the server in OpenAPI format.`)
explainExamples = templates.Examples(i18n.T(`
# Get the documentation of the resource and its fields
kubectl explain pods
# Get the documentation of a specific field of a resource
kubectl explain pods.spec.containers`))
)
// NewCmdExplain returns a cobra command for swagger docs
func NewCmdExplain(f cmdutil.Factory, out, cmdErr io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "explain RESOURCE",
DisableFlagsInUseLine: true,
Short: i18n.T("Documentation of resources"),
Long: explainLong + "\n\n" + cmdutil.ValidResourceTypeList(f),
Example: explainExamples,
Run: func(cmd *cobra.Command, args []string) {
err := RunExplain(f, out, cmdErr, cmd, args)
cmdutil.CheckErr(err)
},
}
cmd.Flags().Bool("recursive", false, "Print the fields of fields (Currently only 1 level deep)")
cmd.Flags().String("api-version", "", "Get different explanations for particular API version")
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
// RunExplain executes the appropriate steps to print a model's documentation
func RunExplain(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, args []string) error {
if len(args) == 0 {
fmt.Fprintf(cmdErr, "You must specify the type of resource to explain. %s\n", cmdutil.ValidResourceTypeList(f))
return cmdutil.UsageErrorf(cmd, "Required resource not specified.")
}
if len(args) > 1 {
return cmdutil.UsageErrorf(cmd, "We accept only this format: explain RESOURCE")
}
recursive := cmdutil.GetFlagBool(cmd, "recursive")
apiVersionString := cmdutil.GetFlagString(cmd, "api-version")
mapper, _ := f.Object()
// TODO: After we figured out the new syntax to separate group and resource, allow
// the users to use it in explain (kubectl explain <group><syntax><resource>).
// Refer to issue #16039 for why we do this. Refer to PR #15808 that used "/" syntax.
inModel, fieldsPath, err := explain.SplitAndParseResourceRequest(args[0], mapper)
if err != nil {
return err
}
// TODO: We should deduce the group for a resource by discovering the supported resources at server.
fullySpecifiedGVR, groupResource := schema.ParseResourceArg(inModel)
gvk := schema.GroupVersionKind{}
if fullySpecifiedGVR != nil {
gvk, _ = mapper.KindFor(*fullySpecifiedGVR)
}
if gvk.Empty() {
gvk, err = mapper.KindFor(groupResource.WithVersion(""))
if err != nil {
return err
}
}
if len(apiVersionString) != 0 {
apiVersion, err := schema.ParseGroupVersion(apiVersionString)
if err != nil {
return err
}
gvk = apiVersion.WithKind(gvk.Kind)
}
resources, err := f.OpenAPISchema()
if err != nil {
return err
}
schema := resources.LookupResource(gvk)
if schema == nil {
return fmt.Errorf("Couldn't find resource for %q", gvk)
}
return explain.PrintModelDescription(fieldsPath, out, schema, gvk, recursive)
}
...@@ -20,7 +20,6 @@ import ( ...@@ -20,7 +20,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"strings"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/spf13/cobra" "github.com/spf13/cobra"
...@@ -42,7 +41,6 @@ import ( ...@@ -42,7 +41,6 @@ import (
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/printers" "k8s.io/kubernetes/pkg/printers"
...@@ -121,8 +119,7 @@ var ( ...@@ -121,8 +119,7 @@ var (
) )
const ( const (
useOpenAPIPrintColumnFlagLabel = "use-openapi-print-columns" useServerPrintColumns = "experimental-server-print"
useServerPrintColumns = "experimental-server-print"
) )
// NewCmdGet creates a command object for the generic "get" action, which // NewCmdGet creates a command object for the generic "get" action, which
...@@ -160,7 +157,6 @@ func NewCmdGet(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Comman ...@@ -160,7 +157,6 @@ func NewCmdGet(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Comman
cmd.Flags().BoolVar(&options.AllNamespaces, "all-namespaces", options.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.") cmd.Flags().BoolVar(&options.AllNamespaces, "all-namespaces", options.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.")
cmdutil.AddIncludeUninitializedFlag(cmd) cmdutil.AddIncludeUninitializedFlag(cmd)
cmdutil.AddPrinterFlags(cmd) cmdutil.AddPrinterFlags(cmd)
addOpenAPIPrintColumnFlags(cmd)
addServerPrintColumnFlags(cmd) addServerPrintColumnFlags(cmd)
cmd.Flags().BoolVar(&options.ShowKind, "show-kind", options.ShowKind, "If present, list the resource type for the requested object(s).") cmd.Flags().BoolVar(&options.ShowKind, "show-kind", options.ShowKind, "If present, list the resource type for the requested object(s).")
cmd.Flags().StringSliceVarP(&options.LabelColumns, "label-columns", "L", options.LabelColumns, "Accepts a comma separated list of labels that are going to be presented as columns. Names are case-sensitive. You can also use multiple flag options like -L label1 -L label2...") cmd.Flags().StringSliceVarP(&options.LabelColumns, "label-columns", "L", options.LabelColumns, "Accepts a comma separated list of labels that are going to be presented as columns. Names are case-sensitive. You can also use multiple flag options like -L label1 -L label2...")
...@@ -337,8 +333,6 @@ func (options *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []str ...@@ -337,8 +333,6 @@ func (options *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []str
var lastMapping *meta.RESTMapping var lastMapping *meta.RESTMapping
w := printers.GetNewTabWriter(options.Out) w := printers.GetNewTabWriter(options.Out)
useOpenAPIPrintColumns := cmdutil.GetFlagBool(cmd, useOpenAPIPrintColumnFlagLabel)
showKind := options.ShowKind || resource.MultipleTypesRequested(args) || cmdutil.MustPrintWithKinds(objs, infos, sorter) showKind := options.ShowKind || resource.MultipleTypesRequested(args) || cmdutil.MustPrintWithKinds(objs, infos, sorter)
filteredResourceCount := 0 filteredResourceCount := 0
...@@ -376,11 +370,6 @@ func (options *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []str ...@@ -376,11 +370,6 @@ func (options *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []str
} }
printOpts := cmdutil.ExtractCmdPrintOptions(cmd, printWithNamespace) printOpts := cmdutil.ExtractCmdPrintOptions(cmd, printWithNamespace)
// if cmd does not specify output format and useOpenAPIPrintColumnFlagLabel flag is true,
// then get the default output options for this mapping from OpenAPI schema.
if !cmdSpecifiesOutputFmt(cmd) && useOpenAPIPrintColumns {
updatePrintOptionsForOpenAPI(f, mapping, printOpts)
}
if showKind && mapping != nil { if showKind && mapping != nil {
printOpts.WithKind = true printOpts.WithKind = true
...@@ -735,10 +724,6 @@ func (options *GetOptions) printGeneric(printer printers.ResourcePrinter, r *res ...@@ -735,10 +724,6 @@ func (options *GetOptions) printGeneric(printer printers.ResourcePrinter, r *res
return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs))) return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs)))
} }
func addOpenAPIPrintColumnFlags(cmd *cobra.Command) {
cmd.Flags().Bool(useOpenAPIPrintColumnFlagLabel, true, "If true, use x-kubernetes-print-column metadata (if present) from the OpenAPI schema for displaying a resource.")
}
func addServerPrintColumnFlags(cmd *cobra.Command) { func addServerPrintColumnFlags(cmd *cobra.Command) {
cmd.Flags().Bool(useServerPrintColumns, false, "If true, have the server return the appropriate table output. Supports extension APIs and CRD. Experimental.") cmd.Flags().Bool(useServerPrintColumns, false, "If true, have the server return the appropriate table output. Supports extension APIs and CRD. Experimental.")
} }
...@@ -750,47 +735,3 @@ func shouldGetNewPrinterForMapping(printer printers.ResourcePrinter, lastMapping ...@@ -750,47 +735,3 @@ func shouldGetNewPrinterForMapping(printer printers.ResourcePrinter, lastMapping
func cmdSpecifiesOutputFmt(cmd *cobra.Command) bool { func cmdSpecifiesOutputFmt(cmd *cobra.Command) bool {
return cmdutil.GetFlagString(cmd, "output") != "" return cmdutil.GetFlagString(cmd, "output") != ""
} }
// outputOptsForMappingFromOpenAPI looks for the output format metatadata in the
// openapi schema and modifies the passed print options for the mapping if found.
func updatePrintOptionsForOpenAPI(f cmdutil.Factory, mapping *meta.RESTMapping, printOpts *printers.PrintOptions) bool {
// user has not specified any output format, check if OpenAPI has
// default specification to print this resource type
api, err := f.OpenAPISchema()
if err != nil {
// Error getting schema
return false
}
// Found openapi metadata for this resource
schema := api.LookupResource(mapping.GroupVersionKind)
if schema == nil {
// Schema not found, return empty columns
return false
}
columns, found := openapi.GetPrintColumns(schema.GetExtensions())
if !found {
// Extension not found, return empty columns
return false
}
return outputOptsFromStr(columns, printOpts)
}
// outputOptsFromStr parses the print-column metadata and generates printer.OutputOptions object.
func outputOptsFromStr(columnStr string, printOpts *printers.PrintOptions) bool {
if columnStr == "" {
return false
}
parts := strings.SplitN(columnStr, "=", 2)
if len(parts) < 2 {
return false
}
printOpts.OutputFormatType = parts[0]
printOpts.OutputFormatArgument = parts[1]
printOpts.AllowMissingKeys = true
return true
}
...@@ -42,7 +42,6 @@ import ( ...@@ -42,7 +42,6 @@ import (
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/categories" "k8s.io/kubernetes/pkg/kubectl/categories"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
"k8s.io/kubernetes/pkg/kubectl/plugins" "k8s.io/kubernetes/pkg/kubectl/plugins"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/validation" "k8s.io/kubernetes/pkg/kubectl/validation"
...@@ -202,8 +201,6 @@ type ObjectMappingFactory interface { ...@@ -202,8 +201,6 @@ type ObjectMappingFactory interface {
// Returns a schema that can validate objects stored on disk. // Returns a schema that can validate objects stored on disk.
Validator(validate bool) (validation.Schema, error) Validator(validate bool) (validation.Schema, error)
// OpenAPISchema returns the schema openapi schema definition
OpenAPISchema() (openapi.Resources, error)
} }
// BuilderFactory holds the third level of factory methods. These functions depend upon ObjectMappingFactory and ClientAccessFactory methods. // BuilderFactory holds the third level of factory methods. These functions depend upon ObjectMappingFactory and ClientAccessFactory methods.
......
...@@ -24,7 +24,6 @@ import ( ...@@ -24,7 +24,6 @@ import (
"os" "os"
"reflect" "reflect"
"sort" "sort"
"sync"
"time" "time"
"github.com/golang/glog" "github.com/golang/glog"
...@@ -46,8 +45,6 @@ import ( ...@@ -46,8 +45,6 @@ import (
"k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/categories" "k8s.io/kubernetes/pkg/kubectl/categories"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
openapivalidation "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/validation"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/validation" "k8s.io/kubernetes/pkg/kubectl/validation"
"k8s.io/kubernetes/pkg/printers" "k8s.io/kubernetes/pkg/printers"
...@@ -56,14 +53,6 @@ import ( ...@@ -56,14 +53,6 @@ import (
type ring1Factory struct { type ring1Factory struct {
clientAccessFactory ClientAccessFactory clientAccessFactory ClientAccessFactory
// openAPIGetter loads and caches openapi specs
openAPIGetter openAPIGetter
}
type openAPIGetter struct {
once sync.Once
getter openapi.Getter
} }
func NewObjectMappingFactory(clientAccessFactory ClientAccessFactory) ObjectMappingFactory { func NewObjectMappingFactory(clientAccessFactory ClientAccessFactory) ObjectMappingFactory {
...@@ -418,34 +407,5 @@ func (f *ring1Factory) AttachablePodForObject(object runtime.Object, timeout tim ...@@ -418,34 +407,5 @@ func (f *ring1Factory) AttachablePodForObject(object runtime.Object, timeout tim
} }
func (f *ring1Factory) Validator(validate bool) (validation.Schema, error) { func (f *ring1Factory) Validator(validate bool) (validation.Schema, error) {
if !validate { return validation.NullSchema{}, nil
return validation.NullSchema{}, nil
}
resources, err := f.OpenAPISchema()
if err != nil {
return nil, err
}
return validation.ConjunctiveSchema{
openapivalidation.NewSchemaValidation(resources),
validation.NoDoubleKeySchema{},
}, nil
}
// OpenAPISchema returns metadata and structural information about Kubernetes object definitions.
func (f *ring1Factory) OpenAPISchema() (openapi.Resources, error) {
discovery, err := f.clientAccessFactory.DiscoveryClient()
if err != nil {
return nil, err
}
// Lazily initialize the OpenAPIGetter once
f.openAPIGetter.once.Do(func() {
// Create the caching OpenAPIGetter
f.openAPIGetter.getter = openapi.NewOpenAPIGetter(discovery)
})
// Delegate to the OpenAPIGetter
return f.openAPIGetter.getter.Get()
} }
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"extensions.go",
"openapi.go",
"openapi_getter.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi",
deps = [
"//vendor/github.com/go-openapi/spec:go_default_library",
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/client-go/discovery:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
],
)
go_test(
name = "go_default_xtest",
size = "small",
srcs = [
"openapi_getter_test.go",
"openapi_suite_test.go",
"openapi_test.go",
],
data = ["//api/openapi-spec:swagger-spec"],
deps = [
":go_default_library",
"//pkg/kubectl/cmd/util/openapi/testing:go_default_library",
"//vendor/github.com/onsi/ginkgo:go_default_library",
"//vendor/github.com/onsi/ginkgo/config:go_default_library",
"//vendor/github.com/onsi/ginkgo/types:go_default_library",
"//vendor/github.com/onsi/gomega:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/kubectl/cmd/util/openapi/testing:all-srcs",
"//pkg/kubectl/cmd/util/openapi/validation:all-srcs",
],
tags = ["automanaged"],
)
filegroup(
name = "testdata",
srcs = glob(["testdata/*"]),
)
approvers:
- apelisse
reviewers:
- apelisse
/*
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 openapi is a collection of libraries for fetching the openapi spec
// from a Kubernetes server and then indexing the type definitions.
// The openapi spec contains the object model definitions and extensions metadata
// such as the patchStrategy and patchMergeKey for creating patches.
package openapi
/*
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 openapi
import "github.com/go-openapi/spec"
const PrintColumnsKey = "x-kubernetes-print-columns"
// GetPrintColumns looks for the open API extension for the display columns.
func GetPrintColumns(extensions spec.Extensions) (string, bool) {
return extensions.GetString(PrintColumnsKey)
}
/*
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 openapi
import (
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kube-openapi/pkg/util/proto"
)
// Resources interface describe a resources provider, that can give you
// resource based on group-version-kind.
type Resources interface {
LookupResource(gvk schema.GroupVersionKind) proto.Schema
}
// groupVersionKindExtensionKey is the key used to lookup the
// GroupVersionKind value for an object definition from the
// definition's "extensions" map.
const groupVersionKindExtensionKey = "x-kubernetes-group-version-kind"
// document is an implementation of `Resources`. It looks for
// resources in an openapi Schema.
type document struct {
// Maps gvk to model name
resources map[schema.GroupVersionKind]string
models proto.Models
}
var _ Resources = &document{}
func NewOpenAPIData(doc *openapi_v2.Document) (Resources, error) {
models, err := proto.NewOpenAPIData(doc)
if err != nil {
return nil, err
}
resources := map[schema.GroupVersionKind]string{}
for _, modelName := range models.ListModels() {
model := models.LookupModel(modelName)
if model == nil {
panic("ListModels returns a model that can't be looked-up.")
}
gvkList := parseGroupVersionKind(model)
for _, gvk := range gvkList {
if len(gvk.Kind) > 0 {
resources[gvk] = modelName
}
}
}
return &document{
resources: resources,
models: models,
}, nil
}
func (d *document) LookupResource(gvk schema.GroupVersionKind) proto.Schema {
modelName, found := d.resources[gvk]
if !found {
return nil
}
return d.models.LookupModel(modelName)
}
// Get and parse GroupVersionKind from the extension. Returns empty if it doesn't have one.
func parseGroupVersionKind(s proto.Schema) []schema.GroupVersionKind {
extensions := s.GetExtensions()
gvkListResult := []schema.GroupVersionKind{}
// Get the extensions
gvkExtension, ok := extensions[groupVersionKindExtensionKey]
if !ok {
return []schema.GroupVersionKind{}
}
// gvk extension must be a list of at least 1 element.
gvkList, ok := gvkExtension.([]interface{})
if !ok {
return []schema.GroupVersionKind{}
}
for _, gvk := range gvkList {
// gvk extension list must be a map with group, version, and
// kind fields
gvkMap, ok := gvk.(map[interface{}]interface{})
if !ok {
continue
}
group, ok := gvkMap["group"].(string)
if !ok {
continue
}
version, ok := gvkMap["version"].(string)
if !ok {
continue
}
kind, ok := gvkMap["kind"].(string)
if !ok {
continue
}
gvkListResult = append(gvkListResult, schema.GroupVersionKind{
Group: group,
Version: version,
Kind: kind,
})
}
return gvkListResult
}
/*
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 openapi
import (
"sync"
"k8s.io/client-go/discovery"
)
// synchronizedOpenAPIGetter fetches the openapi schema once and then caches it in memory
type synchronizedOpenAPIGetter struct {
// Cached results
sync.Once
openAPISchema Resources
err error
openAPIClient discovery.OpenAPISchemaInterface
}
var _ Getter = &synchronizedOpenAPIGetter{}
// Getter is an interface for fetching openapi specs and parsing them into an Resources struct
type Getter interface {
// OpenAPIData returns the parsed OpenAPIData
Get() (Resources, error)
}
// NewOpenAPIGetter returns an object to return OpenAPIDatas which reads
// from a server, and then stores in memory for subsequent invocations
func NewOpenAPIGetter(openAPIClient discovery.OpenAPISchemaInterface) Getter {
return &synchronizedOpenAPIGetter{
openAPIClient: openAPIClient,
}
}
// Resources implements Getter
func (g *synchronizedOpenAPIGetter) Get() (Resources, error) {
g.Do(func() {
s, err := g.openAPIClient.OpenAPISchema()
if err != nil {
g.err = err
return
}
g.openAPISchema, g.err = NewOpenAPIData(s)
})
// Return the save result
return g.openAPISchema, g.err
}
/*
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 openapi_test
import (
"fmt"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
tst "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing"
)
var _ = Describe("Getting the Resources", func() {
var client *tst.FakeClient
var expectedData openapi.Resources
var instance openapi.Getter
BeforeEach(func() {
client = tst.NewFakeClient(&fakeSchema)
d, err := fakeSchema.OpenAPISchema()
Expect(err).To(BeNil())
expectedData, err = openapi.NewOpenAPIData(d)
Expect(err).To(BeNil())
instance = openapi.NewOpenAPIGetter(client)
})
Context("when the server returns a successful result", func() {
It("should return the same data for multiple calls", func() {
Expect(client.Calls).To(Equal(0))
result, err := instance.Get()
Expect(err).To(BeNil())
Expect(result).To(Equal(expectedData))
Expect(client.Calls).To(Equal(1))
result, err = instance.Get()
Expect(err).To(BeNil())
Expect(result).To(Equal(expectedData))
// No additional client calls expected
Expect(client.Calls).To(Equal(1))
})
})
Context("when the server returns an unsuccessful result", func() {
It("should return the same instance for multiple calls.", func() {
Expect(client.Calls).To(Equal(0))
client.Err = fmt.Errorf("expected error")
_, err := instance.Get()
Expect(err).To(Equal(client.Err))
Expect(client.Calls).To(Equal(1))
_, err = instance.Get()
Expect(err).To(Equal(client.Err))
// No additional client calls expected
Expect(client.Calls).To(Equal(1))
})
})
})
/*
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 openapi_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") }
/*
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 openapi_test
import (
"path/filepath"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
tst "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing"
)
var fakeSchema = tst.Fake{Path: filepath.Join("..", "..", "..", "..", "..", "api", "openapi-spec", "swagger.json")}
var _ = Describe("Reading apps/v1beta1/Deployment from openAPIData", func() {
var resources openapi.Resources
BeforeEach(func() {
s, err := fakeSchema.OpenAPISchema()
Expect(err).To(BeNil())
resources, err = openapi.NewOpenAPIData(s)
Expect(err).To(BeNil())
})
gvk := schema.GroupVersionKind{
Kind: "Deployment",
Version: "v1beta1",
Group: "apps",
}
var schema proto.Schema
It("should lookup the Schema by its GroupVersionKind", func() {
schema = resources.LookupResource(gvk)
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 authorization.k8s.io/v1/SubjectAccessReview from openAPIData", func() {
var resources openapi.Resources
BeforeEach(func() {
s, err := fakeSchema.OpenAPISchema()
Expect(err).To(BeNil())
resources, err = openapi.NewOpenAPIData(s)
Expect(err).To(BeNil())
})
gvk := schema.GroupVersionKind{
Kind: "SubjectAccessReview",
Version: "v1",
Group: "authorization.k8s.io",
}
var schema proto.Schema
It("should lookup the Schema by its GroupVersionKind", func() {
schema = resources.LookupResource(gvk)
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())
})
})
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["openapi.go"],
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing",
deps = [
"//pkg/kubectl/cmd/util/openapi:go_default_library",
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
"//vendor/github.com/googleapis/gnostic/compiler:go_default_library",
"//vendor/gopkg.in/yaml.v2:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
/*
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"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
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
}
// FakeClient implements a dummy OpenAPISchemaInterface that uses the
// fake OpenAPI schema given as a parameter, and count the number of
// call to the function.
type FakeClient struct {
Calls int
Err error
fake *Fake
}
// NewFakeClient creates a new FakeClient from the given Fake.
func NewFakeClient(f *Fake) *FakeClient {
return &FakeClient{fake: f}
}
// OpenAPISchema returns a OpenAPI Document as returned by the fake, but
// it also counts the number of calls.
func (f *FakeClient) OpenAPISchema() (*openapi_v2.Document, error) {
f.Calls = f.Calls + 1
if f.Err != nil {
return nil, f.Err
}
return f.fake.OpenAPISchema()
}
// FakeResources is a wrapper to directly load the openapi schema from a
// file, and get the schema for given GVK. This is only for test since
// it's assuming that the file is there and everything will go fine.
type FakeResources struct {
fake Fake
}
var _ openapi.Resources = &FakeResources{}
// NewFakeResources creates a new FakeResources.
func NewFakeResources(path string) *FakeResources {
return &FakeResources{
fake: Fake{Path: path},
}
}
// LookupResource will read the schema, parse it and return the
// resources. It doesn't return errors and will panic instead.
func (f *FakeResources) LookupResource(gvk schema.GroupVersionKind) proto.Schema {
s, err := f.fake.OpenAPISchema()
if err != nil {
panic(err)
}
resources, err := openapi.NewOpenAPIData(s)
if err != nil {
panic(err)
}
return resources.LookupResource(gvk)
}
// EmptyResources implement a Resources that just doesn't have any resources.
type EmptyResources struct{}
var _ openapi.Resources = EmptyResources{}
// LookupResource will always return nil. It doesn't have any resources.
func (f EmptyResources) LookupResource(gvk schema.GroupVersionKind) proto.Schema {
return nil
}
// CreateOpenAPISchemaFunc returns a function useful for the TestFactory.
func CreateOpenAPISchemaFunc(path string) func() (openapi.Resources, error) {
return func() (openapi.Resources, error) {
return NewFakeResources(path), nil
}
}
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["validation.go"],
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/validation",
deps = [
"//pkg/kubectl/cmd/util/openapi:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util/proto/validation:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"validation_suite_test.go",
"validation_test.go",
],
data = ["//api/openapi-spec:swagger-spec"],
embed = [":go_default_library"],
deps = [
"//pkg/api/testapi:go_default_library",
"//pkg/kubectl/cmd/util/openapi:go_default_library",
"//pkg/kubectl/cmd/util/openapi/testing:go_default_library",
"//vendor/github.com/onsi/ginkgo:go_default_library",
"//vendor/github.com/onsi/ginkgo/config:go_default_library",
"//vendor/github.com/onsi/ginkgo/types:go_default_library",
"//vendor/github.com/onsi/gomega:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util/proto/validation:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
/*
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 (
"errors"
"strings"
"k8s.io/apimachinery/pkg/runtime/schema"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/kube-openapi/pkg/util/proto/validation"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
)
// SchemaValidation validates the object against an OpenAPI schema.
type SchemaValidation struct {
resources openapi.Resources
}
// NewSchemaValidation creates a new SchemaValidation that can be used
// to validate objects.
func NewSchemaValidation(resources openapi.Resources) *SchemaValidation {
return &SchemaValidation{
resources: resources,
}
}
// ValidateBytes will validates the object against using the Resources
// object.
func (v *SchemaValidation) ValidateBytes(data []byte) error {
obj, err := parse(data)
if err != nil {
return err
}
gvk, errs := getObjectKind(obj)
if errs != nil {
return utilerrors.NewAggregate(errs)
}
if strings.HasSuffix(gvk.Kind, "List") {
return utilerrors.NewAggregate(v.validateList(obj))
}
return utilerrors.NewAggregate(v.validateResource(obj, gvk))
}
func (v *SchemaValidation) validateList(object interface{}) []error {
fields, ok := object.(map[string]interface{})
if !ok || fields == nil {
return []error{errors.New("invalid object to validate")}
}
allErrors := []error{}
if _, ok := fields["items"].([]interface{}); !ok {
return []error{errors.New("invalid object to validate")}
}
for _, item := range fields["items"].([]interface{}) {
if gvk, errs := getObjectKind(item); errs != nil {
allErrors = append(allErrors, errs...)
} else {
allErrors = append(allErrors, v.validateResource(item, gvk)...)
}
}
return allErrors
}
func (v *SchemaValidation) validateResource(obj interface{}, gvk schema.GroupVersionKind) []error {
resource := v.resources.LookupResource(gvk)
if resource == nil {
// resource is not present, let's just skip validation.
return nil
}
return validation.ValidateModel(obj, resource, gvk.Kind)
}
func parse(data []byte) (interface{}, error) {
var obj interface{}
out, err := yaml.ToJSON(data)
if err != nil {
return nil, err
}
if err := json.Unmarshal(out, &obj); err != nil {
return nil, err
}
return obj, nil
}
func getObjectKind(object interface{}) (schema.GroupVersionKind, []error) {
var listErrors []error
fields, ok := object.(map[string]interface{})
if !ok || fields == nil {
listErrors = append(listErrors, errors.New("invalid object to validate"))
return schema.GroupVersionKind{}, listErrors
}
var group string
var version string
apiVersion := fields["apiVersion"]
if apiVersion == nil {
listErrors = append(listErrors, errors.New("apiVersion not set"))
} else if _, ok := apiVersion.(string); !ok {
listErrors = append(listErrors, errors.New("apiVersion isn't string type"))
} else {
gv, err := schema.ParseGroupVersion(apiVersion.(string))
if err != nil {
listErrors = append(listErrors, err)
} else {
group = gv.Group
version = gv.Version
}
}
kind := fields["kind"]
if kind == nil {
listErrors = append(listErrors, errors.New("kind not set"))
} else if _, ok := kind.(string); !ok {
listErrors = append(listErrors, errors.New("kind isn't string type"))
}
if listErrors != nil {
return schema.GroupVersionKind{}, listErrors
}
return schema.GroupVersionKind{Group: group, Version: version, Kind: kind.(string)}, nil
}
/*
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") }
/*
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 (
"path/filepath"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/kube-openapi/pkg/util/proto/validation"
// This dependency is needed to register API types.
_ "k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
tst "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing"
)
var fakeSchema = tst.Fake{Path: filepath.Join("..", "..", "..", "..", "..", "..", "api", "openapi-spec", "swagger.json")}
var _ = Describe("resource validation using OpenAPI Schema", func() {
var validator *SchemaValidation
BeforeEach(func() {
s, err := fakeSchema.OpenAPISchema()
Expect(err).To(BeNil())
resources, err := openapi.NewOpenAPIData(s)
Expect(err).To(BeNil())
validator = NewSchemaValidation(resources)
Expect(validator).ToNot(BeNil())
})
It("finds Deployment in Schema and validates it", func() {
err := validator.ValidateBytes([]byte(`
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 := validator.ValidateBytes([]byte(`
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 := validator.ValidateBytes([]byte(`
{
"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(utilerrors.NewAggregate([]error{
validation.ValidationError{
Path: "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 := validator.ValidateBytes([]byte(`
{
"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(utilerrors.NewAggregate([]error{
validation.ValidationError{
Path: "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 := validator.ValidateBytes([]byte(`
{
"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.Error()).To(Equal("invalid character ':' after array element"))
})
It("fails because some string lists have empty strings", func() {
err := validator.ValidateBytes([]byte(`
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(utilerrors.NewAggregate([]error{
validation.ValidationError{
Path: "Pod.spec.containers[0].args",
Err: validation.InvalidObjectTypeError{
Path: "Pod.spec.containers[0].args[0]",
Type: "nil",
},
},
validation.ValidationError{
Path: "Pod.spec.containers[0].command",
Err: validation.InvalidObjectTypeError{
Path: "Pod.spec.containers[0].command[0]",
Type: "nil",
},
},
})))
})
It("fails if required fields are missing", func() {
err := validator.ValidateBytes([]byte(`
apiVersion: v1
kind: Pod
metadata:
labels:
name: redis-master
name: name
spec:
containers:
- command: ["my", "command"]
`))
Expect(err).To(Equal(utilerrors.NewAggregate([]error{
validation.ValidationError{
Path: "Pod.spec.containers[0]",
Err: validation.MissingRequiredFieldError{
Path: "io.k8s.api.core.v1.Container",
Field: "name",
},
},
})))
})
It("fails if required fields are empty", func() {
err := validator.ValidateBytes([]byte(`
apiVersion: v1
kind: Pod
metadata:
labels:
name: redis-master
name: name
spec:
containers:
- image:
name:
`))
Expect(err).To(Equal(utilerrors.NewAggregate([]error{
validation.ValidationError{
Path: "Pod.spec.containers[0]",
Err: validation.MissingRequiredFieldError{
Path: "io.k8s.api.core.v1.Container",
Field: "name",
},
},
})))
})
It("is fine with empty non-mandatory fields", func() {
err := validator.ValidateBytes([]byte(`
apiVersion: v1
kind: Pod
metadata:
labels:
name: redis-master
name: name
spec:
containers:
- image: image
name: name
command:
`))
Expect(err).To(BeNil())
})
It("can validate lists", func() {
err := validator.ValidateBytes([]byte(`
apiVersion: v1
kind: List
items:
- apiVersion: v1
kind: Pod
metadata:
labels:
name: redis-master
name: name
spec:
containers:
- name: name
`))
Expect(err).To(BeNil())
})
It("fails because apiVersion is not provided", func() {
err := validator.ValidateBytes([]byte(`
kind: Pod
metadata:
name: name
spec:
containers:
- name: name
image: image
`))
Expect(err.Error()).To(Equal("apiVersion not set"))
})
It("fails because apiVersion type is not string and kind is not provided", func() {
err := validator.ValidateBytes([]byte(`
apiVersion: 1
metadata:
name: name
spec:
containers:
- name: name
image: image
`))
Expect(err.Error()).To(Equal("[apiVersion isn't string type, kind not set]"))
})
It("fails because List first item is missing kind and second item is missing apiVersion", func() {
err := validator.ValidateBytes([]byte(`
apiVersion: v1
kind: List
items:
- apiVersion: v1
metadata:
name: name
spec:
replicas: 1
template:
metadata:
labels:
name: name
spec:
containers:
- name: name
image: image
- kind: Service
metadata:
name: name
spec:
type: NodePort
ports:
- port: 123
targetPort: 1234
name: name
selector:
name: name
`))
Expect(err.Error()).To(Equal("[kind not set, apiVersion not set]"))
})
})
/*
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 explain
import (
"io"
"strings"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kube-openapi/pkg/util/proto"
)
type fieldsPrinter interface {
PrintFields(proto.Schema) error
}
func splitDotNotation(model string) (string, []string) {
var fieldsPath []string
// ignore trailing period
model = strings.TrimSuffix(model, ".")
dotModel := strings.Split(model, ".")
if len(dotModel) >= 1 {
fieldsPath = dotModel[1:]
}
return dotModel[0], fieldsPath
}
// SplitAndParseResourceRequest separates the users input into a model and fields
func SplitAndParseResourceRequest(inResource string, mapper meta.RESTMapper) (string, []string, error) {
inResource, fieldsPath := splitDotNotation(inResource)
inResource, _ = mapper.ResourceSingularizer(inResource)
return inResource, fieldsPath, nil
}
// PrintModelDescription prints the description of a specific model or dot path.
// If recursive, all components nested within the fields of the schema will be
// printed.
func PrintModelDescription(fieldsPath []string, w io.Writer, schema proto.Schema, gvk schema.GroupVersionKind, recursive bool) error {
fieldName := ""
if len(fieldsPath) != 0 {
fieldName = fieldsPath[len(fieldsPath)-1]
}
// Go down the fieldsPath to find what we're trying to explain
schema, err := LookupSchemaForField(schema, fieldsPath)
if err != nil {
return err
}
b := fieldsPrinterBuilder{Recursive: recursive}
f := &Formatter{Writer: w, Wrap: 80}
return PrintModel(fieldName, f, b, schema, gvk)
}
/*
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 explain
import (
"reflect"
"testing"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
func TestSplitAndParseResourceRequest(t *testing.T) {
tests := []struct {
name string
inresource string
expectedInResource string
expectedFieldsPath []string
expectedErr bool
}{
{
name: "no trailing period",
inresource: "field1.field2.field3",
expectedInResource: "field1",
expectedFieldsPath: []string{"field2", "field3"},
},
{
name: "trailing period with correct fieldsPath",
inresource: "field1.field2.field3.",
expectedInResource: "field1",
expectedFieldsPath: []string{"field2", "field3"},
},
{
name: "trailing period with incorrect fieldsPath",
inresource: "field1.field2.field3.",
expectedInResource: "field1",
expectedFieldsPath: []string{"field2", "field3", ""},
expectedErr: true,
},
}
mapper := scheme.Registry.RESTMapper(scheme.Versions...)
for _, test := range tests {
gotInResource, gotFieldsPath, err := SplitAndParseResourceRequest(test.inresource, mapper)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(test.expectedInResource, gotInResource) && !test.expectedErr {
t.Errorf("%s: expected inresource: %s, got: %s", test.name, test.expectedInResource, gotInResource)
}
if !reflect.DeepEqual(test.expectedFieldsPath, gotFieldsPath) && !test.expectedErr {
t.Errorf("%s: expected fieldsPath: %s, got: %s", test.name, test.expectedFieldsPath, gotFieldsPath)
}
}
}
/*
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 explain
import (
"fmt"
"k8s.io/kube-openapi/pkg/util/proto"
)
// fieldLookup walks through a schema by following a path, and returns
// the final schema.
type fieldLookup struct {
// Path to walk
Path []string
// Return information: Schema found, or error.
Schema proto.Schema
Error error
}
// SaveLeafSchema is used to detect if we are done walking the path, and
// saves the schema as a match.
func (f *fieldLookup) SaveLeafSchema(schema proto.Schema) bool {
if len(f.Path) != 0 {
return false
}
f.Schema = schema
return true
}
// VisitArray is mostly a passthrough.
func (f *fieldLookup) VisitArray(a *proto.Array) {
if f.SaveLeafSchema(a) {
return
}
// Passthrough arrays.
a.SubType.Accept(f)
}
// VisitMap is mostly a passthrough.
func (f *fieldLookup) VisitMap(m *proto.Map) {
if f.SaveLeafSchema(m) {
return
}
// Passthrough maps.
m.SubType.Accept(f)
}
// VisitPrimitive stops the operation and returns itself as the found
// schema, even if it had more path to walk.
func (f *fieldLookup) VisitPrimitive(p *proto.Primitive) {
// Even if Path is not empty (we're not expecting a leaf),
// return that primitive.
f.Schema = p
}
// VisitKind unstacks fields as it finds them.
func (f *fieldLookup) VisitKind(k *proto.Kind) {
if f.SaveLeafSchema(k) {
return
}
subSchema, ok := k.Fields[f.Path[0]]
if !ok {
f.Error = fmt.Errorf("field %q does not exist", f.Path[0])
return
}
f.Path = f.Path[1:]
subSchema.Accept(f)
}
// VisitReference is mostly a passthrough.
func (f *fieldLookup) VisitReference(r proto.Reference) {
if f.SaveLeafSchema(r) {
return
}
// Passthrough references.
r.SubSchema().Accept(f)
}
// LookupSchemaForField looks for the schema of a given path in a base schema.
func LookupSchemaForField(schema proto.Schema, path []string) (proto.Schema, error) {
f := &fieldLookup{Path: path}
schema.Accept(f)
return f.Schema, f.Error
}
/*
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 explain
import (
"testing"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func TestFindField(t *testing.T) {
schema := resources.LookupResource(schema.GroupVersionKind{
Group: "",
Version: "v1",
Kind: "OneKind",
})
if schema == nil {
t.Fatal("Counldn't find schema v1.OneKind")
}
tests := []struct {
path []string
err string
expectedPath string
}{
{
path: []string{},
expectedPath: "OneKind",
},
{
path: []string{"field1"},
expectedPath: "OneKind.field1",
},
{
path: []string{"field1", "array"},
expectedPath: "OtherKind.array",
},
{
path: []string{"field1", "what?"},
err: `field "what?" does not exist`,
},
{
path: []string{"field1", ""},
err: `field "" does not exist`,
},
}
for _, test := range tests {
path, err := LookupSchemaForField(schema, test.path)
gotErr := ""
if err != nil {
gotErr = err.Error()
}
gotPath := ""
if path != nil {
gotPath = path.GetPath().String()
}
if gotErr != test.err || gotPath != test.expectedPath {
t.Errorf("LookupSchemaForField(schema, %v) = (path: %q, err: %q), expected (path: %q, err: %q)",
test.path, gotPath, gotErr, test.expectedPath, test.err)
}
}
}
/*
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 explain
import "k8s.io/kube-openapi/pkg/util/proto"
// indentDesc is the level of indentation for descriptions.
const indentDesc = 2
// regularFieldsPrinter prints fields with their type and description.
type regularFieldsPrinter struct {
Writer *Formatter
Error error
}
var _ proto.SchemaVisitor = &regularFieldsPrinter{}
var _ fieldsPrinter = &regularFieldsPrinter{}
// VisitArray prints a Array type. It is just a passthrough.
func (f *regularFieldsPrinter) VisitArray(a *proto.Array) {
a.SubType.Accept(f)
}
// VisitKind prints a Kind type. It prints each key in the kind, with
// the type, the required flag, and the description.
func (f *regularFieldsPrinter) VisitKind(k *proto.Kind) {
for _, key := range k.Keys() {
v := k.Fields[key]
required := ""
if k.IsRequired(key) {
required = " -required-"
}
if err := f.Writer.Write("%s\t<%s>%s", key, GetTypeName(v), required); err != nil {
f.Error = err
return
}
if err := f.Writer.Indent(indentDesc).WriteWrapped("%s", v.GetDescription()); err != nil {
f.Error = err
return
}
if err := f.Writer.Write(""); err != nil {
f.Error = err
return
}
}
}
// VisitMap prints a Map type. It is just a passthrough.
func (f *regularFieldsPrinter) VisitMap(m *proto.Map) {
m.SubType.Accept(f)
}
// VisitPrimitive prints a Primitive type. It stops the recursion.
func (f *regularFieldsPrinter) VisitPrimitive(p *proto.Primitive) {
// Nothing to do. Shouldn't really happen.
}
// VisitReference prints a Reference type. It is just a passthrough.
func (f *regularFieldsPrinter) VisitReference(r proto.Reference) {
r.SubSchema().Accept(f)
}
// PrintFields will write the types from schema.
func (f *regularFieldsPrinter) PrintFields(schema proto.Schema) error {
schema.Accept(f)
return f.Error
}
/*
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 explain
// fieldsPrinterBuilder builds either a regularFieldsPrinter or a
// recursiveFieldsPrinter based on the argument.
type fieldsPrinterBuilder struct {
Recursive bool
}
// BuildFieldsPrinter builds the appropriate fieldsPrinter.
func (f fieldsPrinterBuilder) BuildFieldsPrinter(writer *Formatter) fieldsPrinter {
if f.Recursive {
return &recursiveFieldsPrinter{
Writer: writer,
}
}
return &regularFieldsPrinter{
Writer: writer,
}
}
/*
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 explain
import (
"bytes"
"testing"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func TestFields(t *testing.T) {
schema := resources.LookupResource(schema.GroupVersionKind{
Group: "",
Version: "v1",
Kind: "OneKind",
})
if schema == nil {
t.Fatal("Couldn't find schema v1.OneKind")
}
want := `field1 <Object> -required-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla ut lacus ac
enim vulputate imperdiet ac accumsan risus. Integer vel accumsan lectus.
Praesent tempus nulla id tortor luctus, quis varius nulla laoreet. Ut orci
nisi, suscipit id velit sed, blandit eleifend turpis. Curabitur tempus ante at
lectus viverra, a mattis augue euismod. Morbi quam ligula, porttitor sit amet
lacus non, interdum pulvinar tortor. Praesent accumsan risus et ipsum dictum,
vel ullamcorper lorem egestas.
field2 <[]map[string]string>
This is an array of object of PrimitiveDef
`
buf := bytes.Buffer{}
f := Formatter{
Writer: &buf,
Wrap: 80,
}
s, err := LookupSchemaForField(schema, []string{})
if err != nil {
t.Fatalf("Invalid path %v: %v", []string{}, err)
}
if err := (fieldsPrinterBuilder{Recursive: false}).BuildFieldsPrinter(&f).PrintFields(s); err != nil {
t.Fatalf("Failed to print fields: %v", err)
}
got := buf.String()
if got != want {
t.Errorf("Got:\n%v\nWant:\n%v\n", buf.String(), want)
}
}
/*
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 explain
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kube-openapi/pkg/util/proto"
)
// fieldIndentLevel is the level of indentation for fields.
const fieldIndentLevel = 3
// descriptionIndentLevel is the level of indentation for the
// description.
const descriptionIndentLevel = 5
// modelPrinter prints a schema in Writer. Its "Builder" will decide if
// it's recursive or not.
type modelPrinter struct {
Name string
Type string
Descriptions []string
Writer *Formatter
Builder fieldsPrinterBuilder
GVK schema.GroupVersionKind
Error error
}
var _ proto.SchemaVisitor = &modelPrinter{}
func (m *modelPrinter) PrintKindAndVersion() error {
if err := m.Writer.Write("KIND: %s", m.GVK.Kind); err != nil {
return err
}
return m.Writer.Write("VERSION: %s\n", m.GVK.GroupVersion())
}
// PrintDescription prints the description for a given schema. There
// might be multiple description, since we collect descriptions when we
// go through references, arrays and maps.
func (m *modelPrinter) PrintDescription(schema proto.Schema) error {
if err := m.Writer.Write("DESCRIPTION:"); err != nil {
return err
}
for i, desc := range append(m.Descriptions, schema.GetDescription()) {
if desc == "" {
continue
}
if i != 0 {
if err := m.Writer.Write(""); err != nil {
return err
}
}
if err := m.Writer.Indent(descriptionIndentLevel).WriteWrapped(desc); err != nil {
return err
}
}
return nil
}
// VisitArray recurses inside the subtype, while collecting the type if
// not done yet, and the description.
func (m *modelPrinter) VisitArray(a *proto.Array) {
m.Descriptions = append(m.Descriptions, a.GetDescription())
if m.Type == "" {
m.Type = GetTypeName(a)
}
a.SubType.Accept(m)
}
// VisitKind prints a full resource with its fields.
func (m *modelPrinter) VisitKind(k *proto.Kind) {
if err := m.PrintKindAndVersion(); err != nil {
m.Error = err
return
}
if m.Type == "" {
m.Type = GetTypeName(k)
}
if m.Name != "" {
m.Writer.Write("RESOURCE: %s <%s>\n", m.Name, m.Type)
}
if err := m.PrintDescription(k); err != nil {
m.Error = err
return
}
if err := m.Writer.Write("\nFIELDS:"); err != nil {
m.Error = err
return
}
m.Error = m.Builder.BuildFieldsPrinter(m.Writer.Indent(fieldIndentLevel)).PrintFields(k)
}
// VisitMap recurses inside the subtype, while collecting the type if
// not done yet, and the description.
func (m *modelPrinter) VisitMap(om *proto.Map) {
m.Descriptions = append(m.Descriptions, om.GetDescription())
if m.Type == "" {
m.Type = GetTypeName(om)
}
om.SubType.Accept(m)
}
// VisitPrimitive prints a field type and its description.
func (m *modelPrinter) VisitPrimitive(p *proto.Primitive) {
if err := m.PrintKindAndVersion(); err != nil {
m.Error = err
return
}
if m.Type == "" {
m.Type = GetTypeName(p)
}
if err := m.Writer.Write("FIELD: %s <%s>\n", m.Name, m.Type); err != nil {
m.Error = err
return
}
m.Error = m.PrintDescription(p)
}
// VisitReference recurses inside the subtype, while collecting the description.
func (m *modelPrinter) VisitReference(r proto.Reference) {
m.Descriptions = append(m.Descriptions, r.GetDescription())
r.SubSchema().Accept(m)
}
// PrintModel prints the description of a schema in writer.
func PrintModel(name string, writer *Formatter, builder fieldsPrinterBuilder, schema proto.Schema, gvk schema.GroupVersionKind) error {
m := &modelPrinter{Name: name, Writer: writer, Builder: builder, GVK: gvk}
schema.Accept(m)
return m.Error
}
/*
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 explain
import "k8s.io/kube-openapi/pkg/util/proto"
// indentPerLevel is the level of indentation for each field recursion.
const indentPerLevel = 3
// recursiveFieldsPrinter recursively prints all the fields for a given
// schema.
type recursiveFieldsPrinter struct {
Writer *Formatter
Error error
}
var _ proto.SchemaVisitor = &recursiveFieldsPrinter{}
var _ fieldsPrinter = &recursiveFieldsPrinter{}
// VisitArray is just a passthrough.
func (f *recursiveFieldsPrinter) VisitArray(a *proto.Array) {
a.SubType.Accept(f)
}
// VisitKind prints all its fields with their type, and then recurses
// inside each of these (pre-order).
func (f *recursiveFieldsPrinter) VisitKind(k *proto.Kind) {
for _, key := range k.Keys() {
v := k.Fields[key]
f.Writer.Write("%s\t<%s>", key, GetTypeName(v))
subFields := &recursiveFieldsPrinter{
Writer: f.Writer.Indent(indentPerLevel),
}
if err := subFields.PrintFields(v); err != nil {
f.Error = err
return
}
}
}
// VisitMap is just a passthrough.
func (f *recursiveFieldsPrinter) VisitMap(m *proto.Map) {
m.SubType.Accept(f)
}
// VisitPrimitive does nothing, since it doesn't have sub-fields.
func (f *recursiveFieldsPrinter) VisitPrimitive(p *proto.Primitive) {
// Nothing to do.
}
// VisitReference is just a passthrough.
func (f *recursiveFieldsPrinter) VisitReference(r proto.Reference) {
r.SubSchema().Accept(f)
}
// PrintFields will recursively print all the fields for the given
// schema.
func (f *recursiveFieldsPrinter) PrintFields(schema proto.Schema) error {
schema.Accept(f)
return f.Error
}
/*
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 explain
import (
"bytes"
"testing"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func TestRecursiveFields(t *testing.T) {
schema := resources.LookupResource(schema.GroupVersionKind{
Group: "",
Version: "v1",
Kind: "OneKind",
})
if schema == nil {
t.Fatal("Couldn't find schema v1.OneKind")
}
want := `field1 <Object>
array <[]integer>
int <integer>
object <map[string]string>
primitive <string>
string <string>
field2 <[]map[string]string>
`
buf := bytes.Buffer{}
f := Formatter{
Writer: &buf,
Wrap: 80,
}
s, err := LookupSchemaForField(schema, []string{})
if err != nil {
t.Fatalf("Invalid path %v: %v", []string{}, err)
}
if err := (fieldsPrinterBuilder{Recursive: true}).BuildFieldsPrinter(&f).PrintFields(s); err != nil {
t.Fatalf("Failed to print fields: %v", err)
}
got := buf.String()
if got != want {
t.Errorf("Got:\n%v\nWant:\n%v\n", buf.String(), want)
}
}
/*
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 explain
import (
"fmt"
"k8s.io/kube-openapi/pkg/util/proto"
)
// typeName finds the name of a schema
type typeName struct {
Name string
}
var _ proto.SchemaVisitor = &typeName{}
// VisitArray adds the [] prefix and recurses.
func (t *typeName) VisitArray(a *proto.Array) {
s := &typeName{}
a.SubType.Accept(s)
t.Name = fmt.Sprintf("[]%s", s.Name)
}
// VisitKind just returns "Object".
func (t *typeName) VisitKind(k *proto.Kind) {
t.Name = "Object"
}
// VisitMap adds the map[string] prefix and recurses.
func (t *typeName) VisitMap(m *proto.Map) {
s := &typeName{}
m.SubType.Accept(s)
t.Name = fmt.Sprintf("map[string]%s", s.Name)
}
// VisitPrimitive returns the name of the primitive.
func (t *typeName) VisitPrimitive(p *proto.Primitive) {
t.Name = p.Type
}
// VisitReference is just a passthrough.
func (t *typeName) VisitReference(r proto.Reference) {
r.SubSchema().Accept(t)
}
// GetTypeName returns the type of a schema.
func GetTypeName(schema proto.Schema) string {
t := &typeName{}
schema.Accept(t)
return t.Name
}
/*
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 explain
import (
"testing"
"k8s.io/apimachinery/pkg/runtime/schema"
tst "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing"
)
var resources = tst.NewFakeResources("test-swagger.json")
func TestReferenceTypename(t *testing.T) {
schema := resources.LookupResource(schema.GroupVersionKind{
Group: "",
Version: "v1",
Kind: "OneKind",
})
if schema == nil {
t.Fatal("Couldn't find schema v1.OneKind")
}
tests := []struct {
path []string
expected string
}{
{
// Kind is "Object"
path: []string{},
expected: "Object",
},
{
// Reference is equal to pointed type "Object"
path: []string{"field1"},
expected: "Object",
},
{
// Reference is equal to pointed type "string"
path: []string{"field1", "primitive"},
expected: "string",
},
{
// Array of object of reference to string
path: []string{"field2"},
expected: "[]map[string]string",
},
{
// Array of integer
path: []string{"field1", "array"},
expected: "[]integer",
},
}
for _, test := range tests {
s, err := LookupSchemaForField(schema, test.path)
if err != nil {
t.Fatalf("Invalid test.path %v: %v", test.path, err)
}
got := GetTypeName(s)
if got != test.expected {
t.Errorf("Got %q, expected %q", got, test.expected)
}
}
}
...@@ -274,8 +274,6 @@ type KubeletAuthorizationMode string ...@@ -274,8 +274,6 @@ type KubeletAuthorizationMode string
const ( const (
// KubeletAuthorizationModeAlwaysAllow authorizes all authenticated requests // KubeletAuthorizationModeAlwaysAllow authorizes all authenticated requests
KubeletAuthorizationModeAlwaysAllow KubeletAuthorizationMode = "AlwaysAllow" KubeletAuthorizationModeAlwaysAllow KubeletAuthorizationMode = "AlwaysAllow"
// KubeletAuthorizationModeWebhook uses the SubjectAccessReview API to determine authorization
KubeletAuthorizationModeWebhook KubeletAuthorizationMode = "Webhook"
) )
type KubeletAuthorization struct { type KubeletAuthorization struct {
......
...@@ -26,7 +26,6 @@ import ( ...@@ -26,7 +26,6 @@ import (
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features" apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
) )
...@@ -196,12 +195,6 @@ func ValidateCustomResourceDefinitionValidation(customResourceValidation *apiext ...@@ -196,12 +195,6 @@ func ValidateCustomResourceDefinitionValidation(customResourceValidation *apiext
allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(schema, fldPath.Child("openAPIV3Schema"), openAPIV3Schema)...) allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(schema, fldPath.Child("openAPIV3Schema"), openAPIV3Schema)...)
} }
// if validation passed otherwise, make sure we can actually construct a schema validator from this custom resource validation.
if len(allErrs) == 0 {
if _, _, err := apiservervalidation.NewSchemaValidator(customResourceValidation); err != nil {
allErrs = append(allErrs, field.Invalid(fldPath, "", fmt.Sprintf("error building validator: %v", err)))
}
}
return allErrs return allErrs
} }
......
...@@ -25,7 +25,6 @@ import ( ...@@ -25,7 +25,6 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/go-openapi/validate"
"github.com/golang/glog" "github.com/golang/glog"
apiequality "k8s.io/apimachinery/pkg/api/equality" apiequality "k8s.io/apimachinery/pkg/api/equality"
...@@ -55,7 +54,6 @@ import ( ...@@ -55,7 +54,6 @@ import (
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion" informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion"
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion" listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion"
"k8s.io/apiextensions-apiserver/pkg/controller/finalizer" "k8s.io/apiextensions-apiserver/pkg/controller/finalizer"
...@@ -393,14 +391,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource ...@@ -393,14 +391,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource
} }
creator := unstructuredCreator{} creator := unstructuredCreator{}
validator, _, err := apiservervalidation.NewSchemaValidator(crd.Spec.Validation)
if err != nil {
return nil, err
}
var statusSpec *apiextensions.CustomResourceSubresourceStatus var statusSpec *apiextensions.CustomResourceSubresourceStatus
var statusValidator *validate.SchemaValidator
var scaleSpec *apiextensions.CustomResourceSubresourceScale var scaleSpec *apiextensions.CustomResourceSubresourceScale
// TODO: identify how to pass printer specification from the CRD // TODO: identify how to pass printer specification from the CRD
...@@ -416,8 +407,6 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource ...@@ -416,8 +407,6 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource
typer, typer,
crd.Spec.Scope == apiextensions.NamespaceScoped, crd.Spec.Scope == apiextensions.NamespaceScoped,
kind, kind,
validator,
statusValidator,
statusSpec, statusSpec,
scaleSpec, scaleSpec,
), ),
......
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["validation.go"],
importpath = "k8s.io/apiextensions-apiserver/pkg/apiserver/validation",
deps = [
"//vendor/github.com/go-openapi/spec:go_default_library",
"//vendor/github.com/go-openapi/strfmt:go_default_library",
"//vendor/github.com/go-openapi/validate:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = ["validation_test.go"],
embed = [":go_default_library"],
deps = [
"//vendor/github.com/go-openapi/spec:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality: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/runtime/serializer:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
],
)
/*
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/go-openapi/spec"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
)
// NewSchemaValidator creates an openapi schema validator for the given CRD validation.
func NewSchemaValidator(customResourceValidation *apiextensions.CustomResourceValidation) (*validate.SchemaValidator, *spec.Schema, error) {
// Convert CRD schema to openapi schema
openapiSchema := &spec.Schema{}
if customResourceValidation != nil {
if err := ConvertJSONSchemaProps(customResourceValidation.OpenAPIV3Schema, openapiSchema); err != nil {
return nil, nil, err
}
}
return validate.NewSchemaValidator(openapiSchema, nil, "", strfmt.Default), openapiSchema, nil
}
// ValidateCustomResource validates the Custom Resource against the schema in the CustomResourceDefinition.
// CustomResource is a JSON data structure.
func ValidateCustomResource(customResource interface{}, validator *validate.SchemaValidator) error {
if validator == nil {
return nil
}
result := validator.Validate(customResource)
if result.AsError() != nil {
return result.AsError()
}
return nil
}
// ConvertJSONSchemaProps converts the schema from apiextensions.JSONSchemaPropos to go-openapi/spec.Schema
func ConvertJSONSchemaProps(in *apiextensions.JSONSchemaProps, out *spec.Schema) error {
if in == nil {
return nil
}
out.ID = in.ID
out.Schema = spec.SchemaURL(in.Schema)
out.Description = in.Description
if in.Type != "" {
out.Type = spec.StringOrArray([]string{in.Type})
}
out.Format = in.Format
out.Title = in.Title
out.Maximum = in.Maximum
out.ExclusiveMaximum = in.ExclusiveMaximum
out.Minimum = in.Minimum
out.ExclusiveMinimum = in.ExclusiveMinimum
out.MaxLength = in.MaxLength
out.MinLength = in.MinLength
out.Pattern = in.Pattern
out.MaxItems = in.MaxItems
out.MinItems = in.MinItems
out.UniqueItems = in.UniqueItems
out.MultipleOf = in.MultipleOf
out.MaxProperties = in.MaxProperties
out.MinProperties = in.MinProperties
out.Required = in.Required
if in.Default != nil {
out.Default = *(in.Default)
}
if in.Example != nil {
out.Example = *(in.Example)
}
out.Enum = make([]interface{}, len(in.Enum))
for k, v := range in.Enum {
out.Enum[k] = v
}
if err := convertSliceOfJSONSchemaProps(&in.AllOf, &out.AllOf); err != nil {
return err
}
if err := convertSliceOfJSONSchemaProps(&in.OneOf, &out.OneOf); err != nil {
return err
}
if err := convertSliceOfJSONSchemaProps(&in.AnyOf, &out.AnyOf); err != nil {
return err
}
if in.Not != nil {
in, out := &in.Not, &out.Not
*out = new(spec.Schema)
if err := ConvertJSONSchemaProps(*in, *out); err != nil {
return err
}
}
var err error
out.Properties, err = convertMapOfJSONSchemaProps(in.Properties)
if err != nil {
return err
}
out.PatternProperties, err = convertMapOfJSONSchemaProps(in.PatternProperties)
if err != nil {
return err
}
out.Definitions, err = convertMapOfJSONSchemaProps(in.Definitions)
if err != nil {
return err
}
if in.Ref != nil {
out.Ref, err = spec.NewRef(*in.Ref)
if err != nil {
return err
}
}
if in.AdditionalProperties != nil {
in, out := &in.AdditionalProperties, &out.AdditionalProperties
*out = new(spec.SchemaOrBool)
if err := convertJSONSchemaPropsorBool(*in, *out); err != nil {
return err
}
}
if in.AdditionalItems != nil {
in, out := &in.AdditionalItems, &out.AdditionalItems
*out = new(spec.SchemaOrBool)
if err := convertJSONSchemaPropsorBool(*in, *out); err != nil {
return err
}
}
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = new(spec.SchemaOrArray)
if err := convertJSONSchemaPropsOrArray(*in, *out); err != nil {
return err
}
}
if in.Dependencies != nil {
in, out := &in.Dependencies, &out.Dependencies
*out = make(spec.Dependencies, len(*in))
for key, val := range *in {
newVal := new(spec.SchemaOrStringArray)
if err := convertJSONSchemaPropsOrStringArray(&val, newVal); err != nil {
return err
}
(*out)[key] = *newVal
}
}
if in.ExternalDocs != nil {
out.ExternalDocs = &spec.ExternalDocumentation{}
out.ExternalDocs.Description = in.ExternalDocs.Description
out.ExternalDocs.URL = in.ExternalDocs.URL
}
return nil
}
func convertSliceOfJSONSchemaProps(in *[]apiextensions.JSONSchemaProps, out *[]spec.Schema) error {
if in != nil {
for _, jsonSchemaProps := range *in {
schema := spec.Schema{}
if err := ConvertJSONSchemaProps(&jsonSchemaProps, &schema); err != nil {
return err
}
*out = append(*out, schema)
}
}
return nil
}
func convertMapOfJSONSchemaProps(in map[string]apiextensions.JSONSchemaProps) (map[string]spec.Schema, error) {
out := make(map[string]spec.Schema)
if len(in) != 0 {
for k, jsonSchemaProps := range in {
schema := spec.Schema{}
if err := ConvertJSONSchemaProps(&jsonSchemaProps, &schema); err != nil {
return nil, err
}
out[k] = schema
}
}
return out, nil
}
func convertJSONSchemaPropsOrArray(in *apiextensions.JSONSchemaPropsOrArray, out *spec.SchemaOrArray) error {
if in.Schema != nil {
in, out := &in.Schema, &out.Schema
*out = new(spec.Schema)
if err := ConvertJSONSchemaProps(*in, *out); err != nil {
return err
}
}
if in.JSONSchemas != nil {
in, out := &in.JSONSchemas, &out.Schemas
*out = make([]spec.Schema, len(*in))
for i := range *in {
if err := ConvertJSONSchemaProps(&(*in)[i], &(*out)[i]); err != nil {
return err
}
}
}
return nil
}
func convertJSONSchemaPropsorBool(in *apiextensions.JSONSchemaPropsOrBool, out *spec.SchemaOrBool) error {
out.Allows = in.Allows
if in.Schema != nil {
in, out := &in.Schema, &out.Schema
*out = new(spec.Schema)
if err := ConvertJSONSchemaProps(*in, *out); err != nil {
return err
}
}
return nil
}
func convertJSONSchemaPropsOrStringArray(in *apiextensions.JSONSchemaPropsOrStringArray, out *spec.SchemaOrStringArray) error {
out.Property = in.Property
if in.Schema != nil {
in, out := &in.Schema, &out.Schema
*out = new(spec.Schema)
if err := ConvertJSONSchemaProps(*in, *out); err != nil {
return err
}
}
return nil
}
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment