add a warning printer in cli-runtime to coordinate warning style

modified:   staging/src/k8s.io/kubectl/pkg/cmd/auth/auth.go
This commit is contained in:
jlsong01 2022-02-18 20:33:38 +08:00
parent e7a2ce75e5
commit 272e245f06
13 changed files with 118 additions and 27 deletions

View File

@ -0,0 +1,55 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package printers
import (
"fmt"
"io"
)
const (
yellowColor = "\u001b[33;1m"
resetColor = "\u001b[0m"
)
type WarningPrinter struct {
// out is the writer to output warnings to
out io.Writer
// opts contains options controlling warning output
opts WarningPrinterOptions
}
// WarningPrinterOptions controls the behavior of a WarningPrinter constructed using NewWarningPrinter()
type WarningPrinterOptions struct {
// Color indicates that warning output can include ANSI color codes
Color bool
}
// NewWarningPrinter returns an implementation of warningPrinter that outputs warnings to the specified writer.
func NewWarningPrinter(out io.Writer, opts WarningPrinterOptions) *WarningPrinter {
h := &WarningPrinter{out: out, opts: opts}
return h
}
// Print prints warnings to the configured writer.
func (w *WarningPrinter) Print(message string) {
if w.opts.Color {
fmt.Fprintf(w.out, "%sWarning:%s %s\n", yellowColor, resetColor, message)
} else {
fmt.Fprintf(w.out, "Warning: %s\n", message)
}
}

View File

@ -43,6 +43,7 @@ import (
"k8s.io/kubectl/pkg/describe"
rbacutil "k8s.io/kubectl/pkg/util/rbac"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubectl/pkg/util/term"
)
// CanIOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of
@ -63,6 +64,7 @@ type CanIOptions struct {
List bool
genericclioptions.IOStreams
warningPrinter *printers.WarningPrinter
}
var (
@ -144,6 +146,8 @@ func NewCmdCanI(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.C
// Complete completes all the required options
func (o *CanIOptions) Complete(f cmdutil.Factory, args []string) error {
o.warningPrinter = printers.NewWarningPrinter(o.ErrOut, printers.WarningPrinterOptions{Color: term.AllowsColorOutput(o.ErrOut)})
if o.List {
if len(args) != 0 {
return errors.New("list option must be specified with no arguments")
@ -201,6 +205,10 @@ func (o *CanIOptions) Validate() error {
return nil
}
if o.warningPrinter == nil {
return fmt.Errorf("warningPrinter can not be used without initialization")
}
if o.NonResourceURL != "" {
if o.Subresource != "" {
return fmt.Errorf("--subresource can not be used with NonResourceURL")
@ -209,20 +217,19 @@ func (o *CanIOptions) Validate() error {
return fmt.Errorf("NonResourceURL and ResourceName can not specified together")
}
if !isKnownNonResourceVerb(o.Verb) {
fmt.Fprintf(o.ErrOut, "Warning: verb '%s' is not a known verb\n", o.Verb)
o.warningPrinter.Print(fmt.Sprintf("verb '%s' is not a known verb\n", o.Verb))
}
} else if !o.Resource.Empty() && !o.AllNamespaces && o.DiscoveryClient != nil {
if namespaced, err := isNamespaced(o.Resource, o.DiscoveryClient); err == nil && !namespaced {
if len(o.Resource.Group) == 0 {
fmt.Fprintf(o.ErrOut, "Warning: resource '%s' is not namespace scoped\n", o.Resource.Resource)
o.warningPrinter.Print(fmt.Sprintf("resource '%s' is not namespace scoped\n", o.Resource.Resource))
} else {
fmt.Fprintf(o.ErrOut, "Warning: resource '%s' is not namespace scoped in group '%s'\n", o.Resource.Resource, o.Resource.Group)
o.warningPrinter.Print(fmt.Sprintf("resource '%s' is not namespace scoped in group '%s'\n", o.Resource.Resource, o.Resource.Group))
}
}
if !isKnownResourceVerb(o.Verb) {
fmt.Fprintf(o.ErrOut, "Warning: verb '%s' is not a known verb\n", o.Verb)
o.warningPrinter.Print(fmt.Sprintf("verb '%s' is not a known verb\n", o.Verb))
}
}
if o.NoHeaders {
@ -309,9 +316,9 @@ func (o *CanIOptions) resourceFor(mapper meta.RESTMapper, resourceArg string) sc
if err != nil {
if !nonStandardResourceNames.Has(groupResource.String()) {
if len(groupResource.Group) == 0 {
fmt.Fprintf(o.ErrOut, "Warning: the server doesn't have a resource type '%s'\n", groupResource.Resource)
o.warningPrinter.Print(fmt.Sprintf("the server doesn't have a resource type '%s'\n", groupResource.Resource))
} else {
fmt.Fprintf(o.ErrOut, "Warning: the server doesn't have a resource type '%s' in group '%s'\n", groupResource.Resource, groupResource.Group)
o.warningPrinter.Print(fmt.Sprintf("the server doesn't have a resource type '%s' in group '%s'\n", groupResource.Resource, groupResource.Group))
}
}
return schema.GroupVersionResource{Resource: resourceArg}
@ -323,7 +330,7 @@ func (o *CanIOptions) resourceFor(mapper meta.RESTMapper, resourceArg string) sc
func (o *CanIOptions) printStatus(status authorizationv1.SubjectRulesReviewStatus) error {
if status.Incomplete {
fmt.Fprintf(o.ErrOut, "warning: the list may be incomplete: %v\n", status.EvaluationError)
o.warningPrinter.Print(fmt.Sprintf("the list may be incomplete: %v", status.EvaluationError))
}
breakdownRules := []rbacv1.PolicyRule{}

View File

@ -28,6 +28,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/printers"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
@ -281,7 +282,7 @@ func TestRunResourceFor(t *testing.T) {
expectGVR: schema.GroupVersionResource{
Resource: "invalid",
},
expectedErrOut: "Warning: the server doesn't have a resource type 'invalid'\n",
expectedErrOut: "Warning: the server doesn't have a resource type 'invalid'\n\n",
},
}
@ -292,6 +293,7 @@ func TestRunResourceFor(t *testing.T) {
ioStreams, _, _, buf := genericclioptions.NewTestIOStreams()
test.o.IOStreams = ioStreams
test.o.warningPrinter = printers.NewWarningPrinter(test.o.IOStreams.ErrOut, printers.WarningPrinterOptions{Color: false})
restMapper, err := tf.ToRESTMapper()
if err != nil {

View File

@ -37,6 +37,7 @@ import (
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/cli-runtime/pkg/resource"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/tools/cache"
@ -50,6 +51,7 @@ import (
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/interrupt"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubectl/pkg/util/term"
"k8s.io/utils/pointer"
)
@ -127,6 +129,7 @@ type DebugOptions struct {
podClient corev1client.CoreV1Interface
genericclioptions.IOStreams
warningPrinter *printers.WarningPrinter
}
// NewDebugOptions returns a DebugOptions initialized with default values.
@ -217,6 +220,9 @@ func (o *DebugOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st
o.attachChanged = cmd.Flags().Changed("attach")
o.shareProcessedChanged = cmd.Flags().Changed("share-processes")
// Warning printer
o.warningPrinter = printers.NewWarningPrinter(o.ErrOut, printers.WarningPrinterOptions{Color: term.AllowsColorOutput(o.ErrOut)})
return nil
}
@ -293,6 +299,11 @@ func (o *DebugOptions) Validate(cmd *cobra.Command) error {
return fmt.Errorf("-i/--stdin is required for containers with -t/--tty=true")
}
// warningPrinter
if o.warningPrinter == nil {
return fmt.Errorf("warningPrinter can not be used without initialization")
}
return nil
}
@ -742,7 +753,7 @@ func (o *DebugOptions) waitForContainer(ctx context.Context, ns, podName, contai
return true, nil
}
if !o.Quiet && s.State.Waiting != nil && s.State.Waiting.Message != "" {
fmt.Fprintf(o.ErrOut, "Warning: container %s: %s\n", containerName, s.State.Waiting.Message)
o.warningPrinter.Print(fmt.Sprintf("container %s: %s", containerName, s.State.Waiting.Message))
}
return false, nil
})

View File

@ -39,6 +39,7 @@ import (
"k8s.io/kubectl/pkg/util/completion"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubectl/pkg/util/term"
)
var (
@ -133,6 +134,7 @@ type DeleteOptions struct {
Result *resource.Result
genericclioptions.IOStreams
warningPrinter *printers.WarningPrinter
}
func NewCmdDelete(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
@ -230,6 +232,8 @@ func (o *DeleteOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Co
}
}
o.warningPrinter = printers.NewWarningPrinter(o.ErrOut, printers.WarningPrinterOptions{Color: term.AllowsColorOutput(o.ErrOut)})
return nil
}
@ -244,10 +248,13 @@ func (o *DeleteOptions) Validate() error {
if o.DeleteAll && len(o.FieldSelector) > 0 {
return fmt.Errorf("cannot set --all and --field-selector at the same time")
}
if o.warningPrinter == nil {
return fmt.Errorf("warningPrinter can not be used without initialization")
}
switch {
case o.GracePeriod == 0 && o.ForceDeletion:
fmt.Fprintf(o.ErrOut, "warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.\n")
o.warningPrinter.Print("Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.")
case o.GracePeriod > 0 && o.ForceDeletion:
return fmt.Errorf("--force and --grace-period greater than 0 cannot be specified together")
}
@ -311,7 +318,7 @@ func (o *DeleteOptions) DeleteResult(r *resource.Result) error {
options.PropagationPolicy = &o.CascadingStrategy
if warnClusterScope && info.Mapping.Scope.Name() == meta.RESTScopeNameRoot {
fmt.Fprintf(o.ErrOut, "warning: deleting cluster-scoped resources, not scoped to the provided namespace\n")
o.warningPrinter.Print("deleting cluster-scoped resources, not scoped to the provided namespace")
warnClusterScope = false
}

View File

@ -290,7 +290,7 @@ func TestGracePeriodScenarios(t *testing.T) {
forceFlag: true,
expectedGracePeriod: "0",
expectedOut: "pod/foo\n",
expectedErrOut: "warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.\n",
expectedErrOut: "Warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.\n",
expectedDeleteRequestPath: "/namespaces/test/pods/foo",
},
{
@ -300,7 +300,7 @@ func TestGracePeriodScenarios(t *testing.T) {
gracePeriodFlag: "0",
expectedGracePeriod: "0",
expectedOut: "pod/foo\n",
expectedErrOut: "warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.\n",
expectedErrOut: "Warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.\n",
expectedDeleteRequestPath: "/namespaces/test/pods/foo",
},
{

View File

@ -27,7 +27,6 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/cli-runtime/pkg/resource"
@ -37,6 +36,7 @@ import (
"k8s.io/kubectl/pkg/util/completion"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubectl/pkg/util/term"
)
type DrainCmdOptions struct {
@ -49,6 +49,7 @@ type DrainCmdOptions struct {
nodeInfos []*resource.Info
genericclioptions.IOStreams
warningPrinter *printers.WarningPrinter
}
var (
@ -258,6 +259,8 @@ func (o *DrainCmdOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [
return printer.PrintObj, nil
}
o.warningPrinter = printers.NewWarningPrinter(o.ErrOut, printers.WarningPrinterOptions{Color: term.AllowsColorOutput(o.ErrOut)})
builder := f.NewBuilder().
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
NamespaceParam(o.Namespace).DefaultNamespace().
@ -338,7 +341,7 @@ func (o *DrainCmdOptions) deleteOrEvictPodsSimple(nodeInfo *resource.Info) error
return utilerrors.NewAggregate(errs)
}
if warnings := list.Warnings(); warnings != "" {
fmt.Fprintf(o.ErrOut, "WARNING: %s\n", warnings)
o.warningPrinter.Print(warnings)
}
if o.drainer.DryRunStrategy == cmdutil.DryRunClient {
for _, pod := range list.Pods() {

View File

@ -599,7 +599,7 @@ func TestDrain(t *testing.T) {
args: []string{"node", "--force"},
expectFatal: false,
expectDelete: true,
expectWarning: "WARNING: deleting Pods that declare no controller: default/bar",
expectWarning: "Warning: deleting Pods that declare no controller: default/bar",
expectOutputToContain: "node/node drained",
},
{
@ -620,7 +620,7 @@ func TestDrain(t *testing.T) {
pods: []corev1.Pod{dsPodWithEmptyDir},
rcs: []corev1.ReplicationController{rc},
args: []string{"node", "--ignore-daemonsets"},
expectWarning: "WARNING: ignoring DaemonSet-managed Pods: default/bar",
expectWarning: "Warning: ignoring DaemonSet-managed Pods: default/bar",
expectFatal: false,
expectDelete: false,
expectOutputToContain: "node/node drained",

View File

@ -40,6 +40,7 @@ import (
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubectl/pkg/util/term"
)
var (
@ -130,6 +131,7 @@ type EnvOptions struct {
clientset *kubernetes.Clientset
genericclioptions.IOStreams
warningPrinter *printers.WarningPrinter
}
// NewEnvOptions returns an EnvOptions indicating all containers in the selected
@ -140,14 +142,14 @@ func NewEnvOptions(streams genericclioptions.IOStreams) *EnvOptions {
ContainerSelector: "*",
Overwrite: true,
IOStreams: streams,
IOStreams: streams,
}
}
// NewCmdEnv implements the OpenShift cli env command
func NewCmdEnv(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
o := NewEnvOptions(streams)
cmd := &cobra.Command{
Use: "env RESOURCE/NAME KEY_1=VAL_1 ... KEY_N=VAL_N",
DisableFlagsInUseLine: true,
@ -206,7 +208,7 @@ func contains(key string, keyList []string) bool {
func (o *EnvOptions) keyToEnvName(key string) string {
envName := strings.ToUpper(validEnvNameRegexp.ReplaceAllString(key, "_"))
if envName != key {
fmt.Fprintf(o.ErrOut, "warning: key %s transferred to %s\n", key, envName)
o.warningPrinter.Print(fmt.Sprintf("key %s transferred to %s", key, envName))
}
return envName
}
@ -251,6 +253,7 @@ func (o *EnvOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri
return err
}
o.builder = f.NewBuilder
o.warningPrinter = printers.NewWarningPrinter(o.ErrOut, printers.WarningPrinterOptions{Color: term.AllowsColorOutput(o.ErrOut)})
return nil
}
@ -269,6 +272,9 @@ func (o *EnvOptions) Validate() error {
if len(o.Keys) > 0 && len(o.From) == 0 {
return fmt.Errorf("when specifying --keys, a configmap or secret must be provided with --from")
}
if o.warningPrinter == nil {
return fmt.Errorf("warningPrinter can not be used without initialization")
}
return nil
}
@ -421,7 +427,7 @@ func (o *EnvOptions) RunEnv() error {
}
}
fmt.Fprintf(o.ErrOut, "warning: %s/%s does not have any containers matching %q\n", objKind, objName, o.ContainerSelector)
o.warningPrinter.Print(fmt.Sprintf("%s/%s does not have any containers matching %q", objKind, objName, o.ContainerSelector))
}
return nil
}

View File

@ -720,9 +720,9 @@ func TestSetEnvFromResource(t *testing.T) {
assert.NoError(t, err)
err = opts.RunEnv()
if input.warning {
assert.Contains(t, errOut.String(), "warning")
assert.Contains(t, errOut.String(), "Warning")
} else {
assert.NotContains(t, errOut.String(), "warning")
assert.NotContains(t, errOut.String(), "Warning")
}
assert.NoError(t, err)
})

View File

@ -1481,7 +1481,7 @@ run_namespace_tests() {
kubectl create namespace my-namespace
kube::test::get_object_assert 'namespaces/my-namespace' "{{$id_field}}" 'my-namespace'
output_message=$(! kubectl delete namespace -n my-namespace --all 2>&1 "${kube_flags[@]}")
kube::test::if_has_string "${output_message}" 'warning: deleting cluster-scoped resources'
kube::test::if_has_string "${output_message}" 'Warning: deleting cluster-scoped resources'
kube::test::if_has_string "${output_message}" 'namespace "my-namespace" deleted'
### Quota

View File

@ -41,7 +41,7 @@ run_clusterroles_tests() {
kubectl create "${kube_flags[@]:?}" clusterrole pod-admin --verb=* --resource=pods
kube::test::get_object_assert clusterrole/pod-admin "{{range.rules}}{{range.verbs}}{{.}}:{{end}}{{end}}" '\*:'
output_message=$(kubectl delete clusterrole pod-admin -n test 2>&1 "${kube_flags[@]}")
kube::test::if_has_string "${output_message}" 'warning: deleting cluster-scoped resources'
kube::test::if_has_string "${output_message}" 'Warning: deleting cluster-scoped resources'
kube::test::if_has_string "${output_message}" 'clusterrole.rbac.authorization.k8s.io "pod-admin" deleted'
kubectl create "${kube_flags[@]}" clusterrole pod-admin --verb=* --resource=pods

View File

@ -46,7 +46,7 @@ run_persistent_volumes_tests() {
kubectl create -f test/fixtures/doc-yaml/user-guide/persistent-volumes/volumes/local-01.yaml "${kube_flags[@]}"
kube::test::get_object_assert pv "{{range.items}}{{$id_field}}:{{end}}" 'pv0001:'
output_message=$(kubectl delete pv -n test --all 2>&1 "${kube_flags[@]}")
kube::test::if_has_string "${output_message}" 'warning: deleting cluster-scoped resources'
kube::test::if_has_string "${output_message}" 'Warning: deleting cluster-scoped resources'
kube::test::if_has_string "${output_message}" 'persistentvolume "pv0001" deleted'
kube::test::get_object_assert pv "{{range.items}}{{$id_field}}:{{end}}" ''