mirror of
https://github.com/kubernetes/client-go.git
synced 2025-07-18 09:11:10 +00:00
Merge pull request #117571 from seans3/agg-discovery-fix
Fixes bug when extra params added to discovery content-type Kubernetes-commit: c5c2806e233b9c5ff49ac4ad0c6d734b530771de
This commit is contained in:
commit
68394bf465
@ -20,6 +20,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sort"
|
"sort"
|
||||||
@ -58,8 +59,9 @@ const (
|
|||||||
defaultBurst = 300
|
defaultBurst = 300
|
||||||
|
|
||||||
AcceptV1 = runtime.ContentTypeJSON
|
AcceptV1 = runtime.ContentTypeJSON
|
||||||
// Aggregated discovery content-type (currently v2beta1). NOTE: Currently, we are assuming the order
|
// Aggregated discovery content-type (v2beta1). NOTE: content-type parameters
|
||||||
// for "g", "v", and "as" from the server. We can only compare this string if we can make that assumption.
|
// MUST be ordered (g, v, as) for server in "Accept" header (BUT we are resilient
|
||||||
|
// to ordering when comparing returned values in "Content-Type" header).
|
||||||
AcceptV2Beta1 = runtime.ContentTypeJSON + ";" + "g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList"
|
AcceptV2Beta1 = runtime.ContentTypeJSON + ";" + "g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList"
|
||||||
// Prioritize aggregated discovery by placing first in the order of discovery accept types.
|
// Prioritize aggregated discovery by placing first in the order of discovery accept types.
|
||||||
acceptDiscoveryFormats = AcceptV2Beta1 + "," + AcceptV1
|
acceptDiscoveryFormats = AcceptV2Beta1 + "," + AcceptV1
|
||||||
@ -259,8 +261,16 @@ func (d *DiscoveryClient) downloadLegacy() (
|
|||||||
|
|
||||||
var resourcesByGV map[schema.GroupVersion]*metav1.APIResourceList
|
var resourcesByGV map[schema.GroupVersion]*metav1.APIResourceList
|
||||||
// Switch on content-type server responded with: aggregated or unaggregated.
|
// Switch on content-type server responded with: aggregated or unaggregated.
|
||||||
switch responseContentType {
|
switch {
|
||||||
case AcceptV1:
|
case isV2Beta1ContentType(responseContentType):
|
||||||
|
var aggregatedDiscovery apidiscovery.APIGroupDiscoveryList
|
||||||
|
err = json.Unmarshal(body, &aggregatedDiscovery)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
apiGroupList, resourcesByGV, failedGVs = SplitGroupsAndResources(aggregatedDiscovery)
|
||||||
|
default:
|
||||||
|
// Default is unaggregated discovery v1.
|
||||||
var v metav1.APIVersions
|
var v metav1.APIVersions
|
||||||
err = json.Unmarshal(body, &v)
|
err = json.Unmarshal(body, &v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -271,15 +281,6 @@ func (d *DiscoveryClient) downloadLegacy() (
|
|||||||
apiGroup = apiVersionsToAPIGroup(&v)
|
apiGroup = apiVersionsToAPIGroup(&v)
|
||||||
}
|
}
|
||||||
apiGroupList.Groups = []metav1.APIGroup{apiGroup}
|
apiGroupList.Groups = []metav1.APIGroup{apiGroup}
|
||||||
case AcceptV2Beta1:
|
|
||||||
var aggregatedDiscovery apidiscovery.APIGroupDiscoveryList
|
|
||||||
err = json.Unmarshal(body, &aggregatedDiscovery)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
apiGroupList, resourcesByGV, failedGVs = SplitGroupsAndResources(aggregatedDiscovery)
|
|
||||||
default:
|
|
||||||
return nil, nil, nil, fmt.Errorf("Unknown discovery response content-type: %s", responseContentType)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return apiGroupList, resourcesByGV, failedGVs, nil
|
return apiGroupList, resourcesByGV, failedGVs, nil
|
||||||
@ -313,13 +314,8 @@ func (d *DiscoveryClient) downloadAPIs() (
|
|||||||
failedGVs := map[schema.GroupVersion]error{}
|
failedGVs := map[schema.GroupVersion]error{}
|
||||||
var resourcesByGV map[schema.GroupVersion]*metav1.APIResourceList
|
var resourcesByGV map[schema.GroupVersion]*metav1.APIResourceList
|
||||||
// Switch on content-type server responded with: aggregated or unaggregated.
|
// Switch on content-type server responded with: aggregated or unaggregated.
|
||||||
switch responseContentType {
|
switch {
|
||||||
case AcceptV1:
|
case isV2Beta1ContentType(responseContentType):
|
||||||
err = json.Unmarshal(body, apiGroupList)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
case AcceptV2Beta1:
|
|
||||||
var aggregatedDiscovery apidiscovery.APIGroupDiscoveryList
|
var aggregatedDiscovery apidiscovery.APIGroupDiscoveryList
|
||||||
err = json.Unmarshal(body, &aggregatedDiscovery)
|
err = json.Unmarshal(body, &aggregatedDiscovery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -327,12 +323,38 @@ func (d *DiscoveryClient) downloadAPIs() (
|
|||||||
}
|
}
|
||||||
apiGroupList, resourcesByGV, failedGVs = SplitGroupsAndResources(aggregatedDiscovery)
|
apiGroupList, resourcesByGV, failedGVs = SplitGroupsAndResources(aggregatedDiscovery)
|
||||||
default:
|
default:
|
||||||
return nil, nil, nil, fmt.Errorf("Unknown discovery response content-type: %s", responseContentType)
|
// Default is unaggregated discovery v1.
|
||||||
|
err = json.Unmarshal(body, apiGroupList)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return apiGroupList, resourcesByGV, failedGVs, nil
|
return apiGroupList, resourcesByGV, failedGVs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isV2Beta1ContentType checks of the content-type string is both
|
||||||
|
// "application/json" and contains the v2beta1 content-type params.
|
||||||
|
// NOTE: This function is resilient to the ordering of the
|
||||||
|
// content-type parameters, as well as parameters added by
|
||||||
|
// intermediaries such as proxies or gateways. Examples:
|
||||||
|
//
|
||||||
|
// "application/json; g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList" = true
|
||||||
|
// "application/json; as=APIGroupDiscoveryList;v=v2beta1;g=apidiscovery.k8s.io" = true
|
||||||
|
// "application/json; as=APIGroupDiscoveryList;v=v2beta1;g=apidiscovery.k8s.io;charset=utf-8" = true
|
||||||
|
// "application/json" = false
|
||||||
|
// "application/json; charset=UTF-8" = false
|
||||||
|
func isV2Beta1ContentType(contentType string) bool {
|
||||||
|
base, params, err := mime.ParseMediaType(contentType)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return runtime.ContentTypeJSON == base &&
|
||||||
|
params["g"] == "apidiscovery.k8s.io" &&
|
||||||
|
params["v"] == "v2beta1" &&
|
||||||
|
params["as"] == "APIGroupDiscoveryList"
|
||||||
|
}
|
||||||
|
|
||||||
// ServerGroups returns the supported groups, with information like supported versions and the
|
// ServerGroups returns the supported groups, with information like supported versions and the
|
||||||
// preferred version.
|
// preferred version.
|
||||||
func (d *DiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) {
|
func (d *DiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) {
|
||||||
|
@ -1394,8 +1394,9 @@ func TestAggregatedServerGroups(t *testing.T) {
|
|||||||
}
|
}
|
||||||
output, err := json.Marshal(agg)
|
output, err := json.Marshal(agg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// Content-type is "aggregated" discovery format.
|
// Content-Type is "aggregated" discovery format. Add extra parameter
|
||||||
w.Header().Set("Content-Type", AcceptV2Beta1)
|
// to ensure we are resilient to these extra parameters.
|
||||||
|
w.Header().Set("Content-Type", AcceptV2Beta1+"; charset=utf-8")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write(output)
|
w.Write(output)
|
||||||
}))
|
}))
|
||||||
@ -1984,8 +1985,9 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
|
|||||||
}
|
}
|
||||||
output, err := json.Marshal(agg)
|
output, err := json.Marshal(agg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// Content-type is "aggregated" discovery format.
|
// Content-type is "aggregated" discovery format. Add extra parameter
|
||||||
w.Header().Set("Content-Type", AcceptV2Beta1)
|
// to ensure we are resilient to these extra parameters.
|
||||||
|
w.Header().Set("Content-Type", AcceptV2Beta1+"; charset=utf-8")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write(output)
|
w.Write(output)
|
||||||
}))
|
}))
|
||||||
@ -2124,8 +2126,9 @@ func TestAggregatedServerGroupsAndResourcesWithErrors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
output, err := json.Marshal(agg)
|
output, err := json.Marshal(agg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// Content-type is "aggregated" discovery format.
|
// Content-type is "aggregated" discovery format. Add extra parameter
|
||||||
w.Header().Set("Content-Type", AcceptV2Beta1)
|
// to ensure we are resilient to these extra parameters.
|
||||||
|
w.Header().Set("Content-Type", AcceptV2Beta1+"; charset=utf-8")
|
||||||
w.WriteHeader(status)
|
w.WriteHeader(status)
|
||||||
w.Write(output)
|
w.Write(output)
|
||||||
}))
|
}))
|
||||||
@ -2732,8 +2735,9 @@ func TestAggregatedServerPreferredResources(t *testing.T) {
|
|||||||
}
|
}
|
||||||
output, err := json.Marshal(agg)
|
output, err := json.Marshal(agg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// Content-type is "aggregated" discovery format.
|
// Content-type is "aggregated" discovery format. Add extra parameter
|
||||||
w.Header().Set("Content-Type", AcceptV2Beta1)
|
// to ensure we are resilient to these extra parameters.
|
||||||
|
w.Header().Set("Content-Type", AcceptV2Beta1+"; charset=utf-8")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write(output)
|
w.Write(output)
|
||||||
}))
|
}))
|
||||||
@ -2757,6 +2761,58 @@ func TestAggregatedServerPreferredResources(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDiscoveryContentTypeVersion(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
contentType string
|
||||||
|
isV2Beta1 bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
contentType: "application/json; g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList",
|
||||||
|
isV2Beta1: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// content-type parameters are not in correct order, but comparison ignores order.
|
||||||
|
contentType: "application/json; v=v2beta1;as=APIGroupDiscoveryList;g=apidiscovery.k8s.io",
|
||||||
|
isV2Beta1: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// content-type parameters are not in correct order, but comparison ignores order.
|
||||||
|
contentType: "application/json; as=APIGroupDiscoveryList;g=apidiscovery.k8s.io;v=v2beta1",
|
||||||
|
isV2Beta1: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Ignores extra parameter "charset=utf-8"
|
||||||
|
contentType: "application/json; g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList;charset=utf-8",
|
||||||
|
isV2Beta1: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
contentType: "application/json",
|
||||||
|
isV2Beta1: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
contentType: "application/json; charset=UTF-8",
|
||||||
|
isV2Beta1: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
contentType: "text/json",
|
||||||
|
isV2Beta1: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
contentType: "text/html",
|
||||||
|
isV2Beta1: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
contentType: "",
|
||||||
|
isV2Beta1: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
isV2Beta1 := isV2Beta1ContentType(test.contentType)
|
||||||
|
assert.Equal(t, test.isV2Beta1, isV2Beta1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestUseLegacyDiscovery(t *testing.T) {
|
func TestUseLegacyDiscovery(t *testing.T) {
|
||||||
// Default client sends aggregated discovery accept format (first) as well as legacy format.
|
// Default client sends aggregated discovery accept format (first) as well as legacy format.
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
Loading…
Reference in New Issue
Block a user