diff --git a/cmd/kube-apiserver/app/server.go b/cmd/kube-apiserver/app/server.go index 174543ee9af..9b8b38f8f58 100644 --- a/cmd/kube-apiserver/app/server.go +++ b/cmd/kube-apiserver/app/server.go @@ -53,6 +53,7 @@ import ( "k8s.io/apiserver/pkg/util/webhook" clientgoinformers "k8s.io/client-go/informers" clientgoclientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "k8s.io/client-go/util/keyutil" cloudprovider "k8s.io/cloud-provider" cliflag "k8s.io/component-base/cli/flag" @@ -100,6 +101,12 @@ cluster's shared state through which all other components interact.`, // stop printing usage when the command errors SilenceUsage: true, + PersistentPreRunE: func(*cobra.Command, []string) error { + // silence client-go warnings. + // kube-apiserver loopback clients should not log self-issued warnings. + rest.SetDefaultWarningHandler(rest.NoWarnings{}) + return nil + }, RunE: func(cmd *cobra.Command, args []string) error { verflag.PrintAndExitIfRequested() cliflag.PrintFlags(cmd.Flags()) diff --git a/cmd/kube-controller-manager/app/controllermanager.go b/cmd/kube-controller-manager/app/controllermanager.go index 933eeae4aca..6eb12fe70dc 100644 --- a/cmd/kube-controller-manager/app/controllermanager.go +++ b/cmd/kube-controller-manager/app/controllermanager.go @@ -104,6 +104,13 @@ state of the cluster through the apiserver and makes changes attempting to move current state towards the desired state. Examples of controllers that ship with Kubernetes today are the replication controller, endpoints controller, namespace controller, and serviceaccounts controller.`, + PersistentPreRunE: func(*cobra.Command, []string) error { + // silence client-go warnings. + // kube-controller-manager generically watches APIs (including deprecated ones), + // and CI ensures it works properly against matching kube-apiserver versions. + restclient.SetDefaultWarningHandler(restclient.NoWarnings{}) + return nil + }, Run: func(cmd *cobra.Command, args []string) { verflag.PrintAndExitIfRequested() cliflag.PrintFlags(cmd.Flags()) diff --git a/pkg/kubectl/cmd/cmd.go b/pkg/kubectl/cmd/cmd.go index 98b9a98adc2..191d1a8c3ff 100644 --- a/pkg/kubectl/cmd/cmd.go +++ b/pkg/kubectl/cmd/cmd.go @@ -28,6 +28,7 @@ import ( "github.com/spf13/cobra" + "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" cliflag "k8s.io/component-base/cli/flag" cmdpkg "k8s.io/kubectl/pkg/cmd" @@ -69,6 +70,7 @@ import ( "k8s.io/kubectl/pkg/cmd/wait" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" + "k8s.io/kubectl/pkg/util/term" "k8s.io/kubernetes/pkg/kubectl/cmd/auth" "k8s.io/kubernetes/pkg/kubectl/cmd/convert" "k8s.io/kubernetes/pkg/kubectl/cmd/cp" @@ -428,6 +430,9 @@ func HandlePluginCommand(pluginHandler PluginHandler, cmdArgs []string) error { // NewKubectlCommand creates the `kubectl` command and its nested children. func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command { + warningHandler := rest.NewWarningWriter(err, rest.WarningWriterOptions{Deduplicate: true, Color: term.AllowsColorOutput(err)}) + warningsAsErrors := false + // Parent command to which all subcommands are added. cmds := &cobra.Command{ Use: "kubectl", @@ -441,10 +446,25 @@ func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command { // Hook before and after Run initialize and write profiles to disk, // respectively. PersistentPreRunE: func(*cobra.Command, []string) error { + rest.SetDefaultWarningHandler(warningHandler) return initProfiling() }, PersistentPostRunE: func(*cobra.Command, []string) error { - return flushProfiling() + if err := flushProfiling(); err != nil { + return err + } + if warningsAsErrors { + count := warningHandler.WarningCount() + switch count { + case 0: + // no warnings + case 1: + return fmt.Errorf("%d warning received", count) + default: + return fmt.Errorf("%d warnings received", count) + } + } + return nil }, BashCompletionFunction: bashCompletionFunc, } @@ -458,6 +478,8 @@ func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command { addProfilingFlags(flags) + flags.BoolVar(&warningsAsErrors, "warnings-as-errors", warningsAsErrors, "Treat warnings received from the server as errors and exit with a non-zero exit code") + kubeConfigFlags := genericclioptions.NewConfigFlags(true).WithDeprecatedPasswordFlag() kubeConfigFlags.AddFlags(flags) matchVersionKubeConfigFlags := cmdutil.NewMatchVersionFlags(kubeConfigFlags) diff --git a/staging/src/k8s.io/kubectl/pkg/util/term/term.go b/staging/src/k8s.io/kubectl/pkg/util/term/term.go index 465b771e341..6bcda59d73d 100644 --- a/staging/src/k8s.io/kubectl/pkg/util/term/term.go +++ b/staging/src/k8s.io/kubectl/pkg/util/term/term.go @@ -19,6 +19,7 @@ package term import ( "io" "os" + "runtime" "github.com/moby/term" @@ -70,6 +71,32 @@ func IsTerminal(i interface{}) bool { return terminal } +// AllowsColorOutput returns true if the specified writer is a terminal and +// the process environment indicates color output is supported and desired. +func AllowsColorOutput(w io.Writer) bool { + if !IsTerminal(w) { + return false + } + + // https://en.wikipedia.org/wiki/Computer_terminal#Dumb_terminals + if os.Getenv("TERM") == "dumb" { + return false + } + + // https://no-color.org/ + if _, nocolor := os.LookupEnv("NO_COLOR"); nocolor { + return false + } + + // On Windows WT_SESSION is set by the modern terminal component. + // Older terminals have poor support for UTF-8, VT escape codes, etc. + if runtime.GOOS == "windows" && os.Getenv("WT_SESSION") == "" { + return false + } + + return true +} + // Safe invokes the provided function and will attempt to ensure that when the // function returns (or a termination signal is sent) that the terminal state // is reset to the condition it was in prior to the function being invoked. If diff --git a/test/cmd/rbac.sh b/test/cmd/rbac.sh index 02e304bb791..c9145f7bdc4 100755 --- a/test/cmd/rbac.sh +++ b/test/cmd/rbac.sh @@ -145,6 +145,14 @@ run_role_tests() { create_and_use_new_namespace kube::log::status "Testing role" + # Test deprecated API request output + # TODO(liggitt): switch this to a custom deprecated resource once CRDs support marking versions as deprecated + output_message=$(kubectl get roles.v1beta1.rbac.authorization.k8s.io 2>&1 "${kube_flags[@]}") + kube::test::if_has_string "${output_message}" 'Role is deprecated' + output_message=$(! kubectl get roles.v1beta1.rbac.authorization.k8s.io --warnings-as-errors 2>&1 "${kube_flags[@]}") + kube::test::if_has_string "${output_message}" 'Role is deprecated' + kube::test::if_has_string "${output_message}" 'Error: 1 warning received' + # Dry-run create kubectl create "${kube_flags[@]}" role pod-admin --dry-run=client --verb=* --resource=pods kubectl create "${kube_flags[@]}" role pod-admin --dry-run=server --verb=* --resource=pods