k8s.io/client-go@v0.31.1/discovery/aggregated_discovery.go (about) 1 /* 2 Copyright 2022 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 discovery 18 19 import ( 20 "fmt" 21 22 apidiscovery "k8s.io/api/apidiscovery/v2" 23 apidiscoveryv2beta1 "k8s.io/api/apidiscovery/v2beta1" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "k8s.io/apimachinery/pkg/runtime/schema" 26 ) 27 28 // StaleGroupVersionError encasulates failed GroupVersion marked "stale" 29 // in the returned AggregatedDiscovery format. 30 type StaleGroupVersionError struct { 31 gv schema.GroupVersion 32 } 33 34 func (s StaleGroupVersionError) Error() string { 35 return fmt.Sprintf("stale GroupVersion discovery: %v", s.gv) 36 } 37 38 // SplitGroupsAndResources transforms "aggregated" discovery top-level structure into 39 // the previous "unaggregated" discovery groups and resources. 40 func SplitGroupsAndResources(aggregatedGroups apidiscovery.APIGroupDiscoveryList) ( 41 *metav1.APIGroupList, 42 map[schema.GroupVersion]*metav1.APIResourceList, 43 map[schema.GroupVersion]error) { 44 // Aggregated group list will contain the entirety of discovery, including 45 // groups, versions, and resources. GroupVersions marked "stale" are failed. 46 groups := []*metav1.APIGroup{} 47 failedGVs := map[schema.GroupVersion]error{} 48 resourcesByGV := map[schema.GroupVersion]*metav1.APIResourceList{} 49 for _, aggGroup := range aggregatedGroups.Items { 50 group, resources, failed := convertAPIGroup(aggGroup) 51 groups = append(groups, group) 52 for gv, resourceList := range resources { 53 resourcesByGV[gv] = resourceList 54 } 55 for gv, err := range failed { 56 failedGVs[gv] = err 57 } 58 } 59 // Transform slice of groups to group list before returning. 60 groupList := &metav1.APIGroupList{} 61 groupList.Groups = make([]metav1.APIGroup, 0, len(groups)) 62 for _, group := range groups { 63 groupList.Groups = append(groupList.Groups, *group) 64 } 65 return groupList, resourcesByGV, failedGVs 66 } 67 68 // convertAPIGroup tranforms an "aggregated" APIGroupDiscovery to an "legacy" APIGroup, 69 // also returning the map of APIResourceList for resources within GroupVersions. 70 func convertAPIGroup(g apidiscovery.APIGroupDiscovery) ( 71 *metav1.APIGroup, 72 map[schema.GroupVersion]*metav1.APIResourceList, 73 map[schema.GroupVersion]error) { 74 // Iterate through versions to convert to group and resources. 75 group := &metav1.APIGroup{} 76 gvResources := map[schema.GroupVersion]*metav1.APIResourceList{} 77 failedGVs := map[schema.GroupVersion]error{} 78 group.Name = g.ObjectMeta.Name 79 for _, v := range g.Versions { 80 gv := schema.GroupVersion{Group: g.Name, Version: v.Version} 81 if v.Freshness == apidiscovery.DiscoveryFreshnessStale { 82 failedGVs[gv] = StaleGroupVersionError{gv: gv} 83 continue 84 } 85 version := metav1.GroupVersionForDiscovery{} 86 version.GroupVersion = gv.String() 87 version.Version = v.Version 88 group.Versions = append(group.Versions, version) 89 // PreferredVersion is first non-stale Version 90 if group.PreferredVersion == (metav1.GroupVersionForDiscovery{}) { 91 group.PreferredVersion = version 92 } 93 resourceList := &metav1.APIResourceList{} 94 resourceList.GroupVersion = gv.String() 95 for _, r := range v.Resources { 96 resource, err := convertAPIResource(r) 97 if err == nil { 98 resourceList.APIResources = append(resourceList.APIResources, resource) 99 } 100 // Subresources field in new format get transformed into full APIResources. 101 // It is possible a partial result with an error was returned to be used 102 // as the parent resource for the subresource. 103 for _, subresource := range r.Subresources { 104 sr, err := convertAPISubresource(resource, subresource) 105 if err == nil { 106 resourceList.APIResources = append(resourceList.APIResources, sr) 107 } 108 } 109 } 110 gvResources[gv] = resourceList 111 } 112 return group, gvResources, failedGVs 113 } 114 115 var emptyKind = metav1.GroupVersionKind{} 116 117 // convertAPIResource tranforms a APIResourceDiscovery to an APIResource. We are 118 // resilient to missing GVK, since this resource might be the parent resource 119 // for a subresource. If the parent is missing a GVK, it is not returned in 120 // discovery, and the subresource MUST have the GVK. 121 func convertAPIResource(in apidiscovery.APIResourceDiscovery) (metav1.APIResource, error) { 122 result := metav1.APIResource{ 123 Name: in.Resource, 124 SingularName: in.SingularResource, 125 Namespaced: in.Scope == apidiscovery.ScopeNamespace, 126 Verbs: in.Verbs, 127 ShortNames: in.ShortNames, 128 Categories: in.Categories, 129 } 130 var err error 131 if in.ResponseKind != nil && (*in.ResponseKind) != emptyKind { 132 result.Group = in.ResponseKind.Group 133 result.Version = in.ResponseKind.Version 134 result.Kind = in.ResponseKind.Kind 135 } else { 136 err = fmt.Errorf("discovery resource %s missing GVK", in.Resource) 137 } 138 // Can return partial result with error, which can be the parent for a 139 // subresource. Do not add this result to the returned discovery resources. 140 return result, err 141 } 142 143 // convertAPISubresource tranforms a APISubresourceDiscovery to an APIResource. 144 func convertAPISubresource(parent metav1.APIResource, in apidiscovery.APISubresourceDiscovery) (metav1.APIResource, error) { 145 result := metav1.APIResource{} 146 if in.ResponseKind == nil || (*in.ResponseKind) == emptyKind { 147 return result, fmt.Errorf("subresource %s/%s missing GVK", parent.Name, in.Subresource) 148 } 149 result.Name = fmt.Sprintf("%s/%s", parent.Name, in.Subresource) 150 result.SingularName = parent.SingularName 151 result.Namespaced = parent.Namespaced 152 result.Group = in.ResponseKind.Group 153 result.Version = in.ResponseKind.Version 154 result.Kind = in.ResponseKind.Kind 155 result.Verbs = in.Verbs 156 return result, nil 157 } 158 159 // Please note the functions below will be removed in v1.33. They facilitate conversion 160 // between the deprecated type apidiscoveryv2beta1.APIGroupDiscoveryList. 161 162 // SplitGroupsAndResourcesV2Beta1 transforms "aggregated" discovery top-level structure into 163 // the previous "unaggregated" discovery groups and resources. 164 // Deprecated: Please use SplitGroupsAndResources 165 func SplitGroupsAndResourcesV2Beta1(aggregatedGroups apidiscoveryv2beta1.APIGroupDiscoveryList) ( 166 *metav1.APIGroupList, 167 map[schema.GroupVersion]*metav1.APIResourceList, 168 map[schema.GroupVersion]error) { 169 // Aggregated group list will contain the entirety of discovery, including 170 // groups, versions, and resources. GroupVersions marked "stale" are failed. 171 groups := []*metav1.APIGroup{} 172 failedGVs := map[schema.GroupVersion]error{} 173 resourcesByGV := map[schema.GroupVersion]*metav1.APIResourceList{} 174 for _, aggGroup := range aggregatedGroups.Items { 175 group, resources, failed := convertAPIGroupv2beta1(aggGroup) 176 groups = append(groups, group) 177 for gv, resourceList := range resources { 178 resourcesByGV[gv] = resourceList 179 } 180 for gv, err := range failed { 181 failedGVs[gv] = err 182 } 183 } 184 // Transform slice of groups to group list before returning. 185 groupList := &metav1.APIGroupList{} 186 groupList.Groups = make([]metav1.APIGroup, 0, len(groups)) 187 for _, group := range groups { 188 groupList.Groups = append(groupList.Groups, *group) 189 } 190 return groupList, resourcesByGV, failedGVs 191 } 192 193 // convertAPIGroupv2beta1 tranforms an "aggregated" APIGroupDiscovery to an "legacy" APIGroup, 194 // also returning the map of APIResourceList for resources within GroupVersions. 195 func convertAPIGroupv2beta1(g apidiscoveryv2beta1.APIGroupDiscovery) ( 196 *metav1.APIGroup, 197 map[schema.GroupVersion]*metav1.APIResourceList, 198 map[schema.GroupVersion]error) { 199 // Iterate through versions to convert to group and resources. 200 group := &metav1.APIGroup{} 201 gvResources := map[schema.GroupVersion]*metav1.APIResourceList{} 202 failedGVs := map[schema.GroupVersion]error{} 203 group.Name = g.ObjectMeta.Name 204 for _, v := range g.Versions { 205 gv := schema.GroupVersion{Group: g.Name, Version: v.Version} 206 if v.Freshness == apidiscoveryv2beta1.DiscoveryFreshnessStale { 207 failedGVs[gv] = StaleGroupVersionError{gv: gv} 208 continue 209 } 210 version := metav1.GroupVersionForDiscovery{} 211 version.GroupVersion = gv.String() 212 version.Version = v.Version 213 group.Versions = append(group.Versions, version) 214 // PreferredVersion is first non-stale Version 215 if group.PreferredVersion == (metav1.GroupVersionForDiscovery{}) { 216 group.PreferredVersion = version 217 } 218 resourceList := &metav1.APIResourceList{} 219 resourceList.GroupVersion = gv.String() 220 for _, r := range v.Resources { 221 resource, err := convertAPIResourcev2beta1(r) 222 if err == nil { 223 resourceList.APIResources = append(resourceList.APIResources, resource) 224 } 225 // Subresources field in new format get transformed into full APIResources. 226 // It is possible a partial result with an error was returned to be used 227 // as the parent resource for the subresource. 228 for _, subresource := range r.Subresources { 229 sr, err := convertAPISubresourcev2beta1(resource, subresource) 230 if err == nil { 231 resourceList.APIResources = append(resourceList.APIResources, sr) 232 } 233 } 234 } 235 gvResources[gv] = resourceList 236 } 237 return group, gvResources, failedGVs 238 } 239 240 // convertAPIResource tranforms a APIResourceDiscovery to an APIResource. We are 241 // resilient to missing GVK, since this resource might be the parent resource 242 // for a subresource. If the parent is missing a GVK, it is not returned in 243 // discovery, and the subresource MUST have the GVK. 244 func convertAPIResourcev2beta1(in apidiscoveryv2beta1.APIResourceDiscovery) (metav1.APIResource, error) { 245 result := metav1.APIResource{ 246 Name: in.Resource, 247 SingularName: in.SingularResource, 248 Namespaced: in.Scope == apidiscoveryv2beta1.ScopeNamespace, 249 Verbs: in.Verbs, 250 ShortNames: in.ShortNames, 251 Categories: in.Categories, 252 } 253 // Can return partial result with error, which can be the parent for a 254 // subresource. Do not add this result to the returned discovery resources. 255 if in.ResponseKind == nil || (*in.ResponseKind) == emptyKind { 256 return result, fmt.Errorf("discovery resource %s missing GVK", in.Resource) 257 } 258 result.Group = in.ResponseKind.Group 259 result.Version = in.ResponseKind.Version 260 result.Kind = in.ResponseKind.Kind 261 return result, nil 262 } 263 264 // convertAPISubresource tranforms a APISubresourceDiscovery to an APIResource. 265 func convertAPISubresourcev2beta1(parent metav1.APIResource, in apidiscoveryv2beta1.APISubresourceDiscovery) (metav1.APIResource, error) { 266 result := metav1.APIResource{} 267 if in.ResponseKind == nil || (*in.ResponseKind) == emptyKind { 268 return result, fmt.Errorf("subresource %s/%s missing GVK", parent.Name, in.Subresource) 269 } 270 result.Name = fmt.Sprintf("%s/%s", parent.Name, in.Subresource) 271 result.SingularName = parent.SingularName 272 result.Namespaced = parent.Namespaced 273 result.Group = in.ResponseKind.Group 274 result.Version = in.ResponseKind.Version 275 result.Kind = in.ResponseKind.Kind 276 result.Verbs = in.Verbs 277 return result, nil 278 }