mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-19 09:52:49 +00:00
Merge pull request #116392 from seans3/fallback-verifier
Fallback query param verifier
This commit is contained in:
commit
f5ddaa152e
@ -18,6 +18,7 @@ require (
|
|||||||
k8s.io/api v0.0.0
|
k8s.io/api v0.0.0
|
||||||
k8s.io/apimachinery v0.0.0
|
k8s.io/apimachinery v0.0.0
|
||||||
k8s.io/client-go 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/kube-openapi v0.0.0-20230303024457-afdc3dddf62d
|
||||||
k8s.io/utils v0.0.0-20230209194617-a36077c30491
|
k8s.io/utils v0.0.0-20230209194617-a36077c30491
|
||||||
sigs.k8s.io/kustomize/api v0.12.1
|
sigs.k8s.io/kustomize/api v0.12.1
|
||||||
@ -62,7 +63,6 @@ require (
|
|||||||
google.golang.org/protobuf v1.28.1 // indirect
|
google.golang.org/protobuf v1.28.1 // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.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/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // 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/discovery"
|
||||||
"k8s.io/client-go/dynamic"
|
"k8s.io/client-go/dynamic"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/openapi/cached"
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
"k8s.io/kubectl/pkg/util/openapi"
|
"k8s.io/kubectl/pkg/util/openapi"
|
||||||
@ -167,8 +168,18 @@ func (f *factoryImpl) Validator(validationDirective string) (validation.Schema,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Create the FieldValidationVerifier for use in the ParamVerifyingSchema.
|
// Create the FieldValidationVerifier for use in the ParamVerifyingSchema.
|
||||||
verifier := resource.NewQueryParamVerifier(dynamicClient, f.openAPIGetter(), resource.QueryParamFieldValidation)
|
discoveryClient, err := f.ToDiscoveryClient()
|
||||||
return validation.NewParamVerifyingSchema(schema, verifier, string(validationDirective)), nil
|
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
|
// OpenAPISchema returns metadata and structural information about
|
||||||
|
Loading…
Reference in New Issue
Block a user