mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-11 17:12:51 +00:00
Merge pull request #4467 from KnVerey/fn-cfg-openapi-validation
fn framework: Enable validation using openAPI schema for functionConfig
This commit is contained in:
@@ -48,6 +48,7 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd
|
|||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
|
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
|
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
|
||||||
@@ -156,6 +157,7 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
|
|||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
@@ -218,6 +220,7 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4
|
|||||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
||||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func buildProcessor(value *string) framework.ResourceListProcessor {
|
|||||||
}},
|
}},
|
||||||
// This will be populated from the --value flag if provided,
|
// This will be populated from the --value flag if provided,
|
||||||
// or the config file's `value` field if provided, with the latter taking precedence.
|
// or the config file's `value` field if provided, with the latter taking precedence.
|
||||||
TemplateData: struct {
|
TemplateData: &struct {
|
||||||
Value *string `yaml:"value"`
|
Value *string `yaml:"value"`
|
||||||
}{Value: value}}
|
}{Value: value}}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,14 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
validationErrors "k8s.io/kube-openapi/pkg/validation/errors"
|
||||||
|
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||||
"sigs.k8s.io/kustomize/kyaml/errors"
|
"sigs.k8s.io/kustomize/kyaml/errors"
|
||||||
"sigs.k8s.io/kustomize/kyaml/fn/framework"
|
"sigs.k8s.io/kustomize/kyaml/fn/framework"
|
||||||
"sigs.k8s.io/kustomize/kyaml/fn/framework/command"
|
"sigs.k8s.io/kustomize/kyaml/fn/framework/command"
|
||||||
"sigs.k8s.io/kustomize/kyaml/fn/framework/parser"
|
"sigs.k8s.io/kustomize/kyaml/fn/framework/parser"
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -962,28 +965,61 @@ func (a *v1alpha1JavaSpringBoot) Default() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var javaSpringBootDefinition = `
|
||||||
|
apiVersion: config.kubernetes.io/v1alpha1
|
||||||
|
kind: KRMFunctionDefinition
|
||||||
|
metadata:
|
||||||
|
name: javaspringboot.example.com
|
||||||
|
spec:
|
||||||
|
group: example.com
|
||||||
|
names:
|
||||||
|
kind: JavaSpringBoot
|
||||||
|
versions:
|
||||||
|
- name: v1alpha1
|
||||||
|
schema:
|
||||||
|
openAPIV3Schema:
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
type: string
|
||||||
|
kind:
|
||||||
|
type: string
|
||||||
|
metadata:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
spec:
|
||||||
|
properties:
|
||||||
|
domain:
|
||||||
|
pattern: example\.com$
|
||||||
|
type: string
|
||||||
|
image:
|
||||||
|
type: string
|
||||||
|
replicas:
|
||||||
|
maximum: 9
|
||||||
|
minimum: 0
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
|
`
|
||||||
|
|
||||||
|
func (a v1alpha1JavaSpringBoot) Schema() (*spec.Schema, error) {
|
||||||
|
schema, err := framework.SchemaFromFunctionDefinition(resid.NewGvk("example.com", "v1alpha1", "JavaSpringBoot"), javaSpringBootDefinition)
|
||||||
|
return schema, errors.WrapPrefixf(err, "parsing JavaSpringBoot schema")
|
||||||
|
}
|
||||||
|
|
||||||
func (a *v1alpha1JavaSpringBoot) Validate() error {
|
func (a *v1alpha1JavaSpringBoot) Validate() error {
|
||||||
var messages []string
|
var errs []error
|
||||||
if a.Metadata.Name == "" {
|
|
||||||
messages = append(messages, "name is required")
|
|
||||||
}
|
|
||||||
if a.Spec.Replicas > 10 {
|
|
||||||
messages = append(messages, "replicas must be less than 10")
|
|
||||||
}
|
|
||||||
if !strings.HasSuffix(a.Spec.Domain, "example.com") {
|
|
||||||
messages = append(messages, "domain must be a subdomain of example.com")
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(a.Spec.Image, ":latest") {
|
if strings.HasSuffix(a.Spec.Image, ":latest") {
|
||||||
messages = append(messages, "image should not have latest tag")
|
errs = append(errs, errors.Errorf("spec.image should not have latest tag"))
|
||||||
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return validationErrors.CompositeValidationError(errs...)
|
||||||
}
|
}
|
||||||
if len(messages) == 0 {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
|
||||||
errMsg := fmt.Sprintf("JavaSpringBoot had %d errors:\n", len(messages))
|
|
||||||
for i, msg := range messages {
|
|
||||||
errMsg += fmt.Sprintf(" [%d] %s\n", i+1, msg)
|
|
||||||
}
|
|
||||||
return errors.Errorf(errMsg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExampleVersionedAPIProcessor shows how to use the VersionedAPIProcessor and TemplateProcessor to
|
// ExampleVersionedAPIProcessor shows how to use the VersionedAPIProcessor and TemplateProcessor to
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
goerrors "errors"
|
goerrors "errors"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||||
"sigs.k8s.io/kustomize/kyaml/errors"
|
"sigs.k8s.io/kustomize/kyaml/errors"
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
@@ -92,6 +93,19 @@ type Validator interface {
|
|||||||
Validate() error
|
Validate() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidationSchemaProvider is implemented by APIs to have the openapi schema provided by Schema()
|
||||||
|
// used to validate the input functionConfig before it is parsed into the API's struct.
|
||||||
|
// Use this with framework.SchemaFromFunctionDefinition to load the schema out of a KRMFunctionDefinition
|
||||||
|
// or CRD (e.g. one generated with KubeBuilder).
|
||||||
|
//
|
||||||
|
// func (t MyType) Schema() (*spec.Schema, error) {
|
||||||
|
// schema, err := framework.SchemaFromFunctionDefinition(resid.NewGvk("example.com", "v1", "MyType"), MyTypeDef)
|
||||||
|
// return schema, errors.WrapPrefixf(err, "parsing MyType schema")
|
||||||
|
// }
|
||||||
|
type ValidationSchemaProvider interface {
|
||||||
|
Schema() (*spec.Schema, error)
|
||||||
|
}
|
||||||
|
|
||||||
// Execute is the entrypoint for invoking configuration functions built with this framework
|
// Execute is the entrypoint for invoking configuration functions built with this framework
|
||||||
// from code. See framework/command#Build for a Cobra-based command-line equivalent.
|
// from code. See framework/command#Build for a Cobra-based command-line equivalent.
|
||||||
// Execute reads a ResourceList from the given source, passes it to a ResourceListProcessor,
|
// Execute reads a ResourceList from the given source, passes it to a ResourceListProcessor,
|
||||||
@@ -158,6 +172,9 @@ func Execute(p ResourceListProcessor, rlSource *kio.ByteReadWriter) error {
|
|||||||
// Filters that return a Result as error will store the result in the ResourceList
|
// Filters that return a Result as error will store the result in the ResourceList
|
||||||
// and continue processing instead of erroring out.
|
// and continue processing instead of erroring out.
|
||||||
func (rl *ResourceList) Filter(api kio.Filter) error {
|
func (rl *ResourceList) Filter(api kio.Filter) error {
|
||||||
|
if api == nil {
|
||||||
|
return errors.Errorf("ResourceList cannot run apply nil filter")
|
||||||
|
}
|
||||||
var err error
|
var err error
|
||||||
rl.Items, err = api.Filter(rl.Items)
|
rl.Items, err = api.Filter(rl.Items)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
91
kyaml/fn/framework/function_definition.go
Normal file
91
kyaml/fn/framework/function_definition.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
// Copyright 2022 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package framework
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
const FunctionDefinitionKind = "KRMFunctionDefinition"
|
||||||
|
const FunctionDefinitionGroupVersion = "config.kubernetes.io/v1alpha1"
|
||||||
|
|
||||||
|
// KRMFunctionDefinition is metadata that defines a KRM function the same way a CRD defines a custom resource.
|
||||||
|
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-cli/2906-kustomize-function-catalog#function-metadata-schema
|
||||||
|
type KRMFunctionDefinition struct {
|
||||||
|
// APIVersion and Kind of the object. Must be config.kubernetes.io/v1alpha1 and KRMFunctionDefinition respectively.
|
||||||
|
yaml.TypeMeta `yaml:",inline" json:",inline"`
|
||||||
|
// Standard KRM object metadata
|
||||||
|
yaml.ObjectMeta `yaml:"metadata,omitempty" json:"metadata,omitempty"`
|
||||||
|
// Spec contains the properties of the KRM function this object defines.
|
||||||
|
Spec KrmFunctionDefinitionSpec `yaml:"spec" json:"spec"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KrmFunctionDefinitionSpec struct {
|
||||||
|
//
|
||||||
|
// The following fields are shared with CustomResourceDefinition.
|
||||||
|
//
|
||||||
|
// Group is the API group of the defined KRM function.
|
||||||
|
Group string `yaml:"group" json:"group"`
|
||||||
|
// Names specify the resource and kind names for the KRM function.
|
||||||
|
Names KRMFunctionNames `yaml:"names" json:"names"`
|
||||||
|
// Versions is the list of all API versions of the defined KRM function.
|
||||||
|
Versions []KRMFunctionVersion `yaml:"versions" json:"versions"`
|
||||||
|
|
||||||
|
//
|
||||||
|
// The following fields are custom to KRMFunctionDefinition
|
||||||
|
//
|
||||||
|
// Description briefly describes the KRM function.
|
||||||
|
Description string `yaml:"description,omitempty" json:"description,omitempty"`
|
||||||
|
// Publisher is the entity (e.g. organization) that produced and owns this KRM function.
|
||||||
|
Publisher string `yaml:"publisher,omitempty" json:"publisher,omitempty"`
|
||||||
|
// Home is a URI pointing the home page of the KRM function.
|
||||||
|
Home string `yaml:"home,omitempty" json:"home,omitempty"`
|
||||||
|
// Maintainers lists the individual maintainers of the KRM function.
|
||||||
|
Maintainers []string `yaml:"maintainers,omitempty" json:"maintainers,omitempty"`
|
||||||
|
// Tags are keywords describing the function. e.g. mutator, validator, generator, prefix, GCP.
|
||||||
|
Tags []string `yaml:"tags,omitempty" json:"tags,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KRMFunctionVersion struct {
|
||||||
|
//
|
||||||
|
// The following fields are shared with CustomResourceDefinition.
|
||||||
|
//
|
||||||
|
// Name is the version name, e.g. “v1”, “v2beta1”, etc.
|
||||||
|
Name string `yaml:"name" json:"name"`
|
||||||
|
// Schema describes the schema of this version of the KRM function.
|
||||||
|
// This can be used for validation, pruning, and/or defaulting.
|
||||||
|
Schema *KRMFunctionValidation `yaml:"schema,omitempty" json:"schema,omitempty"`
|
||||||
|
|
||||||
|
//
|
||||||
|
// The following fields are custom to KRMFunctionDefinition
|
||||||
|
//
|
||||||
|
// Idempotent indicates whether the function can be re-run multiple times without changing the result.
|
||||||
|
Idempotent bool `yaml:"idempotent,omitempty" json:"idempotent,omitempty"`
|
||||||
|
// Usage is URI pointing to a README.md that describe the details of how to use the KRM function.
|
||||||
|
// It should at least cover what the function does and should give a detailed explanation about each
|
||||||
|
// field used to configure it.
|
||||||
|
Usage string `yaml:"usage,omitempty" json:"usage,omitempty"`
|
||||||
|
// A list of URIs that point to README.md files. Each README.md should cover an example.
|
||||||
|
// It should at least cover how to get input resources, how to run it and what is the expected
|
||||||
|
// output.
|
||||||
|
Examples []string `yaml:"examples,omitempty" json:"examples,omitempty"`
|
||||||
|
// License is the name of the license covering the function.
|
||||||
|
License string `yaml:"license,omitempty" json:"license,omitempty"`
|
||||||
|
// The maintainers for this version of the function, if different from the primary maintainers.
|
||||||
|
Maintainers []string `yaml:"maintainers,omitempty" json:"maintainers,omitempty"`
|
||||||
|
// The runtime information describing how to execute this function.
|
||||||
|
Runtime runtimeutil.FunctionSpec `yaml:"runtime" json:"runtime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KRMFunctionValidation struct {
|
||||||
|
// OpenAPIV3Schema is the OpenAPI v3 schema for an instance of the KRM function.
|
||||||
|
OpenAPIV3Schema *spec.Schema `yaml:"openAPIV3Schema,omitempty" json:"openAPIV3Schema,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KRMFunctionNames struct {
|
||||||
|
// Kind is the kind of the defined KRM Function. It is normally CamelCase and singular.
|
||||||
|
Kind string `yaml:"kind" json:"kind"`
|
||||||
|
}
|
||||||
@@ -6,12 +6,16 @@ package framework
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
validationErrors "k8s.io/kube-openapi/pkg/validation/errors"
|
||||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||||
|
"k8s.io/kube-openapi/pkg/validation/strfmt"
|
||||||
|
"k8s.io/kube-openapi/pkg/validation/validate"
|
||||||
"sigs.k8s.io/kustomize/kyaml/errors"
|
"sigs.k8s.io/kustomize/kyaml/errors"
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio/filters"
|
"sigs.k8s.io/kustomize/kyaml/kio/filters"
|
||||||
"sigs.k8s.io/kustomize/kyaml/openapi"
|
"sigs.k8s.io/kustomize/kyaml/openapi"
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
|
k8syaml "sigs.k8s.io/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SimpleProcessor processes a ResourceList by loading the FunctionConfig into
|
// SimpleProcessor processes a ResourceList by loading the FunctionConfig into
|
||||||
@@ -35,9 +39,9 @@ type SimpleProcessor struct {
|
|||||||
// defaulting and validation if supported by Config. It then executes the processor's filter.
|
// defaulting and validation if supported by Config. It then executes the processor's filter.
|
||||||
func (p SimpleProcessor) Process(rl *ResourceList) error {
|
func (p SimpleProcessor) Process(rl *ResourceList) error {
|
||||||
if err := LoadFunctionConfig(rl.FunctionConfig, p.Config); err != nil {
|
if err := LoadFunctionConfig(rl.FunctionConfig, p.Config); err != nil {
|
||||||
return errors.Wrap(err)
|
return errors.WrapPrefixf(err, "loading function config")
|
||||||
}
|
}
|
||||||
return errors.Wrap(rl.Filter(p.Filter))
|
return errors.WrapPrefixf(rl.Filter(p.Filter), "processing filter")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GVKFilterMap is a FilterProvider that resolves Filters through a simple lookup in a map.
|
// GVKFilterMap is a FilterProvider that resolves Filters through a simple lookup in a map.
|
||||||
@@ -139,7 +143,24 @@ func LoadFunctionConfig(src *yaml.RNode, api interface{}) error {
|
|||||||
if api == nil {
|
if api == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err := yaml.Unmarshal([]byte(src.MustString()), api); err != nil {
|
// Run this before unmarshalling to avoid nasty unmarshal failure error messages
|
||||||
|
var schemaValidationError error
|
||||||
|
if s, ok := api.(ValidationSchemaProvider); ok {
|
||||||
|
schema, err := s.Schema()
|
||||||
|
if err != nil {
|
||||||
|
return errors.WrapPrefixf(err, "loading provided schema")
|
||||||
|
}
|
||||||
|
schemaValidationError = validate.AgainstSchema(schema, src, strfmt.Default)
|
||||||
|
// don't return it yet--try to make it to custom validation stage to combine errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// using sigs.k8s.io/yaml here lets the custom types embed core types
|
||||||
|
// that only have json tags, notably types from k8s.io/apimachinery/pkg/apis/meta/v1
|
||||||
|
if err := k8syaml.Unmarshal([]byte(src.MustString()), api); err != nil {
|
||||||
|
if schemaValidationError != nil {
|
||||||
|
// if we got a validation error, report it instead as it is likely a nicer version of the same message
|
||||||
|
return schemaValidationError
|
||||||
|
}
|
||||||
return errors.Wrap(err)
|
return errors.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +171,25 @@ func LoadFunctionConfig(src *yaml.RNode, api interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if v, ok := api.(Validator); ok {
|
if v, ok := api.(Validator); ok {
|
||||||
return v.Validate()
|
return combineErrors(schemaValidationError, v.Validate())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func combineErrors(schemaErr, customErr error) error {
|
||||||
|
combined := validationErrors.CompositeValidationError()
|
||||||
|
if compositeSchemaErr, ok := schemaErr.(*validationErrors.CompositeError); ok {
|
||||||
|
combined.Errors = append(combined.Errors, compositeSchemaErr.Errors...)
|
||||||
|
} else if schemaErr != nil {
|
||||||
|
combined.Errors = append(combined.Errors, schemaErr)
|
||||||
|
}
|
||||||
|
if compositeCustomErr, ok := customErr.(*validationErrors.CompositeError); ok {
|
||||||
|
combined.Errors = append(combined.Errors, compositeCustomErr.Errors...)
|
||||||
|
} else if customErr != nil {
|
||||||
|
combined.Errors = append(combined.Errors, customErr)
|
||||||
|
}
|
||||||
|
if len(combined.Errors) > 0 {
|
||||||
|
return combined
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
validationErrors "k8s.io/kube-openapi/pkg/validation/errors"
|
||||||
|
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||||
"sigs.k8s.io/kustomize/kyaml/errors"
|
"sigs.k8s.io/kustomize/kyaml/errors"
|
||||||
"sigs.k8s.io/kustomize/kyaml/fn/framework/frameworktestutil"
|
"sigs.k8s.io/kustomize/kyaml/fn/framework/frameworktestutil"
|
||||||
"sigs.k8s.io/kustomize/kyaml/fn/framework/parser"
|
"sigs.k8s.io/kustomize/kyaml/fn/framework/parser"
|
||||||
@@ -357,13 +359,21 @@ func TestSimpleProcessor_Process_Error(t *testing.T) {
|
|||||||
wantErr string
|
wantErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "error when given func as Config",
|
name: "error when filter is nil",
|
||||||
config: func() {},
|
config: map[string]string{},
|
||||||
wantErr: "cannot unmarshal !!map into func()",
|
filter: nil,
|
||||||
|
wantErr: "processing filter: ResourceList cannot run apply nil filter",
|
||||||
|
}, {
|
||||||
|
name: "no error when config is nil",
|
||||||
|
config: nil,
|
||||||
|
filter: kio.FilterFunc(func(items []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||||
|
return items, nil
|
||||||
|
}),
|
||||||
|
wantErr: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "error in filter",
|
name: "error in filter",
|
||||||
wantErr: "err from filter",
|
wantErr: "processing filter: err from filter",
|
||||||
filter: kio.FilterFunc(func(_ []*yaml.RNode) ([]*yaml.RNode, error) {
|
filter: kio.FilterFunc(func(_ []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||||
return nil, errors.Errorf("err from filter")
|
return nil, errors.Errorf("err from filter")
|
||||||
}),
|
}),
|
||||||
@@ -382,8 +392,11 @@ func TestSimpleProcessor_Process_Error(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
err := p.Process(&rl)
|
err := p.Process(&rl)
|
||||||
require.Error(t, err)
|
if tt.wantErr == "" {
|
||||||
assert.Contains(t, err.Error(), tt.wantErr)
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
assert.EqualError(t, err, tt.wantErr)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -396,15 +409,6 @@ func TestVersionedAPIProcessor_Process_Error(t *testing.T) {
|
|||||||
kind string
|
kind string
|
||||||
wantErr string
|
wantErr string
|
||||||
}{
|
}{
|
||||||
{
|
|
||||||
name: "error when given FilterFunc as Filter",
|
|
||||||
filterProvider: framework.FilterProviderFunc(func(_, _ string) (kio.Filter, error) {
|
|
||||||
return kio.FilterFunc(func(items []*yaml.RNode) ([]*yaml.RNode, error) {
|
|
||||||
return items, nil
|
|
||||||
}), nil
|
|
||||||
}),
|
|
||||||
wantErr: "cannot unmarshal !!map into kio.FilterFunc",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "error in filter",
|
name: "error in filter",
|
||||||
filterProvider: framework.FilterProviderFunc(func(_, _ string) (kio.Filter, error) {
|
filterProvider: framework.FilterProviderFunc(func(_, _ string) (kio.Filter, error) {
|
||||||
@@ -658,3 +662,161 @@ func TestTemplateProcessor_Validator(t *testing.T) {
|
|||||||
}
|
}
|
||||||
c.Assert(t)
|
c.Assert(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type jsonTagTest struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Test bool `json:"test"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type yamlTagTest struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
Test bool `yaml:"test"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type customErrorTest struct {
|
||||||
|
v1alpha1JavaSpringBoot
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e customErrorTest) Schema() (*spec.Schema, error) {
|
||||||
|
return e.v1alpha1JavaSpringBoot.Schema()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e customErrorTest) Validate() error {
|
||||||
|
return errors.Errorf("Custom errors:\n- first error\n- second error")
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorMergeTest struct {
|
||||||
|
v1alpha1JavaSpringBoot
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e errorMergeTest) Schema() (*spec.Schema, error) {
|
||||||
|
return e.v1alpha1JavaSpringBoot.Schema()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e errorMergeTest) Validate() error {
|
||||||
|
if strings.HasSuffix(e.Spec.Image, "latest") {
|
||||||
|
return validationErrors.CompositeValidationError(errors.Errorf("spec.image cannot be tagged :latest"))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadFunctionConfig(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
src *yaml.RNode
|
||||||
|
api interface{}
|
||||||
|
want interface{}
|
||||||
|
wantErrMsgs []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "combines schema-based and non-composite custom errors",
|
||||||
|
src: yaml.MustParse(`
|
||||||
|
apiVersion: example.com/v1alpha1
|
||||||
|
kind: JavaSpringBoot
|
||||||
|
spec:
|
||||||
|
replicas: 11
|
||||||
|
domain: foo.myco.io
|
||||||
|
image: nginx:latest
|
||||||
|
`),
|
||||||
|
api: &customErrorTest{},
|
||||||
|
wantErrMsgs: []string{
|
||||||
|
"validation failure list:",
|
||||||
|
"spec.replicas in body should be less than or equal to 9",
|
||||||
|
"spec.domain in body should match 'example\\.com$'",
|
||||||
|
`Custom errors:
|
||||||
|
- first error
|
||||||
|
- second error`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "merges schema-based errors with custom composite errors",
|
||||||
|
src: yaml.MustParse(`
|
||||||
|
apiVersion: example.com/v1alpha1
|
||||||
|
kind: JavaSpringBoot
|
||||||
|
spec:
|
||||||
|
replicas: 11
|
||||||
|
domain: foo.myco.io
|
||||||
|
image: nginx:latest
|
||||||
|
`),
|
||||||
|
api: &errorMergeTest{},
|
||||||
|
wantErrMsgs: []string{"validation failure list:",
|
||||||
|
"spec.replicas in body should be less than or equal to 9",
|
||||||
|
"spec.domain in body should match 'example\\.com$'",
|
||||||
|
"spec.image cannot be tagged :latest"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "schema errors only",
|
||||||
|
src: yaml.MustParse(`
|
||||||
|
apiVersion: example.com/v1alpha1
|
||||||
|
kind: JavaSpringBoot
|
||||||
|
spec:
|
||||||
|
replicas: 11
|
||||||
|
`),
|
||||||
|
api: &errorMergeTest{},
|
||||||
|
wantErrMsgs: []string{
|
||||||
|
`validation failure list:
|
||||||
|
spec.replicas in body should be less than or equal to 9`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom errors only",
|
||||||
|
src: yaml.MustParse(`
|
||||||
|
apiVersion: example.com/v1alpha1
|
||||||
|
kind: JavaSpringBoot
|
||||||
|
spec:
|
||||||
|
image: nginx:latest
|
||||||
|
`),
|
||||||
|
api: &errorMergeTest{},
|
||||||
|
wantErrMsgs: []string{
|
||||||
|
`validation failure list:
|
||||||
|
spec.image cannot be tagged :latest`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "both custom and schema error hooks defined, but no errors produced",
|
||||||
|
src: yaml.MustParse(`
|
||||||
|
apiVersion: example.com/v1alpha1
|
||||||
|
kind: JavaSpringBoot
|
||||||
|
spec:
|
||||||
|
image: nginx:1.0
|
||||||
|
replicas: 3
|
||||||
|
domain: bar.example.com
|
||||||
|
`),
|
||||||
|
api: &errorMergeTest{},
|
||||||
|
want: &errorMergeTest{v1alpha1JavaSpringBoot: v1alpha1JavaSpringBoot{
|
||||||
|
Spec: v1alpha1JavaSpringBootSpec{Replicas: 3, Domain: "bar.example.com", Image: "nginx:1.0"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "successfully loads types that include fields only tagged with json markers",
|
||||||
|
src: yaml.MustParse(`
|
||||||
|
name: tester
|
||||||
|
test: true
|
||||||
|
`),
|
||||||
|
api: &jsonTagTest{},
|
||||||
|
want: &jsonTagTest{Name: "tester", Test: true},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "successfully loads types that include fields only tagged with yaml markers",
|
||||||
|
src: yaml.MustParse(`
|
||||||
|
name: tester
|
||||||
|
test: true
|
||||||
|
`),
|
||||||
|
api: &yamlTagTest{},
|
||||||
|
want: &yamlTagTest{Name: "tester", Test: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := framework.LoadFunctionConfig(tt.src, tt.api)
|
||||||
|
if len(tt.wantErrMsgs) == 0 {
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tt.want, tt.api)
|
||||||
|
} else {
|
||||||
|
for _, msg := range tt.wantErrMsgs {
|
||||||
|
require.Contains(t, err.Error(), msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
JavaSpringBoot had 4 errors:
|
validation failure list:
|
||||||
\[\d\] replicas must be less than 10
|
spec.domain in body should match 'example\\.com\$'
|
||||||
\[\d\] name is required
|
spec.image should not have latest tag
|
||||||
\[\d\] image should not have latest tag
|
metadata.name in body should be at least 1 chars long
|
||||||
\[\d\] domain must be a subdomain of example.com
|
spec.replicas in body should be less than or equal to 9
|
||||||
|
|||||||
36
kyaml/fn/framework/validation.go
Normal file
36
kyaml/fn/framework/validation.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2022 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package framework
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/errors"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||||
|
k8syaml "sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SchemaFromFunctionDefinition extracts the schema for a particular GVK from the provided KRMFunctionDefinition
|
||||||
|
// Since the relevant fields of KRMFunctionDefinition exactly match the ones in CustomResourceDefinition,
|
||||||
|
// this helper can also load CRDs (e.g. produced by KubeBuilder) transparently.
|
||||||
|
func SchemaFromFunctionDefinition(gvk resid.Gvk, data string) (*spec.Schema, error) {
|
||||||
|
var def KRMFunctionDefinition
|
||||||
|
// need to use sigs yaml because spec.Schema type only has json tags
|
||||||
|
if err := k8syaml.Unmarshal([]byte(data), &def); err != nil {
|
||||||
|
return nil, errors.WrapPrefixf(err, "unmarshalling %s", FunctionDefinitionKind)
|
||||||
|
}
|
||||||
|
var foundGVKs []*resid.Gvk
|
||||||
|
var schema *spec.Schema
|
||||||
|
for i, version := range def.Spec.Versions {
|
||||||
|
versionGVK := resid.Gvk{Group: def.Spec.Group, Kind: def.Spec.Names.Kind, Version: version.Name}
|
||||||
|
if gvk.Equals(versionGVK) {
|
||||||
|
schema = def.Spec.Versions[i].Schema.OpenAPIV3Schema
|
||||||
|
break
|
||||||
|
}
|
||||||
|
foundGVKs = append(foundGVKs, &versionGVK)
|
||||||
|
}
|
||||||
|
if schema == nil {
|
||||||
|
return nil, errors.Errorf("%s does not define %s (defines: %s)", FunctionDefinitionKind, gvk, foundGVKs)
|
||||||
|
}
|
||||||
|
return schema, nil
|
||||||
|
}
|
||||||
153
kyaml/fn/framework/validation_test.go
Normal file
153
kyaml/fn/framework/validation_test.go
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
// Copyright 2022 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package framework
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||||
|
)
|
||||||
|
|
||||||
|
var demoFunctionDefinition = `
|
||||||
|
apiVersion: config.kubernetes.io/v1alpha1
|
||||||
|
kind: KRMFunctionDefinition
|
||||||
|
metadata:
|
||||||
|
name: demos.example.io
|
||||||
|
spec:
|
||||||
|
group: example.io
|
||||||
|
names:
|
||||||
|
kind: Demo
|
||||||
|
versions:
|
||||||
|
- name: v1alpha1
|
||||||
|
schema:
|
||||||
|
openAPIV3Schema:
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
type: string
|
||||||
|
color:
|
||||||
|
type: string
|
||||||
|
kind:
|
||||||
|
type: string
|
||||||
|
metadata:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- color
|
||||||
|
type: object
|
||||||
|
- name: v1alpha2
|
||||||
|
schema:
|
||||||
|
openAPIV3Schema:
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
type: string
|
||||||
|
flavor:
|
||||||
|
type: string
|
||||||
|
kind:
|
||||||
|
type: string
|
||||||
|
metadata:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- flavor
|
||||||
|
type: object
|
||||||
|
`
|
||||||
|
|
||||||
|
var demoCRD = `
|
||||||
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
name: demos.example.io
|
||||||
|
spec:
|
||||||
|
group: example.io
|
||||||
|
names:
|
||||||
|
kind: Demo
|
||||||
|
listKind: DemoList
|
||||||
|
plural: demos
|
||||||
|
singular: demo
|
||||||
|
scope: Namespaced
|
||||||
|
versions:
|
||||||
|
- name: v1alpha1
|
||||||
|
schema:
|
||||||
|
openAPIV3Schema:
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
description: 'APIVersion defines the versioned schema of this representation
|
||||||
|
of an object. Servers should convert recognized schemas to the latest
|
||||||
|
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||||
|
type: string
|
||||||
|
color:
|
||||||
|
type: string
|
||||||
|
kind:
|
||||||
|
description: 'Kind is a string value representing the REST resource this
|
||||||
|
object represents. Servers may infer this from the endpoint the client
|
||||||
|
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||||
|
type: string
|
||||||
|
metadata:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- color
|
||||||
|
type: object
|
||||||
|
served: true
|
||||||
|
storage: true
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestSchemaFromFunctionDefinition(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
gvk resid.Gvk
|
||||||
|
data string
|
||||||
|
wantProps []string
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "demo KRMFunctionDefinition extract v1alpha1",
|
||||||
|
gvk: resid.NewGvk("example.io", "v1alpha1", "Demo"),
|
||||||
|
data: demoFunctionDefinition,
|
||||||
|
wantProps: []string{"apiVersion", "kind", "metadata", "color"},
|
||||||
|
}, {
|
||||||
|
name: "demo KRMFunctionDefinition extract v1alpha2",
|
||||||
|
gvk: resid.NewGvk("example.io", "v1alpha2", "Demo"),
|
||||||
|
data: demoFunctionDefinition,
|
||||||
|
wantProps: []string{"apiVersion", "kind", "metadata", "flavor"},
|
||||||
|
}, {
|
||||||
|
name: "works with CustomResourceDefinition",
|
||||||
|
gvk: resid.NewGvk("example.io", "v1alpha1", "Demo"),
|
||||||
|
data: demoCRD,
|
||||||
|
wantProps: []string{"apiVersion", "kind", "metadata", "color"},
|
||||||
|
}, {
|
||||||
|
name: "group mismatch",
|
||||||
|
gvk: resid.NewGvk("example.com", "v1alpha2", "Demo"),
|
||||||
|
data: demoFunctionDefinition,
|
||||||
|
wantErr: "KRMFunctionDefinition does not define Demo.v1alpha2.example.com (defines: [Demo.v1alpha1.example.io Demo.v1alpha2.example.io])",
|
||||||
|
}, {
|
||||||
|
name: "version mismatch",
|
||||||
|
gvk: resid.NewGvk("example.io", "v1alpha3", "Demo"),
|
||||||
|
data: demoFunctionDefinition,
|
||||||
|
wantErr: "KRMFunctionDefinition does not define Demo.v1alpha3.example.io (defines: [Demo.v1alpha1.example.io Demo.v1alpha2.example.io])",
|
||||||
|
}, {
|
||||||
|
name: "kind mismatch",
|
||||||
|
gvk: resid.NewGvk("example.io", "v1alpha2", "Demonstration"),
|
||||||
|
data: demoFunctionDefinition,
|
||||||
|
wantErr: "KRMFunctionDefinition does not define Demonstration.v1alpha2.example.io (defines: [Demo.v1alpha1.example.io Demo.v1alpha2.example.io])",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := SchemaFromFunctionDefinition(tt.gvk, tt.data)
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
require.EqualError(t, err, tt.wantErr)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
var gotProps []string
|
||||||
|
for prop, _ := range got.Properties {
|
||||||
|
gotProps = append(gotProps, prop)
|
||||||
|
}
|
||||||
|
sort.Strings(tt.wantProps)
|
||||||
|
sort.Strings(gotProps)
|
||||||
|
assert.Equal(t, gotProps, tt.wantProps)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user