github.com/splunk/dan1-qbec@v0.7.3/internal/remote/k8smeta/schema.go (about) 1 /* 2 Copyright 2019 Splunk Inc. 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 k8smeta 18 19 import ( 20 "fmt" 21 "sync" 22 23 openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2" 24 "github.com/pkg/errors" 25 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 26 "k8s.io/apimachinery/pkg/runtime/schema" 27 "k8s.io/kube-openapi/pkg/util/proto" 28 "k8s.io/kube-openapi/pkg/util/proto/validation" 29 "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" 30 ) 31 32 // ErrSchemaNotFound is returned when a schema could not be found. 33 var ErrSchemaNotFound = errors.New("schema not found") // returned when a validation schema is not found 34 35 // Validator validates documents of a specific type. 36 type Validator interface { 37 // Validate validates the supplied object and returns a slice of validation errors. 38 Validate(obj *unstructured.Unstructured) []error 39 } 40 41 // vsSchema implements Validator 42 type vsSchema struct { 43 proto.Schema 44 } 45 46 func (v *vsSchema) Validate(obj *unstructured.Unstructured) []error { 47 gvk := obj.GroupVersionKind() 48 return validation.ValidateModel(obj.UnstructuredContent(), v.Schema, fmt.Sprintf("%s.%s", gvk.Version, gvk.Kind)) 49 } 50 51 type schemaResult struct { 52 validator Validator 53 err error 54 } 55 56 // validators produces Validator instances for k8s types. 57 type validators struct { 58 res openapi.Resources 59 l sync.Mutex 60 cache map[schema.GroupVersionKind]*schemaResult 61 } 62 63 func (v *validators) validatorFor(gvk schema.GroupVersionKind) (Validator, error) { 64 v.l.Lock() 65 defer v.l.Unlock() 66 sr := v.cache[gvk] 67 if sr == nil { 68 var err error 69 valSchema := v.res.LookupResource(gvk) 70 if valSchema == nil { 71 err = ErrSchemaNotFound 72 } 73 sr = &schemaResult{ 74 validator: &vsSchema{valSchema}, 75 err: err, 76 } 77 v.cache[gvk] = sr 78 } 79 return sr.validator, sr.err 80 } 81 82 // openapiResourceResult is the cached result of retrieving an openAPI doc from the server. 83 type openapiResourceResult struct { 84 res openapi.Resources 85 validators *validators 86 err error 87 } 88 89 // SchemaDiscovery is the minimal interface needed to discover the server schema. 90 type SchemaDiscovery interface { 91 OpenAPISchema() (*openapi_v2.Document, error) 92 } 93 94 // ServerSchema is a representation of the resource schema of a Kubernetes server. 95 type ServerSchema struct { 96 ol sync.Mutex 97 oResult *openapiResourceResult 98 disco SchemaDiscovery 99 } 100 101 // NewServerSchema returns a server schema that can supply validators for the given discovery 102 // interface. 103 func NewServerSchema(disco SchemaDiscovery) *ServerSchema { 104 return &ServerSchema{ 105 disco: disco, 106 } 107 } 108 109 // ValidatorFor returns a validator for the supplied GroupVersionKind. 110 func (ss *ServerSchema) ValidatorFor(gvk schema.GroupVersionKind) (Validator, error) { 111 _, v, err := ss.openAPIResources() 112 if err != nil { 113 return nil, err 114 } 115 return v.validatorFor(gvk) 116 } 117 118 // OpenAPIResources returns the OpenAPI resources for the server. 119 func (ss *ServerSchema) OpenAPIResources() (openapi.Resources, error) { 120 r, _, err := ss.openAPIResources() 121 return r, err 122 } 123 124 func (ss *ServerSchema) openAPIResources() (openapi.Resources, *validators, error) { 125 ss.ol.Lock() 126 defer ss.ol.Unlock() 127 ret := ss.oResult 128 if ret != nil { 129 return ret.res, ret.validators, ret.err 130 } 131 handle := func(r openapi.Resources, err error) (openapi.Resources, *validators, error) { 132 ss.oResult = &openapiResourceResult{res: r, err: err} 133 if err == nil { 134 ss.oResult.validators = &validators{ 135 res: r, 136 cache: map[schema.GroupVersionKind]*schemaResult{}, 137 } 138 } 139 return ss.oResult.res, ss.oResult.validators, ss.oResult.err 140 } 141 doc, err := ss.disco.OpenAPISchema() 142 if err != nil { 143 return handle(nil, errors.Wrap(err, "Open API doc from server")) 144 } 145 res, err := openapi.NewOpenAPIData(doc) 146 if err != nil { 147 return handle(nil, errors.Wrap(err, "get resources from validator")) 148 } 149 return handle(res, nil) 150 }