diff --git a/hack/make-rules/test-cmd-util.sh b/hack/make-rules/test-cmd-util.sh index 0734ec051b3..896585eb488 100755 --- a/hack/make-rules/test-cmd-util.sh +++ b/hack/make-rules/test-cmd-util.sh @@ -1460,7 +1460,7 @@ run_kubectl_get_tests() { kube::test::if_has_string "${output_message}" 'valid-pod' # pod details output_message=$(kubectl get pods/valid-pod -o name -w --request-timeout=1 "${kube_flags[@]}") kube::test::if_has_not_string "${output_message}" 'STATUS' # no headers - kube::test::if_has_string "${output_message}" 'pods/valid-pod' # resource name + kube::test::if_has_string "${output_message}" 'pod/valid-pod' # resource name output_message=$(kubectl get pods/valid-pod -o yaml -w --request-timeout=1 "${kube_flags[@]}") kube::test::if_has_not_string "${output_message}" 'STATUS' # no headers kube::test::if_has_string "${output_message}" 'name: valid-pod' # yaml @@ -1574,6 +1574,33 @@ __EOF__ # Post-Condition: assertion object exist kube::test::get_object_assert customresourcedefinitions "{{range.items}}{{$id_field}}:{{end}}" 'bars.company.com:foos.company.com:' + # This test ensures that the name printer is able to output a resource + # in the proper "kind.group/resource_name" format, and that the + # resource builder is able to resolve a GVK when a kind.group pair is given. + kubectl "${kube_flags_with_token[@]}" create -f - << __EOF__ +{ + "kind": "CustomResourceDefinition", + "apiVersion": "apiextensions.k8s.io/v1beta1", + "metadata": { + "name": "resources.mygroup.example.com" + }, + "spec": { + "group": "mygroup.example.com", + "version": "v1alpha1", + "scope": "Namespaced", + "names": { + "plural": "resources", + "singular": "resource", + "kind": "Kind", + "listKind": "KindList" + } + } +} +__EOF__ + + # Post-Condition: assertion crd with non-matching kind and resource exists + kube::test::get_object_assert customresourcedefinitions "{{range.items}}{{$id_field}}:{{end}}" 'bars.company.com:foos.company.com:resources.mygroup.example.com:' + run_non_native_resource_tests # teardown @@ -1621,6 +1648,28 @@ run_non_native_resource_tests() { # Test that we can list this new CustomResource (bars) kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" '' + # Test that we can list this new CustomResource (resources) + kube::test::get_object_assert resources "{{range.items}}{{$id_field}}:{{end}}" '' + + # Test that we can create a new resource of type Kind + kubectl "${kube_flags[@]}" create -f hack/testdata/CRD/resource.yaml "${kube_flags[@]}" + + # Test that -o name returns kind.group/resourcename + output_message=$(kubectl "${kube_flags[@]}" get resource/myobj -o name) + kube::test::if_has_string "${output_message}" 'kind.mygroup.example.com/myobj' + + output_message=$(kubectl "${kube_flags[@]}" get resources/myobj -o name) + kube::test::if_has_string "${output_message}" 'kind.mygroup.example.com/myobj' + + output_message=$(kubectl "${kube_flags[@]}" get kind.mygroup.example.com/myobj -o name) + kube::test::if_has_string "${output_message}" 'kind.mygroup.example.com/myobj' + + # Delete the resource with cascade. + kubectl "${kube_flags[@]}" delete resources myobj --cascade=true + + # Make sure it's gone + kube::test::get_object_assert resources "{{range.items}}{{$id_field}}:{{end}}" '' + # Test that we can create a new resource of type Foo kubectl "${kube_flags[@]}" create -f hack/testdata/CRD/foo.yaml "${kube_flags[@]}" @@ -1649,7 +1698,7 @@ run_non_native_resource_tests() { kubectl "${kube_flags[@]}" get foos -o "go-template={{range .items}}{{.someField}}{{end}}" --allow-missing-template-keys=false kubectl "${kube_flags[@]}" get foos/test -o "go-template={{.someField}}" --allow-missing-template-keys=false output_message=$(kubectl "${kube_flags[@]}" get foos/test -o name) - kube::test::if_has_string "${output_message}" 'foos/test' + kube::test::if_has_string "${output_message}" 'foo.company.com/test' # Test patching kube::log::status "Testing CustomResource patching" @@ -1732,7 +1781,7 @@ run_non_native_resource_tests() { # Stop the watcher and the patch loop. kill -9 ${watch_pid} kill -9 ${patch_pid} - kube::test::if_has_string "${watch_output}" 'bars/test' + kube::test::if_has_string "${watch_output}" 'bar.company.com/test' # Delete the resource without cascade. kubectl "${kube_flags[@]}" delete bars test --cascade=false diff --git a/hack/testdata/CRD/resource.yaml b/hack/testdata/CRD/resource.yaml new file mode 100644 index 00000000000..938665265dc --- /dev/null +++ b/hack/testdata/CRD/resource.yaml @@ -0,0 +1,6 @@ +apiVersion: mygroup.example.com/v1alpha1 +kind: Kind +metadata: + name: myobj +spec: + key: value \ No newline at end of file diff --git a/pkg/kubectl/cmd/label.go b/pkg/kubectl/cmd/label.go index cae6b0f032a..03dc331aa90 100644 --- a/pkg/kubectl/cmd/label.go +++ b/pkg/kubectl/cmd/label.go @@ -287,7 +287,7 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error { return nil } - if o.outputFormat != "" { + if len(o.outputFormat) > 0 { return f.PrintObject(cmd, o.local, r.Mapper().RESTMapper, outputObj, o.out) } f.PrintSuccess(false, o.out, info.Mapping.Resource, info.Name, o.dryrun, dataChangeMsg) diff --git a/pkg/kubectl/cmd/set/set_env_test.go b/pkg/kubectl/cmd/set/set_env_test.go index 14594d039e0..2004fdf84c3 100644 --- a/pkg/kubectl/cmd/set/set_env_test.go +++ b/pkg/kubectl/cmd/set/set_env_test.go @@ -64,8 +64,8 @@ func TestSetEnvLocal(t *testing.T) { cmd.SetOutput(buf) cmd.Flags().Set("output", "name") cmd.Flags().Set("local", "true") - mapper, typer := f.Object() - tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{codec}, Typer: typer, Mapper: mapper} + _, typer := f.Object() + tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{codec}, Typer: typer} opts := EnvOptions{FilenameOptions: resource.FilenameOptions{ Filenames: []string{"../../../../examples/storage/cassandra/cassandra-controller.yaml"}}, @@ -78,7 +78,7 @@ func TestSetEnvLocal(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %v", err) } - if !strings.Contains(buf.String(), "replicationcontrollers/cassandra") { + if !strings.Contains(buf.String(), "replicationcontroller/cassandra") { t.Errorf("did not set env: %s", buf.String()) } } @@ -101,8 +101,8 @@ func TestSetMultiResourcesEnvLocal(t *testing.T) { cmd.SetOutput(buf) cmd.Flags().Set("output", "name") cmd.Flags().Set("local", "true") - mapper, typer := f.Object() - tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{codec}, Typer: typer, Mapper: mapper} + _, typer := f.Object() + tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{codec}, Typer: typer} opts := EnvOptions{FilenameOptions: resource.FilenameOptions{ Filenames: []string{"../../../../test/fixtures/pkg/kubectl/cmd/set/multi-resource-yaml.yaml"}}, @@ -116,7 +116,7 @@ func TestSetMultiResourcesEnvLocal(t *testing.T) { t.Fatalf("unexpected error: %v", err) } - expectedOut := "replicationcontrollers/first-rc\nreplicationcontrollers/second-rc\n" + expectedOut := "replicationcontroller/first-rc\nreplicationcontroller/second-rc\n" if buf.String() != expectedOut { t.Errorf("expected out:\n%s\nbut got:\n%s", expectedOut, buf.String()) } diff --git a/pkg/kubectl/cmd/set/set_image_test.go b/pkg/kubectl/cmd/set/set_image_test.go index ca27bbcdd9c..315f2b98381 100644 --- a/pkg/kubectl/cmd/set/set_image_test.go +++ b/pkg/kubectl/cmd/set/set_image_test.go @@ -63,8 +63,8 @@ func TestImageLocal(t *testing.T) { cmd.SetOutput(buf) cmd.Flags().Set("output", "name") cmd.Flags().Set("local", "true") - mapper, typer := f.Object() - tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{codec}, Typer: typer, Mapper: mapper} + _, typer := f.Object() + tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{codec}, Typer: typer} opts := ImageOptions{FilenameOptions: resource.FilenameOptions{ Filenames: []string{"../../../../examples/storage/cassandra/cassandra-controller.yaml"}}, @@ -80,7 +80,7 @@ func TestImageLocal(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %v", err) } - if !strings.Contains(buf.String(), "replicationcontrollers/cassandra") { + if !strings.Contains(buf.String(), "replicationcontroller/cassandra") { t.Errorf("did not set image: %s", buf.String()) } } @@ -166,8 +166,8 @@ func TestSetMultiResourcesImageLocal(t *testing.T) { cmd.SetOutput(buf) cmd.Flags().Set("output", "name") cmd.Flags().Set("local", "true") - mapper, typer := f.Object() - tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{codec}, Typer: typer, Mapper: mapper} + _, typer := f.Object() + tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{codec}, Typer: typer} opts := ImageOptions{FilenameOptions: resource.FilenameOptions{ Filenames: []string{"../../../../test/fixtures/pkg/kubectl/cmd/set/multi-resource-yaml.yaml"}}, @@ -183,7 +183,7 @@ func TestSetMultiResourcesImageLocal(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %v", err) } - expectedOut := "replicationcontrollers/first-rc\nreplicationcontrollers/second-rc\n" + expectedOut := "replicationcontroller/first-rc\nreplicationcontroller/second-rc\n" if buf.String() != expectedOut { t.Errorf("expected out:\n%s\nbut got:\n%s", expectedOut, buf.String()) } diff --git a/pkg/kubectl/cmd/set/set_resources_test.go b/pkg/kubectl/cmd/set/set_resources_test.go index e423f8c708e..ca0da754a92 100644 --- a/pkg/kubectl/cmd/set/set_resources_test.go +++ b/pkg/kubectl/cmd/set/set_resources_test.go @@ -63,8 +63,8 @@ func TestResourcesLocal(t *testing.T) { cmd.SetOutput(buf) cmd.Flags().Set("output", "name") cmd.Flags().Set("local", "true") - mapper, typer := f.Object() - tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{codec}, Typer: typer, Mapper: mapper} + _, typer := f.Object() + tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{codec}, Typer: typer} opts := ResourcesOptions{FilenameOptions: resource.FilenameOptions{ Filenames: []string{"../../../../examples/storage/cassandra/cassandra-controller.yaml"}}, @@ -84,7 +84,7 @@ func TestResourcesLocal(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %v", err) } - if !strings.Contains(buf.String(), "replicationcontrollers/cassandra") { + if !strings.Contains(buf.String(), "replicationcontroller/cassandra") { t.Errorf("did not set resources: %s", buf.String()) } } @@ -107,8 +107,8 @@ func TestSetMultiResourcesLimitsLocal(t *testing.T) { cmd.SetOutput(buf) cmd.Flags().Set("output", "name") cmd.Flags().Set("local", "true") - mapper, typer := f.Object() - tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{codec}, Typer: typer, Mapper: mapper} + _, typer := f.Object() + tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{codec}, Typer: typer} opts := ResourcesOptions{FilenameOptions: resource.FilenameOptions{ Filenames: []string{"../../../../test/fixtures/pkg/kubectl/cmd/set/multi-resource-yaml.yaml"}}, @@ -128,7 +128,7 @@ func TestSetMultiResourcesLimitsLocal(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %v", err) } - expectedOut := "replicationcontrollers/first-rc\nreplicationcontrollers/second-rc\n" + expectedOut := "replicationcontroller/first-rc\nreplicationcontroller/second-rc\n" if buf.String() != expectedOut { t.Errorf("expected out:\n%s\nbut got:\n%s", expectedOut, buf.String()) } @@ -448,8 +448,8 @@ func TestSetResourcesRemote(t *testing.T) { testapi.Default = testapi.Groups[input.testAPIGroup] f, tf, _, ns := cmdtesting.NewAPIFactory() codec := scheme.Codecs.CodecForVersions(scheme.Codecs.LegacyCodec(groupVersion), scheme.Codecs.UniversalDecoder(groupVersion), groupVersion, groupVersion) - mapper, typer := f.Object() - tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{testapi.Default.Codec()}, Typer: typer, Mapper: mapper} + _, typer := f.Object() + tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{testapi.Default.Codec()}, Typer: typer} tf.Namespace = "test" tf.CategoryExpander = categories.LegacyCategoryExpander tf.Client = &fake.RESTClient{ diff --git a/pkg/kubectl/cmd/set/set_selector_test.go b/pkg/kubectl/cmd/set/set_selector_test.go index 901b9564991..c325c256792 100644 --- a/pkg/kubectl/cmd/set/set_selector_test.go +++ b/pkg/kubectl/cmd/set/set_selector_test.go @@ -335,11 +335,11 @@ func TestSelectorTest(t *testing.T) { cmd.Flags().Set("local", "true") cmd.Flags().Set("filename", "../../../../examples/storage/cassandra/cassandra-service.yaml") - mapper, typer := f.Object() - tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{codec}, Typer: typer, Mapper: mapper} + _, typer := f.Object() + tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{codec}, Typer: typer} cmd.Run(cmd, []string{"environment=qa"}) - if !strings.Contains(buf.String(), "services/cassandra") { + if !strings.Contains(buf.String(), "service/cassandra") { t.Errorf("did not set selector: %s", buf.String()) } } diff --git a/pkg/kubectl/cmd/set/set_serviceaccount_test.go b/pkg/kubectl/cmd/set/set_serviceaccount_test.go index 713ba1b5b3e..eacce264da0 100644 --- a/pkg/kubectl/cmd/set/set_serviceaccount_test.go +++ b/pkg/kubectl/cmd/set/set_serviceaccount_test.go @@ -116,8 +116,8 @@ func TestSetServiceAccountMultiLocal(t *testing.T) { cmd.SetOutput(buf) cmd.Flags().Set("output", "name") cmd.Flags().Set("local", "true") - mapper, typer := f.Object() - tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{codec}, Typer: typer, Mapper: mapper} + _, typer := f.Object() + tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{codec}, Typer: typer} opts := serviceAccountConfig{fileNameOptions: resource.FilenameOptions{ Filenames: []string{"../../../../test/fixtures/pkg/kubectl/cmd/set/multi-resource-yaml.yaml"}}, out: buf, @@ -130,7 +130,7 @@ func TestSetServiceAccountMultiLocal(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %v", err) } - expectedOut := "replicationcontrollers/first-rc\nreplicationcontrollers/second-rc\n" + expectedOut := "replicationcontroller/first-rc\nreplicationcontroller/second-rc\n" if buf.String() != expectedOut { t.Errorf("expected out:\n%s\nbut got:\n%s", expectedOut, buf.String()) } diff --git a/pkg/kubectl/cmd/util/factory_builder.go b/pkg/kubectl/cmd/util/factory_builder.go index 839a58fd3a0..d772772c833 100644 --- a/pkg/kubectl/cmd/util/factory_builder.go +++ b/pkg/kubectl/cmd/util/factory_builder.go @@ -48,15 +48,12 @@ func NewBuilderFactory(clientAccessFactory ClientAccessFactory, objectMappingFac } func (f *ring2Factory) PrinterForOptions(options *printers.PrintOptions) (printers.ResourcePrinter, error) { - var mapper meta.RESTMapper - var typer runtime.ObjectTyper - - mapper, typer = f.objectMappingFactory.Object() + _, typer := f.objectMappingFactory.Object() // TODO: used by the custom column implementation and the name implementation, break this dependency decoders := []runtime.Decoder{f.clientAccessFactory.Decoder(true), unstructured.UnstructuredJSONScheme} encoder := f.clientAccessFactory.JSONEncoder() - return printerForOptions(mapper, typer, encoder, decoders, options) + return printerForOptions(typer, encoder, decoders, options) } func (f *ring2Factory) PrinterForMapping(options *printers.PrintOptions, mapping *meta.RESTMapping) (printers.ResourcePrinter, error) { diff --git a/pkg/kubectl/cmd/util/printing.go b/pkg/kubectl/cmd/util/printing.go index c679dde5e8c..2939e0e74ae 100644 --- a/pkg/kubectl/cmd/util/printing.go +++ b/pkg/kubectl/cmd/util/printing.go @@ -20,7 +20,6 @@ import ( "fmt" "strings" - "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" @@ -84,8 +83,8 @@ func ValidateOutputArgs(cmd *cobra.Command) error { // printerForOptions returns the printer for the outputOptions (if given) or // returns the default printer for the command. Requires that printer flags have // been added to cmd (see AddPrinterFlags). -func printerForOptions(mapper meta.RESTMapper, typer runtime.ObjectTyper, encoder runtime.Encoder, decoders []runtime.Decoder, options *printers.PrintOptions) (printers.ResourcePrinter, error) { - printer, err := printers.GetStandardPrinter(mapper, typer, encoder, decoders, *options) +func printerForOptions(typer runtime.ObjectTyper, encoder runtime.Encoder, decoders []runtime.Decoder, options *printers.PrintOptions) (printers.ResourcePrinter, error) { + printer, err := printers.GetStandardPrinter(typer, encoder, decoders, *options) if err != nil { return nil, err } diff --git a/pkg/kubectl/resource/builder.go b/pkg/kubectl/resource/builder.go index 2194ba4825c..b2385d7d271 100644 --- a/pkg/kubectl/resource/builder.go +++ b/pkg/kubectl/resource/builder.go @@ -598,10 +598,27 @@ func (b *Builder) SingleResourceType() *Builder { return b } -// mappingFor returns the RESTMapping for the Kind referenced by the resource. -// prefers a fully specified GroupVersionResource match. If we don't have one match on GroupResource -func (b *Builder) mappingFor(resourceArg string) (*meta.RESTMapping, error) { - fullySpecifiedGVR, groupResource := schema.ParseResourceArg(resourceArg) +// mappingFor returns the RESTMapping for the Kind given, or the Kind referenced by the resource. +// prefers a fully specified GroupVersionKind match. If we don't have one, match on a fully specified +// GroupVersionResource, or fallback to a match on GroupResource. +func (b *Builder) mappingFor(resourceOrKindArg string) (*meta.RESTMapping, error) { + fullySpecifiedGVK, groupKind := schema.ParseKindArg(resourceOrKindArg) + if fullySpecifiedGVK == nil { + gvk := groupKind.WithVersion("") + fullySpecifiedGVK = &gvk + } + + if !fullySpecifiedGVK.Empty() { + if mapping, err := b.mapper.RESTMapping(fullySpecifiedGVK.GroupKind(), fullySpecifiedGVK.Version); err == nil { + return mapping, nil + } else { + if mapping, err := b.mapper.RESTMapping(groupKind, ""); err == nil { + return mapping, nil + } + } + } + + fullySpecifiedGVR, groupResource := schema.ParseResourceArg(resourceOrKindArg) gvk := schema.GroupVersionKind{} if fullySpecifiedGVR != nil { gvk, _ = b.mapper.KindFor(*fullySpecifiedGVR) diff --git a/pkg/printers/BUILD b/pkg/printers/BUILD index 9d2b02f880a..953effc6404 100644 --- a/pkg/printers/BUILD +++ b/pkg/printers/BUILD @@ -26,6 +26,7 @@ go_library( "//vendor/github.com/ghodss/yaml:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/pkg/printers/internalversion/printers_test.go b/pkg/printers/internalversion/printers_test.go index 4357d774b69..531ee5849b7 100644 --- a/pkg/printers/internalversion/printers_test.go +++ b/pkg/printers/internalversion/printers_test.go @@ -108,7 +108,7 @@ func TestPrintDefault(t *testing.T) { } for _, test := range printerTests { - printer, err := printers.GetStandardPrinter(nil, nil, legacyscheme.Codecs.LegacyCodec(legacyscheme.Registry.EnabledVersions()...), []runtime.Decoder{legacyscheme.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, printers.PrintOptions{AllowMissingKeys: false}) + printer, err := printers.GetStandardPrinter(nil, legacyscheme.Codecs.LegacyCodec(legacyscheme.Registry.EnabledVersions()...), []runtime.Decoder{legacyscheme.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, printers.PrintOptions{AllowMissingKeys: false}) if err != nil { t.Errorf("in %s, unexpected error: %#v", test.Name, err) } @@ -278,12 +278,12 @@ func TestPrinter(t *testing.T) { {"test jsonpath", &printers.PrintOptions{OutputFormatType: "jsonpath", OutputFormatArgument: "{.metadata.name}", AllowMissingKeys: true}, podTest, []schema.GroupVersion{v1.SchemeGroupVersion}, "foo"}, {"test jsonpath list", &printers.PrintOptions{OutputFormatType: "jsonpath", OutputFormatArgument: "{.items[*].metadata.name}", AllowMissingKeys: true}, podListTest, []schema.GroupVersion{v1.SchemeGroupVersion}, "foo bar"}, {"test jsonpath empty list", &printers.PrintOptions{OutputFormatType: "jsonpath", OutputFormatArgument: "{.items[*].metadata.name}", AllowMissingKeys: true}, emptyListTest, []schema.GroupVersion{v1.SchemeGroupVersion}, ""}, - {"test name", &printers.PrintOptions{OutputFormatType: "name", AllowMissingKeys: true}, podTest, []schema.GroupVersion{v1.SchemeGroupVersion}, "pods/foo\n"}, + {"test name", &printers.PrintOptions{OutputFormatType: "name", AllowMissingKeys: true}, podTest, []schema.GroupVersion{v1.SchemeGroupVersion}, "pod/foo\n"}, {"emits versioned objects", &printers.PrintOptions{OutputFormatType: "template", OutputFormatArgument: "{{.kind}}", AllowMissingKeys: true}, testapi, []schema.GroupVersion{v1.SchemeGroupVersion}, "Pod"}, } for _, test := range printerTests { buf := bytes.NewBuffer([]byte{}) - printer, err := printers.GetStandardPrinter(legacyscheme.Registry.RESTMapper(legacyscheme.Registry.EnabledVersions()...), legacyscheme.Scheme, legacyscheme.Codecs.LegacyCodec(legacyscheme.Registry.EnabledVersions()...), []runtime.Decoder{legacyscheme.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, *test.PrintOpts) + printer, err := printers.GetStandardPrinter(legacyscheme.Scheme, legacyscheme.Codecs.LegacyCodec(legacyscheme.Registry.EnabledVersions()...), []runtime.Decoder{legacyscheme.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, *test.PrintOpts) if err != nil { t.Errorf("in %s, unexpected error: %#v", test.Name, err) } @@ -313,7 +313,7 @@ func TestBadPrinter(t *testing.T) { {"unknown format", &printers.PrintOptions{OutputFormatType: "anUnknownFormat", OutputFormatArgument: "", AllowMissingKeys: false}, fmt.Errorf("output format \"anUnknownFormat\" not recognized")}, } for _, test := range badPrinterTests { - _, err := printers.GetStandardPrinter(legacyscheme.Registry.RESTMapper(legacyscheme.Registry.EnabledVersions()...), legacyscheme.Scheme, legacyscheme.Codecs.LegacyCodec(legacyscheme.Registry.EnabledVersions()...), []runtime.Decoder{legacyscheme.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, *test.PrintOpts) + _, err := printers.GetStandardPrinter(legacyscheme.Scheme, legacyscheme.Codecs.LegacyCodec(legacyscheme.Registry.EnabledVersions()...), []runtime.Decoder{legacyscheme.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, *test.PrintOpts) if err == nil || err.Error() != test.Error.Error() { t.Errorf("in %s, expect %s, got %s", test.Name, test.Error, err) } @@ -489,7 +489,7 @@ func TestNamePrinter(t *testing.T) { Name: "foo", }, }, - "pods/foo\n"}, + "pod/foo\n"}, "List": { &v1.List{ TypeMeta: metav1.TypeMeta{ @@ -504,10 +504,10 @@ func TestNamePrinter(t *testing.T) { }, }, }, - "pods/foo\npods/bar\n"}, + "pod/foo\npod/bar\n"}, } printOpts := &printers.PrintOptions{OutputFormatType: "name", AllowMissingKeys: false} - printer, _ := printers.GetStandardPrinter(legacyscheme.Registry.RESTMapper(legacyscheme.Registry.EnabledVersions()...), legacyscheme.Scheme, legacyscheme.Codecs.LegacyCodec(legacyscheme.Registry.EnabledVersions()...), []runtime.Decoder{legacyscheme.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, *printOpts) + printer, _ := printers.GetStandardPrinter(legacyscheme.Scheme, legacyscheme.Codecs.LegacyCodec(legacyscheme.Registry.EnabledVersions()...), []runtime.Decoder{legacyscheme.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, *printOpts) for name, item := range tests { buff := &bytes.Buffer{} err := printer.PrintObj(item.obj, buff) @@ -682,7 +682,6 @@ func TestPrinters(t *testing.T) { "name": &printers.NamePrinter{ Typer: legacyscheme.Scheme, Decoders: []runtime.Decoder{legacyscheme.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, - Mapper: legacyscheme.Registry.RESTMapper(legacyscheme.Registry.EnabledVersions()...), }, } AddHandlers((allPrinters["humanReadable"]).(*printers.HumanReadablePrinter)) @@ -2819,7 +2818,7 @@ func TestAllowMissingKeys(t *testing.T) { } for _, test := range tests { buf := bytes.NewBuffer([]byte{}) - printer, err := printers.GetStandardPrinter(legacyscheme.Registry.RESTMapper(legacyscheme.Registry.EnabledVersions()...), legacyscheme.Scheme, legacyscheme.Codecs.LegacyCodec(legacyscheme.Registry.EnabledVersions()...), []runtime.Decoder{legacyscheme.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, *test.PrintOpts) + printer, err := printers.GetStandardPrinter(legacyscheme.Scheme, legacyscheme.Codecs.LegacyCodec(legacyscheme.Registry.EnabledVersions()...), []runtime.Decoder{legacyscheme.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, *test.PrintOpts) if err != nil { t.Errorf("in %s, unexpected error: %#v", test.Name, err) } diff --git a/pkg/printers/name.go b/pkg/printers/name.go index c720ca583d4..99c9618808d 100644 --- a/pkg/printers/name.go +++ b/pkg/printers/name.go @@ -19,8 +19,10 @@ package printers import ( "fmt" "io" + "strings" "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" utilerrors "k8s.io/apimachinery/pkg/util/errors" ) @@ -29,7 +31,6 @@ import ( type NamePrinter struct { Decoders []runtime.Decoder Typer runtime.ObjectTyper - Mapper meta.RESTMapper } func (p *NamePrinter) AfterPrint(w io.Writer, res string) error { @@ -64,18 +65,27 @@ func (p *NamePrinter) PrintObj(obj runtime.Object, w io.Writer) error { groupVersionKind := obj.GetObjectKind().GroupVersionKind() if len(groupVersionKind.Kind) > 0 { - if mappings, err := p.Mapper.RESTMappings(groupVersionKind.GroupKind(), groupVersionKind.Version); err == nil && len(mappings) > 0 { - fmt.Fprintf(w, "%s/%s\n", mappings[0].Resource, name) - return nil - } + kind := groupVersionKind.Kind + group := groupVersionKind.Group + return printObj(w, name, group, kind) } if gvks, _, err := p.Typer.ObjectKinds(obj); err == nil { for _, gvk := range gvks { - if mappings, err := p.Mapper.RESTMappings(gvk.GroupKind(), gvk.Version); err == nil && len(mappings) > 0 { - fmt.Fprintf(w, "%s/%s\n", mappings[0].Resource, name) - return nil + if len(gvk.Kind) == 0 { + continue } + + return printObj(w, name, gvk.Group, gvk.Kind) + } + } + + if uns, ok := obj.(*unstructured.Unstructured); ok { + group := uns.GroupVersionKind().Group + kind := uns.GroupVersionKind().Kind + + if len(kind) > 0 { + return printObj(w, name, group, kind) } } @@ -83,6 +93,20 @@ func (p *NamePrinter) PrintObj(obj runtime.Object, w io.Writer) error { return nil } +func printObj(w io.Writer, name, group, kind string) error { + if len(kind) == 0 { + return fmt.Errorf("missing kind for resource with name %v", name) + } + + if len(group) == 0 { + fmt.Fprintf(w, "%s/%s\n", strings.ToLower(kind), name) + return nil + } + + fmt.Fprintf(w, "%s.%s/%s\n", strings.ToLower(kind), group, name) + return nil +} + // TODO: implement HandledResources() func (p *NamePrinter) HandledResources() []string { return []string{} diff --git a/pkg/printers/printers.go b/pkg/printers/printers.go index 3ef696ed9b8..357faf498a4 100644 --- a/pkg/printers/printers.go +++ b/pkg/printers/printers.go @@ -21,7 +21,6 @@ import ( "io/ioutil" "os" - "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" ) @@ -29,7 +28,7 @@ import ( // a printer or an error. The printer is agnostic to schema versions, so you must // send arguments to PrintObj in the version you wish them to be shown using a // VersionedPrinter (typically when generic is true). -func GetStandardPrinter(mapper meta.RESTMapper, typer runtime.ObjectTyper, encoder runtime.Encoder, decoders []runtime.Decoder, options PrintOptions) (ResourcePrinter, error) { +func GetStandardPrinter(typer runtime.ObjectTyper, encoder runtime.Encoder, decoders []runtime.Decoder, options PrintOptions) (ResourcePrinter, error) { format, formatArgument, allowMissingTemplateKeys := options.OutputFormatType, options.OutputFormatArgument, options.AllowMissingKeys var printer ResourcePrinter @@ -45,7 +44,6 @@ func GetStandardPrinter(mapper meta.RESTMapper, typer runtime.ObjectTyper, encod printer = &NamePrinter{ Typer: typer, Decoders: decoders, - Mapper: mapper, } case "template", "go-template": diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/schema/group_version.go b/staging/src/k8s.io/apimachinery/pkg/runtime/schema/group_version.go index 1a9bba10603..da642fa73f1 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/schema/group_version.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/schema/group_version.go @@ -36,6 +36,21 @@ func ParseResourceArg(arg string) (*GroupVersionResource, GroupResource) { return gvr, ParseGroupResource(arg) } +// ParseKindArg takes the common style of string which may be either `Kind.group.com` or `Kind.version.group.com` +// and parses it out into both possibilities. This code takes no responsibility for knowing which representation was intended +// but with a knowledge of all GroupKinds, calling code can take a very good guess. If there are only two segments, then +// `*GroupVersionResource` is nil. +// `Kind.group.com` -> `group=com, version=group, kind=Kind` and `group=group.com, kind=Kind` +func ParseKindArg(arg string) (*GroupVersionKind, GroupKind) { + var gvk *GroupVersionKind + if strings.Count(arg, ".") >= 2 { + s := strings.SplitN(arg, ".", 3) + gvk = &GroupVersionKind{Group: s[2], Version: s[1], Kind: s[0]} + } + + return gvk, ParseGroupKind(arg) +} + // GroupResource specifies a Group and a Resource, but does not force a version. This is useful for identifying // concepts during lookup stages without having partially valid types type GroupResource struct { @@ -58,6 +73,15 @@ func (gr *GroupResource) String() string { return gr.Resource + "." + gr.Group } +func ParseGroupKind(gk string) GroupKind { + i := strings.Index(gk, ".") + if i == -1 { + return GroupKind{Kind: gk} + } + + return GroupKind{Group: gk[i+1:], Kind: gk[:i]} +} + // ParseGroupResource turns "resource.group" string into a GroupResource struct. Empty strings are allowed // for each field. func ParseGroupResource(gr string) GroupResource { diff --git a/staging/src/k8s.io/client-go/discovery/restmapper.go b/staging/src/k8s.io/client-go/discovery/restmapper.go index 6d1de8c1b19..df5ab0358a6 100644 --- a/staging/src/k8s.io/client-go/discovery/restmapper.go +++ b/staging/src/k8s.io/client-go/discovery/restmapper.go @@ -18,6 +18,7 @@ package discovery import ( "fmt" + "strings" "sync" "k8s.io/apimachinery/pkg/api/meta" @@ -108,6 +109,7 @@ func NewRESTMapper(groupResources []*APIGroupResources, versionInterfaces meta.V plural := gv.WithResource(resource.Name) singular := gv.WithResource(resource.SingularName) versionMapper.AddSpecific(gv.WithKind(resource.Kind), plural, singular, scope) + versionMapper.AddSpecific(gv.WithKind(strings.ToLower(resource.Kind)), plural, singular, scope) // TODO this is producing unsafe guesses that don't actually work, but it matches previous behavior versionMapper.Add(gv.WithKind(resource.Kind+"List"), scope) }