Skip to content

Commit ea13190

Browse files
committed
Add test-only client feature gates for CBOR.
As with the apiserver feature gate for CBOR as a serving and storage encoding, the client feature gates for CBOR are being initially added through a test-only feature gate instance that is not wired to environment variables or to command-line flags and is intended only to be enabled programmatically from integration tests. The test-only instance will be removed as part of alpha graduation and replaced by conventional client feature gating.
1 parent 0cad1a8 commit ea13190

File tree

6 files changed

+298
-22
lines changed

6 files changed

+298
-22
lines changed

staging/src/k8s.io/client-go/dynamic/scheme.go

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ import (
2121
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2222
"k8s.io/apimachinery/pkg/runtime"
2323
"k8s.io/apimachinery/pkg/runtime/schema"
24+
"k8s.io/apimachinery/pkg/runtime/serializer/cbor"
2425
"k8s.io/apimachinery/pkg/runtime/serializer/json"
26+
"k8s.io/client-go/features"
2527
)
2628

2729
var basicScheme = runtime.NewScheme()
@@ -35,11 +37,8 @@ func init() {
3537
metav1.AddToGroupVersion(parameterScheme, versionV1)
3638
}
3739

38-
// basicNegotiatedSerializer is used to handle discovery and error handling serialization
39-
type basicNegotiatedSerializer struct{}
40-
41-
func (s basicNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo {
42-
return []runtime.SerializerInfo{
40+
func newBasicNegotiatedSerializer() basicNegotiatedSerializer {
41+
supportedMediaTypes := []runtime.SerializerInfo{
4342
{
4443
MediaType: "application/json",
4544
MediaTypeType: "application",
@@ -54,6 +53,27 @@ func (s basicNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInf
5453
},
5554
},
5655
}
56+
if features.TestOnlyFeatureGates.Enabled(features.TestOnlyClientAllowsCBOR) {
57+
supportedMediaTypes = append(supportedMediaTypes, runtime.SerializerInfo{
58+
MediaType: "application/cbor",
59+
MediaTypeType: "application",
60+
MediaTypeSubType: "cbor",
61+
Serializer: cbor.NewSerializer(unstructuredCreater{basicScheme}, unstructuredTyper{basicScheme}),
62+
StreamSerializer: &runtime.StreamSerializerInfo{
63+
Serializer: cbor.NewSerializer(basicScheme, basicScheme, cbor.Transcode(false)),
64+
Framer: cbor.NewFramer(),
65+
},
66+
})
67+
}
68+
return basicNegotiatedSerializer{supportedMediaTypes: supportedMediaTypes}
69+
}
70+
71+
type basicNegotiatedSerializer struct {
72+
supportedMediaTypes []runtime.SerializerInfo
73+
}
74+
75+
func (s basicNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo {
76+
return s.supportedMediaTypes
5777
}
5878

5979
func (s basicNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {

staging/src/k8s.io/client-go/dynamic/simple.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"k8s.io/apimachinery/pkg/runtime/schema"
3030
"k8s.io/apimachinery/pkg/types"
3131
"k8s.io/apimachinery/pkg/watch"
32+
"k8s.io/client-go/features"
3233
"k8s.io/client-go/rest"
3334
"k8s.io/client-go/util/consistencydetector"
3435
"k8s.io/client-go/util/watchlist"
@@ -45,9 +46,17 @@ var _ Interface = &DynamicClient{}
4546
// appropriate dynamic client defaults set.
4647
func ConfigFor(inConfig *rest.Config) *rest.Config {
4748
config := rest.CopyConfig(inConfig)
48-
config.AcceptContentTypes = "application/json"
49+
4950
config.ContentType = "application/json"
50-
config.NegotiatedSerializer = basicNegotiatedSerializer{} // this gets used for discovery and error handling types
51+
config.AcceptContentTypes = "application/json"
52+
if features.TestOnlyFeatureGates.Enabled(features.TestOnlyClientAllowsCBOR) {
53+
config.AcceptContentTypes = "application/json;q=0.9,application/cbor;q=1"
54+
if features.TestOnlyFeatureGates.Enabled(features.TestOnlyClientPrefersCBOR) {
55+
config.ContentType = "application/cbor"
56+
}
57+
}
58+
59+
config.NegotiatedSerializer = newBasicNegotiatedSerializer()
5160
if config.UserAgent == "" {
5261
config.UserAgent = rest.DefaultKubernetesUserAgent()
5362
}

staging/src/k8s.io/client-go/features/features.go

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ package features
1818

1919
import (
2020
"errors"
21+
"fmt"
22+
"sync"
23+
"sync/atomic"
2124

2225
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
23-
"sync/atomic"
2426
)
2527

2628
// NOTE: types Feature, FeatureSpec, prerelease (and its values)
@@ -141,3 +143,43 @@ var (
141143
// should use AddFeaturesToExistingFeatureGates followed by ReplaceFeatureGates.
142144
featureGates = &atomic.Value{}
143145
)
146+
147+
// TestOnlyFeatureGates is a distinct registry of pre-alpha client features that must not be
148+
// included in runtime wiring to command-line flags or environment variables. It exists as a risk
149+
// mitigation to allow only programmatic enablement of CBOR serialization for integration testing
150+
// purposes.
151+
//
152+
// TODO: Once all required integration test coverage is complete, this will be deleted and the
153+
// test-only feature gates will be replaced by normal feature gates.
154+
var TestOnlyFeatureGates = &testOnlyFeatureGates{
155+
features: map[Feature]bool{
156+
TestOnlyClientAllowsCBOR: false,
157+
TestOnlyClientPrefersCBOR: false,
158+
},
159+
}
160+
161+
type testOnlyFeatureGates struct {
162+
lock sync.RWMutex
163+
features map[Feature]bool
164+
}
165+
166+
func (t *testOnlyFeatureGates) Enabled(feature Feature) bool {
167+
t.lock.RLock()
168+
defer t.lock.RUnlock()
169+
170+
enabled, ok := t.features[feature]
171+
if !ok {
172+
panic(fmt.Sprintf("test-only feature %q not recognized", feature))
173+
}
174+
return enabled
175+
}
176+
177+
func (t *testOnlyFeatureGates) Set(feature Feature, enabled bool) error {
178+
t.lock.Lock()
179+
defer t.lock.Unlock()
180+
if _, ok := t.features[feature]; !ok {
181+
return fmt.Errorf("test-only feature %q not recognized", feature)
182+
}
183+
t.features[feature] = enabled
184+
return nil
185+
}

staging/src/k8s.io/client-go/features/known_features.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,27 @@ const (
4141
// owner: @nilekhc
4242
// alpha: v1.30
4343
InformerResourceVersion Feature = "InformerResourceVersion"
44+
45+
// owner: @benluddy
46+
// kep: https://kep.k8s.io/4222
47+
//
48+
// If disabled, clients configured to accept "application/cbor" will instead accept
49+
// "application/json" with the same relative preference, and clients configured to write
50+
// "application/cbor" or "application/apply-patch+cbor" will instead write
51+
// "application/json" or "application/apply-patch+yaml", respectively.
52+
//
53+
// This feature is currently PRE-ALPHA and MUST NOT be enabled outside of integration tests.
54+
TestOnlyClientAllowsCBOR Feature = "TestOnlyClientAllowsCBOR"
55+
56+
// owner: @benluddy
57+
// kep: https://kep.k8s.io/4222
58+
//
59+
// If enabled AND TestOnlyClientAllowsCBOR is also enabled, the default request content type
60+
// (if not explicitly configured) and the dynamic client's request content type both become
61+
// "application/cbor".
62+
//
63+
// This feature is currently PRE-ALPHA and MUST NOT be enabled outside of integration tests.
64+
TestOnlyClientPrefersCBOR Feature = "TestOnlyClientPrefersCBOR"
4465
)
4566

4667
// defaultKubernetesFeatureGates consists of all known Kubernetes-specific feature keys.

0 commit comments

Comments
 (0)