diff --git a/hack/test-cmd.sh b/hack/test-cmd.sh index cdf47c76b54..9d60fb798fe 100755 --- a/hack/test-cmd.sh +++ b/hack/test-cmd.sh @@ -728,7 +728,7 @@ __EOF__ # Command: autoscale rc "frontend" kubectl autoscale -f hack/testdata/frontend-controller.yaml --save-config "${kube_flags[@]}" --max=2 # Post-Condition: hpa "frontend" has configuration annotation - [[ "$(kubectl get hpa.extensions frontend -o yaml "${kube_flags[@]}" | grep kubectl.kubernetes.io/last-applied-configuration)" ]] + [[ "$(kubectl get hpa.v1beta1.extensions frontend -o yaml "${kube_flags[@]}" | grep kubectl.kubernetes.io/last-applied-configuration)" ]] # Ensure we can interact with HPA objects in lists through both the extensions/v1beta1 and autoscaling/v1 APIs output_message=$(kubectl get hpa -o=jsonpath='{.items[0].apiVersion}' 2>&1 "${kube_flags[@]}") kube::test::if_has_string "${output_message}" 'extensions/v1beta1' diff --git a/pkg/api/unversioned/group_version.go b/pkg/api/unversioned/group_version.go index 49155e61fbf..5d350432c3b 100644 --- a/pkg/api/unversioned/group_version.go +++ b/pkg/api/unversioned/group_version.go @@ -22,6 +22,21 @@ import ( "strings" ) +// ParseResourceArg takes the common style of string which may be either `resource.group.com` or `resource.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 GroupVersions, calling code can take a very good guess. If there are only two segments, then +// `*GroupVersionResource` is nil. +// `resource.group.com` -> `group=com, version=group, resource=resource` and `group=group.com, resource=resource` +func ParseResourceArg(arg string) (*GroupVersionResource, GroupResource) { + var gvr *GroupVersionResource + s := strings.SplitN(arg, ".", 3) + if len(s) == 3 { + gvr = &GroupVersionResource{Group: s[2], Version: s[1], Resource: s[0]} + } + + return gvr, ParseGroupResource(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 // diff --git a/pkg/kubectl/cmd/explain.go b/pkg/kubectl/cmd/explain.go index 139729e80d4..2fe12fc12da 100644 --- a/pkg/kubectl/cmd/explain.go +++ b/pkg/kubectl/cmd/explain.go @@ -79,9 +79,16 @@ func RunExplain(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []st } // TODO: We should deduce the group for a resource by discovering the supported resources at server. - gvk, err := mapper.KindFor(unversioned.ParseGroupResource(inModel).WithVersion("")) - if err != nil { - return err + fullySpecifiedGVR, groupResource := unversioned.ParseResourceArg(inModel) + gvk := unversioned.GroupVersionKind{} + if fullySpecifiedGVR != nil { + gvk, _ = mapper.KindFor(*fullySpecifiedGVR) + } + if gvk.IsEmpty() { + gvk, err = mapper.KindFor(groupResource.WithVersion("")) + if err != nil { + return err + } } if len(apiVersionString) == 0 { diff --git a/pkg/kubectl/resource/builder.go b/pkg/kubectl/resource/builder.go index 90000ad5385..7b7bc150402 100644 --- a/pkg/kubectl/resource/builder.go +++ b/pkg/kubectl/resource/builder.go @@ -433,20 +433,36 @@ 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 := unversioned.ParseResourceArg(resourceArg) + gvk := unversioned.GroupVersionKind{} + if fullySpecifiedGVR != nil { + gvk, _ = b.mapper.KindFor(*fullySpecifiedGVR) + } + if gvk.IsEmpty() { + var err error + gvk, err = b.mapper.KindFor(groupResource.WithVersion("")) + if err != nil { + return nil, err + } + } + + return b.mapper.RESTMapping(gvk.GroupKind(), gvk.Version) +} + func (b *Builder) resourceMappings() ([]*meta.RESTMapping, error) { if len(b.resources) > 1 && b.singleResourceType { return nil, fmt.Errorf("you may only specify a single resource type") } mappings := []*meta.RESTMapping{} for _, r := range b.resources { - gvk, err := b.mapper.KindFor(unversioned.ParseGroupResource(r).WithVersion("")) - if err != nil { - return nil, err - } - mapping, err := b.mapper.RESTMapping(gvk.GroupKind(), gvk.Version) + mapping, err := b.mappingFor(r) if err != nil { return nil, err } + mappings = append(mappings, mapping) } return mappings, nil @@ -459,14 +475,11 @@ func (b *Builder) resourceTupleMappings() (map[string]*meta.RESTMapping, error) if _, ok := mappings[r.Resource]; ok { continue } - gvk, err := b.mapper.KindFor(unversioned.ParseGroupResource(r.Resource).WithVersion("")) - if err != nil { - return nil, err - } - mapping, err := b.mapper.RESTMapping(gvk.GroupKind(), gvk.Version) + mapping, err := b.mappingFor(r.Resource) if err != nil { return nil, err } + mappings[mapping.Resource] = mapping mappings[r.Resource] = mapping canonical[mapping.Resource] = struct{}{}