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  }