...

Source file src/k8s.io/kubectl/pkg/cmd/util/helpers.go

Documentation: k8s.io/kubectl/pkg/cmd/util

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package util
    18  
    19  import (
    20  	"bytes"
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"net/url"
    25  	"os"
    26  	"strconv"
    27  	"strings"
    28  	"time"
    29  
    30  	jsonpatch "github.com/evanphx/json-patch"
    31  	"github.com/spf13/cobra"
    32  	"github.com/spf13/pflag"
    33  
    34  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    35  	"k8s.io/apimachinery/pkg/api/meta"
    36  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    37  	"k8s.io/apimachinery/pkg/runtime"
    38  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    39  	"k8s.io/apimachinery/pkg/util/sets"
    40  	"k8s.io/apimachinery/pkg/util/strategicpatch"
    41  	"k8s.io/apimachinery/pkg/util/yaml"
    42  	"k8s.io/cli-runtime/pkg/genericclioptions"
    43  	"k8s.io/cli-runtime/pkg/resource"
    44  	"k8s.io/client-go/dynamic"
    45  	"k8s.io/client-go/rest"
    46  	"k8s.io/client-go/scale"
    47  	"k8s.io/client-go/tools/clientcmd"
    48  	"k8s.io/klog/v2"
    49  	utilexec "k8s.io/utils/exec"
    50  )
    51  
    52  const (
    53  	ApplyAnnotationsFlag = "save-config"
    54  	DefaultErrorExitCode = 1
    55  	DefaultChunkSize     = 500
    56  )
    57  
    58  type debugError interface {
    59  	DebugError() (msg string, args []interface{})
    60  }
    61  
    62  // AddSourceToErr adds handleResourcePrefix and source string to error message.
    63  // verb is the string like "creating", "deleting" etc.
    64  // source is the filename or URL to the template file(*.json or *.yaml), or stdin to use to handle the resource.
    65  func AddSourceToErr(verb string, source string, err error) error {
    66  	if source != "" {
    67  		if statusError, ok := err.(apierrors.APIStatus); ok {
    68  			status := statusError.Status()
    69  			status.Message = fmt.Sprintf("error when %s %q: %v", verb, source, status.Message)
    70  			return &apierrors.StatusError{ErrStatus: status}
    71  		}
    72  		return fmt.Errorf("error when %s %q: %v", verb, source, err)
    73  	}
    74  	return err
    75  }
    76  
    77  var fatalErrHandler = fatal
    78  
    79  // BehaviorOnFatal allows you to override the default behavior when a fatal
    80  // error occurs, which is to call os.Exit(code). You can pass 'panic' as a function
    81  // here if you prefer the panic() over os.Exit(1).
    82  func BehaviorOnFatal(f func(string, int)) {
    83  	fatalErrHandler = f
    84  }
    85  
    86  // DefaultBehaviorOnFatal allows you to undo any previous override.  Useful in
    87  // tests.
    88  func DefaultBehaviorOnFatal() {
    89  	fatalErrHandler = fatal
    90  }
    91  
    92  // fatal prints the message (if provided) and then exits. If V(99) or greater,
    93  // klog.Fatal is invoked for extended information. This is intended for maintainer
    94  // debugging and out of a reasonable range for users.
    95  func fatal(msg string, code int) {
    96  	// nolint:logcheck // Not using the result of klog.V(99) inside the if
    97  	// branch is okay, we just use it to determine how to terminate.
    98  	if klog.V(99).Enabled() {
    99  		klog.FatalDepth(2, msg)
   100  	}
   101  	if len(msg) > 0 {
   102  		// add newline if needed
   103  		if !strings.HasSuffix(msg, "\n") {
   104  			msg += "\n"
   105  		}
   106  		fmt.Fprint(os.Stderr, msg)
   107  	}
   108  	os.Exit(code)
   109  }
   110  
   111  // ErrExit may be passed to CheckError to instruct it to output nothing but exit with
   112  // status code 1.
   113  var ErrExit = fmt.Errorf("exit")
   114  
   115  // CheckErr prints a user friendly error to STDERR and exits with a non-zero
   116  // exit code. Unrecognized errors will be printed with an "error: " prefix.
   117  //
   118  // This method is generic to the command in use and may be used by non-Kubectl
   119  // commands.
   120  func CheckErr(err error) {
   121  	checkErr(err, fatalErrHandler)
   122  }
   123  
   124  // CheckDiffErr prints a user friendly error to STDERR and exits with a
   125  // non-zero and non-one exit code. Unrecognized errors will be printed
   126  // with an "error: " prefix.
   127  //
   128  // This method is meant specifically for `kubectl diff` and may be used
   129  // by other commands.
   130  func CheckDiffErr(err error) {
   131  	checkErr(err, func(msg string, code int) {
   132  		fatalErrHandler(msg, code+1)
   133  	})
   134  }
   135  
   136  // isInvalidReasonStatusError returns true if this is an API Status error with reason=Invalid.
   137  // This is distinct from generic 422 errors we want to fall back to generic error handling.
   138  func isInvalidReasonStatusError(err error) bool {
   139  	if !apierrors.IsInvalid(err) {
   140  		return false
   141  	}
   142  	statusError, isStatusError := err.(*apierrors.StatusError)
   143  	if !isStatusError {
   144  		return false
   145  	}
   146  	status := statusError.Status()
   147  	return status.Reason == metav1.StatusReasonInvalid
   148  }
   149  
   150  // checkErr formats a given error as a string and calls the passed handleErr
   151  // func with that string and an kubectl exit code.
   152  func checkErr(err error, handleErr func(string, int)) {
   153  	// unwrap aggregates of 1
   154  	if agg, ok := err.(utilerrors.Aggregate); ok && len(agg.Errors()) == 1 {
   155  		err = agg.Errors()[0]
   156  	}
   157  
   158  	if err == nil {
   159  		return
   160  	}
   161  
   162  	switch {
   163  	case err == ErrExit:
   164  		handleErr("", DefaultErrorExitCode)
   165  	case isInvalidReasonStatusError(err):
   166  		status := err.(*apierrors.StatusError).Status()
   167  		details := status.Details
   168  		s := "The request is invalid"
   169  		if details == nil {
   170  			// if we have no other details, include the message from the server if present
   171  			if len(status.Message) > 0 {
   172  				s += ": " + status.Message
   173  			}
   174  			handleErr(s, DefaultErrorExitCode)
   175  			return
   176  		}
   177  		if len(details.Kind) != 0 || len(details.Name) != 0 {
   178  			s = fmt.Sprintf("The %s %q is invalid", details.Kind, details.Name)
   179  		} else if len(status.Message) > 0 && len(details.Causes) == 0 {
   180  			// only append the message if we have no kind/name details and no causes,
   181  			// since default invalid error constructors duplicate that information in the message
   182  			s += ": " + status.Message
   183  		}
   184  
   185  		if len(details.Causes) > 0 {
   186  			errs := statusCausesToAggrError(details.Causes)
   187  			handleErr(MultilineError(s+": ", errs), DefaultErrorExitCode)
   188  		} else {
   189  			handleErr(s, DefaultErrorExitCode)
   190  		}
   191  	case clientcmd.IsConfigurationInvalid(err):
   192  		handleErr(MultilineError("Error in configuration: ", err), DefaultErrorExitCode)
   193  	default:
   194  		switch err := err.(type) {
   195  		case *meta.NoResourceMatchError:
   196  			switch {
   197  			case len(err.PartialResource.Group) > 0 && len(err.PartialResource.Version) > 0:
   198  				handleErr(fmt.Sprintf("the server doesn't have a resource type %q in group %q and version %q", err.PartialResource.Resource, err.PartialResource.Group, err.PartialResource.Version), DefaultErrorExitCode)
   199  			case len(err.PartialResource.Group) > 0:
   200  				handleErr(fmt.Sprintf("the server doesn't have a resource type %q in group %q", err.PartialResource.Resource, err.PartialResource.Group), DefaultErrorExitCode)
   201  			case len(err.PartialResource.Version) > 0:
   202  				handleErr(fmt.Sprintf("the server doesn't have a resource type %q in version %q", err.PartialResource.Resource, err.PartialResource.Version), DefaultErrorExitCode)
   203  			default:
   204  				handleErr(fmt.Sprintf("the server doesn't have a resource type %q", err.PartialResource.Resource), DefaultErrorExitCode)
   205  			}
   206  		case utilerrors.Aggregate:
   207  			handleErr(MultipleErrors(``, err.Errors()), DefaultErrorExitCode)
   208  		case utilexec.ExitError:
   209  			handleErr(err.Error(), err.ExitStatus())
   210  		default: // for any other error type
   211  			msg, ok := StandardErrorMessage(err)
   212  			if !ok {
   213  				msg = err.Error()
   214  				if !strings.HasPrefix(msg, "error: ") {
   215  					msg = fmt.Sprintf("error: %s", msg)
   216  				}
   217  			}
   218  			handleErr(msg, DefaultErrorExitCode)
   219  		}
   220  	}
   221  }
   222  
   223  func statusCausesToAggrError(scs []metav1.StatusCause) utilerrors.Aggregate {
   224  	errs := make([]error, 0, len(scs))
   225  	errorMsgs := sets.NewString()
   226  	for _, sc := range scs {
   227  		// check for duplicate error messages and skip them
   228  		msg := fmt.Sprintf("%s: %s", sc.Field, sc.Message)
   229  		if errorMsgs.Has(msg) {
   230  			continue
   231  		}
   232  		errorMsgs.Insert(msg)
   233  		errs = append(errs, errors.New(msg))
   234  	}
   235  	return utilerrors.NewAggregate(errs)
   236  }
   237  
   238  // StandardErrorMessage translates common errors into a human readable message, or returns
   239  // false if the error is not one of the recognized types. It may also log extended
   240  // information to klog.
   241  //
   242  // This method is generic to the command in use and may be used by non-Kubectl
   243  // commands.
   244  func StandardErrorMessage(err error) (string, bool) {
   245  	if debugErr, ok := err.(debugError); ok {
   246  		klog.V(4).Infof(debugErr.DebugError())
   247  	}
   248  	status, isStatus := err.(apierrors.APIStatus)
   249  	switch {
   250  	case isStatus:
   251  		switch s := status.Status(); {
   252  		case s.Reason == metav1.StatusReasonUnauthorized:
   253  			return fmt.Sprintf("error: You must be logged in to the server (%s)", s.Message), true
   254  		case len(s.Reason) > 0:
   255  			return fmt.Sprintf("Error from server (%s): %s", s.Reason, err.Error()), true
   256  		default:
   257  			return fmt.Sprintf("Error from server: %s", err.Error()), true
   258  		}
   259  	case apierrors.IsUnexpectedObjectError(err):
   260  		return fmt.Sprintf("Server returned an unexpected response: %s", err.Error()), true
   261  	}
   262  	switch t := err.(type) {
   263  	case *url.Error:
   264  		klog.V(4).Infof("Connection error: %s %s: %v", t.Op, t.URL, t.Err)
   265  		switch {
   266  		case strings.Contains(t.Err.Error(), "connection refused"):
   267  			host := t.URL
   268  			if server, err := url.Parse(t.URL); err == nil {
   269  				host = server.Host
   270  			}
   271  			return fmt.Sprintf("The connection to the server %s was refused - did you specify the right host or port?", host), true
   272  		}
   273  		return fmt.Sprintf("Unable to connect to the server: %v", t.Err), true
   274  	}
   275  	return "", false
   276  }
   277  
   278  // MultilineError returns a string representing an error that splits sub errors into their own
   279  // lines. The returned string will end with a newline.
   280  func MultilineError(prefix string, err error) string {
   281  	if agg, ok := err.(utilerrors.Aggregate); ok {
   282  		errs := utilerrors.Flatten(agg).Errors()
   283  		buf := &bytes.Buffer{}
   284  		switch len(errs) {
   285  		case 0:
   286  			return fmt.Sprintf("%s%v\n", prefix, err)
   287  		case 1:
   288  			return fmt.Sprintf("%s%v\n", prefix, messageForError(errs[0]))
   289  		default:
   290  			fmt.Fprintln(buf, prefix)
   291  			for _, err := range errs {
   292  				fmt.Fprintf(buf, "* %v\n", messageForError(err))
   293  			}
   294  			return buf.String()
   295  		}
   296  	}
   297  	return fmt.Sprintf("%s%s\n", prefix, err)
   298  }
   299  
   300  // PrintErrorWithCauses prints an error's kind, name, and each of the error's causes in a new line.
   301  // The returned string will end with a newline.
   302  // Returns true if a case exists to handle the error type, or false otherwise.
   303  func PrintErrorWithCauses(err error, errOut io.Writer) bool {
   304  	switch t := err.(type) {
   305  	case *apierrors.StatusError:
   306  		errorDetails := t.Status().Details
   307  		if errorDetails != nil {
   308  			fmt.Fprintf(errOut, "error: %s %q is invalid\n\n", errorDetails.Kind, errorDetails.Name)
   309  			for _, cause := range errorDetails.Causes {
   310  				fmt.Fprintf(errOut, "* %s: %s\n", cause.Field, cause.Message)
   311  			}
   312  			return true
   313  		}
   314  	}
   315  
   316  	fmt.Fprintf(errOut, "error: %v\n", err)
   317  	return false
   318  }
   319  
   320  // MultipleErrors returns a newline delimited string containing
   321  // the prefix and referenced errors in standard form.
   322  func MultipleErrors(prefix string, errs []error) string {
   323  	buf := &bytes.Buffer{}
   324  	for _, err := range errs {
   325  		fmt.Fprintf(buf, "%s%v\n", prefix, messageForError(err))
   326  	}
   327  	return buf.String()
   328  }
   329  
   330  // messageForError returns the string representing the error.
   331  func messageForError(err error) string {
   332  	msg, ok := StandardErrorMessage(err)
   333  	if !ok {
   334  		msg = err.Error()
   335  	}
   336  	return msg
   337  }
   338  
   339  func UsageErrorf(cmd *cobra.Command, format string, args ...interface{}) error {
   340  	msg := fmt.Sprintf(format, args...)
   341  	return fmt.Errorf("%s\nSee '%s -h' for help and examples", msg, cmd.CommandPath())
   342  }
   343  
   344  func IsFilenameSliceEmpty(filenames []string, directory string) bool {
   345  	return len(filenames) == 0 && directory == ""
   346  }
   347  
   348  func GetFlagString(cmd *cobra.Command, flag string) string {
   349  	s, err := cmd.Flags().GetString(flag)
   350  	if err != nil {
   351  		klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
   352  	}
   353  	return s
   354  }
   355  
   356  // GetFlagStringSlice can be used to accept multiple argument with flag repetition (e.g. -f arg1,arg2 -f arg3 ...)
   357  func GetFlagStringSlice(cmd *cobra.Command, flag string) []string {
   358  	s, err := cmd.Flags().GetStringSlice(flag)
   359  	if err != nil {
   360  		klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
   361  	}
   362  	return s
   363  }
   364  
   365  // GetFlagStringArray can be used to accept multiple argument with flag repetition (e.g. -f arg1 -f arg2 ...)
   366  func GetFlagStringArray(cmd *cobra.Command, flag string) []string {
   367  	s, err := cmd.Flags().GetStringArray(flag)
   368  	if err != nil {
   369  		klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
   370  	}
   371  	return s
   372  }
   373  
   374  func GetFlagBool(cmd *cobra.Command, flag string) bool {
   375  	b, err := cmd.Flags().GetBool(flag)
   376  	if err != nil {
   377  		klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
   378  	}
   379  	return b
   380  }
   381  
   382  // Assumes the flag has a default value.
   383  func GetFlagInt(cmd *cobra.Command, flag string) int {
   384  	i, err := cmd.Flags().GetInt(flag)
   385  	if err != nil {
   386  		klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
   387  	}
   388  	return i
   389  }
   390  
   391  // Assumes the flag has a default value.
   392  func GetFlagInt32(cmd *cobra.Command, flag string) int32 {
   393  	i, err := cmd.Flags().GetInt32(flag)
   394  	if err != nil {
   395  		klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
   396  	}
   397  	return i
   398  }
   399  
   400  // Assumes the flag has a default value.
   401  func GetFlagInt64(cmd *cobra.Command, flag string) int64 {
   402  	i, err := cmd.Flags().GetInt64(flag)
   403  	if err != nil {
   404  		klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
   405  	}
   406  	return i
   407  }
   408  
   409  func GetFlagDuration(cmd *cobra.Command, flag string) time.Duration {
   410  	d, err := cmd.Flags().GetDuration(flag)
   411  	if err != nil {
   412  		klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
   413  	}
   414  	return d
   415  }
   416  
   417  func GetPodRunningTimeoutFlag(cmd *cobra.Command) (time.Duration, error) {
   418  	timeout := GetFlagDuration(cmd, "pod-running-timeout")
   419  	if timeout <= 0 {
   420  		return timeout, fmt.Errorf("--pod-running-timeout must be higher than zero")
   421  	}
   422  	return timeout, nil
   423  }
   424  
   425  type FeatureGate string
   426  
   427  const (
   428  	ApplySet                FeatureGate = "KUBECTL_APPLYSET"
   429  	CmdPluginAsSubcommand   FeatureGate = "KUBECTL_ENABLE_CMD_SHADOW"
   430  	OpenAPIV3Patch          FeatureGate = "KUBECTL_OPENAPIV3_PATCH"
   431  	RemoteCommandWebsockets FeatureGate = "KUBECTL_REMOTE_COMMAND_WEBSOCKETS"
   432  	PortForwardWebsockets   FeatureGate = "KUBECTL_PORT_FORWARD_WEBSOCKETS"
   433  	DebugCustomProfile      FeatureGate = "KUBECTL_DEBUG_CUSTOM_PROFILE"
   434  )
   435  
   436  // IsEnabled returns true iff environment variable is set to true.
   437  // All other cases, it returns false.
   438  func (f FeatureGate) IsEnabled() bool {
   439  	return strings.ToLower(os.Getenv(string(f))) == "true"
   440  }
   441  
   442  // IsDisabled returns true iff environment variable is set to false.
   443  // All other cases, it returns true.
   444  // This function is used for the cases where feature is enabled by default,
   445  // but it may be needed to provide a way to ability to disable this feature.
   446  func (f FeatureGate) IsDisabled() bool {
   447  	return strings.ToLower(os.Getenv(string(f))) == "false"
   448  }
   449  
   450  func AddValidateFlags(cmd *cobra.Command) {
   451  	cmd.Flags().String(
   452  		"validate",
   453  		"strict",
   454  		`Must be one of: strict (or true), warn, ignore (or false).
   455  		"true" or "strict" will use a schema to validate the input and fail the request if invalid. It will perform server side validation if ServerSideFieldValidation is enabled on the api-server, but will fall back to less reliable client-side validation if not.
   456  		"warn" will warn about unknown or duplicate fields without blocking the request if server-side field validation is enabled on the API server, and behave as "ignore" otherwise.
   457  		"false" or "ignore" will not perform any schema validation, silently dropping any unknown or duplicate fields.`,
   458  	)
   459  
   460  	cmd.Flags().Lookup("validate").NoOptDefVal = "strict"
   461  }
   462  
   463  func AddFilenameOptionFlags(cmd *cobra.Command, options *resource.FilenameOptions, usage string) {
   464  	AddJsonFilenameFlag(cmd.Flags(), &options.Filenames, "Filename, directory, or URL to files "+usage)
   465  	AddKustomizeFlag(cmd.Flags(), &options.Kustomize)
   466  	cmd.Flags().BoolVarP(&options.Recursive, "recursive", "R", options.Recursive, "Process the directory used in -f, --filename recursively. Useful when you want to manage related manifests organized within the same directory.")
   467  }
   468  
   469  func AddJsonFilenameFlag(flags *pflag.FlagSet, value *[]string, usage string) {
   470  	flags.StringSliceVarP(value, "filename", "f", *value, usage)
   471  	annotations := make([]string, 0, len(resource.FileExtensions))
   472  	for _, ext := range resource.FileExtensions {
   473  		annotations = append(annotations, strings.TrimLeft(ext, "."))
   474  	}
   475  	flags.SetAnnotation("filename", cobra.BashCompFilenameExt, annotations)
   476  }
   477  
   478  // AddKustomizeFlag adds kustomize flag to a command
   479  func AddKustomizeFlag(flags *pflag.FlagSet, value *string) {
   480  	flags.StringVarP(value, "kustomize", "k", *value, "Process the kustomization directory. This flag can't be used together with -f or -R.")
   481  }
   482  
   483  // AddDryRunFlag adds dry-run flag to a command. Usually used by mutations.
   484  func AddDryRunFlag(cmd *cobra.Command) {
   485  	cmd.Flags().String(
   486  		"dry-run",
   487  		"none",
   488  		`Must be "none", "server", or "client". If client strategy, only print the object that would be sent, without sending it. If server strategy, submit server-side request without persisting the resource.`,
   489  	)
   490  	cmd.Flags().Lookup("dry-run").NoOptDefVal = "unchanged"
   491  }
   492  
   493  func AddFieldManagerFlagVar(cmd *cobra.Command, p *string, defaultFieldManager string) {
   494  	cmd.Flags().StringVar(p, "field-manager", defaultFieldManager, "Name of the manager used to track field ownership.")
   495  }
   496  
   497  func AddContainerVarFlags(cmd *cobra.Command, p *string, containerName string) {
   498  	cmd.Flags().StringVarP(p, "container", "c", containerName, "Container name. If omitted, use the kubectl.kubernetes.io/default-container annotation for selecting the container to be attached or the first container in the pod will be chosen")
   499  }
   500  
   501  func AddServerSideApplyFlags(cmd *cobra.Command) {
   502  	cmd.Flags().Bool("server-side", false, "If true, apply runs in the server instead of the client.")
   503  	cmd.Flags().Bool("force-conflicts", false, "If true, server-side apply will force the changes against conflicts.")
   504  }
   505  
   506  func AddPodRunningTimeoutFlag(cmd *cobra.Command, defaultTimeout time.Duration) {
   507  	cmd.Flags().Duration("pod-running-timeout", defaultTimeout, "The length of time (like 5s, 2m, or 3h, higher than zero) to wait until at least one pod is running")
   508  }
   509  
   510  func AddApplyAnnotationFlags(cmd *cobra.Command) {
   511  	cmd.Flags().Bool(ApplyAnnotationsFlag, false, "If true, the configuration of current object will be saved in its annotation. Otherwise, the annotation will be unchanged. This flag is useful when you want to perform kubectl apply on this object in the future.")
   512  }
   513  
   514  func AddApplyAnnotationVarFlags(cmd *cobra.Command, applyAnnotation *bool) {
   515  	cmd.Flags().BoolVar(applyAnnotation, ApplyAnnotationsFlag, *applyAnnotation, "If true, the configuration of current object will be saved in its annotation. Otherwise, the annotation will be unchanged. This flag is useful when you want to perform kubectl apply on this object in the future.")
   516  }
   517  
   518  func AddChunkSizeFlag(cmd *cobra.Command, value *int64) {
   519  	cmd.Flags().Int64Var(value, "chunk-size", *value,
   520  		"Return large lists in chunks rather than all at once. Pass 0 to disable. This flag is beta and may change in the future.")
   521  }
   522  
   523  func AddLabelSelectorFlagVar(cmd *cobra.Command, p *string) {
   524  	cmd.Flags().StringVarP(p, "selector", "l", *p, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints.")
   525  }
   526  
   527  func AddPruningFlags(cmd *cobra.Command, prune *bool, pruneAllowlist *[]string, all *bool, applySetRef *string) {
   528  	// Flags associated with the original allowlist-based alpha
   529  	cmd.Flags().StringArrayVar(pruneAllowlist, "prune-allowlist", *pruneAllowlist, "Overwrite the default allowlist with <group/version/kind> for --prune")
   530  	cmd.Flags().BoolVar(all, "all", *all, "Select all resources in the namespace of the specified resource types.")
   531  
   532  	// Flags associated with the new ApplySet-based alpha
   533  	if ApplySet.IsEnabled() {
   534  		cmd.Flags().StringVar(applySetRef, "applyset", *applySetRef, "[alpha] The name of the ApplySet that tracks which resources are being managed, for the purposes of determining what to prune. Live resources that are part of the ApplySet but have been removed from the provided configs will be deleted. Format: [RESOURCE][.GROUP]/NAME. A Secret will be used if no resource or group is specified.")
   535  		cmd.Flags().BoolVar(prune, "prune", *prune, "Automatically delete previously applied resource objects that do not appear in the provided configs. For alpha1, use with either -l or --all. For alpha2, use with --applyset.")
   536  	} else {
   537  		// different docs for the shared --prune flag if only alpha1 is enabled
   538  		cmd.Flags().BoolVar(prune, "prune", *prune, "Automatically delete resource objects, that do not appear in the configs and are created by either apply or create --save-config. Should be used with either -l or --all.")
   539  	}
   540  }
   541  
   542  func AddSubresourceFlags(cmd *cobra.Command, subresource *string, usage string, allowedSubresources ...string) {
   543  	cmd.Flags().StringVar(subresource, "subresource", "", fmt.Sprintf("%s Must be one of %v. This flag is beta and may change in the future.", usage, allowedSubresources))
   544  	CheckErr(cmd.RegisterFlagCompletionFunc("subresource", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
   545  		return allowedSubresources, cobra.ShellCompDirectiveNoFileComp
   546  	}))
   547  }
   548  
   549  type ValidateOptions struct {
   550  	ValidationDirective string
   551  }
   552  
   553  // Merge converts the passed in object to JSON, merges the fragment into it using an RFC7396 JSON Merge Patch,
   554  // and returns the resulting object
   555  // TODO: merge assumes JSON serialization, and does not properly abstract API retrieval
   556  func Merge(codec runtime.Codec, dst runtime.Object, fragment string) (runtime.Object, error) {
   557  	// encode dst into versioned json and apply fragment directly too it
   558  	target, err := runtime.Encode(codec, dst)
   559  	if err != nil {
   560  		return nil, err
   561  	}
   562  	patched, err := jsonpatch.MergePatch(target, []byte(fragment))
   563  	if err != nil {
   564  		return nil, err
   565  	}
   566  	out, err := runtime.Decode(codec, patched)
   567  	if err != nil {
   568  		return nil, err
   569  	}
   570  	return out, nil
   571  }
   572  
   573  // StrategicMerge converts the passed in object to JSON, merges the fragment into it using a Strategic Merge Patch,
   574  // and returns the resulting object
   575  func StrategicMerge(codec runtime.Codec, dst runtime.Object, fragment string, dataStruct runtime.Object) (runtime.Object, error) {
   576  	target, err := runtime.Encode(codec, dst)
   577  	if err != nil {
   578  		return nil, err
   579  	}
   580  	patched, err := strategicpatch.StrategicMergePatch(target, []byte(fragment), dataStruct)
   581  	if err != nil {
   582  		return nil, err
   583  	}
   584  	out, err := runtime.Decode(codec, patched)
   585  	if err != nil {
   586  		return nil, err
   587  	}
   588  	return out, nil
   589  }
   590  
   591  // JSONPatch converts the passed in object to JSON, performs an RFC6902 JSON Patch using operations specified in the
   592  // fragment, and returns the resulting object
   593  func JSONPatch(codec runtime.Codec, dst runtime.Object, fragment string) (runtime.Object, error) {
   594  	target, err := runtime.Encode(codec, dst)
   595  	if err != nil {
   596  		return nil, err
   597  	}
   598  	patch, err := jsonpatch.DecodePatch([]byte(fragment))
   599  	if err != nil {
   600  		return nil, err
   601  	}
   602  	patched, err := patch.Apply(target)
   603  	if err != nil {
   604  		return nil, err
   605  	}
   606  	out, err := runtime.Decode(codec, patched)
   607  	if err != nil {
   608  		return nil, err
   609  	}
   610  	return out, nil
   611  }
   612  
   613  // DumpReaderToFile writes all data from the given io.Reader to the specified file
   614  // (usually for temporary use).
   615  func DumpReaderToFile(reader io.Reader, filename string) error {
   616  	f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
   617  	if err != nil {
   618  		return err
   619  	}
   620  	defer f.Close()
   621  
   622  	buffer := make([]byte, 1024)
   623  	for {
   624  		count, err := reader.Read(buffer)
   625  		if err == io.EOF {
   626  			break
   627  		}
   628  		if err != nil {
   629  			return err
   630  		}
   631  		_, err = f.Write(buffer[:count])
   632  		if err != nil {
   633  			return err
   634  		}
   635  	}
   636  	return nil
   637  }
   638  
   639  func GetServerSideApplyFlag(cmd *cobra.Command) bool {
   640  	return GetFlagBool(cmd, "server-side")
   641  }
   642  
   643  func GetForceConflictsFlag(cmd *cobra.Command) bool {
   644  	return GetFlagBool(cmd, "force-conflicts")
   645  }
   646  
   647  func GetFieldManagerFlag(cmd *cobra.Command) string {
   648  	return GetFlagString(cmd, "field-manager")
   649  }
   650  
   651  func GetValidationDirective(cmd *cobra.Command) (string, error) {
   652  	var validateFlag = GetFlagString(cmd, "validate")
   653  	b, err := strconv.ParseBool(validateFlag)
   654  	if err != nil {
   655  		switch validateFlag {
   656  		case "strict":
   657  			return metav1.FieldValidationStrict, nil
   658  		case "warn":
   659  			return metav1.FieldValidationWarn, nil
   660  		case "ignore":
   661  			return metav1.FieldValidationIgnore, nil
   662  		default:
   663  			return metav1.FieldValidationStrict, fmt.Errorf(`invalid - validate option %q; must be one of: strict (or true), warn, ignore (or false)`, validateFlag)
   664  		}
   665  	}
   666  	// The flag was a boolean
   667  	if b {
   668  		return metav1.FieldValidationStrict, nil
   669  	}
   670  	return metav1.FieldValidationIgnore, nil
   671  }
   672  
   673  type DryRunStrategy int
   674  
   675  const (
   676  	// DryRunNone indicates the client will make all mutating calls
   677  	DryRunNone DryRunStrategy = iota
   678  
   679  	// DryRunClient, or client-side dry-run, indicates the client will prevent
   680  	// making mutating calls such as CREATE, PATCH, and DELETE
   681  	DryRunClient
   682  
   683  	// DryRunServer, or server-side dry-run, indicates the client will send
   684  	// mutating calls to the APIServer with the dry-run parameter to prevent
   685  	// persisting changes.
   686  	//
   687  	// Note that clients sending server-side dry-run calls should verify that
   688  	// the APIServer and the resource supports server-side dry-run, and otherwise
   689  	// clients should fail early.
   690  	//
   691  	// If a client sends a server-side dry-run call to an APIServer that doesn't
   692  	// support server-side dry-run, then the APIServer will persist changes inadvertently.
   693  	DryRunServer
   694  )
   695  
   696  func GetDryRunStrategy(cmd *cobra.Command) (DryRunStrategy, error) {
   697  	var dryRunFlag = GetFlagString(cmd, "dry-run")
   698  	b, err := strconv.ParseBool(dryRunFlag)
   699  	// The flag is not a boolean
   700  	if err != nil {
   701  		switch dryRunFlag {
   702  		case cmd.Flag("dry-run").NoOptDefVal:
   703  			klog.Warning(`--dry-run is deprecated and can be replaced with --dry-run=client.`)
   704  			return DryRunClient, nil
   705  		case "client":
   706  			return DryRunClient, nil
   707  		case "server":
   708  			return DryRunServer, nil
   709  		case "none":
   710  			return DryRunNone, nil
   711  		default:
   712  			return DryRunNone, fmt.Errorf(`Invalid dry-run value (%v). Must be "none", "server", or "client".`, dryRunFlag)
   713  		}
   714  	}
   715  	// The flag was a boolean
   716  	if b {
   717  		klog.Warningf(`--dry-run=%v is deprecated (boolean value) and can be replaced with --dry-run=%s.`, dryRunFlag, "client")
   718  		return DryRunClient, nil
   719  	}
   720  	klog.Warningf(`--dry-run=%v is deprecated (boolean value) and can be replaced with --dry-run=%s.`, dryRunFlag, "none")
   721  	return DryRunNone, nil
   722  }
   723  
   724  // PrintFlagsWithDryRunStrategy sets a success message at print time for the dry run strategy
   725  //
   726  // TODO(juanvallejo): This can be cleaned up even further by creating
   727  // a PrintFlags struct that binds the --dry-run flag, and whose
   728  // ToPrinter method returns a printer that understands how to print
   729  // this success message.
   730  func PrintFlagsWithDryRunStrategy(printFlags *genericclioptions.PrintFlags, dryRunStrategy DryRunStrategy) *genericclioptions.PrintFlags {
   731  	switch dryRunStrategy {
   732  	case DryRunClient:
   733  		printFlags.Complete("%s (dry run)")
   734  	case DryRunServer:
   735  		printFlags.Complete("%s (server dry run)")
   736  	}
   737  	return printFlags
   738  }
   739  
   740  // GetResourcesAndPairs retrieves resources and "KEY=VALUE or KEY-" pair args from given args
   741  func GetResourcesAndPairs(args []string, pairType string) (resources []string, pairArgs []string, err error) {
   742  	foundPair := false
   743  	for _, s := range args {
   744  		nonResource := (strings.Contains(s, "=") && s[0] != '=') || (strings.HasSuffix(s, "-") && s != "-")
   745  		switch {
   746  		case !foundPair && nonResource:
   747  			foundPair = true
   748  			fallthrough
   749  		case foundPair && nonResource:
   750  			pairArgs = append(pairArgs, s)
   751  		case !foundPair && !nonResource:
   752  			resources = append(resources, s)
   753  		case foundPair && !nonResource:
   754  			err = fmt.Errorf("all resources must be specified before %s changes: %s", pairType, s)
   755  			return
   756  		}
   757  	}
   758  	return
   759  }
   760  
   761  // ParsePairs retrieves new and remove pairs (if supportRemove is true) from "KEY=VALUE or KEY-" pair args
   762  func ParsePairs(pairArgs []string, pairType string, supportRemove bool) (newPairs map[string]string, removePairs []string, err error) {
   763  	newPairs = map[string]string{}
   764  	if supportRemove {
   765  		removePairs = []string{}
   766  	}
   767  	var invalidBuf bytes.Buffer
   768  	var invalidBufNonEmpty bool
   769  	for _, pairArg := range pairArgs {
   770  		if strings.Contains(pairArg, "=") && pairArg[0] != '=' {
   771  			parts := strings.SplitN(pairArg, "=", 2)
   772  			if len(parts) != 2 {
   773  				if invalidBufNonEmpty {
   774  					invalidBuf.WriteString(", ")
   775  				}
   776  				invalidBuf.WriteString(pairArg)
   777  				invalidBufNonEmpty = true
   778  			} else {
   779  				newPairs[parts[0]] = parts[1]
   780  			}
   781  		} else if supportRemove && strings.HasSuffix(pairArg, "-") && pairArg != "-" {
   782  			removePairs = append(removePairs, pairArg[:len(pairArg)-1])
   783  		} else {
   784  			if invalidBufNonEmpty {
   785  				invalidBuf.WriteString(", ")
   786  			}
   787  			invalidBuf.WriteString(pairArg)
   788  			invalidBufNonEmpty = true
   789  		}
   790  	}
   791  	if invalidBufNonEmpty {
   792  		err = fmt.Errorf("invalid %s format: %s", pairType, invalidBuf.String())
   793  		return
   794  	}
   795  
   796  	return
   797  }
   798  
   799  // IsSiblingCommandExists receives a pointer to a cobra command and a target string.
   800  // Returns true if the target string is found in the list of sibling commands.
   801  func IsSiblingCommandExists(cmd *cobra.Command, targetCmdName string) bool {
   802  	for _, c := range cmd.Parent().Commands() {
   803  		if c.Name() == targetCmdName {
   804  			return true
   805  		}
   806  	}
   807  
   808  	return false
   809  }
   810  
   811  // DefaultSubCommandRun prints a command's help string to the specified output if no
   812  // arguments (sub-commands) are provided, or a usage error otherwise.
   813  func DefaultSubCommandRun(out io.Writer) func(c *cobra.Command, args []string) {
   814  	return func(c *cobra.Command, args []string) {
   815  		c.SetOut(out)
   816  		c.SetErr(out)
   817  		RequireNoArguments(c, args)
   818  		c.Help()
   819  		CheckErr(ErrExit)
   820  	}
   821  }
   822  
   823  // RequireNoArguments exits with a usage error if extra arguments are provided.
   824  func RequireNoArguments(c *cobra.Command, args []string) {
   825  	if len(args) > 0 {
   826  		CheckErr(UsageErrorf(c, "unknown command %q", strings.Join(args, " ")))
   827  	}
   828  }
   829  
   830  // StripComments will transform a YAML file into JSON, thus dropping any comments
   831  // in it. Note that if the given file has a syntax error, the transformation will
   832  // fail and we will manually drop all comments from the file.
   833  func StripComments(file []byte) []byte {
   834  	stripped := file
   835  	stripped, err := yaml.ToJSON(stripped)
   836  	if err != nil {
   837  		stripped = ManualStrip(file)
   838  	}
   839  	return stripped
   840  }
   841  
   842  // ManualStrip is used for dropping comments from a YAML file
   843  func ManualStrip(file []byte) []byte {
   844  	stripped := []byte{}
   845  	lines := bytes.Split(file, []byte("\n"))
   846  	for i, line := range lines {
   847  		trimline := bytes.TrimSpace(line)
   848  
   849  		if bytes.HasPrefix(trimline, []byte("#")) && !bytes.HasPrefix(trimline, []byte("#!")) {
   850  			continue
   851  		}
   852  		stripped = append(stripped, line...)
   853  		if i < len(lines)-1 {
   854  			stripped = append(stripped, '\n')
   855  		}
   856  	}
   857  	return stripped
   858  }
   859  
   860  // ScaleClientFunc provides a ScalesGetter
   861  type ScaleClientFunc func(genericclioptions.RESTClientGetter) (scale.ScalesGetter, error)
   862  
   863  // ScaleClientFn gives a way to easily override the function for unit testing if needed.
   864  var ScaleClientFn ScaleClientFunc = scaleClient
   865  
   866  // scaleClient gives you back scale getter
   867  func scaleClient(restClientGetter genericclioptions.RESTClientGetter) (scale.ScalesGetter, error) {
   868  	discoveryClient, err := restClientGetter.ToDiscoveryClient()
   869  	if err != nil {
   870  		return nil, err
   871  	}
   872  
   873  	clientConfig, err := restClientGetter.ToRESTConfig()
   874  	if err != nil {
   875  		return nil, err
   876  	}
   877  
   878  	setKubernetesDefaults(clientConfig)
   879  	restClient, err := rest.RESTClientFor(clientConfig)
   880  	if err != nil {
   881  		return nil, err
   882  	}
   883  	resolver := scale.NewDiscoveryScaleKindResolver(discoveryClient)
   884  	mapper, err := restClientGetter.ToRESTMapper()
   885  	if err != nil {
   886  		return nil, err
   887  	}
   888  
   889  	return scale.New(restClient, mapper, dynamic.LegacyAPIPathResolverFunc, resolver), nil
   890  }
   891  
   892  func Warning(cmdErr io.Writer, newGeneratorName, oldGeneratorName string) {
   893  	fmt.Fprintf(cmdErr, "WARNING: New generator %q specified, "+
   894  		"but it isn't available. "+
   895  		"Falling back to %q.\n",
   896  		newGeneratorName,
   897  		oldGeneratorName,
   898  	)
   899  }
   900  
   901  // Difference removes any elements of subArray from fullArray and returns the result
   902  func Difference(fullArray []string, subArray []string) []string {
   903  	exclude := make(map[string]bool, len(subArray))
   904  	for _, elem := range subArray {
   905  		exclude[elem] = true
   906  	}
   907  	var result []string
   908  	for _, elem := range fullArray {
   909  		if _, found := exclude[elem]; !found {
   910  			result = append(result, elem)
   911  		}
   912  	}
   913  	return result
   914  }
   915  

View as plain text