mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 10:51:29 +00:00
Fallback query param verifier
This commit is contained in:
parent
3307b39bba
commit
f5865043ed
@ -18,6 +18,7 @@ require (
|
||||
k8s.io/api v0.0.0
|
||||
k8s.io/apimachinery v0.0.0
|
||||
k8s.io/client-go v0.0.0
|
||||
k8s.io/klog/v2 v2.90.1
|
||||
k8s.io/kube-openapi v0.0.0-20230303024457-afdc3dddf62d
|
||||
k8s.io/utils v0.0.0-20230209194617-a36077c30491
|
||||
sigs.k8s.io/kustomize/api v0.12.1
|
||||
@ -62,7 +63,6 @@ require (
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/klog/v2 v2.90.1 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
)
|
||||
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package resource
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// fallbackQueryParamVerifier encapsulates the primary Verifier that
|
||||
// is invoked, and the secondary/fallback Verifier.
|
||||
type fallbackQueryParamVerifier struct {
|
||||
primary Verifier
|
||||
secondary Verifier
|
||||
}
|
||||
|
||||
var _ Verifier = &fallbackQueryParamVerifier{}
|
||||
|
||||
// NewFallbackQueryParamVerifier returns a new Verifier which will invoke the
|
||||
// initial/primary Verifier. If the primary Verifier is "NotFound", then the
|
||||
// secondary Verifier is invoked as a fallback.
|
||||
func NewFallbackQueryParamVerifier(primary Verifier, secondary Verifier) Verifier {
|
||||
return &fallbackQueryParamVerifier{
|
||||
primary: primary,
|
||||
secondary: secondary,
|
||||
}
|
||||
}
|
||||
|
||||
// HasSupport returns an error if the passed GVK does not support the
|
||||
// query param (fieldValidation), as determined by the primary and
|
||||
// secondary OpenAPI endpoints. The primary endoint is checked first,
|
||||
// but if it not found, the secondary attempts to determine support.
|
||||
// If the GVK supports the query param, nil is returned.
|
||||
func (f *fallbackQueryParamVerifier) HasSupport(gvk schema.GroupVersionKind) error {
|
||||
err := f.primary.HasSupport(gvk)
|
||||
if errors.IsNotFound(err) {
|
||||
klog.V(7).Infoln("openapi v3 endpoint not found...falling back to legacy")
|
||||
err = f.secondary.HasSupport(gvk)
|
||||
}
|
||||
return err
|
||||
}
|
@ -0,0 +1,271 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package resource
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/openapi/cached"
|
||||
"k8s.io/client-go/openapi/openapitest"
|
||||
"k8s.io/client-go/openapi3"
|
||||
)
|
||||
|
||||
func TestFallbackQueryParamVerifier_PrimaryNoFallback(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
crds []schema.GroupKind // CRDFinder returns these CRD's
|
||||
gvk schema.GroupVersionKind // GVK whose OpenAPI spec is checked
|
||||
queryParam VerifiableQueryParam // Usually "fieldValidation"
|
||||
primaryError error
|
||||
expectedSupports bool
|
||||
}{
|
||||
"Field validation query param is supported for batch/v1/Job, primary verifier": {
|
||||
crds: []schema.GroupKind{},
|
||||
gvk: schema.GroupVersionKind{
|
||||
Group: "batch",
|
||||
Version: "v1",
|
||||
Kind: "Job",
|
||||
},
|
||||
queryParam: QueryParamFieldValidation,
|
||||
expectedSupports: true,
|
||||
},
|
||||
"Field validation query param supported for core/v1/Namespace, primary verifier": {
|
||||
crds: []schema.GroupKind{},
|
||||
gvk: schema.GroupVersionKind{
|
||||
Group: "",
|
||||
Version: "v1",
|
||||
Kind: "Namespace",
|
||||
},
|
||||
queryParam: QueryParamFieldValidation,
|
||||
expectedSupports: true,
|
||||
},
|
||||
"Field validation unsupported for unknown GVK in primary verifier": {
|
||||
crds: []schema.GroupKind{},
|
||||
gvk: schema.GroupVersionKind{
|
||||
Group: "bad",
|
||||
Version: "v1",
|
||||
Kind: "Uknown",
|
||||
},
|
||||
queryParam: QueryParamFieldValidation,
|
||||
expectedSupports: false,
|
||||
},
|
||||
"Unknown query param unsupported (for all GVK's) in primary verifier": {
|
||||
crds: []schema.GroupKind{},
|
||||
gvk: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
queryParam: "UnknownQueryParam",
|
||||
expectedSupports: false,
|
||||
},
|
||||
"Field validation query param supported for found CRD in primary verifier": {
|
||||
crds: []schema.GroupKind{
|
||||
{
|
||||
Group: "example.com",
|
||||
Kind: "ExampleCRD",
|
||||
},
|
||||
},
|
||||
// GVK matches above CRD GroupKind
|
||||
gvk: schema.GroupVersionKind{
|
||||
Group: "example.com",
|
||||
Version: "v1",
|
||||
Kind: "ExampleCRD",
|
||||
},
|
||||
queryParam: QueryParamFieldValidation,
|
||||
expectedSupports: true,
|
||||
},
|
||||
"Field validation query param unsupported for missing CRD in primary verifier": {
|
||||
crds: []schema.GroupKind{
|
||||
{
|
||||
Group: "different.com",
|
||||
Kind: "DifferentCRD",
|
||||
},
|
||||
},
|
||||
// GVK does NOT match above CRD GroupKind
|
||||
gvk: schema.GroupVersionKind{
|
||||
Group: "example.com",
|
||||
Version: "v1",
|
||||
Kind: "ExampleCRD",
|
||||
},
|
||||
queryParam: QueryParamFieldValidation,
|
||||
expectedSupports: false,
|
||||
},
|
||||
"List GVK is specifically unsupported in primary verfier": {
|
||||
crds: []schema.GroupKind{},
|
||||
gvk: schema.GroupVersionKind{
|
||||
Group: "",
|
||||
Version: "v1",
|
||||
Kind: "List",
|
||||
},
|
||||
queryParam: QueryParamFieldValidation,
|
||||
expectedSupports: false,
|
||||
},
|
||||
}
|
||||
|
||||
root := openapi3.NewRoot(cached.NewClient(openapitest.NewFileClient(t)))
|
||||
for tn, tc := range tests {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
primary := createFakeV3Verifier(tc.crds, root, tc.queryParam)
|
||||
secondary := createFakeLegacyVerifier(tc.crds, &fakeSchema, tc.queryParam)
|
||||
verifier := NewFallbackQueryParamVerifier(primary, secondary)
|
||||
err := verifier.HasSupport(tc.gvk)
|
||||
if tc.expectedSupports && err != nil {
|
||||
t.Errorf("Expected supports, but returned err for GVK (%s)", tc.gvk)
|
||||
} else if !tc.expectedSupports && err == nil {
|
||||
t.Errorf("Expected not supports, but returned no err for GVK (%s)", tc.gvk)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFallbackQueryParamVerifier_SecondaryFallback(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
crds []schema.GroupKind // CRDFinder returns these CRD's
|
||||
gvk schema.GroupVersionKind // GVK whose OpenAPI spec is checked
|
||||
queryParam VerifiableQueryParam // Usually "fieldValidation"
|
||||
primaryError error
|
||||
expectedSupports bool
|
||||
}{
|
||||
"Field validation query param is supported for batch/v1/Job, secondary verifier": {
|
||||
crds: []schema.GroupKind{},
|
||||
gvk: schema.GroupVersionKind{
|
||||
Group: "batch",
|
||||
Version: "v1",
|
||||
Kind: "Job",
|
||||
},
|
||||
queryParam: QueryParamFieldValidation,
|
||||
expectedSupports: true,
|
||||
},
|
||||
"Field validation query param supported for core/v1/Namespace, secondary verifier": {
|
||||
crds: []schema.GroupKind{},
|
||||
gvk: schema.GroupVersionKind{
|
||||
Group: "",
|
||||
Version: "v1",
|
||||
Kind: "Namespace",
|
||||
},
|
||||
queryParam: QueryParamFieldValidation,
|
||||
expectedSupports: true,
|
||||
},
|
||||
"Field validation unsupported for unknown GVK, secondary verifier": {
|
||||
crds: []schema.GroupKind{},
|
||||
gvk: schema.GroupVersionKind{
|
||||
Group: "bad",
|
||||
Version: "v1",
|
||||
Kind: "Uknown",
|
||||
},
|
||||
queryParam: QueryParamFieldValidation,
|
||||
expectedSupports: false,
|
||||
},
|
||||
"Unknown query param unsupported (for all GVK's), secondary verifier": {
|
||||
crds: []schema.GroupKind{},
|
||||
gvk: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
queryParam: "UnknownQueryParam",
|
||||
expectedSupports: false,
|
||||
},
|
||||
"Field validation query param supported for found CRD, secondary verifier": {
|
||||
crds: []schema.GroupKind{
|
||||
{
|
||||
Group: "example.com",
|
||||
Kind: "ExampleCRD",
|
||||
},
|
||||
},
|
||||
// GVK matches above CRD GroupKind
|
||||
gvk: schema.GroupVersionKind{
|
||||
Group: "example.com",
|
||||
Version: "v1",
|
||||
Kind: "ExampleCRD",
|
||||
},
|
||||
queryParam: QueryParamFieldValidation,
|
||||
expectedSupports: true,
|
||||
},
|
||||
"Field validation query param unsupported for missing CRD, secondary verifier": {
|
||||
crds: []schema.GroupKind{
|
||||
{
|
||||
Group: "different.com",
|
||||
Kind: "DifferentCRD",
|
||||
},
|
||||
},
|
||||
// GVK does NOT match above CRD GroupKind
|
||||
gvk: schema.GroupVersionKind{
|
||||
Group: "example.com",
|
||||
Version: "v1",
|
||||
Kind: "ExampleCRD",
|
||||
},
|
||||
queryParam: QueryParamFieldValidation,
|
||||
expectedSupports: false,
|
||||
},
|
||||
"List GVK is specifically unsupported": {
|
||||
crds: []schema.GroupKind{},
|
||||
gvk: schema.GroupVersionKind{
|
||||
Group: "",
|
||||
Version: "v1",
|
||||
Kind: "List",
|
||||
},
|
||||
queryParam: QueryParamFieldValidation,
|
||||
expectedSupports: false,
|
||||
},
|
||||
}
|
||||
|
||||
// Primary OpenAPI client always returns "NotFound" error, so secondary verifier is used.
|
||||
fakeOpenAPIClient := openapitest.NewFakeClient()
|
||||
fakeOpenAPIClient.ForcedErr = errors.NewNotFound(schema.GroupResource{}, "OpenAPI V3 endpoint not found")
|
||||
root := openapi3.NewRoot(fakeOpenAPIClient)
|
||||
for tn, tc := range tests {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
primary := createFakeV3Verifier(tc.crds, root, tc.queryParam)
|
||||
secondary := createFakeLegacyVerifier(tc.crds, &fakeSchema, tc.queryParam)
|
||||
verifier := NewFallbackQueryParamVerifier(primary, secondary)
|
||||
err := verifier.HasSupport(tc.gvk)
|
||||
if tc.expectedSupports && err != nil {
|
||||
t.Errorf("Expected supports, but returned err for GVK (%s)", tc.gvk)
|
||||
} else if !tc.expectedSupports && err == nil {
|
||||
t.Errorf("Expected not supports, but returned no err for GVK (%s)", tc.gvk)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// createFakeV3Verifier returns a fake OpenAPI V3 queryParamVerifierV3 struct
|
||||
// filled in with passed values; implements Verifier interface.
|
||||
func createFakeV3Verifier(crds []schema.GroupKind, root openapi3.Root, queryParam VerifiableQueryParam) Verifier {
|
||||
return &queryParamVerifierV3{
|
||||
finder: NewCRDFinder(func() ([]schema.GroupKind, error) {
|
||||
return crds, nil
|
||||
}),
|
||||
root: root,
|
||||
queryParam: queryParam,
|
||||
}
|
||||
}
|
||||
|
||||
// createFakeLegacyVerifier returns a fake QueryParamVerifier struct for legacy
|
||||
// OpenAPI V2; implements Verifier interface.
|
||||
func createFakeLegacyVerifier(crds []schema.GroupKind, fakeSchema discovery.OpenAPISchemaInterface, queryParam VerifiableQueryParam) Verifier {
|
||||
return &QueryParamVerifier{
|
||||
finder: NewCRDFinder(func() ([]schema.GroupKind, error) {
|
||||
return crds, nil
|
||||
}),
|
||||
openAPIGetter: fakeSchema,
|
||||
queryParam: queryParam,
|
||||
}
|
||||
}
|
@ -30,6 +30,7 @@ import (
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/openapi/cached"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/kubectl/pkg/util/openapi"
|
||||
@ -167,8 +168,18 @@ func (f *factoryImpl) Validator(validationDirective string) (validation.Schema,
|
||||
return nil, err
|
||||
}
|
||||
// Create the FieldValidationVerifier for use in the ParamVerifyingSchema.
|
||||
verifier := resource.NewQueryParamVerifier(dynamicClient, f.openAPIGetter(), resource.QueryParamFieldValidation)
|
||||
return validation.NewParamVerifyingSchema(schema, verifier, string(validationDirective)), nil
|
||||
discoveryClient, err := f.ToDiscoveryClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Memory-cache the OpenAPI V3 responses. The disk cache behavior is determined by
|
||||
// the discovery client.
|
||||
oapiV3Client := cached.NewClient(discoveryClient.OpenAPIV3())
|
||||
queryParam := resource.QueryParamFieldValidation
|
||||
primary := resource.NewQueryParamVerifierV3(dynamicClient, oapiV3Client, queryParam)
|
||||
secondary := resource.NewQueryParamVerifier(dynamicClient, f.openAPIGetter(), queryParam)
|
||||
fallback := resource.NewFallbackQueryParamVerifier(primary, secondary)
|
||||
return validation.NewParamVerifyingSchema(schema, fallback, string(validationDirective)), nil
|
||||
}
|
||||
|
||||
// OpenAPISchema returns metadata and structural information about
|
||||
|
Loading…
Reference in New Issue
Block a user