Commit 1aca10d2 authored by Darren Shepherd's avatar Darren Shepherd

Remove openapi/swagger

parent bb6ca7ce
...@@ -110,7 +110,6 @@ kubernetes.tar.gz ...@@ -110,7 +110,6 @@ kubernetes.tar.gz
# generated files in any directory # generated files in any directory
# TODO(thockin): uncomment this when we stop committing the generated files. # TODO(thockin): uncomment this when we stop committing the generated files.
#zz_generated.* #zz_generated.*
zz_generated.openapi.go
zz_generated_*_test.go zz_generated_*_test.go
# make-related metadata # make-related metadata
......
...@@ -73,10 +73,6 @@ func createAggregatorConfig( ...@@ -73,10 +73,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.Paging = utilfeature.DefaultFeatureGate.Enabled(features.APIListChunking) etcdOptions.StorageConfig.Paging = utilfeature.DefaultFeatureGate.Enabled(features.APIListChunking)
......
...@@ -341,7 +341,7 @@ func (s *KubeControllerManagerOptions) ApplyTo(c *kubecontrollerconfig.Config) e ...@@ -341,7 +341,7 @@ func (s *KubeControllerManagerOptions) ApplyTo(c *kubecontrollerconfig.Config) e
return err return err
} }
if s.SecureServing.BindPort != 0 || s.SecureServing.Listener != nil { if s.SecureServing.BindPort != 0 || s.SecureServing.Listener != nil {
if err := s.Authentication.ApplyTo(&c.Authentication, c.SecureServing, nil); err != nil { if err := s.Authentication.ApplyTo(&c.Authentication, c.SecureServing); err != nil {
return err return err
} }
if err := s.Authorization.ApplyTo(&c.Authorization); err != nil { if err := s.Authorization.ApplyTo(&c.Authorization); err != nil {
......
...@@ -190,7 +190,7 @@ func (o *Options) ApplyTo(c *schedulerappconfig.Config) error { ...@@ -190,7 +190,7 @@ func (o *Options) ApplyTo(c *schedulerappconfig.Config) error {
return err return err
} }
if o.SecureServing != nil && (o.SecureServing.BindPort != 0 || o.SecureServing.Listener != nil) { if o.SecureServing != nil && (o.SecureServing.BindPort != 0 || o.SecureServing.Listener != nil) {
if err := o.Authentication.ApplyTo(&c.Authentication, c.SecureServing, nil); err != nil { if err := o.Authentication.ApplyTo(&c.Authentication, c.SecureServing); err != nil {
return err return err
} }
if err := o.Authorization.ApplyTo(&c.Authorization); err != nil { if err := o.Authorization.ApplyTo(&c.Authorization); err != nil {
......
...@@ -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
......
# 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 = [
"cmd/cloud-controller-manager/app/apis/config/v1alpha1",
"pkg/apis/abac/v0",
"pkg/apis/abac/v1beta1",
"pkg/apis/auditregistration",
"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/auditregistration/v1alpha1",
"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/autoscaling/v2beta2",
"k8s.io/api/batch/v1",
"k8s.io/api/batch/v1beta1",
"k8s.io/api/batch/v2alpha1",
"k8s.io/api/certificates/v1beta1",
"k8s.io/api/coordination/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/scheduling/v1beta1",
"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/v1",
"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/apis/clientauthentication/v1beta1",
"k8s.io/client-go/pkg/version",
"k8s.io/csi-api/pkg/apis/csi/v1alpha1",
"k8s.io/kube-aggregator/pkg/apis/apiregistration/v1",
"k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1",
"k8s.io/kube-controller-manager/config/v1alpha1",
"k8s.io/kube-proxy/config/v1alpha1",
"k8s.io/kube-scheduler/config/v1alpha1",
"k8s.io/kubelet/config/v1beta1",
"k8s.io/metrics/pkg/apis/custom_metrics/v1beta1",
"k8s.io/metrics/pkg/apis/custom_metrics/v1beta2",
"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] + ["//staging/src/%s:go_default_library" % target for target in vendor_targets]
go_library(
name = name,
srcs = srcs + [":zz_generated.openapi"],
importpath = go_prefix + "pkg/generated/openapi",
tags = tags,
deps = deps,
)
go_genrule(
name = "zz_generated.openapi",
srcs = ["//" + vendor_prefix + "hack/boilerplate:boilerplate.go.txt"],
outs = ["zz_generated.openapi.go"],
# In order for vendored dependencies to be imported correctly,
# the generator must run from the repo root inside the generated GOPATH.
# All of bazel's $(location)s are relative to the original working directory, however,
# so we must save it first.
cmd = " ".join([
"cd $$GOPATH/src/" + go_prefix + ";",
"$$GO_GENRULE_EXECROOT/$(location //vendor/k8s.io/kube-openapi/cmd/openapi-gen)",
"--v 1",
"--logtostderr",
"--go-header-file $$GO_GENRULE_EXECROOT/$(location //" + vendor_prefix + "hack/boilerplate:boilerplate.go.txt)",
"--output-file-base zz_generated.openapi",
"--output-package " + go_prefix + "pkg/generated/openapi",
"--report-filename tmp_api_violations.report",
"--input-dirs " + ",".join([go_prefix + target for target in openapi_targets] + [go_prefix + "vendor/" + target for target in vendor_targets]),
"&& cp $$GOPATH/src/" + go_prefix + "pkg/generated/openapi/zz_generated.openapi.go $$GO_GENRULE_EXECROOT/$(location :zz_generated.openapi.go)",
"&& rm tmp_api_violations.report",
]),
go_deps = deps,
tools = ["//vendor/k8s.io/kube-openapi/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"
...@@ -80,10 +78,9 @@ type Config struct { ...@@ -80,10 +78,9 @@ type Config 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 Config) New() (authenticator.Request, *spec.SecurityDefinitions, error) { func (config Config) New() (authenticator.Request, error) {
var authenticators []authenticator.Request var authenticators []authenticator.Request
var tokenAuthenticators []authenticator.Token var tokenAuthenticators []authenticator.Token
securityDefinitions := spec.SecurityDefinitions{}
// 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
...@@ -96,7 +93,7 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er ...@@ -96,7 +93,7 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er
config.RequestHeaderConfig.ExtraHeaderPrefixes, config.RequestHeaderConfig.ExtraHeaderPrefixes,
) )
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
authenticators = append(authenticators, authenticator.WrapAudienceAgnosticRequest(config.APIAudiences, requestHeaderAuthenticator)) authenticators = append(authenticators, authenticator.WrapAudienceAgnosticRequest(config.APIAudiences, requestHeaderAuthenticator))
} }
...@@ -105,23 +102,16 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er ...@@ -105,23 +102,16 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er
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, authenticator.WrapAudienceAgnosticRequest(config.APIAudiences, basicAuth)) authenticators = append(authenticators, authenticator.WrapAudienceAgnosticRequest(config.APIAudiences, basicAuth))
securityDefinitions["HTTPBasic"] = &spec.SecurityScheme{
SecuritySchemeProps: spec.SecuritySchemeProps{
Type: "basic",
Description: "HTTP Basic authentication",
},
}
} }
// 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)
} }
...@@ -130,21 +120,21 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er ...@@ -130,21 +120,21 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er
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, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, tokenAuth)) tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, tokenAuth))
} }
if len(config.ServiceAccountKeyFiles) > 0 { if len(config.ServiceAccountKeyFiles) > 0 {
serviceAccountAuth, err := newLegacyServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.APIAudiences, config.ServiceAccountTokenGetter) serviceAccountAuth, err := newLegacyServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.APIAudiences, config.ServiceAccountTokenGetter)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth) tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
} }
if utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) && config.ServiceAccountIssuer != "" { if utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) && config.ServiceAccountIssuer != "" {
serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountIssuer, config.ServiceAccountKeyFiles, config.APIAudiences, config.ServiceAccountTokenGetter) serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountIssuer, config.ServiceAccountKeyFiles, config.APIAudiences, config.ServiceAccountTokenGetter)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth) tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
} }
...@@ -174,14 +164,14 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er ...@@ -174,14 +164,14 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er
RequiredClaims: config.OIDCRequiredClaims, RequiredClaims: config.OIDCRequiredClaims,
}) })
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
tokenAuthenticators = append(tokenAuthenticators, oidcAuth) tokenAuthenticators = append(tokenAuthenticators, oidcAuth)
} }
if len(config.WebhookTokenAuthnConfigFile) > 0 { if len(config.WebhookTokenAuthnConfigFile) > 0 {
webhookTokenAuth, err := newWebhookTokenAuthenticator(config.WebhookTokenAuthnConfigFile, config.WebhookTokenAuthnCacheTTL, config.APIAudiences) webhookTokenAuth, err := newWebhookTokenAuthenticator(config.WebhookTokenAuthnConfigFile, config.WebhookTokenAuthnCacheTTL, config.APIAudiences)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
tokenAuthenticators = append(tokenAuthenticators, webhookTokenAuth) tokenAuthenticators = append(tokenAuthenticators, webhookTokenAuth)
} }
...@@ -194,21 +184,13 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er ...@@ -194,21 +184,13 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er
tokenAuth = tokencache.New(tokenAuth, true, config.TokenSuccessCacheTTL, config.TokenFailureCacheTTL) tokenAuth = tokencache.New(tokenAuth, true, 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
} }
return nil, &securityDefinitions, nil return nil, nil
} }
authenticator := union.New(authenticators...) authenticator := union.New(authenticators...)
...@@ -221,7 +203,7 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er ...@@ -221,7 +203,7 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er
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",
"//staging/src/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_test",
srcs = ["suite_test.go"],
data = [
"//api/openapi-spec:swagger-spec",
],
embed = [":go_default_library"],
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 %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", e)
}
// 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)
}
...@@ -44,11 +44,9 @@ import ( ...@@ -44,11 +44,9 @@ import (
"k8s.io/client-go/discovery" "k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic" "k8s.io/client-go/dynamic"
"k8s.io/klog" "k8s.io/klog"
oapi "k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/delete" "k8s.io/kubernetes/pkg/kubectl/cmd/delete"
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/scheme" "k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/kubectl/util/templates" "k8s.io/kubernetes/pkg/kubectl/util/templates"
...@@ -73,7 +71,6 @@ type ApplyOptions struct { ...@@ -73,7 +71,6 @@ type ApplyOptions struct {
cmdBaseName string cmdBaseName string
All bool All bool
Overwrite bool Overwrite bool
OpenApiPatch bool
PruneWhitelist []string PruneWhitelist []string
ShouldIncludeUninitialized bool ShouldIncludeUninitialized bool
...@@ -82,7 +79,6 @@ type ApplyOptions struct { ...@@ -82,7 +79,6 @@ type ApplyOptions struct {
Mapper meta.RESTMapper Mapper meta.RESTMapper
DynamicClient dynamic.Interface DynamicClient dynamic.Interface
DiscoveryClient discovery.DiscoveryInterface DiscoveryClient discovery.DiscoveryInterface
OpenAPISchema openapi.Resources
Namespace string Namespace string
EnforceNamespace bool EnforceNamespace bool
...@@ -133,7 +129,6 @@ func NewApplyOptions(ioStreams genericclioptions.IOStreams) *ApplyOptions { ...@@ -133,7 +129,6 @@ func NewApplyOptions(ioStreams genericclioptions.IOStreams) *ApplyOptions {
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme), PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
Overwrite: true, Overwrite: true,
OpenApiPatch: true,
Recorder: genericclioptions.NoopRecorder{}, Recorder: genericclioptions.NoopRecorder{},
...@@ -174,7 +169,6 @@ func NewCmdApply(baseName string, f cmdutil.Factory, ioStreams genericclioptions ...@@ -174,7 +169,6 @@ func NewCmdApply(baseName string, f cmdutil.Factory, ioStreams genericclioptions
cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
cmd.Flags().BoolVar(&o.All, "all", o.All, "Select all resources in the namespace of the specified resource types.") cmd.Flags().BoolVar(&o.All, "all", o.All, "Select all resources in the namespace of the specified resource types.")
cmd.Flags().StringArrayVar(&o.PruneWhitelist, "prune-whitelist", o.PruneWhitelist, "Overwrite the default whitelist with <group/version/kind> for --prune") cmd.Flags().StringArrayVar(&o.PruneWhitelist, "prune-whitelist", o.PruneWhitelist, "Overwrite the default whitelist with <group/version/kind> for --prune")
cmd.Flags().BoolVar(&o.OpenApiPatch, "openapi-patch", o.OpenApiPatch, "If true, use openapi to calculate diff when the openapi presents and the resource can be found in the openapi spec. Otherwise, fall back to use baked-in types.")
cmd.Flags().BoolVar(&o.ServerDryRun, "server-dry-run", o.ServerDryRun, "If true, request will be sent to server with dry-run flag, which means the modifications won't be persisted. This is an alpha feature and flag.") cmd.Flags().BoolVar(&o.ServerDryRun, "server-dry-run", o.ServerDryRun, "If true, request will be sent to server with dry-run flag, which means the modifications won't be persisted. This is an alpha feature and flag.")
cmdutil.AddDryRunFlag(cmd) cmdutil.AddDryRunFlag(cmd)
cmdutil.AddIncludeUninitializedFlag(cmd) cmdutil.AddIncludeUninitializedFlag(cmd)
...@@ -225,7 +219,6 @@ func (o *ApplyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { ...@@ -225,7 +219,6 @@ func (o *ApplyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
o.DeleteOptions = o.DeleteFlags.ToOptions(dynamicClient, o.IOStreams) o.DeleteOptions = o.DeleteFlags.ToOptions(dynamicClient, o.IOStreams)
o.ShouldIncludeUninitialized = cmdutil.ShouldIncludeUninitialized(cmd, o.Prune) o.ShouldIncludeUninitialized = cmdutil.ShouldIncludeUninitialized(cmd, o.Prune)
o.OpenAPISchema, _ = f.OpenAPISchema()
o.Validator, err = f.Validator(cmdutil.GetFlagBool(cmd, "validate")) o.Validator, err = f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
o.Builder = f.NewBuilder() o.Builder = f.NewBuilder()
o.Mapper, err = f.ToRESTMapper() o.Mapper, err = f.ToRESTMapper()
...@@ -295,14 +288,8 @@ func parsePruneResources(mapper meta.RESTMapper, gvks []string) ([]pruneResource ...@@ -295,14 +288,8 @@ func parsePruneResources(mapper meta.RESTMapper, gvks []string) ([]pruneResource
} }
func (o *ApplyOptions) Run() error { func (o *ApplyOptions) Run() error {
var openapiSchema openapi.Resources
if o.OpenApiPatch {
openapiSchema = o.OpenAPISchema
}
dryRunVerifier := &DryRunVerifier{ dryRunVerifier := &DryRunVerifier{
Finder: cmdutil.NewCRDFinder(cmdutil.CRDFromDynamic(o.DynamicClient)), Finder: cmdutil.NewCRDFinder(cmdutil.CRDFromDynamic(o.DynamicClient)),
OpenAPIGetter: o.DiscoveryClient,
} }
// include the uninitialized objects by default if --prune is true // include the uninitialized objects by default if --prune is true
...@@ -437,7 +424,6 @@ func (o *ApplyOptions) Run() error { ...@@ -437,7 +424,6 @@ func (o *ApplyOptions) Run() error {
Timeout: o.DeleteOptions.Timeout, Timeout: o.DeleteOptions.Timeout,
GracePeriod: o.DeleteOptions.GracePeriod, GracePeriod: o.DeleteOptions.GracePeriod,
ServerDryRun: o.ServerDryRun, ServerDryRun: o.ServerDryRun,
OpenapiSchema: openapiSchema,
Retries: maxPatchRetry, Retries: maxPatchRetry,
} }
...@@ -707,8 +693,6 @@ type Patcher struct { ...@@ -707,8 +693,6 @@ type Patcher struct {
// Number of retries to make if the patch fails with conflict // Number of retries to make if the patch fails with conflict
Retries int Retries int
OpenapiSchema openapi.Resources
} }
// DryRunVerifier verifies if a given group-version-kind supports DryRun // DryRunVerifier verifies if a given group-version-kind supports DryRun
...@@ -722,27 +706,14 @@ type Patcher struct { ...@@ -722,27 +706,14 @@ type Patcher struct {
// requires an extra round-trip to the server. // requires an extra round-trip to the server.
type DryRunVerifier struct { type DryRunVerifier struct {
Finder cmdutil.CRDFinder Finder cmdutil.CRDFinder
OpenAPIGetter discovery.OpenAPISchemaInterface
} }
// HasSupport verifies if the given gvk supports DryRun. An error is // HasSupport verifies if the given gvk supports DryRun. An error is
// returned if it doesn't. // returned if it doesn't.
func (v *DryRunVerifier) HasSupport(gvk schema.GroupVersionKind) error { func (v *DryRunVerifier) HasSupport(gvk schema.GroupVersionKind) error {
oapi, err := v.OpenAPIGetter.OpenAPISchema() supports, err := v.Finder.HasCRD(gvk.GroupKind())
if err != nil { if err != nil {
return fmt.Errorf("failed to download openapi: %v", err) return fmt.Errorf("failed to check CRD: %v", err)
}
supports, err := openapi.SupportsDryRun(oapi, gvk)
if err != nil {
// We assume that we couldn't find the type, then check for namespace:
supports, _ = openapi.SupportsDryRun(oapi, schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"})
// If namespace supports dryRun, then we will support dryRun for CRDs only.
if supports {
supports, err = v.Finder.HasCRD(gvk.GroupKind())
if err != nil {
return fmt.Errorf("failed to check CRD: %v", err)
}
}
} }
if !supports { if !supports {
return fmt.Errorf("%v doesn't support dry-run", gvk) return fmt.Errorf("%v doesn't support dry-run", gvk)
...@@ -782,7 +753,6 @@ func (p *Patcher) patchSimple(obj runtime.Object, modified []byte, source, names ...@@ -782,7 +753,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
...@@ -807,20 +777,6 @@ func (p *Patcher) patchSimple(obj runtime.Object, modified []byte, source, names ...@@ -807,20 +777,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 {
......
...@@ -45,11 +45,9 @@ import ( ...@@ -45,11 +45,9 @@ import (
"k8s.io/kubernetes/pkg/kubectl/cmd/create" "k8s.io/kubernetes/pkg/kubectl/cmd/create"
"k8s.io/kubernetes/pkg/kubectl/cmd/delete" "k8s.io/kubernetes/pkg/kubectl/cmd/delete"
"k8s.io/kubernetes/pkg/kubectl/cmd/describe" "k8s.io/kubernetes/pkg/kubectl/cmd/describe"
"k8s.io/kubernetes/pkg/kubectl/cmd/diff"
"k8s.io/kubernetes/pkg/kubectl/cmd/drain" "k8s.io/kubernetes/pkg/kubectl/cmd/drain"
"k8s.io/kubernetes/pkg/kubectl/cmd/edit" "k8s.io/kubernetes/pkg/kubectl/cmd/edit"
cmdexec "k8s.io/kubernetes/pkg/kubectl/cmd/exec" cmdexec "k8s.io/kubernetes/pkg/kubectl/cmd/exec"
"k8s.io/kubernetes/pkg/kubectl/cmd/explain"
"k8s.io/kubernetes/pkg/kubectl/cmd/expose" "k8s.io/kubernetes/pkg/kubectl/cmd/expose"
"k8s.io/kubernetes/pkg/kubectl/cmd/get" "k8s.io/kubernetes/pkg/kubectl/cmd/get"
"k8s.io/kubernetes/pkg/kubectl/cmd/label" "k8s.io/kubernetes/pkg/kubectl/cmd/label"
...@@ -457,7 +455,6 @@ func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command { ...@@ -457,7 +455,6 @@ func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command {
{ {
Message: "Basic Commands (Intermediate):", Message: "Basic Commands (Intermediate):",
Commands: []*cobra.Command{ Commands: []*cobra.Command{
explain.NewCmdExplain("kubectl", f, ioStreams),
get.NewCmdGet("kubectl", f, ioStreams), get.NewCmdGet("kubectl", f, ioStreams),
edit.NewCmdEdit(f, ioStreams), edit.NewCmdEdit(f, ioStreams),
delete.NewCmdDelete(f, ioStreams), delete.NewCmdDelete(f, ioStreams),
...@@ -500,7 +497,6 @@ func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command { ...@@ -500,7 +497,6 @@ func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command {
{ {
Message: "Advanced Commands:", Message: "Advanced Commands:",
Commands: []*cobra.Command{ Commands: []*cobra.Command{
diff.NewCmdDiff(f, ioStreams),
apply.NewCmdApply("kubectl", f, ioStreams), apply.NewCmdApply("kubectl", f, ioStreams),
patch.NewCmdPatch(f, ioStreams), patch.NewCmdPatch(f, ioStreams),
replace.NewCmdReplace(f, ioStreams), replace.NewCmdReplace(f, ioStreams),
......
/*
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 diff
import (
"bytes"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"testing"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/utils/exec"
)
type FakeObject struct {
name string
merged map[string]interface{}
live map[string]interface{}
}
var _ Object = &FakeObject{}
func (f *FakeObject) Name() string {
return f.name
}
func (f *FakeObject) Merged() (runtime.Object, error) {
return &unstructured.Unstructured{Object: f.merged}, nil
}
func (f *FakeObject) Live() runtime.Object {
return &unstructured.Unstructured{Object: f.live}
}
func TestDiffProgram(t *testing.T) {
os.Setenv("KUBECTL_EXTERNAL_DIFF", "echo")
streams, _, stdout, _ := genericclioptions.NewTestIOStreams()
diff := DiffProgram{
IOStreams: streams,
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 := &unstructured.Unstructured{Object: 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("MERGED")
if err != nil {
t.Fatal(err)
}
defer diff.Dir.Delete()
obj := FakeObject{
name: "bla",
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 := "merged: 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("LIVE", "MERGED")
if err != nil {
t.Fatal(err)
}
defer diff.TearDown()
obj := FakeObject{
name: "bla",
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 := "live: 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 = "merged: 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 explain
import (
"fmt"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
"k8s.io/kubernetes/pkg/kubectl/explain"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/kubectl/util/templates"
)
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`))
)
type ExplainOptions struct {
genericclioptions.IOStreams
CmdParent string
ApiVersion string
Recursive bool
Mapper meta.RESTMapper
Schema openapi.Resources
}
func NewExplainOptions(parent string, streams genericclioptions.IOStreams) *ExplainOptions {
return &ExplainOptions{
IOStreams: streams,
CmdParent: parent,
}
}
// NewCmdExplain returns a cobra command for swagger docs
func NewCmdExplain(parent string, f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
o := NewExplainOptions(parent, streams)
cmd := &cobra.Command{
Use: "explain RESOURCE",
DisableFlagsInUseLine: true,
Short: i18n.T("Documentation of resources"),
Long: explainLong + "\n\n" + cmdutil.SuggestApiResources(parent),
Example: explainExamples,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(f, cmd))
cmdutil.CheckErr(o.Validate(args))
cmdutil.CheckErr(o.Run(args))
},
}
cmd.Flags().BoolVar(&o.Recursive, "recursive", o.Recursive, "Print the fields of fields (Currently only 1 level deep)")
cmd.Flags().StringVar(&o.ApiVersion, "api-version", o.ApiVersion, "Get different explanations for particular API version")
return cmd
}
func (o *ExplainOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
var err error
o.Mapper, err = f.ToRESTMapper()
if err != nil {
return err
}
o.Schema, err = f.OpenAPISchema()
if err != nil {
return err
}
return nil
}
func (o *ExplainOptions) Validate(args []string) error {
if len(args) == 0 {
return fmt.Errorf("You must specify the type of resource to explain. %s\n", cmdutil.SuggestApiResources(o.CmdParent))
}
if len(args) > 1 {
return fmt.Errorf("We accept only this format: explain RESOURCE\n")
}
return nil
}
// Run executes the appropriate steps to print a model's documentation
func (o *ExplainOptions) Run(args []string) error {
recursive := o.Recursive
apiVersionString := o.ApiVersion
// 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], o.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, _ = o.Mapper.KindFor(*fullySpecifiedGVR)
}
if gvk.Empty() {
gvk, err = o.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)
}
schema := o.Schema.LookupResource(gvk)
if schema == nil {
return fmt.Errorf("Couldn't find resource for %q", gvk)
}
return explain.PrintModelDescription(fieldsPath, o.Out, schema, gvk, recursive)
}
...@@ -55,7 +55,6 @@ type GetOptions struct { ...@@ -55,7 +55,6 @@ type GetOptions struct {
PrintFlags *PrintFlags PrintFlags *PrintFlags
ToPrinter func(*meta.RESTMapping, bool, bool) (printers.ResourcePrinterFunc, error) ToPrinter func(*meta.RESTMapping, bool, bool) (printers.ResourcePrinterFunc, error)
IsHumanReadablePrinter bool IsHumanReadablePrinter bool
PrintWithOpenAPICols bool
CmdParent string CmdParent string
...@@ -231,11 +230,6 @@ func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri ...@@ -231,11 +230,6 @@ func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri
printFlags := o.PrintFlags.Copy() printFlags := o.PrintFlags.Copy()
if mapping != nil { if mapping != nil {
if !cmdSpecifiesOutputFmt(cmd) && o.PrintWithOpenAPICols {
if apiSchema, err := f.OpenAPISchema(); err == nil {
printFlags.UseOpenAPIColumns(apiSchema, mapping)
}
}
printFlags.SetKind(mapping.GroupVersionKind.GroupKind()) printFlags.SetKind(mapping.GroupVersionKind.GroupKind())
} }
if withNamespace { if withNamespace {
...@@ -401,11 +395,6 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e ...@@ -401,11 +395,6 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e
return o.watch(f, cmd, args) return o.watch(f, cmd, args)
} }
// openapi printing is mutually exclusive with server side printing
if o.PrintWithOpenAPICols && o.ServerPrint {
fmt.Fprintf(o.IOStreams.ErrOut, "warning: --%s requested, --%s will be ignored\n", useOpenAPIPrintColumnFlagLabel, useServerPrintColumns)
}
chunkSize := o.ChunkSize chunkSize := o.ChunkSize
if o.Sort { if o.Sort {
// TODO(juanvallejo): in the future, we could have the client use chunking // TODO(juanvallejo): in the future, we could have the client use chunking
...@@ -428,9 +417,6 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e ...@@ -428,9 +417,6 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e
Flatten(). Flatten().
TransformRequests(func(req *rest.Request) { TransformRequests(func(req *rest.Request) {
// We need full objects if printing with openapi columns // We need full objects if printing with openapi columns
if o.PrintWithOpenAPICols {
return
}
if !o.ServerPrint || !o.IsHumanReadablePrinter { if !o.ServerPrint || !o.IsHumanReadablePrinter {
return return
} }
...@@ -550,13 +536,6 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e ...@@ -550,13 +536,6 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e
lastMapping = mapping lastMapping = mapping
} }
// ensure a versioned object is passed to the custom-columns printer
// if we are using OpenAPI columns to print
if o.PrintWithOpenAPICols {
printer.PrintObj(info.Object, w)
continue
}
internalObj, err := legacyscheme.Scheme.ConvertToVersion(info.Object, info.Mapping.GroupVersionKind.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion()) internalObj, err := legacyscheme.Scheme.ConvertToVersion(info.Object, info.Mapping.GroupVersionKind.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion())
if err != nil { if err != nil {
// if there's an error, try to print what you have (mirrors old behavior). // if there's an error, try to print what you have (mirrors old behavior).
...@@ -839,7 +818,6 @@ func (o *GetOptions) printGeneric(r *resource.Result) error { ...@@ -839,7 +818,6 @@ func (o *GetOptions) printGeneric(r *resource.Result) error {
} }
func addOpenAPIPrintColumnFlags(cmd *cobra.Command, opt *GetOptions) { func addOpenAPIPrintColumnFlags(cmd *cobra.Command, opt *GetOptions) {
cmd.Flags().BoolVar(&opt.PrintWithOpenAPICols, useOpenAPIPrintColumnFlagLabel, opt.PrintWithOpenAPICols, "If true, use x-kubernetes-print-column metadata (if present) from the OpenAPI schema for displaying a resource.")
cmd.Flags().MarkDeprecated(useOpenAPIPrintColumnFlagLabel, "deprecated in favor of server-side printing") cmd.Flags().MarkDeprecated(useOpenAPIPrintColumnFlagLabel, "deprecated in favor of server-side printing")
} }
...@@ -851,10 +829,6 @@ func shouldGetNewPrinterForMapping(printer printers.ResourcePrinter, lastMapping ...@@ -851,10 +829,6 @@ func shouldGetNewPrinterForMapping(printer printers.ResourcePrinter, lastMapping
return printer == nil || lastMapping == nil || mapping == nil || mapping.Resource != lastMapping.Resource return printer == nil || lastMapping == nil || mapping == nil || mapping.Resource != lastMapping.Resource
} }
func cmdSpecifiesOutputFmt(cmd *cobra.Command) bool {
return cmdutil.GetFlagString(cmd, "output") != ""
}
func maybeWrapSortingPrinter(printer printers.ResourcePrinter, sortBy string) printers.ResourcePrinter { func maybeWrapSortingPrinter(printer printers.ResourcePrinter, sortBy string) printers.ResourcePrinter {
if len(sortBy) != 0 { if len(sortBy) != 0 {
return &SortingPrinter{ return &SortingPrinter{
......
...@@ -17,15 +17,11 @@ limitations under the License. ...@@ -17,15 +17,11 @@ limitations under the License.
package get package get
import ( import (
"strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/printers" "k8s.io/cli-runtime/pkg/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
) )
// PrintFlags composes common printer flag structs // PrintFlags composes common printer flag structs
...@@ -73,34 +69,6 @@ func (f *PrintFlags) AllowedFormats() []string { ...@@ -73,34 +69,6 @@ func (f *PrintFlags) AllowedFormats() []string {
return formats return formats
} }
// UseOpenAPIColumns modifies the output format, as well as the
// "allowMissingKeys" option for template printers, to values
// defined in the OpenAPI schema of a resource.
func (f *PrintFlags) UseOpenAPIColumns(api openapi.Resources, mapping *meta.RESTMapping) error {
// Found openapi metadata for this resource
schema := api.LookupResource(mapping.GroupVersionKind)
if schema == nil {
// Schema not found, return empty columns
return nil
}
columns, found := openapi.GetPrintColumns(schema.GetExtensions())
if !found {
// Extension not found, return empty columns
return nil
}
parts := strings.SplitN(columns, "=", 2)
if len(parts) < 2 {
return nil
}
allowMissingKeys := true
f.OutputFormat = &parts[0]
f.TemplateFlags.TemplateArgument = &parts[1]
f.TemplateFlags.AllowMissingKeys = &allowMissingKeys
return nil
}
// ToPrinter attempts to find a composed set of PrintFlags suitable for // ToPrinter attempts to find a composed set of PrintFlags suitable for
// returning a printer based on current flag values. // returning a printer based on current flag values.
......
...@@ -23,7 +23,6 @@ import ( ...@@ -23,7 +23,6 @@ import (
"k8s.io/client-go/dynamic" "k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
"k8s.io/kubernetes/pkg/kubectl/validation" "k8s.io/kubernetes/pkg/kubectl/validation"
) )
...@@ -61,6 +60,4 @@ type Factory interface { ...@@ -61,6 +60,4 @@ type Factory 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)
} }
...@@ -19,8 +19,6 @@ limitations under the License. ...@@ -19,8 +19,6 @@ limitations under the License.
package util package util
import ( import (
"sync"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
...@@ -30,21 +28,11 @@ import ( ...@@ -30,21 +28,11 @@ import (
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
openapivalidation "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/validation"
"k8s.io/kubernetes/pkg/kubectl/validation" "k8s.io/kubernetes/pkg/kubectl/validation"
) )
type factoryImpl struct { type factoryImpl struct {
clientGetter genericclioptions.RESTClientGetter clientGetter genericclioptions.RESTClientGetter
// openAPIGetter loads and caches openapi specs
openAPIGetter openAPIGetter
}
type openAPIGetter struct {
once sync.Once
getter openapi.Getter
} }
func NewFactory(clientGetter genericclioptions.RESTClientGetter) Factory { func NewFactory(clientGetter genericclioptions.RESTClientGetter) Factory {
...@@ -148,30 +136,7 @@ func (f *factoryImpl) Validator(validate bool) (validation.Schema, error) { ...@@ -148,30 +136,7 @@ func (f *factoryImpl) Validator(validate bool) (validation.Schema, error) {
return validation.NullSchema{}, nil return validation.NullSchema{}, nil
} }
resources, err := f.OpenAPISchema()
if err != nil {
return nil, err
}
return validation.ConjunctiveSchema{ return validation.ConjunctiveSchema{
openapivalidation.NewSchemaValidation(resources),
validation.NoDoubleKeySchema{}, validation.NoDoubleKeySchema{},
}, nil }, nil
} }
// OpenAPISchema returns metadata and structural information about Kubernetes object definitions.
func (f *factoryImpl) OpenAPISchema() (openapi.Resources, error) {
discovery, err := f.clientGetter.ToDiscoveryClient()
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",
"dryrun.go",
"extensions.go",
"openapi.go",
"openapi_getter.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi",
deps = [
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/client-go/discovery:go_default_library",
"//vendor/github.com/go-openapi/spec:go_default_library",
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
"//vendor/gopkg.in/yaml.v2:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
],
)
go_test(
name = "go_default_test",
size = "small",
srcs = [
"dryrun_test.go",
"openapi_getter_test.go",
"openapi_suite_test.go",
"openapi_test.go",
],
data = ["//api/openapi-spec:swagger-spec"],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/github.com/googleapis/gnostic/OpenAPIv2: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/kube-openapi/pkg/util/proto:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util/proto/testing: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 // k8s.io/kubernetes/pkg/kubectl/cmd/util/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 (
"errors"
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
yaml "gopkg.in/yaml.v2"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func hasGVKExtension(extensions []*openapi_v2.NamedAny, gvk schema.GroupVersionKind) bool {
for _, extension := range extensions {
if extension.GetValue().GetYaml() == "" ||
extension.GetName() != "x-kubernetes-group-version-kind" {
continue
}
var value map[string]string
err := yaml.Unmarshal([]byte(extension.GetValue().GetYaml()), &value)
if err != nil {
continue
}
if value["group"] == gvk.Group && value["kind"] == gvk.Kind && value["version"] == gvk.Version {
return true
}
return false
}
return false
}
// SupportsDryRun is a method that let's us look in the OpenAPI if the
// specific group-version-kind supports the dryRun query parameter for
// the PATCH end-point.
func SupportsDryRun(doc *openapi_v2.Document, gvk schema.GroupVersionKind) (bool, error) {
for _, path := range doc.GetPaths().GetPath() {
// Is this describing the gvk we're looking for?
if !hasGVKExtension(path.GetValue().GetPatch().GetVendorExtension(), gvk) {
continue
}
for _, param := range path.GetValue().GetPatch().GetParameters() {
if param.GetParameter().GetNonBodyParameter().GetQueryParameterSubSchema().GetName() == "dryRun" {
return true, nil
}
}
return false, nil
}
return false, errors.New("couldn't find GVK in openapi")
}
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package openapi_test
import (
"testing"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
)
func TestSupportsDryRun(t *testing.T) {
doc, err := fakeSchema.OpenAPISchema()
if err != nil {
t.Fatalf("Failed to get OpenAPI Schema: %v", err)
}
tests := []struct {
gvk schema.GroupVersionKind
success bool
supports bool
}{
{
gvk: schema.GroupVersionKind{
Group: "",
Version: "v1",
Kind: "Pod",
},
success: true,
supports: true,
},
{
gvk: schema.GroupVersionKind{
Group: "",
Version: "v1",
Kind: "UnknownKind",
},
success: false,
supports: false,
},
{
gvk: schema.GroupVersionKind{
Group: "",
Version: "v1",
Kind: "NodeProxyOptions",
},
success: true,
supports: false,
},
}
for _, test := range tests {
supports, err := openapi.SupportsDryRun(doc, test.gvk)
if supports != test.supports || ((err == nil) != test.success) {
errStr := "nil"
if test.success == false {
errStr = "err"
}
t.Errorf("SupportsDryRun(doc, %v) = (%v, %v), expected (%v, %v)",
test.gvk,
supports, err,
test.supports, errStr,
)
}
}
}
/*
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"
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
)
// FakeCounter returns a "null" document and the specified error. It
// also counts how many times the OpenAPISchema method has been called.
type FakeCounter struct {
Calls int
Err error
}
func (f *FakeCounter) OpenAPISchema() (*openapi_v2.Document, error) {
f.Calls = f.Calls + 1
return nil, f.Err
}
var _ = Describe("Getting the Resources", func() {
var client FakeCounter
var instance openapi.Getter
var expectedData openapi.Resources
BeforeEach(func() {
client = FakeCounter{}
instance = openapi.NewOpenAPIGetter(&client)
var err error
expectedData, err = openapi.NewOpenAPIData(nil)
Expect(err).To(BeNil())
})
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/kube-openapi/pkg/util/proto/testing"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
)
var fakeSchema = testing.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",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util/proto/testing: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 (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kube-openapi/pkg/util/proto/testing"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
)
// 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 testing.Fake
}
var _ openapi.Resources = &FakeResources{}
// NewFakeResources creates a new FakeResources.
func NewFakeResources(path string) *FakeResources {
return &FakeResources{
fake: testing.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",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/json:go_default_library",
"//staging/src/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/kubectl/cmd/util/openapi:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/errors: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/kube-openapi/pkg/util/proto/testing: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"
"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 (gvk == schema.GroupVersionKind{Version: "v1", 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/kube-openapi/pkg/util/proto/testing"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
)
var fakeSchema = testing.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]"))
})
It("is fine with crd resource with List as a suffix kind name, which may not be a list of resources", func() {
err := validator.ValidateBytes([]byte(`
apiVersion: fake.com/v1
kind: FakeList
metadata:
name: fake
spec:
foo: bar
`))
Expect(err).To(BeNil())
})
})
/*
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/apimachinery/pkg/api/meta/testrestmapper"
"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 := testrestmapper.TestOnlyStaticRESTMapper(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotInResource, gotFieldsPath, err := SplitAndParseResourceRequest(tt.inresource, mapper)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(tt.expectedInResource, gotInResource) && !tt.expectedErr {
t.Errorf("%s: expected inresource: %s, got: %s", tt.name, tt.expectedInResource, gotInResource)
}
if !reflect.DeepEqual(tt.expectedFieldsPath, gotFieldsPath) && !tt.expectedErr {
t.Errorf("%s: expected fieldsPath: %s, got: %s", tt.name, tt.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 {
name string
path []string
err string
expectedPath string
}{
{
name: "test1",
path: []string{},
expectedPath: "OneKind",
},
{
name: "test2",
path: []string{"field1"},
expectedPath: "OneKind.field1",
},
{
name: "test3",
path: []string{"field1", "array"},
expectedPath: "OtherKind.array",
},
{
name: "test4",
path: []string{"field1", "what?"},
err: `field "what?" does not exist`,
},
{
name: "test5",
path: []string{"field1", ""},
err: `field "" does not exist`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
path, err := LookupSchemaForField(schema, tt.path)
gotErr := ""
if err != nil {
gotErr = err.Error()
}
gotPath := ""
if path != nil {
gotPath = path.GetPath().String()
}
if gotErr != tt.err || gotPath != tt.expectedPath {
t.Errorf("LookupSchemaForField(schema, %v) = (path: %q, err: %q), expected (path: %q, err: %q)",
tt.path, gotPath, gotErr, tt.expectedPath, tt.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{}
var visitedReferences = map[string]struct{}{}
// 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) {
if _, ok := visitedReferences[r.Reference()]; ok {
return
}
visitedReferences[r.Reference()] = struct{}{}
r.SubSchema().Accept(f)
delete(visitedReferences, r.Reference())
}
// 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"
tst "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing"
)
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)
}
}
func TestRecursiveFieldsWithSelfReferenceObjects(t *testing.T) {
var resources = tst.NewFakeResources("test-recursive-swagger.json")
schema := resources.LookupResource(schema.GroupVersionKind{
Group: "",
Version: "v2",
Kind: "OneKind",
})
if schema == nil {
t.Fatal("Couldn't find schema v2.OneKind")
}
want := `field1 <Object>
referencefield <Object>
referencesarray <[]Object>
field2 <Object>
reference <Object>
referencefield <Object>
referencesarray <[]Object>
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 {
name string
path []string
expected string
}{
{
// Kind is "Object"
name: "test1",
path: []string{},
expected: "Object",
},
{
// Reference is equal to pointed type "Object"
name: "test2",
path: []string{"field1"},
expected: "Object",
},
{
// Reference is equal to pointed type "string"
name: "test3",
path: []string{"field1", "primitive"},
expected: "string",
},
{
// Array of object of reference to string
name: "test4",
path: []string{"field2"},
expected: "[]map[string]string",
},
{
// Array of integer
name: "test5",
path: []string{"field1", "array"},
expected: "[]integer",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := LookupSchemaForField(schema, tt.path)
if err != nil {
t.Fatalf("Invalid tt.path %v: %v", tt.path, err)
}
got := GetTypeName(s)
if got != tt.expected {
t.Errorf("Got %q, expected %q", got, tt.expected)
}
})
}
}
...@@ -30,7 +30,6 @@ import ( ...@@ -30,7 +30,6 @@ import (
"k8s.io/apiserver/pkg/util/webhook" "k8s.io/apiserver/pkg/util/webhook"
"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"
) )
...@@ -507,12 +506,6 @@ func ValidateCustomResourceDefinitionValidation(customResourceValidation *apiext ...@@ -507,12 +506,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,9 +25,6 @@ import ( ...@@ -25,9 +25,6 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
"k8s.io/klog" "k8s.io/klog"
apiequality "k8s.io/apimachinery/pkg/api/equality" apiequality "k8s.io/apimachinery/pkg/api/equality"
...@@ -58,7 +55,6 @@ import ( ...@@ -58,7 +55,6 @@ import (
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
"k8s.io/apiextensions-apiserver/pkg/apiserver/conversion" "k8s.io/apiextensions-apiserver/pkg/apiserver/conversion"
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/establish" "k8s.io/apiextensions-apiserver/pkg/controller/establish"
...@@ -462,18 +458,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource ...@@ -462,18 +458,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource
typer := newUnstructuredObjectTyper(parameterScheme) typer := newUnstructuredObjectTyper(parameterScheme)
creator := unstructuredCreator{} creator := unstructuredCreator{}
validationSchema, err := getSchemaForVersion(crd, v.Name)
if err != nil {
utilruntime.HandleError(err)
return nil, fmt.Errorf("the server could not properly serve the CR schema")
}
validator, _, err := apiservervalidation.NewSchemaValidator(validationSchema)
if err != nil {
return nil, err
}
var statusSpec *apiextensions.CustomResourceSubresourceStatus var statusSpec *apiextensions.CustomResourceSubresourceStatus
var statusValidator *validate.SchemaValidator
subresources, err := getSubresourcesForVersion(crd, v.Name) subresources, err := getSubresourcesForVersion(crd, v.Name)
if err != nil { if err != nil {
utilruntime.HandleError(err) utilruntime.HandleError(err)
...@@ -481,16 +466,6 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource ...@@ -481,16 +466,6 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource
} }
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) && subresources != nil && subresources.Status != nil { if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) && subresources != nil && subresources.Status != nil {
statusSpec = subresources.Status statusSpec = subresources.Status
// for the status subresource, validate only against the status schema
if validationSchema != nil && validationSchema.OpenAPIV3Schema != nil && validationSchema.OpenAPIV3Schema.Properties != nil {
if statusSchema, ok := validationSchema.OpenAPIV3Schema.Properties["status"]; ok {
openapiSchema := &spec.Schema{}
if err := apiservervalidation.ConvertJSONSchemaProps(&statusSchema, openapiSchema); err != nil {
return nil, err
}
statusValidator = validate.NewSchemaValidator(openapiSchema, nil, "", strfmt.Default)
}
}
} }
var scaleSpec *apiextensions.CustomResourceSubresourceScale var scaleSpec *apiextensions.CustomResourceSubresourceScale
...@@ -516,8 +491,6 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource ...@@ -516,8 +491,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"],
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/validation",
importpath = "k8s.io/apiextensions-apiserver/pkg/apiserver/validation",
deps = [
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
"//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",
],
)
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 = [
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/apitesting/fuzzer:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/json:go_default_library",
"//vendor/github.com/go-openapi/spec:go_default_library",
],
)
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