Merge pull request #62820 from juanvallejo/jvallejo/wire-more-print-flags

Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

wire printflags through additional cmds

**Release note**:
```release-note
NONE
```

Adds PrintFlag pattern to more commands.

cc @deads2k @soltysh
This commit is contained in:
Kubernetes Submit Queue 2018-04-26 02:20:34 -07:00 committed by GitHub
commit acbccfba84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 782 additions and 423 deletions

View File

@ -693,7 +693,7 @@ run_pod_tests() {
kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" 'valid-pod:' kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" 'valid-pod:'
## Patch can modify a local object ## Patch can modify a local object
kubectl patch --local -f pkg/kubectl/validation/testdata/v1/validPod.yaml --patch='{"spec": {"restartPolicy":"Never"}}' -o jsonpath='{.spec.restartPolicy}' | grep -q "Never" kubectl patch --local -f pkg/kubectl/validation/testdata/v1/validPod.yaml --patch='{"spec": {"restartPolicy":"Never"}}' -o yaml | grep -q "Never"
## Patch fails with error message "not patched" and exit code 1 ## Patch fails with error message "not patched" and exit code 1
output_message=$(! kubectl patch "${kube_flags[@]}" pod valid-pod -p='{"spec":{"replicas":7}}' 2>&1) output_message=$(! kubectl patch "${kube_flags[@]}" pod valid-pod -p='{"spec":{"replicas":7}}' 2>&1)
@ -2139,8 +2139,7 @@ run_recursive_resources_tests() {
kube::test::get_object_assert deployment "{{range.items}}{{$id_field}}:{{end}}" 'nginx:' kube::test::get_object_assert deployment "{{range.items}}{{$id_field}}:{{end}}" 'nginx:'
kube::test::get_object_assert deployment "{{range.items}}{{$image_field0}}:{{end}}" "${IMAGE_DEPLOYMENT_R1}:" kube::test::get_object_assert deployment "{{range.items}}{{$image_field0}}:{{end}}" "${IMAGE_DEPLOYMENT_R1}:"
# Command # Command
output_message=$(kubectl convert --local -f hack/testdata/deployment-revision1.yaml --output-version=apps/v1beta1 -o go-template='{{ .apiVersion }}' "${kube_flags[@]}") output_message=$(kubectl convert --local -f hack/testdata/deployment-revision1.yaml --output-version=apps/v1beta1 -o yaml "${kube_flags[@]}")
echo $output_message
# Post-condition: apiVersion is still extensions/v1beta1 in the live deployment, but command output is the new value # Post-condition: apiVersion is still extensions/v1beta1 in the live deployment, but command output is the new value
kube::test::get_object_assert 'deployment nginx' "{{ .apiVersion }}" 'extensions/v1beta1' kube::test::get_object_assert 'deployment nginx' "{{ .apiVersion }}" 'extensions/v1beta1'
kube::test::if_has_string "${output_message}" "apps/v1beta1" kube::test::if_has_string "${output_message}" "apps/v1beta1"
@ -2843,7 +2842,7 @@ run_rc_tests() {
# Pre-condition: default run without --name flag; should succeed by truncating the inherited name # Pre-condition: default run without --name flag; should succeed by truncating the inherited name
output_message=$(kubectl expose -f hack/testdata/pod-with-large-name.yaml --port=8081 2>&1 "${kube_flags[@]}") output_message=$(kubectl expose -f hack/testdata/pod-with-large-name.yaml --port=8081 2>&1 "${kube_flags[@]}")
# Post-condition: inherited name from pod has been truncated # Post-condition: inherited name from pod has been truncated
kube::test::if_has_string "${output_message}" '\"kubernetes-serve-hostname-testing-sixty-three-characters-in-len\" exposed' kube::test::if_has_string "${output_message}" 'kubernetes-serve-hostname-testing-sixty-three-characters-in-len exposed'
# Clean-up # Clean-up
kubectl delete svc kubernetes-serve-hostname-testing-sixty-three-characters-in-len "${kube_flags[@]}" kubectl delete svc kubernetes-serve-hostname-testing-sixty-three-characters-in-len "${kube_flags[@]}"
@ -2851,7 +2850,7 @@ run_rc_tests() {
# Pre-condition: don't use --port flag # Pre-condition: don't use --port flag
output_message=$(kubectl expose -f test/fixtures/doc-yaml/admin/high-availability/etcd.yaml --selector=test=etcd 2>&1 "${kube_flags[@]}") output_message=$(kubectl expose -f test/fixtures/doc-yaml/admin/high-availability/etcd.yaml --selector=test=etcd 2>&1 "${kube_flags[@]}")
# Post-condition: expose succeeded # Post-condition: expose succeeded
kube::test::if_has_string "${output_message}" '\"etcd-server\" exposed' kube::test::if_has_string "${output_message}" 'etcd-server exposed'
# Post-condition: generated service has both ports from the exposed pod # Post-condition: generated service has both ports from the exposed pod
kube::test::get_object_assert 'service etcd-server' "{{$port_name}} {{$port_field}}" 'port-1 2380' kube::test::get_object_assert 'service etcd-server' "{{$port_name}} {{$port_field}}" 'port-1 2380'
kube::test::get_object_assert 'service etcd-server' "{{$second_port_name}} {{$second_port_field}}" 'port-2 2379' kube::test::get_object_assert 'service etcd-server' "{{$second_port_name}} {{$second_port_field}}" 'port-2 2379'
@ -4647,7 +4646,7 @@ __EOF__
kube::test::if_has_string "${response}" 'must provide one or more resources' kube::test::if_has_string "${response}" 'must provide one or more resources'
# test=label matches our node # test=label matches our node
response=$(kubectl cordon --selector test=label) response=$(kubectl cordon --selector test=label)
kube::test::if_has_string "${response}" 'node "127.0.0.1" cordoned' kube::test::if_has_string "${response}" 'node/127.0.0.1 cordoned'
# invalid=label does not match any nodes # invalid=label does not match any nodes
response=$(kubectl cordon --selector invalid=label) response=$(kubectl cordon --selector invalid=label)
kube::test::if_has_not_string "${response}" 'cordoned' kube::test::if_has_not_string "${response}" 'cordoned'

View File

@ -19,6 +19,7 @@ package cmd
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io"
jsonpatch "github.com/evanphx/json-patch" jsonpatch "github.com/evanphx/json-patch"
"github.com/golang/glog" "github.com/golang/glog"
@ -29,6 +30,7 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/json"
"k8s.io/kubernetes/pkg/printers"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
@ -40,6 +42,9 @@ import (
// AnnotateOptions have the data required to perform the annotate operation // AnnotateOptions have the data required to perform the annotate operation
type AnnotateOptions struct { type AnnotateOptions struct {
PrintFlags *printers.PrintFlags
PrintObj printers.ResourcePrinterFunc
// Filename options // Filename options
resource.FilenameOptions resource.FilenameOptions
RecordFlags *genericclioptions.RecordFlags RecordFlags *genericclioptions.RecordFlags
@ -99,8 +104,9 @@ var (
func NewAnnotateOptions(ioStreams genericclioptions.IOStreams) *AnnotateOptions { func NewAnnotateOptions(ioStreams genericclioptions.IOStreams) *AnnotateOptions {
return &AnnotateOptions{ return &AnnotateOptions{
RecordFlags: genericclioptions.NewRecordFlags(), PrintFlags: printers.NewPrintFlags("annotated"),
RecordFlags: genericclioptions.NewRecordFlags(),
Recorder: genericclioptions.NoopRecorder{}, Recorder: genericclioptions.NoopRecorder{},
IOStreams: ioStreams, IOStreams: ioStreams,
} }
@ -131,8 +137,8 @@ func NewCmdAnnotate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *c
// bind flag structs // bind flag structs
o.RecordFlags.AddFlags(cmd) o.RecordFlags.AddFlags(cmd)
o.PrintFlags.AddFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddIncludeUninitializedFlag(cmd) cmdutil.AddIncludeUninitializedFlag(cmd)
cmd.Flags().BoolVar(&o.overwrite, "overwrite", o.overwrite, "If true, allow annotations to be overwritten, otherwise reject annotation updates that overwrite existing annotations.") cmd.Flags().BoolVar(&o.overwrite, "overwrite", o.overwrite, "If true, allow annotations to be overwritten, otherwise reject annotation updates that overwrite existing annotations.")
cmd.Flags().BoolVar(&o.local, "local", o.local, "If true, annotation will NOT contact api-server but run locally.") cmd.Flags().BoolVar(&o.local, "local", o.local, "If true, annotation will NOT contact api-server but run locally.")
@ -159,6 +165,17 @@ func (o *AnnotateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [
o.outputFormat = cmdutil.GetFlagString(cmd, "output") o.outputFormat = cmdutil.GetFlagString(cmd, "output")
o.dryrun = cmdutil.GetDryRunFlag(cmd) o.dryrun = cmdutil.GetDryRunFlag(cmd)
if o.dryrun {
o.PrintFlags.Complete("%s (dry run)")
}
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return err
}
o.PrintObj = func(obj runtime.Object, out io.Writer) error {
return printer.PrintObj(obj, out)
}
// retrieves resource and annotation args from args // retrieves resource and annotation args from args
// also checks args to verify that all resources are specified before annotations // also checks args to verify that all resources are specified before annotations
resources, annotationArgs, err := cmdutil.GetResourcesAndPairs(args, "annotation") resources, annotationArgs, err := cmdutil.GetResourcesAndPairs(args, "annotation")
@ -280,11 +297,7 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro
} }
} }
if len(o.outputFormat) > 0 { return o.PrintObj(outputObj, o.Out)
return cmdutil.PrintObject(cmd, outputObj, o.Out)
}
cmdutil.PrintSuccess(false, o.Out, info.Object, o.dryrun, "annotated")
return nil
}) })
} }

View File

@ -18,8 +18,13 @@ package cmd
import ( import (
"fmt" "fmt"
"io"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
utilerrors "k8s.io/apimachinery/pkg/util/errors" utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
@ -27,9 +32,7 @@ import (
"k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/printers"
"github.com/golang/glog"
"github.com/spf13/cobra"
) )
var ( var (
@ -48,22 +51,46 @@ var (
) )
type AutoscaleOptions struct { type AutoscaleOptions struct {
FilenameOptions resource.FilenameOptions FilenameOptions *resource.FilenameOptions
RecordFlags *genericclioptions.RecordFlags
RecordFlags *genericclioptions.RecordFlags
Recorder genericclioptions.Recorder Recorder genericclioptions.Recorder
PrintFlags *printers.PrintFlags
ToPrinter func(string) (printers.ResourcePrinterFunc, error)
Builder *resource.Builder
CanBeAutoscaled func(kind schema.GroupKind) error
CreateAnnotation bool
DryRun bool
EnforceNamespace bool
Mapper meta.RESTMapper
Typer runtime.ObjectTyper
ClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error)
GeneratorFunc func(string, *meta.RESTMapping) (kubectl.StructuredGenerator, error)
Namespace string
BuilderArgs []string
genericclioptions.IOStreams
} }
func NewAutoscaleOptions() *AutoscaleOptions { func NewAutoscaleOptions(ioStreams genericclioptions.IOStreams) *AutoscaleOptions {
return &AutoscaleOptions{ return &AutoscaleOptions{
FilenameOptions: resource.FilenameOptions{}, PrintFlags: printers.NewPrintFlags("autoscaled"),
FilenameOptions: &resource.FilenameOptions{},
RecordFlags: genericclioptions.NewRecordFlags(), RecordFlags: genericclioptions.NewRecordFlags(),
Recorder: genericclioptions.NoopRecorder{}, Recorder: genericclioptions.NoopRecorder{},
IOStreams: ioStreams,
} }
} }
func NewCmdAutoscale(f cmdutil.Factory, out io.Writer) *cobra.Command { func NewCmdAutoscale(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := NewAutoscaleOptions() o := NewAutoscaleOptions(ioStreams)
validArgs := []string{"deployment", "replicaset", "replicationcontroller"} validArgs := []string{"deployment", "replicaset", "replicationcontroller"}
argAliases := kubectl.ResourceAliases(validArgs) argAliases := kubectl.ResourceAliases(validArgs)
@ -75,8 +102,9 @@ func NewCmdAutoscale(f cmdutil.Factory, out io.Writer) *cobra.Command {
Long: autoscaleLong, Long: autoscaleLong,
Example: autoscaleExample, Example: autoscaleExample,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(f, cmd)) cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(o.RunAutoscale(f, out, cmd, args)) cmdutil.CheckErr(o.Validate(cmd))
cmdutil.CheckErr(o.Run())
}, },
ValidArgs: validArgs, ValidArgs: validArgs,
ArgAliases: argAliases, ArgAliases: argAliases,
@ -84,8 +112,8 @@ func NewCmdAutoscale(f cmdutil.Factory, out io.Writer) *cobra.Command {
// bind flag structs // bind flag structs
o.RecordFlags.AddFlags(cmd) o.RecordFlags.AddFlags(cmd)
o.PrintFlags.AddFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmd.Flags().String("generator", cmdutil.HorizontalPodAutoscalerV1GeneratorName, i18n.T("The name of the API generator to use. Currently there is only 1 generator.")) cmd.Flags().String("generator", cmdutil.HorizontalPodAutoscalerV1GeneratorName, i18n.T("The name of the API generator to use. Currently there is only 1 generator."))
cmd.Flags().Int32("min", -1, "The lower limit for the number of pods that can be set by the autoscaler. If it's not specified or negative, the server will apply a default value.") cmd.Flags().Int32("min", -1, "The lower limit for the number of pods that can be set by the autoscaler. If it's not specified or negative, the server will apply a default value.")
cmd.Flags().Int32("max", -1, "The upper limit for the number of pods that can be set by the autoscaler. Required.") cmd.Flags().Int32("max", -1, "The upper limit for the number of pods that can be set by the autoscaler. Required.")
@ -94,73 +122,105 @@ func NewCmdAutoscale(f cmdutil.Factory, out io.Writer) *cobra.Command {
cmd.Flags().String("name", "", i18n.T("The name for the newly created object. If not specified, the name of the input resource will be used.")) cmd.Flags().String("name", "", i18n.T("The name for the newly created object. If not specified, the name of the input resource will be used."))
cmdutil.AddDryRunFlag(cmd) cmdutil.AddDryRunFlag(cmd)
usage := "identifying the resource to autoscale." usage := "identifying the resource to autoscale."
cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage) cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, usage)
cmdutil.AddApplyAnnotationFlags(cmd) cmdutil.AddApplyAnnotationFlags(cmd)
return cmd return cmd
} }
func (o *AutoscaleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { func (o *AutoscaleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
var err error o.DryRun = cmdutil.GetFlagBool(cmd, "dry-run")
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
o.Builder = f.NewBuilder()
o.CanBeAutoscaled = f.CanBeAutoscaled
o.Mapper, o.Typer = f.Object()
o.ClientForMapping = f.ClientForMapping
o.BuilderArgs = args
o.RecordFlags.Complete(f.Command(cmd, false)) o.RecordFlags.Complete(f.Command(cmd, false))
var err error
o.Recorder, err = o.RecordFlags.ToRecorder() o.Recorder, err = o.RecordFlags.ToRecorder()
if err != nil { if err != nil {
return err return err
} }
// get the generator
o.GeneratorFunc = func(name string, mapping *meta.RESTMapping) (kubectl.StructuredGenerator, error) {
var generator kubectl.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case cmdutil.HorizontalPodAutoscalerV1GeneratorName:
generator = &kubectl.HorizontalPodAutoscalerGeneratorV1{
Name: name,
MinReplicas: cmdutil.GetFlagInt32(cmd, "min"),
MaxReplicas: cmdutil.GetFlagInt32(cmd, "max"),
CPUPercent: cmdutil.GetFlagInt32(cmd, "cpu-percent"),
ScaleRefName: name,
ScaleRefKind: mapping.GroupVersionKind.Kind,
ScaleRefApiVersion: mapping.GroupVersionKind.GroupVersion().String(),
}
default:
return nil, cmdutil.UsageErrorf(cmd, "Generator %s not supported. ", generatorName)
}
return generator, nil
}
o.Namespace, o.EnforceNamespace, err = f.DefaultNamespace()
if err != nil {
return err
}
o.ToPrinter = func(operation string) (printers.ResourcePrinterFunc, error) {
o.PrintFlags.NamePrintFlags.Operation = operation
if o.DryRun {
o.PrintFlags.Complete("%s (dry run)")
}
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return nil, err
}
return printer.PrintObj, nil
}
return nil
}
func (o *AutoscaleOptions) Validate(cmd *cobra.Command) error {
if err := validateFlags(cmd); err != nil {
return err
}
return nil return nil
} }
func (o *AutoscaleOptions) RunAutoscale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error { func (o *AutoscaleOptions) Run() error {
namespace, enforceNamespace, err := f.DefaultNamespace() r := o.Builder.
if err != nil {
return err
}
// validate flags
if err := validateFlags(cmd); err != nil {
return err
}
r := f.NewBuilder().
Internal(). Internal().
ContinueOnError(). ContinueOnError().
NamespaceParam(namespace).DefaultNamespace(). NamespaceParam(o.Namespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions). FilenameParam(o.EnforceNamespace, o.FilenameOptions).
ResourceTypeOrNameArgs(false, args...). ResourceTypeOrNameArgs(false, o.BuilderArgs...).
Flatten(). Flatten().
Do() Do()
err = r.Err() if err := r.Err(); err != nil {
if err != nil {
return err return err
} }
count := 0 count := 0
err = r.Visit(func(info *resource.Info, err error) error { err := r.Visit(func(info *resource.Info, err error) error {
if err != nil { if err != nil {
return err return err
} }
mapping := info.ResourceMapping() mapping := info.ResourceMapping()
if err := f.CanBeAutoscaled(mapping.GroupVersionKind.GroupKind()); err != nil { if err := o.CanBeAutoscaled(mapping.GroupVersionKind.GroupKind()); err != nil {
return err return err
} }
// get the generator generator, err := o.GeneratorFunc(info.Name, mapping)
var generator kubectl.StructuredGenerator if err != nil {
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName { return err
case cmdutil.HorizontalPodAutoscalerV1GeneratorName:
generator = &kubectl.HorizontalPodAutoscalerGeneratorV1{
Name: info.Name,
MinReplicas: cmdutil.GetFlagInt32(cmd, "min"),
MaxReplicas: cmdutil.GetFlagInt32(cmd, "max"),
CPUPercent: cmdutil.GetFlagInt32(cmd, "cpu-percent"),
ScaleRefName: info.Name,
ScaleRefKind: mapping.GroupVersionKind.Kind,
ScaleRefApiVersion: mapping.GroupVersionKind.GroupVersion().String(),
}
default:
return cmdutil.UsageErrorf(cmd, "Generator %s not supported. ", generatorName)
} }
// Generate new object // Generate new object
@ -169,11 +229,10 @@ func (o *AutoscaleOptions) RunAutoscale(f cmdutil.Factory, out io.Writer, cmd *c
return err return err
} }
mapper, typer := f.Object()
resourceMapper := &resource.Mapper{ resourceMapper := &resource.Mapper{
ObjectTyper: typer, ObjectTyper: o.Typer,
RESTMapper: mapper, RESTMapper: o.Mapper,
ClientMapper: resource.ClientMapperFunc(f.ClientForMapping), ClientMapper: resource.ClientMapperFunc(o.ClientForMapping),
Decoder: cmdutil.InternalVersionDecoder(), Decoder: cmdutil.InternalVersionDecoder(),
} }
hpa, err := resourceMapper.InfoForObject(object, nil) hpa, err := resourceMapper.InfoForObject(object, nil)
@ -183,26 +242,33 @@ func (o *AutoscaleOptions) RunAutoscale(f cmdutil.Factory, out io.Writer, cmd *c
if err := o.Recorder.Record(hpa.Object); err != nil { if err := o.Recorder.Record(hpa.Object); err != nil {
glog.V(4).Infof("error recording current command: %v", err) glog.V(4).Infof("error recording current command: %v", err)
} }
if cmdutil.GetDryRunFlag(cmd) { object = hpa.Object
return cmdutil.PrintObject(cmd, object, out)
if o.DryRun {
count++
printer, err := o.ToPrinter("created")
if err != nil {
return err
}
return printer.PrintObj(hpa.AsVersioned(), o.Out)
} }
if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), hpa.Object, cmdutil.InternalVersionJSONEncoder()); err != nil { if err := kubectl.CreateOrUpdateAnnotation(o.CreateAnnotation, hpa.Object, cmdutil.InternalVersionJSONEncoder()); err != nil {
return err return err
} }
object, err = resource.NewHelper(hpa.Client, hpa.Mapping).Create(namespace, false, object) _, err = resource.NewHelper(hpa.Client, hpa.Mapping).Create(o.Namespace, false, object)
if err != nil { if err != nil {
return err return err
} }
count++ count++
if len(cmdutil.GetFlagString(cmd, "output")) > 0 { printer, err := o.ToPrinter("autoscaled")
return cmdutil.PrintObject(cmd, object, out) if err != nil {
return err
} }
return printer.PrintObj(info.AsVersioned(), o.Out)
cmdutil.PrintSuccess(false, out, info.Object, cmdutil.GetDryRunFlag(cmd), "autoscaled")
return nil
}) })
if err != nil { if err != nil {
return err return err

View File

@ -21,16 +21,20 @@ import (
"io" "io"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/apis/certificates" "k8s.io/kubernetes/pkg/apis/certificates"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/printers"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func NewCmdCertificate(f cmdutil.Factory, out io.Writer) *cobra.Command { func NewCmdCertificate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "certificate SUBCOMMAND", Use: "certificate SUBCOMMAND",
DisableFlagsInUseLine: true, DisableFlagsInUseLine: true,
@ -41,33 +45,61 @@ func NewCmdCertificate(f cmdutil.Factory, out io.Writer) *cobra.Command {
}, },
} }
cmd.AddCommand(NewCmdCertificateApprove(f, out)) cmd.AddCommand(NewCmdCertificateApprove(f, ioStreams))
cmd.AddCommand(NewCmdCertificateDeny(f, out)) cmd.AddCommand(NewCmdCertificateDeny(f, ioStreams))
return cmd return cmd
} }
type CertificateOptions struct { type CertificateOptions struct {
resource.FilenameOptions resource.FilenameOptions
PrintFlags *printers.PrintFlags
PrintObj printers.ResourcePrinterFunc
csrNames []string csrNames []string
outputStyle string outputStyle string
clientSet internalclientset.Interface
builder *resource.Builder
genericclioptions.IOStreams
} }
func (options *CertificateOptions) Complete(cmd *cobra.Command, args []string) error { func (o *CertificateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
options.csrNames = args o.csrNames = args
options.outputStyle = cmdutil.GetFlagString(cmd, "output") o.outputStyle = cmdutil.GetFlagString(cmd, "output")
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return err
}
o.PrintObj = func(obj runtime.Object, out io.Writer) error {
return printer.PrintObj(obj, out)
}
o.builder = f.NewBuilder()
o.clientSet, err = f.ClientSet()
if err != nil {
return err
}
return nil return nil
} }
func (options *CertificateOptions) Validate() error { func (o *CertificateOptions) Validate() error {
if len(options.csrNames) < 1 && cmdutil.IsFilenameSliceEmpty(options.Filenames) { if len(o.csrNames) < 1 && cmdutil.IsFilenameSliceEmpty(o.Filenames) {
return fmt.Errorf("one or more CSRs must be specified as <name> or -f <filename>") return fmt.Errorf("one or more CSRs must be specified as <name> or -f <filename>")
} }
return nil return nil
} }
func NewCmdCertificateApprove(f cmdutil.Factory, out io.Writer) *cobra.Command { func NewCmdCertificateApprove(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := CertificateOptions{} options := CertificateOptions{
PrintFlags: printers.NewPrintFlags("approved"),
IOStreams: ioStreams,
}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "approve (-f FILENAME | NAME)", Use: "approve (-f FILENAME | NAME)",
DisableFlagsInUseLine: true, DisableFlagsInUseLine: true,
@ -85,9 +117,9 @@ func NewCmdCertificateApprove(f cmdutil.Factory, out io.Writer) *cobra.Command {
signed certificate can do. signed certificate can do.
`), `),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.Complete(cmd, args)) cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Validate()) cmdutil.CheckErr(options.Validate())
cmdutil.CheckErr(options.RunCertificateApprove(f, out, cmdutil.GetFlagBool(cmd, "force"))) cmdutil.CheckErr(options.RunCertificateApprove(cmdutil.GetFlagBool(cmd, "force")))
}, },
} }
cmd.Flags().Bool("force", false, "Update the CSR even if it is already approved.") cmd.Flags().Bool("force", false, "Update the CSR even if it is already approved.")
@ -97,8 +129,8 @@ func NewCmdCertificateApprove(f cmdutil.Factory, out io.Writer) *cobra.Command {
return cmd return cmd
} }
func (options *CertificateOptions) RunCertificateApprove(f cmdutil.Factory, out io.Writer, force bool) error { func (o *CertificateOptions) RunCertificateApprove(force bool) error {
return options.modifyCertificateCondition(f, out, force, func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, bool, string) { return o.modifyCertificateCondition(o.builder, o.clientSet, force, func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, bool) {
var alreadyApproved bool var alreadyApproved bool
for _, c := range csr.Status.Conditions { for _, c := range csr.Status.Conditions {
if c.Type == certificates.CertificateApproved { if c.Type == certificates.CertificateApproved {
@ -106,7 +138,7 @@ func (options *CertificateOptions) RunCertificateApprove(f cmdutil.Factory, out
} }
} }
if alreadyApproved { if alreadyApproved {
return csr, true, "approved" return csr, true
} }
csr.Status.Conditions = append(csr.Status.Conditions, certificates.CertificateSigningRequestCondition{ csr.Status.Conditions = append(csr.Status.Conditions, certificates.CertificateSigningRequestCondition{
Type: certificates.CertificateApproved, Type: certificates.CertificateApproved,
@ -114,12 +146,15 @@ func (options *CertificateOptions) RunCertificateApprove(f cmdutil.Factory, out
Message: "This CSR was approved by kubectl certificate approve.", Message: "This CSR was approved by kubectl certificate approve.",
LastUpdateTime: metav1.Now(), LastUpdateTime: metav1.Now(),
}) })
return csr, false, "approved" return csr, false
}) })
} }
func NewCmdCertificateDeny(f cmdutil.Factory, out io.Writer) *cobra.Command { func NewCmdCertificateDeny(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := CertificateOptions{} options := CertificateOptions{
PrintFlags: printers.NewPrintFlags("denied"),
IOStreams: ioStreams,
}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "deny (-f FILENAME | NAME)", Use: "deny (-f FILENAME | NAME)",
DisableFlagsInUseLine: true, DisableFlagsInUseLine: true,
@ -132,9 +167,9 @@ func NewCmdCertificateDeny(f cmdutil.Factory, out io.Writer) *cobra.Command {
not to issue a certificate to the requestor. not to issue a certificate to the requestor.
`), `),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.Complete(cmd, args)) cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Validate()) cmdutil.CheckErr(options.Validate())
cmdutil.CheckErr(options.RunCertificateDeny(f, out, cmdutil.GetFlagBool(cmd, "force"))) cmdutil.CheckErr(options.RunCertificateDeny(cmdutil.GetFlagBool(cmd, "force")))
}, },
} }
cmd.Flags().Bool("force", false, "Update the CSR even if it is already denied.") cmd.Flags().Bool("force", false, "Update the CSR even if it is already denied.")
@ -144,8 +179,8 @@ func NewCmdCertificateDeny(f cmdutil.Factory, out io.Writer) *cobra.Command {
return cmd return cmd
} }
func (options *CertificateOptions) RunCertificateDeny(f cmdutil.Factory, out io.Writer, force bool) error { func (o *CertificateOptions) RunCertificateDeny(force bool) error {
return options.modifyCertificateCondition(f, out, force, func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, bool, string) { return o.modifyCertificateCondition(o.builder, o.clientSet, force, func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, bool) {
var alreadyDenied bool var alreadyDenied bool
for _, c := range csr.Status.Conditions { for _, c := range csr.Status.Conditions {
if c.Type == certificates.CertificateDenied { if c.Type == certificates.CertificateDenied {
@ -153,7 +188,7 @@ func (options *CertificateOptions) RunCertificateDeny(f cmdutil.Factory, out io.
} }
} }
if alreadyDenied { if alreadyDenied {
return csr, true, "denied" return csr, true
} }
csr.Status.Conditions = append(csr.Status.Conditions, certificates.CertificateSigningRequestCondition{ csr.Status.Conditions = append(csr.Status.Conditions, certificates.CertificateSigningRequestCondition{
Type: certificates.CertificateDenied, Type: certificates.CertificateDenied,
@ -161,17 +196,13 @@ func (options *CertificateOptions) RunCertificateDeny(f cmdutil.Factory, out io.
Message: "This CSR was approved by kubectl certificate deny.", Message: "This CSR was approved by kubectl certificate deny.",
LastUpdateTime: metav1.Now(), LastUpdateTime: metav1.Now(),
}) })
return csr, false, "denied" return csr, false
}) })
} }
func (options *CertificateOptions) modifyCertificateCondition(f cmdutil.Factory, out io.Writer, force bool, modify func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, bool, string)) error { func (options *CertificateOptions) modifyCertificateCondition(builder *resource.Builder, clientSet internalclientset.Interface, force bool, modify func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, bool)) error {
var found int var found int
c, err := f.ClientSet() r := builder.
if err != nil {
return err
}
r := f.NewBuilder().
Internal(). Internal().
ContinueOnError(). ContinueOnError().
FilenameParam(false, &options.FilenameOptions). FilenameParam(false, &options.FilenameOptions).
@ -180,14 +211,14 @@ func (options *CertificateOptions) modifyCertificateCondition(f cmdutil.Factory,
Flatten(). Flatten().
Latest(). Latest().
Do() Do()
err = r.Visit(func(info *resource.Info, err error) error { err := r.Visit(func(info *resource.Info, err error) error {
if err != nil { if err != nil {
return err return err
} }
csr := info.Object.(*certificates.CertificateSigningRequest) csr := info.Object.(*certificates.CertificateSigningRequest)
csr, hasCondition, verb := modify(csr) csr, hasCondition := modify(csr)
if !hasCondition || force { if !hasCondition || force {
csr, err = c.Certificates(). csr, err = clientSet.Certificates().
CertificateSigningRequests(). CertificateSigningRequests().
UpdateApproval(csr) UpdateApproval(csr)
if err != nil { if err != nil {
@ -195,11 +226,11 @@ func (options *CertificateOptions) modifyCertificateCondition(f cmdutil.Factory,
} }
} }
found++ found++
cmdutil.PrintSuccess(options.outputStyle == "name", out, info.Object, false, verb)
return nil return options.PrintObj(info.AsVersioned(), options.Out)
}) })
if found == 0 { if found == 0 {
fmt.Fprintf(out, "No resources found\n") fmt.Fprintf(options.Out, "No resources found\n")
} }
return err return err
} }

View File

@ -23,9 +23,11 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilnet "k8s.io/apimachinery/pkg/util/net" utilnet "k8s.io/apimachinery/pkg/util/net"
restclient "k8s.io/client-go/rest"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
@ -43,41 +45,62 @@ var (
kubectl cluster-info`)) kubectl cluster-info`))
) )
func NewCmdClusterInfo(f cmdutil.Factory, out io.Writer) *cobra.Command { type ClusterInfoOptions struct {
genericclioptions.IOStreams
Namespace string
Builder *resource.Builder
Client *restclient.Config
}
func NewCmdClusterInfo(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := &ClusterInfoOptions{
IOStreams: ioStreams,
}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "cluster-info", Use: "cluster-info",
Short: i18n.T("Display cluster info"), Short: i18n.T("Display cluster info"),
Long: longDescr, Long: longDescr,
Example: clusterinfoExample, Example: clusterinfoExample,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
err := RunClusterInfo(f, out, cmd) cmdutil.CheckErr(o.Complete(f, cmd))
cmdutil.CheckErr(err) cmdutil.CheckErr(o.Run())
}, },
} }
cmd.AddCommand(NewCmdClusterInfoDump(f, out)) cmd.AddCommand(NewCmdClusterInfoDump(f, ioStreams))
return cmd return cmd
} }
func RunClusterInfo(f cmdutil.Factory, out io.Writer, cmd *cobra.Command) error { func (o *ClusterInfoOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
client, err := f.ClientConfig() var err error
o.Client, err = f.ClientConfig()
if err != nil { if err != nil {
return err return err
} }
printService(out, "Kubernetes master", client.Host)
cmdNamespace := cmdutil.GetFlagString(cmd, "namespace") cmdNamespace := cmdutil.GetFlagString(cmd, "namespace")
if cmdNamespace == "" { if cmdNamespace == "" {
cmdNamespace = metav1.NamespaceSystem cmdNamespace = metav1.NamespaceSystem
} }
o.Namespace = cmdNamespace
o.Builder = f.NewBuilder()
return nil
}
func (o *ClusterInfoOptions) Run() error {
printService(o.Out, "Kubernetes master", o.Client.Host)
// TODO use generalized labels once they are implemented (#341) // TODO use generalized labels once they are implemented (#341)
b := f.NewBuilder(). b := o.Builder.
Internal(). Internal().
NamespaceParam(cmdNamespace).DefaultNamespace(). NamespaceParam(o.Namespace).DefaultNamespace().
LabelSelectorParam("kubernetes.io/cluster-service=true"). LabelSelectorParam("kubernetes.io/cluster-service=true").
ResourceTypeOrNameArgs(false, []string{"services"}...). ResourceTypeOrNameArgs(false, []string{"services"}...).
Latest() Latest()
err = b.Do().Visit(func(r *resource.Info, err error) error { err := b.Do().Visit(func(r *resource.Info, err error) error {
if err != nil { if err != nil {
return err return err
} }
@ -109,10 +132,10 @@ func RunClusterInfo(f cmdutil.Factory, out io.Writer, cmd *cobra.Command) error
name = utilnet.JoinSchemeNamePort(scheme, service.ObjectMeta.Name, port.Name) name = utilnet.JoinSchemeNamePort(scheme, service.ObjectMeta.Name, port.Name)
} }
if len(client.GroupVersion.Group) == 0 { if len(o.Client.GroupVersion.Group) == 0 {
link = client.Host + "/api/" + client.GroupVersion.Version + "/namespaces/" + service.ObjectMeta.Namespace + "/services/" + name + "/proxy" link = o.Client.Host + "/api/" + o.Client.GroupVersion.Version + "/namespaces/" + service.ObjectMeta.Namespace + "/services/" + name + "/proxy"
} else { } else {
link = client.Host + "/api/" + client.GroupVersion.Group + "/" + client.GroupVersion.Version + "/namespaces/" + service.ObjectMeta.Namespace + "/services/" + name + "/proxy" link = o.Client.Host + "/api/" + o.Client.GroupVersion.Group + "/" + o.Client.GroupVersion.Version + "/namespaces/" + service.ObjectMeta.Namespace + "/services/" + name + "/proxy"
} }
} }
@ -120,11 +143,11 @@ func RunClusterInfo(f cmdutil.Factory, out io.Writer, cmd *cobra.Command) error
if len(name) == 0 { if len(name) == 0 {
name = service.ObjectMeta.Name name = service.ObjectMeta.Name
} }
printService(out, name, link) printService(o.Out, name, link)
} }
return nil return nil
}) })
out.Write([]byte("\nTo further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.\n")) o.Out.Write([]byte("\nTo further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.\n"))
return err return err
// TODO consider printing more information about cluster // TODO consider printing more information about cluster

View File

@ -28,19 +28,34 @@ import (
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/printers" "k8s.io/kubernetes/pkg/printers"
) )
type ClusterInfoDumpOptions struct {
PrintFlags *printers.PrintFlags
PrintObj printers.ResourcePrinterFunc
genericclioptions.IOStreams
}
// NewCmdCreateSecret groups subcommands to create various types of secrets // NewCmdCreateSecret groups subcommands to create various types of secrets
func NewCmdClusterInfoDump(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command { func NewCmdClusterInfoDump(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := &ClusterInfoDumpOptions{
PrintFlags: printers.NewPrintFlags(""),
IOStreams: ioStreams,
}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "dump", Use: "dump",
Short: i18n.T("Dump lots of relevant info for debugging and diagnosis"), Short: i18n.T("Dump lots of relevant info for debugging and diagnosis"),
Long: dumpLong, Long: dumpLong,
Example: dumpExample, Example: dumpExample,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(dumpClusterInfo(f, cmd, cmdOut)) cmdutil.CheckErr(o.Complete())
cmdutil.CheckErr(o.Run(f, cmd))
}, },
} }
cmd.Flags().String("output-directory", "", i18n.T("Where to output the files. If empty or '-' uses stdout, otherwise creates a directory hierarchy in that directory")) cmd.Flags().String("output-directory", "", i18n.T("Where to output the files. If empty or '-' uses stdout, otherwise creates a directory hierarchy in that directory"))
@ -88,7 +103,20 @@ func setupOutputWriter(cmd *cobra.Command, defaultWriter io.Writer, filename str
return file return file
} }
func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error { func (o *ClusterInfoDumpOptions) Complete() error {
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return err
}
jsonOutputFmt := "json"
o.PrintFlags.OutputFormat = &jsonOutputFmt
o.PrintObj = printer.PrintObj
return nil
}
func (o *ClusterInfoDumpOptions) Run(f cmdutil.Factory, cmd *cobra.Command) error {
timeout, err := cmdutil.GetPodRunningTimeoutFlag(cmd) timeout, err := cmdutil.GetPodRunningTimeoutFlag(cmd)
if err != nil { if err != nil {
return cmdutil.UsageErrorf(cmd, err.Error()) return cmdutil.UsageErrorf(cmd, err.Error())
@ -99,14 +127,12 @@ func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error
return err return err
} }
printer := &printers.JSONPrinter{}
nodes, err := clientset.Core().Nodes().List(metav1.ListOptions{}) nodes, err := clientset.Core().Nodes().List(metav1.ListOptions{})
if err != nil { if err != nil {
return err return err
} }
if err := printer.PrintObj(nodes, setupOutputWriter(cmd, out, "nodes.json")); err != nil { if err := o.PrintObj(nodes, setupOutputWriter(cmd, o.Out, "nodes.json")); err != nil {
return err return err
} }
@ -139,7 +165,7 @@ func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error
if err != nil { if err != nil {
return err return err
} }
if err := printer.PrintObj(events, setupOutputWriter(cmd, out, path.Join(namespace, "events.json"))); err != nil { if err := o.PrintObj(events, setupOutputWriter(cmd, o.Out, path.Join(namespace, "events.json"))); err != nil {
return err return err
} }
@ -147,7 +173,7 @@ func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error
if err != nil { if err != nil {
return err return err
} }
if err := printer.PrintObj(rcs, setupOutputWriter(cmd, out, path.Join(namespace, "replication-controllers.json"))); err != nil { if err := o.PrintObj(rcs, setupOutputWriter(cmd, o.Out, path.Join(namespace, "replication-controllers.json"))); err != nil {
return err return err
} }
@ -155,7 +181,7 @@ func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error
if err != nil { if err != nil {
return err return err
} }
if err := printer.PrintObj(svcs, setupOutputWriter(cmd, out, path.Join(namespace, "services.json"))); err != nil { if err := o.PrintObj(svcs, setupOutputWriter(cmd, o.Out, path.Join(namespace, "services.json"))); err != nil {
return err return err
} }
@ -163,7 +189,7 @@ func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error
if err != nil { if err != nil {
return err return err
} }
if err := printer.PrintObj(sets, setupOutputWriter(cmd, out, path.Join(namespace, "daemonsets.json"))); err != nil { if err := o.PrintObj(sets, setupOutputWriter(cmd, o.Out, path.Join(namespace, "daemonsets.json"))); err != nil {
return err return err
} }
@ -171,7 +197,7 @@ func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error
if err != nil { if err != nil {
return err return err
} }
if err := printer.PrintObj(deps, setupOutputWriter(cmd, out, path.Join(namespace, "deployments.json"))); err != nil { if err := o.PrintObj(deps, setupOutputWriter(cmd, o.Out, path.Join(namespace, "deployments.json"))); err != nil {
return err return err
} }
@ -179,7 +205,7 @@ func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error
if err != nil { if err != nil {
return err return err
} }
if err := printer.PrintObj(rps, setupOutputWriter(cmd, out, path.Join(namespace, "replicasets.json"))); err != nil { if err := o.PrintObj(rps, setupOutputWriter(cmd, o.Out, path.Join(namespace, "replicasets.json"))); err != nil {
return err return err
} }
@ -188,7 +214,7 @@ func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error
return err return err
} }
if err := printer.PrintObj(pods, setupOutputWriter(cmd, out, path.Join(namespace, "pods.json"))); err != nil { if err := o.PrintObj(pods, setupOutputWriter(cmd, o.Out, path.Join(namespace, "pods.json"))); err != nil {
return err return err
} }
@ -215,7 +241,7 @@ func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error
for ix := range pods.Items { for ix := range pods.Items {
pod := &pods.Items[ix] pod := &pods.Items[ix]
containers := pod.Spec.Containers containers := pod.Spec.Containers
writer := setupOutputWriter(cmd, out, path.Join(namespace, pod.Name, "logs.txt")) writer := setupOutputWriter(cmd, o.Out, path.Join(namespace, pod.Name, "logs.txt"))
for i := range containers { for i := range containers {
printContainer(writer, containers[i], pod) printContainer(writer, containers[i], pod)
@ -227,7 +253,7 @@ func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, out io.Writer) error
dir = "standard output" dir = "standard output"
} }
if dir != "-" { if dir != "-" {
fmt.Fprintf(out, "Cluster info dumped to %s\n", dir) fmt.Fprintf(o.Out, "Cluster info dumped to %s\n", dir)
} }
return nil return nil
} }

View File

@ -17,27 +17,27 @@ limitations under the License.
package cmd package cmd
import ( import (
"bytes"
"io/ioutil" "io/ioutil"
"os" "os"
"path" "path"
"testing" "testing"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
) )
func TestSetupOutputWriterNoOp(t *testing.T) { func TestSetupOutputWriterNoOp(t *testing.T) {
tests := []string{"", "-"} tests := []string{"", "-"}
for _, test := range tests { for _, test := range tests {
out := &bytes.Buffer{} _, _, buf, _ := genericclioptions.NewTestIOStreams()
f := cmdtesting.NewTestFactory() f := cmdtesting.NewTestFactory()
defer f.Cleanup() defer f.Cleanup()
cmd := NewCmdClusterInfoDump(f, os.Stdout) cmd := NewCmdClusterInfoDump(f, genericclioptions.NewTestIOStreamsDiscard())
cmd.Flag("output-directory").Value.Set(test) cmd.Flag("output-directory").Value.Set(test)
writer := setupOutputWriter(cmd, out, "/some/file/that/should/be/ignored") writer := setupOutputWriter(cmd, buf, "/some/file/that/should/be/ignored")
if writer != out { if writer != buf {
t.Errorf("expected: %v, saw: %v", out, writer) t.Errorf("expected: %v, saw: %v", buf, writer)
} }
} }
} }
@ -51,15 +51,15 @@ func TestSetupOutputWriterFile(t *testing.T) {
fullPath := path.Join(dir, file) fullPath := path.Join(dir, file)
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
out := &bytes.Buffer{} _, _, buf, _ := genericclioptions.NewTestIOStreams()
f := cmdtesting.NewTestFactory() f := cmdtesting.NewTestFactory()
defer f.Cleanup() defer f.Cleanup()
cmd := NewCmdClusterInfoDump(f, os.Stdout) cmd := NewCmdClusterInfoDump(f, genericclioptions.NewTestIOStreamsDiscard())
cmd.Flag("output-directory").Value.Set(dir) cmd.Flag("output-directory").Value.Set(dir)
writer := setupOutputWriter(cmd, out, file) writer := setupOutputWriter(cmd, buf, file)
if writer == out { if writer == buf {
t.Errorf("expected: %v, saw: %v", out, writer) t.Errorf("expected: %v, saw: %v", buf, writer)
} }
output := "some data here" output := "some data here"
writer.Write([]byte(output)) writer.Write([]byte(output))

View File

@ -276,18 +276,18 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob
rollout.NewCmdRollout(f, out, err), rollout.NewCmdRollout(f, out, err),
NewCmdRollingUpdate(f, out), NewCmdRollingUpdate(f, out),
NewCmdScale(f, out, err), NewCmdScale(f, out, err),
NewCmdAutoscale(f, out), NewCmdAutoscale(f, ioStreams),
}, },
}, },
{ {
Message: "Cluster Management Commands:", Message: "Cluster Management Commands:",
Commands: []*cobra.Command{ Commands: []*cobra.Command{
NewCmdCertificate(f, out), NewCmdCertificate(f, ioStreams),
NewCmdClusterInfo(f, out), NewCmdClusterInfo(f, ioStreams),
NewCmdTop(f, out, err), NewCmdTop(f, out, err),
NewCmdCordon(f, out), NewCmdCordon(f, ioStreams),
NewCmdUncordon(f, out), NewCmdUncordon(f, ioStreams),
NewCmdDrain(f, out, err), NewCmdDrain(f, ioStreams),
NewCmdTaint(f, out), NewCmdTaint(f, out),
}, },
}, },
@ -300,7 +300,7 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob
NewCmdExec(f, in, out, err), NewCmdExec(f, in, out, err),
NewCmdPortForward(f, out, err), NewCmdPortForward(f, out, err),
NewCmdProxy(f, out), NewCmdProxy(f, out),
NewCmdCp(f, out, err), NewCmdCp(f, ioStreams),
auth.NewCmdAuth(f, out, err), auth.NewCmdAuth(f, out, err),
}, },
}, },
@ -310,13 +310,13 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob
NewCmdApply("kubectl", f, ioStreams), NewCmdApply("kubectl", f, ioStreams),
NewCmdPatch(f, out), NewCmdPatch(f, out),
NewCmdReplace(f, out, err), NewCmdReplace(f, out, err),
NewCmdConvert(f, out), NewCmdConvert(f, ioStreams),
}, },
}, },
{ {
Message: "Settings Commands:", Message: "Settings Commands:",
Commands: []*cobra.Command{ Commands: []*cobra.Command{
NewCmdLabel(f, out, err), NewCmdLabel(f, ioStreams),
NewCmdAnnotate(f, ioStreams), NewCmdAnnotate(f, ioStreams),
NewCmdCompletion(out, ""), NewCmdCompletion(out, ""),
}, },

View File

@ -18,7 +18,6 @@ package cmd
import ( import (
"fmt" "fmt"
"io"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -27,6 +26,7 @@ import (
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/printers" "k8s.io/kubernetes/pkg/printers"
@ -61,8 +61,8 @@ var (
// NewCmdConvert creates a command object for the generic "convert" action, which // NewCmdConvert creates a command object for the generic "convert" action, which
// translates the config file into a given version. // translates the config file into a given version.
func NewCmdConvert(f cmdutil.Factory, out io.Writer) *cobra.Command { func NewCmdConvert(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := NewConvertOptions() options := NewConvertOptions(ioStreams)
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "convert -f FILENAME", Use: "convert -f FILENAME",
@ -71,18 +71,17 @@ func NewCmdConvert(f cmdutil.Factory, out io.Writer) *cobra.Command {
Long: convert_long, Long: convert_long,
Example: convert_example, Example: convert_example,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
err := options.Complete(f, out, cmd) cmdutil.CheckErr(options.Complete(f, cmd))
cmdutil.CheckErr(err) cmdutil.CheckErr(options.RunConvert())
err = options.RunConvert()
cmdutil.CheckErr(err)
}, },
} }
options.PrintFlags.AddFlags(cmd)
usage := "to need to get converted." usage := "to need to get converted."
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage) cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
cmd.MarkFlagRequired("filename") cmd.MarkFlagRequired("filename")
cmdutil.AddValidateFlags(cmd) cmdutil.AddValidateFlags(cmd)
cmdutil.AddNonDeprecatedPrinterFlags(cmd)
cmd.Flags().BoolVar(&options.local, "local", options.local, "If true, convert will NOT try to contact api-server but run locally.") cmd.Flags().BoolVar(&options.local, "local", options.local, "If true, convert will NOT try to contact api-server but run locally.")
cmd.Flags().String("output-version", "", i18n.T("Output the formatted object with the given group version (for ex: 'extensions/v1beta1').)")) cmd.Flags().String("output-version", "", i18n.T("Output the formatted object with the given group version (for ex: 'extensions/v1beta1').)"))
return cmd return cmd
@ -90,19 +89,24 @@ func NewCmdConvert(f cmdutil.Factory, out io.Writer) *cobra.Command {
// ConvertOptions have the data required to perform the convert operation // ConvertOptions have the data required to perform the convert operation
type ConvertOptions struct { type ConvertOptions struct {
PrintFlags *printers.PrintFlags
PrintObj printers.ResourcePrinterFunc
resource.FilenameOptions resource.FilenameOptions
builder *resource.Builder builder *resource.Builder
local bool local bool
out io.Writer genericclioptions.IOStreams
printer printers.ResourcePrinter
specifiedOutputVersion schema.GroupVersion specifiedOutputVersion schema.GroupVersion
} }
func NewConvertOptions() *ConvertOptions { func NewConvertOptions(ioStreams genericclioptions.IOStreams) *ConvertOptions {
return &ConvertOptions{local: true} return &ConvertOptions{
PrintFlags: printers.NewPrintFlags("converted").WithDefaultOutput("yaml"),
local: true,
IOStreams: ioStreams,
}
} }
// outputVersion returns the preferred output version for generic content (JSON, YAML, or templates) // outputVersion returns the preferred output version for generic content (JSON, YAML, or templates)
@ -117,7 +121,7 @@ func outputVersion(cmd *cobra.Command) (schema.GroupVersion, error) {
} }
// Complete collects information required to run Convert command from command line. // Complete collects information required to run Convert command from command line.
func (o *ConvertOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.Command) (err error) { func (o *ConvertOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) (err error) {
o.specifiedOutputVersion, err = outputVersion(cmd) o.specifiedOutputVersion, err = outputVersion(cmd)
if err != nil { if err != nil {
return err return err
@ -145,20 +149,12 @@ func (o *ConvertOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.C
Flatten() Flatten()
// build the printer // build the printer
o.out = out printer, err := o.PrintFlags.ToPrinter()
outputFormat := cmdutil.GetFlagString(cmd, "output") if err != nil {
templateFile := cmdutil.GetFlagString(cmd, "template")
if len(outputFormat) == 0 {
if len(templateFile) == 0 {
outputFormat = "yaml"
} else {
outputFormat = "template"
}
// TODO: once printing is abstracted, this should be handled at flag declaration time
cmd.Flags().Set("output", outputFormat)
}
o.printer, err = cmdutil.PrinterForOptions(cmdutil.ExtractCmdPrintOptions(cmd, false))
return err return err
}
o.PrintObj = printer.PrintObj
return nil
} }
// RunConvert implements the generic Convert command // RunConvert implements the generic Convert command
@ -189,10 +185,10 @@ func (o *ConvertOptions) RunConvert() error {
if err != nil { if err != nil {
return err return err
} }
return o.printer.PrintObj(obj, o.out) return o.PrintObj(obj, o.Out)
} }
return o.printer.PrintObj(objects, o.out) return o.PrintObj(objects, o.Out)
} }
// objectListToVersionedObject receives a list of api objects and a group version // objectListToVersionedObject receives a list of api objects and a group version

View File

@ -20,10 +20,12 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"net/http" "net/http"
"strings"
"testing" "testing"
"k8s.io/client-go/rest/fake" "k8s.io/client-go/rest/fake"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
) )
type testcase struct { type testcase struct {
@ -34,7 +36,6 @@ type testcase struct {
} }
type checkField struct { type checkField struct {
template string
expected string expected string
} }
@ -46,8 +47,7 @@ func TestConvertObject(t *testing.T) {
outputVersion: "extensions/v1beta1", outputVersion: "extensions/v1beta1",
fields: []checkField{ fields: []checkField{
{ {
template: "{{.apiVersion}}", expected: "apiVersion: extensions/v1beta1",
expected: "extensions/v1beta1",
}, },
}, },
}, },
@ -57,8 +57,7 @@ func TestConvertObject(t *testing.T) {
outputVersion: "apps/v1beta2", outputVersion: "apps/v1beta2",
fields: []checkField{ fields: []checkField{
{ {
template: "{{.apiVersion}}", expected: "apiVersion: apps/v1beta2",
expected: "apps/v1beta2",
}, },
}, },
}, },
@ -68,16 +67,13 @@ func TestConvertObject(t *testing.T) {
outputVersion: "autoscaling/v2beta1", outputVersion: "autoscaling/v2beta1",
fields: []checkField{ fields: []checkField{
{ {
template: "{{.apiVersion}}", expected: "apiVersion: autoscaling/v2beta1",
expected: "autoscaling/v2beta1",
}, },
{ {
template: "{{(index .spec.metrics 0).resource.name}}", expected: "name: cpu",
expected: "cpu",
}, },
{ {
template: "{{(index .spec.metrics 0).resource.targetAverageUtilization}}", expected: "targetAverageUtilization: 50",
expected: "50",
}, },
}, },
}, },
@ -87,12 +83,10 @@ func TestConvertObject(t *testing.T) {
outputVersion: "autoscaling/v1", outputVersion: "autoscaling/v1",
fields: []checkField{ fields: []checkField{
{ {
template: "{{.apiVersion}}", expected: "apiVersion: autoscaling/v1",
expected: "autoscaling/v1",
}, },
{ {
template: "{{.spec.targetCPUUtilizationPercentage}}", expected: "targetCPUUtilizationPercentage: 50",
expected: "50",
}, },
}, },
}, },
@ -113,13 +107,13 @@ func TestConvertObject(t *testing.T) {
tf.Namespace = "test" tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
cmd := NewCmdConvert(tf, buf) cmd := NewCmdConvert(tf, genericclioptions.IOStreams{Out: buf, ErrOut: buf})
cmd.Flags().Set("filename", tc.file) cmd.Flags().Set("filename", tc.file)
cmd.Flags().Set("output-version", tc.outputVersion) cmd.Flags().Set("output-version", tc.outputVersion)
cmd.Flags().Set("local", "true") cmd.Flags().Set("local", "true")
cmd.Flags().Set("output", "go-template="+field.template) cmd.Flags().Set("output", "yaml")
cmd.Run(cmd, []string{}) cmd.Run(cmd, []string{})
if buf.String() != field.expected { if !strings.Contains(buf.String(), field.expected) {
t.Errorf("unexpected output when converting %s to %q, expected: %q, but got %q", tc.file, tc.outputVersion, field.expected, buf.String()) t.Errorf("unexpected output when converting %s to %q, expected: %q, but got %q", tc.file, tc.outputVersion, field.expected, buf.String())
} }
}) })

View File

@ -28,8 +28,11 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
restclient "k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
"github.com/renstrom/dedent" "github.com/renstrom/dedent"
@ -61,8 +64,26 @@ var (
/file/path for a local file`) /file/path for a local file`)
) )
type CopyOptions struct {
Container string
Namespace string
ClientConfig *restclient.Config
Clientset internalclientset.Interface
genericclioptions.IOStreams
}
func NewCopyOptions(ioStreams genericclioptions.IOStreams) *CopyOptions {
return &CopyOptions{
IOStreams: ioStreams,
}
}
// NewCmdCp creates a new Copy command. // NewCmdCp creates a new Copy command.
func NewCmdCp(f cmdutil.Factory, cmdOut, cmdErr io.Writer) *cobra.Command { func NewCmdCp(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := NewCopyOptions(ioStreams)
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "cp <file-spec-src> <file-spec-dest>", Use: "cp <file-spec-src> <file-spec-dest>",
DisableFlagsInUseLine: true, DisableFlagsInUseLine: true,
@ -70,7 +91,8 @@ func NewCmdCp(f cmdutil.Factory, cmdOut, cmdErr io.Writer) *cobra.Command {
Long: "Copy files and directories to and from containers.", Long: "Copy files and directories to and from containers.",
Example: cpExample, Example: cpExample,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(runCopy(f, cmd, cmdOut, cmdErr, args)) cmdutil.CheckErr(o.Complete(f, cmd))
cmdutil.CheckErr(o.Run(args))
}, },
} }
cmd.Flags().StringP("container", "c", "", "Container name. If omitted, the first container in the pod will be chosen") cmd.Flags().StringP("container", "c", "", "Container name. If omitted, the first container in the pod will be chosen")
@ -119,10 +141,35 @@ func extractFileSpec(arg string) (fileSpec, error) {
return fileSpec{}, errFileSpecDoesntMatchFormat return fileSpec{}, errFileSpecDoesntMatchFormat
} }
func runCopy(f cmdutil.Factory, cmd *cobra.Command, out, cmderr io.Writer, args []string) error { func (o *CopyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
o.Container = cmdutil.GetFlagString(cmd, "container")
var err error
o.Namespace, _, err = f.DefaultNamespace()
if err != nil {
return err
}
o.Clientset, err = f.ClientSet()
if err != nil {
return err
}
o.ClientConfig, err = f.ClientConfig()
if err != nil {
return err
}
return nil
}
func (o *CopyOptions) Validate(cmd *cobra.Command, args []string) error {
if len(args) != 2 { if len(args) != 2 {
return cmdutil.UsageErrorf(cmd, cpUsageStr) return cmdutil.UsageErrorf(cmd, cpUsageStr)
} }
return nil
}
func (o *CopyOptions) Run(args []string) error {
srcSpec, err := extractFileSpec(args[0]) srcSpec, err := extractFileSpec(args[0])
if err != nil { if err != nil {
return err return err
@ -132,19 +179,19 @@ func runCopy(f cmdutil.Factory, cmd *cobra.Command, out, cmderr io.Writer, args
return err return err
} }
if len(srcSpec.PodName) != 0 { if len(srcSpec.PodName) != 0 {
return copyFromPod(f, cmd, cmderr, srcSpec, destSpec) return o.copyFromPod(srcSpec, destSpec)
} }
if len(destSpec.PodName) != 0 { if len(destSpec.PodName) != 0 {
return copyToPod(f, cmd, out, cmderr, srcSpec, destSpec) return o.copyToPod(srcSpec, destSpec)
} }
return cmdutil.UsageErrorf(cmd, "One of src or dest must be a remote file specification") return fmt.Errorf("One of src or dest must be a remote file specification")
} }
// checkDestinationIsDir receives a destination fileSpec and // checkDestinationIsDir receives a destination fileSpec and
// determines if the provided destination path exists on the // determines if the provided destination path exists on the
// pod. If the destination path does not exist or is _not_ a // pod. If the destination path does not exist or is _not_ a
// directory, an error is returned with the exit code received. // directory, an error is returned with the exit code received.
func checkDestinationIsDir(dest fileSpec, f cmdutil.Factory, cmd *cobra.Command) error { func (o *CopyOptions) checkDestinationIsDir(dest fileSpec) error {
options := &ExecOptions{ options := &ExecOptions{
StreamOptions: StreamOptions{ StreamOptions: StreamOptions{
Out: bytes.NewBuffer([]byte{}), Out: bytes.NewBuffer([]byte{}),
@ -158,10 +205,10 @@ func checkDestinationIsDir(dest fileSpec, f cmdutil.Factory, cmd *cobra.Command)
Executor: &DefaultRemoteExecutor{}, Executor: &DefaultRemoteExecutor{},
} }
return execute(f, cmd, options) return o.execute(options)
} }
func copyToPod(f cmdutil.Factory, cmd *cobra.Command, stdout, stderr io.Writer, src, dest fileSpec) error { func (o *CopyOptions) copyToPod(src, dest fileSpec) error {
if len(src.File) == 0 || len(dest.File) == 0 { if len(src.File) == 0 || len(dest.File) == 0 {
return errFileCannotBeEmpty return errFileCannotBeEmpty
} }
@ -172,7 +219,7 @@ func copyToPod(f cmdutil.Factory, cmd *cobra.Command, stdout, stderr io.Writer,
dest.File = dest.File[:len(dest.File)-1] dest.File = dest.File[:len(dest.File)-1]
} }
if err := checkDestinationIsDir(dest, f, cmd); err == nil { if err := o.checkDestinationIsDir(dest); err == nil {
// If no error, dest.File was found to be a directory. // If no error, dest.File was found to be a directory.
// Copy specified src into it // Copy specified src into it
dest.File = dest.File + "/" + path.Base(src.File) dest.File = dest.File + "/" + path.Base(src.File)
@ -194,8 +241,8 @@ func copyToPod(f cmdutil.Factory, cmd *cobra.Command, stdout, stderr io.Writer,
options := &ExecOptions{ options := &ExecOptions{
StreamOptions: StreamOptions{ StreamOptions: StreamOptions{
In: reader, In: reader,
Out: stdout, Out: o.Out,
Err: stderr, Err: o.ErrOut,
Stdin: true, Stdin: true,
Namespace: dest.PodNamespace, Namespace: dest.PodNamespace,
@ -205,10 +252,10 @@ func copyToPod(f cmdutil.Factory, cmd *cobra.Command, stdout, stderr io.Writer,
Command: cmdArr, Command: cmdArr,
Executor: &DefaultRemoteExecutor{}, Executor: &DefaultRemoteExecutor{},
} }
return execute(f, cmd, options) return o.execute(options)
} }
func copyFromPod(f cmdutil.Factory, cmd *cobra.Command, cmderr io.Writer, src, dest fileSpec) error { func (o *CopyOptions) copyFromPod(src, dest fileSpec) error {
if len(src.File) == 0 || len(dest.File) == 0 { if len(src.File) == 0 || len(dest.File) == 0 {
return errFileCannotBeEmpty return errFileCannotBeEmpty
} }
@ -218,7 +265,7 @@ func copyFromPod(f cmdutil.Factory, cmd *cobra.Command, cmderr io.Writer, src, d
StreamOptions: StreamOptions{ StreamOptions: StreamOptions{
In: nil, In: nil,
Out: outStream, Out: outStream,
Err: cmderr, Err: o.Out,
Namespace: src.PodNamespace, Namespace: src.PodNamespace,
PodName: src.PodName, PodName: src.PodName,
@ -231,7 +278,7 @@ func copyFromPod(f cmdutil.Factory, cmd *cobra.Command, cmderr io.Writer, src, d
go func() { go func() {
defer outStream.Close() defer outStream.Close()
execute(f, cmd, options) o.execute(options)
}() }()
prefix := getPrefix(src.File) prefix := getPrefix(src.File)
prefix = path.Clean(prefix) prefix = path.Clean(prefix)
@ -389,31 +436,17 @@ func getPrefix(file string) string {
return strings.TrimLeft(file, "/") return strings.TrimLeft(file, "/")
} }
func execute(f cmdutil.Factory, cmd *cobra.Command, options *ExecOptions) error { func (o *CopyOptions) execute(options *ExecOptions) error {
if len(options.Namespace) == 0 { if len(options.Namespace) == 0 {
namespace, _, err := f.DefaultNamespace() options.Namespace = o.Namespace
if err != nil {
return err
}
options.Namespace = namespace
} }
container := cmdutil.GetFlagString(cmd, "container") if len(o.Container) > 0 {
if len(container) > 0 { options.ContainerName = o.Container
options.ContainerName = container
} }
config, err := f.ClientConfig() options.Config = o.ClientConfig
if err != nil { options.PodClient = o.Clientset.Core()
return err
}
options.Config = config
clientset, err := f.ClientSet()
if err != nil {
return err
}
options.PodClient = clientset.Core()
if err := options.Validate(); err != nil { if err := options.Validate(); err != nil {
return err return err

View File

@ -36,6 +36,7 @@ import (
"k8s.io/client-go/rest/fake" "k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/kubectl/scheme"
) )
@ -524,9 +525,9 @@ func TestCopyToPod(t *testing.T) {
} }
tf.ClientConfigVal = defaultClientConfig() tf.ClientConfigVal = defaultClientConfig()
buf := bytes.NewBuffer([]byte{}) ioStreams, _, _, _ := genericclioptions.NewTestIOStreams()
errBuf := bytes.NewBuffer([]byte{})
cmd := NewCmdCp(tf, buf, errBuf) cmd := NewCmdCp(tf, ioStreams)
srcFile, err := ioutil.TempDir("", "test") srcFile, err := ioutil.TempDir("", "test")
if err != nil { if err != nil {
@ -554,6 +555,7 @@ func TestCopyToPod(t *testing.T) {
} }
for name, test := range tests { for name, test := range tests {
opts := NewCopyOptions(ioStreams)
src := fileSpec{ src := fileSpec{
File: srcFile, File: srcFile,
} }
@ -562,8 +564,9 @@ func TestCopyToPod(t *testing.T) {
PodName: "pod-name", PodName: "pod-name",
File: test.dest, File: test.dest,
} }
opts.Complete(tf, cmd)
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
err = copyToPod(tf, cmd, buf, errBuf, src, dest) err = opts.copyToPod(src, dest)
//If error is NotFound error , it indicates that the //If error is NotFound error , it indicates that the
//request has been sent correctly. //request has been sent correctly.
//Treat this as no error. //Treat this as no error.

View File

@ -19,7 +19,6 @@ package cmd
import ( import (
"errors" "errors"
"fmt" "fmt"
"io"
"math" "math"
"strings" "strings"
"time" "time"
@ -42,18 +41,23 @@ import (
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/printers"
) )
type DrainOptions struct { type DrainOptions struct {
PrintFlags *printers.PrintFlags
ToPrinter func(string) (printers.ResourcePrinterFunc, error)
Namespace string
client kubernetes.Interface client kubernetes.Interface
restClient *restclient.RESTClient restClient *restclient.RESTClient
Factory cmdutil.Factory
Force bool Force bool
DryRun bool DryRun bool
GracePeriodSeconds int GracePeriodSeconds int
@ -65,9 +69,9 @@ type DrainOptions struct {
PodSelector string PodSelector string
mapper meta.RESTMapper mapper meta.RESTMapper
nodeInfos []*resource.Info nodeInfos []*resource.Info
Out io.Writer
ErrOut io.Writer
typer runtime.ObjectTyper typer runtime.ObjectTyper
genericclioptions.IOStreams
} }
// Takes a pod and returns a bool indicating whether or not to operate on the // Takes a pod and returns a bool indicating whether or not to operate on the
@ -101,8 +105,12 @@ var (
kubectl cordon foo`)) kubectl cordon foo`))
) )
func NewCmdCordon(f cmdutil.Factory, out io.Writer) *cobra.Command { func NewCmdCordon(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := &DrainOptions{Factory: f, Out: out} options := &DrainOptions{
PrintFlags: printers.NewPrintFlags("cordoned"),
IOStreams: ioStreams,
}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "cordon NODE", Use: "cordon NODE",
@ -111,7 +119,7 @@ func NewCmdCordon(f cmdutil.Factory, out io.Writer) *cobra.Command {
Long: cordon_long, Long: cordon_long,
Example: cordon_example, Example: cordon_example,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.SetupDrain(cmd, args)) cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.RunCordonOrUncordon(true)) cmdutil.CheckErr(options.RunCordonOrUncordon(true))
}, },
} }
@ -129,8 +137,11 @@ var (
$ kubectl uncordon foo`)) $ kubectl uncordon foo`))
) )
func NewCmdUncordon(f cmdutil.Factory, out io.Writer) *cobra.Command { func NewCmdUncordon(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := &DrainOptions{Factory: f, Out: out} options := &DrainOptions{
PrintFlags: printers.NewPrintFlags("uncordoned"),
IOStreams: ioStreams,
}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "uncordon NODE", Use: "uncordon NODE",
@ -139,7 +150,7 @@ func NewCmdUncordon(f cmdutil.Factory, out io.Writer) *cobra.Command {
Long: uncordon_long, Long: uncordon_long,
Example: uncordon_example, Example: uncordon_example,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.SetupDrain(cmd, args)) cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.RunCordonOrUncordon(false)) cmdutil.CheckErr(options.RunCordonOrUncordon(false))
}, },
} }
@ -182,18 +193,18 @@ var (
$ kubectl drain foo --grace-period=900`)) $ kubectl drain foo --grace-period=900`))
) )
func NewDrainOptions(f cmdutil.Factory, out, errOut io.Writer) *DrainOptions { func NewDrainOptions(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *DrainOptions {
return &DrainOptions{ return &DrainOptions{
Factory: f, PrintFlags: printers.NewPrintFlags("drained"),
Out: out,
ErrOut: errOut, IOStreams: ioStreams,
backOff: clockwork.NewRealClock(), backOff: clockwork.NewRealClock(),
GracePeriodSeconds: -1, GracePeriodSeconds: -1,
} }
} }
func NewCmdDrain(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { func NewCmdDrain(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := NewDrainOptions(f, out, errOut) options := NewDrainOptions(f, ioStreams)
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "drain NODE", Use: "drain NODE",
@ -202,7 +213,7 @@ func NewCmdDrain(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
Long: drain_long, Long: drain_long,
Example: drain_example, Example: drain_example,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.SetupDrain(cmd, args)) cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.RunDrain()) cmdutil.CheckErr(options.RunDrain())
}, },
} }
@ -218,9 +229,9 @@ func NewCmdDrain(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
return cmd return cmd
} }
// SetupDrain populates some fields from the factory, grabs command line // Complete populates some fields from the factory, grabs command line
// arguments and looks up the node using Builder // arguments and looks up the node using Builder
func (o *DrainOptions) SetupDrain(cmd *cobra.Command, args []string) error { func (o *DrainOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
var err error var err error
if len(args) == 0 && !cmd.Flags().Changed("selector") { if len(args) == 0 && !cmd.Flags().Changed("selector") {
@ -235,7 +246,7 @@ func (o *DrainOptions) SetupDrain(cmd *cobra.Command, args []string) error {
o.DryRun = cmdutil.GetDryRunFlag(cmd) o.DryRun = cmdutil.GetDryRunFlag(cmd)
if o.client, err = o.Factory.KubernetesClientSet(); err != nil { if o.client, err = f.KubernetesClientSet(); err != nil {
return err return err
} }
@ -245,21 +256,34 @@ func (o *DrainOptions) SetupDrain(cmd *cobra.Command, args []string) error {
} }
} }
o.restClient, err = o.Factory.RESTClient() o.restClient, err = f.RESTClient()
if err != nil { if err != nil {
return err return err
} }
o.nodeInfos = []*resource.Info{} o.nodeInfos = []*resource.Info{}
cmdNamespace, _, err := o.Factory.DefaultNamespace() o.Namespace, _, err = f.DefaultNamespace()
if err != nil { if err != nil {
return err return err
} }
builder := o.Factory.NewBuilder(). o.ToPrinter = func(operation string) (printers.ResourcePrinterFunc, error) {
o.PrintFlags.NamePrintFlags.Operation = operation
if o.DryRun {
o.PrintFlags.Complete("%s (dry run)")
}
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return nil, err
}
return printer.PrintObj, nil
}
builder := f.NewBuilder().
Internal(). Internal().
NamespaceParam(cmdNamespace).DefaultNamespace(). NamespaceParam(o.Namespace).DefaultNamespace().
ResourceNames("nodes", args...). ResourceNames("nodes", args...).
SingleResourceType(). SingleResourceType().
Flatten() Flatten()
@ -294,6 +318,11 @@ func (o *DrainOptions) RunDrain() error {
return err return err
} }
printer, err := o.ToPrinter("drained")
if err != nil {
return err
}
drainedNodes := sets.NewString() drainedNodes := sets.NewString()
var fatal error var fatal error
@ -304,7 +333,7 @@ func (o *DrainOptions) RunDrain() error {
} }
if err == nil || o.DryRun { if err == nil || o.DryRun {
drainedNodes.Insert(info.Name) drainedNodes.Insert(info.Name)
cmdutil.PrintSuccess(false, o.Out, info.Object, o.DryRun, "drained") printer.PrintObj(info.Object, o.Out)
} else { } else {
fmt.Fprintf(o.ErrOut, "error: unable to drain node %q, aborting command...\n\n", info.Name) fmt.Fprintf(o.ErrOut, "error: unable to drain node %q, aborting command...\n\n", info.Name)
remainingNodes := []string{} remainingNodes := []string{}
@ -620,12 +649,17 @@ func (o *DrainOptions) waitForDelete(pods []corev1.Pod, interval, timeout time.D
} else { } else {
verbStr = "deleted" verbStr = "deleted"
} }
err := wait.PollImmediate(interval, timeout, func() (bool, error) { printer, err := o.ToPrinter(verbStr)
if err != nil {
return pods, err
}
err = wait.PollImmediate(interval, timeout, func() (bool, error) {
pendingPods := []corev1.Pod{} pendingPods := []corev1.Pod{}
for i, pod := range pods { for i, pod := range pods {
p, err := getPodFn(pod.Namespace, pod.Name) p, err := getPodFn(pod.Namespace, pod.Name)
if apierrors.IsNotFound(err) || (p != nil && p.ObjectMeta.UID != pod.ObjectMeta.UID) { if apierrors.IsNotFound(err) || (p != nil && p.ObjectMeta.UID != pod.ObjectMeta.UID) {
cmdutil.PrintSuccess(false, o.Out, &pod, false, verbStr) printer.PrintObj(&pod, o.Out)
continue continue
} else if err != nil { } else if err != nil {
return false, err return false, err
@ -677,11 +711,6 @@ func SupportEviction(clientset kubernetes.Interface) (string, error) {
// RunCordonOrUncordon runs either Cordon or Uncordon. The desired value for // RunCordonOrUncordon runs either Cordon or Uncordon. The desired value for
// "Unschedulable" is passed as the first arg. // "Unschedulable" is passed as the first arg.
func (o *DrainOptions) RunCordonOrUncordon(desired bool) error { func (o *DrainOptions) RunCordonOrUncordon(desired bool) error {
cmdNamespace, _, err := o.Factory.DefaultNamespace()
if err != nil {
return err
}
cordonOrUncordon := "cordon" cordonOrUncordon := "cordon"
if !desired { if !desired {
cordonOrUncordon = "un" + cordonOrUncordon cordonOrUncordon = "un" + cordonOrUncordon
@ -706,7 +735,12 @@ func (o *DrainOptions) RunCordonOrUncordon(desired bool) error {
} }
unsched := node.Spec.Unschedulable unsched := node.Spec.Unschedulable
if unsched == desired { if unsched == desired {
cmdutil.PrintSuccess(false, o.Out, nodeInfo.Object, o.DryRun, already(desired)) printer, err := o.ToPrinter(already(desired))
if err != nil {
fmt.Printf("error: %v", err)
continue
}
printer.PrintObj(nodeInfo.AsVersioned(), o.Out)
} else { } else {
if !o.DryRun { if !o.DryRun {
helper := resource.NewHelper(o.restClient, nodeInfo.Mapping) helper := resource.NewHelper(o.restClient, nodeInfo.Mapping)
@ -721,16 +755,26 @@ func (o *DrainOptions) RunCordonOrUncordon(desired bool) error {
fmt.Printf("error: unable to %s node %q: %v", cordonOrUncordon, nodeInfo.Name, err) fmt.Printf("error: unable to %s node %q: %v", cordonOrUncordon, nodeInfo.Name, err)
continue continue
} }
_, err = helper.Patch(cmdNamespace, nodeInfo.Name, types.StrategicMergePatchType, patchBytes) _, err = helper.Patch(o.Namespace, nodeInfo.Name, types.StrategicMergePatchType, patchBytes)
if err != nil { if err != nil {
fmt.Printf("error: unable to %s node %q: %v", cordonOrUncordon, nodeInfo.Name, err) fmt.Printf("error: unable to %s node %q: %v", cordonOrUncordon, nodeInfo.Name, err)
continue continue
} }
} }
cmdutil.PrintSuccess(false, o.Out, nodeInfo.Object, o.DryRun, changed(desired)) printer, err := o.ToPrinter(changed(desired))
if err != nil {
fmt.Fprintf(o.ErrOut, "%v", err)
continue
}
printer.PrintObj(nodeInfo.AsVersioned(), o.Out)
} }
} else { } else {
cmdutil.PrintSuccess(false, o.Out, nodeInfo.Object, o.DryRun, "skipped") printer, err := o.ToPrinter("skipped")
if err != nil {
fmt.Fprintf(o.ErrOut, "%v", err)
continue
}
printer.PrintObj(nodeInfo.AsVersioned(), o.Out)
} }
} }

View File

@ -17,7 +17,6 @@ limitations under the License.
package cmd package cmd
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -32,6 +31,7 @@ import (
"time" "time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
policyv1beta1 "k8s.io/api/policy/v1beta1" policyv1beta1 "k8s.io/api/policy/v1beta1"
@ -51,6 +51,7 @@ import (
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/printers"
) )
const ( const (
@ -84,7 +85,7 @@ func TestCordon(t *testing.T) {
description string description string
node *corev1.Node node *corev1.Node
expected *corev1.Node expected *corev1.Node
cmd func(cmdutil.Factory, io.Writer) *cobra.Command cmd func(cmdutil.Factory, genericclioptions.IOStreams) *cobra.Command
arg string arg string
expectFatal bool expectFatal bool
}{ }{
@ -196,8 +197,8 @@ func TestCordon(t *testing.T) {
} }
tf.ClientConfigVal = defaultClientConfig() tf.ClientConfigVal = defaultClientConfig()
buf := bytes.NewBuffer([]byte{}) ioStreams, _, _, _ := genericclioptions.NewTestIOStreams()
cmd := test.cmd(tf, buf) cmd := test.cmd(tf, ioStreams)
saw_fatal := false saw_fatal := false
func() { func() {
@ -706,9 +707,8 @@ func TestDrain(t *testing.T) {
} }
tf.ClientConfigVal = defaultClientConfig() tf.ClientConfigVal = defaultClientConfig()
buf := bytes.NewBuffer([]byte{}) ioStreams, _, _, errBuf := genericclioptions.NewTestIOStreams()
errBuf := bytes.NewBuffer([]byte{}) cmd := NewCmdDrain(tf, ioStreams)
cmd := NewCmdDrain(tf, buf, errBuf)
saw_fatal := false saw_fatal := false
fatal_msg := "" fatal_msg := ""
@ -833,9 +833,18 @@ func TestDeletePods(t *testing.T) {
tf := cmdtesting.NewTestFactory() tf := cmdtesting.NewTestFactory()
defer tf.Cleanup() defer tf.Cleanup()
o := DrainOptions{Factory: tf} o := DrainOptions{
PrintFlags: printers.NewPrintFlags("drained"),
}
o.mapper, _ = tf.Object() o.mapper, _ = tf.Object()
o.Out = os.Stdout o.Out = os.Stdout
o.ToPrinter = func(operation string) (printers.ResourcePrinterFunc, error) {
return func(obj runtime.Object, out io.Writer) error {
return nil
}, nil
}
_, pods := createPods(false) _, pods := createPods(false)
pendingPods, err := o.waitForDelete(pods, test.interval, test.timeout, false, test.getPodFn) pendingPods, err := o.waitForDelete(pods, test.interval, test.timeout, false, test.getPodFn)

View File

@ -94,11 +94,11 @@ func NewCmdEdit(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra
// bind flag structs // bind flag structs
o.RecordFlags.AddFlags(cmd) o.RecordFlags.AddFlags(cmd)
o.PrintFlags.AddFlags(cmd)
usage := "to use to edit the resource" usage := "to use to edit the resource"
cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage) cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
cmdutil.AddValidateOptionFlags(cmd, &o.ValidateOptions) cmdutil.AddValidateOptionFlags(cmd, &o.ValidateOptions)
cmd.Flags().StringVarP(&o.Output, "output", "o", o.Output, "Output format. One of: yaml|json.")
cmd.Flags().BoolVarP(&o.OutputPatch, "output-patch", "", o.OutputPatch, "Output the patch if the resource is edited.") cmd.Flags().BoolVarP(&o.OutputPatch, "output-patch", "", o.OutputPatch, "Output the patch if the resource is edited.")
cmd.Flags().BoolVar(&o.WindowsLineEndings, "windows-line-endings", o.WindowsLineEndings, cmd.Flags().BoolVar(&o.WindowsLineEndings, "windows-line-endings", o.WindowsLineEndings,
"Defaults to the line ending native to your platform.") "Defaults to the line ending native to your platform.")

View File

@ -20,10 +20,12 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/golang/glog"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/golang/glog" "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
@ -31,6 +33,7 @@ import (
"k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/printers"
) )
var ( var (
@ -76,17 +79,37 @@ var (
type ExposeServiceOptions struct { type ExposeServiceOptions struct {
FilenameOptions resource.FilenameOptions FilenameOptions resource.FilenameOptions
RecordFlags *genericclioptions.RecordFlags RecordFlags *genericclioptions.RecordFlags
PrintFlags *printers.PrintFlags
PrintObj printers.ResourcePrinterFunc
DryRun bool
EnforceNamespace bool
Generators func(string) map[string]kubectl.Generator
CanBeExposed func(kind schema.GroupKind) error
ClientForMapping func(*meta.RESTMapping) (resource.RESTClient, error)
MapBasedSelectorForObject func(runtime.Object) (string, error)
PortsForObject func(runtime.Object) ([]string, error)
ProtocolsForObject func(runtime.Object) (map[string]string, error)
LabelsForObject func(runtime.Object) (map[string]string, error)
Namespace string
Mapper meta.RESTMapper
Typer runtime.ObjectTyper
Builder *resource.Builder
Recorder genericclioptions.Recorder Recorder genericclioptions.Recorder
genericclioptions.IOStreams genericclioptions.IOStreams
} }
func NewExposeServiceOptions(streams genericclioptions.IOStreams) *ExposeServiceOptions { func NewExposeServiceOptions(ioStreams genericclioptions.IOStreams) *ExposeServiceOptions {
return &ExposeServiceOptions{ return &ExposeServiceOptions{
RecordFlags: genericclioptions.NewRecordFlags(), RecordFlags: genericclioptions.NewRecordFlags(),
PrintFlags: printers.NewPrintFlags("exposed"),
Recorder: genericclioptions.NoopRecorder{}, Recorder: genericclioptions.NoopRecorder{},
IOStreams: streams, IOStreams: ioStreams,
} }
} }
@ -107,15 +130,15 @@ func NewCmdExposeService(f cmdutil.Factory, streams genericclioptions.IOStreams)
Example: exposeExample, Example: exposeExample,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(f, cmd)) cmdutil.CheckErr(o.Complete(f, cmd))
cmdutil.CheckErr(o.RunExpose(f, cmd, args)) cmdutil.CheckErr(o.RunExpose(cmd, args))
}, },
ValidArgs: validArgs, ValidArgs: validArgs,
ArgAliases: kubectl.ResourceAliases(validArgs), ArgAliases: kubectl.ResourceAliases(validArgs),
} }
o.RecordFlags.AddFlags(cmd) o.RecordFlags.AddFlags(cmd)
o.PrintFlags.AddFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmd.Flags().String("generator", "service/v2", i18n.T("The name of the API generator to use. There are 2 generators: 'service/v1' and 'service/v2'. The only difference between them is that service port in v1 is named 'default', while it is left unnamed in v2. Default is 'service/v2'.")) cmd.Flags().String("generator", "service/v2", i18n.T("The name of the API generator to use. There are 2 generators: 'service/v1' and 'service/v2'. The only difference between them is that service port in v1 is named 'default', while it is left unnamed in v2. Default is 'service/v2'."))
cmd.Flags().String("protocol", "", i18n.T("The network protocol for the service to be created. Default is 'TCP'.")) cmd.Flags().String("protocol", "", i18n.T("The network protocol for the service to be created. Default is 'TCP'."))
cmd.Flags().String("port", "", i18n.T("The port that the service should serve on. Copied from the resource being exposed, if unspecified")) cmd.Flags().String("port", "", i18n.T("The port that the service should serve on. Copied from the resource being exposed, if unspecified"))
@ -140,7 +163,16 @@ func NewCmdExposeService(f cmdutil.Factory, streams genericclioptions.IOStreams)
} }
func (o *ExposeServiceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { func (o *ExposeServiceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
var err error o.DryRun = cmdutil.GetDryRunFlag(cmd)
if o.DryRun {
o.PrintFlags.Complete("%s (dry run)")
}
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return err
}
o.PrintObj = printer.PrintObj
o.RecordFlags.Complete(f.Command(cmd, false)) o.RecordFlags.Complete(f.Command(cmd, false))
o.Recorder, err = o.RecordFlags.ToRecorder() o.Recorder, err = o.RecordFlags.ToRecorder()
@ -148,32 +180,41 @@ func (o *ExposeServiceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) e
return err return err
} }
return err o.Generators = f.Generators
} o.Builder = f.NewBuilder()
o.CanBeExposed = f.CanBeExposed
o.ClientForMapping = f.ClientForMapping
o.MapBasedSelectorForObject = f.MapBasedSelectorForObject
o.PortsForObject = f.PortsForObject
o.ProtocolsForObject = f.ProtocolsForObject
o.Mapper, o.Typer = f.Object()
o.LabelsForObject = f.LabelsForObject
func (o *ExposeServiceOptions) RunExpose(f cmdutil.Factory, cmd *cobra.Command, args []string) error { o.Namespace, o.EnforceNamespace, err = f.DefaultNamespace()
namespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil { if err != nil {
return err return err
} }
mapper, typer := f.Object() return err
r := f.NewBuilder(). }
func (o *ExposeServiceOptions) RunExpose(cmd *cobra.Command, args []string) error {
r := o.Builder.
Internal(). Internal().
ContinueOnError(). ContinueOnError().
NamespaceParam(namespace).DefaultNamespace(). NamespaceParam(o.Namespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions). FilenameParam(o.EnforceNamespace, &o.FilenameOptions).
ResourceTypeOrNameArgs(false, args...). ResourceTypeOrNameArgs(false, args...).
Flatten(). Flatten().
Do() Do()
err = r.Err() err := r.Err()
if err != nil { if err != nil {
return cmdutil.UsageErrorf(cmd, err.Error()) return cmdutil.UsageErrorf(cmd, err.Error())
} }
// Get the generator, setup and validate all required parameters // Get the generator, setup and validate all required parameters
generatorName := cmdutil.GetFlagString(cmd, "generator") generatorName := cmdutil.GetFlagString(cmd, "generator")
generators := f.Generators("expose") generators := o.Generators("expose")
generator, found := generators[generatorName] generator, found := generators[generatorName]
if !found { if !found {
return cmdutil.UsageErrorf(cmd, "generator %q not found.", generatorName) return cmdutil.UsageErrorf(cmd, "generator %q not found.", generatorName)
@ -186,7 +227,7 @@ func (o *ExposeServiceOptions) RunExpose(f cmdutil.Factory, cmd *cobra.Command,
} }
mapping := info.ResourceMapping() mapping := info.ResourceMapping()
if err := f.CanBeExposed(mapping.GroupVersionKind.GroupKind()); err != nil { if err := o.CanBeExposed(mapping.GroupVersionKind.GroupKind()); err != nil {
return err return err
} }
@ -200,7 +241,7 @@ func (o *ExposeServiceOptions) RunExpose(f cmdutil.Factory, cmd *cobra.Command,
// For objects that need a pod selector, derive it from the exposed object in case a user // For objects that need a pod selector, derive it from the exposed object in case a user
// didn't explicitly specify one via --selector // didn't explicitly specify one via --selector
if s, found := params["selector"]; found && kubectl.IsZero(s) { if s, found := params["selector"]; found && kubectl.IsZero(s) {
s, err := f.MapBasedSelectorForObject(info.Object) s, err := o.MapBasedSelectorForObject(info.Object)
if err != nil { if err != nil {
return cmdutil.UsageErrorf(cmd, "couldn't retrieve selectors via --selector flag or introspection: %v", err) return cmdutil.UsageErrorf(cmd, "couldn't retrieve selectors via --selector flag or introspection: %v", err)
} }
@ -212,7 +253,7 @@ func (o *ExposeServiceOptions) RunExpose(f cmdutil.Factory, cmd *cobra.Command,
// For objects that need a port, derive it from the exposed object in case a user // For objects that need a port, derive it from the exposed object in case a user
// didn't explicitly specify one via --port // didn't explicitly specify one via --port
if port, found := params["port"]; found && kubectl.IsZero(port) { if port, found := params["port"]; found && kubectl.IsZero(port) {
ports, err := f.PortsForObject(info.Object) ports, err := o.PortsForObject(info.Object)
if err != nil { if err != nil {
return cmdutil.UsageErrorf(cmd, "couldn't find port via --port flag or introspection: %v", err) return cmdutil.UsageErrorf(cmd, "couldn't find port via --port flag or introspection: %v", err)
} }
@ -231,7 +272,7 @@ func (o *ExposeServiceOptions) RunExpose(f cmdutil.Factory, cmd *cobra.Command,
// Always try to derive protocols from the exposed object, may use // Always try to derive protocols from the exposed object, may use
// different protocols for different ports. // different protocols for different ports.
if _, found := params["protocol"]; found { if _, found := params["protocol"]; found {
protocolsMap, err := f.ProtocolsForObject(info.Object) protocolsMap, err := o.ProtocolsForObject(info.Object)
if err != nil { if err != nil {
return cmdutil.UsageErrorf(cmd, "couldn't find protocol via introspection: %v", err) return cmdutil.UsageErrorf(cmd, "couldn't find protocol via introspection: %v", err)
} }
@ -241,7 +282,7 @@ func (o *ExposeServiceOptions) RunExpose(f cmdutil.Factory, cmd *cobra.Command,
} }
if kubectl.IsZero(params["labels"]) { if kubectl.IsZero(params["labels"]) {
labels, err := f.LabelsForObject(info.Object) labels, err := o.LabelsForObject(info.Object)
if err != nil { if err != nil {
return err return err
} }
@ -270,9 +311,9 @@ func (o *ExposeServiceOptions) RunExpose(f cmdutil.Factory, cmd *cobra.Command,
} }
resourceMapper := &resource.Mapper{ resourceMapper := &resource.Mapper{
ObjectTyper: typer, ObjectTyper: o.Typer,
RESTMapper: mapper, RESTMapper: o.Mapper,
ClientMapper: resource.ClientMapperFunc(f.ClientForMapping), ClientMapper: resource.ClientMapperFunc(o.ClientForMapping),
Decoder: cmdutil.InternalVersionDecoder(), Decoder: cmdutil.InternalVersionDecoder(),
} }
info, err = resourceMapper.InfoForObject(object, nil) info, err = resourceMapper.InfoForObject(object, nil)
@ -283,29 +324,20 @@ func (o *ExposeServiceOptions) RunExpose(f cmdutil.Factory, cmd *cobra.Command,
glog.V(4).Infof("error recording current command: %v", err) glog.V(4).Infof("error recording current command: %v", err)
} }
info.Refresh(object, true) info.Refresh(object, true)
if cmdutil.GetDryRunFlag(cmd) { if o.DryRun {
if len(cmdutil.GetFlagString(cmd, "output")) > 0 { return o.PrintObj(object, o.Out)
return cmdutil.PrintObject(cmd, object, o.Out)
}
cmdutil.PrintSuccess(false, o.Out, info.Object, true, "exposed")
return nil
} }
if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info.Object, cmdutil.InternalVersionJSONEncoder()); err != nil { if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info.Object, cmdutil.InternalVersionJSONEncoder()); err != nil {
return err return err
} }
// Serialize the object with the annotation applied. // Serialize the object with the annotation applied.
object, err = resource.NewHelper(info.Client, info.Mapping).Create(namespace, false, object) object, err = resource.NewHelper(info.Client, info.Mapping).Create(o.Namespace, false, object)
if err != nil { if err != nil {
return err return err
} }
if len(cmdutil.GetFlagString(cmd, "output")) > 0 { return o.PrintObj(info.AsVersioned(), o.Out)
return cmdutil.PrintObject(cmd, object, o.Out)
}
cmdutil.PrintSuccess(false, o.Out, info.Object, false, "exposed")
return nil
}) })
if err != nil { if err != nil {
return err return err

View File

@ -79,7 +79,7 @@ func TestRunExposeService(t *testing.T) {
Selector: map[string]string{"app": "go"}, Selector: map[string]string{"app": "go"},
}, },
}, },
expected: "service \"foo\" exposed", expected: "service/foo exposed",
status: 200, status: 200,
}, },
{ {
@ -110,7 +110,7 @@ func TestRunExposeService(t *testing.T) {
Selector: map[string]string{"func": "stream"}, Selector: map[string]string{"func": "stream"},
}, },
}, },
expected: "service \"foo\" exposed", expected: "service/foo exposed",
status: 200, status: 200,
}, },
{ {
@ -142,7 +142,7 @@ func TestRunExposeService(t *testing.T) {
Selector: map[string]string{"run": "this"}, Selector: map[string]string{"run": "this"},
}, },
}, },
expected: "service \"mayor\" exposed", expected: "service/mayor exposed",
status: 200, status: 200,
}, },
{ {
@ -237,7 +237,7 @@ func TestRunExposeService(t *testing.T) {
ClusterIP: "10.10.10.10", ClusterIP: "10.10.10.10",
}, },
}, },
expected: "service \"foo\" exposed", expected: "service /foo exposed",
status: 200, status: 200,
}, },
{ {
@ -269,7 +269,7 @@ func TestRunExposeService(t *testing.T) {
ClusterIP: api.ClusterIPNone, ClusterIP: api.ClusterIPNone,
}, },
}, },
expected: "service \"foo\" exposed", expected: "service/foo exposed",
status: 200, status: 200,
}, },
{ {
@ -295,7 +295,7 @@ func TestRunExposeService(t *testing.T) {
ClusterIP: api.ClusterIPNone, ClusterIP: api.ClusterIPNone,
}, },
}, },
expected: "service \"foo\" exposed", expected: "service/foo exposed",
status: 200, status: 200,
}, },
{ {
@ -353,7 +353,7 @@ func TestRunExposeService(t *testing.T) {
Selector: map[string]string{"svc": "frompod"}, Selector: map[string]string{"svc": "frompod"},
}, },
}, },
expected: "service \"a-name-that-is-toooo-big-for-a-service-because-it-can-only-hand\" exposed", expected: "service/a-name-that-is-toooo-big-for-a-service-because-it-can-only-hand exposed",
status: 200, status: 200,
}, },
{ {
@ -500,7 +500,7 @@ func TestRunExposeService(t *testing.T) {
out := buf.String() out := buf.String()
if _, ok := test.flags["dry-run"]; ok { if _, ok := test.flags["dry-run"]; ok {
test.expected = fmt.Sprintf("service %q exposed (dry run)", test.flags["name"]) test.expected = fmt.Sprintf("service/%s exposed (dry run)", test.flags["name"])
} }
if !strings.Contains(out, test.expected) { if !strings.Contains(out, test.expected) {

View File

@ -18,7 +18,6 @@ package cmd
import ( import (
"fmt" "fmt"
"io"
"reflect" "reflect"
"strings" "strings"
@ -40,6 +39,7 @@ import (
"k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/printers"
) )
// LabelOptions have the data required to perform the label operation // LabelOptions have the data required to perform the label operation
@ -48,6 +48,9 @@ type LabelOptions struct {
resource.FilenameOptions resource.FilenameOptions
RecordFlags *genericclioptions.RecordFlags RecordFlags *genericclioptions.RecordFlags
PrintFlags *printers.PrintFlags
ToPrinter func(string) (printers.ResourcePrinterFunc, error)
// Common user flags // Common user flags
overwrite bool overwrite bool
list bool list bool
@ -66,8 +69,7 @@ type LabelOptions struct {
Recorder genericclioptions.Recorder Recorder genericclioptions.Recorder
// Common shared fields // Common shared fields
out io.Writer genericclioptions.IOStreams
errout io.Writer
} }
var ( var (
@ -100,19 +102,19 @@ var (
kubectl label pods foo bar-`)) kubectl label pods foo bar-`))
) )
func NewLabelOptions(out, errOut io.Writer) *LabelOptions { func NewLabelOptions(ioStreams genericclioptions.IOStreams) *LabelOptions {
return &LabelOptions{ return &LabelOptions{
RecordFlags: genericclioptions.NewRecordFlags(), RecordFlags: genericclioptions.NewRecordFlags(),
Recorder: genericclioptions.NoopRecorder{}, Recorder: genericclioptions.NoopRecorder{},
out: out, PrintFlags: printers.NewPrintFlags("labeled"),
errout: errOut,
IOStreams: ioStreams,
} }
} }
func NewCmdLabel(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { func NewCmdLabel(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := NewLabelOptions(out, errOut) o := NewLabelOptions(ioStreams)
validArgs := cmdutil.ValidArgList(f) validArgs := cmdutil.ValidArgList(f)
@ -136,8 +138,8 @@ func NewCmdLabel(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
} }
o.RecordFlags.AddFlags(cmd) o.RecordFlags.AddFlags(cmd)
o.PrintFlags.AddFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmd.Flags().BoolVar(&o.overwrite, "overwrite", o.overwrite, "If true, allow labels to be overwritten, otherwise reject label updates that overwrite existing labels.") cmd.Flags().BoolVar(&o.overwrite, "overwrite", o.overwrite, "If true, allow labels to be overwritten, otherwise reject label updates that overwrite existing labels.")
cmd.Flags().BoolVar(&o.list, "list", o.list, "If true, display the labels for a given resource.") cmd.Flags().BoolVar(&o.list, "list", o.list, "If true, display the labels for a given resource.")
cmd.Flags().BoolVar(&o.local, "local", o.local, "If true, label will NOT contact api-server but run locally.") cmd.Flags().BoolVar(&o.local, "local", o.local, "If true, label will NOT contact api-server but run locally.")
@ -165,6 +167,19 @@ func (o *LabelOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st
o.outputFormat = cmdutil.GetFlagString(cmd, "output") o.outputFormat = cmdutil.GetFlagString(cmd, "output")
o.dryrun = cmdutil.GetDryRunFlag(cmd) o.dryrun = cmdutil.GetDryRunFlag(cmd)
o.ToPrinter = func(operation string) (printers.ResourcePrinterFunc, error) {
o.PrintFlags.NamePrintFlags.Operation = operation
if o.dryrun {
o.PrintFlags.Complete("%s (dry run)")
}
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return nil, err
}
return printer.PrintObj, nil
}
resources, labelArgs, err := cmdutil.GetResourcesAndPairs(args, "label") resources, labelArgs, err := cmdutil.GetResourcesAndPairs(args, "label")
if err != nil { if err != nil {
return err return err
@ -255,7 +270,7 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error {
} }
for _, label := range o.removeLabels { for _, label := range o.removeLabels {
if _, ok := accessor.GetLabels()[label]; !ok { if _, ok := accessor.GetLabels()[label]; !ok {
fmt.Fprintf(o.out, "label %q not found.\n", label) fmt.Fprintf(o.Out, "label %q not found.\n", label)
} }
} }
@ -304,19 +319,20 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error {
indent := "" indent := ""
if !one { if !one {
indent = " " indent = " "
fmt.Fprintf(o.errout, "Listing labels for %s.%s/%s:\n", info.Mapping.GroupVersionKind.Kind, info.Mapping.GroupVersionKind.Group, info.Name) fmt.Fprintf(o.ErrOut, "Listing labels for %s.%s/%s:\n", info.Mapping.GroupVersionKind.Kind, info.Mapping.GroupVersionKind.Group, info.Name)
} }
for k, v := range accessor.GetLabels() { for k, v := range accessor.GetLabels() {
fmt.Fprintf(o.out, "%s%s=%s\n", indent, k, v) fmt.Fprintf(o.Out, "%s%s=%s\n", indent, k, v)
} }
return nil return nil
} }
if len(o.outputFormat) > 0 { printer, err := o.ToPrinter(dataChangeMsg)
return cmdutil.PrintObject(cmd, outputObj, o.out) if err != nil {
return err
} }
cmdutil.PrintSuccess(false, o.out, info.Object, o.dryrun, dataChangeMsg) printer.PrintObj(info.AsVersioned(), o.Out)
return nil return nil
}) })
} }

View File

@ -29,6 +29,7 @@ import (
"k8s.io/client-go/rest/fake" "k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/legacyscheme"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/kubectl/scheme"
) )
@ -327,14 +328,15 @@ func TestLabelErrors(t *testing.T) {
tf.Namespace = "test" tf.Namespace = "test"
tf.ClientConfigVal = defaultClientConfig() tf.ClientConfigVal = defaultClientConfig()
ioStreams, _, _, _ := genericclioptions.NewTestIOStreams()
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
cmd := NewCmdLabel(tf, buf, buf) cmd := NewCmdLabel(tf, ioStreams)
cmd.SetOutput(buf) cmd.SetOutput(buf)
for k, v := range testCase.flags { for k, v := range testCase.flags {
cmd.Flags().Set(k, v) cmd.Flags().Set(k, v)
} }
opts := NewLabelOptions(buf, buf) opts := NewLabelOptions(ioStreams)
err := opts.Complete(tf, cmd, testCase.args) err := opts.Complete(tf, cmd, testCase.args)
if err == nil { if err == nil {
err = opts.Validate() err = opts.Validate()
@ -389,9 +391,9 @@ func TestLabelForResourceFromFile(t *testing.T) {
tf.Namespace = "test" tf.Namespace = "test"
tf.ClientConfigVal = defaultClientConfig() tf.ClientConfigVal = defaultClientConfig()
buf := bytes.NewBuffer([]byte{}) ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdLabel(tf, buf, buf) cmd := NewCmdLabel(tf, ioStreams)
opts := NewLabelOptions(buf, buf) opts := NewLabelOptions(ioStreams)
opts.Filenames = []string{"../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"} opts.Filenames = []string{"../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"}
err := opts.Complete(tf, cmd, []string{"a=b"}) err := opts.Complete(tf, cmd, []string{"a=b"})
if err == nil { if err == nil {
@ -422,9 +424,9 @@ func TestLabelLocal(t *testing.T) {
tf.Namespace = "test" tf.Namespace = "test"
tf.ClientConfigVal = defaultClientConfig() tf.ClientConfigVal = defaultClientConfig()
buf := bytes.NewBuffer([]byte{}) ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdLabel(tf, buf, buf) cmd := NewCmdLabel(tf, ioStreams)
opts := NewLabelOptions(buf, buf) opts := NewLabelOptions(ioStreams)
opts.Filenames = []string{"../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"} opts.Filenames = []string{"../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml"}
opts.local = true opts.local = true
err := opts.Complete(tf, cmd, []string{"a=b"}) err := opts.Complete(tf, cmd, []string{"a=b"})
@ -480,10 +482,10 @@ func TestLabelMultipleObjects(t *testing.T) {
tf.Namespace = "test" tf.Namespace = "test"
tf.ClientConfigVal = defaultClientConfig() tf.ClientConfigVal = defaultClientConfig()
buf := bytes.NewBuffer([]byte{}) ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
opts := NewLabelOptions(buf, buf) opts := NewLabelOptions(ioStreams)
opts.all = true opts.all = true
cmd := NewCmdLabel(tf, buf, buf) cmd := NewCmdLabel(tf, ioStreams)
err := opts.Complete(tf, cmd, []string{"pods", "a=b"}) err := opts.Complete(tf, cmd, []string{"pods", "a=b"})
if err == nil { if err == nil {
err = opts.Validate() err = opts.Validate()

View File

@ -25,6 +25,7 @@ import (
"time" "time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"

View File

@ -40,6 +40,7 @@ import (
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/printers"
) )
var patchTypes = map[string]types.PatchType{"json": types.JSONPatchType, "merge": types.MergePatchType, "strategic": types.StrategicMergePatchType} var patchTypes = map[string]types.PatchType{"json": types.JSONPatchType, "merge": types.MergePatchType, "strategic": types.StrategicMergePatchType}
@ -49,6 +50,8 @@ var patchTypes = map[string]types.PatchType{"json": types.JSONPatchType, "merge"
type PatchOptions struct { type PatchOptions struct {
resource.FilenameOptions resource.FilenameOptions
RecordFlags *genericclioptions.RecordFlags RecordFlags *genericclioptions.RecordFlags
PrintFlags *printers.PrintFlags
ToPrinter func(string) (printers.ResourcePrinterFunc, error)
Local bool Local bool
DryRun bool DryRun bool
@ -86,8 +89,8 @@ var (
func NewPatchOptions() *PatchOptions { func NewPatchOptions() *PatchOptions {
return &PatchOptions{ return &PatchOptions{
RecordFlags: genericclioptions.NewRecordFlags(), RecordFlags: genericclioptions.NewRecordFlags(),
Recorder: genericclioptions.NoopRecorder{}, Recorder: genericclioptions.NoopRecorder{},
PrintFlags: printers.NewPrintFlags("patched"),
} }
} }
@ -110,11 +113,11 @@ func NewCmdPatch(f cmdutil.Factory, out io.Writer) *cobra.Command {
} }
o.RecordFlags.AddFlags(cmd) o.RecordFlags.AddFlags(cmd)
o.PrintFlags.AddFlags(cmd)
cmd.Flags().StringP("patch", "p", "", "The patch to be applied to the resource JSON file.") cmd.Flags().StringP("patch", "p", "", "The patch to be applied to the resource JSON file.")
cmd.MarkFlagRequired("patch") cmd.MarkFlagRequired("patch")
cmd.Flags().String("type", "strategic", fmt.Sprintf("The type of patch being provided; one of %v", sets.StringKeySet(patchTypes).List())) cmd.Flags().String("type", "strategic", fmt.Sprintf("The type of patch being provided; one of %v", sets.StringKeySet(patchTypes).List()))
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddDryRunFlag(cmd) cmdutil.AddDryRunFlag(cmd)
usage := "identifying the resource to update" usage := "identifying the resource to update"
@ -137,6 +140,19 @@ func (o *PatchOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
o.OutputFormat = cmdutil.GetFlagString(cmd, "output") o.OutputFormat = cmdutil.GetFlagString(cmd, "output")
o.DryRun = cmdutil.GetFlagBool(cmd, "dry-run") o.DryRun = cmdutil.GetFlagBool(cmd, "dry-run")
o.ToPrinter = func(operation string) (printers.ResourcePrinterFunc, error) {
o.PrintFlags.NamePrintFlags.Operation = operation
if o.DryRun {
o.PrintFlags.Complete("%s (dry run)")
}
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return nil, err
}
return printer.PrintObj, nil
}
return err return err
} }
@ -222,10 +238,11 @@ func (o *PatchOptions) RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Com
return err return err
} }
if len(o.OutputFormat) > 0 && o.OutputFormat != "name" { printer, err := o.ToPrinter(patchOperation(didPatch))
return cmdutil.PrintObject(cmd, info.Object, out) if err != nil {
return err
} }
cmdutil.PrintSuccess(o.OutputFormat == "name", out, info.Object, false, patchOperation(didPatch)) printer.PrintObj(info.AsVersioned(), out)
// if object was not successfully patched, exit with error code 1 // if object was not successfully patched, exit with error code 1
if !didPatch { if !didPatch {
@ -264,12 +281,11 @@ func (o *PatchOptions) RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Com
} }
} }
if len(o.OutputFormat) > 0 && o.OutputFormat != "name" { printer, err := o.ToPrinter(patchOperation(didPatch))
return cmdutil.PrintObject(cmd, info.Object, out) if err != nil {
return err
} }
return printer.PrintObj(info.AsVersioned(), out)
cmdutil.PrintSuccess(o.OutputFormat == "name", out, info.Object, o.DryRun, patchOperation(didPatch))
return nil
}) })
if err != nil { if err != nil {
return err return err

View File

@ -144,7 +144,7 @@ func TestPatchNoop(t *testing.T) {
cmd.Flags().Set("namespace", "test") cmd.Flags().Set("namespace", "test")
cmd.Flags().Set("patch", `{"metadata":{"annotations":{"foo":"bar"}}}`) cmd.Flags().Set("patch", `{"metadata":{"annotations":{"foo":"bar"}}}`)
cmd.Run(cmd, []string{"services", "frontend"}) cmd.Run(cmd, []string{"services", "frontend"})
if buf.String() != "service \"baz\" patched\n" { if buf.String() != "service/baz patched\n" {
t.Errorf("unexpected output: %s", buf.String()) t.Errorf("unexpected output: %s", buf.String())
} }
} }

View File

@ -5,8 +5,8 @@ args:
- service/svc1 - service/svc1
namespace: "myproject" namespace: "myproject"
expectedStdout: expectedStdout:
- configmap "cm1" edited - configmap/cm1 edited
- service "svc1" edited - service/svc1 edited
expectedExitCode: 0 expectedExitCode: 0
steps: steps:
- type: request - type: request

View File

@ -5,8 +5,8 @@ args:
- service/svc1 - service/svc1
namespace: "myproject" namespace: "myproject"
expectedStdout: expectedStdout:
- configmap "cm1" edited - configmap/cm1 edited
- service "svc1" edited - service/svc1 edited
expectedExitCode: 0 expectedExitCode: 0
steps: steps:
- type: request - type: request

View File

@ -4,7 +4,7 @@ args:
- service/svc1 - service/svc1
namespace: myproject namespace: myproject
expectedStdout: expectedStdout:
- "service \"svc1\" edited" - "service/svc1 edited"
expectedExitCode: 0 expectedExitCode: 0
steps: steps:
- type: request - type: request

View File

@ -6,7 +6,7 @@ args:
outputFormat: yaml outputFormat: yaml
namespace: myproject namespace: myproject
expectedStdout: expectedStdout:
- service "svc1" edited - service/svc1 edited
expectedExitCode: 0 expectedExitCode: 0
steps: steps:
- type: request - type: request

View File

@ -3,7 +3,7 @@ mode: create
filename: "svc.yaml" filename: "svc.yaml"
namespace: "edit-test" namespace: "edit-test"
expectedStdout: expectedStdout:
- "service \"svc1\" created" - "service/svc1 created"
expectedStderr: expectedStderr:
- "\"svc2\" is invalid" - "\"svc2\" is invalid"
expectedExitCode: 1 expectedExitCode: 1

View File

@ -3,8 +3,8 @@ mode: create
filename: "svc.yaml" filename: "svc.yaml"
namespace: "edit-test" namespace: "edit-test"
expectedStdout: expectedStdout:
- service "svc1" created - service/svc1 created
- service "svc2" created - service/svc2 created
expectedExitCode: 0 expectedExitCode: 0
steps: steps:
- type: edit - type: edit

View File

@ -5,7 +5,7 @@ args:
- svc1 - svc1
namespace: edit-test namespace: edit-test
expectedStdout: expectedStdout:
- service "svc1" edited - service/svc1 edited
expectedStderr: expectedStderr:
- "error: services \"svc1\" is invalid" - "error: services \"svc1\" is invalid"
expectedExitCode: 0 expectedExitCode: 0

View File

@ -11,7 +11,7 @@ outputPatch: "true"
namespace: edit-test namespace: edit-test
expectedStdout: expectedStdout:
- 'Patch: {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"creationTimestamp\":\"2017-02-27T19:40:53Z\",\"labels\":{\"app\":\"svc1\",\"new-label\":\"new-value\"},\"name\":\"svc1\",\"namespace\":\"edit-test\",\"resourceVersion\":\"670\",\"selfLink\":\"/api/v1/namespaces/edit-test/services/svc1\",\"uid\":\"a6c11186-fd24-11e6-b53c-480fcf4a5275\"},\"spec\":{\"clusterIP\":\"10.0.0.204\",\"ports\":[{\"name\":\"80\",\"port\":80,\"protocol\":\"TCP\",\"targetPort\":80}],\"selector\":{\"app\":\"svc1\"},\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"},"labels":{"new-label":"new-value"}}}' - 'Patch: {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"creationTimestamp\":\"2017-02-27T19:40:53Z\",\"labels\":{\"app\":\"svc1\",\"new-label\":\"new-value\"},\"name\":\"svc1\",\"namespace\":\"edit-test\",\"resourceVersion\":\"670\",\"selfLink\":\"/api/v1/namespaces/edit-test/services/svc1\",\"uid\":\"a6c11186-fd24-11e6-b53c-480fcf4a5275\"},\"spec\":{\"clusterIP\":\"10.0.0.204\",\"ports\":[{\"name\":\"80\",\"port\":80,\"protocol\":\"TCP\",\"targetPort\":80}],\"selector\":{\"app\":\"svc1\"},\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"},"labels":{"new-label":"new-value"}}}'
- service "svc1" edited - service/svc1 edited
expectedExitCode: 0 expectedExitCode: 0
steps: steps:
- type: request - type: request

View File

@ -4,8 +4,8 @@ args:
- configmaps,services - configmaps,services
namespace: "edit-test" namespace: "edit-test"
expectedStdout: expectedStdout:
- configmap "cm1" edited - configmap/cm1 edited
- service "svc1" edited - service/svc1 edited
expectedExitCode: 0 expectedExitCode: 0
steps: steps:
- type: request - type: request

View File

@ -5,8 +5,8 @@ args:
- service/svc1 - service/svc1
namespace: "edit-test" namespace: "edit-test"
expectedStdout: expectedStdout:
- configmap "cm1" edited - configmap/cm1 edited
- service "svc1" edited - service/svc1 edited
expectedExitCode: 0 expectedExitCode: 0
steps: steps:
- type: request - type: request

View File

@ -5,8 +5,8 @@ args:
- service/svc1 - service/svc1
namespace: "edit-test" namespace: "edit-test"
expectedStdout: expectedStdout:
- configmap "cm1" edited - configmap/cm1 edited
- service "svc1" edited - service/svc1 edited
expectedExitCode: 0 expectedExitCode: 0
steps: steps:
- type: request - type: request

View File

@ -9,7 +9,7 @@ args:
saveConfig: "false" saveConfig: "false"
namespace: edit-test namespace: edit-test
expectedStdout: expectedStdout:
- service "svc1" edited - service/svc1 edited
expectedExitCode: 0 expectedExitCode: 0
steps: steps:
- type: request - type: request

View File

@ -6,9 +6,9 @@ args:
- bars/test2 - bars/test2
namespace: default namespace: default
expectedStdout: expectedStdout:
- "service \"kubernetes\" edited" - "service/kubernetes edited"
- "bar.company.com \"test\" edited" - "bar.company.com/test edited"
- "bar.company.com \"test2\" edited" - "bar.company.com/test2 edited"
expectedExitCode: 0 expectedExitCode: 0
steps: steps:
- type: request - type: request

View File

@ -8,7 +8,7 @@ args:
- svc1 - svc1
namespace: edit-test namespace: edit-test
expectedStdout: expectedStdout:
- service "svc1" edited - service/svc1 edited
expectedExitCode: 0 expectedExitCode: 0
steps: steps:
- type: request - type: request

View File

@ -4,7 +4,7 @@ args:
- service/kubernetes - service/kubernetes
namespace: default namespace: default
expectedStdout: expectedStdout:
- "service \"kubernetes\" edited" - "service/kubernetes edited"
expectedExitCode: 0 expectedExitCode: 0
steps: steps:
- type: request - type: request

View File

@ -4,7 +4,7 @@ args:
- storageclasses.v1beta1.storage.k8s.io/foo - storageclasses.v1beta1.storage.k8s.io/foo
namespace: default namespace: default
expectedStdout: expectedStdout:
- "storageclass.storage.k8s.io \"foo\" edited" - "storageclass.storage.k8s.io/foo edited"
expectedExitCode: 0 expectedExitCode: 0
steps: steps:
- type: request - type: request

View File

@ -4,7 +4,7 @@ args:
- storageclasses.v0.storage.k8s.io/foo - storageclasses.v0.storage.k8s.io/foo
namespace: default namespace: default
expectedStdout: expectedStdout:
- "storageclass.storage.k8s.io \"foo\" edited" - "storageclass.storage.k8s.io/foo edited"
expectedExitCode: 0 expectedExitCode: 0
steps: steps:
- type: request - type: request

View File

@ -9,7 +9,7 @@ args:
saveConfig: "true" saveConfig: "true"
namespace: edit-test namespace: edit-test
expectedStdout: expectedStdout:
- service "svc1" edited - service/svc1 edited
expectedExitCode: 0 expectedExitCode: 0
steps: steps:
- type: request - type: request

View File

@ -57,6 +57,9 @@ type EditOptions struct {
resource.FilenameOptions resource.FilenameOptions
RecordFlags *genericclioptions.RecordFlags RecordFlags *genericclioptions.RecordFlags
PrintFlags *printers.PrintFlags
ToPrinter func(string) (printers.ResourcePrinterFunc, error)
Output string Output string
OutputPatch bool OutputPatch bool
WindowsLineEndings bool WindowsLineEndings bool
@ -86,12 +89,14 @@ func NewEditOptions(editMode EditMode, ioStreams genericclioptions.IOStreams) *E
EditMode: editMode, EditMode: editMode,
Output: "yaml", PrintFlags: printers.NewPrintFlags("edited"),
WindowsLineEndings: goruntime.GOOS == "windows", WindowsLineEndings: goruntime.GOOS == "windows",
Recorder: genericclioptions.NoopRecorder{}, Recorder: genericclioptions.NoopRecorder{},
IOStreams: ioStreams, IOStreams: ioStreams,
Output: "yaml",
} }
} }
@ -159,6 +164,15 @@ func (o *EditOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Comm
Do() Do()
} }
o.ToPrinter = func(operation string) (printers.ResourcePrinterFunc, error) {
o.PrintFlags.NamePrintFlags.Operation = operation
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return nil, err
}
return printer.PrintObj, nil
}
o.CmdNamespace = cmdNamespace o.CmdNamespace = cmdNamespace
o.f = f o.f = f
@ -423,14 +437,23 @@ func (o *EditOptions) visitToApplyEditPatch(originalInfos []*resource.Info, patc
} }
if reflect.DeepEqual(originalJS, editedJS) { if reflect.DeepEqual(originalJS, editedJS) {
cmdutil.PrintSuccess(false, o.Out, info.Object, false, "skipped") printer, err := o.ToPrinter("skipped")
if err != nil {
return err
}
printer.PrintObj(info.AsVersioned(), o.Out)
return nil return nil
} else { } else {
err := o.annotationPatch(info) err := o.annotationPatch(info)
if err != nil { if err != nil {
return err return err
} }
cmdutil.PrintSuccess(false, o.Out, info.Object, false, "edited")
printer, err := o.ToPrinter("edited")
if err != nil {
return err
}
printer.PrintObj(info.AsVersioned(), o.Out)
return nil return nil
} }
}) })
@ -549,7 +572,11 @@ func (o *EditOptions) visitToPatch(originalInfos []*resource.Info, patchVisitor
if reflect.DeepEqual(originalJS, editedJS) { if reflect.DeepEqual(originalJS, editedJS) {
// no edit, so just skip it. // no edit, so just skip it.
cmdutil.PrintSuccess(false, o.Out, info.Object, false, "skipped") printer, err := o.ToPrinter("skipped")
if err != nil {
return err
}
printer.PrintObj(info.AsVersioned(), o.Out)
return nil return nil
} }
@ -603,7 +630,11 @@ func (o *EditOptions) visitToPatch(originalInfos []*resource.Info, patchVisitor
return nil return nil
} }
info.Refresh(patched, true) info.Refresh(patched, true)
cmdutil.PrintSuccess(false, o.Out, info.Object, false, "edited") printer, err := o.ToPrinter("edited")
if err != nil {
return err
}
printer.PrintObj(info.AsVersioned(), o.Out)
return nil return nil
}) })
return err return err
@ -614,7 +645,11 @@ func (o *EditOptions) visitToCreate(createVisitor resource.Visitor) error {
if err := resource.CreateAndRefresh(info); err != nil { if err := resource.CreateAndRefresh(info); err != nil {
return err return err
} }
cmdutil.PrintSuccess(false, o.Out, info.Object, false, "created") printer, err := o.ToPrinter("created")
if err != nil {
return err
}
printer.PrintObj(info.AsVersioned(), o.Out)
return nil return nil
}) })
return err return err

View File

@ -23,11 +23,17 @@ import (
) )
type NoCompatiblePrinterError struct { type NoCompatiblePrinterError struct {
OutputFormat *string
Options interface{} Options interface{}
} }
func (e NoCompatiblePrinterError) Error() string { func (e NoCompatiblePrinterError) Error() string {
return fmt.Sprintf("unable to match a printer suitable for the options specified: %#v", e.Options) output := ""
if e.OutputFormat != nil {
output = *e.OutputFormat
}
return fmt.Sprintf("unable to match a printer suitable for the output format %q and the options specified: %#v", output, e.Options)
} }
func IsNoCompatiblePrinterError(err error) bool { func IsNoCompatiblePrinterError(err error) bool {
@ -67,7 +73,7 @@ func (f *PrintFlags) ToPrinter() (ResourcePrinter, error) {
return p, err return p, err
} }
return nil, NoCompatiblePrinterError{f} return nil, NoCompatiblePrinterError{Options: f, OutputFormat: f.OutputFormat}
} }
func (f *PrintFlags) AddFlags(cmd *cobra.Command) { func (f *PrintFlags) AddFlags(cmd *cobra.Command) {
@ -79,6 +85,20 @@ func (f *PrintFlags) AddFlags(cmd *cobra.Command) {
} }
} }
// WithDefaultOutput sets a default output format if one is not provided through a flag value
func (f *PrintFlags) WithDefaultOutput(output string) *PrintFlags {
existingFormat := ""
if f.OutputFormat != nil {
existingFormat = *f.OutputFormat
}
if len(existingFormat) == 0 {
existingFormat = output
}
f.OutputFormat = &existingFormat
return f
}
func NewPrintFlags(operation string) *PrintFlags { func NewPrintFlags(operation string) *PrintFlags {
outputFormat := "" outputFormat := ""

View File

@ -44,7 +44,7 @@ func (f *JSONYamlPrintFlags) ToPrinter(outputFormat string) (ResourcePrinter, er
case "yaml": case "yaml":
printer = &YAMLPrinter{} printer = &YAMLPrinter{}
default: default:
return nil, NoCompatiblePrinterError{f} return nil, NoCompatiblePrinterError{Options: f, OutputFormat: &outputFormat}
} }
// wrap the printer in a versioning printer that understands when to convert and when not to convert // wrap the printer in a versioning printer that understands when to convert and when not to convert

View File

@ -39,7 +39,7 @@ type JSONPathPrintFlags struct {
// Returns false if the specified templateFormat does not match a template format. // Returns false if the specified templateFormat does not match a template format.
func (f *JSONPathPrintFlags) ToPrinter(templateFormat string) (ResourcePrinter, error) { func (f *JSONPathPrintFlags) ToPrinter(templateFormat string) (ResourcePrinter, error) {
if (f.TemplateArgument == nil || len(*f.TemplateArgument) == 0) && len(templateFormat) == 0 { if (f.TemplateArgument == nil || len(*f.TemplateArgument) == 0) && len(templateFormat) == 0 {
return nil, NoCompatiblePrinterError{f} return nil, NoCompatiblePrinterError{Options: f, OutputFormat: &templateFormat}
} }
templateValue := "" templateValue := ""
@ -66,7 +66,7 @@ func (f *JSONPathPrintFlags) ToPrinter(templateFormat string) (ResourcePrinter,
} }
if _, supportedFormat := templateFormats[templateFormat]; !supportedFormat { if _, supportedFormat := templateFormats[templateFormat]; !supportedFormat {
return nil, NoCompatiblePrinterError{f} return nil, NoCompatiblePrinterError{Options: f, OutputFormat: &templateFormat}
} }
if len(templateValue) == 0 { if len(templateValue) == 0 {

View File

@ -63,7 +63,7 @@ func (f *NamePrintFlags) ToPrinter(outputFormat string) (ResourcePrinter, error)
case "": case "":
return namePrinter, nil return namePrinter, nil
default: default:
return nil, NoCompatiblePrinterError{f} return nil, NoCompatiblePrinterError{Options: f, OutputFormat: &outputFormat}
} }
} }

View File

@ -39,7 +39,7 @@ type GoTemplatePrintFlags struct {
// Returns false if the specified templateFormat does not match a template format. // Returns false if the specified templateFormat does not match a template format.
func (f *GoTemplatePrintFlags) ToPrinter(templateFormat string) (ResourcePrinter, error) { func (f *GoTemplatePrintFlags) ToPrinter(templateFormat string) (ResourcePrinter, error) {
if (f.TemplateArgument == nil || len(*f.TemplateArgument) == 0) && len(templateFormat) == 0 { if (f.TemplateArgument == nil || len(*f.TemplateArgument) == 0) && len(templateFormat) == 0 {
return nil, NoCompatiblePrinterError{f} return nil, NoCompatiblePrinterError{Options: f, OutputFormat: &templateFormat}
} }
templateValue := "" templateValue := ""
@ -68,7 +68,7 @@ func (f *GoTemplatePrintFlags) ToPrinter(templateFormat string) (ResourcePrinter
} }
if _, supportedFormat := supportedFormats[templateFormat]; !supportedFormat { if _, supportedFormat := supportedFormats[templateFormat]; !supportedFormat {
return nil, NoCompatiblePrinterError{f} return nil, NoCompatiblePrinterError{Options: f, OutputFormat: &templateFormat}
} }
if len(templateValue) == 0 { if len(templateValue) == 0 {