discovery: Add profile-based content negotiation

Add support for 'profile=nopeer' in Accept headers to allow clients
to opt out of peer-aggregated discovery and request local-only results.

Updates discovery client to set appropriate Accept headers based on
whether peer-aggregated discovery is desired.

Part of KEP-4020: Unknown Version Interoperability Proxy

Kubernetes-commit: 6a07342d37a762230209e362d383e1fbfc325b51
This commit is contained in:
Richa Banker
2025-11-05 21:14:25 -08:00
committed by Kubernetes Publisher
parent 0e6fc04326
commit 09ccc185ed

View File

@@ -59,12 +59,14 @@ const (
// defaultBurst is the default burst to be used with the discovery client's token bucket rate limiter
defaultBurst = 300
AcceptV1 = runtime.ContentTypeJSON
AcceptV1 = runtime.ContentTypeJSON
// Aggregated discovery content-type (v2beta1). NOTE: content-type parameters
// 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"
AcceptV2 = runtime.ContentTypeJSON + ";" + "g=apidiscovery.k8s.io;v=v2;as=APIGroupDiscoveryList"
AcceptV2Beta1 = runtime.ContentTypeJSON + ";" + "g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList"
AcceptV2 = runtime.ContentTypeJSON + ";" + "g=apidiscovery.k8s.io;v=v2;as=APIGroupDiscoveryList"
AcceptV2NoPeer = runtime.ContentTypeJSON + ";" + "g=apidiscovery.k8s.io;v=v2;as=APIGroupDiscoveryList;profile=nopeer"
// Prioritize aggregated discovery by placing first in the order of discovery accept types.
acceptDiscoveryFormats = AcceptV2 + "," + AcceptV2Beta1 + "," + AcceptV1
)
@@ -168,6 +170,11 @@ type DiscoveryClient struct {
LegacyPrefix string
// Forces the client to request only "unaggregated" (legacy) discovery.
UseLegacyDiscovery bool
// NoPeerDiscovery will request the "nopeer" profile of aggregated discovery.
// This allows a client to get just the discovery documents served by the single apiserver
// that it is talking to. This is useful for clients that need to understand the state
// of a single apiserver, for example, to validate that the apiserver is ready to serve traffic.
NoPeerDiscovery bool
}
var _ AggregatedDiscoveryInterface = &DiscoveryClient{}
@@ -241,10 +248,7 @@ func (d *DiscoveryClient) downloadLegacy() (
map[schema.GroupVersion]*metav1.APIResourceList,
map[schema.GroupVersion]error,
error) {
accept := acceptDiscoveryFormats
if d.UseLegacyDiscovery {
accept = AcceptV1
}
accept := selectDiscoveryAcceptHeader(d.UseLegacyDiscovery, d.NoPeerDiscovery)
var responseContentType string
body, err := d.restClient.Get().
AbsPath("/api").
@@ -307,10 +311,7 @@ func (d *DiscoveryClient) downloadAPIs() (
map[schema.GroupVersion]*metav1.APIResourceList,
map[schema.GroupVersion]error,
error) {
accept := acceptDiscoveryFormats
if d.UseLegacyDiscovery {
accept = AcceptV1
}
accept := selectDiscoveryAcceptHeader(d.UseLegacyDiscovery, d.NoPeerDiscovery)
var responseContentType string
body, err := d.restClient.Get().
AbsPath("/apis").
@@ -351,6 +352,16 @@ func (d *DiscoveryClient) downloadAPIs() (
return apiGroupList, resourcesByGV, failedGVs, nil
}
func selectDiscoveryAcceptHeader(useLegacy, nopeer bool) string {
if useLegacy {
return AcceptV1
}
if nopeer {
return AcceptV2NoPeer + "," + acceptDiscoveryFormats
}
return acceptDiscoveryFormats
}
// ContentTypeIsGVK checks of the content-type string is both
// "application/json" and matches the provided GVK. An error
// is returned if the content type string is malformed.