Skip to content

Commit a16d525

Browse files
seans3k8s-publishing-bot
authored andcommitted
Refactors discovery content-type and helper functions
Kubernetes-commit: 3ce0c108fe9587be2e5195ad872578877970a7a9
1 parent 559da62 commit a16d525

File tree

2 files changed

+107
-29
lines changed

2 files changed

+107
-29
lines changed

discovery/discovery_client.go

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"encoding/json"
2222
"fmt"
23+
"mime"
2324
"net/http"
2425
"net/url"
2526
"sort"
@@ -58,8 +59,9 @@ const (
5859
defaultBurst = 300
5960

6061
AcceptV1 = runtime.ContentTypeJSON
61-
// Aggregated discovery content-type (currently v2beta1). NOTE: Currently, we are assuming the order
62-
// for "g", "v", and "as" from the server. We can only compare this string if we can make that assumption.
62+
// Aggregated discovery content-type (v2beta1). NOTE: content-type parameters
63+
// MUST be ordered (g, v, as) for server in "Accept" header (BUT we are resilient
64+
// to ordering when comparing returned values in "Content-Type" header).
6365
AcceptV2Beta1 = runtime.ContentTypeJSON + ";" + "g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList"
6466
// Prioritize aggregated discovery by placing first in the order of discovery accept types.
6567
acceptDiscoveryFormats = AcceptV2Beta1 + "," + AcceptV1
@@ -259,8 +261,16 @@ func (d *DiscoveryClient) downloadLegacy() (
259261

260262
var resourcesByGV map[schema.GroupVersion]*metav1.APIResourceList
261263
// Switch on content-type server responded with: aggregated or unaggregated.
262-
switch responseContentType {
263-
case AcceptV1:
264+
switch {
265+
case isV2Beta1ContentType(responseContentType):
266+
var aggregatedDiscovery apidiscovery.APIGroupDiscoveryList
267+
err = json.Unmarshal(body, &aggregatedDiscovery)
268+
if err != nil {
269+
return nil, nil, nil, err
270+
}
271+
apiGroupList, resourcesByGV, failedGVs = SplitGroupsAndResources(aggregatedDiscovery)
272+
default:
273+
// Default is unaggregated discovery v1.
264274
var v metav1.APIVersions
265275
err = json.Unmarshal(body, &v)
266276
if err != nil {
@@ -271,15 +281,6 @@ func (d *DiscoveryClient) downloadLegacy() (
271281
apiGroup = apiVersionsToAPIGroup(&v)
272282
}
273283
apiGroupList.Groups = []metav1.APIGroup{apiGroup}
274-
case AcceptV2Beta1:
275-
var aggregatedDiscovery apidiscovery.APIGroupDiscoveryList
276-
err = json.Unmarshal(body, &aggregatedDiscovery)
277-
if err != nil {
278-
return nil, nil, nil, err
279-
}
280-
apiGroupList, resourcesByGV, failedGVs = SplitGroupsAndResources(aggregatedDiscovery)
281-
default:
282-
return nil, nil, nil, fmt.Errorf("Unknown discovery response content-type: %s", responseContentType)
283284
}
284285

285286
return apiGroupList, resourcesByGV, failedGVs, nil
@@ -313,26 +314,47 @@ func (d *DiscoveryClient) downloadAPIs() (
313314
failedGVs := map[schema.GroupVersion]error{}
314315
var resourcesByGV map[schema.GroupVersion]*metav1.APIResourceList
315316
// Switch on content-type server responded with: aggregated or unaggregated.
316-
switch responseContentType {
317-
case AcceptV1:
318-
err = json.Unmarshal(body, apiGroupList)
319-
if err != nil {
320-
return nil, nil, nil, err
321-
}
322-
case AcceptV2Beta1:
317+
switch {
318+
case isV2Beta1ContentType(responseContentType):
323319
var aggregatedDiscovery apidiscovery.APIGroupDiscoveryList
324320
err = json.Unmarshal(body, &aggregatedDiscovery)
325321
if err != nil {
326322
return nil, nil, nil, err
327323
}
328324
apiGroupList, resourcesByGV, failedGVs = SplitGroupsAndResources(aggregatedDiscovery)
329325
default:
330-
return nil, nil, nil, fmt.Errorf("Unknown discovery response content-type: %s", responseContentType)
326+
// Default is unaggregated discovery v1.
327+
err = json.Unmarshal(body, apiGroupList)
328+
if err != nil {
329+
return nil, nil, nil, err
330+
}
331331
}
332332

333333
return apiGroupList, resourcesByGV, failedGVs, nil
334334
}
335335

336+
// isV2Beta1ContentType checks of the content-type string is both
337+
// "application/json" and contains the v2beta1 content-type params.
338+
// NOTE: This function is resilient to the ordering of the
339+
// content-type parameters, as well as parameters added by
340+
// intermediaries such as proxies or gateways. Examples:
341+
//
342+
// "application/json; g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList" = true
343+
// "application/json; as=APIGroupDiscoveryList;v=v2beta1;g=apidiscovery.k8s.io" = true
344+
// "application/json; as=APIGroupDiscoveryList;v=v2beta1;g=apidiscovery.k8s.io;charset=utf-8" = true
345+
// "application/json" = false
346+
// "application/json; charset=UTF-8" = false
347+
func isV2Beta1ContentType(contentType string) bool {
348+
base, params, err := mime.ParseMediaType(contentType)
349+
if err != nil {
350+
return false
351+
}
352+
return runtime.ContentTypeJSON == base &&
353+
params["g"] == "apidiscovery.k8s.io" &&
354+
params["v"] == "v2beta1" &&
355+
params["as"] == "APIGroupDiscoveryList"
356+
}
357+
336358
// ServerGroups returns the supported groups, with information like supported versions and the
337359
// preferred version.
338360
func (d *DiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) {

discovery/discovery_client_test.go

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,8 +1395,9 @@ func TestAggregatedServerGroups(t *testing.T) {
13951395
}
13961396
output, err := json.Marshal(agg)
13971397
require.NoError(t, err)
1398-
// Content-type is "aggregated" discovery format.
1399-
w.Header().Set("Content-Type", AcceptV2Beta1)
1398+
// Content-Type is "aggregated" discovery format. Add extra parameter
1399+
// to ensure we are resilient to these extra parameters.
1400+
w.Header().Set("Content-Type", AcceptV2Beta1+"; charset=utf-8")
14001401
w.WriteHeader(http.StatusOK)
14011402
w.Write(output)
14021403
}))
@@ -1985,8 +1986,9 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
19851986
}
19861987
output, err := json.Marshal(agg)
19871988
require.NoError(t, err)
1988-
// Content-type is "aggregated" discovery format.
1989-
w.Header().Set("Content-Type", AcceptV2Beta1)
1989+
// Content-type is "aggregated" discovery format. Add extra parameter
1990+
// to ensure we are resilient to these extra parameters.
1991+
w.Header().Set("Content-Type", AcceptV2Beta1+"; charset=utf-8")
19901992
w.WriteHeader(http.StatusOK)
19911993
w.Write(output)
19921994
}))
@@ -2125,8 +2127,9 @@ func TestAggregatedServerGroupsAndResourcesWithErrors(t *testing.T) {
21252127
}
21262128
output, err := json.Marshal(agg)
21272129
require.NoError(t, err)
2128-
// Content-type is "aggregated" discovery format.
2129-
w.Header().Set("Content-Type", AcceptV2Beta1)
2130+
// Content-type is "aggregated" discovery format. Add extra parameter
2131+
// to ensure we are resilient to these extra parameters.
2132+
w.Header().Set("Content-Type", AcceptV2Beta1+"; charset=utf-8")
21302133
w.WriteHeader(status)
21312134
w.Write(output)
21322135
}))
@@ -2733,8 +2736,9 @@ func TestAggregatedServerPreferredResources(t *testing.T) {
27332736
}
27342737
output, err := json.Marshal(agg)
27352738
require.NoError(t, err)
2736-
// Content-type is "aggregated" discovery format.
2737-
w.Header().Set("Content-Type", AcceptV2Beta1)
2739+
// Content-type is "aggregated" discovery format. Add extra parameter
2740+
// to ensure we are resilient to these extra parameters.
2741+
w.Header().Set("Content-Type", AcceptV2Beta1+"; charset=utf-8")
27382742
w.WriteHeader(http.StatusOK)
27392743
w.Write(output)
27402744
}))
@@ -2758,6 +2762,58 @@ func TestAggregatedServerPreferredResources(t *testing.T) {
27582762
}
27592763
}
27602764

2765+
func TestDiscoveryContentTypeVersion(t *testing.T) {
2766+
tests := []struct {
2767+
contentType string
2768+
isV2Beta1 bool
2769+
}{
2770+
{
2771+
contentType: "application/json; g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList",
2772+
isV2Beta1: true,
2773+
},
2774+
{
2775+
// content-type parameters are not in correct order, but comparison ignores order.
2776+
contentType: "application/json; v=v2beta1;as=APIGroupDiscoveryList;g=apidiscovery.k8s.io",
2777+
isV2Beta1: true,
2778+
},
2779+
{
2780+
// content-type parameters are not in correct order, but comparison ignores order.
2781+
contentType: "application/json; as=APIGroupDiscoveryList;g=apidiscovery.k8s.io;v=v2beta1",
2782+
isV2Beta1: true,
2783+
},
2784+
{
2785+
// Ignores extra parameter "charset=utf-8"
2786+
contentType: "application/json; g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList;charset=utf-8",
2787+
isV2Beta1: true,
2788+
},
2789+
{
2790+
contentType: "application/json",
2791+
isV2Beta1: false,
2792+
},
2793+
{
2794+
contentType: "application/json; charset=UTF-8",
2795+
isV2Beta1: false,
2796+
},
2797+
{
2798+
contentType: "text/json",
2799+
isV2Beta1: false,
2800+
},
2801+
{
2802+
contentType: "text/html",
2803+
isV2Beta1: false,
2804+
},
2805+
{
2806+
contentType: "",
2807+
isV2Beta1: false,
2808+
},
2809+
}
2810+
2811+
for _, test := range tests {
2812+
isV2Beta1 := isV2Beta1ContentType(test.contentType)
2813+
assert.Equal(t, test.isV2Beta1, isV2Beta1)
2814+
}
2815+
}
2816+
27612817
func TestUseLegacyDiscovery(t *testing.T) {
27622818
// Default client sends aggregated discovery accept format (first) as well as legacy format.
27632819
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {

0 commit comments

Comments
 (0)