...

Source file src/k8s.io/client-go/discovery/aggregated_discovery.go

Documentation: k8s.io/client-go/discovery

     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  }
   279  

View as plain text