k8s.io/client-go@v0.31.1/restmapper/shortcut.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package restmapper 18 19 import ( 20 "fmt" 21 "strings" 22 23 "k8s.io/klog/v2" 24 25 "k8s.io/apimachinery/pkg/api/meta" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/runtime/schema" 28 "k8s.io/client-go/discovery" 29 ) 30 31 // shortcutExpander is a RESTMapper that can be used for Kubernetes resources. It expands the resource first, then invokes the wrapped 32 type shortcutExpander struct { 33 RESTMapper meta.RESTMapper 34 35 discoveryClient discovery.DiscoveryInterface 36 37 warningHandler func(string) 38 } 39 40 var _ meta.ResettableRESTMapper = shortcutExpander{} 41 42 // NewShortcutExpander wraps a restmapper in a layer that expands shortcuts found via discovery 43 func NewShortcutExpander(delegate meta.RESTMapper, client discovery.DiscoveryInterface, warningHandler func(string)) meta.RESTMapper { 44 return shortcutExpander{RESTMapper: delegate, discoveryClient: client, warningHandler: warningHandler} 45 } 46 47 // KindFor fulfills meta.RESTMapper 48 func (e shortcutExpander) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) { 49 // expandResourceShortcut works with current API resources as read from discovery cache. 50 // In case of new CRDs this means we potentially don't have current state of discovery. 51 // In the current wiring in k8s.io/cli-runtime/pkg/genericclioptions/config_flags.go#toRESTMapper, 52 // we are using DeferredDiscoveryRESTMapper which on KindFor failure will clear the 53 // cache and fetch all data from a cluster (see k8s.io/client-go/restmapper/discovery.go#KindFor). 54 // Thus another call to expandResourceShortcut, after a NoMatchError should successfully 55 // read Kind to the user or an error. 56 gvk, err := e.RESTMapper.KindFor(e.expandResourceShortcut(resource)) 57 if meta.IsNoMatchError(err) { 58 return e.RESTMapper.KindFor(e.expandResourceShortcut(resource)) 59 } 60 return gvk, err 61 } 62 63 // KindsFor fulfills meta.RESTMapper 64 func (e shortcutExpander) KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) { 65 return e.RESTMapper.KindsFor(e.expandResourceShortcut(resource)) 66 } 67 68 // ResourcesFor fulfills meta.RESTMapper 69 func (e shortcutExpander) ResourcesFor(resource schema.GroupVersionResource) ([]schema.GroupVersionResource, error) { 70 return e.RESTMapper.ResourcesFor(e.expandResourceShortcut(resource)) 71 } 72 73 // ResourceFor fulfills meta.RESTMapper 74 func (e shortcutExpander) ResourceFor(resource schema.GroupVersionResource) (schema.GroupVersionResource, error) { 75 return e.RESTMapper.ResourceFor(e.expandResourceShortcut(resource)) 76 } 77 78 // ResourceSingularizer fulfills meta.RESTMapper 79 func (e shortcutExpander) ResourceSingularizer(resource string) (string, error) { 80 return e.RESTMapper.ResourceSingularizer(e.expandResourceShortcut(schema.GroupVersionResource{Resource: resource}).Resource) 81 } 82 83 // RESTMapping fulfills meta.RESTMapper 84 func (e shortcutExpander) RESTMapping(gk schema.GroupKind, versions ...string) (*meta.RESTMapping, error) { 85 return e.RESTMapper.RESTMapping(gk, versions...) 86 } 87 88 // RESTMappings fulfills meta.RESTMapper 89 func (e shortcutExpander) RESTMappings(gk schema.GroupKind, versions ...string) ([]*meta.RESTMapping, error) { 90 return e.RESTMapper.RESTMappings(gk, versions...) 91 } 92 93 // getShortcutMappings returns a set of tuples which holds short names for resources. 94 // First the list of potential resources will be taken from the API server. 95 // Next we will append the hardcoded list of resources - to be backward compatible with old servers. 96 // NOTE that the list is ordered by group priority. 97 func (e shortcutExpander) getShortcutMappings() ([]*metav1.APIResourceList, []resourceShortcuts, error) { 98 res := []resourceShortcuts{} 99 // get server resources 100 // This can return an error *and* the results it was able to find. We don't need to fail on the error. 101 _, apiResList, err := e.discoveryClient.ServerGroupsAndResources() 102 if err != nil { 103 klog.V(1).Infof("Error loading discovery information: %v", err) 104 } 105 for _, apiResources := range apiResList { 106 gv, err := schema.ParseGroupVersion(apiResources.GroupVersion) 107 if err != nil { 108 klog.V(1).Infof("Unable to parse groupversion = %s due to = %s", apiResources.GroupVersion, err.Error()) 109 continue 110 } 111 for _, apiRes := range apiResources.APIResources { 112 for _, shortName := range apiRes.ShortNames { 113 rs := resourceShortcuts{ 114 ShortForm: schema.GroupResource{Group: gv.Group, Resource: shortName}, 115 LongForm: schema.GroupResource{Group: gv.Group, Resource: apiRes.Name}, 116 } 117 res = append(res, rs) 118 } 119 } 120 } 121 122 return apiResList, res, nil 123 } 124 125 // expandResourceShortcut will return the expanded version of resource 126 // (something that a pkg/api/meta.RESTMapper can understand), if it is 127 // indeed a shortcut. If no match has been found, we will match on group prefixing. 128 // Lastly we will return resource unmodified. 129 func (e shortcutExpander) expandResourceShortcut(resource schema.GroupVersionResource) schema.GroupVersionResource { 130 // get the shortcut mappings and return on first match. 131 if allResources, shortcutResources, err := e.getShortcutMappings(); err == nil { 132 // avoid expanding if there's an exact match to a full resource name 133 for _, apiResources := range allResources { 134 gv, err := schema.ParseGroupVersion(apiResources.GroupVersion) 135 if err != nil { 136 continue 137 } 138 if len(resource.Group) != 0 && resource.Group != gv.Group { 139 continue 140 } 141 for _, apiRes := range apiResources.APIResources { 142 if resource.Resource == apiRes.Name { 143 return resource 144 } 145 if resource.Resource == apiRes.SingularName { 146 return resource 147 } 148 } 149 } 150 151 found := false 152 var rsc schema.GroupVersionResource 153 warnedAmbiguousShortcut := make(map[schema.GroupResource]bool) 154 for _, item := range shortcutResources { 155 if len(resource.Group) != 0 && resource.Group != item.ShortForm.Group { 156 continue 157 } 158 if resource.Resource == item.ShortForm.Resource { 159 if found { 160 if item.LongForm.Group == rsc.Group && item.LongForm.Resource == rsc.Resource { 161 // It is common and acceptable that group/resource has multiple 162 // versions registered in cluster. This does not introduce ambiguity 163 // in terms of shortname usage. 164 continue 165 } 166 if !warnedAmbiguousShortcut[item.LongForm] { 167 if e.warningHandler != nil { 168 e.warningHandler(fmt.Sprintf("short name %q could also match lower priority resource %s", resource.Resource, item.LongForm.String())) 169 } 170 warnedAmbiguousShortcut[item.LongForm] = true 171 } 172 continue 173 } 174 rsc.Resource = item.LongForm.Resource 175 rsc.Group = item.LongForm.Group 176 found = true 177 } 178 } 179 if found { 180 return rsc 181 } 182 183 // we didn't find exact match so match on group prefixing. This allows autoscal to match autoscaling 184 if len(resource.Group) == 0 { 185 return resource 186 } 187 for _, item := range shortcutResources { 188 if !strings.HasPrefix(item.ShortForm.Group, resource.Group) { 189 continue 190 } 191 if resource.Resource == item.ShortForm.Resource { 192 resource.Resource = item.LongForm.Resource 193 resource.Group = item.LongForm.Group 194 return resource 195 } 196 } 197 } 198 199 return resource 200 } 201 202 func (e shortcutExpander) Reset() { 203 meta.MaybeResetRESTMapper(e.RESTMapper) 204 } 205 206 // ResourceShortcuts represents a structure that holds the information how to 207 // transition from resource's shortcut to its full name. 208 type resourceShortcuts struct { 209 ShortForm schema.GroupResource 210 LongForm schema.GroupResource 211 }