1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
/*
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 (
"fmt"
"strings"
"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 {
if (gvk == schema.GroupVersionKind{Version: "v1", Kind: "List"}) {
return NewParamUnsupportedError(gvk, v.queryParam)
}
gvSpec, err := v.root.GVSpec(gvk.GroupVersion())
if err == nil {
return supportsQueryParamV3(gvSpec, 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
}
return supportsQueryParamV3(namespaceSpec, namespaceGVK, v.queryParam)
}
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 nil if the passed GVK supports the passed
// query parameter; otherwise, a "paramUnsupportedError" is returned (except
// when an invalid document error is returned when an invalid OpenAPI V3
// is passed in).
func supportsQueryParamV3(doc *spec3.OpenAPI, gvk schema.GroupVersionKind, queryParam VerifiableQueryParam) error {
if doc == nil || doc.Paths == nil {
return fmt.Errorf("Invalid OpenAPI V3 document")
}
for _, path := range doc.Paths.Paths {
// If operation is not PATCH, then continue.
if path == nil {
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) && param.In == "query" {
return nil
}
// lookup global parameters
if ref := param.Refable.Ref.Ref.String(); strings.HasPrefix(ref, "#/parameters/") && doc.Components != nil {
k := strings.TrimPrefix(ref, "#/parameters/")
if globalParam, ok := doc.Components.Parameters[k]; ok && globalParam != nil {
if globalParam.In == "query" && globalParam.Name == string(queryParam) {
return nil
}
}
}
}
return NewParamUnsupportedError(gvk, queryParam)
}
return fmt.Errorf("Path not found for GVK (%s) in OpenAPI V3 doc", gvk)
}
|