forked from kubernetes-sigs/controller-tools
/
parser.go
237 lines (200 loc) · 7.8 KB
/
parser.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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
/*
Copyright 2019 The Kubernetes 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 crd
import (
"fmt"
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/Diggs/controller-tools/pkg/loader"
"github.com/Diggs/controller-tools/pkg/markers"
)
// TypeIdent represents some type in a Package.
type TypeIdent struct {
Package *loader.Package
Name string
}
func (t TypeIdent) String() string {
return fmt.Sprintf("%q.%s", t.Package.ID, t.Name)
}
// PackageOverride overrides the loading of some package
// (potentially setting custom schemata, etc). It must
// call AddPackage if it wants to continue with the default
// loading behavior.
type PackageOverride func(p *Parser, pkg *loader.Package)
// Parser knows how to parse out CRD information and generate
// OpenAPI schemata from some collection of types and markers.
// Most methods on Parser cache their results automatically,
// and thus may be called any number of times.
type Parser struct {
Collector *markers.Collector
// Types contains the known TypeInfo for this parser.
Types map[TypeIdent]*markers.TypeInfo
// Schemata contains the known OpenAPI JSONSchemata for this parser.
Schemata map[TypeIdent]apiext.JSONSchemaProps
// GroupVersions contains the known group-versions of each package in this parser.
GroupVersions map[*loader.Package]schema.GroupVersion
// CustomResourceDefinitions contains the known CustomResourceDefinitions for types in this parser.
CustomResourceDefinitions map[schema.GroupKind]apiext.CustomResourceDefinition
// FlattenedSchemata contains fully flattened schemata for use in building
// CustomResourceDefinition validation. Each schema has been flattened by the flattener,
// and then embedded fields have been flattened with FlattenEmbedded.
FlattenedSchemata map[TypeIdent]apiext.JSONSchemaProps
// PackageOverrides indicates that the loading of any package with
// the given path should be handled by the given overrider.
PackageOverrides map[string]PackageOverride
// checker stores persistent partial type-checking/reference-traversal information.
Checker *loader.TypeChecker
// packages marks packages as loaded, to avoid re-loading them.
packages map[*loader.Package]struct{}
flattener *Flattener
// AllowDangerousTypes controls the handling of non-recommended types such as float. If
// false (the default), these types are not supported.
// There is a continuum here:
// 1. Types that are always supported.
// 2. Types that are allowed by default, but not recommended (warning emitted when they are encountered as per PR #443).
// Possibly they are allowed by default for historical reasons and may even be "on their way out" at some point in the future.
// 3. Types that are not allowed by default, not recommended, but there are some legitimate reasons to need them in certain corner cases.
// Possibly these types should also emit a warning as per PR #443 even when they are "switched on" (an integration point between
// this feature and #443 if desired). This is the category that this flag deals with.
// 4. Types that are not allowed and will not be allowed, possibly because it just "doesn't make sense" or possibly
// because the implementation is too difficult/clunky to promote them to category 3.
// TODO: Should we have a more formal mechanism for putting "type patterns" in each of the above categories?
AllowDangerousTypes bool
}
func (p *Parser) init() {
if p.packages == nil {
p.packages = make(map[*loader.Package]struct{})
}
if p.flattener == nil {
p.flattener = &Flattener{
Parser: p,
}
}
if p.Schemata == nil {
p.Schemata = make(map[TypeIdent]apiext.JSONSchemaProps)
}
if p.Types == nil {
p.Types = make(map[TypeIdent]*markers.TypeInfo)
}
if p.PackageOverrides == nil {
p.PackageOverrides = make(map[string]PackageOverride)
}
if p.GroupVersions == nil {
p.GroupVersions = make(map[*loader.Package]schema.GroupVersion)
}
if p.CustomResourceDefinitions == nil {
p.CustomResourceDefinitions = make(map[schema.GroupKind]apiext.CustomResourceDefinition)
}
if p.FlattenedSchemata == nil {
p.FlattenedSchemata = make(map[TypeIdent]apiext.JSONSchemaProps)
}
}
// indexTypes loads all types in the package into Types.
func (p *Parser) indexTypes(pkg *loader.Package) {
// autodetect
pkgMarkers, err := markers.PackageMarkers(p.Collector, pkg)
if err != nil {
pkg.AddError(err)
} else {
if skipPkg := pkgMarkers.Get("kubebuilder:skip"); skipPkg != nil {
return
}
if nameVal := pkgMarkers.Get("groupName"); nameVal != nil {
versionVal := pkg.Name // a reasonable guess
if versionMarker := pkgMarkers.Get("versionName"); versionMarker != nil {
versionVal = versionMarker.(string)
}
p.GroupVersions[pkg] = schema.GroupVersion{
Version: versionVal,
Group: nameVal.(string),
}
}
}
if err := markers.EachType(p.Collector, pkg, func(info *markers.TypeInfo) {
ident := TypeIdent{
Package: pkg,
Name: info.Name,
}
p.Types[ident] = info
}); err != nil {
pkg.AddError(err)
}
}
// LookupType fetches type info from Types.
func (p *Parser) LookupType(pkg *loader.Package, name string) *markers.TypeInfo {
return p.Types[TypeIdent{Package: pkg, Name: name}]
}
// NeedSchemaFor indicates that a schema should be generated for the given type.
func (p *Parser) NeedSchemaFor(typ TypeIdent) {
p.init()
p.NeedPackage(typ.Package)
if _, knownSchema := p.Schemata[typ]; knownSchema {
return
}
info, knownInfo := p.Types[typ]
if !knownInfo {
typ.Package.AddError(fmt.Errorf("unknown type %s", typ))
return
}
// avoid tripping recursive schemata, like ManagedFields, by adding an empty WIP schema
p.Schemata[typ] = apiext.JSONSchemaProps{}
schemaCtx := newSchemaContext(typ.Package, p, p.AllowDangerousTypes)
ctxForInfo := schemaCtx.ForInfo(info)
pkgMarkers, err := markers.PackageMarkers(p.Collector, typ.Package)
if err != nil {
typ.Package.AddError(err)
}
ctxForInfo.PackageMarkers = pkgMarkers
schema := infoToSchema(ctxForInfo)
p.Schemata[typ] = *schema
}
func (p *Parser) NeedFlattenedSchemaFor(typ TypeIdent) {
p.init()
if _, knownSchema := p.FlattenedSchemata[typ]; knownSchema {
return
}
p.NeedSchemaFor(typ)
partialFlattened := p.flattener.FlattenType(typ)
fullyFlattened := FlattenEmbedded(partialFlattened, typ.Package)
p.FlattenedSchemata[typ] = *fullyFlattened
}
// NeedCRDFor lives off in spec.go
// AddPackage indicates that types and type-checking information is needed
// for the the given package, *ignoring* overrides.
// Generally, consumers should call NeedPackage, while PackageOverrides should
// call AddPackage to continue with the normal loading procedure.
func (p *Parser) AddPackage(pkg *loader.Package) {
p.init()
if _, checked := p.packages[pkg]; checked {
return
}
p.indexTypes(pkg)
p.Checker.Check(pkg)
p.packages[pkg] = struct{}{}
}
// NeedPackage indicates that types and type-checking information
// is needed for the given package.
func (p *Parser) NeedPackage(pkg *loader.Package) {
p.init()
if _, checked := p.packages[pkg]; checked {
return
}
// overrides are going to be written without vendor. This is why we index by the actual
// object when we can.
if override, overridden := p.PackageOverrides[loader.NonVendorPath(pkg.PkgPath)]; overridden {
override(p, pkg)
p.packages[pkg] = struct{}{}
return
}
p.AddPackage(pkg)
}