/
validator.go
144 lines (119 loc) · 4.73 KB
/
validator.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/*
Copyright 2023 the Crossplane Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package composition
import (
"context"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
xperrors "github.com/crossplane/crossplane-runtime/pkg/errors"
v1 "github.com/crossplane/crossplane/apis/apiextensions/v1"
)
// Validator validates the provided Composition.
type Validator struct {
logicalValidation func(*v1.Composition) ([]string, field.ErrorList)
crdGetter CRDGetter
}
// CRDGetter is used to get all CRDs the Validator needs, either one by one or all at once.
type CRDGetter interface {
Get(ctx context.Context, gvk schema.GroupKind) (*apiextensions.CustomResourceDefinition, error)
GetAll(ctx context.Context) (map[schema.GroupKind]apiextensions.CustomResourceDefinition, error)
}
// ValidatorOption is used to configure the Validator.
type ValidatorOption func(*Validator)
// NewValidator returns a new Validator starting with the default configuration and applying
// the given options.
func NewValidator(opts ...ValidatorOption) (*Validator, error) {
v := &Validator{}
// Configure all default options and then any option passed in.
for _, f := range append([]ValidatorOption{
WithLogicalValidation(),
},
opts...) {
f(v)
}
return v, v.isValid()
}
func (v *Validator) isValid() error {
if v.crdGetter == nil {
return xperrors.New("CRDGetterFn is required")
}
return nil
}
// WithCRDGetter returns a ValidatorOption that configure the validator to use the given CRDGetter to retrieve the CRD
// it needs.
func WithCRDGetter(c CRDGetter) ValidatorOption {
return func(v *Validator) {
v.crdGetter = c
}
}
// WithCRDGetterFromMap returns a ValidatorOption that configure the Validator to use the given map as a CRDGetter.
// Will return an error if the CRD is not found on calls to Get.
func WithCRDGetterFromMap(m map[schema.GroupKind]apiextensions.CustomResourceDefinition) ValidatorOption {
return WithCRDGetter(crdGetterMap(m))
}
type crdGetterMap map[schema.GroupKind]apiextensions.CustomResourceDefinition
func (c crdGetterMap) Get(_ context.Context, gk schema.GroupKind) (*apiextensions.CustomResourceDefinition, error) {
if crd, ok := c[gk]; ok {
return &crd, nil
}
return nil, kerrors.NewNotFound(schema.GroupResource{Group: gk.Group, Resource: "CustomResourceDefinition"}, gk.String())
}
func (c crdGetterMap) GetAll(_ context.Context) (map[schema.GroupKind]apiextensions.CustomResourceDefinition, error) {
return c, nil
}
// WithLogicalValidation returns a ValidatorOption that configures the Validator to use the given function to logically
// validate the Composition.
func WithLogicalValidation() ValidatorOption {
return func(v *Validator) {
v.logicalValidation = func(in *v1.Composition) ([]string, field.ErrorList) {
return in.Validate()
}
}
}
// WithoutLogicalValidation returns a ValidatorOption that configures the Validator to not perform any logical check on
// the Composition.
func WithoutLogicalValidation() ValidatorOption {
return func(v *Validator) {
v.logicalValidation = func(*v1.Composition) ([]string, field.ErrorList) {
return nil, nil
}
}
}
// Validate validates the provided Composition.
func (v *Validator) Validate(ctx context.Context, obj runtime.Object) (warns []string, errs field.ErrorList) {
comp, ok := obj.(*v1.Composition)
if !ok {
return nil, append(errs, field.NotSupported(field.NewPath("kind"), obj.GetObjectKind().GroupVersionKind().Kind, []string{v1.CompositionGroupVersionKind.Kind}))
}
// Validate the Composition itself
if v.logicalValidation != nil {
if warns, errs := v.logicalValidation(comp); len(errs) != 0 {
return warns, errs
}
}
// Validate patches given the above CRDs, skip if any of the required CRDs is not available
for _, f := range []func(context.Context, *v1.Composition) field.ErrorList{
v.validatePatchesWithSchemas,
v.validateReadinessChecksWithSchemas,
v.validateConnectionDetailsWithSchemas,
v.validateEnvironmentPatchesWithSchemas,
// TODO(phisco): add more phase 2 validation here
} {
errs = append(errs, f(ctx, comp)...)
}
// TODO(phisco): add more phase 3 validation here
return nil, errs
}