mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 10:33:35 +00:00
vendor update for CSI 0.3.0
This commit is contained in:
72
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/BUILD
generated
vendored
Normal file
72
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/BUILD
generated
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"builder_flags.go",
|
||||
"builder_flags_fake.go",
|
||||
"config_flags.go",
|
||||
"config_flags_fake.go",
|
||||
"doc.go",
|
||||
"filename_flags.go",
|
||||
"io_options.go",
|
||||
"json_yaml_flags.go",
|
||||
"jsonpath_flags.go",
|
||||
"kube_template_flags.go",
|
||||
"name_flags.go",
|
||||
"print_flags.go",
|
||||
"record_flags.go",
|
||||
"template_flags.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/kubectl/genericclioptions",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/kubectl/genericclioptions/printers:go_default_library",
|
||||
"//pkg/kubectl/genericclioptions/resource:go_default_library",
|
||||
"//vendor/github.com/evanphx/json-patch:go_default_library",
|
||||
"//vendor/github.com/spf13/cobra:go_default_library",
|
||||
"//vendor/github.com/spf13/pflag:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
|
||||
"//vendor/k8s.io/client-go/discovery:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
"//vendor/k8s.io/client-go/restmapper:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/homedir:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//pkg/kubectl/genericclioptions/printers:all-srcs",
|
||||
"//pkg/kubectl/genericclioptions/resource:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"json_yaml_flags_test.go",
|
||||
"jsonpath_flags_test.go",
|
||||
"name_flags_test.go",
|
||||
"template_flags_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
],
|
||||
)
|
233
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/builder_flags.go
generated
vendored
Normal file
233
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/builder_flags.go
generated
vendored
Normal file
@ -0,0 +1,233 @@
|
||||
/*
|
||||
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 genericclioptions
|
||||
|
||||
import (
|
||||
"github.com/spf13/pflag"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
|
||||
)
|
||||
|
||||
// ResourceBuilderFlags are flags for finding resources
|
||||
// TODO(juanvallejo): wire --local flag from commands through
|
||||
type ResourceBuilderFlags struct {
|
||||
FileNameFlags *FileNameFlags
|
||||
|
||||
LabelSelector *string
|
||||
FieldSelector *string
|
||||
AllNamespaces *bool
|
||||
All *bool
|
||||
Local *bool
|
||||
IncludeUninitialized *bool
|
||||
|
||||
Scheme *runtime.Scheme
|
||||
Latest bool
|
||||
StopOnFirstError bool
|
||||
}
|
||||
|
||||
// NewResourceBuilderFlags returns a default ResourceBuilderFlags
|
||||
func NewResourceBuilderFlags() *ResourceBuilderFlags {
|
||||
filenames := []string{}
|
||||
|
||||
return &ResourceBuilderFlags{
|
||||
FileNameFlags: &FileNameFlags{
|
||||
Usage: "identifying the resource.",
|
||||
Filenames: &filenames,
|
||||
Recursive: boolPtr(true),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (o *ResourceBuilderFlags) WithFile(recurse bool, files ...string) *ResourceBuilderFlags {
|
||||
o.FileNameFlags = &FileNameFlags{
|
||||
Usage: "identifying the resource.",
|
||||
Filenames: &files,
|
||||
Recursive: boolPtr(recurse),
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *ResourceBuilderFlags) WithLabelSelector(selector string) *ResourceBuilderFlags {
|
||||
o.LabelSelector = &selector
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *ResourceBuilderFlags) WithFieldSelector(selector string) *ResourceBuilderFlags {
|
||||
o.FieldSelector = &selector
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *ResourceBuilderFlags) WithAllNamespaces(defaultVal bool) *ResourceBuilderFlags {
|
||||
o.AllNamespaces = &defaultVal
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *ResourceBuilderFlags) WithAll(defaultVal bool) *ResourceBuilderFlags {
|
||||
o.All = &defaultVal
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *ResourceBuilderFlags) WithLocal(defaultVal bool) *ResourceBuilderFlags {
|
||||
o.Local = &defaultVal
|
||||
return o
|
||||
}
|
||||
|
||||
// WithUninitialized is using an alpha feature and may be dropped
|
||||
func (o *ResourceBuilderFlags) WithUninitialized(defaultVal bool) *ResourceBuilderFlags {
|
||||
o.IncludeUninitialized = &defaultVal
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *ResourceBuilderFlags) WithScheme(scheme *runtime.Scheme) *ResourceBuilderFlags {
|
||||
o.Scheme = scheme
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *ResourceBuilderFlags) WithLatest() *ResourceBuilderFlags {
|
||||
o.Latest = true
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *ResourceBuilderFlags) StopOnError() *ResourceBuilderFlags {
|
||||
o.StopOnFirstError = true
|
||||
return o
|
||||
}
|
||||
|
||||
// AddFlags registers flags for finding resources
|
||||
func (o *ResourceBuilderFlags) AddFlags(flagset *pflag.FlagSet) {
|
||||
o.FileNameFlags.AddFlags(flagset)
|
||||
|
||||
if o.LabelSelector != nil {
|
||||
flagset.StringVarP(o.LabelSelector, "selector", "l", *o.LabelSelector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
|
||||
}
|
||||
if o.FieldSelector != nil {
|
||||
flagset.StringVar(o.FieldSelector, "field-selector", *o.FieldSelector, "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type.")
|
||||
}
|
||||
if o.AllNamespaces != nil {
|
||||
flagset.BoolVar(o.AllNamespaces, "all-namespaces", *o.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.")
|
||||
}
|
||||
if o.All != nil {
|
||||
flagset.BoolVar(o.All, "all", *o.All, "Select all resources in the namespace of the specified resource types")
|
||||
}
|
||||
if o.Local != nil {
|
||||
flagset.BoolVar(o.Local, "local", *o.Local, "If true, annotation will NOT contact api-server but run locally.")
|
||||
}
|
||||
if o.IncludeUninitialized != nil {
|
||||
flagset.BoolVar(o.IncludeUninitialized, "include-uninitialized", *o.IncludeUninitialized, `If true, the kubectl command applies to uninitialized objects. If explicitly set to false, this flag overrides other flags that make the kubectl commands apply to uninitialized objects, e.g., "--all". Objects with empty metadata.initializers are regarded as initialized.`)
|
||||
}
|
||||
}
|
||||
|
||||
// ToBuilder gives you back a resource finder to visit resources that are located
|
||||
func (o *ResourceBuilderFlags) ToBuilder(restClientGetter RESTClientGetter, resources []string) ResourceFinder {
|
||||
namespace, enforceNamespace, namespaceErr := restClientGetter.ToRawKubeConfigLoader().Namespace()
|
||||
|
||||
builder := resource.NewBuilder(restClientGetter).
|
||||
NamespaceParam(namespace).DefaultNamespace()
|
||||
|
||||
if o.Scheme != nil {
|
||||
builder.WithScheme(o.Scheme, o.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
} else {
|
||||
builder.Unstructured()
|
||||
}
|
||||
|
||||
if o.FileNameFlags != nil {
|
||||
opts := o.FileNameFlags.ToOptions()
|
||||
builder.FilenameParam(enforceNamespace, &opts)
|
||||
}
|
||||
|
||||
if o.Local == nil || !*o.Local {
|
||||
// resource type/name tuples only work non-local
|
||||
if o.All != nil {
|
||||
builder.ResourceTypeOrNameArgs(*o.All, resources...)
|
||||
} else {
|
||||
builder.ResourceTypeOrNameArgs(false, resources...)
|
||||
}
|
||||
// label selectors only work non-local (for now)
|
||||
if o.LabelSelector != nil {
|
||||
builder.LabelSelectorParam(*o.LabelSelector)
|
||||
}
|
||||
// field selectors only work non-local (forever)
|
||||
if o.FieldSelector != nil {
|
||||
builder.FieldSelectorParam(*o.FieldSelector)
|
||||
}
|
||||
// latest only works non-local (forever)
|
||||
if o.Latest {
|
||||
builder.Latest()
|
||||
}
|
||||
|
||||
} else {
|
||||
builder.Local()
|
||||
|
||||
if len(resources) > 0 {
|
||||
builder.AddError(resource.LocalResourceError)
|
||||
}
|
||||
}
|
||||
|
||||
if o.IncludeUninitialized != nil {
|
||||
builder.IncludeUninitialized(*o.IncludeUninitialized)
|
||||
}
|
||||
|
||||
if !o.StopOnFirstError {
|
||||
builder.ContinueOnError()
|
||||
}
|
||||
|
||||
return &ResourceFindBuilderWrapper{
|
||||
builder: builder.
|
||||
Flatten(). // I think we're going to recommend this everywhere
|
||||
AddError(namespaceErr),
|
||||
}
|
||||
}
|
||||
|
||||
// ResourceFindBuilderWrapper wraps a builder in an interface
|
||||
type ResourceFindBuilderWrapper struct {
|
||||
builder *resource.Builder
|
||||
}
|
||||
|
||||
// Do finds you resources to check
|
||||
func (b *ResourceFindBuilderWrapper) Do() resource.Visitor {
|
||||
return b.builder.Do()
|
||||
}
|
||||
|
||||
// ResourceFinder allows mocking the resource builder
|
||||
// TODO resource builders needs to become more interfacey
|
||||
type ResourceFinder interface {
|
||||
Do() resource.Visitor
|
||||
}
|
||||
|
||||
// ResourceFinderFunc is a handy way to make a ResourceFinder
|
||||
type ResourceFinderFunc func() resource.Visitor
|
||||
|
||||
// Do implements ResourceFinder
|
||||
func (fn ResourceFinderFunc) Do() resource.Visitor {
|
||||
return fn()
|
||||
}
|
||||
|
||||
// ResourceFinderForResult skins a visitor for re-use as a ResourceFinder
|
||||
func ResourceFinderForResult(result resource.Visitor) ResourceFinder {
|
||||
return ResourceFinderFunc(func() resource.Visitor {
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
func strPtr(val string) *string {
|
||||
return &val
|
||||
}
|
||||
|
||||
func boolPtr(val bool) *bool {
|
||||
return &val
|
||||
}
|
54
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/builder_flags_fake.go
generated
vendored
Normal file
54
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/builder_flags_fake.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
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 genericclioptions
|
||||
|
||||
import (
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
|
||||
)
|
||||
|
||||
// NewSimpleResourceFinder builds a super simple ResourceFinder that just iterates over the objects you provided
|
||||
func NewSimpleFakeResourceFinder(infos ...*resource.Info) ResourceFinder {
|
||||
return &fakeResourceFinder{
|
||||
Infos: infos,
|
||||
}
|
||||
}
|
||||
|
||||
type fakeResourceFinder struct {
|
||||
Infos []*resource.Info
|
||||
}
|
||||
|
||||
// Do implements the interface
|
||||
func (f *fakeResourceFinder) Do() resource.Visitor {
|
||||
return &fakeResourceResult{
|
||||
Infos: f.Infos,
|
||||
}
|
||||
}
|
||||
|
||||
type fakeResourceResult struct {
|
||||
Infos []*resource.Info
|
||||
}
|
||||
|
||||
// Visit just iterates over info
|
||||
func (r *fakeResourceResult) Visit(fn resource.VisitorFunc) error {
|
||||
for _, info := range r.Infos {
|
||||
err := fn(info, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
326
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/config_flags.go
generated
vendored
Normal file
326
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/config_flags.go
generated
vendored
Normal file
@ -0,0 +1,326 @@
|
||||
/*
|
||||
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 genericclioptions
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/restmapper"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/client-go/util/homedir"
|
||||
)
|
||||
|
||||
const (
|
||||
flagClusterName = "cluster"
|
||||
flagAuthInfoName = "user"
|
||||
flagContext = "context"
|
||||
flagNamespace = "namespace"
|
||||
flagAPIServer = "server"
|
||||
flagInsecure = "insecure-skip-tls-verify"
|
||||
flagCertFile = "client-certificate"
|
||||
flagKeyFile = "client-key"
|
||||
flagCAFile = "certificate-authority"
|
||||
flagBearerToken = "token"
|
||||
flagImpersonate = "as"
|
||||
flagImpersonateGroup = "as-group"
|
||||
flagUsername = "username"
|
||||
flagPassword = "password"
|
||||
flagTimeout = "request-timeout"
|
||||
flagHTTPCacheDir = "cache-dir"
|
||||
)
|
||||
|
||||
var defaultCacheDir = filepath.Join(homedir.HomeDir(), ".kube", "http-cache")
|
||||
|
||||
// RESTClientGetter is an interface that the ConfigFlags describe to provide an easier way to mock for commands
|
||||
// and eliminate the direct coupling to a struct type. Users may wish to duplicate this type in their own packages
|
||||
// as per the golang type overlapping.
|
||||
type RESTClientGetter interface {
|
||||
// ToRESTConfig returns restconfig
|
||||
ToRESTConfig() (*rest.Config, error)
|
||||
// ToDiscoveryClient returns discovery client
|
||||
ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error)
|
||||
// ToRESTMapper returns a restmapper
|
||||
ToRESTMapper() (meta.RESTMapper, error)
|
||||
// ToRawKubeConfigLoader return kubeconfig loader as-is
|
||||
ToRawKubeConfigLoader() clientcmd.ClientConfig
|
||||
}
|
||||
|
||||
var _ RESTClientGetter = &ConfigFlags{}
|
||||
|
||||
// ConfigFlags composes the set of values necessary
|
||||
// for obtaining a REST client config
|
||||
type ConfigFlags struct {
|
||||
CacheDir *string
|
||||
KubeConfig *string
|
||||
|
||||
// config flags
|
||||
ClusterName *string
|
||||
AuthInfoName *string
|
||||
Context *string
|
||||
Namespace *string
|
||||
APIServer *string
|
||||
Insecure *bool
|
||||
CertFile *string
|
||||
KeyFile *string
|
||||
CAFile *string
|
||||
BearerToken *string
|
||||
Impersonate *string
|
||||
ImpersonateGroup *[]string
|
||||
Username *string
|
||||
Password *string
|
||||
Timeout *string
|
||||
}
|
||||
|
||||
// ToRESTConfig implements RESTClientGetter.
|
||||
// Returns a REST client configuration based on a provided path
|
||||
// to a .kubeconfig file, loading rules, and config flag overrides.
|
||||
// Expects the AddFlags method to have been called.
|
||||
func (f *ConfigFlags) ToRESTConfig() (*rest.Config, error) {
|
||||
return f.ToRawKubeConfigLoader().ClientConfig()
|
||||
}
|
||||
|
||||
// ToRawKubeConfigLoader binds config flag values to config overrides
|
||||
// Returns an interactive clientConfig if the password flag is enabled,
|
||||
// or a non-interactive clientConfig otherwise.
|
||||
func (f *ConfigFlags) ToRawKubeConfigLoader() clientcmd.ClientConfig {
|
||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
// use the standard defaults for this client command
|
||||
// DEPRECATED: remove and replace with something more accurate
|
||||
loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
|
||||
|
||||
if f.KubeConfig != nil {
|
||||
loadingRules.ExplicitPath = *f.KubeConfig
|
||||
}
|
||||
|
||||
overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmd.ClusterDefaults}
|
||||
|
||||
// bind auth info flag values to overrides
|
||||
if f.CertFile != nil {
|
||||
overrides.AuthInfo.ClientCertificate = *f.CertFile
|
||||
}
|
||||
if f.KeyFile != nil {
|
||||
overrides.AuthInfo.ClientKey = *f.KeyFile
|
||||
}
|
||||
if f.BearerToken != nil {
|
||||
overrides.AuthInfo.Token = *f.BearerToken
|
||||
}
|
||||
if f.Impersonate != nil {
|
||||
overrides.AuthInfo.Impersonate = *f.Impersonate
|
||||
}
|
||||
if f.ImpersonateGroup != nil {
|
||||
overrides.AuthInfo.ImpersonateGroups = *f.ImpersonateGroup
|
||||
}
|
||||
if f.Username != nil {
|
||||
overrides.AuthInfo.Username = *f.Username
|
||||
}
|
||||
if f.Password != nil {
|
||||
overrides.AuthInfo.Password = *f.Password
|
||||
}
|
||||
|
||||
// bind cluster flags
|
||||
if f.APIServer != nil {
|
||||
overrides.ClusterInfo.Server = *f.APIServer
|
||||
}
|
||||
if f.CAFile != nil {
|
||||
overrides.ClusterInfo.CertificateAuthority = *f.CAFile
|
||||
}
|
||||
if f.Insecure != nil {
|
||||
overrides.ClusterInfo.InsecureSkipTLSVerify = *f.Insecure
|
||||
}
|
||||
|
||||
// bind context flags
|
||||
if f.Context != nil {
|
||||
overrides.CurrentContext = *f.Context
|
||||
}
|
||||
if f.ClusterName != nil {
|
||||
overrides.Context.Cluster = *f.ClusterName
|
||||
}
|
||||
if f.AuthInfoName != nil {
|
||||
overrides.Context.AuthInfo = *f.AuthInfoName
|
||||
}
|
||||
if f.Namespace != nil {
|
||||
overrides.Context.Namespace = *f.Namespace
|
||||
}
|
||||
|
||||
if f.Timeout != nil {
|
||||
overrides.Timeout = *f.Timeout
|
||||
}
|
||||
|
||||
var clientConfig clientcmd.ClientConfig
|
||||
|
||||
// we only have an interactive prompt when a password is allowed
|
||||
if f.Password == nil {
|
||||
clientConfig = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides)
|
||||
} else {
|
||||
clientConfig = clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, overrides, os.Stdin)
|
||||
}
|
||||
|
||||
return clientConfig
|
||||
}
|
||||
|
||||
// ToDiscoveryClient implements RESTClientGetter.
|
||||
// Expects the AddFlags method to have been called.
|
||||
// Returns a CachedDiscoveryInterface using a computed RESTConfig.
|
||||
func (f *ConfigFlags) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
|
||||
config, err := f.ToRESTConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// The more groups you have, the more discovery requests you need to make.
|
||||
// given 25 groups (our groups + a few custom resources) with one-ish version each, discovery needs to make 50 requests
|
||||
// double it just so we don't end up here again for a while. This config is only used for discovery.
|
||||
config.Burst = 100
|
||||
|
||||
// retrieve a user-provided value for the "cache-dir"
|
||||
// defaulting to ~/.kube/http-cache if no user-value is given.
|
||||
httpCacheDir := defaultCacheDir
|
||||
if f.CacheDir != nil {
|
||||
httpCacheDir = *f.CacheDir
|
||||
}
|
||||
|
||||
discoveryCacheDir := computeDiscoverCacheDir(filepath.Join(homedir.HomeDir(), ".kube", "cache", "discovery"), config.Host)
|
||||
return discovery.NewCachedDiscoveryClientForConfig(config, discoveryCacheDir, httpCacheDir, time.Duration(10*time.Minute))
|
||||
}
|
||||
|
||||
// ToRESTMapper returns a mapper.
|
||||
func (f *ConfigFlags) ToRESTMapper() (meta.RESTMapper, error) {
|
||||
discoveryClient, err := f.ToDiscoveryClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient)
|
||||
expander := restmapper.NewShortcutExpander(mapper, discoveryClient)
|
||||
return expander, nil
|
||||
}
|
||||
|
||||
// AddFlags binds client configuration flags to a given flagset
|
||||
func (f *ConfigFlags) AddFlags(flags *pflag.FlagSet) {
|
||||
if f.KubeConfig != nil {
|
||||
flags.StringVar(f.KubeConfig, "kubeconfig", *f.KubeConfig, "Path to the kubeconfig file to use for CLI requests.")
|
||||
}
|
||||
if f.CacheDir != nil {
|
||||
flags.StringVar(f.CacheDir, flagHTTPCacheDir, *f.CacheDir, "Default HTTP cache directory")
|
||||
}
|
||||
|
||||
// add config options
|
||||
if f.CertFile != nil {
|
||||
flags.StringVar(f.CertFile, flagCertFile, *f.CertFile, "Path to a client certificate file for TLS")
|
||||
}
|
||||
if f.KeyFile != nil {
|
||||
flags.StringVar(f.KeyFile, flagKeyFile, *f.KeyFile, "Path to a client key file for TLS")
|
||||
}
|
||||
if f.BearerToken != nil {
|
||||
flags.StringVar(f.BearerToken, flagBearerToken, *f.BearerToken, "Bearer token for authentication to the API server")
|
||||
}
|
||||
if f.Impersonate != nil {
|
||||
flags.StringVar(f.Impersonate, flagImpersonate, *f.Impersonate, "Username to impersonate for the operation")
|
||||
}
|
||||
if f.ImpersonateGroup != nil {
|
||||
flags.StringArrayVar(f.ImpersonateGroup, flagImpersonateGroup, *f.ImpersonateGroup, "Group to impersonate for the operation, this flag can be repeated to specify multiple groups.")
|
||||
}
|
||||
if f.Username != nil {
|
||||
flags.StringVar(f.Username, flagUsername, *f.Username, "Username for basic authentication to the API server")
|
||||
}
|
||||
if f.Password != nil {
|
||||
flags.StringVar(f.Password, flagPassword, *f.Password, "Password for basic authentication to the API server")
|
||||
}
|
||||
if f.ClusterName != nil {
|
||||
flags.StringVar(f.ClusterName, flagClusterName, *f.ClusterName, "The name of the kubeconfig cluster to use")
|
||||
}
|
||||
if f.AuthInfoName != nil {
|
||||
flags.StringVar(f.AuthInfoName, flagAuthInfoName, *f.AuthInfoName, "The name of the kubeconfig user to use")
|
||||
}
|
||||
if f.Namespace != nil {
|
||||
flags.StringVarP(f.Namespace, flagNamespace, "n", *f.Namespace, "If present, the namespace scope for this CLI request")
|
||||
}
|
||||
if f.Context != nil {
|
||||
flags.StringVar(f.Context, flagContext, *f.Context, "The name of the kubeconfig context to use")
|
||||
}
|
||||
|
||||
if f.APIServer != nil {
|
||||
flags.StringVarP(f.APIServer, flagAPIServer, "s", *f.APIServer, "The address and port of the Kubernetes API server")
|
||||
}
|
||||
if f.Insecure != nil {
|
||||
flags.BoolVar(f.Insecure, flagInsecure, *f.Insecure, "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure")
|
||||
}
|
||||
if f.CAFile != nil {
|
||||
flags.StringVar(f.CAFile, flagCAFile, *f.CAFile, "Path to a cert file for the certificate authority")
|
||||
}
|
||||
if f.Timeout != nil {
|
||||
flags.StringVar(f.Timeout, flagTimeout, *f.Timeout, "The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// WithDeprecatedPasswordFlag enables the username and password config flags
|
||||
func (f *ConfigFlags) WithDeprecatedPasswordFlag() *ConfigFlags {
|
||||
f.Username = stringptr("")
|
||||
f.Password = stringptr("")
|
||||
return f
|
||||
}
|
||||
|
||||
// NewConfigFlags returns ConfigFlags with default values set
|
||||
func NewConfigFlags() *ConfigFlags {
|
||||
impersonateGroup := []string{}
|
||||
insecure := false
|
||||
|
||||
return &ConfigFlags{
|
||||
Insecure: &insecure,
|
||||
Timeout: stringptr("0"),
|
||||
KubeConfig: stringptr(""),
|
||||
|
||||
CacheDir: stringptr(defaultCacheDir),
|
||||
ClusterName: stringptr(""),
|
||||
AuthInfoName: stringptr(""),
|
||||
Context: stringptr(""),
|
||||
Namespace: stringptr(""),
|
||||
APIServer: stringptr(""),
|
||||
CertFile: stringptr(""),
|
||||
KeyFile: stringptr(""),
|
||||
CAFile: stringptr(""),
|
||||
BearerToken: stringptr(""),
|
||||
Impersonate: stringptr(""),
|
||||
ImpersonateGroup: &impersonateGroup,
|
||||
}
|
||||
}
|
||||
|
||||
func stringptr(val string) *string {
|
||||
return &val
|
||||
}
|
||||
|
||||
// overlyCautiousIllegalFileCharacters matches characters that *might* not be supported. Windows is really restrictive, so this is really restrictive
|
||||
var overlyCautiousIllegalFileCharacters = regexp.MustCompile(`[^(\w/\.)]`)
|
||||
|
||||
// computeDiscoverCacheDir takes the parentDir and the host and comes up with a "usually non-colliding" name.
|
||||
func computeDiscoverCacheDir(parentDir, host string) string {
|
||||
// strip the optional scheme from host if its there:
|
||||
schemelessHost := strings.Replace(strings.Replace(host, "https://", "", 1), "http://", "", 1)
|
||||
// now do a simple collapse of non-AZ09 characters. Collisions are possible but unlikely. Even if we do collide the problem is short lived
|
||||
safeHost := overlyCautiousIllegalFileCharacters.ReplaceAllString(schemelessHost, "_")
|
||||
return filepath.Join(parentDir, safeHost)
|
||||
}
|
110
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/config_flags_fake.go
generated
vendored
Normal file
110
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/config_flags_fake.go
generated
vendored
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package genericclioptions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/restmapper"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
type TestConfigFlags struct {
|
||||
clientConfig clientcmd.ClientConfig
|
||||
discoveryClient discovery.CachedDiscoveryInterface
|
||||
restMapper meta.RESTMapper
|
||||
}
|
||||
|
||||
func (f *TestConfigFlags) ToRawKubeConfigLoader() clientcmd.ClientConfig {
|
||||
if f.clientConfig == nil {
|
||||
panic("attempt to obtain a test RawKubeConfigLoader with no clientConfig specified")
|
||||
}
|
||||
return f.clientConfig
|
||||
}
|
||||
|
||||
func (f *TestConfigFlags) ToRESTConfig() (*rest.Config, error) {
|
||||
return f.ToRawKubeConfigLoader().ClientConfig()
|
||||
}
|
||||
|
||||
func (f *TestConfigFlags) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
|
||||
return f.discoveryClient, nil
|
||||
}
|
||||
|
||||
func (f *TestConfigFlags) ToRESTMapper() (meta.RESTMapper, error) {
|
||||
if f.restMapper != nil {
|
||||
return f.restMapper, nil
|
||||
}
|
||||
if f.discoveryClient != nil {
|
||||
mapper := restmapper.NewDeferredDiscoveryRESTMapper(f.discoveryClient)
|
||||
expander := restmapper.NewShortcutExpander(mapper, f.discoveryClient)
|
||||
return expander, nil
|
||||
}
|
||||
return nil, fmt.Errorf("no restmapper")
|
||||
}
|
||||
|
||||
func (f *TestConfigFlags) WithClientConfig(clientConfig clientcmd.ClientConfig) *TestConfigFlags {
|
||||
f.clientConfig = clientConfig
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *TestConfigFlags) WithRESTMapper(mapper meta.RESTMapper) *TestConfigFlags {
|
||||
f.restMapper = mapper
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *TestConfigFlags) WithDiscoveryClient(c discovery.CachedDiscoveryInterface) *TestConfigFlags {
|
||||
f.discoveryClient = c
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *TestConfigFlags) WithNamespace(ns string) *TestConfigFlags {
|
||||
if f.clientConfig == nil {
|
||||
panic("attempt to obtain a test RawKubeConfigLoader with no clientConfig specified")
|
||||
}
|
||||
f.clientConfig = &namespacedClientConfig{
|
||||
delegate: f.clientConfig,
|
||||
namespace: ns,
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func NewTestConfigFlags() *TestConfigFlags {
|
||||
return &TestConfigFlags{}
|
||||
}
|
||||
|
||||
type namespacedClientConfig struct {
|
||||
delegate clientcmd.ClientConfig
|
||||
namespace string
|
||||
}
|
||||
|
||||
func (c *namespacedClientConfig) Namespace() (string, bool, error) {
|
||||
return c.namespace, false, nil
|
||||
}
|
||||
|
||||
func (c *namespacedClientConfig) RawConfig() (clientcmdapi.Config, error) {
|
||||
return c.delegate.RawConfig()
|
||||
}
|
||||
func (c *namespacedClientConfig) ClientConfig() (*rest.Config, error) {
|
||||
return c.delegate.ClientConfig()
|
||||
}
|
||||
func (c *namespacedClientConfig) ConfigAccess() clientcmd.ConfigAccess {
|
||||
return c.delegate.ConfigAccess()
|
||||
}
|
19
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/doc.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
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 genericclioptions contains flags which can be added to you command, bound, completed, and produce
|
||||
// useful helper functions. Nothing in this package can depend on kube/kube
|
||||
package genericclioptions
|
71
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/filename_flags.go
generated
vendored
Normal file
71
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/filename_flags.go
generated
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
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 genericclioptions
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
|
||||
)
|
||||
|
||||
// Usage of this struct by itself is discouraged.
|
||||
// These flags are composed by ResourceBuilderFlags
|
||||
// which should be used instead.
|
||||
type FileNameFlags struct {
|
||||
Usage string
|
||||
|
||||
Filenames *[]string
|
||||
Recursive *bool
|
||||
}
|
||||
|
||||
func (o *FileNameFlags) ToOptions() resource.FilenameOptions {
|
||||
options := resource.FilenameOptions{}
|
||||
|
||||
if o == nil {
|
||||
return options
|
||||
}
|
||||
|
||||
if o.Recursive != nil {
|
||||
options.Recursive = *o.Recursive
|
||||
}
|
||||
if o.Filenames != nil {
|
||||
options.Filenames = *o.Filenames
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
func (o *FileNameFlags) AddFlags(flags *pflag.FlagSet) {
|
||||
if o == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if o.Recursive != nil {
|
||||
flags.BoolVarP(o.Recursive, "recursive", "R", *o.Recursive, "Process the directory used in -f, --filename recursively. Useful when you want to manage related manifests organized within the same directory.")
|
||||
}
|
||||
if o.Filenames != nil {
|
||||
flags.StringSliceVarP(o.Filenames, "filename", "f", *o.Filenames, o.Usage)
|
||||
annotations := make([]string, 0, len(resource.FileExtensions))
|
||||
for _, ext := range resource.FileExtensions {
|
||||
annotations = append(annotations, strings.TrimLeft(ext, "."))
|
||||
}
|
||||
flags.SetAnnotation("filename", cobra.BashCompFilenameExt, annotations)
|
||||
}
|
||||
}
|
57
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/io_options.go
generated
vendored
Normal file
57
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/io_options.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
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 genericclioptions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// IOStreams provides the standard names for iostreams. This is useful for embedding and for unit testing.
|
||||
// Inconsistent and different names make it hard to read and review code
|
||||
type IOStreams struct {
|
||||
// In think, os.Stdin
|
||||
In io.Reader
|
||||
// Out think, os.Stdout
|
||||
Out io.Writer
|
||||
// ErrOut think, os.Stderr
|
||||
ErrOut io.Writer
|
||||
}
|
||||
|
||||
// NewTestIOStreams returns a valid IOStreams and in, out, errout buffers for unit tests
|
||||
func NewTestIOStreams() (IOStreams, *bytes.Buffer, *bytes.Buffer, *bytes.Buffer) {
|
||||
in := &bytes.Buffer{}
|
||||
out := &bytes.Buffer{}
|
||||
errOut := &bytes.Buffer{}
|
||||
|
||||
return IOStreams{
|
||||
In: in,
|
||||
Out: out,
|
||||
ErrOut: errOut,
|
||||
}, in, out, errOut
|
||||
}
|
||||
|
||||
// NewTestIOStreamsDiscard returns a valid IOStreams that just discards
|
||||
func NewTestIOStreamsDiscard() IOStreams {
|
||||
in := &bytes.Buffer{}
|
||||
return IOStreams{
|
||||
In: in,
|
||||
Out: ioutil.Discard,
|
||||
ErrOut: ioutil.Discard,
|
||||
}
|
||||
}
|
65
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/json_yaml_flags.go
generated
vendored
Normal file
65
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/json_yaml_flags.go
generated
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
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 genericclioptions
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
|
||||
)
|
||||
|
||||
func (f *JSONYamlPrintFlags) AllowedFormats() []string {
|
||||
return []string{"json", "yaml"}
|
||||
}
|
||||
|
||||
// JSONYamlPrintFlags provides default flags necessary for json/yaml printing.
|
||||
// Given the following flag values, a printer can be requested that knows
|
||||
// how to handle printing based on these values.
|
||||
type JSONYamlPrintFlags struct {
|
||||
}
|
||||
|
||||
// ToPrinter receives an outputFormat and returns a printer capable of
|
||||
// handling --output=(yaml|json) printing.
|
||||
// Returns false if the specified outputFormat does not match a supported format.
|
||||
// Supported Format types can be found in pkg/printers/printers.go
|
||||
func (f *JSONYamlPrintFlags) ToPrinter(outputFormat string) (printers.ResourcePrinter, error) {
|
||||
var printer printers.ResourcePrinter
|
||||
|
||||
outputFormat = strings.ToLower(outputFormat)
|
||||
switch outputFormat {
|
||||
case "json":
|
||||
printer = &printers.JSONPrinter{}
|
||||
case "yaml":
|
||||
printer = &printers.YAMLPrinter{}
|
||||
default:
|
||||
return nil, NoCompatiblePrinterError{OutputFormat: &outputFormat, AllowedFormats: f.AllowedFormats()}
|
||||
}
|
||||
|
||||
return printer, nil
|
||||
}
|
||||
|
||||
// AddFlags receives a *cobra.Command reference and binds
|
||||
// flags related to JSON or Yaml printing to it
|
||||
func (f *JSONYamlPrintFlags) AddFlags(c *cobra.Command) {}
|
||||
|
||||
// NewJSONYamlPrintFlags returns flags associated with
|
||||
// yaml or json printing, with default values set.
|
||||
func NewJSONYamlPrintFlags() *JSONYamlPrintFlags {
|
||||
return &JSONYamlPrintFlags{}
|
||||
}
|
91
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/json_yaml_flags_test.go
generated
vendored
Normal file
91
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/json_yaml_flags_test.go
generated
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package genericclioptions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestPrinterSupportsExpectedJSONYamlFormats(t *testing.T) {
|
||||
testObject := &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
outputFormat string
|
||||
expectedOutput string
|
||||
expectNoMatch bool
|
||||
}{
|
||||
{
|
||||
name: "json output format matches a json printer",
|
||||
outputFormat: "json",
|
||||
expectedOutput: "\"name\": \"foo\"",
|
||||
},
|
||||
{
|
||||
name: "yaml output format matches a yaml printer",
|
||||
outputFormat: "yaml",
|
||||
expectedOutput: "name: foo",
|
||||
},
|
||||
{
|
||||
name: "output format for another printer does not match a json/yaml printer",
|
||||
outputFormat: "jsonpath",
|
||||
expectNoMatch: true,
|
||||
},
|
||||
{
|
||||
name: "invalid output format results in no match",
|
||||
outputFormat: "invalid",
|
||||
expectNoMatch: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
printFlags := JSONYamlPrintFlags{}
|
||||
|
||||
p, err := printFlags.ToPrinter(tc.outputFormat)
|
||||
if tc.expectNoMatch {
|
||||
if !IsNoCompatiblePrinterError(err) {
|
||||
t.Fatalf("expected no printer matches for output format %q", tc.outputFormat)
|
||||
}
|
||||
return
|
||||
}
|
||||
if IsNoCompatiblePrinterError(err) {
|
||||
t.Fatalf("expected to match template printer for output format %q", tc.outputFormat)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
out := bytes.NewBuffer([]byte{})
|
||||
err = p.PrintObj(testObject, out)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(out.String(), tc.expectedOutput) {
|
||||
t.Errorf("unexpected output: expecting %q, got %q", tc.expectedOutput, out.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
128
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/jsonpath_flags.go
generated
vendored
Normal file
128
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/jsonpath_flags.go
generated
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
/*
|
||||
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 genericclioptions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
|
||||
)
|
||||
|
||||
// templates are logically optional for specifying a format.
|
||||
// this allows a user to specify a template format value
|
||||
// as --output=jsonpath=
|
||||
var jsonFormats = map[string]bool{
|
||||
"jsonpath": true,
|
||||
"jsonpath-file": true,
|
||||
}
|
||||
|
||||
// JSONPathPrintFlags provides default flags necessary for template printing.
|
||||
// Given the following flag values, a printer can be requested that knows
|
||||
// how to handle printing based on these values.
|
||||
type JSONPathPrintFlags struct {
|
||||
// indicates if it is OK to ignore missing keys for rendering
|
||||
// an output template.
|
||||
AllowMissingKeys *bool
|
||||
TemplateArgument *string
|
||||
}
|
||||
|
||||
func (f *JSONPathPrintFlags) AllowedFormats() []string {
|
||||
formats := make([]string, 0, len(jsonFormats))
|
||||
for format := range jsonFormats {
|
||||
formats = append(formats, format)
|
||||
}
|
||||
return formats
|
||||
}
|
||||
|
||||
// ToPrinter receives an templateFormat and returns a printer capable of
|
||||
// handling --template format printing.
|
||||
// Returns false if the specified templateFormat does not match a template format.
|
||||
func (f *JSONPathPrintFlags) ToPrinter(templateFormat string) (printers.ResourcePrinter, error) {
|
||||
if (f.TemplateArgument == nil || len(*f.TemplateArgument) == 0) && len(templateFormat) == 0 {
|
||||
return nil, NoCompatiblePrinterError{Options: f, OutputFormat: &templateFormat}
|
||||
}
|
||||
|
||||
templateValue := ""
|
||||
|
||||
if f.TemplateArgument == nil || len(*f.TemplateArgument) == 0 {
|
||||
for format := range jsonFormats {
|
||||
format = format + "="
|
||||
if strings.HasPrefix(templateFormat, format) {
|
||||
templateValue = templateFormat[len(format):]
|
||||
templateFormat = format[:len(format)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
templateValue = *f.TemplateArgument
|
||||
}
|
||||
|
||||
if _, supportedFormat := jsonFormats[templateFormat]; !supportedFormat {
|
||||
return nil, NoCompatiblePrinterError{OutputFormat: &templateFormat, AllowedFormats: f.AllowedFormats()}
|
||||
}
|
||||
|
||||
if len(templateValue) == 0 {
|
||||
return nil, fmt.Errorf("template format specified but no template given")
|
||||
}
|
||||
|
||||
if templateFormat == "jsonpath-file" {
|
||||
data, err := ioutil.ReadFile(templateValue)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading --template %s, %v\n", templateValue, err)
|
||||
}
|
||||
|
||||
templateValue = string(data)
|
||||
}
|
||||
|
||||
p, err := printers.NewJSONPathPrinter(templateValue)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing jsonpath %s, %v\n", templateValue, err)
|
||||
}
|
||||
|
||||
allowMissingKeys := true
|
||||
if f.AllowMissingKeys != nil {
|
||||
allowMissingKeys = *f.AllowMissingKeys
|
||||
}
|
||||
|
||||
p.AllowMissingKeys(allowMissingKeys)
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// AddFlags receives a *cobra.Command reference and binds
|
||||
// flags related to template printing to it
|
||||
func (f *JSONPathPrintFlags) AddFlags(c *cobra.Command) {
|
||||
if f.TemplateArgument != nil {
|
||||
c.Flags().StringVar(f.TemplateArgument, "template", *f.TemplateArgument, "Template string or path to template file to use when --output=jsonpath, --output=jsonpath-file.")
|
||||
c.MarkFlagFilename("template")
|
||||
}
|
||||
if f.AllowMissingKeys != nil {
|
||||
c.Flags().BoolVar(f.AllowMissingKeys, "allow-missing-template-keys", *f.AllowMissingKeys, "If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats.")
|
||||
}
|
||||
}
|
||||
|
||||
// NewJSONPathPrintFlags returns flags associated with
|
||||
// --template printing, with default values set.
|
||||
func NewJSONPathPrintFlags(templateValue string, allowMissingKeys bool) *JSONPathPrintFlags {
|
||||
return &JSONPathPrintFlags{
|
||||
TemplateArgument: &templateValue,
|
||||
AllowMissingKeys: &allowMissingKeys,
|
||||
}
|
||||
}
|
211
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/jsonpath_flags_test.go
generated
vendored
Normal file
211
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/jsonpath_flags_test.go
generated
vendored
Normal file
@ -0,0 +1,211 @@
|
||||
/*
|
||||
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 genericclioptions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestPrinterSupportsExpectedJSONPathFormats(t *testing.T) {
|
||||
testObject := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
|
||||
|
||||
jsonpathFile, err := ioutil.TempFile("", "printers_jsonpath_flags")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
defer func(tempFile *os.File) {
|
||||
tempFile.Close()
|
||||
os.Remove(tempFile.Name())
|
||||
}(jsonpathFile)
|
||||
|
||||
fmt.Fprintf(jsonpathFile, "{ .metadata.name }\n")
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
outputFormat string
|
||||
templateArg string
|
||||
expectedError string
|
||||
expectedParseError string
|
||||
expectedOutput string
|
||||
expectNoMatch bool
|
||||
}{
|
||||
{
|
||||
name: "valid output format also containing the jsonpath argument succeeds",
|
||||
outputFormat: "jsonpath={ .metadata.name }",
|
||||
expectedOutput: "foo",
|
||||
},
|
||||
{
|
||||
name: "valid output format and no --template argument results in an error",
|
||||
outputFormat: "jsonpath",
|
||||
expectedError: "template format specified but no template given",
|
||||
},
|
||||
{
|
||||
name: "valid output format and --template argument succeeds",
|
||||
outputFormat: "jsonpath",
|
||||
templateArg: "{ .metadata.name }",
|
||||
expectedOutput: "foo",
|
||||
},
|
||||
{
|
||||
name: "jsonpath template file should match, and successfully return correct value",
|
||||
outputFormat: "jsonpath-file",
|
||||
templateArg: jsonpathFile.Name(),
|
||||
expectedOutput: "foo",
|
||||
},
|
||||
{
|
||||
name: "valid output format and invalid --template argument results in a parsing from the printer",
|
||||
outputFormat: "jsonpath",
|
||||
templateArg: "{invalid}",
|
||||
expectedParseError: "unrecognized identifier invalid",
|
||||
},
|
||||
{
|
||||
name: "no printer is matched on an invalid outputFormat",
|
||||
outputFormat: "invalid",
|
||||
expectNoMatch: true,
|
||||
},
|
||||
{
|
||||
name: "jsonpath printer should not match on any other format supported by another printer",
|
||||
outputFormat: "go-template",
|
||||
expectNoMatch: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
templateArg := &tc.templateArg
|
||||
if len(tc.templateArg) == 0 {
|
||||
templateArg = nil
|
||||
}
|
||||
|
||||
printFlags := JSONPathPrintFlags{
|
||||
TemplateArgument: templateArg,
|
||||
}
|
||||
|
||||
p, err := printFlags.ToPrinter(tc.outputFormat)
|
||||
if tc.expectNoMatch {
|
||||
if !IsNoCompatiblePrinterError(err) {
|
||||
t.Fatalf("expected no printer matches for output format %q", tc.outputFormat)
|
||||
}
|
||||
return
|
||||
}
|
||||
if IsNoCompatiblePrinterError(err) {
|
||||
t.Fatalf("expected to match template printer for output format %q", tc.outputFormat)
|
||||
}
|
||||
|
||||
if len(tc.expectedError) > 0 {
|
||||
if err == nil || !strings.Contains(err.Error(), tc.expectedError) {
|
||||
t.Errorf("expecting error %q, got %v", tc.expectedError, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
out := bytes.NewBuffer([]byte{})
|
||||
err = p.PrintObj(testObject, out)
|
||||
if len(tc.expectedParseError) > 0 {
|
||||
if err == nil || !strings.Contains(err.Error(), tc.expectedParseError) {
|
||||
t.Errorf("expecting error %q, got %v", tc.expectedError, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(out.String(), tc.expectedOutput) {
|
||||
t.Errorf("unexpected output: expecting %q, got %q", tc.expectedOutput, out.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONPathPrinterDefaultsAllowMissingKeysToTrue(t *testing.T) {
|
||||
testObject := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
|
||||
|
||||
allowMissingKeys := false
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
templateArg string
|
||||
expectedOutput string
|
||||
expectedError string
|
||||
allowMissingKeys *bool
|
||||
}{
|
||||
{
|
||||
name: "existing field does not error and returns expected value",
|
||||
templateArg: "{ .metadata.name }",
|
||||
expectedOutput: "foo",
|
||||
allowMissingKeys: &allowMissingKeys,
|
||||
},
|
||||
{
|
||||
name: "missing field does not error and returns an empty string since missing keys are allowed by default",
|
||||
templateArg: "{ .metadata.missing }",
|
||||
expectedOutput: "",
|
||||
allowMissingKeys: nil,
|
||||
},
|
||||
{
|
||||
name: "missing field returns expected error if field is missing and allowMissingKeys is explicitly set to false",
|
||||
templateArg: "{ .metadata.missing }",
|
||||
expectedError: "error executing jsonpath",
|
||||
allowMissingKeys: &allowMissingKeys,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
printFlags := JSONPathPrintFlags{
|
||||
TemplateArgument: &tc.templateArg,
|
||||
AllowMissingKeys: tc.allowMissingKeys,
|
||||
}
|
||||
|
||||
outputFormat := "jsonpath"
|
||||
p, err := printFlags.ToPrinter(outputFormat)
|
||||
if IsNoCompatiblePrinterError(err) {
|
||||
t.Fatalf("expected to match template printer for output format %q", outputFormat)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
out := bytes.NewBuffer([]byte{})
|
||||
err = p.PrintObj(testObject, out)
|
||||
|
||||
if len(tc.expectedError) > 0 {
|
||||
if err == nil || !strings.Contains(err.Error(), tc.expectedError) {
|
||||
t.Errorf("expecting error %q, got %v", tc.expectedError, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(out.String()) != len(tc.expectedOutput) {
|
||||
t.Errorf("unexpected output: expecting %q, got %q", tc.expectedOutput, out.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
88
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/kube_template_flags.go
generated
vendored
Normal file
88
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/kube_template_flags.go
generated
vendored
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
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 genericclioptions
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
|
||||
)
|
||||
|
||||
// KubeTemplatePrintFlags composes print flags that provide both a JSONPath and a go-template printer.
|
||||
// This is necessary if dealing with cases that require support both both printers, since both sets of flags
|
||||
// require overlapping flags.
|
||||
type KubeTemplatePrintFlags struct {
|
||||
GoTemplatePrintFlags *GoTemplatePrintFlags
|
||||
JSONPathPrintFlags *JSONPathPrintFlags
|
||||
|
||||
AllowMissingKeys *bool
|
||||
TemplateArgument *string
|
||||
}
|
||||
|
||||
func (f *KubeTemplatePrintFlags) AllowedFormats() []string {
|
||||
if f == nil {
|
||||
return []string{}
|
||||
}
|
||||
return append(f.GoTemplatePrintFlags.AllowedFormats(), f.JSONPathPrintFlags.AllowedFormats()...)
|
||||
}
|
||||
|
||||
func (f *KubeTemplatePrintFlags) ToPrinter(outputFormat string) (printers.ResourcePrinter, error) {
|
||||
if f == nil {
|
||||
return nil, NoCompatiblePrinterError{}
|
||||
}
|
||||
|
||||
if p, err := f.JSONPathPrintFlags.ToPrinter(outputFormat); !IsNoCompatiblePrinterError(err) {
|
||||
return p, err
|
||||
}
|
||||
return f.GoTemplatePrintFlags.ToPrinter(outputFormat)
|
||||
}
|
||||
|
||||
// AddFlags receives a *cobra.Command reference and binds
|
||||
// flags related to template printing to it
|
||||
func (f *KubeTemplatePrintFlags) AddFlags(c *cobra.Command) {
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if f.TemplateArgument != nil {
|
||||
c.Flags().StringVar(f.TemplateArgument, "template", *f.TemplateArgument, "Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].")
|
||||
c.MarkFlagFilename("template")
|
||||
}
|
||||
if f.AllowMissingKeys != nil {
|
||||
c.Flags().BoolVar(f.AllowMissingKeys, "allow-missing-template-keys", *f.AllowMissingKeys, "If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats.")
|
||||
}
|
||||
}
|
||||
|
||||
// NewKubeTemplatePrintFlags returns flags associated with
|
||||
// --template printing, with default values set.
|
||||
func NewKubeTemplatePrintFlags() *KubeTemplatePrintFlags {
|
||||
allowMissingKeysPtr := true
|
||||
templateArgPtr := ""
|
||||
|
||||
return &KubeTemplatePrintFlags{
|
||||
GoTemplatePrintFlags: &GoTemplatePrintFlags{
|
||||
TemplateArgument: &templateArgPtr,
|
||||
AllowMissingKeys: &allowMissingKeysPtr,
|
||||
},
|
||||
JSONPathPrintFlags: &JSONPathPrintFlags{
|
||||
TemplateArgument: &templateArgPtr,
|
||||
AllowMissingKeys: &allowMissingKeysPtr,
|
||||
},
|
||||
|
||||
TemplateArgument: &templateArgPtr,
|
||||
AllowMissingKeys: &allowMissingKeysPtr,
|
||||
}
|
||||
}
|
78
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/name_flags.go
generated
vendored
Normal file
78
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/name_flags.go
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
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 genericclioptions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
|
||||
)
|
||||
|
||||
// NamePrintFlags provides default flags necessary for printing
|
||||
// a resource's fully-qualified Kind.group/name, or a successful
|
||||
// message about that resource if an Operation is provided.
|
||||
type NamePrintFlags struct {
|
||||
// Operation describes the name of the action that
|
||||
// took place on an object, to be included in the
|
||||
// finalized "successful" message.
|
||||
Operation string
|
||||
}
|
||||
|
||||
func (f *NamePrintFlags) Complete(successTemplate string) error {
|
||||
f.Operation = fmt.Sprintf(successTemplate, f.Operation)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *NamePrintFlags) AllowedFormats() []string {
|
||||
return []string{"name"}
|
||||
}
|
||||
|
||||
// ToPrinter receives an outputFormat and returns a printer capable of
|
||||
// handling --output=name printing.
|
||||
// Returns false if the specified outputFormat does not match a supported format.
|
||||
// Supported format types can be found in pkg/printers/printers.go
|
||||
func (f *NamePrintFlags) ToPrinter(outputFormat string) (printers.ResourcePrinter, error) {
|
||||
namePrinter := &printers.NamePrinter{
|
||||
Operation: f.Operation,
|
||||
}
|
||||
|
||||
outputFormat = strings.ToLower(outputFormat)
|
||||
switch outputFormat {
|
||||
case "name":
|
||||
namePrinter.ShortOutput = true
|
||||
fallthrough
|
||||
case "":
|
||||
return namePrinter, nil
|
||||
default:
|
||||
return nil, NoCompatiblePrinterError{OutputFormat: &outputFormat, AllowedFormats: f.AllowedFormats()}
|
||||
}
|
||||
}
|
||||
|
||||
// AddFlags receives a *cobra.Command reference and binds
|
||||
// flags related to name printing to it
|
||||
func (f *NamePrintFlags) AddFlags(c *cobra.Command) {}
|
||||
|
||||
// NewNamePrintFlags returns flags associated with
|
||||
// --name printing, with default values set.
|
||||
func NewNamePrintFlags(operation string) *NamePrintFlags {
|
||||
return &NamePrintFlags{
|
||||
Operation: operation,
|
||||
}
|
||||
}
|
116
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/name_flags_test.go
generated
vendored
Normal file
116
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/name_flags_test.go
generated
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package genericclioptions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestNamePrinterSupportsExpectedFormats(t *testing.T) {
|
||||
testObject := &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
outputFormat string
|
||||
operation string
|
||||
dryRun bool
|
||||
expectedError string
|
||||
expectedOutput string
|
||||
expectNoMatch bool
|
||||
}{
|
||||
{
|
||||
name: "valid \"name\" output format with no operation prints resource name",
|
||||
outputFormat: "name",
|
||||
expectedOutput: "pod/foo",
|
||||
},
|
||||
{
|
||||
name: "valid \"name\" output format and an operation results in a short-output (non success printer) message",
|
||||
outputFormat: "name",
|
||||
operation: "patched",
|
||||
expectedOutput: "pod/foo",
|
||||
},
|
||||
{
|
||||
name: "operation and no valid \"name\" output does not match a printer",
|
||||
operation: "patched",
|
||||
outputFormat: "invalid",
|
||||
dryRun: true,
|
||||
expectNoMatch: true,
|
||||
},
|
||||
{
|
||||
name: "operation and empty output still matches name printer",
|
||||
expectedOutput: "pod/foo patched",
|
||||
operation: "patched",
|
||||
},
|
||||
{
|
||||
name: "no printer is matched on an invalid outputFormat",
|
||||
outputFormat: "invalid",
|
||||
expectNoMatch: true,
|
||||
},
|
||||
{
|
||||
name: "printer should not match on any other format supported by another printer",
|
||||
outputFormat: "go-template",
|
||||
expectNoMatch: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
printFlags := NamePrintFlags{
|
||||
Operation: tc.operation,
|
||||
}
|
||||
|
||||
p, err := printFlags.ToPrinter(tc.outputFormat)
|
||||
if tc.expectNoMatch {
|
||||
if !IsNoCompatiblePrinterError(err) {
|
||||
t.Fatalf("expected no printer matches for output format %q", tc.outputFormat)
|
||||
}
|
||||
return
|
||||
}
|
||||
if IsNoCompatiblePrinterError(err) {
|
||||
t.Fatalf("expected to match name printer for output format %q", tc.outputFormat)
|
||||
}
|
||||
|
||||
if len(tc.expectedError) > 0 {
|
||||
if err == nil || !strings.Contains(err.Error(), tc.expectedError) {
|
||||
t.Errorf("expecting error %q, got %v", tc.expectedError, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
out := bytes.NewBuffer([]byte{})
|
||||
err = p.PrintObj(testObject, out)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(out.String(), tc.expectedOutput) {
|
||||
t.Errorf("unexpected output: expecting %q, got %q", tc.expectedOutput, out.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
158
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/print_flags.go
generated
vendored
Normal file
158
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/print_flags.go
generated
vendored
Normal file
@ -0,0 +1,158 @@
|
||||
/*
|
||||
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 genericclioptions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
|
||||
)
|
||||
|
||||
type NoCompatiblePrinterError struct {
|
||||
OutputFormat *string
|
||||
AllowedFormats []string
|
||||
Options interface{}
|
||||
}
|
||||
|
||||
func (e NoCompatiblePrinterError) Error() string {
|
||||
output := ""
|
||||
if e.OutputFormat != nil {
|
||||
output = *e.OutputFormat
|
||||
}
|
||||
|
||||
sort.Strings(e.AllowedFormats)
|
||||
return fmt.Sprintf("unable to match a printer suitable for the output format %q, allowed formats are: %s", output, strings.Join(e.AllowedFormats, ","))
|
||||
}
|
||||
|
||||
func IsNoCompatiblePrinterError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_, ok := err.(NoCompatiblePrinterError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// PrintFlags composes common printer flag structs
|
||||
// used across all commands, and provides a method
|
||||
// of retrieving a known printer based on flag values provided.
|
||||
type PrintFlags struct {
|
||||
JSONYamlPrintFlags *JSONYamlPrintFlags
|
||||
NamePrintFlags *NamePrintFlags
|
||||
TemplatePrinterFlags *KubeTemplatePrintFlags
|
||||
|
||||
TypeSetterPrinter *printers.TypeSetterPrinter
|
||||
|
||||
OutputFormat *string
|
||||
|
||||
// OutputFlagSpecified indicates whether the user specifically requested a certain kind of output.
|
||||
// Using this function allows a sophisticated caller to change the flag binding logic if they so desire.
|
||||
OutputFlagSpecified func() bool
|
||||
}
|
||||
|
||||
func (f *PrintFlags) Complete(successTemplate string) error {
|
||||
return f.NamePrintFlags.Complete(successTemplate)
|
||||
}
|
||||
|
||||
func (f *PrintFlags) AllowedFormats() []string {
|
||||
ret := []string{}
|
||||
ret = append(ret, f.JSONYamlPrintFlags.AllowedFormats()...)
|
||||
ret = append(ret, f.NamePrintFlags.AllowedFormats()...)
|
||||
ret = append(ret, f.TemplatePrinterFlags.AllowedFormats()...)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (f *PrintFlags) ToPrinter() (printers.ResourcePrinter, error) {
|
||||
outputFormat := ""
|
||||
if f.OutputFormat != nil {
|
||||
outputFormat = *f.OutputFormat
|
||||
}
|
||||
// For backwards compatibility we want to support a --template argument given, even when no --output format is provided.
|
||||
// If no explicit output format has been provided via the --output flag, fallback
|
||||
// to honoring the --template argument.
|
||||
templateFlagSpecified := f.TemplatePrinterFlags != nil &&
|
||||
f.TemplatePrinterFlags.TemplateArgument != nil &&
|
||||
len(*f.TemplatePrinterFlags.TemplateArgument) > 0
|
||||
outputFlagSpecified := f.OutputFlagSpecified != nil && f.OutputFlagSpecified()
|
||||
if templateFlagSpecified && !outputFlagSpecified {
|
||||
outputFormat = "go-template"
|
||||
}
|
||||
|
||||
if f.JSONYamlPrintFlags != nil {
|
||||
if p, err := f.JSONYamlPrintFlags.ToPrinter(outputFormat); !IsNoCompatiblePrinterError(err) {
|
||||
return f.TypeSetterPrinter.WrapToPrinter(p, err)
|
||||
}
|
||||
}
|
||||
|
||||
if f.NamePrintFlags != nil {
|
||||
if p, err := f.NamePrintFlags.ToPrinter(outputFormat); !IsNoCompatiblePrinterError(err) {
|
||||
return f.TypeSetterPrinter.WrapToPrinter(p, err)
|
||||
}
|
||||
}
|
||||
|
||||
if f.TemplatePrinterFlags != nil {
|
||||
if p, err := f.TemplatePrinterFlags.ToPrinter(outputFormat); !IsNoCompatiblePrinterError(err) {
|
||||
return f.TypeSetterPrinter.WrapToPrinter(p, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, NoCompatiblePrinterError{OutputFormat: f.OutputFormat, AllowedFormats: f.AllowedFormats()}
|
||||
}
|
||||
|
||||
func (f *PrintFlags) AddFlags(cmd *cobra.Command) {
|
||||
f.JSONYamlPrintFlags.AddFlags(cmd)
|
||||
f.NamePrintFlags.AddFlags(cmd)
|
||||
f.TemplatePrinterFlags.AddFlags(cmd)
|
||||
|
||||
if f.OutputFormat != nil {
|
||||
cmd.Flags().StringVarP(f.OutputFormat, "output", "o", *f.OutputFormat, fmt.Sprintf("Output format. One of: %s.", strings.Join(f.AllowedFormats(), "|")))
|
||||
if f.OutputFlagSpecified == nil {
|
||||
f.OutputFlagSpecified = func() bool {
|
||||
return cmd.Flag("output").Changed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithDefaultOutput sets a default output format if one is not provided through a flag value
|
||||
func (f *PrintFlags) WithDefaultOutput(output string) *PrintFlags {
|
||||
f.OutputFormat = &output
|
||||
return f
|
||||
}
|
||||
|
||||
// WithTypeSetter sets a wrapper than will surround the returned printer with a printer to type resources
|
||||
func (f *PrintFlags) WithTypeSetter(scheme *runtime.Scheme) *PrintFlags {
|
||||
f.TypeSetterPrinter = printers.NewTypeSetter(scheme)
|
||||
return f
|
||||
}
|
||||
|
||||
func NewPrintFlags(operation string) *PrintFlags {
|
||||
outputFormat := ""
|
||||
|
||||
return &PrintFlags{
|
||||
OutputFormat: &outputFormat,
|
||||
|
||||
JSONYamlPrintFlags: NewJSONYamlPrintFlags(),
|
||||
NamePrintFlags: NewNamePrintFlags(operation),
|
||||
TemplatePrinterFlags: NewKubeTemplatePrintFlags(),
|
||||
}
|
||||
}
|
52
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers/BUILD
generated
vendored
Normal file
52
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers/BUILD
generated
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"discard.go",
|
||||
"interface.go",
|
||||
"json.go",
|
||||
"jsonpath.go",
|
||||
"name.go",
|
||||
"sourcechecker.go",
|
||||
"template.go",
|
||||
"typesetter.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//vendor/github.com/ghodss/yaml:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/jsonpath:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"sourcechecker_test.go",
|
||||
"template_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime: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"],
|
||||
)
|
30
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers/discard.go
generated
vendored
Normal file
30
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers/discard.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package printers
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// NewDiscardingPrinter is a printer that discards all objects
|
||||
func NewDiscardingPrinter() ResourcePrinterFunc {
|
||||
return ResourcePrinterFunc(func(runtime.Object, io.Writer) error {
|
||||
return nil
|
||||
})
|
||||
}
|
37
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers/interface.go
generated
vendored
Normal file
37
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers/interface.go
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
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 printers
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// ResourcePrinterFunc is a function that can print objects
|
||||
type ResourcePrinterFunc func(runtime.Object, io.Writer) error
|
||||
|
||||
// PrintObj implements ResourcePrinter
|
||||
func (fn ResourcePrinterFunc) PrintObj(obj runtime.Object, w io.Writer) error {
|
||||
return fn(obj, w)
|
||||
}
|
||||
|
||||
// ResourcePrinter is an interface that knows how to print runtime objects.
|
||||
type ResourcePrinter interface {
|
||||
// Print receives a runtime object, formats it and prints it to a writer.
|
||||
PrintObj(runtime.Object, io.Writer) error
|
||||
}
|
105
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers/json.go
generated
vendored
Normal file
105
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers/json.go
generated
vendored
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
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 printers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
)
|
||||
|
||||
// JSONPrinter is an implementation of ResourcePrinter which outputs an object as JSON.
|
||||
type JSONPrinter struct{}
|
||||
|
||||
// PrintObj is an implementation of ResourcePrinter.PrintObj which simply writes the object to the Writer.
|
||||
func (p *JSONPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
|
||||
// we use reflect.Indirect here in order to obtain the actual value from a pointer.
|
||||
// we need an actual value in order to retrieve the package path for an object.
|
||||
// using reflect.Indirect indiscriminately is valid here, as all runtime.Objects are supposed to be pointers.
|
||||
if InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) {
|
||||
return fmt.Errorf(InternalObjectPrinterErr)
|
||||
}
|
||||
|
||||
switch obj := obj.(type) {
|
||||
case *runtime.Unknown:
|
||||
var buf bytes.Buffer
|
||||
err := json.Indent(&buf, obj.Raw, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf.WriteRune('\n')
|
||||
_, err = buf.WriteTo(w)
|
||||
return err
|
||||
}
|
||||
|
||||
if obj.GetObjectKind().GroupVersionKind().Empty() {
|
||||
return fmt.Errorf("missing apiVersion or kind; try GetObjectKind().SetGroupVersionKind() if you know the type")
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(obj, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data = append(data, '\n')
|
||||
_, err = w.Write(data)
|
||||
return err
|
||||
}
|
||||
|
||||
// YAMLPrinter is an implementation of ResourcePrinter which outputs an object as YAML.
|
||||
// The input object is assumed to be in the internal version of an API and is converted
|
||||
// to the given version first.
|
||||
type YAMLPrinter struct {
|
||||
version string
|
||||
converter runtime.ObjectConvertor
|
||||
}
|
||||
|
||||
// PrintObj prints the data as YAML.
|
||||
func (p *YAMLPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
|
||||
// we use reflect.Indirect here in order to obtain the actual value from a pointer.
|
||||
// we need an actual value in order to retrieve the package path for an object.
|
||||
// using reflect.Indirect indiscriminately is valid here, as all runtime.Objects are supposed to be pointers.
|
||||
if InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) {
|
||||
return fmt.Errorf(InternalObjectPrinterErr)
|
||||
}
|
||||
|
||||
switch obj := obj.(type) {
|
||||
case *runtime.Unknown:
|
||||
data, err := yaml.JSONToYAML(obj.Raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(data)
|
||||
return err
|
||||
}
|
||||
|
||||
if obj.GetObjectKind().GroupVersionKind().Empty() {
|
||||
return fmt.Errorf("missing apiVersion or kind; try GetObjectKind().SetGroupVersionKind() if you know the type")
|
||||
}
|
||||
|
||||
output, err := yaml.Marshal(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Fprint(w, string(output))
|
||||
return err
|
||||
}
|
158
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers/jsonpath.go
generated
vendored
Normal file
158
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers/jsonpath.go
generated
vendored
Normal file
@ -0,0 +1,158 @@
|
||||
/*
|
||||
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 printers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/util/jsonpath"
|
||||
)
|
||||
|
||||
// exists returns true if it would be possible to call the index function
|
||||
// with these arguments.
|
||||
//
|
||||
// TODO: how to document this for users?
|
||||
//
|
||||
// index returns the result of indexing its first argument by the following
|
||||
// arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each
|
||||
// indexed item must be a map, slice, or array.
|
||||
func exists(item interface{}, indices ...interface{}) bool {
|
||||
v := reflect.ValueOf(item)
|
||||
for _, i := range indices {
|
||||
index := reflect.ValueOf(i)
|
||||
var isNil bool
|
||||
if v, isNil = indirect(v); isNil {
|
||||
return false
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Slice, reflect.String:
|
||||
var x int64
|
||||
switch index.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
x = index.Int()
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
x = int64(index.Uint())
|
||||
default:
|
||||
return false
|
||||
}
|
||||
if x < 0 || x >= int64(v.Len()) {
|
||||
return false
|
||||
}
|
||||
v = v.Index(int(x))
|
||||
case reflect.Map:
|
||||
if !index.IsValid() {
|
||||
index = reflect.Zero(v.Type().Key())
|
||||
}
|
||||
if !index.Type().AssignableTo(v.Type().Key()) {
|
||||
return false
|
||||
}
|
||||
if x := v.MapIndex(index); x.IsValid() {
|
||||
v = x
|
||||
} else {
|
||||
v = reflect.Zero(v.Type().Elem())
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
if _, isNil := indirect(v); isNil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// stolen from text/template
|
||||
// indirect returns the item at the end of indirection, and a bool to indicate if it's nil.
|
||||
// We indirect through pointers and empty interfaces (only) because
|
||||
// non-empty interfaces have methods we might need.
|
||||
func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
|
||||
for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
|
||||
if v.IsNil() {
|
||||
return v, true
|
||||
}
|
||||
if v.Kind() == reflect.Interface && v.NumMethod() > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return v, false
|
||||
}
|
||||
|
||||
// JSONPathPrinter is an implementation of ResourcePrinter which formats data with jsonpath expression.
|
||||
type JSONPathPrinter struct {
|
||||
rawTemplate string
|
||||
*jsonpath.JSONPath
|
||||
}
|
||||
|
||||
func NewJSONPathPrinter(tmpl string) (*JSONPathPrinter, error) {
|
||||
j := jsonpath.New("out")
|
||||
if err := j.Parse(tmpl); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &JSONPathPrinter{
|
||||
rawTemplate: tmpl,
|
||||
JSONPath: j,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PrintObj formats the obj with the JSONPath Template.
|
||||
func (j *JSONPathPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
|
||||
// we use reflect.Indirect here in order to obtain the actual value from a pointer.
|
||||
// we need an actual value in order to retrieve the package path for an object.
|
||||
// using reflect.Indirect indiscriminately is valid here, as all runtime.Objects are supposed to be pointers.
|
||||
if InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) {
|
||||
return fmt.Errorf(InternalObjectPrinterErr)
|
||||
}
|
||||
|
||||
var queryObj interface{} = obj
|
||||
if meta.IsListType(obj) {
|
||||
data, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
queryObj = map[string]interface{}{}
|
||||
if err := json.Unmarshal(data, &queryObj); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if unknown, ok := obj.(*runtime.Unknown); ok {
|
||||
data, err := json.Marshal(unknown)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
queryObj = map[string]interface{}{}
|
||||
if err := json.Unmarshal(data, &queryObj); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if unstructured, ok := obj.(runtime.Unstructured); ok {
|
||||
queryObj = unstructured.UnstructuredContent()
|
||||
}
|
||||
|
||||
if err := j.JSONPath.Execute(w, queryObj); err != nil {
|
||||
fmt.Fprintf(w, "Error executing template: %v. Printing more information for debugging the template:\n", err)
|
||||
fmt.Fprintf(w, "\ttemplate was:\n\t\t%v\n", j.rawTemplate)
|
||||
fmt.Fprintf(w, "\tobject given to jsonpath engine was:\n\t\t%#v\n\n", queryObj)
|
||||
return fmt.Errorf("error executing jsonpath %q: %v\n", j.rawTemplate, err)
|
||||
}
|
||||
return nil
|
||||
}
|
124
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers/name.go
generated
vendored
Normal file
124
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers/name.go
generated
vendored
Normal file
@ -0,0 +1,124 @@
|
||||
/*
|
||||
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 printers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// NamePrinter is an implementation of ResourcePrinter which outputs "resource/name" pair of an object.
|
||||
type NamePrinter struct {
|
||||
// ShortOutput indicates whether an operation should be
|
||||
// printed along side the "resource/name" pair for an object.
|
||||
ShortOutput bool
|
||||
// Operation describes the name of the action that
|
||||
// took place on an object, to be included in the
|
||||
// finalized "successful" message.
|
||||
Operation string
|
||||
}
|
||||
|
||||
// PrintObj is an implementation of ResourcePrinter.PrintObj which decodes the object
|
||||
// and print "resource/name" pair. If the object is a List, print all items in it.
|
||||
func (p *NamePrinter) PrintObj(obj runtime.Object, w io.Writer) error {
|
||||
// we use reflect.Indirect here in order to obtain the actual value from a pointer.
|
||||
// using reflect.Indirect indiscriminately is valid here, as all runtime.Objects are supposed to be pointers.
|
||||
// we need an actual value in order to retrieve the package path for an object.
|
||||
if InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) {
|
||||
return fmt.Errorf(InternalObjectPrinterErr)
|
||||
}
|
||||
|
||||
if meta.IsListType(obj) {
|
||||
// we allow unstructured lists for now because they always contain the GVK information. We should chase down
|
||||
// callers and stop them from passing unflattened lists
|
||||
// TODO chase the caller that is setting this and remove it.
|
||||
if _, ok := obj.(*unstructured.UnstructuredList); !ok {
|
||||
return fmt.Errorf("list types are not supported by name printing: %T", obj)
|
||||
}
|
||||
|
||||
items, err := meta.ExtractList(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, obj := range items {
|
||||
if err := p.PrintObj(obj, w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if obj.GetObjectKind().GroupVersionKind().Empty() {
|
||||
return fmt.Errorf("missing apiVersion or kind; try GetObjectKind().SetGroupVersionKind() if you know the type")
|
||||
}
|
||||
|
||||
name := "<unknown>"
|
||||
if acc, err := meta.Accessor(obj); err == nil {
|
||||
if n := acc.GetName(); len(n) > 0 {
|
||||
name = n
|
||||
}
|
||||
}
|
||||
|
||||
return printObj(w, name, p.Operation, p.ShortOutput, GetObjectGroupKind(obj))
|
||||
}
|
||||
|
||||
func GetObjectGroupKind(obj runtime.Object) schema.GroupKind {
|
||||
if obj == nil {
|
||||
return schema.GroupKind{Kind: "<unknown>"}
|
||||
}
|
||||
groupVersionKind := obj.GetObjectKind().GroupVersionKind()
|
||||
if len(groupVersionKind.Kind) > 0 {
|
||||
return groupVersionKind.GroupKind()
|
||||
}
|
||||
|
||||
if uns, ok := obj.(*unstructured.Unstructured); ok {
|
||||
if len(uns.GroupVersionKind().Kind) > 0 {
|
||||
return uns.GroupVersionKind().GroupKind()
|
||||
}
|
||||
}
|
||||
|
||||
return schema.GroupKind{Kind: "<unknown>"}
|
||||
}
|
||||
|
||||
func printObj(w io.Writer, name string, operation string, shortOutput bool, groupKind schema.GroupKind) error {
|
||||
if len(groupKind.Kind) == 0 {
|
||||
return fmt.Errorf("missing kind for resource with name %v", name)
|
||||
}
|
||||
|
||||
if len(operation) > 0 {
|
||||
operation = " " + operation
|
||||
}
|
||||
|
||||
if shortOutput {
|
||||
operation = ""
|
||||
}
|
||||
|
||||
if len(groupKind.Group) == 0 {
|
||||
fmt.Fprintf(w, "%s/%s%s\n", strings.ToLower(groupKind.Kind), name, operation)
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s.%s/%s%s\n", strings.ToLower(groupKind.Kind), groupKind.Group, name, operation)
|
||||
return nil
|
||||
}
|
60
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers/sourcechecker.go
generated
vendored
Normal file
60
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers/sourcechecker.go
generated
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package printers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
InternalObjectPrinterErr = "a versioned object must be passed to a printer"
|
||||
|
||||
// disallowedPackagePrefixes contains regular expression templates
|
||||
// for object package paths that are not allowed by printers.
|
||||
disallowedPackagePrefixes = []string{
|
||||
"k8s.io/kubernetes/pkg/apis/",
|
||||
}
|
||||
)
|
||||
|
||||
var InternalObjectPreventer = &illegalPackageSourceChecker{disallowedPackagePrefixes}
|
||||
|
||||
func IsInternalObjectError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return err.Error() == InternalObjectPrinterErr
|
||||
}
|
||||
|
||||
// illegalPackageSourceChecker compares a given
|
||||
// object's package path, and determines if the
|
||||
// object originates from a disallowed source.
|
||||
type illegalPackageSourceChecker struct {
|
||||
// disallowedPrefixes is a slice of disallowed package path
|
||||
// prefixes for a given runtime.Object that we are printing.
|
||||
disallowedPrefixes []string
|
||||
}
|
||||
|
||||
func (c *illegalPackageSourceChecker) IsForbidden(pkgPath string) bool {
|
||||
for _, forbiddenPrefix := range c.disallowedPrefixes {
|
||||
if strings.HasPrefix(pkgPath, forbiddenPrefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
66
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers/sourcechecker_test.go
generated
vendored
Normal file
66
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers/sourcechecker_test.go
generated
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
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 printers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIllegalPackageSourceChecker(t *testing.T) {
|
||||
disallowedPrefixes := []string{
|
||||
"foo/bar",
|
||||
"k8s.io/foo/bar/vendor/k8s.io/baz/buz",
|
||||
"bar/foo/baz",
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
pkgPath string
|
||||
shouldBeAllowed bool
|
||||
}{
|
||||
{
|
||||
name: "package path beginning with forbidden prefix is rejected",
|
||||
pkgPath: "foo/bar/baz/buz",
|
||||
shouldBeAllowed: false,
|
||||
},
|
||||
{
|
||||
name: "package path not fully matching forbidden prefix is allowed",
|
||||
pkgPath: "bar/foo",
|
||||
shouldBeAllowed: true,
|
||||
},
|
||||
{
|
||||
name: "package path containing forbidden prefix (not as prefix) is allowed",
|
||||
pkgPath: "k8s.io/bar/foo/baz/etc",
|
||||
shouldBeAllowed: true,
|
||||
},
|
||||
}
|
||||
|
||||
checker := &illegalPackageSourceChecker{disallowedPrefixes}
|
||||
|
||||
for _, tc := range testCases {
|
||||
if checker.IsForbidden(tc.pkgPath) {
|
||||
if tc.shouldBeAllowed {
|
||||
t.Fatalf("expected package path %q to have been allowed", tc.pkgPath)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if !tc.shouldBeAllowed {
|
||||
t.Fatalf("expected package path %q to have been rejected", tc.pkgPath)
|
||||
}
|
||||
}
|
||||
}
|
118
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers/template.go
generated
vendored
Normal file
118
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers/template.go
generated
vendored
Normal file
@ -0,0 +1,118 @@
|
||||
/*
|
||||
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 printers
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"text/template"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// GoTemplatePrinter is an implementation of ResourcePrinter which formats data with a Go Template.
|
||||
type GoTemplatePrinter struct {
|
||||
rawTemplate string
|
||||
template *template.Template
|
||||
}
|
||||
|
||||
func NewGoTemplatePrinter(tmpl []byte) (*GoTemplatePrinter, error) {
|
||||
t, err := template.New("output").
|
||||
Funcs(template.FuncMap{
|
||||
"exists": exists,
|
||||
"base64decode": base64decode,
|
||||
}).
|
||||
Parse(string(tmpl))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &GoTemplatePrinter{
|
||||
rawTemplate: string(tmpl),
|
||||
template: t,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AllowMissingKeys tells the template engine if missing keys are allowed.
|
||||
func (p *GoTemplatePrinter) AllowMissingKeys(allow bool) {
|
||||
if allow {
|
||||
p.template.Option("missingkey=default")
|
||||
} else {
|
||||
p.template.Option("missingkey=error")
|
||||
}
|
||||
}
|
||||
|
||||
// PrintObj formats the obj with the Go Template.
|
||||
func (p *GoTemplatePrinter) PrintObj(obj runtime.Object, w io.Writer) error {
|
||||
if InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) {
|
||||
return fmt.Errorf(InternalObjectPrinterErr)
|
||||
}
|
||||
|
||||
var data []byte
|
||||
var err error
|
||||
data, err = json.Marshal(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out := map[string]interface{}{}
|
||||
if err := json.Unmarshal(data, &out); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = p.safeExecute(w, out); err != nil {
|
||||
// It is way easier to debug this stuff when it shows up in
|
||||
// stdout instead of just stdin. So in addition to returning
|
||||
// a nice error, also print useful stuff with the writer.
|
||||
fmt.Fprintf(w, "Error executing template: %v. Printing more information for debugging the template:\n", err)
|
||||
fmt.Fprintf(w, "\ttemplate was:\n\t\t%v\n", p.rawTemplate)
|
||||
fmt.Fprintf(w, "\traw data was:\n\t\t%v\n", string(data))
|
||||
fmt.Fprintf(w, "\tobject given to template engine was:\n\t\t%+v\n\n", out)
|
||||
return fmt.Errorf("error executing template %q: %v", p.rawTemplate, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// safeExecute tries to execute the template, but catches panics and returns an error
|
||||
// should the template engine panic.
|
||||
func (p *GoTemplatePrinter) safeExecute(w io.Writer, obj interface{}) error {
|
||||
var panicErr error
|
||||
// Sorry for the double anonymous function. There's probably a clever way
|
||||
// to do this that has the defer'd func setting the value to be returned, but
|
||||
// that would be even less obvious.
|
||||
retErr := func() error {
|
||||
defer func() {
|
||||
if x := recover(); x != nil {
|
||||
panicErr = fmt.Errorf("caught panic: %+v", x)
|
||||
}
|
||||
}()
|
||||
return p.template.Execute(w, obj)
|
||||
}()
|
||||
if panicErr != nil {
|
||||
return panicErr
|
||||
}
|
||||
return retErr
|
||||
}
|
||||
|
||||
func base64decode(v string) (string, error) {
|
||||
data, err := base64.StdEncoding.DecodeString(v)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("base64 decode failed: %v", err)
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
102
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers/template_test.go
generated
vendored
Normal file
102
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers/template_test.go
generated
vendored
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
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 printers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
func TestTemplate(t *testing.T) {
|
||||
testCase := []struct {
|
||||
name string
|
||||
template string
|
||||
obj runtime.Object
|
||||
expectOut string
|
||||
expectErr func(error) (string, bool)
|
||||
}{
|
||||
{
|
||||
name: "support base64 decoding of secret data",
|
||||
template: "{{ .data.username | base64decode }}",
|
||||
obj: &v1.Secret{
|
||||
Data: map[string][]byte{
|
||||
"username": []byte("hunter"),
|
||||
},
|
||||
},
|
||||
expectOut: "hunter",
|
||||
},
|
||||
{
|
||||
name: "test error path for base64 decoding",
|
||||
template: "{{ .data.username | base64decode }}",
|
||||
obj: &badlyMarshaledSecret{},
|
||||
expectErr: func(err error) (string, bool) {
|
||||
matched := strings.Contains(err.Error(), "base64 decode")
|
||||
return "a base64 decode error", matched
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range testCase {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
buffer := &bytes.Buffer{}
|
||||
|
||||
p, err := NewGoTemplatePrinter([]byte(test.template))
|
||||
if err != nil {
|
||||
if test.expectErr == nil {
|
||||
t.Errorf("[%s]expected success but got:\n %v\n", test.name, err)
|
||||
return
|
||||
}
|
||||
if expected, ok := test.expectErr(err); !ok {
|
||||
t.Errorf("[%s]expect:\n %v\n but got:\n %v\n", test.name, expected, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
err = p.PrintObj(test.obj, buffer)
|
||||
if err != nil {
|
||||
if test.expectErr == nil {
|
||||
t.Errorf("[%s]expected success but got:\n %v\n", test.name, err)
|
||||
return
|
||||
}
|
||||
if expected, ok := test.expectErr(err); !ok {
|
||||
t.Errorf("[%s]expect:\n %v\n but got:\n %v\n", test.name, expected, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if test.expectErr != nil {
|
||||
t.Errorf("[%s]expect:\n error\n but got:\n no error\n", test.name)
|
||||
return
|
||||
}
|
||||
|
||||
if test.expectOut != buffer.String() {
|
||||
t.Errorf("[%s]expect:\n %v\n but got:\n %v\n", test.name, test.expectOut, buffer.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type badlyMarshaledSecret struct {
|
||||
v1.Secret
|
||||
}
|
||||
|
||||
func (a badlyMarshaledSecret) MarshalJSON() ([]byte, error) {
|
||||
return []byte(`{"apiVersion":"v1","data":{"username":"--THIS IS NOT BASE64--"},"kind":"Secret"}`), nil
|
||||
}
|
95
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers/typesetter.go
generated
vendored
Normal file
95
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers/typesetter.go
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
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 printers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// TypeSetterPrinter is an implementation of ResourcePrinter wraps another printer with types set on the objects
|
||||
type TypeSetterPrinter struct {
|
||||
Delegate ResourcePrinter
|
||||
|
||||
Typer runtime.ObjectTyper
|
||||
}
|
||||
|
||||
// NewTypeSetter constructs a wrapping printer with required params
|
||||
func NewTypeSetter(typer runtime.ObjectTyper) *TypeSetterPrinter {
|
||||
return &TypeSetterPrinter{Typer: typer}
|
||||
}
|
||||
|
||||
// PrintObj is an implementation of ResourcePrinter.PrintObj which sets type information on the obj for the duration
|
||||
// of printing. It is NOT threadsafe.
|
||||
func (p *TypeSetterPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
|
||||
if obj == nil {
|
||||
return p.Delegate.PrintObj(obj, w)
|
||||
}
|
||||
if !obj.GetObjectKind().GroupVersionKind().Empty() {
|
||||
return p.Delegate.PrintObj(obj, w)
|
||||
}
|
||||
|
||||
// we were empty coming in, make sure we're empty going out. This makes the call thread-unsafe
|
||||
defer func() {
|
||||
obj.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{})
|
||||
}()
|
||||
|
||||
gvks, _, err := p.Typer.ObjectKinds(obj)
|
||||
if err != nil {
|
||||
// printers wrapped by us expect to find the type information present
|
||||
return fmt.Errorf("missing apiVersion or kind and cannot assign it; %v", err)
|
||||
}
|
||||
|
||||
for _, gvk := range gvks {
|
||||
if len(gvk.Kind) == 0 {
|
||||
continue
|
||||
}
|
||||
if len(gvk.Version) == 0 || gvk.Version == runtime.APIVersionInternal {
|
||||
continue
|
||||
}
|
||||
obj.GetObjectKind().SetGroupVersionKind(gvk)
|
||||
break
|
||||
}
|
||||
|
||||
return p.Delegate.PrintObj(obj, w)
|
||||
}
|
||||
|
||||
// ToPrinter returns a printer (not threadsafe!) that has been wrapped
|
||||
func (p *TypeSetterPrinter) ToPrinter(delegate ResourcePrinter) ResourcePrinter {
|
||||
if p == nil {
|
||||
return delegate
|
||||
}
|
||||
|
||||
p.Delegate = delegate
|
||||
return p
|
||||
}
|
||||
|
||||
// WrapToPrinter wraps the common ToPrinter method
|
||||
func (p *TypeSetterPrinter) WrapToPrinter(delegate ResourcePrinter, err error) (ResourcePrinter, error) {
|
||||
if err != nil {
|
||||
return delegate, err
|
||||
}
|
||||
if p == nil {
|
||||
return delegate, nil
|
||||
}
|
||||
|
||||
p.Delegate = delegate
|
||||
return p, nil
|
||||
}
|
199
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/record_flags.go
generated
vendored
Normal file
199
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/record_flags.go
generated
vendored
Normal file
@ -0,0 +1,199 @@
|
||||
/*
|
||||
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 genericclioptions
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/evanphx/json-patch"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
)
|
||||
|
||||
// ChangeCauseAnnotation is the annotation indicating a guess at "why" something was changed
|
||||
const ChangeCauseAnnotation = "kubernetes.io/change-cause"
|
||||
|
||||
// RecordFlags contains all flags associated with the "--record" operation
|
||||
type RecordFlags struct {
|
||||
// Record indicates the state of the recording flag. It is a pointer so a caller can opt out or rebind
|
||||
Record *bool
|
||||
|
||||
changeCause string
|
||||
}
|
||||
|
||||
// ToRecorder returns a ChangeCause recorder if --record=false was not
|
||||
// explicitly given by the user
|
||||
func (f *RecordFlags) ToRecorder() (Recorder, error) {
|
||||
if f == nil {
|
||||
return NoopRecorder{}, nil
|
||||
}
|
||||
|
||||
shouldRecord := false
|
||||
if f.Record != nil {
|
||||
shouldRecord = *f.Record
|
||||
}
|
||||
|
||||
// if flag was explicitly set to false by the user,
|
||||
// do not record
|
||||
if !shouldRecord {
|
||||
return NoopRecorder{}, nil
|
||||
}
|
||||
|
||||
return &ChangeCauseRecorder{
|
||||
changeCause: f.changeCause,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Complete is called before the command is run, but after it is invoked to finish the state of the struct before use.
|
||||
func (f *RecordFlags) Complete(cmd *cobra.Command) error {
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
f.changeCause = parseCommandArguments(cmd)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *RecordFlags) CompleteWithChangeCause(cause string) error {
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
f.changeCause = cause
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddFlags binds the requested flags to the provided flagset
|
||||
// TODO have this only take a flagset
|
||||
func (f *RecordFlags) AddFlags(cmd *cobra.Command) {
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if f.Record != nil {
|
||||
cmd.Flags().BoolVar(f.Record, "record", *f.Record, "Record current kubectl command in the resource annotation. If set to false, do not record the command. If set to true, record the command. If not set, default to updating the existing annotation value only if one already exists.")
|
||||
}
|
||||
}
|
||||
|
||||
// NewRecordFlags provides a RecordFlags with reasonable default values set for use
|
||||
func NewRecordFlags() *RecordFlags {
|
||||
record := false
|
||||
|
||||
return &RecordFlags{
|
||||
Record: &record,
|
||||
}
|
||||
}
|
||||
|
||||
// Recorder is used to record why a runtime.Object was changed in an annotation.
|
||||
type Recorder interface {
|
||||
// Record records why a runtime.Object was changed in an annotation.
|
||||
Record(runtime.Object) error
|
||||
MakeRecordMergePatch(runtime.Object) ([]byte, error)
|
||||
}
|
||||
|
||||
// NoopRecorder does nothing. It is a "do nothing" that can be returned so code doesn't switch on it.
|
||||
type NoopRecorder struct{}
|
||||
|
||||
// Record implements Recorder
|
||||
func (r NoopRecorder) Record(obj runtime.Object) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MakeRecordMergePatch implements Recorder
|
||||
func (r NoopRecorder) MakeRecordMergePatch(obj runtime.Object) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ChangeCauseRecorder annotates a "change-cause" to an input runtime object
|
||||
type ChangeCauseRecorder struct {
|
||||
changeCause string
|
||||
}
|
||||
|
||||
// Record annotates a "change-cause" to a given info if either "shouldRecord" is true,
|
||||
// or the resource info previously contained a "change-cause" annotation.
|
||||
func (r *ChangeCauseRecorder) Record(obj runtime.Object) error {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
annotations := accessor.GetAnnotations()
|
||||
if annotations == nil {
|
||||
annotations = make(map[string]string)
|
||||
}
|
||||
annotations[ChangeCauseAnnotation] = r.changeCause
|
||||
accessor.SetAnnotations(annotations)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MakeRecordMergePatch produces a merge patch for updating the recording annotation.
|
||||
func (r *ChangeCauseRecorder) MakeRecordMergePatch(obj runtime.Object) ([]byte, error) {
|
||||
// copy so we don't mess with the original
|
||||
objCopy := obj.DeepCopyObject()
|
||||
if err := r.Record(objCopy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oldData, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newData, err := json.Marshal(objCopy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return jsonpatch.CreateMergePatch(oldData, newData)
|
||||
}
|
||||
|
||||
// parseCommandArguments will stringify and return all environment arguments ie. a command run by a client
|
||||
// using the factory.
|
||||
// Set showSecrets false to filter out stuff like secrets.
|
||||
func parseCommandArguments(cmd *cobra.Command) string {
|
||||
if len(os.Args) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
flags := ""
|
||||
parseFunc := func(flag *pflag.Flag, value string) error {
|
||||
flags = flags + " --" + flag.Name
|
||||
if set, ok := flag.Annotations["classified"]; !ok || len(set) == 0 {
|
||||
flags = flags + "=" + value
|
||||
} else {
|
||||
flags = flags + "=CLASSIFIED"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
err = cmd.Flags().ParseAll(os.Args[1:], parseFunc)
|
||||
if err != nil || !cmd.Flags().Parsed() {
|
||||
return ""
|
||||
}
|
||||
|
||||
args := ""
|
||||
if arguments := cmd.Flags().Args(); len(arguments) > 0 {
|
||||
args = " " + strings.Join(arguments, " ")
|
||||
}
|
||||
|
||||
base := filepath.Base(os.Args[0])
|
||||
return base + args + flags
|
||||
}
|
95
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/BUILD
generated
vendored
Normal file
95
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/BUILD
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"builder.go",
|
||||
"client.go",
|
||||
"doc.go",
|
||||
"fake.go",
|
||||
"helper.go",
|
||||
"interfaces.go",
|
||||
"mapper.go",
|
||||
"result.go",
|
||||
"scheme.go",
|
||||
"selector.go",
|
||||
"visitor.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//vendor/golang.org/x/text/encoding/unicode:go_default_library",
|
||||
"//vendor/golang.org/x/text/transform:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
|
||||
"//vendor/k8s.io/client-go/discovery:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
"//vendor/k8s.io/client-go/restmapper:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"builder_test.go",
|
||||
"helper_test.go",
|
||||
"visitor_test.go",
|
||||
],
|
||||
data = [
|
||||
"//test/e2e/testing-manifests:all-srcs",
|
||||
"//test/fixtures",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//vendor/github.com/davecgh/go-spew/spew:go_default_library",
|
||||
"//vendor/github.com/ghodss/yaml:go_default_library",
|
||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta/testrestmapper:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer/streaming:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest/fake:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest/watch:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/testing: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"],
|
||||
)
|
1143
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/builder.go
generated
vendored
Normal file
1143
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/builder.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1443
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/builder_test.go
generated
vendored
Normal file
1443
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/builder_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
58
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/client.go
generated
vendored
Normal file
58
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/client.go
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package resource
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// TODO require negotiatedSerializer. leaving it optional lets us plumb current behavior and deal with the difference after major plumbing is complete
|
||||
func (clientConfigFn ClientConfigFunc) clientForGroupVersion(gv schema.GroupVersion, negotiatedSerializer runtime.NegotiatedSerializer) (RESTClient, error) {
|
||||
cfg, err := clientConfigFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if negotiatedSerializer != nil {
|
||||
cfg.ContentConfig.NegotiatedSerializer = negotiatedSerializer
|
||||
}
|
||||
cfg.GroupVersion = &gv
|
||||
if len(gv.Group) == 0 {
|
||||
cfg.APIPath = "/api"
|
||||
} else {
|
||||
cfg.APIPath = "/apis"
|
||||
}
|
||||
|
||||
return rest.RESTClientFor(cfg)
|
||||
}
|
||||
|
||||
func (clientConfigFn ClientConfigFunc) unstructuredClientForGroupVersion(gv schema.GroupVersion) (RESTClient, error) {
|
||||
cfg, err := clientConfigFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.ContentConfig = UnstructuredPlusDefaultContentConfig()
|
||||
cfg.GroupVersion = &gv
|
||||
if len(gv.Group) == 0 {
|
||||
cfg.APIPath = "/api"
|
||||
} else {
|
||||
cfg.APIPath = "/apis"
|
||||
}
|
||||
|
||||
return rest.RESTClientFor(cfg)
|
||||
}
|
24
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/doc.go
generated
vendored
Normal file
24
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/doc.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
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 resource assists clients in dealing with RESTful objects that match the
|
||||
// Kubernetes API conventions. The Helper object provides simple CRUD operations
|
||||
// on resources. The Visitor interface makes it easy to deal with multiple resources
|
||||
// in bulk for retrieval and operation. The Builder object simplifies converting
|
||||
// standard command line arguments and parameters into a Visitor that can iterate
|
||||
// over all of the identified resources, whether on the server or on the local
|
||||
// filesystem.
|
||||
package resource
|
40
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/fake.go
generated
vendored
Normal file
40
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/fake.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
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 resource
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/restmapper"
|
||||
)
|
||||
|
||||
// FakeCategoryExpander is for testing only
|
||||
var FakeCategoryExpander restmapper.CategoryExpander = restmapper.SimpleCategoryExpander{
|
||||
Expansions: map[string][]schema.GroupResource{
|
||||
"all": {
|
||||
{Group: "", Resource: "pods"},
|
||||
{Group: "", Resource: "replicationcontrollers"},
|
||||
{Group: "", Resource: "services"},
|
||||
{Group: "apps", Resource: "statefulsets"},
|
||||
{Group: "autoscaling", Resource: "horizontalpodautoscalers"},
|
||||
{Group: "batch", Resource: "jobs"},
|
||||
{Group: "batch", Resource: "cronjobs"},
|
||||
{Group: "extensions", Resource: "daemonsets"},
|
||||
{Group: "extensions", Resource: "deployments"},
|
||||
{Group: "extensions", Resource: "replicasets"},
|
||||
},
|
||||
},
|
||||
}
|
172
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/helper.go
generated
vendored
Normal file
172
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/helper.go
generated
vendored
Normal file
@ -0,0 +1,172 @@
|
||||
/*
|
||||
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 resource
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
)
|
||||
|
||||
var metadataAccessor = meta.NewAccessor()
|
||||
|
||||
// Helper provides methods for retrieving or mutating a RESTful
|
||||
// resource.
|
||||
type Helper struct {
|
||||
// The name of this resource as the server would recognize it
|
||||
Resource string
|
||||
// A RESTClient capable of mutating this resource.
|
||||
RESTClient RESTClient
|
||||
// True if the resource type is scoped to namespaces
|
||||
NamespaceScoped bool
|
||||
}
|
||||
|
||||
// NewHelper creates a Helper from a ResourceMapping
|
||||
func NewHelper(client RESTClient, mapping *meta.RESTMapping) *Helper {
|
||||
return &Helper{
|
||||
Resource: mapping.Resource.Resource,
|
||||
RESTClient: client,
|
||||
NamespaceScoped: mapping.Scope.Name() == meta.RESTScopeNameNamespace,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Helper) Get(namespace, name string, export bool) (runtime.Object, error) {
|
||||
req := m.RESTClient.Get().
|
||||
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
||||
Resource(m.Resource).
|
||||
Name(name)
|
||||
if export {
|
||||
// TODO: I should be part of GetOptions
|
||||
req.Param("export", strconv.FormatBool(export))
|
||||
}
|
||||
return req.Do().Get()
|
||||
}
|
||||
|
||||
func (m *Helper) List(namespace, apiVersion string, export bool, options *metav1.ListOptions) (runtime.Object, error) {
|
||||
req := m.RESTClient.Get().
|
||||
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
||||
Resource(m.Resource).
|
||||
VersionedParams(options, metav1.ParameterCodec)
|
||||
if export {
|
||||
// TODO: I should be part of ListOptions
|
||||
req.Param("export", strconv.FormatBool(export))
|
||||
}
|
||||
return req.Do().Get()
|
||||
}
|
||||
|
||||
func (m *Helper) Watch(namespace, apiVersion string, options *metav1.ListOptions) (watch.Interface, error) {
|
||||
options.Watch = true
|
||||
return m.RESTClient.Get().
|
||||
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
||||
Resource(m.Resource).
|
||||
VersionedParams(options, metav1.ParameterCodec).
|
||||
Watch()
|
||||
}
|
||||
|
||||
func (m *Helper) WatchSingle(namespace, name, resourceVersion string) (watch.Interface, error) {
|
||||
return m.RESTClient.Get().
|
||||
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
||||
Resource(m.Resource).
|
||||
VersionedParams(&metav1.ListOptions{
|
||||
ResourceVersion: resourceVersion,
|
||||
Watch: true,
|
||||
FieldSelector: fields.OneTermEqualSelector("metadata.name", name).String(),
|
||||
}, metav1.ParameterCodec).
|
||||
Watch()
|
||||
}
|
||||
|
||||
func (m *Helper) Delete(namespace, name string) error {
|
||||
return m.DeleteWithOptions(namespace, name, nil)
|
||||
}
|
||||
|
||||
func (m *Helper) DeleteWithOptions(namespace, name string, options *metav1.DeleteOptions) error {
|
||||
return m.RESTClient.Delete().
|
||||
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
||||
Resource(m.Resource).
|
||||
Name(name).
|
||||
Body(options).
|
||||
Do().
|
||||
Error()
|
||||
}
|
||||
|
||||
func (m *Helper) Create(namespace string, modify bool, obj runtime.Object) (runtime.Object, error) {
|
||||
if modify {
|
||||
// Attempt to version the object based on client logic.
|
||||
version, err := metadataAccessor.ResourceVersion(obj)
|
||||
if err != nil {
|
||||
// We don't know how to clear the version on this object, so send it to the server as is
|
||||
return m.createResource(m.RESTClient, m.Resource, namespace, obj)
|
||||
}
|
||||
if version != "" {
|
||||
if err := metadataAccessor.SetResourceVersion(obj, ""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m.createResource(m.RESTClient, m.Resource, namespace, obj)
|
||||
}
|
||||
|
||||
func (m *Helper) createResource(c RESTClient, resource, namespace string, obj runtime.Object) (runtime.Object, error) {
|
||||
return c.Post().NamespaceIfScoped(namespace, m.NamespaceScoped).Resource(resource).Body(obj).Do().Get()
|
||||
}
|
||||
func (m *Helper) Patch(namespace, name string, pt types.PatchType, data []byte) (runtime.Object, error) {
|
||||
return m.RESTClient.Patch(pt).
|
||||
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
||||
Resource(m.Resource).
|
||||
Name(name).
|
||||
Body(data).
|
||||
Do().
|
||||
Get()
|
||||
}
|
||||
|
||||
func (m *Helper) Replace(namespace, name string, overwrite bool, obj runtime.Object) (runtime.Object, error) {
|
||||
c := m.RESTClient
|
||||
|
||||
// Attempt to version the object based on client logic.
|
||||
version, err := metadataAccessor.ResourceVersion(obj)
|
||||
if err != nil {
|
||||
// We don't know how to version this object, so send it to the server as is
|
||||
return m.replaceResource(c, m.Resource, namespace, name, obj)
|
||||
}
|
||||
if version == "" && overwrite {
|
||||
// Retrieve the current version of the object to overwrite the server object
|
||||
serverObj, err := c.Get().NamespaceIfScoped(namespace, m.NamespaceScoped).Resource(m.Resource).Name(name).Do().Get()
|
||||
if err != nil {
|
||||
// The object does not exist, but we want it to be created
|
||||
return m.replaceResource(c, m.Resource, namespace, name, obj)
|
||||
}
|
||||
serverVersion, err := metadataAccessor.ResourceVersion(serverObj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := metadataAccessor.SetResourceVersion(obj, serverVersion); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return m.replaceResource(c, m.Resource, namespace, name, obj)
|
||||
}
|
||||
|
||||
func (m *Helper) replaceResource(c RESTClient, resource, namespace, name string, obj runtime.Object) (runtime.Object, error) {
|
||||
return c.Put().NamespaceIfScoped(namespace, m.NamespaceScoped).Resource(resource).Name(name).Body(obj).Do().Get()
|
||||
}
|
596
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/helper_test.go
generated
vendored
Normal file
596
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/helper_test.go
generated
vendored
Normal file
@ -0,0 +1,596 @@
|
||||
/*
|
||||
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 resource
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
|
||||
// TODO we need to remove this linkage and create our own scheme
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
)
|
||||
|
||||
func objBody(obj runtime.Object) io.ReadCloser {
|
||||
return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(corev1Codec, obj))))
|
||||
}
|
||||
|
||||
func header() http.Header {
|
||||
header := http.Header{}
|
||||
header.Set("Content-Type", runtime.ContentTypeJSON)
|
||||
return header
|
||||
}
|
||||
|
||||
// splitPath returns the segments for a URL path.
|
||||
func splitPath(path string) []string {
|
||||
path = strings.Trim(path, "/")
|
||||
if path == "" {
|
||||
return []string{}
|
||||
}
|
||||
return strings.Split(path, "/")
|
||||
}
|
||||
|
||||
// V1DeepEqualSafePodSpec returns a PodSpec which is ready to be used with apiequality.Semantic.DeepEqual
|
||||
func V1DeepEqualSafePodSpec() corev1.PodSpec {
|
||||
grace := int64(30)
|
||||
return corev1.PodSpec{
|
||||
RestartPolicy: corev1.RestartPolicyAlways,
|
||||
DNSPolicy: corev1.DNSClusterFirst,
|
||||
TerminationGracePeriodSeconds: &grace,
|
||||
SecurityContext: &corev1.PodSecurityContext{},
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperDelete(t *testing.T) {
|
||||
tests := []struct {
|
||||
Err bool
|
||||
Req func(*http.Request) bool
|
||||
Resp *http.Response
|
||||
HttpErr error
|
||||
}{
|
||||
{
|
||||
HttpErr: errors.New("failure"),
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusNotFound,
|
||||
Header: header(),
|
||||
Body: objBody(&metav1.Status{Status: metav1.StatusFailure}),
|
||||
},
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: header(),
|
||||
Body: objBody(&metav1.Status{Status: metav1.StatusSuccess}),
|
||||
},
|
||||
Req: func(req *http.Request) bool {
|
||||
if req.Method != "DELETE" {
|
||||
t.Errorf("unexpected method: %#v", req)
|
||||
return false
|
||||
}
|
||||
parts := splitPath(req.URL.Path)
|
||||
if len(parts) < 3 {
|
||||
t.Errorf("expected URL path to have 3 parts: %s", req.URL.Path)
|
||||
return false
|
||||
}
|
||||
if parts[1] != "bar" {
|
||||
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||
return false
|
||||
}
|
||||
if parts[2] != "foo" {
|
||||
t.Errorf("url doesn't contain name: %#v", req)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
client := &fake.RESTClient{
|
||||
NegotiatedSerializer: scheme.Codecs,
|
||||
Resp: test.Resp,
|
||||
Err: test.HttpErr,
|
||||
}
|
||||
modifier := &Helper{
|
||||
RESTClient: client,
|
||||
NamespaceScoped: true,
|
||||
}
|
||||
err := modifier.Delete("bar", "foo")
|
||||
if (err != nil) != test.Err {
|
||||
t.Errorf("unexpected error: %t %v", test.Err, err)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if test.Req != nil && !test.Req(client.Req) {
|
||||
t.Errorf("unexpected request: %#v", client.Req)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperCreate(t *testing.T) {
|
||||
expectPost := func(req *http.Request) bool {
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("unexpected method: %#v", req)
|
||||
return false
|
||||
}
|
||||
parts := splitPath(req.URL.Path)
|
||||
if parts[1] != "bar" {
|
||||
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
Resp *http.Response
|
||||
HttpErr error
|
||||
Modify bool
|
||||
Object runtime.Object
|
||||
|
||||
ExpectObject runtime.Object
|
||||
Err bool
|
||||
Req func(*http.Request) bool
|
||||
}{
|
||||
{
|
||||
HttpErr: errors.New("failure"),
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusNotFound,
|
||||
Header: header(),
|
||||
Body: objBody(&metav1.Status{Status: metav1.StatusFailure}),
|
||||
},
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: header(),
|
||||
Body: objBody(&metav1.Status{Status: metav1.StatusSuccess}),
|
||||
},
|
||||
Object: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
|
||||
ExpectObject: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
|
||||
Req: expectPost,
|
||||
},
|
||||
{
|
||||
Modify: false,
|
||||
Object: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
|
||||
ExpectObject: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
|
||||
Resp: &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})},
|
||||
Req: expectPost,
|
||||
},
|
||||
{
|
||||
Modify: true,
|
||||
Object: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"},
|
||||
Spec: V1DeepEqualSafePodSpec(),
|
||||
},
|
||||
ExpectObject: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Spec: V1DeepEqualSafePodSpec(),
|
||||
},
|
||||
Resp: &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})},
|
||||
Req: expectPost,
|
||||
},
|
||||
}
|
||||
for i, test := range tests {
|
||||
client := &fake.RESTClient{
|
||||
GroupVersion: corev1GV,
|
||||
NegotiatedSerializer: scheme.Codecs,
|
||||
Resp: test.Resp,
|
||||
Err: test.HttpErr,
|
||||
}
|
||||
modifier := &Helper{
|
||||
RESTClient: client,
|
||||
NamespaceScoped: true,
|
||||
}
|
||||
_, err := modifier.Create("bar", test.Modify, test.Object)
|
||||
if (err != nil) != test.Err {
|
||||
t.Errorf("%d: unexpected error: %t %v", i, test.Err, err)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if test.Req != nil && !test.Req(client.Req) {
|
||||
t.Errorf("%d: unexpected request: %#v", i, client.Req)
|
||||
}
|
||||
body, err := ioutil.ReadAll(client.Req.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("%d: unexpected error: %#v", i, err)
|
||||
}
|
||||
t.Logf("got body: %s", string(body))
|
||||
expect := []byte{}
|
||||
if test.ExpectObject != nil {
|
||||
expect = []byte(runtime.EncodeOrDie(corev1Codec, test.ExpectObject))
|
||||
}
|
||||
if !reflect.DeepEqual(expect, body) {
|
||||
t.Errorf("%d: unexpected body: %s (expected %s)", i, string(body), string(expect))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperGet(t *testing.T) {
|
||||
tests := []struct {
|
||||
Err bool
|
||||
Req func(*http.Request) bool
|
||||
Resp *http.Response
|
||||
HttpErr error
|
||||
}{
|
||||
{
|
||||
HttpErr: errors.New("failure"),
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusNotFound,
|
||||
Header: header(),
|
||||
Body: objBody(&metav1.Status{Status: metav1.StatusFailure}),
|
||||
},
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: header(),
|
||||
Body: objBody(&corev1.Pod{TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"}, ObjectMeta: metav1.ObjectMeta{Name: "foo"}}),
|
||||
},
|
||||
Req: func(req *http.Request) bool {
|
||||
if req.Method != "GET" {
|
||||
t.Errorf("unexpected method: %#v", req)
|
||||
return false
|
||||
}
|
||||
parts := splitPath(req.URL.Path)
|
||||
if parts[1] != "bar" {
|
||||
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||
return false
|
||||
}
|
||||
if parts[2] != "foo" {
|
||||
t.Errorf("url doesn't contain name: %#v", req)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
for i, test := range tests {
|
||||
client := &fake.RESTClient{
|
||||
GroupVersion: corev1GV,
|
||||
NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs},
|
||||
Resp: test.Resp,
|
||||
Err: test.HttpErr,
|
||||
}
|
||||
modifier := &Helper{
|
||||
RESTClient: client,
|
||||
NamespaceScoped: true,
|
||||
}
|
||||
obj, err := modifier.Get("bar", "foo", false)
|
||||
|
||||
if (err != nil) != test.Err {
|
||||
t.Errorf("unexpected error: %d %t %v", i, test.Err, err)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if obj.(*corev1.Pod).Name != "foo" {
|
||||
t.Errorf("unexpected object: %#v", obj)
|
||||
}
|
||||
if test.Req != nil && !test.Req(client.Req) {
|
||||
t.Errorf("unexpected request: %#v", client.Req)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperList(t *testing.T) {
|
||||
tests := []struct {
|
||||
Err bool
|
||||
Req func(*http.Request) bool
|
||||
Resp *http.Response
|
||||
HttpErr error
|
||||
}{
|
||||
{
|
||||
HttpErr: errors.New("failure"),
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusNotFound,
|
||||
Header: header(),
|
||||
Body: objBody(&metav1.Status{Status: metav1.StatusFailure}),
|
||||
},
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: header(),
|
||||
Body: objBody(&corev1.PodList{
|
||||
Items: []corev1.Pod{{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
Req: func(req *http.Request) bool {
|
||||
if req.Method != "GET" {
|
||||
t.Errorf("unexpected method: %#v", req)
|
||||
return false
|
||||
}
|
||||
if req.URL.Path != "/namespaces/bar" {
|
||||
t.Errorf("url doesn't contain name: %#v", req.URL)
|
||||
return false
|
||||
}
|
||||
if req.URL.Query().Get(metav1.LabelSelectorQueryParam(corev1GV.String())) != labels.SelectorFromSet(labels.Set{"foo": "baz"}).String() {
|
||||
t.Errorf("url doesn't contain query parameters: %#v", req.URL)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
client := &fake.RESTClient{
|
||||
GroupVersion: corev1GV,
|
||||
NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs},
|
||||
Resp: test.Resp,
|
||||
Err: test.HttpErr,
|
||||
}
|
||||
modifier := &Helper{
|
||||
RESTClient: client,
|
||||
NamespaceScoped: true,
|
||||
}
|
||||
obj, err := modifier.List("bar", corev1GV.String(), false, &metav1.ListOptions{LabelSelector: "foo=baz"})
|
||||
if (err != nil) != test.Err {
|
||||
t.Errorf("unexpected error: %t %v", test.Err, err)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if obj.(*corev1.PodList).Items[0].Name != "foo" {
|
||||
t.Errorf("unexpected object: %#v", obj)
|
||||
}
|
||||
if test.Req != nil && !test.Req(client.Req) {
|
||||
t.Errorf("unexpected request: %#v", client.Req)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperListSelectorCombination(t *testing.T) {
|
||||
tests := []struct {
|
||||
Name string
|
||||
Err bool
|
||||
ErrMsg string
|
||||
FieldSelector string
|
||||
LabelSelector string
|
||||
}{
|
||||
{
|
||||
Name: "No selector",
|
||||
Err: false,
|
||||
},
|
||||
{
|
||||
Name: "Only Label Selector",
|
||||
Err: false,
|
||||
LabelSelector: "foo=baz",
|
||||
},
|
||||
{
|
||||
Name: "Only Field Selector",
|
||||
Err: false,
|
||||
FieldSelector: "xyz=zyx",
|
||||
},
|
||||
{
|
||||
Name: "Both Label and Field Selector",
|
||||
Err: false,
|
||||
LabelSelector: "foo=baz",
|
||||
FieldSelector: "xyz=zyx",
|
||||
},
|
||||
}
|
||||
|
||||
resp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: header(),
|
||||
Body: objBody(&corev1.PodList{
|
||||
Items: []corev1.Pod{{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
client := &fake.RESTClient{
|
||||
NegotiatedSerializer: scheme.Codecs,
|
||||
Resp: resp,
|
||||
Err: nil,
|
||||
}
|
||||
modifier := &Helper{
|
||||
RESTClient: client,
|
||||
NamespaceScoped: true,
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
_, err := modifier.List("bar",
|
||||
corev1GV.String(),
|
||||
false,
|
||||
&metav1.ListOptions{LabelSelector: test.LabelSelector, FieldSelector: test.FieldSelector})
|
||||
if test.Err {
|
||||
if err == nil {
|
||||
t.Errorf("%q expected error: %q", test.Name, test.ErrMsg)
|
||||
}
|
||||
if err != nil && err.Error() != test.ErrMsg {
|
||||
t.Errorf("%q expected error: %q", test.Name, test.ErrMsg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperReplace(t *testing.T) {
|
||||
expectPut := func(path string, req *http.Request) bool {
|
||||
if req.Method != "PUT" {
|
||||
t.Errorf("unexpected method: %#v", req)
|
||||
return false
|
||||
}
|
||||
if req.URL.Path != path {
|
||||
t.Errorf("unexpected url: %v", req.URL)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
Resp *http.Response
|
||||
HTTPClient *http.Client
|
||||
HttpErr error
|
||||
Overwrite bool
|
||||
Object runtime.Object
|
||||
Namespace string
|
||||
NamespaceScoped bool
|
||||
|
||||
ExpectPath string
|
||||
ExpectObject runtime.Object
|
||||
Err bool
|
||||
Req func(string, *http.Request) bool
|
||||
}{
|
||||
{
|
||||
Namespace: "bar",
|
||||
NamespaceScoped: true,
|
||||
HttpErr: errors.New("failure"),
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Namespace: "bar",
|
||||
NamespaceScoped: true,
|
||||
Object: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusNotFound,
|
||||
Header: header(),
|
||||
Body: objBody(&metav1.Status{Status: metav1.StatusFailure}),
|
||||
},
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Namespace: "bar",
|
||||
NamespaceScoped: true,
|
||||
Object: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
|
||||
ExpectPath: "/namespaces/bar/foo",
|
||||
ExpectObject: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: header(),
|
||||
Body: objBody(&metav1.Status{Status: metav1.StatusSuccess}),
|
||||
},
|
||||
Req: expectPut,
|
||||
},
|
||||
// namespace scoped resource
|
||||
{
|
||||
Namespace: "bar",
|
||||
NamespaceScoped: true,
|
||||
Object: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Spec: V1DeepEqualSafePodSpec(),
|
||||
},
|
||||
ExpectPath: "/namespaces/bar/foo",
|
||||
ExpectObject: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"},
|
||||
Spec: V1DeepEqualSafePodSpec(),
|
||||
},
|
||||
Overwrite: true,
|
||||
HTTPClient: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
if req.Method == "PUT" {
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})}, nil
|
||||
}
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}})}, nil
|
||||
}),
|
||||
Req: expectPut,
|
||||
},
|
||||
// cluster scoped resource
|
||||
{
|
||||
Object: &corev1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
},
|
||||
ExpectObject: &corev1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"},
|
||||
},
|
||||
Overwrite: true,
|
||||
ExpectPath: "/foo",
|
||||
HTTPClient: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
if req.Method == "PUT" {
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})}, nil
|
||||
}
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}})}, nil
|
||||
}),
|
||||
Req: expectPut,
|
||||
},
|
||||
{
|
||||
Namespace: "bar",
|
||||
NamespaceScoped: true,
|
||||
Object: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
|
||||
ExpectPath: "/namespaces/bar/foo",
|
||||
ExpectObject: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
|
||||
Resp: &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})},
|
||||
Req: expectPut,
|
||||
},
|
||||
}
|
||||
for i, test := range tests {
|
||||
client := &fake.RESTClient{
|
||||
GroupVersion: corev1GV,
|
||||
NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs},
|
||||
Client: test.HTTPClient,
|
||||
Resp: test.Resp,
|
||||
Err: test.HttpErr,
|
||||
}
|
||||
modifier := &Helper{
|
||||
RESTClient: client,
|
||||
NamespaceScoped: test.NamespaceScoped,
|
||||
}
|
||||
_, err := modifier.Replace(test.Namespace, "foo", test.Overwrite, test.Object)
|
||||
if (err != nil) != test.Err {
|
||||
t.Errorf("%d: unexpected error: %t %v", i, test.Err, err)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if test.Req != nil && !test.Req(test.ExpectPath, client.Req) {
|
||||
t.Errorf("%d: unexpected request: %#v", i, client.Req)
|
||||
}
|
||||
body, err := ioutil.ReadAll(client.Req.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("%d: unexpected error: %#v", i, err)
|
||||
}
|
||||
expect := []byte{}
|
||||
if test.ExpectObject != nil {
|
||||
expect = []byte(runtime.EncodeOrDie(corev1Codec, test.ExpectObject))
|
||||
}
|
||||
if !reflect.DeepEqual(expect, body) {
|
||||
t.Errorf("%d: unexpected body: %s", i, string(body))
|
||||
}
|
||||
}
|
||||
}
|
100
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/interfaces.go
generated
vendored
Normal file
100
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/interfaces.go
generated
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
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 resource
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type RESTClientGetter interface {
|
||||
ToRESTConfig() (*rest.Config, error)
|
||||
ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error)
|
||||
ToRESTMapper() (meta.RESTMapper, error)
|
||||
}
|
||||
|
||||
type ClientConfigFunc func() (*rest.Config, error)
|
||||
|
||||
// RESTClient is a client helper for dealing with RESTful resources
|
||||
// in a generic way.
|
||||
type RESTClient interface {
|
||||
Get() *rest.Request
|
||||
Post() *rest.Request
|
||||
Patch(types.PatchType) *rest.Request
|
||||
Delete() *rest.Request
|
||||
Put() *rest.Request
|
||||
}
|
||||
|
||||
// RequestTransform is a function that is given a chance to modify the outgoing request.
|
||||
type RequestTransform func(*rest.Request)
|
||||
|
||||
// NewClientWithOptions wraps the provided RESTClient and invokes each transform on each
|
||||
// newly created request.
|
||||
func NewClientWithOptions(c RESTClient, transforms ...RequestTransform) RESTClient {
|
||||
if len(transforms) == 0 {
|
||||
return c
|
||||
}
|
||||
return &clientOptions{c: c, transforms: transforms}
|
||||
}
|
||||
|
||||
type clientOptions struct {
|
||||
c RESTClient
|
||||
transforms []RequestTransform
|
||||
}
|
||||
|
||||
func (c *clientOptions) modify(req *rest.Request) *rest.Request {
|
||||
for _, transform := range c.transforms {
|
||||
transform(req)
|
||||
}
|
||||
return req
|
||||
}
|
||||
|
||||
func (c *clientOptions) Get() *rest.Request {
|
||||
return c.modify(c.c.Get())
|
||||
}
|
||||
|
||||
func (c *clientOptions) Post() *rest.Request {
|
||||
return c.modify(c.c.Post())
|
||||
}
|
||||
func (c *clientOptions) Patch(t types.PatchType) *rest.Request {
|
||||
return c.modify(c.c.Patch(t))
|
||||
}
|
||||
func (c *clientOptions) Delete() *rest.Request {
|
||||
return c.modify(c.c.Delete())
|
||||
}
|
||||
func (c *clientOptions) Put() *rest.Request {
|
||||
return c.modify(c.c.Put())
|
||||
}
|
||||
|
||||
// ContentValidator is an interface that knows how to validate an API object serialized to a byte array.
|
||||
type ContentValidator interface {
|
||||
ValidateBytes(data []byte) error
|
||||
}
|
||||
|
||||
// Visitor lets clients walk a list of resources.
|
||||
type Visitor interface {
|
||||
Visit(VisitorFunc) error
|
||||
}
|
||||
|
||||
// VisitorFunc implements the Visitor interface for a matching function.
|
||||
// If there was a problem walking a list of resources, the incoming error
|
||||
// will describe the problem and the function can decide how to handle that error.
|
||||
// A nil returned indicates to accept an error to continue loops even when errors happen.
|
||||
// This is useful for ignoring certain kinds of errors or aggregating errors in some way.
|
||||
type VisitorFunc func(*Info, error) error
|
154
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/mapper.go
generated
vendored
Normal file
154
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/mapper.go
generated
vendored
Normal file
@ -0,0 +1,154 @@
|
||||
/*
|
||||
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 resource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// Mapper is a convenience struct for holding references to the interfaces
|
||||
// needed to create Info for arbitrary objects.
|
||||
type mapper struct {
|
||||
// localFn indicates the call can't make server requests
|
||||
localFn func() bool
|
||||
|
||||
restMapper meta.RESTMapper
|
||||
clientFn func(version schema.GroupVersion) (RESTClient, error)
|
||||
decoder runtime.Decoder
|
||||
}
|
||||
|
||||
// InfoForData creates an Info object for the given data. An error is returned
|
||||
// if any of the decoding or client lookup steps fail. Name and namespace will be
|
||||
// set into Info if the mapping's MetadataAccessor can retrieve them.
|
||||
func (m *mapper) infoForData(data []byte, source string) (*Info, error) {
|
||||
obj, gvk, err := m.decoder.Decode(data, nil, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decode %q: %v", source, err)
|
||||
}
|
||||
|
||||
name, _ := metadataAccessor.Name(obj)
|
||||
namespace, _ := metadataAccessor.Namespace(obj)
|
||||
resourceVersion, _ := metadataAccessor.ResourceVersion(obj)
|
||||
|
||||
ret := &Info{
|
||||
Source: source,
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
ResourceVersion: resourceVersion,
|
||||
|
||||
Object: obj,
|
||||
}
|
||||
|
||||
if m.localFn == nil || !m.localFn() {
|
||||
mapping, err := m.restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to recognize %q: %v", source, err)
|
||||
}
|
||||
ret.Mapping = mapping
|
||||
|
||||
client, err := m.clientFn(gvk.GroupVersion())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to connect to a server to handle %q: %v", mapping.Resource, err)
|
||||
}
|
||||
ret.Client = client
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// InfoForObject creates an Info object for the given Object. An error is returned
|
||||
// if the object cannot be introspected. Name and namespace will be set into Info
|
||||
// if the mapping's MetadataAccessor can retrieve them.
|
||||
func (m *mapper) infoForObject(obj runtime.Object, typer runtime.ObjectTyper, preferredGVKs []schema.GroupVersionKind) (*Info, error) {
|
||||
groupVersionKinds, _, err := typer.ObjectKinds(obj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get type info from the object %q: %v", reflect.TypeOf(obj), err)
|
||||
}
|
||||
|
||||
gvk := groupVersionKinds[0]
|
||||
if len(groupVersionKinds) > 1 && len(preferredGVKs) > 0 {
|
||||
gvk = preferredObjectKind(groupVersionKinds, preferredGVKs)
|
||||
}
|
||||
|
||||
name, _ := metadataAccessor.Name(obj)
|
||||
namespace, _ := metadataAccessor.Namespace(obj)
|
||||
resourceVersion, _ := metadataAccessor.ResourceVersion(obj)
|
||||
ret := &Info{
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
ResourceVersion: resourceVersion,
|
||||
|
||||
Object: obj,
|
||||
}
|
||||
|
||||
if m.localFn == nil || !m.localFn() {
|
||||
mapping, err := m.restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to recognize %v", err)
|
||||
}
|
||||
ret.Mapping = mapping
|
||||
|
||||
client, err := m.clientFn(gvk.GroupVersion())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to connect to a server to handle %q: %v", mapping.Resource, err)
|
||||
}
|
||||
ret.Client = client
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// preferredObjectKind picks the possibility that most closely matches the priority list in this order:
|
||||
// GroupVersionKind matches (exact match)
|
||||
// GroupKind matches
|
||||
// Group matches
|
||||
func preferredObjectKind(possibilities []schema.GroupVersionKind, preferences []schema.GroupVersionKind) schema.GroupVersionKind {
|
||||
// Exact match
|
||||
for _, priority := range preferences {
|
||||
for _, possibility := range possibilities {
|
||||
if possibility == priority {
|
||||
return possibility
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GroupKind match
|
||||
for _, priority := range preferences {
|
||||
for _, possibility := range possibilities {
|
||||
if possibility.GroupKind() == priority.GroupKind() {
|
||||
return possibility
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Group match
|
||||
for _, priority := range preferences {
|
||||
for _, possibility := range possibilities {
|
||||
if possibility.Group == priority.Group {
|
||||
return possibility
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Just pick the first
|
||||
return possibilities[0]
|
||||
}
|
242
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/result.go
generated
vendored
Normal file
242
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/result.go
generated
vendored
Normal file
@ -0,0 +1,242 @@
|
||||
/*
|
||||
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 resource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
)
|
||||
|
||||
// ErrMatchFunc can be used to filter errors that may not be true failures.
|
||||
type ErrMatchFunc func(error) bool
|
||||
|
||||
// Result contains helper methods for dealing with the outcome of a Builder.
|
||||
type Result struct {
|
||||
err error
|
||||
visitor Visitor
|
||||
|
||||
sources []Visitor
|
||||
singleItemImplied bool
|
||||
targetsSingleItems bool
|
||||
|
||||
mapper *mapper
|
||||
ignoreErrors []utilerrors.Matcher
|
||||
|
||||
// populated by a call to Infos
|
||||
info []*Info
|
||||
}
|
||||
|
||||
// withError allows a fluent style for internal result code.
|
||||
func (r *Result) withError(err error) *Result {
|
||||
r.err = err
|
||||
return r
|
||||
}
|
||||
|
||||
// TargetsSingleItems returns true if any of the builder arguments pointed
|
||||
// to non-list calls (if the user explicitly asked for any object by name).
|
||||
// This includes directories, streams, URLs, and resource name tuples.
|
||||
func (r *Result) TargetsSingleItems() bool {
|
||||
return r.targetsSingleItems
|
||||
}
|
||||
|
||||
// IgnoreErrors will filter errors that occur when by visiting the result
|
||||
// (but not errors that occur by creating the result in the first place),
|
||||
// eliminating any that match fns. This is best used in combination with
|
||||
// Builder.ContinueOnError(), where the visitors accumulate errors and return
|
||||
// them after visiting as a slice of errors. If no errors remain after
|
||||
// filtering, the various visitor methods on Result will return nil for
|
||||
// err.
|
||||
func (r *Result) IgnoreErrors(fns ...ErrMatchFunc) *Result {
|
||||
for _, fn := range fns {
|
||||
r.ignoreErrors = append(r.ignoreErrors, utilerrors.Matcher(fn))
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Mapper returns a copy of the builder's mapper.
|
||||
func (r *Result) Mapper() *mapper {
|
||||
return r.mapper
|
||||
}
|
||||
|
||||
// Err returns one or more errors (via a util.ErrorList) that occurred prior
|
||||
// to visiting the elements in the visitor. To see all errors including those
|
||||
// that occur during visitation, invoke Infos().
|
||||
func (r *Result) Err() error {
|
||||
return r.err
|
||||
}
|
||||
|
||||
// Visit implements the Visitor interface on the items described in the Builder.
|
||||
// Note that some visitor sources are not traversable more than once, or may
|
||||
// return different results. If you wish to operate on the same set of resources
|
||||
// multiple times, use the Infos() method.
|
||||
func (r *Result) Visit(fn VisitorFunc) error {
|
||||
if r.err != nil {
|
||||
return r.err
|
||||
}
|
||||
err := r.visitor.Visit(fn)
|
||||
return utilerrors.FilterOut(err, r.ignoreErrors...)
|
||||
}
|
||||
|
||||
// IntoSingleItemImplied sets the provided boolean pointer to true if the Builder input
|
||||
// implies a single item, or multiple.
|
||||
func (r *Result) IntoSingleItemImplied(b *bool) *Result {
|
||||
*b = r.singleItemImplied
|
||||
return r
|
||||
}
|
||||
|
||||
// Infos returns an array of all of the resource infos retrieved via traversal.
|
||||
// Will attempt to traverse the entire set of visitors only once, and will return
|
||||
// a cached list on subsequent calls.
|
||||
func (r *Result) Infos() ([]*Info, error) {
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
}
|
||||
if r.info != nil {
|
||||
return r.info, nil
|
||||
}
|
||||
|
||||
infos := []*Info{}
|
||||
err := r.visitor.Visit(func(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
infos = append(infos, info)
|
||||
return nil
|
||||
})
|
||||
err = utilerrors.FilterOut(err, r.ignoreErrors...)
|
||||
|
||||
r.info, r.err = infos, err
|
||||
return infos, err
|
||||
}
|
||||
|
||||
// Object returns a single object representing the output of a single visit to all
|
||||
// found resources. If the Builder was a singular context (expected to return a
|
||||
// single resource by user input) and only a single resource was found, the resource
|
||||
// will be returned as is. Otherwise, the returned resources will be part of an
|
||||
// v1.List. The ResourceVersion of the v1.List will be set only if it is identical
|
||||
// across all infos returned.
|
||||
func (r *Result) Object() (runtime.Object, error) {
|
||||
infos, err := r.Infos()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
versions := sets.String{}
|
||||
objects := []runtime.Object{}
|
||||
for _, info := range infos {
|
||||
if info.Object != nil {
|
||||
objects = append(objects, info.Object)
|
||||
versions.Insert(info.ResourceVersion)
|
||||
}
|
||||
}
|
||||
|
||||
if len(objects) == 1 {
|
||||
if r.singleItemImplied {
|
||||
return objects[0], nil
|
||||
}
|
||||
// if the item is a list already, don't create another list
|
||||
if meta.IsListType(objects[0]) {
|
||||
return objects[0], nil
|
||||
}
|
||||
}
|
||||
|
||||
version := ""
|
||||
if len(versions) == 1 {
|
||||
version = versions.List()[0]
|
||||
}
|
||||
|
||||
return toV1List(objects, version), err
|
||||
}
|
||||
|
||||
// Compile time check to enforce that list implements the necessary interface
|
||||
var _ metav1.ListInterface = &v1.List{}
|
||||
var _ metav1.ListMetaAccessor = &v1.List{}
|
||||
|
||||
// toV1List takes a slice of Objects + their version, and returns
|
||||
// a v1.List Object containing the objects in the Items field
|
||||
func toV1List(objects []runtime.Object, version string) runtime.Object {
|
||||
raw := []runtime.RawExtension{}
|
||||
for _, o := range objects {
|
||||
raw = append(raw, runtime.RawExtension{Object: o})
|
||||
}
|
||||
return &v1.List{
|
||||
ListMeta: metav1.ListMeta{
|
||||
ResourceVersion: version,
|
||||
},
|
||||
Items: raw,
|
||||
}
|
||||
}
|
||||
|
||||
// ResourceMapping returns a single meta.RESTMapping representing the
|
||||
// resources located by the builder, or an error if more than one
|
||||
// mapping was found.
|
||||
func (r *Result) ResourceMapping() (*meta.RESTMapping, error) {
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
}
|
||||
mappings := map[schema.GroupVersionResource]*meta.RESTMapping{}
|
||||
for i := range r.sources {
|
||||
m, ok := r.sources[i].(ResourceMapping)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("a resource mapping could not be loaded from %v", reflect.TypeOf(r.sources[i]))
|
||||
}
|
||||
mapping := m.ResourceMapping()
|
||||
mappings[mapping.Resource] = mapping
|
||||
}
|
||||
if len(mappings) != 1 {
|
||||
return nil, fmt.Errorf("expected only a single resource type")
|
||||
}
|
||||
for _, mapping := range mappings {
|
||||
return mapping, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Watch retrieves changes that occur on the server to the specified resource.
|
||||
// It currently supports watching a single source - if the resource source
|
||||
// (selectors or pure types) can be watched, they will be, otherwise the list
|
||||
// will be visited (equivalent to the Infos() call) and if there is a single
|
||||
// resource present, it will be watched, otherwise an error will be returned.
|
||||
func (r *Result) Watch(resourceVersion string) (watch.Interface, error) {
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
}
|
||||
if len(r.sources) != 1 {
|
||||
return nil, fmt.Errorf("you may only watch a single resource or type of resource at a time")
|
||||
}
|
||||
w, ok := r.sources[0].(Watchable)
|
||||
if !ok {
|
||||
info, err := r.Infos()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(info) != 1 {
|
||||
return nil, fmt.Errorf("watch is only supported on individual resources and resource collections - %d resources were found", len(info))
|
||||
}
|
||||
return info[0].Watch(resourceVersion)
|
||||
}
|
||||
return w.Watch(resourceVersion)
|
||||
}
|
79
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/scheme.go
generated
vendored
Normal file
79
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/scheme.go
generated
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
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 resource
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// dynamicCodec is a codec that wraps the standard unstructured codec
|
||||
// with special handling for Status objects.
|
||||
// Deprecated only used by test code and its wrong
|
||||
type dynamicCodec struct{}
|
||||
|
||||
func (dynamicCodec) Decode(data []byte, gvk *schema.GroupVersionKind, obj runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
|
||||
obj, gvk, err := unstructured.UnstructuredJSONScheme.Decode(data, gvk, obj)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if _, ok := obj.(*metav1.Status); !ok && strings.ToLower(gvk.Kind) == "status" {
|
||||
obj = &metav1.Status{}
|
||||
err := json.Unmarshal(data, obj)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return obj, gvk, nil
|
||||
}
|
||||
|
||||
func (dynamicCodec) Encode(obj runtime.Object, w io.Writer) error {
|
||||
return unstructured.UnstructuredJSONScheme.Encode(obj, w)
|
||||
}
|
||||
|
||||
// ContentConfig returns a rest.ContentConfig for dynamic types. It includes enough codecs to act as a "normal"
|
||||
// serializer for the rest.client with options, status and the like.
|
||||
func UnstructuredPlusDefaultContentConfig() rest.ContentConfig {
|
||||
var jsonInfo runtime.SerializerInfo
|
||||
// TODO: scheme.Codecs here should become "pkg/apis/server/scheme" which is the minimal core you need
|
||||
// to talk to a kubernetes server
|
||||
for _, info := range scheme.Codecs.SupportedMediaTypes() {
|
||||
if info.MediaType == runtime.ContentTypeJSON {
|
||||
jsonInfo = info
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
jsonInfo.Serializer = dynamicCodec{}
|
||||
jsonInfo.PrettySerializer = nil
|
||||
return rest.ContentConfig{
|
||||
AcceptContentTypes: runtime.ContentTypeJSON,
|
||||
ContentType: runtime.ContentTypeJSON,
|
||||
NegotiatedSerializer: serializer.NegotiatedSerializerWrapper(jsonInfo),
|
||||
}
|
||||
}
|
121
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/selector.go
generated
vendored
Normal file
121
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/selector.go
generated
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
/*
|
||||
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 resource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
)
|
||||
|
||||
// Selector is a Visitor for resources that match a label selector.
|
||||
type Selector struct {
|
||||
Client RESTClient
|
||||
Mapping *meta.RESTMapping
|
||||
Namespace string
|
||||
LabelSelector string
|
||||
FieldSelector string
|
||||
Export bool
|
||||
IncludeUninitialized bool
|
||||
LimitChunks int64
|
||||
}
|
||||
|
||||
// NewSelector creates a resource selector which hides details of getting items by their label selector.
|
||||
func NewSelector(client RESTClient, mapping *meta.RESTMapping, namespace, labelSelector, fieldSelector string, export, includeUninitialized bool, limitChunks int64) *Selector {
|
||||
return &Selector{
|
||||
Client: client,
|
||||
Mapping: mapping,
|
||||
Namespace: namespace,
|
||||
LabelSelector: labelSelector,
|
||||
FieldSelector: fieldSelector,
|
||||
Export: export,
|
||||
IncludeUninitialized: includeUninitialized,
|
||||
LimitChunks: limitChunks,
|
||||
}
|
||||
}
|
||||
|
||||
// Visit implements Visitor and uses request chunking by default.
|
||||
func (r *Selector) Visit(fn VisitorFunc) error {
|
||||
var continueToken string
|
||||
for {
|
||||
list, err := NewHelper(r.Client, r.Mapping).List(
|
||||
r.Namespace,
|
||||
r.ResourceMapping().GroupVersionKind.GroupVersion().String(),
|
||||
r.Export,
|
||||
&metav1.ListOptions{
|
||||
LabelSelector: r.LabelSelector,
|
||||
FieldSelector: r.FieldSelector,
|
||||
IncludeUninitialized: r.IncludeUninitialized,
|
||||
Limit: r.LimitChunks,
|
||||
Continue: continueToken,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if errors.IsResourceExpired(err) {
|
||||
return err
|
||||
}
|
||||
if errors.IsBadRequest(err) || errors.IsNotFound(err) {
|
||||
if se, ok := err.(*errors.StatusError); ok {
|
||||
// modify the message without hiding this is an API error
|
||||
if len(r.LabelSelector) == 0 && len(r.FieldSelector) == 0 {
|
||||
se.ErrStatus.Message = fmt.Sprintf("Unable to list %q: %v", r.Mapping.Resource, se.ErrStatus.Message)
|
||||
} else {
|
||||
se.ErrStatus.Message = fmt.Sprintf("Unable to find %q that match label selector %q, field selector %q: %v", r.Mapping.Resource, r.LabelSelector, r.FieldSelector, se.ErrStatus.Message)
|
||||
}
|
||||
return se
|
||||
}
|
||||
if len(r.LabelSelector) == 0 && len(r.FieldSelector) == 0 {
|
||||
return fmt.Errorf("Unable to list %q: %v", r.Mapping.Resource, err)
|
||||
}
|
||||
return fmt.Errorf("Unable to find %q that match label selector %q, field selector %q: %v", r.Mapping.Resource, r.LabelSelector, r.FieldSelector, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
resourceVersion, _ := metadataAccessor.ResourceVersion(list)
|
||||
nextContinueToken, _ := metadataAccessor.Continue(list)
|
||||
info := &Info{
|
||||
Client: r.Client,
|
||||
Mapping: r.Mapping,
|
||||
|
||||
Namespace: r.Namespace,
|
||||
ResourceVersion: resourceVersion,
|
||||
|
||||
Object: list,
|
||||
}
|
||||
|
||||
if err := fn(info, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(nextContinueToken) == 0 {
|
||||
return nil
|
||||
}
|
||||
continueToken = nextContinueToken
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Selector) Watch(resourceVersion string) (watch.Interface, error) {
|
||||
return NewHelper(r.Client, r.Mapping).Watch(r.Namespace, r.ResourceMapping().GroupVersionKind.GroupVersion().String(),
|
||||
&metav1.ListOptions{ResourceVersion: resourceVersion, LabelSelector: r.LabelSelector, FieldSelector: r.FieldSelector})
|
||||
}
|
||||
|
||||
// ResourceMapping returns the mapping for this resource and implements ResourceMapping
|
||||
func (r *Selector) ResourceMapping() *meta.RESTMapping {
|
||||
return r.Mapping
|
||||
}
|
707
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/visitor.go
generated
vendored
Normal file
707
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/visitor.go
generated
vendored
Normal file
@ -0,0 +1,707 @@
|
||||
/*
|
||||
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 resource
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/transform"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
)
|
||||
|
||||
const (
|
||||
constSTDINstr string = "STDIN"
|
||||
stopValidateMessage = "if you choose to ignore these errors, turn validation off with --validate=false"
|
||||
)
|
||||
|
||||
// Watchable describes a resource that can be watched for changes that occur on the server,
|
||||
// beginning after the provided resource version.
|
||||
type Watchable interface {
|
||||
Watch(resourceVersion string) (watch.Interface, error)
|
||||
}
|
||||
|
||||
// ResourceMapping allows an object to return the resource mapping associated with
|
||||
// the resource or resources it represents.
|
||||
type ResourceMapping interface {
|
||||
ResourceMapping() *meta.RESTMapping
|
||||
}
|
||||
|
||||
// Info contains temporary info to execute a REST call, or show the results
|
||||
// of an already completed REST call.
|
||||
type Info struct {
|
||||
// Client will only be present if this builder was not local
|
||||
Client RESTClient
|
||||
// Mapping will only be present if this builder was not local
|
||||
Mapping *meta.RESTMapping
|
||||
|
||||
// Namespace will be set if the object is namespaced and has a specified value.
|
||||
Namespace string
|
||||
Name string
|
||||
|
||||
// Optional, Source is the filename or URL to template file (.json or .yaml),
|
||||
// or stdin to use to handle the resource
|
||||
Source string
|
||||
// Optional, this is the most recent value returned by the server if available. It will
|
||||
// typically be in unstructured or internal forms, depending on how the Builder was
|
||||
// defined. If retrieved from the server, the Builder expects the mapping client to
|
||||
// decide the final form. Use the AsVersioned, AsUnstructured, and AsInternal helpers
|
||||
// to alter the object versions.
|
||||
Object runtime.Object
|
||||
// Optional, this is the most recent resource version the server knows about for
|
||||
// this type of resource. It may not match the resource version of the object,
|
||||
// but if set it should be equal to or newer than the resource version of the
|
||||
// object (however the server defines resource version).
|
||||
ResourceVersion string
|
||||
// Optional, should this resource be exported, stripped of cluster-specific and instance specific fields
|
||||
Export bool
|
||||
}
|
||||
|
||||
// Visit implements Visitor
|
||||
func (i *Info) Visit(fn VisitorFunc) error {
|
||||
return fn(i, nil)
|
||||
}
|
||||
|
||||
// Get retrieves the object from the Namespace and Name fields
|
||||
func (i *Info) Get() (err error) {
|
||||
obj, err := NewHelper(i.Client, i.Mapping).Get(i.Namespace, i.Name, i.Export)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) && len(i.Namespace) > 0 && i.Namespace != metav1.NamespaceDefault && i.Namespace != metav1.NamespaceAll {
|
||||
err2 := i.Client.Get().AbsPath("api", "v1", "namespaces", i.Namespace).Do().Error()
|
||||
if err2 != nil && errors.IsNotFound(err2) {
|
||||
return err2
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
i.Object = obj
|
||||
i.ResourceVersion, _ = metadataAccessor.ResourceVersion(obj)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Refresh updates the object with another object. If ignoreError is set
|
||||
// the Object will be updated even if name, namespace, or resourceVersion
|
||||
// attributes cannot be loaded from the object.
|
||||
func (i *Info) Refresh(obj runtime.Object, ignoreError bool) error {
|
||||
name, err := metadataAccessor.Name(obj)
|
||||
if err != nil {
|
||||
if !ignoreError {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
i.Name = name
|
||||
}
|
||||
namespace, err := metadataAccessor.Namespace(obj)
|
||||
if err != nil {
|
||||
if !ignoreError {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
i.Namespace = namespace
|
||||
}
|
||||
version, err := metadataAccessor.ResourceVersion(obj)
|
||||
if err != nil {
|
||||
if !ignoreError {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
i.ResourceVersion = version
|
||||
}
|
||||
i.Object = obj
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns the general purpose string representation
|
||||
func (i *Info) String() string {
|
||||
basicInfo := fmt.Sprintf("Name: %q, Namespace: %q\nObject: %+q", i.Name, i.Namespace, i.Object)
|
||||
if i.Mapping != nil {
|
||||
mappingInfo := fmt.Sprintf("Resource: %q, GroupVersionKind: %q", i.Mapping.Resource.String(),
|
||||
i.Mapping.GroupVersionKind.String())
|
||||
return fmt.Sprint(mappingInfo, "\n", basicInfo)
|
||||
}
|
||||
return basicInfo
|
||||
}
|
||||
|
||||
// Namespaced returns true if the object belongs to a namespace
|
||||
func (i *Info) Namespaced() bool {
|
||||
return i.Mapping != nil && i.Mapping.Scope.Name() == meta.RESTScopeNameNamespace
|
||||
}
|
||||
|
||||
// Watch returns server changes to this object after it was retrieved.
|
||||
func (i *Info) Watch(resourceVersion string) (watch.Interface, error) {
|
||||
return NewHelper(i.Client, i.Mapping).WatchSingle(i.Namespace, i.Name, resourceVersion)
|
||||
}
|
||||
|
||||
// ResourceMapping returns the mapping for this resource and implements ResourceMapping
|
||||
func (i *Info) ResourceMapping() *meta.RESTMapping {
|
||||
return i.Mapping
|
||||
}
|
||||
|
||||
// VisitorList implements Visit for the sub visitors it contains. The first error
|
||||
// returned from a child Visitor will terminate iteration.
|
||||
type VisitorList []Visitor
|
||||
|
||||
// Visit implements Visitor
|
||||
func (l VisitorList) Visit(fn VisitorFunc) error {
|
||||
for i := range l {
|
||||
if err := l[i].Visit(fn); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EagerVisitorList implements Visit for the sub visitors it contains. All errors
|
||||
// will be captured and returned at the end of iteration.
|
||||
type EagerVisitorList []Visitor
|
||||
|
||||
// Visit implements Visitor, and gathers errors that occur during processing until
|
||||
// all sub visitors have been visited.
|
||||
func (l EagerVisitorList) Visit(fn VisitorFunc) error {
|
||||
errs := []error(nil)
|
||||
for i := range l {
|
||||
if err := l[i].Visit(func(info *Info, err error) error {
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
return nil
|
||||
}
|
||||
if err := fn(info, nil); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
func ValidateSchema(data []byte, schema ContentValidator) error {
|
||||
if schema == nil {
|
||||
return nil
|
||||
}
|
||||
if err := schema.ValidateBytes(data); err != nil {
|
||||
return fmt.Errorf("error validating data: %v; %s", err, stopValidateMessage)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// URLVisitor downloads the contents of a URL, and if successful, returns
|
||||
// an info object representing the downloaded object.
|
||||
type URLVisitor struct {
|
||||
URL *url.URL
|
||||
*StreamVisitor
|
||||
HttpAttemptCount int
|
||||
}
|
||||
|
||||
func (v *URLVisitor) Visit(fn VisitorFunc) error {
|
||||
body, err := readHttpWithRetries(httpgetImpl, time.Second, v.URL.String(), v.HttpAttemptCount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer body.Close()
|
||||
v.StreamVisitor.Reader = body
|
||||
return v.StreamVisitor.Visit(fn)
|
||||
}
|
||||
|
||||
// readHttpWithRetries tries to http.Get the v.URL retries times before giving up.
|
||||
func readHttpWithRetries(get httpget, duration time.Duration, u string, attempts int) (io.ReadCloser, error) {
|
||||
var err error
|
||||
var body io.ReadCloser
|
||||
if attempts <= 0 {
|
||||
return nil, fmt.Errorf("http attempts must be greater than 0, was %d", attempts)
|
||||
}
|
||||
for i := 0; i < attempts; i++ {
|
||||
var statusCode int
|
||||
var status string
|
||||
if i > 0 {
|
||||
time.Sleep(duration)
|
||||
}
|
||||
|
||||
// Try to get the URL
|
||||
statusCode, status, body, err = get(u)
|
||||
|
||||
// Retry Errors
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Error - Set the error condition from the StatusCode
|
||||
if statusCode != http.StatusOK {
|
||||
err = fmt.Errorf("unable to read URL %q, server reported %s, status code=%d", u, status, statusCode)
|
||||
}
|
||||
|
||||
if statusCode >= 500 && statusCode < 600 {
|
||||
// Retry 500's
|
||||
continue
|
||||
} else {
|
||||
// Don't retry other StatusCodes
|
||||
break
|
||||
}
|
||||
}
|
||||
return body, err
|
||||
}
|
||||
|
||||
// httpget Defines function to retrieve a url and return the results. Exists for unit test stubbing.
|
||||
type httpget func(url string) (int, string, io.ReadCloser, error)
|
||||
|
||||
// httpgetImpl Implements a function to retrieve a url and return the results.
|
||||
func httpgetImpl(url string) (int, string, io.ReadCloser, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return 0, "", nil, err
|
||||
}
|
||||
return resp.StatusCode, resp.Status, resp.Body, nil
|
||||
}
|
||||
|
||||
// DecoratedVisitor will invoke the decorators in order prior to invoking the visitor function
|
||||
// passed to Visit. An error will terminate the visit.
|
||||
type DecoratedVisitor struct {
|
||||
visitor Visitor
|
||||
decorators []VisitorFunc
|
||||
}
|
||||
|
||||
// NewDecoratedVisitor will create a visitor that invokes the provided visitor functions before
|
||||
// the user supplied visitor function is invoked, giving them the opportunity to mutate the Info
|
||||
// object or terminate early with an error.
|
||||
func NewDecoratedVisitor(v Visitor, fn ...VisitorFunc) Visitor {
|
||||
if len(fn) == 0 {
|
||||
return v
|
||||
}
|
||||
return DecoratedVisitor{v, fn}
|
||||
}
|
||||
|
||||
// Visit implements Visitor
|
||||
func (v DecoratedVisitor) Visit(fn VisitorFunc) error {
|
||||
return v.visitor.Visit(func(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range v.decorators {
|
||||
if err := v.decorators[i](info, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return fn(info, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// ContinueOnErrorVisitor visits each item and, if an error occurs on
|
||||
// any individual item, returns an aggregate error after all items
|
||||
// are visited.
|
||||
type ContinueOnErrorVisitor struct {
|
||||
Visitor
|
||||
}
|
||||
|
||||
// Visit returns nil if no error occurs during traversal, a regular
|
||||
// error if one occurs, or if multiple errors occur, an aggregate
|
||||
// error. If the provided visitor fails on any individual item it
|
||||
// will not prevent the remaining items from being visited. An error
|
||||
// returned by the visitor directly may still result in some items
|
||||
// not being visited.
|
||||
func (v ContinueOnErrorVisitor) Visit(fn VisitorFunc) error {
|
||||
errs := []error{}
|
||||
err := v.Visitor.Visit(func(info *Info, err error) error {
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
return nil
|
||||
}
|
||||
if err := fn(info, nil); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if len(errs) == 1 {
|
||||
return errs[0]
|
||||
}
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
// FlattenListVisitor flattens any objects that runtime.ExtractList recognizes as a list
|
||||
// - has an "Items" public field that is a slice of runtime.Objects or objects satisfying
|
||||
// that interface - into multiple Infos. An error on any sub item (for instance, if a List
|
||||
// contains an object that does not have a registered client or resource) will terminate
|
||||
// the visit.
|
||||
// TODO: allow errors to be aggregated?
|
||||
type FlattenListVisitor struct {
|
||||
visitor Visitor
|
||||
typer runtime.ObjectTyper
|
||||
mapper *mapper
|
||||
}
|
||||
|
||||
// NewFlattenListVisitor creates a visitor that will expand list style runtime.Objects
|
||||
// into individual items and then visit them individually.
|
||||
func NewFlattenListVisitor(v Visitor, typer runtime.ObjectTyper, mapper *mapper) Visitor {
|
||||
return FlattenListVisitor{v, typer, mapper}
|
||||
}
|
||||
|
||||
func (v FlattenListVisitor) Visit(fn VisitorFunc) error {
|
||||
return v.visitor.Visit(func(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Object == nil {
|
||||
return fn(info, nil)
|
||||
}
|
||||
items, err := meta.ExtractList(info.Object)
|
||||
if err != nil {
|
||||
return fn(info, nil)
|
||||
}
|
||||
if errs := runtime.DecodeList(items, v.mapper.decoder); len(errs) > 0 {
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
// If we have a GroupVersionKind on the list, prioritize that when asking for info on the objects contained in the list
|
||||
var preferredGVKs []schema.GroupVersionKind
|
||||
if info.Mapping != nil && !info.Mapping.GroupVersionKind.Empty() {
|
||||
preferredGVKs = append(preferredGVKs, info.Mapping.GroupVersionKind)
|
||||
}
|
||||
|
||||
for i := range items {
|
||||
item, err := v.mapper.infoForObject(items[i], v.typer, preferredGVKs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(info.ResourceVersion) != 0 {
|
||||
item.ResourceVersion = info.ResourceVersion
|
||||
}
|
||||
if err := fn(item, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func ignoreFile(path string, extensions []string) bool {
|
||||
if len(extensions) == 0 {
|
||||
return false
|
||||
}
|
||||
ext := filepath.Ext(path)
|
||||
for _, s := range extensions {
|
||||
if s == ext {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// FileVisitorForSTDIN return a special FileVisitor just for STDIN
|
||||
func FileVisitorForSTDIN(mapper *mapper, schema ContentValidator) Visitor {
|
||||
return &FileVisitor{
|
||||
Path: constSTDINstr,
|
||||
StreamVisitor: NewStreamVisitor(nil, mapper, constSTDINstr, schema),
|
||||
}
|
||||
}
|
||||
|
||||
// ExpandPathsToFileVisitors will return a slice of FileVisitors that will handle files from the provided path.
|
||||
// After FileVisitors open the files, they will pass an io.Reader to a StreamVisitor to do the reading. (stdin
|
||||
// is also taken care of). Paths argument also accepts a single file, and will return a single visitor
|
||||
func ExpandPathsToFileVisitors(mapper *mapper, paths string, recursive bool, extensions []string, schema ContentValidator) ([]Visitor, error) {
|
||||
var visitors []Visitor
|
||||
err := filepath.Walk(paths, func(path string, fi os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fi.IsDir() {
|
||||
if path != paths && !recursive {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// Don't check extension if the filepath was passed explicitly
|
||||
if path != paths && ignoreFile(path, extensions) {
|
||||
return nil
|
||||
}
|
||||
|
||||
visitor := &FileVisitor{
|
||||
Path: path,
|
||||
StreamVisitor: NewStreamVisitor(nil, mapper, path, schema),
|
||||
}
|
||||
|
||||
visitors = append(visitors, visitor)
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return visitors, nil
|
||||
}
|
||||
|
||||
// FileVisitor is wrapping around a StreamVisitor, to handle open/close files
|
||||
type FileVisitor struct {
|
||||
Path string
|
||||
*StreamVisitor
|
||||
}
|
||||
|
||||
// Visit in a FileVisitor is just taking care of opening/closing files
|
||||
func (v *FileVisitor) Visit(fn VisitorFunc) error {
|
||||
var f *os.File
|
||||
if v.Path == constSTDINstr {
|
||||
f = os.Stdin
|
||||
} else {
|
||||
var err error
|
||||
f, err = os.Open(v.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
}
|
||||
|
||||
// TODO: Consider adding a flag to force to UTF16, apparently some
|
||||
// Windows tools don't write the BOM
|
||||
utf16bom := unicode.BOMOverride(unicode.UTF8.NewDecoder())
|
||||
v.StreamVisitor.Reader = transform.NewReader(f, utf16bom)
|
||||
|
||||
return v.StreamVisitor.Visit(fn)
|
||||
}
|
||||
|
||||
// StreamVisitor reads objects from an io.Reader and walks them. A stream visitor can only be
|
||||
// visited once.
|
||||
// TODO: depends on objects being in JSON format before being passed to decode - need to implement
|
||||
// a stream decoder method on runtime.Codec to properly handle this.
|
||||
type StreamVisitor struct {
|
||||
io.Reader
|
||||
*mapper
|
||||
|
||||
Source string
|
||||
Schema ContentValidator
|
||||
}
|
||||
|
||||
// NewStreamVisitor is a helper function that is useful when we want to change the fields of the struct but keep calls the same.
|
||||
func NewStreamVisitor(r io.Reader, mapper *mapper, source string, schema ContentValidator) *StreamVisitor {
|
||||
return &StreamVisitor{
|
||||
Reader: r,
|
||||
mapper: mapper,
|
||||
Source: source,
|
||||
Schema: schema,
|
||||
}
|
||||
}
|
||||
|
||||
// Visit implements Visitor over a stream. StreamVisitor is able to distinct multiple resources in one stream.
|
||||
func (v *StreamVisitor) Visit(fn VisitorFunc) error {
|
||||
d := yaml.NewYAMLOrJSONDecoder(v.Reader, 4096)
|
||||
for {
|
||||
ext := runtime.RawExtension{}
|
||||
if err := d.Decode(&ext); err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("error parsing %s: %v", v.Source, err)
|
||||
}
|
||||
// TODO: This needs to be able to handle object in other encodings and schemas.
|
||||
ext.Raw = bytes.TrimSpace(ext.Raw)
|
||||
if len(ext.Raw) == 0 || bytes.Equal(ext.Raw, []byte("null")) {
|
||||
continue
|
||||
}
|
||||
if err := ValidateSchema(ext.Raw, v.Schema); err != nil {
|
||||
return fmt.Errorf("error validating %q: %v", v.Source, err)
|
||||
}
|
||||
info, err := v.infoForData(ext.Raw, v.Source)
|
||||
if err != nil {
|
||||
if fnErr := fn(info, err); fnErr != nil {
|
||||
return fnErr
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := fn(info, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateObjectNamespace(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Object != nil {
|
||||
return metadataAccessor.SetNamespace(info.Object, info.Namespace)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FilterNamespace omits the namespace if the object is not namespace scoped
|
||||
func FilterNamespace(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.Namespaced() {
|
||||
info.Namespace = ""
|
||||
UpdateObjectNamespace(info, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetNamespace ensures that every Info object visited will have a namespace
|
||||
// set. If info.Object is set, it will be mutated as well.
|
||||
func SetNamespace(namespace string) VisitorFunc {
|
||||
return func(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.Namespaced() {
|
||||
return nil
|
||||
}
|
||||
if len(info.Namespace) == 0 {
|
||||
info.Namespace = namespace
|
||||
UpdateObjectNamespace(info, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// RequireNamespace will either set a namespace if none is provided on the
|
||||
// Info object, or if the namespace is set and does not match the provided
|
||||
// value, returns an error. This is intended to guard against administrators
|
||||
// accidentally operating on resources outside their namespace.
|
||||
func RequireNamespace(namespace string) VisitorFunc {
|
||||
return func(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.Namespaced() {
|
||||
return nil
|
||||
}
|
||||
if len(info.Namespace) == 0 {
|
||||
info.Namespace = namespace
|
||||
UpdateObjectNamespace(info, nil)
|
||||
return nil
|
||||
}
|
||||
if info.Namespace != namespace {
|
||||
return fmt.Errorf("the namespace from the provided object %q does not match the namespace %q. You must pass '--namespace=%s' to perform this operation.", info.Namespace, namespace, info.Namespace)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// RetrieveLatest updates the Object on each Info by invoking a standard client
|
||||
// Get.
|
||||
func RetrieveLatest(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if meta.IsListType(info.Object) {
|
||||
return fmt.Errorf("watch is only supported on individual resources and resource collections, but a list of resources is found")
|
||||
}
|
||||
if len(info.Name) == 0 {
|
||||
return nil
|
||||
}
|
||||
if info.Namespaced() && len(info.Namespace) == 0 {
|
||||
return fmt.Errorf("no namespace set on resource %s %q", info.Mapping.Resource, info.Name)
|
||||
}
|
||||
return info.Get()
|
||||
}
|
||||
|
||||
// RetrieveLazy updates the object if it has not been loaded yet.
|
||||
func RetrieveLazy(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Object == nil {
|
||||
return info.Get()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateAndRefresh creates an object from input info and refreshes info with that object
|
||||
func CreateAndRefresh(info *Info) error {
|
||||
obj, err := NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info.Refresh(obj, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
type FilterFunc func(info *Info, err error) (bool, error)
|
||||
|
||||
type FilteredVisitor struct {
|
||||
visitor Visitor
|
||||
filters []FilterFunc
|
||||
}
|
||||
|
||||
func NewFilteredVisitor(v Visitor, fn ...FilterFunc) Visitor {
|
||||
if len(fn) == 0 {
|
||||
return v
|
||||
}
|
||||
return FilteredVisitor{v, fn}
|
||||
}
|
||||
|
||||
func (v FilteredVisitor) Visit(fn VisitorFunc) error {
|
||||
return v.visitor.Visit(func(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, filter := range v.filters {
|
||||
ok, err := filter(info, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fn(info, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func FilterByLabelSelector(s labels.Selector) FilterFunc {
|
||||
return func(info *Info, err error) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
a, err := meta.Accessor(info.Object)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !s.Matches(labels.Set(a.GetLabels())) {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
type InfoListVisitor []*Info
|
||||
|
||||
func (infos InfoListVisitor) Visit(fn VisitorFunc) error {
|
||||
var err error
|
||||
for _, i := range infos {
|
||||
err = fn(i, err)
|
||||
}
|
||||
return err
|
||||
}
|
102
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/visitor_test.go
generated
vendored
Normal file
102
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource/visitor_test.go
generated
vendored
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package resource
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestVisitorHttpGet(t *testing.T) {
|
||||
// Test retries on errors
|
||||
i := 0
|
||||
expectedErr := fmt.Errorf("Failed to get http")
|
||||
actualBytes, actualErr := readHttpWithRetries(func(url string) (int, string, io.ReadCloser, error) {
|
||||
assert.Equal(t, "hello", url)
|
||||
i++
|
||||
if i > 2 {
|
||||
return 0, "", nil, expectedErr
|
||||
}
|
||||
return 0, "", nil, fmt.Errorf("Unexpected error")
|
||||
}, 0, "hello", 3)
|
||||
assert.Equal(t, expectedErr, actualErr)
|
||||
assert.Nil(t, actualBytes)
|
||||
assert.Equal(t, 3, i)
|
||||
|
||||
// Test that 500s are retried.
|
||||
i = 0
|
||||
actualBytes, actualErr = readHttpWithRetries(func(url string) (int, string, io.ReadCloser, error) {
|
||||
assert.Equal(t, "hello", url)
|
||||
i++
|
||||
return 501, "Status", nil, nil
|
||||
}, 0, "hello", 3)
|
||||
assert.Error(t, actualErr)
|
||||
assert.Nil(t, actualBytes)
|
||||
assert.Equal(t, 3, i)
|
||||
|
||||
// Test that 300s are not retried
|
||||
i = 0
|
||||
actualBytes, actualErr = readHttpWithRetries(func(url string) (int, string, io.ReadCloser, error) {
|
||||
assert.Equal(t, "hello", url)
|
||||
i++
|
||||
return 300, "Status", nil, nil
|
||||
}, 0, "hello", 3)
|
||||
assert.Error(t, actualErr)
|
||||
assert.Nil(t, actualBytes)
|
||||
assert.Equal(t, 1, i)
|
||||
|
||||
// Test attempt count is respected
|
||||
i = 0
|
||||
actualBytes, actualErr = readHttpWithRetries(func(url string) (int, string, io.ReadCloser, error) {
|
||||
assert.Equal(t, "hello", url)
|
||||
i++
|
||||
return 501, "Status", nil, nil
|
||||
}, 0, "hello", 1)
|
||||
assert.Error(t, actualErr)
|
||||
assert.Nil(t, actualBytes)
|
||||
assert.Equal(t, 1, i)
|
||||
|
||||
// Test attempts less than 1 results in an error
|
||||
i = 0
|
||||
b := bytes.Buffer{}
|
||||
actualBytes, actualErr = readHttpWithRetries(func(url string) (int, string, io.ReadCloser, error) {
|
||||
return 200, "Status", ioutil.NopCloser(&b), nil
|
||||
}, 0, "hello", 0)
|
||||
assert.Error(t, actualErr)
|
||||
assert.Nil(t, actualBytes)
|
||||
assert.Equal(t, 0, i)
|
||||
|
||||
// Test Success
|
||||
i = 0
|
||||
b = bytes.Buffer{}
|
||||
actualBytes, actualErr = readHttpWithRetries(func(url string) (int, string, io.ReadCloser, error) {
|
||||
assert.Equal(t, "hello", url)
|
||||
i++
|
||||
if i > 1 {
|
||||
return 200, "Status", ioutil.NopCloser(&b), nil
|
||||
}
|
||||
return 501, "Status", nil, nil
|
||||
}, 0, "hello", 3)
|
||||
assert.Nil(t, actualErr)
|
||||
assert.NotNil(t, actualBytes)
|
||||
assert.Equal(t, 2, i)
|
||||
}
|
132
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/template_flags.go
generated
vendored
Normal file
132
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/template_flags.go
generated
vendored
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
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 genericclioptions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
|
||||
)
|
||||
|
||||
// templates are logically optional for specifying a format.
|
||||
// this allows a user to specify a template format value
|
||||
// as --output=go-template=
|
||||
var templateFormats = map[string]bool{
|
||||
"template": true,
|
||||
"go-template": true,
|
||||
"go-template-file": true,
|
||||
"templatefile": true,
|
||||
}
|
||||
|
||||
// GoTemplatePrintFlags provides default flags necessary for template printing.
|
||||
// Given the following flag values, a printer can be requested that knows
|
||||
// how to handle printing based on these values.
|
||||
type GoTemplatePrintFlags struct {
|
||||
// indicates if it is OK to ignore missing keys for rendering
|
||||
// an output template.
|
||||
AllowMissingKeys *bool
|
||||
TemplateArgument *string
|
||||
}
|
||||
|
||||
func (f *GoTemplatePrintFlags) AllowedFormats() []string {
|
||||
formats := make([]string, 0, len(templateFormats))
|
||||
for format := range templateFormats {
|
||||
formats = append(formats, format)
|
||||
}
|
||||
return formats
|
||||
}
|
||||
|
||||
// ToPrinter receives an templateFormat and returns a printer capable of
|
||||
// handling --template format printing.
|
||||
// Returns false if the specified templateFormat does not match a template format.
|
||||
func (f *GoTemplatePrintFlags) ToPrinter(templateFormat string) (printers.ResourcePrinter, error) {
|
||||
if (f.TemplateArgument == nil || len(*f.TemplateArgument) == 0) && len(templateFormat) == 0 {
|
||||
return nil, NoCompatiblePrinterError{Options: f, OutputFormat: &templateFormat}
|
||||
}
|
||||
|
||||
templateValue := ""
|
||||
|
||||
if f.TemplateArgument == nil || len(*f.TemplateArgument) == 0 {
|
||||
for format := range templateFormats {
|
||||
format = format + "="
|
||||
if strings.HasPrefix(templateFormat, format) {
|
||||
templateValue = templateFormat[len(format):]
|
||||
templateFormat = format[:len(format)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
templateValue = *f.TemplateArgument
|
||||
}
|
||||
|
||||
if _, supportedFormat := templateFormats[templateFormat]; !supportedFormat {
|
||||
return nil, NoCompatiblePrinterError{OutputFormat: &templateFormat, AllowedFormats: f.AllowedFormats()}
|
||||
}
|
||||
|
||||
if len(templateValue) == 0 {
|
||||
return nil, fmt.Errorf("template format specified but no template given")
|
||||
}
|
||||
|
||||
if templateFormat == "templatefile" || templateFormat == "go-template-file" {
|
||||
data, err := ioutil.ReadFile(templateValue)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading --template %s, %v\n", templateValue, err)
|
||||
}
|
||||
|
||||
templateValue = string(data)
|
||||
}
|
||||
|
||||
p, err := printers.NewGoTemplatePrinter([]byte(templateValue))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing template %s, %v\n", templateValue, err)
|
||||
}
|
||||
|
||||
allowMissingKeys := true
|
||||
if f.AllowMissingKeys != nil {
|
||||
allowMissingKeys = *f.AllowMissingKeys
|
||||
}
|
||||
|
||||
p.AllowMissingKeys(allowMissingKeys)
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// AddFlags receives a *cobra.Command reference and binds
|
||||
// flags related to template printing to it
|
||||
func (f *GoTemplatePrintFlags) AddFlags(c *cobra.Command) {
|
||||
if f.TemplateArgument != nil {
|
||||
c.Flags().StringVar(f.TemplateArgument, "template", *f.TemplateArgument, "Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].")
|
||||
c.MarkFlagFilename("template")
|
||||
}
|
||||
if f.AllowMissingKeys != nil {
|
||||
c.Flags().BoolVar(f.AllowMissingKeys, "allow-missing-template-keys", *f.AllowMissingKeys, "If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats.")
|
||||
}
|
||||
}
|
||||
|
||||
// NewGoTemplatePrintFlags returns flags associated with
|
||||
// --template printing, with default values set.
|
||||
func NewGoTemplatePrintFlags() *GoTemplatePrintFlags {
|
||||
allowMissingKeysPtr := true
|
||||
templateValuePtr := ""
|
||||
|
||||
return &GoTemplatePrintFlags{
|
||||
TemplateArgument: &templateValuePtr,
|
||||
AllowMissingKeys: &allowMissingKeysPtr,
|
||||
}
|
||||
}
|
205
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/template_flags_test.go
generated
vendored
Normal file
205
vendor/k8s.io/kubernetes/pkg/kubectl/genericclioptions/template_flags_test.go
generated
vendored
Normal file
@ -0,0 +1,205 @@
|
||||
/*
|
||||
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 genericclioptions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestPrinterSupportsExpectedTemplateFormats(t *testing.T) {
|
||||
testObject := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
|
||||
|
||||
templateFile, err := ioutil.TempFile("", "printers_jsonpath_flags")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
defer func(tempFile *os.File) {
|
||||
tempFile.Close()
|
||||
os.Remove(tempFile.Name())
|
||||
}(templateFile)
|
||||
|
||||
fmt.Fprintf(templateFile, "{{ .metadata.name }}")
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
outputFormat string
|
||||
templateArg string
|
||||
expectedError string
|
||||
expectedParseError string
|
||||
expectedOutput string
|
||||
expectNoMatch bool
|
||||
}{
|
||||
{
|
||||
name: "valid output format also containing the template argument succeeds",
|
||||
outputFormat: "go-template={{ .metadata.name }}",
|
||||
expectedOutput: "foo",
|
||||
},
|
||||
{
|
||||
name: "valid output format and no template argument results in an error",
|
||||
outputFormat: "template",
|
||||
expectedError: "template format specified but no template given",
|
||||
},
|
||||
{
|
||||
name: "valid output format and template argument succeeds",
|
||||
outputFormat: "go-template",
|
||||
templateArg: "{{ .metadata.name }}",
|
||||
expectedOutput: "foo",
|
||||
},
|
||||
{
|
||||
name: "Go-template file should match, and successfully return correct value",
|
||||
outputFormat: "go-template-file",
|
||||
templateArg: templateFile.Name(),
|
||||
expectedOutput: "foo",
|
||||
},
|
||||
{
|
||||
name: "valid output format and invalid template argument results in the templateArg contents as the output",
|
||||
outputFormat: "go-template",
|
||||
templateArg: "invalid",
|
||||
expectedOutput: "invalid",
|
||||
},
|
||||
{
|
||||
name: "no printer is matched on an invalid outputFormat",
|
||||
outputFormat: "invalid",
|
||||
expectNoMatch: true,
|
||||
},
|
||||
{
|
||||
name: "go-template printer should not match on any other format supported by another printer",
|
||||
outputFormat: "jsonpath",
|
||||
expectNoMatch: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
templateArg := &tc.templateArg
|
||||
if len(tc.templateArg) == 0 {
|
||||
templateArg = nil
|
||||
}
|
||||
|
||||
printFlags := GoTemplatePrintFlags{
|
||||
TemplateArgument: templateArg,
|
||||
}
|
||||
|
||||
p, err := printFlags.ToPrinter(tc.outputFormat)
|
||||
if tc.expectNoMatch {
|
||||
if !IsNoCompatiblePrinterError(err) {
|
||||
t.Fatalf("expected no printer matches for output format %q", tc.outputFormat)
|
||||
}
|
||||
return
|
||||
}
|
||||
if IsNoCompatiblePrinterError(err) {
|
||||
t.Fatalf("expected to match template printer for output format %q", tc.outputFormat)
|
||||
}
|
||||
|
||||
if len(tc.expectedError) > 0 {
|
||||
if err == nil || !strings.Contains(err.Error(), tc.expectedError) {
|
||||
t.Errorf("expecting error %q, got %v", tc.expectedError, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
out := bytes.NewBuffer([]byte{})
|
||||
err = p.PrintObj(testObject, out)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(out.String()) != len(tc.expectedOutput) {
|
||||
t.Errorf("unexpected output: expecting %q, got %q", tc.expectedOutput, out.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplatePrinterDefaultsAllowMissingKeysToTrue(t *testing.T) {
|
||||
testObject := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
|
||||
|
||||
allowMissingKeys := false
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
templateArg string
|
||||
expectedOutput string
|
||||
expectedError string
|
||||
allowMissingKeys *bool
|
||||
}{
|
||||
{
|
||||
name: "existing field does not error and returns expected value",
|
||||
templateArg: "{{ .metadata.name }}",
|
||||
expectedOutput: "foo",
|
||||
allowMissingKeys: &allowMissingKeys,
|
||||
},
|
||||
{
|
||||
name: "missing field does not error and returns no value since missing keys are allowed by default",
|
||||
templateArg: "{{ .metadata.missing }}",
|
||||
expectedOutput: "<no value>",
|
||||
allowMissingKeys: nil,
|
||||
},
|
||||
{
|
||||
name: "missing field returns expected error if field is missing and allowMissingKeys is explicitly set to false",
|
||||
templateArg: "{{ .metadata.missing }}",
|
||||
expectedError: "error executing template",
|
||||
allowMissingKeys: &allowMissingKeys,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
printFlags := GoTemplatePrintFlags{
|
||||
TemplateArgument: &tc.templateArg,
|
||||
AllowMissingKeys: tc.allowMissingKeys,
|
||||
}
|
||||
|
||||
outputFormat := "template"
|
||||
p, err := printFlags.ToPrinter(outputFormat)
|
||||
if IsNoCompatiblePrinterError(err) {
|
||||
t.Fatalf("expected to match template printer for output format %q", outputFormat)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
out := bytes.NewBuffer([]byte{})
|
||||
err = p.PrintObj(testObject, out)
|
||||
|
||||
if len(tc.expectedError) > 0 {
|
||||
if err == nil || !strings.Contains(err.Error(), tc.expectedError) {
|
||||
t.Errorf("expecting error %q, got %v", tc.expectedError, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(out.String()) != len(tc.expectedOutput) {
|
||||
t.Errorf("unexpected output: expecting %q, got %q", tc.expectedOutput, out.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user