working commit

This commit is contained in:
2026-03-13 19:02:42 +02:00
parent bebbf79c7a
commit 5c1da77f4c
1329 changed files with 314708 additions and 39 deletions
+228
View File
@@ -0,0 +1,228 @@
/*
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/cli-runtime/pkg/resource"
"k8s.io/utils/ptr"
)
// 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
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: ptr.To(true),
},
}
}
// WithFile sets the FileNameFlags.
// If recurse is set, it will process directory recursively. Useful when you want to manage related manifests
// organized within the same directory.
func (o *ResourceBuilderFlags) WithFile(recurse bool, files ...string) *ResourceBuilderFlags {
o.FileNameFlags = &FileNameFlags{
Usage: "identifying the resource.",
Filenames: &files,
Recursive: ptr.To(recurse),
}
return o
}
// WithLabelSelector sets the LabelSelector flag
func (o *ResourceBuilderFlags) WithLabelSelector(selector string) *ResourceBuilderFlags {
o.LabelSelector = &selector
return o
}
// WithFieldSelector sets the FieldSelector flag
func (o *ResourceBuilderFlags) WithFieldSelector(selector string) *ResourceBuilderFlags {
o.FieldSelector = &selector
return o
}
// WithAllNamespaces sets the AllNamespaces flag
func (o *ResourceBuilderFlags) WithAllNamespaces(defaultVal bool) *ResourceBuilderFlags {
o.AllNamespaces = &defaultVal
return o
}
// WithAll sets the All flag
func (o *ResourceBuilderFlags) WithAll(defaultVal bool) *ResourceBuilderFlags {
o.All = &defaultVal
return o
}
// WithLocal sets the Local flag
func (o *ResourceBuilderFlags) WithLocal(defaultVal bool) *ResourceBuilderFlags {
o.Local = &defaultVal
return o
}
// WithScheme sets the Scheme flag
func (o *ResourceBuilderFlags) WithScheme(scheme *runtime.Scheme) *ResourceBuilderFlags {
o.Scheme = scheme
return o
}
// WithLatest sets the Latest flag
func (o *ResourceBuilderFlags) WithLatest() *ResourceBuilderFlags {
o.Latest = true
return o
}
// StopOnError sets the StopOnFirstError flag
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.BoolVarP(o.AllNamespaces, "all-namespaces", "A", *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.")
}
}
// 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.AllNamespaces != nil {
builder.AllNamespaces(*o.AllNamespaces)
}
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.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
})
}
+65
View 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 (
"k8s.io/cli-runtime/pkg/resource"
)
// NewSimpleFakeResourceFinder builds a super simple ResourceFinder that just iterates over the objects you provided
func NewSimpleFakeResourceFinder(infos ...*resource.Info) *FakeResourceFinder {
return &FakeResourceFinder{
Infos: infos,
}
}
func (f *FakeResourceFinder) WithError(err error) *FakeResourceFinder {
f.err = err
return f
}
type FakeResourceFinder struct {
Infos []*resource.Info
err error
}
// Do implements the interface
func (f *FakeResourceFinder) Do() resource.Visitor {
return &fakeResourceResult{
Infos: f.Infos,
err: f.err,
}
}
type fakeResourceResult struct {
Infos []*resource.Info
err error
}
// Visit just iterates over info
func (r *fakeResourceResult) Visit(fn resource.VisitorFunc) error {
if r.err != nil {
return r.err
}
for _, info := range r.Infos {
err := fn(info, nil)
if err != nil {
return err
}
}
return nil
}
+72
View File
@@ -0,0 +1,72 @@
/*
Copyright 2020 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 (
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
var (
// ErrEmptyConfig is the error message to be displayed if the configuration info is missing or incomplete
ErrEmptyConfig = clientcmd.NewEmptyConfigError(`Missing or incomplete configuration info. Please point to an existing, complete config file:
1. Via the command-line flag --kubeconfig
2. Via the KUBECONFIG environment variable
3. In your home directory as ~/.kube/config
To view or setup config directly use the 'config' command.`)
)
var _ = clientcmd.ClientConfig(&clientConfig{})
type clientConfig struct {
defaultClientConfig clientcmd.ClientConfig
}
func (c *clientConfig) RawConfig() (clientcmdapi.Config, error) {
config, err := c.defaultClientConfig.RawConfig()
// replace client-go's ErrEmptyConfig error with our custom, more verbose version
if clientcmd.IsEmptyConfig(err) {
return config, ErrEmptyConfig
}
return config, err
}
func (c *clientConfig) ClientConfig() (*restclient.Config, error) {
config, err := c.defaultClientConfig.ClientConfig()
// replace client-go's ErrEmptyConfig error with our custom, more verbose version
if clientcmd.IsEmptyConfig(err) {
return config, ErrEmptyConfig
}
return config, err
}
func (c *clientConfig) Namespace() (string, bool, error) {
namespace, ok, err := c.defaultClientConfig.Namespace()
// replace client-go's ErrEmptyConfig error with our custom, more verbose version
if clientcmd.IsEmptyConfig(err) {
return namespace, ok, ErrEmptyConfig
}
return namespace, ok, err
}
func (c *clientConfig) ConfigAccess() clientcmd.ConfigAccess {
return c.defaultClientConfig.ConfigAccess()
}
+109
View File
@@ -0,0 +1,109 @@
/*
Copyright 2021 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 (
"net/http"
"strings"
"sync/atomic"
"github.com/google/uuid"
"github.com/spf13/cobra"
)
const (
kubectlCommandHeader = "Kubectl-Command"
kubectlSessionHeader = "Kubectl-Session"
)
// CommandHeaderRoundTripper adds a layer around the standard
// round tripper to add Request headers before delegation. Implements
// the go standard library "http.RoundTripper" interface.
type CommandHeaderRoundTripper struct {
Delegate http.RoundTripper
Headers map[string]string
SkipHeaders *atomic.Bool
}
// CommandHeaderRoundTripper adds Request headers before delegating to standard
// round tripper. These headers are kubectl command headers which
// detail the kubectl command. See SIG CLI KEP 859:
//
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-cli/859-kubectl-headers
func (c *CommandHeaderRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
if c.shouldSkipHeaders() {
return c.Delegate.RoundTrip(req)
}
for header, value := range c.Headers {
req.Header.Set(header, value)
}
return c.Delegate.RoundTrip(req)
}
// ParseCommandHeaders fills in a map of custom headers into the CommandHeaderRoundTripper. These
// headers are then filled into each request. For details on the custom headers see:
//
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-cli/859-kubectl-headers
//
// Each call overwrites the previously parsed command headers (not additive).
// TODO(seans3): Parse/add flags removing PII from flag values.
func (c *CommandHeaderRoundTripper) ParseCommandHeaders(cmd *cobra.Command, args []string) {
if cmd == nil {
return
}
// Overwrites previously parsed command headers (headers not additive).
c.Headers = map[string]string{}
// Session identifier to aggregate multiple Requests from single kubectl command.
uid := uuid.New().String()
c.Headers[kubectlSessionHeader] = uid
// Iterate up the hierarchy of commands from the leaf command to create
// the full command string. Example: kubectl create secret generic
cmdStrs := []string{}
for cmd.HasParent() {
parent := cmd.Parent()
currName := strings.TrimSpace(cmd.Name())
cmdStrs = append([]string{currName}, cmdStrs...)
cmd = parent
}
currName := strings.TrimSpace(cmd.Name())
cmdStrs = append([]string{currName}, cmdStrs...)
if len(cmdStrs) > 0 {
c.Headers[kubectlCommandHeader] = strings.Join(cmdStrs, " ")
}
}
// CancelRequest is propagated to the Delegate RoundTripper within
// if the wrapped RoundTripper implements this function.
func (c *CommandHeaderRoundTripper) CancelRequest(req *http.Request) {
type canceler interface {
CancelRequest(*http.Request)
}
// If possible, call "CancelRequest" on the wrapped Delegate RoundTripper.
if cr, ok := c.Delegate.(canceler); ok {
cr.CancelRequest(req)
}
}
func (c *CommandHeaderRoundTripper) shouldSkipHeaders() bool {
if c.SkipHeaders == nil {
return false
}
return c.SkipHeaders.Load()
}
+514
View File
@@ -0,0 +1,514 @@
/*
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"
"sync"
"time"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/cli-runtime/pkg/genericiooptions"
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/client-go/discovery"
diskcached "k8s.io/client-go/discovery/cached/disk"
"k8s.io/client-go/rest"
"k8s.io/client-go/restmapper"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
"k8s.io/utils/ptr"
)
const (
flagClusterName = "cluster"
flagAuthInfoName = "user"
flagContext = "context"
flagNamespace = "namespace"
flagAPIServer = "server"
flagTLSServerName = "tls-server-name"
flagInsecure = "insecure-skip-tls-verify"
flagCertFile = "client-certificate"
flagKeyFile = "client-key"
flagCAFile = "certificate-authority"
flagBearerToken = "token"
flagImpersonate = "as"
flagImpersonateUID = "as-uid"
flagImpersonateGroup = "as-group"
flagImpersonateUserExtra = "as-user-extra"
flagUsername = "username"
flagPassword = "password"
flagTimeout = "request-timeout"
flagCacheDir = "cache-dir"
flagDisableCompression = "disable-compression"
)
// 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
TLSServerName *string
Insecure *bool
CertFile *string
KeyFile *string
CAFile *string
BearerToken *string
Impersonate *string
ImpersonateUID *string
ImpersonateGroup *[]string
ImpersonateUserExtra *[]string
Username *string
Password *string
Timeout *string
DisableCompression *bool
// If non-nil, wrap config function can transform the Config
// before it is returned in ToRESTConfig function.
WrapConfigFn func(*rest.Config) *rest.Config
clientConfig clientcmd.ClientConfig
clientConfigLock sync.Mutex
restMapper meta.RESTMapper
restMapperLock sync.Mutex
discoveryClient discovery.CachedDiscoveryInterface
discoveryClientLock sync.Mutex
// If set to true, will use persistent client config, rest mapper, discovery client, and
// propagate them to the places that need them, rather than
// instantiating them multiple times.
usePersistentConfig bool
// Allows increasing burst used for discovery, this is useful
// in clusters with many registered resources
discoveryBurst int
// Allows increasing qps used for discovery, this is useful
// in clusters with many registered resources
discoveryQPS float32
// Allows all possible warnings are printed in a standardized
// format.
warningPrinter *printers.WarningPrinter
}
// 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. If WrapConfigFn
// is non-nil this function can transform config before return.
func (f *ConfigFlags) ToRESTConfig() (*rest.Config, error) {
c, err := f.ToRawKubeConfigLoader().ClientConfig()
if err != nil {
return nil, err
}
if f.WrapConfigFn != nil {
return f.WrapConfigFn(c), nil
}
return c, nil
}
// 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 {
if f.usePersistentConfig {
return f.toRawKubePersistentConfigLoader()
}
return f.toRawKubeConfigLoader()
}
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
overrides.AuthInfo.ClientCertificateData = nil
}
if f.KeyFile != nil {
overrides.AuthInfo.ClientKey = *f.KeyFile
overrides.AuthInfo.ClientKeyData = nil
}
if f.BearerToken != nil {
overrides.AuthInfo.Token = *f.BearerToken
overrides.AuthInfo.TokenFile = ""
}
if f.Impersonate != nil {
overrides.AuthInfo.Impersonate = *f.Impersonate
}
if f.ImpersonateUserExtra != nil && len(*f.ImpersonateUserExtra) > 0 {
userExtras := make(map[string][]string)
for _, extra := range *f.ImpersonateUserExtra {
parts := strings.SplitN(extra, "=", 2)
if len(parts) != 2 {
continue
}
key := parts[0]
value := parts[1]
userExtras[key] = append(userExtras[key], value)
}
overrides.AuthInfo.ImpersonateUserExtra = userExtras
}
if f.ImpersonateUID != nil {
overrides.AuthInfo.ImpersonateUID = *f.ImpersonateUID
}
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.TLSServerName != nil {
overrides.ClusterInfo.TLSServerName = *f.TLSServerName
}
if f.CAFile != nil {
overrides.ClusterInfo.CertificateAuthority = *f.CAFile
}
if f.Insecure != nil {
overrides.ClusterInfo.InsecureSkipTLSVerify = *f.Insecure
}
if f.DisableCompression != nil {
overrides.ClusterInfo.DisableCompression = *f.DisableCompression
}
// 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
}
// we only have an interactive prompt when a password is allowed
if f.Password == nil {
return &clientConfig{clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides)}
}
return &clientConfig{clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, overrides, os.Stdin)}
}
// toRawKubePersistentConfigLoader binds config flag values to config overrides
// Returns a persistent clientConfig for propagation.
func (f *ConfigFlags) toRawKubePersistentConfigLoader() clientcmd.ClientConfig {
f.clientConfigLock.Lock()
defer f.clientConfigLock.Unlock()
if f.clientConfig == nil {
f.clientConfig = f.toRawKubeConfigLoader()
}
return f.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) {
if f.usePersistentConfig {
return f.toPersistentDiscoveryClient()
}
return f.toDiscoveryClient()
}
func (f *ConfigFlags) toPersistentDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
f.discoveryClientLock.Lock()
defer f.discoveryClientLock.Unlock()
if f.discoveryClient == nil {
discoveryClient, err := f.toDiscoveryClient()
if err != nil {
return nil, err
}
f.discoveryClient = discoveryClient
}
return f.discoveryClient, nil
}
func (f *ConfigFlags) toDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
config, err := f.ToRESTConfig()
if err != nil {
return nil, err
}
config.Burst = f.discoveryBurst
config.QPS = f.discoveryQPS
cacheDir := getDefaultCacheDir()
// retrieve a user-provided value for the "cache-dir"
// override httpCacheDir and discoveryCacheDir if user-value is given.
// user-provided value has higher precedence than default
// and KUBECACHEDIR environment variable.
if f.CacheDir != nil && *f.CacheDir != "" && *f.CacheDir != getDefaultCacheDir() {
cacheDir = *f.CacheDir
}
httpCacheDir := filepath.Join(cacheDir, "http")
discoveryCacheDir := computeDiscoverCacheDir(filepath.Join(cacheDir, "discovery"), config.Host)
return diskcached.NewCachedDiscoveryClientForConfig(config, discoveryCacheDir, httpCacheDir, time.Duration(6*time.Hour))
}
// getDefaultCacheDir returns default caching directory path.
// it first looks at KUBECACHEDIR env var if it is set, otherwise
// it returns standard kube cache dir.
func getDefaultCacheDir() string {
if kcd := os.Getenv("KUBECACHEDIR"); kcd != "" {
return kcd
}
return filepath.Join(homedir.HomeDir(), ".kube", "cache")
}
// ToRESTMapper returns a mapper.
func (f *ConfigFlags) ToRESTMapper() (meta.RESTMapper, error) {
if f.usePersistentConfig {
return f.toPersistentRESTMapper()
}
return f.toRESTMapper()
}
func (f *ConfigFlags) toPersistentRESTMapper() (meta.RESTMapper, error) {
f.restMapperLock.Lock()
defer f.restMapperLock.Unlock()
if f.restMapper == nil {
restMapper, err := f.toRESTMapper()
if err != nil {
return nil, err
}
f.restMapper = restMapper
}
return f.restMapper, nil
}
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, func(a string) {
if f.warningPrinter != nil {
f.warningPrinter.Print(a)
}
})
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, flagCacheDir, *f.CacheDir, "Default 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. User could be a regular user or a service account in a namespace.")
}
if f.ImpersonateUID != nil {
flags.StringVar(f.ImpersonateUID, flagImpersonateUID, *f.ImpersonateUID, "UID 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.ImpersonateUserExtra != nil {
flags.StringArrayVar(f.ImpersonateUserExtra, flagImpersonateUserExtra, *f.ImpersonateUserExtra, "User extras to impersonate for the operation, this flag can be repeated to specify multiple values for the same key.")
}
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.TLSServerName != nil {
flags.StringVar(f.TLSServerName, flagTLSServerName, *f.TLSServerName, "Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used")
}
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.")
}
if f.DisableCompression != nil {
flags.BoolVar(f.DisableCompression, flagDisableCompression, *f.DisableCompression, "If true, opt-out of response compression for all requests to the server")
}
}
// WithDeprecatedPasswordFlag enables the username and password config flags
func (f *ConfigFlags) WithDeprecatedPasswordFlag() *ConfigFlags {
f.Username = ptr.To("")
f.Password = ptr.To("")
return f
}
// WithDiscoveryBurst sets the RESTClient burst for discovery.
func (f *ConfigFlags) WithDiscoveryBurst(discoveryBurst int) *ConfigFlags {
f.discoveryBurst = discoveryBurst
return f
}
// WithDiscoveryQPS sets the RESTClient QPS for discovery.
func (f *ConfigFlags) WithDiscoveryQPS(discoveryQPS float32) *ConfigFlags {
f.discoveryQPS = discoveryQPS
return f
}
// WithWrapConfigFn allows providing a wrapper function for the client Config.
func (f *ConfigFlags) WithWrapConfigFn(wrapConfigFn func(*rest.Config) *rest.Config) *ConfigFlags {
f.WrapConfigFn = wrapConfigFn
return f
}
// WithWarningPrinter initializes WarningPrinter with the given IOStreams
func (f *ConfigFlags) WithWarningPrinter(ioStreams genericiooptions.IOStreams) *ConfigFlags {
f.warningPrinter = printers.NewWarningPrinter(ioStreams.ErrOut, printers.WarningPrinterOptions{Color: printers.AllowsColorOutput(ioStreams.ErrOut)})
return f
}
// NewConfigFlags returns ConfigFlags with default values set
func NewConfigFlags(usePersistentConfig bool) *ConfigFlags {
impersonateGroup := []string{}
impersonateUserExtra := []string{}
insecure := false
disableCompression := false
return &ConfigFlags{
Insecure: &insecure,
Timeout: ptr.To("0"),
KubeConfig: ptr.To(""),
CacheDir: ptr.To(getDefaultCacheDir()),
ClusterName: ptr.To(""),
AuthInfoName: ptr.To(""),
Context: ptr.To(""),
Namespace: ptr.To(""),
APIServer: ptr.To(""),
TLSServerName: ptr.To(""),
CertFile: ptr.To(""),
KeyFile: ptr.To(""),
CAFile: ptr.To(""),
BearerToken: ptr.To(""),
Impersonate: ptr.To(""),
ImpersonateUID: ptr.To(""),
ImpersonateGroup: &impersonateGroup,
ImpersonateUserExtra: &impersonateUserExtra,
DisableCompression: &disableCompression,
usePersistentConfig: usePersistentConfig,
// The more groups you have, the more discovery requests you need to make.
// with a burst of 300, we will not be rate-limiting for most clusters but
// the safeguard will still be here. This config is only used for discovery.
discoveryBurst: 300,
}
}
// 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)
}
+127
View File
@@ -0,0 +1,127 @@
/*
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"
)
// TestConfigFlags contains clientConfig struct
// and interfaces that implements RESTClientGetter
type TestConfigFlags struct {
clientConfig clientcmd.ClientConfig
discoveryClient discovery.CachedDiscoveryInterface
restMapper meta.RESTMapper
}
// ToRawKubeConfigLoader implements RESTClientGetter
// Returns a clientconfig if it's set
func (f *TestConfigFlags) ToRawKubeConfigLoader() clientcmd.ClientConfig {
if f.clientConfig == nil {
panic("attempt to obtain a test RawKubeConfigLoader with no clientConfig specified")
}
return f.clientConfig
}
// 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 *TestConfigFlags) ToRESTConfig() (*rest.Config, error) {
return f.ToRawKubeConfigLoader().ClientConfig()
}
// ToDiscoveryClient implements RESTClientGetter.
// Returns a CachedDiscoveryInterface
func (f *TestConfigFlags) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
return f.discoveryClient, nil
}
// ToRESTMapper implements RESTClientGetter.
// Returns a mapper.
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, nil)
return expander, nil
}
return nil, fmt.Errorf("no restmapper")
}
// WithClientConfig sets the clientConfig flag
func (f *TestConfigFlags) WithClientConfig(clientConfig clientcmd.ClientConfig) *TestConfigFlags {
f.clientConfig = clientConfig
return f
}
// WithRESTMapper sets the restMapper flag
func (f *TestConfigFlags) WithRESTMapper(mapper meta.RESTMapper) *TestConfigFlags {
f.restMapper = mapper
return f
}
// WithDiscoveryClient sets the discoveryClient flag
func (f *TestConfigFlags) WithDiscoveryClient(c discovery.CachedDiscoveryInterface) *TestConfigFlags {
f.discoveryClient = c
return f
}
// WithNamespace sets the clientConfig flag by modifying delagate and namespace
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
}
// NewTestConfigFlags builds a TestConfigFlags struct to test ConfigFlags
func NewTestConfigFlags() *TestConfigFlags {
return &TestConfigFlags{}
}
type namespacedClientConfig struct {
delegate clientcmd.ClientConfig
namespace string
}
func (c *namespacedClientConfig) Namespace() (string, bool, error) {
return c.namespace, len(c.namespace) > 0, 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
View 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 your command, bound, completed, and produce
// useful helper functions. Nothing in this package can depend on kube/kube
package genericclioptions
+82
View File
@@ -0,0 +1,82 @@
/*
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/cli-runtime/pkg/resource"
)
// FileNameFlags are flags for processing files.
// 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
Kustomize *string
Recursive *bool
}
// ToOptions creates a new FileNameOptions struct and sets FilenameOptions based on FileNameflags
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
}
if o.Kustomize != nil {
options.Kustomize = *o.Kustomize
}
return options
}
// AddFlags binds file name flags to a given flagset
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)
}
if o.Kustomize != nil {
flags.StringVarP(o.Kustomize, "kustomize", "k", *o.Kustomize,
"Process a kustomization directory. This flag can't be used together with -f or -R.")
}
}
+54
View 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 (
"bytes"
"io"
"k8s.io/cli-runtime/pkg/genericiooptions"
)
// 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
// DEPRECATED: use genericiooptions.IOStreams
type IOStreams = genericiooptions.IOStreams
// NewTestIOStreams returns a valid IOStreams and in, out, errout buffers for unit tests
// DEPRECATED: use genericiooptions.NewTestIOStreams
func NewTestIOStreams() (genericiooptions.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
// DEPRECATED: use genericiooptions.NewTestIOStreamsDiscard
func NewTestIOStreamsDiscard() genericiooptions.IOStreams {
in := &bytes.Buffer{}
return IOStreams{
In: in,
Out: io.Discard,
ErrOut: io.Discard,
}
}
+94
View File
@@ -0,0 +1,94 @@
/*
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"
"slices"
"strings"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/printers"
)
// AllowedFormats returns slice of string of allowed JSONYaml printing format
func (f *JSONYamlPrintFlags) AllowedFormats() []string {
if f == nil {
return []string{}
}
formats := []string{"json", "yaml"}
// We can't use the cmdutil pkg directly because of import cycle.
if strings.ToLower(os.Getenv("KUBECTL_KYAML")) != "false" {
formats = append(formats, "kyaml")
}
return formats
}
// 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 {
ShowManagedFields bool
}
// 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)
valid := f.AllowedFormats()
if !slices.Contains(valid, outputFormat) {
return nil, NoCompatiblePrinterError{OutputFormat: &outputFormat, AllowedFormats: valid}
}
switch outputFormat {
case "json":
printer = &printers.JSONPrinter{}
case "yaml":
printer = &printers.YAMLPrinter{}
case "kyaml":
printer = &printers.KYAMLPrinter{}
default:
return nil, NoCompatiblePrinterError{OutputFormat: &outputFormat, AllowedFormats: f.AllowedFormats()}
}
if !f.ShowManagedFields {
printer = &printers.OmitManagedFieldsPrinter{Delegate: printer}
}
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) {
if f == nil {
return
}
c.Flags().BoolVar(&f.ShowManagedFields, "show-managed-fields", f.ShowManagedFields, "If true, keep the managedFields when printing objects in JSON or YAML format.")
}
// NewJSONYamlPrintFlags returns flags associated with
// yaml or json printing, with default values set.
func NewJSONYamlPrintFlags() *JSONYamlPrintFlags {
return &JSONYamlPrintFlags{}
}
+137
View File
@@ -0,0 +1,137 @@
/*
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"
"os"
"sort"
"strings"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/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,
"jsonpath-as-json": 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
}
// AllowedFormats returns slice of string of allowed JSONPath printing format
func (f *JSONPathPrintFlags) AllowedFormats() []string {
formats := make([]string, 0, len(jsonFormats))
for format := range jsonFormats {
formats = append(formats, format)
}
sort.Strings(formats)
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 := os.ReadFile(templateValue)
if err != nil {
return nil, fmt.Errorf("error reading --template %s, %v", templateValue, err)
}
templateValue = string(data)
}
p, err := printers.NewJSONPathPrinter(templateValue)
if err != nil {
return nil, fmt.Errorf("error parsing jsonpath %s, %v", templateValue, err)
}
allowMissingKeys := true
if f.AllowMissingKeys != nil {
allowMissingKeys = *f.AllowMissingKeys
}
p.AllowMissingKeys(allowMissingKeys)
if templateFormat == "jsonpath-as-json" {
p.EnableJSONOutput(true)
}
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,
}
}
+94
View File
@@ -0,0 +1,94 @@
/*
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/cli-runtime/pkg/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
}
// AllowedFormats returns slice of string of allowed GoTemplete and JSONPathPrint printing formats
func (f *KubeTemplatePrintFlags) AllowedFormats() []string {
if f == nil {
return []string{}
}
return append(f.GoTemplatePrintFlags.AllowedFormats(), f.JSONPathPrintFlags.AllowedFormats()...)
}
// ToPrinter receives an outputFormat and returns a printer capable of
// handling --template 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 *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,
}
}
+83
View File
@@ -0,0 +1,83 @@
/*
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/cli-runtime/pkg/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
}
// Complete sets NamePrintFlags operation flag from successTemplate
func (f *NamePrintFlags) Complete(successTemplate string) error {
f.Operation = fmt.Sprintf(successTemplate, f.Operation)
return nil
}
// AllowedFormats returns slice of string of allowed Name printing format
func (f *NamePrintFlags) AllowedFormats() []string {
if f == nil {
return []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,
}
}
+171
View File
@@ -0,0 +1,171 @@
/*
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/cli-runtime/pkg/printers"
)
// NoCompatiblePrinterError is a struct that contains error information.
// It will be constructed when a invalid printing format is provided
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, ","))
}
// IsNoCompatiblePrinterError returns true if it is a not a compatible printer
// otherwise it will return false
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
}
// Complete sets NamePrintFlags operation flag from successTemplate
func (f *PrintFlags) Complete(successTemplate string) error {
return f.NamePrintFlags.Complete(successTemplate)
}
// AllowedFormats returns slice of string of allowed JSONYaml/Name/Template printing format
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
}
// ToPrinter returns a printer capable of
// handling --output or --template 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 *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()}
}
// AddFlags receives a *cobra.Command reference and binds
// flags related to JSON/Yaml/Name/Template printing to it
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
}
// NewPrintFlags returns a default *PrintFlags
func NewPrintFlags(operation string) *PrintFlags {
outputFormat := ""
return &PrintFlags{
OutputFormat: &outputFormat,
JSONYamlPrintFlags: NewJSONYamlPrintFlags(),
NamePrintFlags: NewNamePrintFlags(operation),
TemplatePrinterFlags: NewKubeTemplatePrintFlags(),
}
}
+201
View File
@@ -0,0 +1,201 @@
/*
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/spf13/cobra"
"github.com/spf13/pflag"
jsonpatch "gopkg.in/evanphx/json-patch.v4"
"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
}
// CompleteWithChangeCause alters changeCause value with a new cause
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.")
cmd.Flags().MarkDeprecated("record", "--record will be removed in the future")
}
}
// 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
}
+136
View File
@@ -0,0 +1,136 @@
/*
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"
"os"
"sort"
"strings"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/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
}
// AllowedFormats returns slice of string of allowed GoTemplatePrint printing format
func (f *GoTemplatePrintFlags) AllowedFormats() []string {
formats := make([]string, 0, len(templateFormats))
for format := range templateFormats {
formats = append(formats, format)
}
sort.Strings(formats)
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 := os.ReadFile(templateValue)
if err != nil {
return nil, fmt.Errorf("error reading --template %s, %v", templateValue, err)
}
templateValue = string(data)
}
p, err := printers.NewGoTemplatePrinter([]byte(templateValue))
if err != nil {
return nil, fmt.Errorf("error parsing template %s, %v", 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,
}
}