k8s.io/apiserver@v0.31.1/pkg/cel/openapi/resolver/discovery.go (about) 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 resolver 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "strings" 23 24 "k8s.io/apimachinery/pkg/runtime" 25 "k8s.io/apimachinery/pkg/runtime/schema" 26 "k8s.io/client-go/discovery" 27 "k8s.io/kube-openapi/pkg/validation/spec" 28 ) 29 30 // ClientDiscoveryResolver uses client-go discovery to resolve schemas at run time. 31 type ClientDiscoveryResolver struct { 32 Discovery discovery.DiscoveryInterface 33 } 34 35 var _ SchemaResolver = (*ClientDiscoveryResolver)(nil) 36 37 func (r *ClientDiscoveryResolver) ResolveSchema(gvk schema.GroupVersionKind) (*spec.Schema, error) { 38 p, err := r.Discovery.OpenAPIV3().Paths() 39 if err != nil { 40 return nil, err 41 } 42 resourcePath := resourcePathFromGV(gvk.GroupVersion()) 43 c, ok := p[resourcePath] 44 if !ok { 45 return nil, fmt.Errorf("cannot resolve group version %q: %w", gvk.GroupVersion(), ErrSchemaNotFound) 46 } 47 b, err := c.Schema(runtime.ContentTypeJSON) 48 if err != nil { 49 return nil, err 50 } 51 resp := new(schemaResponse) 52 err = json.Unmarshal(b, resp) 53 if err != nil { 54 return nil, err 55 } 56 ref, err := resolveRef(resp, gvk) 57 if err != nil { 58 return nil, err 59 } 60 s, err := PopulateRefs(func(ref string) (*spec.Schema, bool) { 61 s, ok := resp.Components.Schemas[strings.TrimPrefix(ref, refPrefix)] 62 return s, ok 63 }, ref) 64 if err != nil { 65 return nil, err 66 } 67 return s, nil 68 } 69 70 func resolveRef(resp *schemaResponse, gvk schema.GroupVersionKind) (string, error) { 71 for ref, s := range resp.Components.Schemas { 72 var gvks []schema.GroupVersionKind 73 err := s.Extensions.GetObject(extGVK, &gvks) 74 if err != nil { 75 return "", err 76 } 77 for _, g := range gvks { 78 if g == gvk { 79 return ref, nil 80 } 81 } 82 } 83 return "", fmt.Errorf("cannot resolve group version kind %q: %w", gvk, ErrSchemaNotFound) 84 } 85 86 func resourcePathFromGV(gv schema.GroupVersion) string { 87 var resourcePath string 88 if len(gv.Group) == 0 { 89 resourcePath = fmt.Sprintf("api/%s", gv.Version) 90 } else { 91 resourcePath = fmt.Sprintf("apis/%s/%s", gv.Group, gv.Version) 92 } 93 return resourcePath 94 } 95 96 type schemaResponse struct { 97 Components struct { 98 Schemas map[string]*spec.Schema `json:"schemas"` 99 } `json:"components"` 100 } 101 102 const refPrefix = "#/components/schemas/" 103 104 const extGVK = "x-kubernetes-group-version-kind"