diff --git a/hack/make-rules/test-cmd-util.sh b/hack/make-rules/test-cmd-util.sh index 8e7598c722..93c0b76714 100755 --- a/hack/make-rules/test-cmd-util.sh +++ b/hack/make-rules/test-cmd-util.sh @@ -1710,6 +1710,208 @@ run_kubectl_request_timeout_tests() { set +o errexit } +run_template_output_tests() { + set -o nounset + set -o errexit + + kube::log::status "Testing --template support on commands" + ### Test global request timeout option + # Pre-condition: no POD exists + create_and_use_new_namespace + kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" '' + # Command + # check that create supports --template output + kubectl create "${kube_flags[@]}" -f test/fixtures/doc-yaml/admin/limitrange/valid-pod.yaml + # Post-condition: valid-pod POD is created + kubectl get "${kube_flags[@]}" pods -o json + kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" 'valid-pod:' + + # check that patch command supports --template output + output_message=$(kubectl "${kube_flags[@]}" patch --dry-run pods/valid-pod -p '{"patched":"value3"}' --type=merge --template="{{ .metadata.name }}:") + kube::test::if_has_string "${output_message}" 'valid-pod:' + + # check that label command supports --template output + output_message=$(kubectl "${kube_flags[@]}" label --dry-run pods/valid-pod label=value --template="{{ .metadata.name }}:") + kube::test::if_has_string "${output_message}" 'valid-pod:' + + # check that annotate command supports --template output + output_message=$(kubectl "${kube_flags[@]}" annotate --dry-run pods/valid-pod annotation=value --template="{{ .metadata.name }}:") + kube::test::if_has_string "${output_message}" 'valid-pod:' + + # check that apply command supports --template output + output_message=$(kubectl "${kube_flags[@]}" apply --dry-run -f test/fixtures/doc-yaml/admin/limitrange/valid-pod.yaml --template="{{ .metadata.name }}:") + kube::test::if_has_string "${output_message}" 'valid-pod:' + + # check that create command supports --template output + output_message=$(kubectl "${kube_flags[@]}" create -f test/fixtures/doc-yaml/admin/limitrange/valid-pod.yaml --dry-run --template="{{ .metadata.name }}:") + kube::test::if_has_string "${output_message}" 'valid-pod:' + + # check that autoscale command supports --template output + output_message=$(kubectl "${kube_flags[@]}" autoscale --max=2 -f hack/testdata/scale-deploy-1.yaml --dry-run --template="{{ .metadata.name }}:") + kube::test::if_has_string "${output_message}" 'scale-1:' + + # check that expose command supports --template output + output_message=$(kubectl "${kube_flags[@]}" expose -f hack/testdata/redis-slave-replicaset.yaml --save-config --port=80 --target-port=8000 --dry-run --template="{{ .metadata.name }}:") + kube::test::if_has_string "${output_message}" 'redis-slave:' + + # check that convert command supports --template output + output_message=$(kubectl "${kube_flags[@]}" convert -f hack/testdata/deployment-revision1.yaml --output-version=apps/v1beta1 --template="{{ .metadata.name }}:") + kube::test::if_has_string "${output_message}" 'nginx:' + + # check that run command supports --template output + output_message=$(kubectl "${kube_flags[@]}" run --dry-run --template="{{ .metadata.name }}:" pi --image=perl --restart=OnFailure -- perl -Mbignum=bpi -wle 'print bpi(2000)') + kube::test::if_has_string "${output_message}" 'pi:' + + # check that taint command supports --template output + output_message=$(kubectl "${kube_flags[@]}" taint node 127.0.0.1 dedicated=foo:PreferNoSchedule --template="{{ .metadata.name }}:") + kube::test::if_has_string "${output_message}" '127.0.0.1:' + # untaint node + kubectl taint node 127.0.0.1 dedicated- + + # check that "apply set-last-applied" command supports --template output + kubectl "${kube_flags[@]}" create -f test/e2e/testing-manifests/statefulset/cassandra/controller.yaml + output_message=$(kubectl "${kube_flags[@]}" apply set-last-applied -f test/e2e/testing-manifests/statefulset/cassandra/controller.yaml --dry-run --create-annotation --template="{{ .metadata.name }}:") + kube::test::if_has_string "${output_message}" 'cassandra:' + + # check that "auth reconcile" command supports --template output + output_message=$(kubectl "${kube_flags[@]}" auth reconcile --dry-run -f test/fixtures/pkg/kubectl/cmd/auth/rbac-resource-plus.yaml --template="{{ .metadata.name }}:") + kube::test::if_has_string "${output_message}" 'testing-CR:testing-CRB:testing-RB:testing-R:' + + # check that "config view" command supports --template output + output_message=$(kubectl "${kube_flags[@]}" config view --output=go-template="{{ .kind }}:") + kube::test::if_has_string "${output_message}" 'Config' + + # check that "create clusterrole" command supports --template output + output_message=$(kubectl "${kube_flags[@]}" create clusterrole --template="{{ .metadata.name }}:" --verb get myclusterrole --non-resource-url /logs/ --resource pods) + kube::test::if_has_string "${output_message}" 'myclusterrole:' + + # check that "create clusterrolebinding" command supports --template output + output_message=$(kubectl "${kube_flags[@]}" create clusterrolebinding foo --clusterrole=myclusterrole --template="{{ .metadata.name }}:") + kube::test::if_has_string "${output_message}" 'foo:' + + # check that "create configmap" command supports --template output + output_message=$(kubectl "${kube_flags[@]}" create configmap cm --dry-run --template="{{ .metadata.name }}:") + kube::test::if_has_string "${output_message}" 'cm:' + + # check that "create deployment" command supports --template output + output_message=$(kubectl "${kube_flags[@]}" create deployment deploy --dry-run --image=nginx --template="{{ .metadata.name }}:") + kube::test::if_has_string "${output_message}" 'deploy:' + + # check that "create job" command supports --template output + kubectl create "${kube_flags[@]}" -f - < 0 && len(outputFormat) == 0 { + outputFormat = "go-template" + } if f.JSONYamlPrintFlags != nil { if p, err := f.JSONYamlPrintFlags.ToPrinter(outputFormat); !IsNoCompatiblePrinterError(err) { @@ -89,12 +98,19 @@ func (f *PrintFlags) ToPrinter() (printers.ResourcePrinter, error) { } } + if f.TemplatePrinterFlags != nil { + if p, err := f.TemplatePrinterFlags.ToPrinter(outputFormat); !IsNoCompatiblePrinterError(err) { + return f.TypeSetterPrinter.WrapToPrinter(p, err) + } + } + return nil, NoCompatiblePrinterError{OutputFormat: f.OutputFormat, AllowedFormats: f.AllowedFormats()} } func (f *PrintFlags) AddFlags(cmd *cobra.Command) { f.JSONYamlPrintFlags.AddFlags(cmd) f.NamePrintFlags.AddFlags(cmd) + f.TemplatePrinterFlags.AddFlags(cmd) if f.OutputFormat != nil { cmd.Flags().StringVarP(f.OutputFormat, "output", "o", *f.OutputFormat, fmt.Sprintf("Output format. One of: %s.", strings.Join(f.AllowedFormats(), "|"))) @@ -119,7 +135,8 @@ func NewPrintFlags(operation string) *PrintFlags { return &PrintFlags{ OutputFormat: &outputFormat, - JSONYamlPrintFlags: NewJSONYamlPrintFlags(), - NamePrintFlags: NewNamePrintFlags(operation), + JSONYamlPrintFlags: NewJSONYamlPrintFlags(), + NamePrintFlags: NewNamePrintFlags(operation), + TemplatePrinterFlags: NewKubeTemplatePrintFlags(), } } diff --git a/pkg/kubectl/genericclioptions/printers/BUILD b/pkg/kubectl/genericclioptions/printers/BUILD index 681bef193c..7893ac02b3 100644 --- a/pkg/kubectl/genericclioptions/printers/BUILD +++ b/pkg/kubectl/genericclioptions/printers/BUILD @@ -6,8 +6,10 @@ go_library( "discard.go", "interface.go", "json.go", + "jsonpath.go", "name.go", "sourcechecker.go", + "template.go", "typesetter.go", ], importpath = "k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers", @@ -17,14 +19,22 @@ go_library( "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//staging/src/k8s.io/client-go/util/jsonpath:go_default_library", "//vendor/github.com/ghodss/yaml:go_default_library", ], ) go_test( name = "go_default_test", - srcs = ["sourcechecker_test.go"], + srcs = [ + "sourcechecker_test.go", + "template_test.go", + ], embed = [":go_default_library"], + deps = [ + "//staging/src/k8s.io/api/core/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", + ], ) filegroup( diff --git a/pkg/printers/jsonpath.go b/pkg/kubectl/genericclioptions/printers/jsonpath.go similarity index 94% rename from pkg/printers/jsonpath.go rename to pkg/kubectl/genericclioptions/printers/jsonpath.go index 1e2e7b303c..0bdb3511f1 100644 --- a/pkg/printers/jsonpath.go +++ b/pkg/kubectl/genericclioptions/printers/jsonpath.go @@ -24,7 +24,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/util/jsonpath" - "k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers" ) // exists returns true if it would be possible to call the index function @@ -118,8 +117,8 @@ func (j *JSONPathPrinter) PrintObj(obj runtime.Object, w io.Writer) error { // we use reflect.Indirect here in order to obtain the actual value from a pointer. // we need an actual value in order to retrieve the package path for an object. // using reflect.Indirect indiscriminately is valid here, as all runtime.Objects are supposed to be pointers. - if printers.InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) { - return fmt.Errorf(printers.InternalObjectPrinterErr) + if InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) { + return fmt.Errorf(InternalObjectPrinterErr) } var queryObj interface{} = obj diff --git a/pkg/printers/template.go b/pkg/kubectl/genericclioptions/printers/template.go similarity index 93% rename from pkg/printers/template.go rename to pkg/kubectl/genericclioptions/printers/template.go index 678b46e3ba..5dd807dad9 100644 --- a/pkg/printers/template.go +++ b/pkg/kubectl/genericclioptions/printers/template.go @@ -25,7 +25,6 @@ import ( "text/template" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers" ) // GoTemplatePrinter is an implementation of ResourcePrinter which formats data with a Go Template. @@ -61,8 +60,8 @@ func (p *GoTemplatePrinter) AllowMissingKeys(allow bool) { // PrintObj formats the obj with the Go Template. func (p *GoTemplatePrinter) PrintObj(obj runtime.Object, w io.Writer) error { - if printers.InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) { - return fmt.Errorf(printers.InternalObjectPrinterErr) + if InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) { + return fmt.Errorf(InternalObjectPrinterErr) } var data []byte diff --git a/pkg/printers/template_test.go b/pkg/kubectl/genericclioptions/printers/template_test.go similarity index 100% rename from pkg/printers/template_test.go rename to pkg/kubectl/genericclioptions/printers/template_test.go diff --git a/pkg/printers/template_flags.go b/pkg/kubectl/genericclioptions/template_flags.go similarity index 91% rename from pkg/printers/template_flags.go rename to pkg/kubectl/genericclioptions/template_flags.go index ffd7a24dca..ac835a0643 100644 --- a/pkg/printers/template_flags.go +++ b/pkg/kubectl/genericclioptions/template_flags.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package printers +package genericclioptions import ( "fmt" @@ -23,7 +23,7 @@ import ( "github.com/spf13/cobra" - "k8s.io/kubernetes/pkg/kubectl/genericclioptions" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers" ) // templates are logically optional for specifying a format. @@ -57,9 +57,9 @@ func (f *GoTemplatePrintFlags) AllowedFormats() []string { // ToPrinter receives an templateFormat and returns a printer capable of // handling --template format printing. // 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) (printers.ResourcePrinter, error) { if (f.TemplateArgument == nil || len(*f.TemplateArgument) == 0) && len(templateFormat) == 0 { - return nil, genericclioptions.NoCompatiblePrinterError{Options: f, OutputFormat: &templateFormat} + return nil, NoCompatiblePrinterError{Options: f, OutputFormat: &templateFormat} } templateValue := "" @@ -78,7 +78,7 @@ func (f *GoTemplatePrintFlags) ToPrinter(templateFormat string) (ResourcePrinter } if _, supportedFormat := templateFormats[templateFormat]; !supportedFormat { - return nil, genericclioptions.NoCompatiblePrinterError{OutputFormat: &templateFormat, AllowedFormats: f.AllowedFormats()} + return nil, NoCompatiblePrinterError{OutputFormat: &templateFormat, AllowedFormats: f.AllowedFormats()} } if len(templateValue) == 0 { @@ -94,7 +94,7 @@ func (f *GoTemplatePrintFlags) ToPrinter(templateFormat string) (ResourcePrinter templateValue = string(data) } - p, err := NewGoTemplatePrinter([]byte(templateValue)) + p, err := printers.NewGoTemplatePrinter([]byte(templateValue)) if err != nil { return nil, fmt.Errorf("error parsing template %s, %v\n", templateValue, err) } diff --git a/pkg/printers/template_flags_test.go b/pkg/kubectl/genericclioptions/template_flags_test.go similarity index 95% rename from pkg/printers/template_flags_test.go rename to pkg/kubectl/genericclioptions/template_flags_test.go index 3a2400ca87..e1f5ae60e5 100644 --- a/pkg/printers/template_flags_test.go +++ b/pkg/kubectl/genericclioptions/template_flags_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package printers +package genericclioptions import ( "bytes" @@ -26,7 +26,6 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/kubectl/genericclioptions" ) func TestPrinterSupportsExpectedTemplateFormats(t *testing.T) { @@ -105,12 +104,12 @@ func TestPrinterSupportsExpectedTemplateFormats(t *testing.T) { p, err := printFlags.ToPrinter(tc.outputFormat) if tc.expectNoMatch { - if !genericclioptions.IsNoCompatiblePrinterError(err) { + if !IsNoCompatiblePrinterError(err) { t.Fatalf("expected no printer matches for output format %q", tc.outputFormat) } return } - if genericclioptions.IsNoCompatiblePrinterError(err) { + if IsNoCompatiblePrinterError(err) { t.Fatalf("expected to match template printer for output format %q", tc.outputFormat) } @@ -178,7 +177,7 @@ func TestTemplatePrinterDefaultsAllowMissingKeysToTrue(t *testing.T) { outputFormat := "template" p, err := printFlags.ToPrinter(outputFormat) - if genericclioptions.IsNoCompatiblePrinterError(err) { + if IsNoCompatiblePrinterError(err) { t.Fatalf("expected to match template printer for output format %q", outputFormat) } if err != nil { diff --git a/pkg/printers/BUILD b/pkg/printers/BUILD index b9478446e8..8026a11b25 100644 --- a/pkg/printers/BUILD +++ b/pkg/printers/BUILD @@ -13,12 +13,7 @@ go_library( "customcolumn_flags.go", "humanreadable.go", "interface.go", - "jsonpath.go", - "jsonpath_flags.go", - "kube_template_flags.go", "tabwriter.go", - "template.go", - "template_flags.go", ], importpath = "k8s.io/kubernetes/pkg/printers", deps = [ @@ -61,9 +56,6 @@ go_test( "customcolumn_flags_test.go", "customcolumn_test.go", "humanreadable_test.go", - "jsonpath_flags_test.go", - "template_flags_test.go", - "template_test.go", ], embed = [":go_default_library"], deps = [ diff --git a/pkg/printers/internalversion/printers_test.go b/pkg/printers/internalversion/printers_test.go index 82569ec1b2..318734d6c4 100644 --- a/pkg/printers/internalversion/printers_test.go +++ b/pkg/printers/internalversion/printers_test.go @@ -338,7 +338,7 @@ func TestUnknownTypePrinting(t *testing.T) { func TestTemplatePanic(t *testing.T) { tmpl := `{{and ((index .currentState.info "foo").state.running.startedAt) .currentState.info.net.state.running.startedAt}}` - printer, err := printers.NewGoTemplatePrinter([]byte(tmpl)) + printer, err := genericprinters.NewGoTemplatePrinter([]byte(tmpl)) if err != nil { t.Fatalf("tmpl fail: %v", err) } @@ -503,7 +503,7 @@ func TestTemplateStrings(t *testing.T) { } // The point of this test is to verify that the below template works. tmpl := `{{if (exists . "status" "containerStatuses")}}{{range .status.containerStatuses}}{{if (and (eq .name "foo") (exists . "state" "running"))}}true{{end}}{{end}}{{end}}` - printer, err := printers.NewGoTemplatePrinter([]byte(tmpl)) + printer, err := genericprinters.NewGoTemplatePrinter([]byte(tmpl)) if err != nil { t.Fatalf("tmpl fail: %v", err) } @@ -535,17 +535,17 @@ func TestPrinters(t *testing.T) { jsonpathPrinter printers.ResourcePrinter ) - templatePrinter, err = printers.NewGoTemplatePrinter([]byte("{{.name}}")) + templatePrinter, err = genericprinters.NewGoTemplatePrinter([]byte("{{.name}}")) if err != nil { t.Fatal(err) } - templatePrinter2, err = printers.NewGoTemplatePrinter([]byte("{{len .items}}")) + templatePrinter2, err = genericprinters.NewGoTemplatePrinter([]byte("{{len .items}}")) if err != nil { t.Fatal(err) } - jsonpathPrinter, err = printers.NewJSONPathPrinter("{.metadata.name}") + jsonpathPrinter, err = genericprinters.NewJSONPathPrinter("{.metadata.name}") if err != nil { t.Fatal(err) }