update vendor

Signed-off-by: Roland.Ma <rolandma@yunify.com>
This commit is contained in:
Roland.Ma
2021-08-11 07:10:14 +00:00
parent a18f72b565
commit ea8f47c73a
2901 changed files with 269317 additions and 43103 deletions

View File

@@ -52,6 +52,9 @@ func NewResourceBuilderFlags() *ResourceBuilderFlags {
}
}
// 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.",
@@ -62,41 +65,49 @@ func (o *ResourceBuilderFlags) WithFile(recurse bool, files ...string) *Resource
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

View File

@@ -20,7 +20,7 @@ import (
"k8s.io/cli-runtime/pkg/resource"
)
// NewSimpleResourceFinder builds a super simple ResourceFinder that just iterates over the objects you provided
// NewSimpleFakeResourceFinder builds a super simple ResourceFinder that just iterates over the objects you provided
func NewSimpleFakeResourceFinder(infos ...*resource.Info) ResourceFinder {
return &fakeResourceFinder{
Infos: infos,

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()
}

View File

@@ -0,0 +1,79 @@
/*
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"
"github.com/google/uuid"
"github.com/spf13/cobra"
)
const (
kubectlCommandHeader = "X-Kubectl-Command"
kubectlSessionHeader = "X-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
}
// 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) {
for header, value := range c.Headers {
req.Header.Set(header, value)
}
return c.Delegate.RoundTrip(req)
}
// ParseCommandHeaders fills in a map of X-Headers into the CommandHeaderRoundTripper. These
// headers are then filled into each request. For details on X-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, " ")
}
}

View File

@@ -17,7 +17,6 @@ limitations under the License.
package genericclioptions
import (
"errors"
"os"
"path/filepath"
"regexp"
@@ -53,19 +52,11 @@ const (
flagUsername = "username"
flagPassword = "password"
flagTimeout = "request-timeout"
flagHTTPCacheDir = "cache-dir"
flagCacheDir = "cache-dir"
)
var (
defaultCacheDir = filepath.Join(homedir.HomeDir(), ".kube", "http-cache")
ErrEmptyConfig = errors.New(`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.`)
defaultCacheDir = filepath.Join(homedir.HomeDir(), ".kube", "cache")
)
// RESTClientGetter is an interface that the ConfigFlags describe to provide an easier way to mock for commands
@@ -107,6 +98,9 @@ type ConfigFlags struct {
Username *string
Password *string
Timeout *string
// 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
lock sync.Mutex
@@ -114,19 +108,25 @@ type ConfigFlags struct {
// propagate the config to the places that need it, rather than
// loading the config multiple times
usePersistentConfig bool
// Allows increasing burst used for discovery, this is useful
// in clusters with many registered resources
discoveryBurst int
}
// 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.
// 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) {
config, err := f.ToRawKubeConfigLoader().ClientConfig()
// replace client-go's ErrEmptyConfig error with our custom, more verbose version
if clientcmd.IsEmptyConfig(err) {
return nil, ErrEmptyConfig
c, err := f.ToRawKubeConfigLoader().ClientConfig()
if err != nil {
return nil, err
}
return config, err
if f.WrapConfigFn != nil {
return f.WrapConfigFn(c), nil
}
return c, nil
}
// ToRawKubeConfigLoader binds config flag values to config overrides
@@ -206,16 +206,11 @@ func (f *ConfigFlags) toRawKubeConfigLoader() clientcmd.ClientConfig {
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{clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides)}
}
return clientConfig
return &clientConfig{clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, overrides, os.Stdin)}
}
// toRawKubePersistentConfigLoader binds config flag values to config overrides
@@ -243,16 +238,18 @@ func (f *ConfigFlags) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, e
// 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
config.Burst = f.discoveryBurst
cacheDir := defaultCacheDir
// retrieve a user-provided value for the "cache-dir"
// defaulting to ~/.kube/http-cache if no user-value is given.
httpCacheDir := defaultCacheDir
// override httpCacheDir and discoveryCacheDir if user-value is given.
if f.CacheDir != nil {
httpCacheDir = *f.CacheDir
cacheDir = *f.CacheDir
}
httpCacheDir := filepath.Join(cacheDir, "http")
discoveryCacheDir := computeDiscoverCacheDir(filepath.Join(cacheDir, "discovery"), config.Host)
discoveryCacheDir := computeDiscoverCacheDir(filepath.Join(homedir.HomeDir(), ".kube", "cache", "discovery"), config.Host)
return diskcached.NewCachedDiscoveryClientForConfig(config, discoveryCacheDir, httpCacheDir, time.Duration(10*time.Minute))
}
@@ -274,7 +271,7 @@ func (f *ConfigFlags) AddFlags(flags *pflag.FlagSet) {
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")
flags.StringVar(f.CacheDir, flagCacheDir, *f.CacheDir, "Default cache directory")
}
// add config options
@@ -337,6 +334,12 @@ func (f *ConfigFlags) WithDeprecatedPasswordFlag() *ConfigFlags {
return f
}
// WithDiscoveryBurst sets the RESTClient burst for discovery.
func (f *ConfigFlags) WithDiscoveryBurst(discoveryBurst int) *ConfigFlags {
f.discoveryBurst = discoveryBurst
return f
}
// NewConfigFlags returns ConfigFlags with default values set
func NewConfigFlags(usePersistentConfig bool) *ConfigFlags {
impersonateGroup := []string{}
@@ -362,6 +365,10 @@ func NewConfigFlags(usePersistentConfig bool) *ConfigFlags {
ImpersonateGroup: &impersonateGroup,
usePersistentConfig: usePersistentConfig,
// 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.
discoveryBurst: 100,
}
}

View File

@@ -27,12 +27,16 @@ import (
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")
@@ -40,14 +44,22 @@ func (f *TestConfigFlags) ToRawKubeConfigLoader() clientcmd.ClientConfig {
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
@@ -60,21 +72,25 @@ func (f *TestConfigFlags) ToRESTMapper() (meta.RESTMapper, error) {
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")
@@ -86,6 +102,7 @@ func (f *TestConfigFlags) WithNamespace(ns string) *TestConfigFlags {
return f
}
// NewTestConfigFlags builds a TestConfigFlags struct to test ConfigFlags
func NewTestConfigFlags() *TestConfigFlags {
return &TestConfigFlags{}
}
@@ -96,7 +113,7 @@ type namespacedClientConfig struct {
}
func (c *namespacedClientConfig) Namespace() (string, bool, error) {
return c.namespace, false, nil
return c.namespace, len(c.namespace) > 0, nil
}
func (c *namespacedClientConfig) RawConfig() (clientcmdapi.Config, error) {

View File

@@ -14,6 +14,6 @@ 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
// 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 // import "k8s.io/cli-runtime/pkg/genericclioptions"

View File

@@ -25,6 +25,7 @@ import (
"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.
@@ -36,6 +37,7 @@ type FileNameFlags struct {
Recursive *bool
}
// ToOptions creates a new FileNameOptions struct and sets FilenameOptions based on FileNameflags
func (o *FileNameFlags) ToOptions() resource.FilenameOptions {
options := resource.FilenameOptions{}
@@ -56,6 +58,7 @@ func (o *FileNameFlags) ToOptions() resource.FilenameOptions {
return options
}
// AddFlags binds file name flags to a given flagset
func (o *FileNameFlags) AddFlags(flags *pflag.FlagSet) {
if o == nil {
return

View File

@@ -24,6 +24,7 @@ import (
"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{}
@@ -35,6 +36,7 @@ func (f *JSONYamlPrintFlags) AllowedFormats() []string {
// 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
@@ -54,12 +56,21 @@ func (f *JSONYamlPrintFlags) ToPrinter(outputFormat string) (printers.ResourcePr
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) {}
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.

View File

@@ -31,8 +31,9 @@ import (
// this allows a user to specify a template format value
// as --output=jsonpath=
var jsonFormats = map[string]bool{
"jsonpath": true,
"jsonpath-file": true,
"jsonpath": true,
"jsonpath-file": true,
"jsonpath-as-json": true,
}
// JSONPathPrintFlags provides default flags necessary for template printing.
@@ -45,6 +46,7 @@ type JSONPathPrintFlags struct {
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 {
@@ -88,7 +90,7 @@ func (f *JSONPathPrintFlags) ToPrinter(templateFormat string) (printers.Resource
if templateFormat == "jsonpath-file" {
data, err := ioutil.ReadFile(templateValue)
if err != nil {
return nil, fmt.Errorf("error reading --template %s, %v\n", templateValue, err)
return nil, fmt.Errorf("error reading --template %s, %v", templateValue, err)
}
templateValue = string(data)
@@ -96,7 +98,7 @@ func (f *JSONPathPrintFlags) ToPrinter(templateFormat string) (printers.Resource
p, err := printers.NewJSONPathPrinter(templateValue)
if err != nil {
return nil, fmt.Errorf("error parsing jsonpath %s, %v\n", templateValue, err)
return nil, fmt.Errorf("error parsing jsonpath %s, %v", templateValue, err)
}
allowMissingKeys := true
@@ -105,6 +107,11 @@ func (f *JSONPathPrintFlags) ToPrinter(templateFormat string) (printers.Resource
}
p.AllowMissingKeys(allowMissingKeys)
if templateFormat == "jsonpath-as-json" {
p.EnableJSONOutput(true)
}
return p, nil
}

View File

@@ -33,6 +33,7 @@ type KubeTemplatePrintFlags struct {
TemplateArgument *string
}
// AllowedFormats returns slice of string of allowed GoTemplete and JSONPathPrint printing formats
func (f *KubeTemplatePrintFlags) AllowedFormats() []string {
if f == nil {
return []string{}
@@ -40,6 +41,10 @@ func (f *KubeTemplatePrintFlags) AllowedFormats() []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{}

View File

@@ -35,11 +35,13 @@ type NamePrintFlags struct {
Operation string
}
// Complete sets NamePrintFlags operation flag from sucessTemplate
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{}

View File

@@ -27,6 +27,8 @@ import (
"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
@@ -43,6 +45,8 @@ func (e NoCompatiblePrinterError) Error() string {
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
@@ -69,10 +73,12 @@ type PrintFlags struct {
OutputFlagSpecified func() bool
}
// Complete sets NamePrintFlags operation flag from sucessTemplate
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()...)
@@ -81,6 +87,10 @@ func (f *PrintFlags) AllowedFormats() []string {
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 {
@@ -118,6 +128,8 @@ func (f *PrintFlags) ToPrinter() (printers.ResourcePrinter, error) {
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)
@@ -145,6 +157,7 @@ func (f *PrintFlags) WithTypeSetter(scheme *runtime.Scheme) *PrintFlags {
return f
}
// NewPrintFlags returns a default *PrintFlags
func NewPrintFlags(operation string) *PrintFlags {
outputFormat := ""

View File

@@ -21,7 +21,7 @@ import (
"path/filepath"
"strings"
"github.com/evanphx/json-patch"
jsonpatch "github.com/evanphx/json-patch"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@@ -74,6 +74,7 @@ func (f *RecordFlags) Complete(cmd *cobra.Command) error {
return nil
}
// CompleteWithChangeCause alters changeCause value with a new cause
func (f *RecordFlags) CompleteWithChangeCause(cause string) error {
if f == nil {
return nil

View File

@@ -47,6 +47,7 @@ type GoTemplatePrintFlags struct {
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 {
@@ -90,7 +91,7 @@ func (f *GoTemplatePrintFlags) ToPrinter(templateFormat string) (printers.Resour
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)
return nil, fmt.Errorf("error reading --template %s, %v", templateValue, err)
}
templateValue = string(data)
@@ -98,7 +99,7 @@ func (f *GoTemplatePrintFlags) ToPrinter(templateFormat string) (printers.Resour
p, err := printers.NewGoTemplatePrinter([]byte(templateValue))
if err != nil {
return nil, fmt.Errorf("error parsing template %s, %v\n", templateValue, err)
return nil, fmt.Errorf("error parsing template %s, %v", templateValue, err)
}
allowMissingKeys := true

View File

@@ -1,32 +0,0 @@
/*
Copyright 2019 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 kustomize
import (
"io"
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps"
"sigs.k8s.io/kustomize/pkg/commands/build"
"sigs.k8s.io/kustomize/pkg/fs"
)
// RunKustomizeBuild runs kustomize build given a filesystem and a path
func RunKustomizeBuild(out io.Writer, fSys fs.FileSystem, path string) error {
f := k8sdeps.NewFactory()
o := build.NewOptions(path, "")
return o.RunBuild(out, fSys, f.ResmapF, f.TransformerF)
}

View File

@@ -1,125 +0,0 @@
/*
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 configmapandsecret generates configmaps and secrets per generator rules.
package configmapandsecret
import (
"fmt"
"strings"
"unicode/utf8"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kv"
"sigs.k8s.io/kustomize/pkg/ifc"
"sigs.k8s.io/kustomize/pkg/types"
)
// ConfigMapFactory makes ConfigMaps.
type ConfigMapFactory struct {
ldr ifc.Loader
}
// NewConfigMapFactory returns a new ConfigMapFactory.
func NewConfigMapFactory(l ifc.Loader) *ConfigMapFactory {
return &ConfigMapFactory{ldr: l}
}
func (f *ConfigMapFactory) makeFreshConfigMap(
args *types.ConfigMapArgs) *corev1.ConfigMap {
cm := &corev1.ConfigMap{}
cm.APIVersion = "v1"
cm.Kind = "ConfigMap"
cm.Name = args.Name
cm.Namespace = args.Namespace
cm.Data = map[string]string{}
return cm
}
// MakeConfigMap returns a new ConfigMap, or nil and an error.
func (f *ConfigMapFactory) MakeConfigMap(
args *types.ConfigMapArgs, options *types.GeneratorOptions) (*corev1.ConfigMap, error) {
var all []kv.Pair
var err error
cm := f.makeFreshConfigMap(args)
pairs, err := keyValuesFromEnvFile(f.ldr, args.EnvSource)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf(
"env source file: %s",
args.EnvSource))
}
all = append(all, pairs...)
pairs, err = keyValuesFromLiteralSources(args.LiteralSources)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf(
"literal sources %v", args.LiteralSources))
}
all = append(all, pairs...)
pairs, err = keyValuesFromFileSources(f.ldr, args.FileSources)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf(
"file sources: %v", args.FileSources))
}
all = append(all, pairs...)
for _, p := range all {
err = addKvToConfigMap(cm, p.Key, p.Value)
if err != nil {
return nil, err
}
}
if options != nil {
cm.SetLabels(options.Labels)
cm.SetAnnotations(options.Annotations)
}
return cm, nil
}
// addKvToConfigMap adds the given key and data to the given config map.
// Error if key invalid, or already exists.
func addKvToConfigMap(configMap *corev1.ConfigMap, keyName, data string) error {
// Note, the rules for ConfigMap keys are the exact same as the ones for SecretKeys.
if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 {
return fmt.Errorf("%q is not a valid key name for a ConfigMap: %s", keyName, strings.Join(errs, ";"))
}
keyExistsErrorMsg := "cannot add key %s, another key by that name already exists: %v"
// If the configmap data contains byte sequences that are all in the UTF-8
// range, we will write it to .Data
if utf8.Valid([]byte(data)) {
if _, entryExists := configMap.Data[keyName]; entryExists {
return fmt.Errorf(keyExistsErrorMsg, keyName, configMap.Data)
}
configMap.Data[keyName] = data
return nil
}
// otherwise, it's BinaryData
if configMap.BinaryData == nil {
configMap.BinaryData = map[string][]byte{}
}
if _, entryExists := configMap.BinaryData[keyName]; entryExists {
return fmt.Errorf(keyExistsErrorMsg, keyName, configMap.BinaryData)
}
configMap.BinaryData[keyName] = []byte(data)
return nil
}

View File

@@ -1,107 +0,0 @@
/*
Copyright 2019 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 configmapandsecret
import (
"fmt"
"path"
"strings"
"github.com/pkg/errors"
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kv"
"sigs.k8s.io/kustomize/pkg/ifc"
)
func keyValuesFromLiteralSources(sources []string) ([]kv.Pair, error) {
var kvs []kv.Pair
for _, s := range sources {
k, v, err := parseLiteralSource(s)
if err != nil {
return nil, err
}
kvs = append(kvs, kv.Pair{Key: k, Value: v})
}
return kvs, nil
}
func keyValuesFromFileSources(ldr ifc.Loader, sources []string) ([]kv.Pair, error) {
var kvs []kv.Pair
for _, s := range sources {
k, fPath, err := parseFileSource(s)
if err != nil {
return nil, err
}
content, err := ldr.Load(fPath)
if err != nil {
return nil, err
}
kvs = append(kvs, kv.Pair{Key: k, Value: string(content)})
}
return kvs, nil
}
func keyValuesFromEnvFile(l ifc.Loader, path string) ([]kv.Pair, error) {
if path == "" {
return nil, nil
}
content, err := l.Load(path)
if err != nil {
return nil, err
}
return kv.KeyValuesFromLines(content)
}
// parseFileSource parses the source given.
//
// Acceptable formats include:
// 1. source-path: the basename will become the key name
// 2. source-name=source-path: the source-name will become the key name and
// source-path is the path to the key file.
//
// Key names cannot include '='.
func parseFileSource(source string) (keyName, filePath string, err error) {
numSeparators := strings.Count(source, "=")
switch {
case numSeparators == 0:
return path.Base(source), source, nil
case numSeparators == 1 && strings.HasPrefix(source, "="):
return "", "", fmt.Errorf("key name for file path %v missing", strings.TrimPrefix(source, "="))
case numSeparators == 1 && strings.HasSuffix(source, "="):
return "", "", fmt.Errorf("file path for key name %v missing", strings.TrimSuffix(source, "="))
case numSeparators > 1:
return "", "", errors.New("key names or file paths cannot contain '='")
default:
components := strings.Split(source, "=")
return components[0], components[1], nil
}
}
// parseLiteralSource parses the source key=val pair into its component pieces.
// This functionality is distinguished from strings.SplitN(source, "=", 2) since
// it returns an error in the case of empty keys, values, or a missing equals sign.
func parseLiteralSource(source string) (keyName, value string, err error) {
// leading equal is invalid
if strings.Index(source, "=") == 0 {
return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
}
// split after the first equal (so values can have the = character)
items := strings.SplitN(source, "=", 2)
if len(items) != 2 {
return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
}
return items[0], strings.Trim(items[1], "\"'"), nil
}

View File

@@ -1,106 +0,0 @@
/*
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 configmapandsecret
import (
"fmt"
"strings"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kv"
"sigs.k8s.io/kustomize/pkg/ifc"
"sigs.k8s.io/kustomize/pkg/types"
)
// SecretFactory makes Secrets.
type SecretFactory struct {
ldr ifc.Loader
}
// NewSecretFactory returns a new SecretFactory.
func NewSecretFactory(ldr ifc.Loader) *SecretFactory {
return &SecretFactory{ldr: ldr}
}
func (f *SecretFactory) makeFreshSecret(args *types.SecretArgs) *corev1.Secret {
s := &corev1.Secret{}
s.APIVersion = "v1"
s.Kind = "Secret"
s.Name = args.Name
s.Namespace = args.Namespace
s.Type = corev1.SecretType(args.Type)
if s.Type == "" {
s.Type = corev1.SecretTypeOpaque
}
s.Data = map[string][]byte{}
return s
}
// MakeSecret returns a new secret.
func (f *SecretFactory) MakeSecret(args *types.SecretArgs, options *types.GeneratorOptions) (*corev1.Secret, error) {
var all []kv.Pair
var err error
s := f.makeFreshSecret(args)
pairs, err := keyValuesFromEnvFile(f.ldr, args.EnvSource)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf(
"env source file: %s",
args.EnvSource))
}
all = append(all, pairs...)
pairs, err = keyValuesFromLiteralSources(args.LiteralSources)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf(
"literal sources %v", args.LiteralSources))
}
all = append(all, pairs...)
pairs, err = keyValuesFromFileSources(f.ldr, args.FileSources)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf(
"file sources: %v", args.FileSources))
}
all = append(all, pairs...)
for _, p := range all {
err = addKvToSecret(s, p.Key, p.Value)
if err != nil {
return nil, err
}
}
if options != nil {
s.SetLabels(options.Labels)
s.SetAnnotations(options.Annotations)
}
return s, nil
}
func addKvToSecret(secret *corev1.Secret, keyName, data string) error {
// Note, the rules for SecretKeys keys are the exact same as the ones for ConfigMap.
if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 {
return fmt.Errorf("%q is not a valid key name for a Secret: %s", keyName, strings.Join(errs, ";"))
}
if _, entryExists := secret.Data[keyName]; entryExists {
return fmt.Errorf("cannot add key %s, another key by that name already exists", keyName)
}
secret.Data[keyName] = []byte(data)
return nil
}

View File

@@ -1,76 +0,0 @@
/*
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.
*/
// It's possible that kustomize's features will be vendored into
// the kubernetes/kubernetes repo and made available to kubectl
// commands, while at the same time the kustomize program will
// continue to exist as an independent CLI. Vendoring snapshots
// would be taken just before a kubectl release.
//
// This creates a problem in that freestanding-kustomize depends on
// (for example):
//
// https://github.com/kubernetes/apimachinery/
// tree/master/pkg/util/yaml
//
// It vendors that package into
// sigs.k8s.io/kustomize/vendor/k8s.io/apimachinery/
//
// Whereas kubectl-kustomize would have to depend on the "staging"
// version of this code, located at
//
// https://github.com/kubernetes/kubernetes/
// blob/master/staging/src/k8s.io/apimachinery/pkg/util/yaml
//
// which is "vendored" via symlinks:
// k8s.io/kubernetes/vendor/k8s.io/apimachinery
// is a symlink to
// ../../staging/src/k8s.io/apimachinery
//
// The staging version is the canonical, under-development
// version of the code that kubectl depends on, whereas the packages
// at kubernetes/apimachinery are periodic snapshots of staging made
// for outside tools to depend on.
//
// apimachinery isn't the only package that poses this problem, just
// using it as a specific example.
//
// The kubectl binary cannot vendor in kustomize code that in
// turn vendors in the non-staging packages.
//
// One way to fix some of this would be to copy code - a hard fork.
// This has all the problems associated with a hard forking.
//
// Another way would be to break the kustomize repo into three:
//
// (1) kustomize - repo with the main() function,
// vendoring (2) and (3).
//
// (2) kustomize-libs - packages used by (1) with no
// apimachinery dependence.
//
// (3) kustomize-k8sdeps - A thin code layer that depends
// on (vendors) apimachinery to provide thin implementations
// to interfaces used in (2).
//
// The kubectl repo would then vendor from (2) only, and have
// a local implementation of (3). With that in mind, it's clear
// that (3) doesn't have to be a repo; the kustomize version of
// the thin layer can live directly in (1).
//
// This package is the code in (3), meant for kustomize.
package k8sdeps

View File

@@ -1,34 +0,0 @@
/*
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 k8sdeps provides kustomize factory with k8s dependencies
package k8sdeps
import (
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kunstruct"
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer"
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/validator"
"sigs.k8s.io/kustomize/pkg/factory"
)
// NewFactory creates an instance of KustFactory using k8sdeps factories
func NewFactory() *factory.KustFactory {
return factory.NewKustFactory(
kunstruct.NewKunstructuredFactoryImpl(),
validator.NewKustValidator(),
transformer.NewFactoryImpl(),
)
}

View File

@@ -1,118 +0,0 @@
/*
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 kunstruct
import (
"bytes"
"fmt"
"io"
"strings"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/configmapandsecret"
"sigs.k8s.io/kustomize/pkg/ifc"
"sigs.k8s.io/kustomize/pkg/types"
)
// KunstructuredFactoryImpl hides construction using apimachinery types.
type KunstructuredFactoryImpl struct {
cmFactory *configmapandsecret.ConfigMapFactory
secretFactory *configmapandsecret.SecretFactory
}
var _ ifc.KunstructuredFactory = &KunstructuredFactoryImpl{}
// NewKunstructuredFactoryImpl returns a factory.
func NewKunstructuredFactoryImpl() ifc.KunstructuredFactory {
return &KunstructuredFactoryImpl{}
}
// SliceFromBytes returns a slice of Kunstructured.
func (kf *KunstructuredFactoryImpl) SliceFromBytes(
in []byte) ([]ifc.Kunstructured, error) {
decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(in), 1024)
var result []ifc.Kunstructured
var err error
for err == nil || isEmptyYamlError(err) {
var out unstructured.Unstructured
err = decoder.Decode(&out)
if err == nil {
if len(out.Object) == 0 {
continue
}
err = kf.validate(out)
if err != nil {
return nil, err
}
result = append(result, &UnstructAdapter{Unstructured: out})
}
}
if err != io.EOF {
return nil, err
}
return result, nil
}
func isEmptyYamlError(err error) bool {
return strings.Contains(err.Error(), "is missing in 'null'")
}
// FromMap returns an instance of Kunstructured.
func (kf *KunstructuredFactoryImpl) FromMap(
m map[string]interface{}) ifc.Kunstructured {
return &UnstructAdapter{Unstructured: unstructured.Unstructured{Object: m}}
}
// MakeConfigMap returns an instance of Kunstructured for ConfigMap
func (kf *KunstructuredFactoryImpl) MakeConfigMap(args *types.ConfigMapArgs, options *types.GeneratorOptions) (ifc.Kunstructured, error) {
cm, err := kf.cmFactory.MakeConfigMap(args, options)
if err != nil {
return nil, err
}
return NewKunstructuredFromObject(cm)
}
// MakeSecret returns an instance of Kunstructured for Secret
func (kf *KunstructuredFactoryImpl) MakeSecret(args *types.SecretArgs, options *types.GeneratorOptions) (ifc.Kunstructured, error) {
sec, err := kf.secretFactory.MakeSecret(args, options)
if err != nil {
return nil, err
}
return NewKunstructuredFromObject(sec)
}
// Set sets loader
func (kf *KunstructuredFactoryImpl) Set(ldr ifc.Loader) {
kf.cmFactory = configmapandsecret.NewConfigMapFactory(ldr)
kf.secretFactory = configmapandsecret.NewSecretFactory(ldr)
}
// validate validates that u has kind and name
// except for kind `List`, which doesn't require a name
func (kf *KunstructuredFactoryImpl) validate(u unstructured.Unstructured) error {
kind := u.GetKind()
if kind == "" {
return fmt.Errorf("missing kind in object %v", u)
} else if strings.HasSuffix(kind, "List") {
return nil
}
if u.GetName() == "" {
return fmt.Errorf("missing metadata.name in object %v", u)
}
return nil
}

View File

@@ -1,71 +0,0 @@
/*
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 kunstruct provides unstructured from api machinery and factory for creating unstructured
package kunstruct
import (
"fmt"
"strings"
)
func parseFields(path string) ([]string, error) {
if !strings.Contains(path, "[") {
return strings.Split(path, "."), nil
}
var fields []string
start := 0
insideParentheses := false
for i := range path {
switch path[i] {
case '.':
if !insideParentheses {
fields = append(fields, path[start:i])
start = i + 1
}
case '[':
if !insideParentheses {
if i == start {
start = i + 1
} else {
fields = append(fields, path[start:i])
start = i + 1
}
insideParentheses = true
} else {
return nil, fmt.Errorf("nested parentheses are not allowed: %s", path)
}
case ']':
if insideParentheses {
fields = append(fields, path[start:i])
start = i + 1
insideParentheses = false
} else {
return nil, fmt.Errorf("invalid field path %s", path)
}
}
}
if start < len(path)-1 {
fields = append(fields, path[start:])
}
for i, f := range fields {
if strings.HasPrefix(f, "\"") || strings.HasPrefix(f, "'") {
fields[i] = strings.Trim(f, "\"'")
}
}
return fields, nil
}

View File

@@ -1,92 +0,0 @@
/*
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 kunstruct provides unstructured from api machinery and factory for creating unstructured
package kunstruct
import (
"encoding/json"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/kustomize/pkg/gvk"
"sigs.k8s.io/kustomize/pkg/ifc"
)
var _ ifc.Kunstructured = &UnstructAdapter{}
// UnstructAdapter wraps unstructured.Unstructured from
// https://github.com/kubernetes/apimachinery/blob/master/
// pkg/apis/meta/v1/unstructured/unstructured.go
// to isolate dependence on apimachinery.
type UnstructAdapter struct {
unstructured.Unstructured
}
// NewKunstructuredFromObject returns a new instance of Kunstructured.
func NewKunstructuredFromObject(obj runtime.Object) (ifc.Kunstructured, error) {
// Convert obj to a byte stream, then convert that to JSON (Unstructured).
marshaled, err := json.Marshal(obj)
if err != nil {
return &UnstructAdapter{}, err
}
var u unstructured.Unstructured
err = u.UnmarshalJSON(marshaled)
// creationTimestamp always 'null', remove it
u.SetCreationTimestamp(metav1.Time{})
return &UnstructAdapter{Unstructured: u}, err
}
// GetGvk returns the Gvk name of the object.
func (fs *UnstructAdapter) GetGvk() gvk.Gvk {
x := fs.GroupVersionKind()
return gvk.Gvk{
Group: x.Group,
Version: x.Version,
Kind: x.Kind,
}
}
// Copy provides a copy behind an interface.
func (fs *UnstructAdapter) Copy() ifc.Kunstructured {
return &UnstructAdapter{*fs.DeepCopy()}
}
// Map returns the unstructured content map.
func (fs *UnstructAdapter) Map() map[string]interface{} {
return fs.Object
}
// SetMap overrides the unstructured content map.
func (fs *UnstructAdapter) SetMap(m map[string]interface{}) {
fs.Object = m
}
// GetFieldValue returns value at the given fieldpath.
func (fs *UnstructAdapter) GetFieldValue(path string) (string, error) {
fields, err := parseFields(path)
if err != nil {
return "", err
}
s, found, err := unstructured.NestedString(
fs.UnstructuredContent(), fields...)
if found || err != nil {
return s, err
}
return "", fmt.Errorf("no field named '%s'", path)
}

View File

@@ -1,102 +0,0 @@
/*
Copyright 2019 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 kv
import (
"bufio"
"bytes"
"fmt"
"os"
"strings"
"unicode"
"unicode/utf8"
"k8s.io/apimachinery/pkg/util/validation"
)
// Pair represents a <key, value> pair.
type Pair struct {
Key string
Value string
}
var utf8bom = []byte{0xEF, 0xBB, 0xBF}
// KeyValuesFromLines parses given content in to a list of key-value pairs.
func KeyValuesFromLines(content []byte) ([]Pair, error) {
var kvs []Pair
scanner := bufio.NewScanner(bytes.NewReader(content))
currentLine := 0
for scanner.Scan() {
// Process the current line, retrieving a key/value pair if
// possible.
scannedBytes := scanner.Bytes()
kv, err := KeyValuesFromLine(scannedBytes, currentLine)
if err != nil {
return nil, err
}
currentLine++
if len(kv.Key) == 0 {
// no key means line was empty or a comment
continue
}
kvs = append(kvs, kv)
}
return kvs, nil
}
// KeyValuesFromLine returns a kv with blank key if the line is empty or a comment.
// The value will be retrieved from the environment if necessary.
func KeyValuesFromLine(line []byte, currentLine int) (Pair, error) {
kv := Pair{}
if !utf8.Valid(line) {
return kv, fmt.Errorf("line %d has invalid utf8 bytes : %v", line, string(line))
}
// We trim UTF8 BOM from the first line of the file but no others
if currentLine == 0 {
line = bytes.TrimPrefix(line, utf8bom)
}
// trim the line from all leading whitespace first
line = bytes.TrimLeftFunc(line, unicode.IsSpace)
// If the line is empty or a comment, we return a blank key/value pair.
if len(line) == 0 || line[0] == '#' {
return kv, nil
}
data := strings.SplitN(string(line), "=", 2)
key := data[0]
if errs := validation.IsEnvVarName(key); len(errs) != 0 {
return kv, fmt.Errorf("%q is not a valid key name: %s", key, strings.Join(errs, ";"))
}
if len(data) == 2 {
kv.Value = data[1]
} else {
// No value (no `=` in the line) is a signal to obtain the value
// from the environment.
kv.Value = os.Getenv(key)
}
kv.Key = key
return kv, nil
}

View File

@@ -1,43 +0,0 @@
/*
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 transformer provides transformer factory
package transformer
import (
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/hash"
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/patch"
"sigs.k8s.io/kustomize/pkg/resource"
"sigs.k8s.io/kustomize/pkg/transformers"
)
// FactoryImpl makes patch transformer and name hash transformer
type FactoryImpl struct{}
// NewFactoryImpl makes a new factoryImpl instance
func NewFactoryImpl() *FactoryImpl {
return &FactoryImpl{}
}
// MakePatchTransformer makes a new patch transformer
func (p *FactoryImpl) MakePatchTransformer(slice []*resource.Resource, rf *resource.Factory) (transformers.Transformer, error) {
return patch.NewPatchTransformer(slice, rf)
}
// MakeHashTransformer makes a new name hash transformer
func (p *FactoryImpl) MakeHashTransformer() transformers.Transformer {
return hash.NewNameHashTransformer()
}

View File

@@ -1,184 +0,0 @@
/*
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 hash
import (
"crypto/sha256"
"encoding/json"
"fmt"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// KustHash compute hash for unstructured objects
type KustHash struct{}
// NewKustHash returns a KustHash object
func NewKustHash() *KustHash {
return &KustHash{}
}
// Hash returns a hash of either a ConfigMap or a Secret
func (h *KustHash) Hash(m map[string]interface{}) (string, error) {
u := unstructured.Unstructured{
Object: m,
}
kind := u.GetKind()
switch kind {
case "ConfigMap":
cm, err := unstructuredToConfigmap(u)
if err != nil {
return "", err
}
return ConfigMapHash(cm)
case "Secret":
sec, err := unstructuredToSecret(u)
if err != nil {
return "", err
}
return SecretHash(sec)
default:
return "", fmt.Errorf("type %s is supported for hashing in %v", kind, m)
}
}
// ConfigMapHash returns a hash of the ConfigMap.
// The Data, Kind, and Name are taken into account.
func ConfigMapHash(cm *v1.ConfigMap) (string, error) {
encoded, err := encodeConfigMap(cm)
if err != nil {
return "", err
}
h, err := encodeHash(hash(encoded))
if err != nil {
return "", err
}
return h, nil
}
// SecretHash returns a hash of the Secret.
// The Data, Kind, Name, and Type are taken into account.
func SecretHash(sec *v1.Secret) (string, error) {
encoded, err := encodeSecret(sec)
if err != nil {
return "", err
}
h, err := encodeHash(hash(encoded))
if err != nil {
return "", err
}
return h, nil
}
// encodeConfigMap encodes a ConfigMap.
// Data, Kind, and Name are taken into account.
func encodeConfigMap(cm *v1.ConfigMap) (string, error) {
// json.Marshal sorts the keys in a stable order in the encoding
m := map[string]interface{}{
"kind": "ConfigMap",
"name": cm.Name,
"data": cm.Data,
}
if cm.Immutable != nil {
m["immutable"] = *cm.Immutable
}
if len(cm.BinaryData) > 0 {
m["binaryData"] = cm.BinaryData
}
data, err := json.Marshal(m)
if err != nil {
return "", err
}
return string(data), nil
}
// encodeSecret encodes a Secret.
// Data, Kind, Name, and Type are taken into account.
func encodeSecret(sec *v1.Secret) (string, error) {
// json.Marshal sorts the keys in a stable order in the encoding
m := map[string]interface{}{
"kind": "Secret",
"type": sec.Type,
"name": sec.Name,
"data": sec.Data,
}
if sec.Immutable != nil {
m["immutable"] = *sec.Immutable
}
data, err := json.Marshal(m)
if err != nil {
return "", err
}
return string(data), nil
}
// encodeHash extracts the first 40 bits of the hash from the hex string
// (1 hex char represents 4 bits), and then maps vowels and vowel-like hex
// characters to consonants to prevent bad words from being formed (the theory
// is that no vowels makes it really hard to make bad words). Since the string
// is hex, the only vowels it can contain are 'a' and 'e'.
// We picked some arbitrary consonants to map to from the same character set as GenerateName.
// See: https://github.com/kubernetes/apimachinery/blob/dc1f89aff9a7509782bde3b68824c8043a3e58cc/pkg/util/rand/rand.go#L75
// If the hex string contains fewer than ten characters, returns an error.
func encodeHash(hex string) (string, error) {
if len(hex) < 10 {
return "", fmt.Errorf("the hex string must contain at least 10 characters")
}
enc := []rune(hex[:10])
for i := range enc {
switch enc[i] {
case '0':
enc[i] = 'g'
case '1':
enc[i] = 'h'
case '3':
enc[i] = 'k'
case 'a':
enc[i] = 'm'
case 'e':
enc[i] = 't'
}
}
return string(enc), nil
}
// hash hashes `data` with sha256 and returns the hex string
func hash(data string) string {
return fmt.Sprintf("%x", sha256.Sum256([]byte(data)))
}
func unstructuredToConfigmap(u unstructured.Unstructured) (*v1.ConfigMap, error) {
marshaled, err := json.Marshal(u.Object)
if err != nil {
return nil, err
}
var out v1.ConfigMap
err = json.Unmarshal(marshaled, &out)
return &out, err
}
func unstructuredToSecret(u unstructured.Unstructured) (*v1.Secret, error) {
marshaled, err := json.Marshal(u.Object)
if err != nil {
return nil, err
}
var out v1.Secret
err = json.Unmarshal(marshaled, &out)
return &out, err
}

View File

@@ -1,47 +0,0 @@
/*
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 hash
import (
"fmt"
"sigs.k8s.io/kustomize/pkg/resmap"
"sigs.k8s.io/kustomize/pkg/transformers"
)
type nameHashTransformer struct{}
var _ transformers.Transformer = &nameHashTransformer{}
// NewNameHashTransformer construct a nameHashTransformer.
func NewNameHashTransformer() transformers.Transformer {
return &nameHashTransformer{}
}
// Transform appends hash to generated resources.
func (o *nameHashTransformer) Transform(m resmap.ResMap) error {
for _, res := range m {
if res.NeedHashSuffix() {
h, err := NewKustHash().Hash(res.Map())
if err != nil {
return err
}
res.SetName(fmt.Sprintf("%s-%s", res.GetName(), h))
}
}
return nil
}

View File

@@ -1,174 +0,0 @@
/*
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 patch
import (
"encoding/json"
"fmt"
"github.com/evanphx/json-patch"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/kustomize/pkg/gvk"
"sigs.k8s.io/kustomize/pkg/resmap"
"sigs.k8s.io/kustomize/pkg/resource"
"sigs.k8s.io/kustomize/pkg/transformers"
)
// patchTransformer applies patches.
type patchTransformer struct {
patches []*resource.Resource
rf *resource.Factory
}
var _ transformers.Transformer = &patchTransformer{}
// NewPatchTransformer constructs a patchTransformer.
func NewPatchTransformer(
slice []*resource.Resource, rf *resource.Factory) (transformers.Transformer, error) {
if len(slice) == 0 {
return transformers.NewNoOpTransformer(), nil
}
return &patchTransformer{patches: slice, rf: rf}, nil
}
// Transform apply the patches on top of the base resources.
func (pt *patchTransformer) Transform(baseResourceMap resmap.ResMap) error {
// Merge and then index the patches by Id.
patches, err := pt.mergePatches()
if err != nil {
return err
}
// Strategic merge the resources exist in both base and patches.
for _, patch := range patches {
// Merge patches with base resource.
id := patch.Id()
matchedIds := baseResourceMap.GetMatchingIds(id.GvknEquals)
if len(matchedIds) == 0 {
return fmt.Errorf("failed to find an object with %s to apply the patch", id.GvknString())
}
if len(matchedIds) > 1 {
return fmt.Errorf("found multiple objects %#v targeted by patch %#v (ambiguous)", matchedIds, id)
}
id = matchedIds[0]
base := baseResourceMap[id]
merged := map[string]interface{}{}
versionedObj, err := scheme.Scheme.New(toSchemaGvk(id.Gvk()))
baseName := base.GetName()
switch {
case runtime.IsNotRegisteredError(err):
// Use JSON merge patch to handle types w/o schema
baseBytes, err := json.Marshal(base.Map())
if err != nil {
return err
}
patchBytes, err := json.Marshal(patch.Map())
if err != nil {
return err
}
mergedBytes, err := jsonpatch.MergePatch(baseBytes, patchBytes)
if err != nil {
return err
}
err = json.Unmarshal(mergedBytes, &merged)
if err != nil {
return err
}
case err != nil:
return err
default:
// Use Strategic-Merge-Patch to handle types w/ schema
// TODO: Change this to use the new Merge package.
// Store the name of the base object, because this name may have been munged.
// Apply this name to the patched object.
lookupPatchMeta, err := strategicpatch.NewPatchMetaFromStruct(versionedObj)
if err != nil {
return err
}
merged, err = strategicpatch.StrategicMergeMapPatchUsingLookupPatchMeta(
base.Map(),
patch.Map(),
lookupPatchMeta)
if err != nil {
return err
}
}
base.SetName(baseName)
baseResourceMap[id].SetMap(merged)
}
return nil
}
// mergePatches merge and index patches by Id.
// It errors out if there is conflict between patches.
func (pt *patchTransformer) mergePatches() (resmap.ResMap, error) {
rc := resmap.ResMap{}
for ix, patch := range pt.patches {
id := patch.Id()
existing, found := rc[id]
if !found {
rc[id] = patch
continue
}
versionedObj, err := scheme.Scheme.New(toSchemaGvk(id.Gvk()))
if err != nil && !runtime.IsNotRegisteredError(err) {
return nil, err
}
var cd conflictDetector
if err != nil {
cd = newJMPConflictDetector(pt.rf)
} else {
cd, err = newSMPConflictDetector(versionedObj, pt.rf)
if err != nil {
return nil, err
}
}
conflict, err := cd.hasConflict(existing, patch)
if err != nil {
return nil, err
}
if conflict {
conflictingPatch, err := cd.findConflict(ix, pt.patches)
if err != nil {
return nil, err
}
return nil, fmt.Errorf(
"conflict between %#v and %#v",
conflictingPatch.Map(), patch.Map())
}
merged, err := cd.mergePatches(existing, patch)
if err != nil {
return nil, err
}
rc[id] = merged
}
return rc, nil
}
// toSchemaGvk converts to a schema.GroupVersionKind.
func toSchemaGvk(x gvk.Gvk) schema.GroupVersionKind {
return schema.GroupVersionKind{
Group: x.Group,
Version: x.Version,
Kind: x.Kind,
}
}

View File

@@ -1,137 +0,0 @@
/*
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 patch
import (
"encoding/json"
"github.com/evanphx/json-patch"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/mergepatch"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"sigs.k8s.io/kustomize/pkg/resource"
)
type conflictDetector interface {
hasConflict(patch1, patch2 *resource.Resource) (bool, error)
findConflict(conflictingPatchIdx int, patches []*resource.Resource) (*resource.Resource, error)
mergePatches(patch1, patch2 *resource.Resource) (*resource.Resource, error)
}
type jsonMergePatch struct {
rf *resource.Factory
}
var _ conflictDetector = &jsonMergePatch{}
func newJMPConflictDetector(rf *resource.Factory) conflictDetector {
return &jsonMergePatch{rf: rf}
}
func (jmp *jsonMergePatch) hasConflict(
patch1, patch2 *resource.Resource) (bool, error) {
return mergepatch.HasConflicts(patch1.Map(), patch2.Map())
}
func (jmp *jsonMergePatch) findConflict(
conflictingPatchIdx int, patches []*resource.Resource) (*resource.Resource, error) {
for i, patch := range patches {
if i == conflictingPatchIdx {
continue
}
if !patches[conflictingPatchIdx].Id().GvknEquals(patch.Id()) {
continue
}
conflict, err := mergepatch.HasConflicts(
patch.Map(),
patches[conflictingPatchIdx].Map())
if err != nil {
return nil, err
}
if conflict {
return patch, nil
}
}
return nil, nil
}
func (jmp *jsonMergePatch) mergePatches(
patch1, patch2 *resource.Resource) (*resource.Resource, error) {
baseBytes, err := json.Marshal(patch1.Map())
if err != nil {
return nil, err
}
patchBytes, err := json.Marshal(patch2.Map())
if err != nil {
return nil, err
}
mergedBytes, err := jsonpatch.MergeMergePatches(baseBytes, patchBytes)
if err != nil {
return nil, err
}
mergedMap := make(map[string]interface{})
err = json.Unmarshal(mergedBytes, &mergedMap)
return jmp.rf.FromMap(mergedMap), err
}
type strategicMergePatch struct {
lookupPatchMeta strategicpatch.LookupPatchMeta
rf *resource.Factory
}
var _ conflictDetector = &strategicMergePatch{}
func newSMPConflictDetector(
versionedObj runtime.Object,
rf *resource.Factory) (conflictDetector, error) {
lookupPatchMeta, err := strategicpatch.NewPatchMetaFromStruct(versionedObj)
return &strategicMergePatch{lookupPatchMeta: lookupPatchMeta, rf: rf}, err
}
func (smp *strategicMergePatch) hasConflict(p1, p2 *resource.Resource) (bool, error) {
return strategicpatch.MergingMapsHaveConflicts(
p1.Map(), p2.Map(), smp.lookupPatchMeta)
}
func (smp *strategicMergePatch) findConflict(
conflictingPatchIdx int, patches []*resource.Resource) (*resource.Resource, error) {
for i, patch := range patches {
if i == conflictingPatchIdx {
continue
}
if !patches[conflictingPatchIdx].Id().GvknEquals(patch.Id()) {
continue
}
conflict, err := strategicpatch.MergingMapsHaveConflicts(
patch.Map(),
patches[conflictingPatchIdx].Map(),
smp.lookupPatchMeta)
if err != nil {
return nil, err
}
if conflict {
return patch, nil
}
}
return nil, nil
}
func (smp *strategicMergePatch) mergePatches(patch1, patch2 *resource.Resource) (*resource.Resource, error) {
mergeJSONMap, err := strategicpatch.MergeStrategicMergeMapPatchUsingLookupPatchMeta(
smp.lookupPatchMeta, patch1.Map(), patch2.Map())
return smp.rf.FromMap(mergeJSONMap), err
}

View File

@@ -1,61 +0,0 @@
/*
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 validator provides functions to validate labels, annotations, namespace using apimachinery
package validator
import (
"errors"
apivalidation "k8s.io/apimachinery/pkg/api/validation"
v1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// KustValidator validates Labels and annotations by apimachinery
type KustValidator struct{}
// NewKustValidator returns a KustValidator object
func NewKustValidator() *KustValidator {
return &KustValidator{}
}
// MakeAnnotationValidator returns a MapValidatorFunc using apimachinery.
func (v *KustValidator) MakeAnnotationValidator() func(map[string]string) error {
return func(x map[string]string) error {
errs := apivalidation.ValidateAnnotations(x, field.NewPath("field"))
if len(errs) > 0 {
return errors.New(errs.ToAggregate().Error())
}
return nil
}
}
// MakeLabelValidator returns a MapValidatorFunc using apimachinery.
func (v *KustValidator) MakeLabelValidator() func(map[string]string) error {
return func(x map[string]string) error {
errs := v1validation.ValidateLabels(x, field.NewPath("field"))
if len(errs) > 0 {
return errors.New(errs.ToAggregate().Error())
}
return nil
}
}
// ValidateNamespace validates a string is a valid namespace using apimachinery.
func (v *KustValidator) ValidateNamespace(s string) []string {
return validation.IsDNS1123Label(s)
}

View File

@@ -0,0 +1,59 @@
/*
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 printers
import (
"io"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
)
// OmitManagedFieldsPrinter wraps an existing printer and omits the managed fields from the object
// before printing it.
type OmitManagedFieldsPrinter struct {
Delegate ResourcePrinter
}
var _ ResourcePrinter = (*OmitManagedFieldsPrinter)(nil)
func omitManagedFields(o runtime.Object) runtime.Object {
a, err := meta.Accessor(o)
if err != nil {
// The object is not a `metav1.Object`, ignore it.
return o
}
a.SetManagedFields(nil)
return o
}
// PrintObj copies the object and omits the managed fields from the copied object before printing it.
func (p *OmitManagedFieldsPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
if obj == nil {
return p.Delegate.PrintObj(obj, w)
}
if meta.IsListType(obj) {
obj = obj.DeepCopyObject()
_ = meta.EachListItem(obj, func(item runtime.Object) error {
omitManagedFields(item)
return nil
})
} else if _, err := meta.Accessor(obj); err == nil {
obj = omitManagedFields(obj.DeepCopyObject())
}
return p.Delegate.PrintObj(obj, w)
}

View File

@@ -94,9 +94,8 @@ func printHeader(columnNames []string, w io.Writer) error {
// PrintObj prints the obj in a human-friendly format according to the type of the obj.
func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) error {
w, found := output.(*tabwriter.Writer)
if !found {
w = GetNewTabWriter(output)
if _, found := output.(*tabwriter.Writer); !found {
w := GetNewTabWriter(output)
output = w
defer w.Flush()
}

View File

@@ -35,7 +35,10 @@ import (
"k8s.io/apimachinery/pkg/runtime/serializer"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/discovery"
"k8s.io/client-go/rest"
"k8s.io/client-go/restmapper"
"sigs.k8s.io/kustomize/api/filesys"
)
var FileExtensions = []string{".json", ".yaml", ".yml"}
@@ -100,8 +103,6 @@ type Builder struct {
singleItemImplied bool
export bool
schema ContentValidator
// fakeClientFn is used for testing
@@ -178,6 +179,25 @@ func newBuilder(clientConfigFn ClientConfigFunc, restMapper RESTMapperFunc, cate
}
}
// noopClientGetter implements RESTClientGetter returning only errors.
// used as a dummy getter in a local-only builder.
type noopClientGetter struct{}
func (noopClientGetter) ToRESTConfig() (*rest.Config, error) {
return nil, fmt.Errorf("local operation only")
}
func (noopClientGetter) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
return nil, fmt.Errorf("local operation only")
}
func (noopClientGetter) ToRESTMapper() (meta.RESTMapper, error) {
return nil, fmt.Errorf("local operation only")
}
// NewLocalBuilder returns a builder that is configured not to create REST clients and avoids asking the server for results.
func NewLocalBuilder() *Builder {
return NewBuilder(noopClientGetter{}).Local()
}
func NewBuilder(restClientGetter RESTClientGetter) *Builder {
categoryExpanderFn := func() (restmapper.CategoryExpander, error) {
discoveryClient, err := restClientGetter.ToDiscoveryClient()
@@ -239,8 +259,14 @@ func (b *Builder) FilenameParam(enforceNamespace bool, filenameOptions *Filename
}
}
if filenameOptions.Kustomize != "" {
b.paths = append(b.paths, &KustomizeVisitor{filenameOptions.Kustomize,
NewStreamVisitor(nil, b.mapper, filenameOptions.Kustomize, b.schema)})
b.paths = append(
b.paths,
&KustomizeVisitor{
mapper: b.mapper,
dirPath: filenameOptions.Kustomize,
schema: b.schema,
fSys: filesys.MakeFsOnDisk(),
})
}
if enforceNamespace {
@@ -461,12 +487,6 @@ func (b *Builder) FieldSelectorParam(s string) *Builder {
return b
}
// ExportParam accepts the export boolean for these resources
func (b *Builder) ExportParam(export bool) *Builder {
b.export = export
return b
}
// NamespaceParam accepts the namespace that these resources should be
// considered under from - used by DefaultNamespace() and RequireNamespace()
func (b *Builder) NamespaceParam(namespace string) *Builder {
@@ -826,7 +846,7 @@ func (b *Builder) visitorResult() *Result {
return &Result{err: err}
}
}
return &Result{err: fmt.Errorf("resource(s) were provided, but no name, label selector, or --all flag specified")}
return &Result{err: fmt.Errorf("resource(s) were provided, but no name was specified")}
}
return &Result{err: missingResourceError}
}
@@ -870,7 +890,7 @@ func (b *Builder) visitBySelector() *Result {
if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
selectorNamespace = ""
}
visitors = append(visitors, NewSelector(client, mapping, selectorNamespace, labelSelector, fieldSelector, b.export, b.limitChunks))
visitors = append(visitors, NewSelector(client, mapping, selectorNamespace, labelSelector, fieldSelector, b.limitChunks))
}
if b.continueOnError {
result.visitor = EagerVisitorList(visitors)
@@ -970,7 +990,6 @@ func (b *Builder) visitByResource() *Result {
Mapping: mapping,
Namespace: selectorNamespace,
Name: tuple.Name,
Export: b.export,
}
items = append(items, info)
}
@@ -1035,7 +1054,6 @@ func (b *Builder) visitByName() *Result {
Mapping: mapping,
Namespace: selectorNamespace,
Name: name,
Export: b.export,
}
visitors = append(visitors, info)
}

View File

@@ -20,24 +20,17 @@ import (
"errors"
"fmt"
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
openapi_v2 "github.com/googleapis/gnostic/openapiv2"
yaml "gopkg.in/yaml.v2"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
)
// VerifyDryRun returns nil if a resource group-version-kind supports
// server-side dry-run. Otherwise, an error is returned.
func VerifyDryRun(gvk schema.GroupVersionKind, dynamicClient dynamic.Interface, discoveryClient discovery.DiscoveryInterface) error {
verifier := NewDryRunVerifier(dynamicClient, discoveryClient)
return verifier.HasSupport(gvk)
}
func NewDryRunVerifier(dynamicClient dynamic.Interface, discoveryClient discovery.DiscoveryInterface) *DryRunVerifier {
func NewDryRunVerifier(dynamicClient dynamic.Interface, openAPIGetter discovery.OpenAPISchemaInterface) *DryRunVerifier {
return &DryRunVerifier{
finder: NewCRDFinder(CRDFromDynamic(dynamicClient)),
openAPIGetter: discoveryClient,
openAPIGetter: openAPIGetter,
}
}

View File

@@ -18,7 +18,6 @@ package resource
import (
"context"
"strconv"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -46,6 +45,10 @@ type Helper struct {
// and on resources that support dry-run. If the apiserver or the resource
// does not support dry-run, then the change will be persisted to storage.
ServerDryRun bool
// FieldManager is the name associated with the actor or entity that is making
// changes.
FieldManager string
}
// NewHelper creates a Helper from a ResourceMapping
@@ -64,27 +67,26 @@ func (m *Helper) DryRun(dryRun bool) *Helper {
return m
}
func (m *Helper) Get(namespace, name string, export bool) (runtime.Object, error) {
// WithFieldManager sets the field manager option to indicate the actor or entity
// that is making changes in a create or update operation.
func (m *Helper) WithFieldManager(fieldManager string) *Helper {
m.FieldManager = fieldManager
return m
}
func (m *Helper) Get(namespace, name string) (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(context.TODO()).Get()
}
func (m *Helper) List(namespace, apiVersion string, export bool, options *metav1.ListOptions) (runtime.Object, error) {
func (m *Helper) List(namespace, apiVersion string, 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(context.TODO()).Get()
}
@@ -141,6 +143,9 @@ func (m *Helper) CreateWithOptions(namespace string, modify bool, obj runtime.Ob
if m.ServerDryRun {
options.DryRun = []string{metav1.DryRunAll}
}
if m.FieldManager != "" {
options.FieldManager = m.FieldManager
}
if modify {
// Attempt to version the object based on client logic.
version, err := metadataAccessor.ResourceVersion(obj)
@@ -174,6 +179,9 @@ func (m *Helper) Patch(namespace, name string, pt types.PatchType, data []byte,
if m.ServerDryRun {
options.DryRun = []string{metav1.DryRunAll}
}
if m.FieldManager != "" {
options.FieldManager = m.FieldManager
}
return m.RESTClient.Patch(pt).
NamespaceIfScoped(namespace, m.NamespaceScoped).
Resource(m.Resource).
@@ -190,6 +198,9 @@ func (m *Helper) Replace(namespace, name string, overwrite bool, obj runtime.Obj
if m.ServerDryRun {
options.DryRun = []string{metav1.DryRunAll}
}
if m.FieldManager != "" {
options.FieldManager = m.FieldManager
}
// Attempt to version the object based on client logic.
version, err := metadataAccessor.ResourceVersion(obj)

View File

@@ -0,0 +1,54 @@
/*
Copyright 2019 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"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/krusty"
)
// KustomizeVisitor handles kustomization.yaml files.
type KustomizeVisitor struct {
mapper *mapper
schema ContentValidator
// Directory expected to contain a kustomization file.
dirPath string
// File system containing dirPath.
fSys filesys.FileSystem
// Holds result of kustomize build, retained for tests.
yml []byte
}
// Visit passes the result of a kustomize build to a StreamVisitor.
func (v *KustomizeVisitor) Visit(fn VisitorFunc) error {
kOpts := krusty.MakeDefaultOptions()
kOpts.DoLegacyResourceSort = true
k := krusty.MakeKustomizer(kOpts)
m, err := k.Run(v.fSys, v.dirPath)
if err != nil {
return err
}
v.yml, err = m.AsYaml()
if err != nil {
return err
}
sv := NewStreamVisitor(
bytes.NewReader(v.yml), v.mapper, v.dirPath, v.schema)
return sv.Visit(fn)
}

View File

@@ -24,7 +24,7 @@ import (
)
// hold a single instance of the case-sensitive decoder
var caseSensitiveJsonIterator = json.CaseSensitiveJsonIterator()
var caseSensitiveJsonIterator = json.CaseSensitiveJSONIterator()
// metadataValidatingDecoder wraps a decoder and additionally ensures metadata schema fields decode before returning an unstructured object
type metadataValidatingDecoder struct {

View File

@@ -32,19 +32,17 @@ type Selector struct {
Namespace string
LabelSelector string
FieldSelector string
Export 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 bool, limitChunks int64) *Selector {
func NewSelector(client RESTClient, mapping *meta.RESTMapping, namespace, labelSelector, fieldSelector string, limitChunks int64) *Selector {
return &Selector{
Client: client,
Mapping: mapping,
Namespace: namespace,
LabelSelector: labelSelector,
FieldSelector: fieldSelector,
Export: export,
LimitChunks: limitChunks,
}
}
@@ -56,7 +54,6 @@ func (r *Selector) Visit(fn VisitorFunc) error {
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,

View File

@@ -30,9 +30,6 @@ import (
"golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform"
"sigs.k8s.io/kustomize/pkg/fs"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -42,7 +39,6 @@ import (
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/cli-runtime/pkg/kustomize"
)
const (
@@ -88,8 +84,6 @@ type Info struct {
// 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
@@ -99,7 +93,7 @@ func (i *Info) Visit(fn VisitorFunc) error {
// 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)
obj, err := NewHelper(i.Client, i.Mapping).Get(i.Namespace, i.Name)
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(context.TODO()).Error()
@@ -530,24 +524,6 @@ func (v *FileVisitor) Visit(fn VisitorFunc) error {
return v.StreamVisitor.Visit(fn)
}
// KustomizeVisitor is wrapper around a StreamVisitor, to handle Kustomization directories
type KustomizeVisitor struct {
Path string
*StreamVisitor
}
// Visit in a KustomizeVisitor gets the output of Kustomize build and save it in the Streamvisitor
func (v *KustomizeVisitor) Visit(fn VisitorFunc) error {
fSys := fs.MakeRealFS()
var out bytes.Buffer
err := kustomize.RunKustomizeBuild(&out, fSys, v.Path)
if err != nil {
return err
}
v.StreamVisitor.Reader = bytes.NewReader(out.Bytes())
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