mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-20 18:31:15 +00:00
Merge pull request #115119 from seans3/openapi-query-param-v3
Open API V3 version of QueryParamVerifier
This commit is contained in:
commit
30df862563
@ -0,0 +1,126 @@
|
||||
/*
|
||||
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/runtime/schema"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/openapi"
|
||||
"k8s.io/client-go/openapi3"
|
||||
"k8s.io/kube-openapi/pkg/spec3"
|
||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||
)
|
||||
|
||||
var _ Verifier = &queryParamVerifierV3{}
|
||||
|
||||
// NewQueryParamVerifierV3 returns a pointer to the created queryParamVerifier3 struct,
|
||||
// which implements the Verifier interface. The caching characteristics of the
|
||||
// OpenAPI V3 specs are determined by the passed oapiClient. For memory caching, the
|
||||
// client should be wrapped beforehand as: cached.NewClient(oapiClient). The disk
|
||||
// caching is determined by the discovery client the oapiClient is created from.
|
||||
func NewQueryParamVerifierV3(dynamicClient dynamic.Interface, oapiClient openapi.Client, queryParam VerifiableQueryParam) Verifier {
|
||||
return &queryParamVerifierV3{
|
||||
finder: NewCRDFinder(CRDFromDynamic(dynamicClient)),
|
||||
root: openapi3.NewRoot(oapiClient),
|
||||
queryParam: queryParam,
|
||||
}
|
||||
}
|
||||
|
||||
// queryParamVerifierV3 encapsulates info necessary to determine if
|
||||
// the queryParam is a parameter for the Patch endpoint for a
|
||||
// passed GVK.
|
||||
type queryParamVerifierV3 struct {
|
||||
finder CRDFinder
|
||||
root openapi3.Root
|
||||
queryParam VerifiableQueryParam
|
||||
}
|
||||
|
||||
var namespaceGVK = schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"}
|
||||
|
||||
// HasSupport returns nil error if the passed GVK supports the parameter
|
||||
// (stored in struct; usually "fieldValidation") for Patch endpoint.
|
||||
// Returns an error if the passed GVK does not support the query param,
|
||||
// or if another error occurred. If the Open API V3 spec for a CRD is not
|
||||
// found, then the spec for Namespace is checked for query param support instead.
|
||||
func (v *queryParamVerifierV3) HasSupport(gvk schema.GroupVersionKind) error {
|
||||
gvSpec, err := v.root.GVSpec(gvk.GroupVersion())
|
||||
if err == nil {
|
||||
if supports := supportsQueryParamV3(gvSpec, gvk, v.queryParam); supports {
|
||||
return nil
|
||||
}
|
||||
return NewParamUnsupportedError(gvk, v.queryParam)
|
||||
}
|
||||
if _, isErr := err.(*openapi3.GroupVersionNotFoundError); !isErr {
|
||||
return err
|
||||
}
|
||||
// If the spec for the passed GVK is not found, then check if it is a CRD.
|
||||
// For CRD's substitute Namespace OpenAPI V3 spec to check if query param is supported.
|
||||
if found, _ := v.finder.HasCRD(gvk.GroupKind()); found {
|
||||
namespaceSpec, err := v.root.GVSpec(namespaceGVK.GroupVersion())
|
||||
if err != nil {
|
||||
// If error retrieving Namespace spec, propagate error.
|
||||
return err
|
||||
}
|
||||
if supports := supportsQueryParamV3(namespaceSpec, namespaceGVK, v.queryParam); supports {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return NewParamUnsupportedError(gvk, v.queryParam)
|
||||
}
|
||||
|
||||
// hasGVKExtensionV3 returns true if the passed OpenAPI extensions map contains
|
||||
// the passed GVK; false otherwise.
|
||||
func hasGVKExtensionV3(extensions spec.Extensions, gvk schema.GroupVersionKind) bool {
|
||||
var oapiGVK map[string]string
|
||||
err := extensions.GetObject("x-kubernetes-group-version-kind", &oapiGVK)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if oapiGVK["group"] == gvk.Group &&
|
||||
oapiGVK["version"] == gvk.Version &&
|
||||
oapiGVK["kind"] == gvk.Kind {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// supportsQueryParam is a method that let's us look in the OpenAPI if the
|
||||
// specific group-version-kind supports the specific query parameter for
|
||||
// the PATCH end-point. Returns true if the query param is supported by the
|
||||
// spec for the passed GVK; false otherwise.
|
||||
func supportsQueryParamV3(doc *spec3.OpenAPI, gvk schema.GroupVersionKind, queryParam VerifiableQueryParam) bool {
|
||||
for _, path := range doc.Paths.Paths {
|
||||
// If operation is not PATCH, then continue.
|
||||
op := path.PathProps.Patch
|
||||
if op == nil {
|
||||
continue
|
||||
}
|
||||
// Is this PATCH operation for the passed GVK?
|
||||
if !hasGVKExtensionV3(op.VendorExtensible.Extensions, gvk) {
|
||||
continue
|
||||
}
|
||||
// Now look for the query parameter among the parameters
|
||||
// for the PATCH operation.
|
||||
for _, param := range op.OperationProps.Parameters {
|
||||
if param.ParameterProps.Name == string(queryParam) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
/*
|
||||
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/runtime/schema"
|
||||
"k8s.io/client-go/openapi/cached"
|
||||
"k8s.io/client-go/openapi/openapitest"
|
||||
"k8s.io/client-go/openapi3"
|
||||
)
|
||||
|
||||
func TestV3SupportsQueryParamBatchV1(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
crds []schema.GroupKind // CRDFinder returns these CRD's
|
||||
gvk schema.GroupVersionKind // GVK whose OpenAPI V3 spec is checked
|
||||
queryParam VerifiableQueryParam // Usually "fieldValidation"
|
||||
expectedSupports bool
|
||||
}{
|
||||
"Field validation query param is supported for batch/v1/Job": {
|
||||
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": {
|
||||
crds: []schema.GroupKind{},
|
||||
gvk: schema.GroupVersionKind{
|
||||
Group: "",
|
||||
Version: "v1",
|
||||
Kind: "Namespace",
|
||||
},
|
||||
queryParam: QueryParamFieldValidation,
|
||||
expectedSupports: true,
|
||||
},
|
||||
"Field validation unsupported for unknown GVK": {
|
||||
crds: []schema.GroupKind{},
|
||||
gvk: schema.GroupVersionKind{
|
||||
Group: "bad",
|
||||
Version: "v1",
|
||||
Kind: "Uknown",
|
||||
},
|
||||
queryParam: QueryParamFieldValidation,
|
||||
expectedSupports: false,
|
||||
},
|
||||
"Unknown query param unsupported (for all GVK's)": {
|
||||
crds: []schema.GroupKind{},
|
||||
gvk: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
queryParam: "UnknownQueryParam",
|
||||
expectedSupports: false,
|
||||
},
|
||||
"Field validation query param supported for found CRD": {
|
||||
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": {
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
root := openapi3.NewRoot(cached.NewClient(openapitest.NewFileClient(t)))
|
||||
for tn, tc := range tests {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
verifier := &queryParamVerifierV3{
|
||||
finder: NewCRDFinder(func() ([]schema.GroupKind, error) {
|
||||
return tc.crds, nil
|
||||
}),
|
||||
root: root,
|
||||
queryParam: tc.queryParam,
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
1
vendor/modules.txt
vendored
1
vendor/modules.txt
vendored
@ -1892,6 +1892,7 @@ k8s.io/client-go/metadata/metadatainformer
|
||||
k8s.io/client-go/metadata/metadatalister
|
||||
k8s.io/client-go/openapi
|
||||
k8s.io/client-go/openapi/cached
|
||||
k8s.io/client-go/openapi3
|
||||
k8s.io/client-go/pkg/apis/clientauthentication
|
||||
k8s.io/client-go/pkg/apis/clientauthentication/install
|
||||
k8s.io/client-go/pkg/apis/clientauthentication/v1
|
||||
|
Loading…
Reference in New Issue
Block a user