mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-10 12:32:03 +00:00
QueryParamVerifier falls back on invalid v3 document
This commit is contained in:
parent
5d524f3dc3
commit
6f23c77408
@ -17,7 +17,6 @@ limitations under the License.
|
|||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
)
|
)
|
||||||
@ -44,12 +43,16 @@ func NewFallbackQueryParamVerifier(primary Verifier, secondary Verifier) Verifie
|
|||||||
// HasSupport returns an error if the passed GVK does not support the
|
// HasSupport returns an error if the passed GVK does not support the
|
||||||
// query param (fieldValidation), as determined by the primary and
|
// query param (fieldValidation), as determined by the primary and
|
||||||
// secondary OpenAPI endpoints. The primary endoint is checked first,
|
// secondary OpenAPI endpoints. The primary endoint is checked first,
|
||||||
// but if it not found, the secondary attempts to determine support.
|
// but if there is an error retrieving the OpenAPI V3 document, the
|
||||||
// If the GVK supports the query param, nil is returned.
|
// secondary attempts to determine support. If the GVK supports the query param,
|
||||||
|
// nil is returned.
|
||||||
func (f *fallbackQueryParamVerifier) HasSupport(gvk schema.GroupVersionKind) error {
|
func (f *fallbackQueryParamVerifier) HasSupport(gvk schema.GroupVersionKind) error {
|
||||||
err := f.primary.HasSupport(gvk)
|
err := f.primary.HasSupport(gvk)
|
||||||
if errors.IsNotFound(err) {
|
// If an error was returned from the primary OpenAPI endpoint,
|
||||||
klog.V(7).Infoln("openapi v3 endpoint not found...falling back to legacy")
|
// we fallback to check the secondary OpenAPI endpoint for an
|
||||||
|
// any error *except* "paramUnsupportedError".
|
||||||
|
if err != nil && !IsParamUnsupportedError(err) {
|
||||||
|
klog.V(7).Infof("openapi v3 error...falling back to legacy: %s", err)
|
||||||
err = f.secondary.HasSupport(gvk)
|
err = f.secondary.HasSupport(gvk)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
@ -32,7 +33,6 @@ func TestFallbackQueryParamVerifier_PrimaryNoFallback(t *testing.T) {
|
|||||||
crds []schema.GroupKind // CRDFinder returns these CRD's
|
crds []schema.GroupKind // CRDFinder returns these CRD's
|
||||||
gvk schema.GroupVersionKind // GVK whose OpenAPI spec is checked
|
gvk schema.GroupVersionKind // GVK whose OpenAPI spec is checked
|
||||||
queryParam VerifiableQueryParam // Usually "fieldValidation"
|
queryParam VerifiableQueryParam // Usually "fieldValidation"
|
||||||
primaryError error
|
|
||||||
expectedSupports bool
|
expectedSupports bool
|
||||||
}{
|
}{
|
||||||
"Field validation query param is supported for batch/v1/Job, primary verifier": {
|
"Field validation query param is supported for batch/v1/Job, primary verifier": {
|
||||||
@ -123,7 +123,8 @@ func TestFallbackQueryParamVerifier_PrimaryNoFallback(t *testing.T) {
|
|||||||
for tn, tc := range tests {
|
for tn, tc := range tests {
|
||||||
t.Run(tn, func(t *testing.T) {
|
t.Run(tn, func(t *testing.T) {
|
||||||
primary := createFakeV3Verifier(tc.crds, root, tc.queryParam)
|
primary := createFakeV3Verifier(tc.crds, root, tc.queryParam)
|
||||||
secondary := createFakeLegacyVerifier(tc.crds, &fakeSchema, tc.queryParam)
|
// secondary verifier should not be called.
|
||||||
|
secondary := &failingVerifier{name: "secondary", t: t}
|
||||||
verifier := NewFallbackQueryParamVerifier(primary, secondary)
|
verifier := NewFallbackQueryParamVerifier(primary, secondary)
|
||||||
err := verifier.HasSupport(tc.gvk)
|
err := verifier.HasSupport(tc.gvk)
|
||||||
if tc.expectedSupports && err != nil {
|
if tc.expectedSupports && err != nil {
|
||||||
@ -151,6 +152,29 @@ func TestFallbackQueryParamVerifier_SecondaryFallback(t *testing.T) {
|
|||||||
Kind: "Job",
|
Kind: "Job",
|
||||||
},
|
},
|
||||||
queryParam: QueryParamFieldValidation,
|
queryParam: QueryParamFieldValidation,
|
||||||
|
primaryError: errors.NewNotFound(schema.GroupResource{}, "OpenAPI V3 endpoint not found"),
|
||||||
|
expectedSupports: true,
|
||||||
|
},
|
||||||
|
"Field validation query param is supported for batch/v1/Job, invalid v3 document error": {
|
||||||
|
crds: []schema.GroupKind{},
|
||||||
|
gvk: schema.GroupVersionKind{
|
||||||
|
Group: "batch",
|
||||||
|
Version: "v1",
|
||||||
|
Kind: "Job",
|
||||||
|
},
|
||||||
|
queryParam: QueryParamFieldValidation,
|
||||||
|
primaryError: fmt.Errorf("Invalid OpenAPI V3 document"),
|
||||||
|
expectedSupports: true,
|
||||||
|
},
|
||||||
|
"Field validation query param is supported for batch/v1/Job, timeout error": {
|
||||||
|
crds: []schema.GroupKind{},
|
||||||
|
gvk: schema.GroupVersionKind{
|
||||||
|
Group: "batch",
|
||||||
|
Version: "v1",
|
||||||
|
Kind: "Job",
|
||||||
|
},
|
||||||
|
queryParam: QueryParamFieldValidation,
|
||||||
|
primaryError: fmt.Errorf("timeout"),
|
||||||
expectedSupports: true,
|
expectedSupports: true,
|
||||||
},
|
},
|
||||||
"Field validation query param supported for core/v1/Namespace, secondary verifier": {
|
"Field validation query param supported for core/v1/Namespace, secondary verifier": {
|
||||||
@ -161,6 +185,7 @@ func TestFallbackQueryParamVerifier_SecondaryFallback(t *testing.T) {
|
|||||||
Kind: "Namespace",
|
Kind: "Namespace",
|
||||||
},
|
},
|
||||||
queryParam: QueryParamFieldValidation,
|
queryParam: QueryParamFieldValidation,
|
||||||
|
primaryError: errors.NewNotFound(schema.GroupResource{}, "OpenAPI V3 endpoint not found"),
|
||||||
expectedSupports: true,
|
expectedSupports: true,
|
||||||
},
|
},
|
||||||
"Field validation unsupported for unknown GVK, secondary verifier": {
|
"Field validation unsupported for unknown GVK, secondary verifier": {
|
||||||
@ -171,6 +196,18 @@ func TestFallbackQueryParamVerifier_SecondaryFallback(t *testing.T) {
|
|||||||
Kind: "Uknown",
|
Kind: "Uknown",
|
||||||
},
|
},
|
||||||
queryParam: QueryParamFieldValidation,
|
queryParam: QueryParamFieldValidation,
|
||||||
|
primaryError: errors.NewNotFound(schema.GroupResource{}, "OpenAPI V3 endpoint not found"),
|
||||||
|
expectedSupports: false,
|
||||||
|
},
|
||||||
|
"Field validation unsupported for unknown GVK, invalid document causes secondary verifier": {
|
||||||
|
crds: []schema.GroupKind{},
|
||||||
|
gvk: schema.GroupVersionKind{
|
||||||
|
Group: "bad",
|
||||||
|
Version: "v1",
|
||||||
|
Kind: "Uknown",
|
||||||
|
},
|
||||||
|
queryParam: QueryParamFieldValidation,
|
||||||
|
primaryError: fmt.Errorf("Invalid OpenAPI V3 document"),
|
||||||
expectedSupports: false,
|
expectedSupports: false,
|
||||||
},
|
},
|
||||||
"Unknown query param unsupported (for all GVK's), secondary verifier": {
|
"Unknown query param unsupported (for all GVK's), secondary verifier": {
|
||||||
@ -181,6 +218,7 @@ func TestFallbackQueryParamVerifier_SecondaryFallback(t *testing.T) {
|
|||||||
Kind: "Deployment",
|
Kind: "Deployment",
|
||||||
},
|
},
|
||||||
queryParam: "UnknownQueryParam",
|
queryParam: "UnknownQueryParam",
|
||||||
|
primaryError: errors.NewNotFound(schema.GroupResource{}, "OpenAPI V3 endpoint not found"),
|
||||||
expectedSupports: false,
|
expectedSupports: false,
|
||||||
},
|
},
|
||||||
"Field validation query param supported for found CRD, secondary verifier": {
|
"Field validation query param supported for found CRD, secondary verifier": {
|
||||||
@ -197,6 +235,7 @@ func TestFallbackQueryParamVerifier_SecondaryFallback(t *testing.T) {
|
|||||||
Kind: "ExampleCRD",
|
Kind: "ExampleCRD",
|
||||||
},
|
},
|
||||||
queryParam: QueryParamFieldValidation,
|
queryParam: QueryParamFieldValidation,
|
||||||
|
primaryError: errors.NewNotFound(schema.GroupResource{}, "OpenAPI V3 endpoint not found"),
|
||||||
expectedSupports: true,
|
expectedSupports: true,
|
||||||
},
|
},
|
||||||
"Field validation query param unsupported for missing CRD, secondary verifier": {
|
"Field validation query param unsupported for missing CRD, secondary verifier": {
|
||||||
@ -213,6 +252,7 @@ func TestFallbackQueryParamVerifier_SecondaryFallback(t *testing.T) {
|
|||||||
Kind: "ExampleCRD",
|
Kind: "ExampleCRD",
|
||||||
},
|
},
|
||||||
queryParam: QueryParamFieldValidation,
|
queryParam: QueryParamFieldValidation,
|
||||||
|
primaryError: errors.NewNotFound(schema.GroupResource{}, "OpenAPI V3 endpoint not found"),
|
||||||
expectedSupports: false,
|
expectedSupports: false,
|
||||||
},
|
},
|
||||||
"List GVK is specifically unsupported": {
|
"List GVK is specifically unsupported": {
|
||||||
@ -223,16 +263,17 @@ func TestFallbackQueryParamVerifier_SecondaryFallback(t *testing.T) {
|
|||||||
Kind: "List",
|
Kind: "List",
|
||||||
},
|
},
|
||||||
queryParam: QueryParamFieldValidation,
|
queryParam: QueryParamFieldValidation,
|
||||||
|
primaryError: errors.NewNotFound(schema.GroupResource{}, "OpenAPI V3 endpoint not found"),
|
||||||
expectedSupports: false,
|
expectedSupports: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Primary OpenAPI client always returns "NotFound" error, so secondary verifier is used.
|
// Primary OpenAPI client always returns "NotFound" error, so secondary verifier is used.
|
||||||
fakeOpenAPIClient := openapitest.NewFakeClient()
|
fakeOpenAPIClient := openapitest.NewFakeClient()
|
||||||
fakeOpenAPIClient.ForcedErr = errors.NewNotFound(schema.GroupResource{}, "OpenAPI V3 endpoint not found")
|
|
||||||
root := openapi3.NewRoot(fakeOpenAPIClient)
|
root := openapi3.NewRoot(fakeOpenAPIClient)
|
||||||
for tn, tc := range tests {
|
for tn, tc := range tests {
|
||||||
t.Run(tn, func(t *testing.T) {
|
t.Run(tn, func(t *testing.T) {
|
||||||
|
fakeOpenAPIClient.ForcedErr = tc.primaryError
|
||||||
primary := createFakeV3Verifier(tc.crds, root, tc.queryParam)
|
primary := createFakeV3Verifier(tc.crds, root, tc.queryParam)
|
||||||
secondary := createFakeLegacyVerifier(tc.crds, &fakeSchema, tc.queryParam)
|
secondary := createFakeLegacyVerifier(tc.crds, &fakeSchema, tc.queryParam)
|
||||||
verifier := NewFallbackQueryParamVerifier(primary, secondary)
|
verifier := NewFallbackQueryParamVerifier(primary, secondary)
|
||||||
@ -269,3 +310,14 @@ func createFakeLegacyVerifier(crds []schema.GroupKind, fakeSchema discovery.Open
|
|||||||
queryParam: queryParam,
|
queryParam: queryParam,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// failingVerifier always crashes when called; implements Verifier
|
||||||
|
type failingVerifier struct {
|
||||||
|
name string
|
||||||
|
t *testing.T
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *failingVerifier) HasSupport(gvk schema.GroupVersionKind) error {
|
||||||
|
c.t.Fatalf("%s verifier should not be called", c.name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -17,6 +17,8 @@ limitations under the License.
|
|||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/client-go/dynamic"
|
"k8s.io/client-go/dynamic"
|
||||||
"k8s.io/client-go/openapi"
|
"k8s.io/client-go/openapi"
|
||||||
@ -62,6 +64,11 @@ func (v *queryParamVerifierV3) HasSupport(gvk schema.GroupVersionKind) error {
|
|||||||
}
|
}
|
||||||
gvSpec, err := v.root.GVSpec(gvk.GroupVersion())
|
gvSpec, err := v.root.GVSpec(gvk.GroupVersion())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
// Even if there is no error returned fetching OpenAPI V3 document,
|
||||||
|
// check if the returned document is valid before checking support.
|
||||||
|
if gvSpec == nil || gvSpec.Paths == nil {
|
||||||
|
return fmt.Errorf("Invalid OpenAPI V3 document")
|
||||||
|
}
|
||||||
if supports := supportsQueryParamV3(gvSpec, gvk, v.queryParam); supports {
|
if supports := supportsQueryParamV3(gvSpec, gvk, v.queryParam); supports {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -106,9 +113,6 @@ func hasGVKExtensionV3(extensions spec.Extensions, gvk schema.GroupVersionKind)
|
|||||||
// the PATCH end-point. Returns true if the query param is supported by the
|
// the PATCH end-point. Returns true if the query param is supported by the
|
||||||
// spec for the passed GVK; false otherwise.
|
// spec for the passed GVK; false otherwise.
|
||||||
func supportsQueryParamV3(doc *spec3.OpenAPI, gvk schema.GroupVersionKind, queryParam VerifiableQueryParam) bool {
|
func supportsQueryParamV3(doc *spec3.OpenAPI, gvk schema.GroupVersionKind, queryParam VerifiableQueryParam) bool {
|
||||||
if doc == nil || doc.Paths == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, path := range doc.Paths.Paths {
|
for _, path := range doc.Paths.Paths {
|
||||||
// If operation is not PATCH, then continue.
|
// If operation is not PATCH, then continue.
|
||||||
op := path.PathProps.Patch
|
op := path.PathProps.Patch
|
||||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
@ -141,24 +142,18 @@ func TestInvalidOpenAPIV3Document(t *testing.T) {
|
|||||||
tests := map[string]struct {
|
tests := map[string]struct {
|
||||||
spec *spec3.OpenAPI
|
spec *spec3.OpenAPI
|
||||||
}{
|
}{
|
||||||
"nil document correctly returns Unsupported error": {
|
"nil document returns error": {
|
||||||
spec: nil,
|
spec: nil,
|
||||||
},
|
},
|
||||||
"empty document correctly returns Unsupported error": {
|
"empty document returns error": {
|
||||||
spec: &spec3.OpenAPI{},
|
spec: &spec3.OpenAPI{},
|
||||||
},
|
},
|
||||||
"minimal document correctly returns Unsupported error": {
|
"minimal document returns error": {
|
||||||
spec: &spec3.OpenAPI{
|
spec: &spec3.OpenAPI{
|
||||||
Version: "openapi 3.0.0",
|
Version: "openapi 3.0.0",
|
||||||
Paths: nil,
|
Paths: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"document with empty Paths correctly returns Unsupported error": {
|
|
||||||
spec: &spec3.OpenAPI{
|
|
||||||
Version: "openapi 3.0.0",
|
|
||||||
Paths: &spec3.Paths{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gvk := schema.GroupVersionKind{
|
gvk := schema.GroupVersionKind{
|
||||||
@ -177,8 +172,8 @@ func TestInvalidOpenAPIV3Document(t *testing.T) {
|
|||||||
queryParam: QueryParamFieldValidation,
|
queryParam: QueryParamFieldValidation,
|
||||||
}
|
}
|
||||||
err := verifier.HasSupport(gvk)
|
err := verifier.HasSupport(gvk)
|
||||||
if err == nil {
|
if !strings.Contains(err.Error(), "Invalid OpenAPI V3 document") {
|
||||||
t.Errorf("Expected not supports error, but none received.")
|
t.Errorf("Expected invalid document error, but none received.")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user