diff --git a/staging/src/k8s.io/apiserver/pkg/server/flagz/flagz.go b/staging/src/k8s.io/apiserver/pkg/server/flagz/flagz.go index 235b8bbcd8e..dcdfc682753 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/flagz/flagz.go +++ b/staging/src/k8s.io/apiserver/pkg/server/flagz/flagz.go @@ -46,6 +46,7 @@ const ( // from the supported media types list, so error messages only show actually supported types. type flagzCodecFactory struct { serializer.CodecFactory + supportedMediaTypes []runtime.SerializerInfo } type mux interface { @@ -64,9 +65,19 @@ func Install(m mux, componentName string, flagReader Reader, opts ...Option) { scheme := runtime.NewScheme() utilruntime.Must(v1alpha1.AddToScheme(scheme)) + filteredCodecFactory, err := newFlagzCodecFactory(scheme, componentName, reg.reader) + if err != nil { + utilruntime.HandleError(err) + } + m.Handle(DefaultFlagzPath, handleFlagz(componentName, reg, filteredCodecFactory, negotiate.FlagzEndpointRestrictions{})) +} + +// newFlagzCodecFactory creates a codec factory with the standard serializers for flagz, +// filtering out unsupported media types (e.g., protobuf). +func newFlagzCodecFactory(scheme *runtime.Scheme, componentName string, flagReader Reader) (*flagzCodecFactory, error) { codecFactoryOpts := []serializer.CodecFactoryOptionsMutator{ serializer.WithSerializer(func(_ runtime.ObjectCreater, _ runtime.ObjectTyper) runtime.SerializerInfo { - textSerializer := flagzTextSerializer{componentName, reg.reader} + textSerializer := flagzTextSerializer{componentName, flagReader} return runtime.SerializerInfo{ MediaType: "text/plain", MediaTypeType: "text", @@ -83,21 +94,36 @@ func Install(m mux, componentName string, flagReader Reader, opts ...Option) { } codecFactory := serializer.NewCodecFactory(scheme, codecFactoryOpts...) - // Wrap to filter out unsupported media types (e.g., protobuf) - filteredCodecFactory := &flagzCodecFactory{codecFactory} - m.Handle(DefaultFlagzPath, handleFlagz(componentName, reg, filteredCodecFactory, negotiate.FlagzEndpointRestrictions{})) + allTypes := codecFactory.SupportedMediaTypes() + filtered := make([]runtime.SerializerInfo, 0, len(allTypes)) + + var unknownTypes []string + for _, info := range allTypes { + switch info.MediaType { + // Supported media types + case "text/plain", runtime.ContentTypeJSON, runtime.ContentTypeYAML, runtime.ContentTypeCBOR: + filtered = append(filtered, info) + // Unsupported media types + case runtime.ContentTypeProtobuf: + continue + default: + unknownTypes = append(unknownTypes, info.MediaType) + } + } + + var err error + if len(unknownTypes) > 0 { + err = fmt.Errorf("flagz: unknown media type(s) %v, excluding from supported types", unknownTypes) + } + + return &flagzCodecFactory{ + CodecFactory: codecFactory, + supportedMediaTypes: filtered, + }, err } func (f *flagzCodecFactory) SupportedMediaTypes() []runtime.SerializerInfo { - allTypes := f.CodecFactory.SupportedMediaTypes() - filtered := make([]runtime.SerializerInfo, 0, len(allTypes)) - for _, info := range allTypes { - if info.MediaType == runtime.ContentTypeProtobuf { - continue - } - filtered = append(filtered, info) - } - return filtered + return f.supportedMediaTypes } func handleFlagz(componentName string, reg *registry, serializer runtime.NegotiatedSerializer, restrictions negotiate.FlagzEndpointRestrictions) http.HandlerFunc { diff --git a/staging/src/k8s.io/apiserver/pkg/server/flagz/flagz_test.go b/staging/src/k8s.io/apiserver/pkg/server/flagz/flagz_test.go index 68b303ff712..075f32b4296 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/flagz/flagz_test.go +++ b/staging/src/k8s.io/apiserver/pkg/server/flagz/flagz_test.go @@ -27,7 +27,9 @@ import ( "github.com/google/go-cmp/cmp" "github.com/spf13/pflag" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct" + "k8s.io/apimachinery/pkg/runtime" + cbordirect "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apiserver/pkg/features" v1alpha1 "k8s.io/apiserver/pkg/server/flagz/api/v1alpha1" utilfeature "k8s.io/apiserver/pkg/util/feature" @@ -306,7 +308,7 @@ func unmarshalResponse(t *testing.T, contentType string, body []byte, got *v1alp t.Fatalf("unexpected error while unmarshalling YAML response: %v", err) } case strings.Contains(contentType, "application/cbor"): - if err := cbor.Unmarshal(body, got); err != nil { + if err := cbordirect.Unmarshal(body, got); err != nil { t.Fatalf("unexpected error while unmarshalling CBOR response: %v", err) } default: @@ -360,3 +362,18 @@ func TestCache(t *testing.T) { t.Errorf("Unexpected diff on cached response (-want,+got):\n%s", diff) } } + +// TestNewFlagzCodecFactory ensures all media types in the codec factory +// are explicitly handled. If this test fails, a new media type was added +// to the codec factory and needs to be explicitly added to the supported +// or unsupported list in newFlagzCodecFactory. +func TestNewFlagzCodecFactory(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CBORServingAndStorage, true) + scheme := runtime.NewScheme() + utilruntime.Must(v1alpha1.AddToScheme(scheme)) + + _, err := newFlagzCodecFactory(scheme, "", nil) + if err != nil { + t.Fatalf("unknown media type(s) detected - update newFlagzCodecFactory to explicitly handle them: %v", err) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/statusz/statusz.go b/staging/src/k8s.io/apiserver/pkg/server/statusz/statusz.go index ef4f05da5b4..aed4bf6a20a 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/statusz/statusz.go +++ b/staging/src/k8s.io/apiserver/pkg/server/statusz/statusz.go @@ -67,6 +67,7 @@ Warning: This endpoint is not meant to be machine parseable, has no formatting c // from the supported media types list, so error messages only show actually supported types. type statuszCodecFactory struct { serializer.CodecFactory + supportedMediaTypes []runtime.SerializerInfo } type mux interface { @@ -89,6 +90,16 @@ func NewRegistry(effectiveVersion compatibility.EffectiveVersion, opts ...Option func Install(m mux, componentName string, reg statuszRegistry) { scheme := runtime.NewScheme() utilruntime.Must(v1alpha1.AddToScheme(scheme)) + filteredCodecFactory, err := newStatuszCodecFactory(scheme, componentName, reg) + if err != nil { + utilruntime.HandleError(err) + } + m.Handle(DefaultStatuszPath, handleStatusz(componentName, reg, filteredCodecFactory, negotiate.StatuszEndpointRestrictions{})) +} + +// newStatuszCodecFactory creates a codec factory with the standard serializers for statusz, +// filtering out unsupported media types (e.g., protobuf). +func newStatuszCodecFactory(scheme *runtime.Scheme, componentName string, reg statuszRegistry) (*statuszCodecFactory, error) { codecFactoryOpts := []serializer.CodecFactoryOptionsMutator{ serializer.WithSerializer(func(_ runtime.ObjectCreater, _ runtime.ObjectTyper) runtime.SerializerInfo { textSerializer := statuszTextSerializer{componentName, reg} @@ -108,21 +119,36 @@ func Install(m mux, componentName string, reg statuszRegistry) { } codecFactory := serializer.NewCodecFactory(scheme, codecFactoryOpts...) - // Wrap to filter out unsupported media types (e.g., protobuf) - filteredCodecFactory := &statuszCodecFactory{codecFactory} - m.Handle(DefaultStatuszPath, handleStatusz(componentName, reg, filteredCodecFactory, negotiate.StatuszEndpointRestrictions{})) + allTypes := codecFactory.SupportedMediaTypes() + filtered := make([]runtime.SerializerInfo, 0, len(allTypes)) + + var unknownTypes []string + for _, info := range allTypes { + switch info.MediaType { + // Supported media types + case "text/plain", runtime.ContentTypeJSON, runtime.ContentTypeYAML, runtime.ContentTypeCBOR: + filtered = append(filtered, info) + // Unsupported media types + case runtime.ContentTypeProtobuf: + continue + default: + unknownTypes = append(unknownTypes, info.MediaType) + } + } + + var err error + if len(unknownTypes) > 0 { + err = fmt.Errorf("statusz: unknown media type(s) %v, excluding from supported types", unknownTypes) + } + + return &statuszCodecFactory{ + CodecFactory: codecFactory, + supportedMediaTypes: filtered, + }, err } func (f *statuszCodecFactory) SupportedMediaTypes() []runtime.SerializerInfo { - allTypes := f.CodecFactory.SupportedMediaTypes() - filtered := make([]runtime.SerializerInfo, 0, len(allTypes)) - for _, info := range allTypes { - if info.MediaType == runtime.ContentTypeProtobuf { - continue - } - filtered = append(filtered, info) - } - return filtered + return f.supportedMediaTypes } func handleStatusz(componentName string, reg statuszRegistry, serializer runtime.NegotiatedSerializer, restrictions negotiate.StatuszEndpointRestrictions) http.HandlerFunc { diff --git a/staging/src/k8s.io/apiserver/pkg/server/statusz/statusz_test.go b/staging/src/k8s.io/apiserver/pkg/server/statusz/statusz_test.go index 77938027819..9b0b5515e2b 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/statusz/statusz_test.go +++ b/staging/src/k8s.io/apiserver/pkg/server/statusz/statusz_test.go @@ -27,7 +27,9 @@ import ( "github.com/google/go-cmp/cmp" + "k8s.io/apimachinery/pkg/runtime" cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/version" "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/apiserver/pkg/features" @@ -443,3 +445,18 @@ func timeEqual() cmp.Option { return expectedTime.Truncate(time.Second).Equal(actualTime.Truncate(time.Second)) }) } + +// TestNewStatuszCodecFactory ensures all media types in the codec factory +// are explicitly handled. If this test fails, a new media type was added +// to the codec factory and needs to be explicitly added to the supported +// or unsupported list in newStatuszCodecFactory. +func TestNewStatuszCodecFactory(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CBORServingAndStorage, true) + scheme := runtime.NewScheme() + utilruntime.Must(v1alpha1.AddToScheme(scheme)) + + _, err := newStatuszCodecFactory(scheme, "", nil) + if err != nil { + t.Fatalf("unknown media type(s) detected - update newStatuszCodecFactory to explicitly handle them: %v", err) + } +}