Skip to content

Commit adfbf77

Browse files
dvaldiviaBlaineEXE
authored andcommitted
Generate embedded objectmeta in CRDs
make it so embedded ObjectMeta in the CRD get's properly generated if the generator option generateEmbeddedObjectMeta=true is passed, this is needed because if a CRD has embedded ObjectMeta in any field and preserveUnknowFields is set to false, all the metadata will be lost when doing conversion between versions. By default any embedded ObjectMeta is not generated in the resulting CRD, however the top level ObjectMeta belonging to the CRD itself is never generated as the kubernetes API disallows changes to the CRD metadata between conversions. The generated ObjectMeta is also only a subset of the original set of fields inside ObjectMeta this is due to the fact that other runtime fields are problematic if they are being traded with the kubernetes API, such as creationTimeStamp so this only generates name, namespace, labels, annotations and finalizers which from a design perspective should be enough. An example of why this is useful is if a CRD had a volumeClaimTemplate (for an underlying statefulset) which include ObjectMeta such as Labels, Annotations and/or name which are meant to be passed to the PVC. Also addresses the type of FieldsV1 being typed as "Any" and instead types it as "object"
1 parent ea8d4ea commit adfbf77

File tree

6 files changed

+88
-2
lines changed

6 files changed

+88
-2
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ require (
1818
k8s.io/apimachinery v0.20.2
1919
sigs.k8s.io/yaml v1.2.0
2020
)
21+

pkg/crd/gen.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ type Generator struct {
8484
// You'll need to use "v1" to get support for features like defaulting,
8585
// along with an API server that supports it (Kubernetes 1.16+).
8686
CRDVersions []string `marker:"crdVersions,optional"`
87+
88+
// GenerateEmbeddedObjectMeta specifies if any embedded ObjectMeta in the CRD should be generated
89+
GenerateEmbeddedObjectMeta *bool `marker:",optional"`
8790
}
8891

8992
func (Generator) CheckFilter() loader.NodeFilter {
@@ -98,6 +101,8 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
98101
Checker: ctx.Checker,
99102
// Perform defaulting here to avoid ambiguity later
100103
AllowDangerousTypes: g.AllowDangerousTypes != nil && *g.AllowDangerousTypes == true,
104+
// Indicates the parser on whether to register the ObjectMeta type or not
105+
GenerateEmbeddedObjectMeta: g.GenerateEmbeddedObjectMeta != nil && *g.GenerateEmbeddedObjectMeta == true,
101106
}
102107

103108
AddKnownTypes(parser)
@@ -129,6 +134,9 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
129134
crdRaw := parser.CustomResourceDefinitions[groupKind]
130135
addAttribution(&crdRaw)
131136

137+
// Prevent the top level metadata for the CRD to be generate regardless of the intention in the arguments
138+
FixTopLevelMetadata(crdRaw)
139+
132140
versionedCRDs := make([]interface{}, len(crdVersions))
133141
for i, ver := range crdVersions {
134142
conv, err := AsVersion(crdRaw, schema.GroupVersion{Group: apiext.SchemeGroupVersion.Group, Version: ver})
@@ -269,6 +277,20 @@ func removeDefaultsFromSchemaProps(v *apiextlegacy.JSONSchemaProps) {
269277
}
270278
}
271279

280+
// FixTopLevelMetadata resets the schema for the top-level metadata field which is needed for CRD validation
281+
func FixTopLevelMetadata(crd apiext.CustomResourceDefinition) {
282+
for _, v := range crd.Spec.Versions {
283+
if v.Schema != nil && v.Schema.OpenAPIV3Schema != nil && v.Schema.OpenAPIV3Schema.Properties != nil {
284+
schemaProperties := v.Schema.OpenAPIV3Schema.Properties
285+
if _, ok := schemaProperties["metadata"]; ok {
286+
schemaProperties["metadata"] = apiext.JSONSchemaProps{Type: "object"}
287+
}
288+
}
289+
290+
}
291+
292+
}
293+
272294
// toTrivialVersions strips out all schemata except for the storage schema,
273295
// and moves that up into the root object. This makes the CRD compatible
274296
// with pre 1.13 clusters.

pkg/crd/known_types.go

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ var KnownPackages = map[string]PackageOverride{
3434
},
3535

3636
"k8s.io/apimachinery/pkg/apis/meta/v1": func(p *Parser, pkg *loader.Package) {
37-
// ObjectMeta is managed by the Kubernetes API server, so no need to
38-
// generate validation for it.
3937
p.Schemata[TypeIdent{Name: "ObjectMeta", Package: pkg}] = apiext.JSONSchemaProps{
4038
Type: "object",
4139
}
@@ -113,6 +111,52 @@ var KnownPackages = map[string]PackageOverride{
113111
},
114112
}
115113

114+
// ObjectMetaPackages overrides the ObjectMeta in all types
115+
var ObjectMetaPackages = map[string]PackageOverride{
116+
"k8s.io/apimachinery/pkg/apis/meta/v1": func(p *Parser, pkg *loader.Package) {
117+
// execute the KnowPackages for `k8s.io/apimachinery/pkg/apis/meta/v1` if any
118+
if f, ok := KnownPackages["k8s.io/apimachinery/pkg/apis/meta/v1"]; ok {
119+
f(p, pkg)
120+
}
121+
// This is a allow-listed set of properties of ObjectMeta, other runtime properties are not part of this list
122+
p.Schemata[TypeIdent{Name: "ObjectMeta", Package: pkg}] = apiext.JSONSchemaProps{
123+
Type: "object",
124+
Properties: map[string]apiext.JSONSchemaProps{
125+
"name": apiext.JSONSchemaProps{
126+
Type: "string",
127+
},
128+
"namespace": apiext.JSONSchemaProps{
129+
Type: "string",
130+
},
131+
"annotations": apiext.JSONSchemaProps{
132+
Type: "object",
133+
AdditionalProperties: &apiext.JSONSchemaPropsOrBool{
134+
Schema: &apiext.JSONSchemaProps{
135+
Type: "string",
136+
},
137+
},
138+
},
139+
"labels": apiext.JSONSchemaProps{
140+
Type: "object",
141+
AdditionalProperties: &apiext.JSONSchemaPropsOrBool{
142+
Schema: &apiext.JSONSchemaProps{
143+
Type: "string",
144+
},
145+
},
146+
},
147+
"finalizers": apiext.JSONSchemaProps{
148+
Type: "array",
149+
Items: &apiext.JSONSchemaPropsOrArray{
150+
Schema: &apiext.JSONSchemaProps{
151+
Type: "string",
152+
},
153+
},
154+
},
155+
},
156+
}
157+
},
158+
}
159+
116160
func boolPtr(b bool) *bool {
117161
return &b
118162
}
@@ -125,4 +169,10 @@ func AddKnownTypes(parser *Parser) {
125169
for pkgName, override := range KnownPackages {
126170
parser.PackageOverrides[pkgName] = override
127171
}
172+
// if we want to generate the embedded ObjectMeta in the CRD we need to add the ObjectMetaPackages
173+
if parser.GenerateEmbeddedObjectMeta {
174+
for pkgName, override := range ObjectMetaPackages {
175+
parser.PackageOverrides[pkgName] = override
176+
}
177+
}
128178
}

pkg/crd/parser.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ type Parser struct {
8686
// because the implementation is too difficult/clunky to promote them to category 3.
8787
// TODO: Should we have a more formal mechanism for putting "type patterns" in each of the above categories?
8888
AllowDangerousTypes bool
89+
90+
// GenerateEmbeddedObjectMeta specifies if any embedded ObjectMeta should be generated
91+
GenerateEmbeddedObjectMeta bool
8992
}
9093

9194
func (p *Parser) init() {

pkg/crd/parser_integration_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ var _ = Describe("CRD Generation From Parsing to CustomResourceDefinition", func
8484
groupKind := schema.GroupKind{Kind: "CronJob", Group: "testdata.kubebuilder.io"}
8585
parser.NeedCRDFor(groupKind, nil)
8686

87+
By("fixing top level ObjectMeta on the CRD")
88+
crd.FixTopLevelMetadata(parser.CustomResourceDefinitions[groupKind])
89+
8790
By("checking that no errors occurred along the way (expect for type errors)")
8891
Expect(packageErrors(cronJobPkg, packages.TypeError)).NotTo(HaveOccurred())
8992

@@ -133,6 +136,9 @@ var _ = Describe("CRD Generation From Parsing to CustomResourceDefinition", func
133136
groupKind := schema.GroupKind{Kind: "TestQuota", Group: "plural.example.com"}
134137
parser.NeedCRDFor(groupKind, nil)
135138

139+
By("fixing top level ObjectMeta on the CRD")
140+
crd.FixTopLevelMetadata(parser.CustomResourceDefinitions[groupKind])
141+
136142
By("loading the desired YAML")
137143
expectedFile, err := ioutil.ReadFile("plural.example.com_testquotas.yaml")
138144
Expect(err).NotTo(HaveOccurred())

pkg/crd/zz_generated.markerhelp.go

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)