...

Source file src/k8s.io/cli-runtime/pkg/resource/query_param_verifier_v3.go

Documentation: k8s.io/cli-runtime/pkg/resource

     1  /*
     2  Copyright 2023 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package resource
    18  
    19  import (
    20  	"fmt"
    21  	"strings"
    22  
    23  	"k8s.io/apimachinery/pkg/runtime/schema"
    24  	"k8s.io/client-go/dynamic"
    25  	"k8s.io/client-go/openapi"
    26  	"k8s.io/client-go/openapi3"
    27  	"k8s.io/kube-openapi/pkg/spec3"
    28  	"k8s.io/kube-openapi/pkg/validation/spec"
    29  )
    30  
    31  var _ Verifier = &queryParamVerifierV3{}
    32  
    33  // NewQueryParamVerifierV3 returns a pointer to the created queryParamVerifier3 struct,
    34  // which implements the Verifier interface. The caching characteristics of the
    35  // OpenAPI V3 specs are determined by the passed oapiClient. For memory caching, the
    36  // client should be wrapped beforehand as: cached.NewClient(oapiClient). The disk
    37  // caching is determined by the discovery client the oapiClient is created from.
    38  func NewQueryParamVerifierV3(dynamicClient dynamic.Interface, oapiClient openapi.Client, queryParam VerifiableQueryParam) Verifier {
    39  	return &queryParamVerifierV3{
    40  		finder:     NewCRDFinder(CRDFromDynamic(dynamicClient)),
    41  		root:       openapi3.NewRoot(oapiClient),
    42  		queryParam: queryParam,
    43  	}
    44  }
    45  
    46  // queryParamVerifierV3 encapsulates info necessary to determine if
    47  // the queryParam is a parameter for the Patch endpoint for a
    48  // passed GVK.
    49  type queryParamVerifierV3 struct {
    50  	finder     CRDFinder
    51  	root       openapi3.Root
    52  	queryParam VerifiableQueryParam
    53  }
    54  
    55  var namespaceGVK = schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"}
    56  
    57  // HasSupport returns nil error if the passed GVK supports the parameter
    58  // (stored in struct; usually "fieldValidation") for Patch endpoint.
    59  // Returns an error if the passed GVK does not support the query param,
    60  // or if another error occurred. If the Open API V3 spec for a CRD is not
    61  // found, then the spec for Namespace is checked for query param support instead.
    62  func (v *queryParamVerifierV3) HasSupport(gvk schema.GroupVersionKind) error {
    63  	if (gvk == schema.GroupVersionKind{Version: "v1", Kind: "List"}) {
    64  		return NewParamUnsupportedError(gvk, v.queryParam)
    65  	}
    66  	gvSpec, err := v.root.GVSpec(gvk.GroupVersion())
    67  	if err == nil {
    68  		return supportsQueryParamV3(gvSpec, gvk, v.queryParam)
    69  	}
    70  	if _, isErr := err.(*openapi3.GroupVersionNotFoundError); !isErr {
    71  		return err
    72  	}
    73  	// If the spec for the passed GVK is not found, then check if it is a CRD.
    74  	// For CRD's substitute Namespace OpenAPI V3 spec to check if query param is supported.
    75  	if found, _ := v.finder.HasCRD(gvk.GroupKind()); found {
    76  		namespaceSpec, err := v.root.GVSpec(namespaceGVK.GroupVersion())
    77  		if err != nil {
    78  			// If error retrieving Namespace spec, propagate error.
    79  			return err
    80  		}
    81  		return supportsQueryParamV3(namespaceSpec, namespaceGVK, v.queryParam)
    82  	}
    83  	return NewParamUnsupportedError(gvk, v.queryParam)
    84  }
    85  
    86  // hasGVKExtensionV3 returns true if the passed OpenAPI extensions map contains
    87  // the passed GVK; false otherwise.
    88  func hasGVKExtensionV3(extensions spec.Extensions, gvk schema.GroupVersionKind) bool {
    89  	var oapiGVK map[string]string
    90  	err := extensions.GetObject("x-kubernetes-group-version-kind", &oapiGVK)
    91  	if err != nil {
    92  		return false
    93  	}
    94  	if oapiGVK["group"] == gvk.Group &&
    95  		oapiGVK["version"] == gvk.Version &&
    96  		oapiGVK["kind"] == gvk.Kind {
    97  		return true
    98  	}
    99  	return false
   100  }
   101  
   102  // supportsQueryParam is a method that let's us look in the OpenAPI if the
   103  // specific group-version-kind supports the specific query parameter for
   104  // the PATCH end-point. Returns nil if the passed GVK supports the passed
   105  // query parameter; otherwise, a "paramUnsupportedError" is returned (except
   106  // when an invalid document error is returned when an invalid OpenAPI V3
   107  // is passed in).
   108  func supportsQueryParamV3(doc *spec3.OpenAPI, gvk schema.GroupVersionKind, queryParam VerifiableQueryParam) error {
   109  	if doc == nil || doc.Paths == nil {
   110  		return fmt.Errorf("Invalid OpenAPI V3 document")
   111  	}
   112  	for _, path := range doc.Paths.Paths {
   113  		// If operation is not PATCH, then continue.
   114  		if path == nil {
   115  			continue
   116  		}
   117  		op := path.PathProps.Patch
   118  		if op == nil {
   119  			continue
   120  		}
   121  		// Is this PATCH operation for the passed GVK?
   122  		if !hasGVKExtensionV3(op.VendorExtensible.Extensions, gvk) {
   123  			continue
   124  		}
   125  		// Now look for the query parameter among the parameters
   126  		// for the PATCH operation.
   127  		for _, param := range op.OperationProps.Parameters {
   128  			if param.ParameterProps.Name == string(queryParam) && param.In == "query" {
   129  				return nil
   130  			}
   131  
   132  			// lookup global parameters
   133  			if ref := param.Refable.Ref.Ref.String(); strings.HasPrefix(ref, "#/parameters/") && doc.Components != nil {
   134  				k := strings.TrimPrefix(ref, "#/parameters/")
   135  				if globalParam, ok := doc.Components.Parameters[k]; ok && globalParam != nil {
   136  					if globalParam.In == "query" && globalParam.Name == string(queryParam) {
   137  						return nil
   138  					}
   139  				}
   140  			}
   141  		}
   142  		return NewParamUnsupportedError(gvk, queryParam)
   143  	}
   144  	return fmt.Errorf("Path not found for GVK (%s) in OpenAPI V3 doc", gvk)
   145  }
   146  

View as plain text