Skip to content

Commit 707ef71

Browse files
s-urbaniakigor-karpukhin
authored andcommitted
parse crd using the scheme converter
1 parent 8986874 commit 707ef71

File tree

3 files changed

+91
-33
lines changed

3 files changed

+91
-33
lines changed

tools/scaffolder/go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
// github.com/josvazg/crd2go v0.0.0-00010101000000-000000000000
1010
github.com/spf13/cobra v1.10.1
1111
gopkg.in/yaml.v3 v3.0.1
12-
k8s.io/apimachinery v0.34.1 // indirect
12+
k8s.io/apimachinery v0.34.1
1313
)
1414

1515
require (
@@ -40,4 +40,5 @@ require (
4040
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
4141
sigs.k8s.io/randfill v1.0.0 // indirect
4242
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
43+
sigs.k8s.io/yaml v1.6.0 // indirect
4344
)

tools/scaffolder/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
5454
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
5555
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
5656
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
57+
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
58+
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
5759
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
5860
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
5961
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=

tools/scaffolder/internal/generate/indexers.go

Lines changed: 87 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ import (
2525

2626
"github.com/dave/jennifer/jen"
2727
"gopkg.in/yaml.v3"
28+
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
29+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
30+
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
31+
clientsetscheme "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme"
32+
"k8s.io/apimachinery/pkg/runtime"
33+
"k8s.io/apimachinery/pkg/runtime/serializer"
2834
)
2935

3036
type ReferenceField struct {
@@ -112,19 +118,19 @@ func parseNextCRDReferences(scanner *bufio.Scanner, targetKind string) ([]Refere
112118
}
113119

114120
func extractReferencesFromCRD(content []byte, targetKind string) ([]ReferenceField, error) {
115-
var crd CRDDocument
116-
if err := yaml.Unmarshal(content, &crd); err != nil {
117-
return nil, fmt.Errorf("failed to unmarshal YAML: %w", err)
121+
crd, err := Decode(content)
122+
if err != nil {
123+
return nil, fmt.Errorf("failed to decode YAML: %w", err)
118124
}
119125

120-
if crd.Kind != "CustomResourceDefinition" || crd.Spec.Names.Kind != targetKind {
126+
if crd.Spec.Names.Kind != targetKind {
121127
return nil, fmt.Errorf("not target CRD")
122128
}
123129

124130
var references []ReferenceField
125131

126-
if apiMappingsStr, exists := crd.Metadata.Annotations["api-mappings"]; exists {
127-
refs, err := parseAPIMapping(apiMappingsStr)
132+
if apiMappingsStr, exists := crd.GetAnnotations()["api-mappings"]; exists {
133+
refs, err := parseAPIMapping(apiMappingsStr, crd.Spec.Versions[0].Schema.OpenAPIV3Schema)
128134
if err == nil {
129135
references = append(references, refs...)
130136
}
@@ -134,34 +140,36 @@ func extractReferencesFromCRD(content []byte, targetKind string) ([]ReferenceFie
134140
return references, nil
135141
}
136142

137-
type VersionProperties struct {
138-
Properties map[string]PropertyMapping `yaml:"properties"`
139-
}
143+
func Decode(content []byte) (*apiextensionsv1.CustomResourceDefinition, error) {
144+
sch := runtime.NewScheme()
145+
_ = clientsetscheme.AddToScheme(sch)
146+
_ = apiextensions.AddToScheme(sch)
147+
_ = apiextensionsv1.AddToScheme(sch)
148+
_ = apiextensionsv1.RegisterConversions(sch)
149+
_ = apiextensionsv1beta1.AddToScheme(sch)
150+
_ = apiextensionsv1beta1.RegisterConversions(sch)
140151

141-
type PropertyMapping struct {
142-
KubernetesMapping *KubernetesMapping `yaml:"x-kubernetes-mapping,omitempty"`
143-
OpenAPIMapping *OpenAPIMapping `yaml:"x-openapi-mapping,omitempty"`
144-
}
152+
decode := serializer.NewCodecFactory(sch).UniversalDeserializer().Decode
145153

146-
type KubernetesMapping struct {
147-
NameSelector string `yaml:"nameSelector"`
148-
Properties []string `yaml:"properties"`
149-
Type KubernetesRefType `yaml:"type"`
150-
}
154+
obj, _, err := decode(content, nil, nil)
155+
if err != nil {
156+
return nil, fmt.Errorf("failed to decode YAML: %w", err)
157+
}
151158

152-
type KubernetesRefType struct {
153-
Group string `yaml:"group"`
154-
Kind string `yaml:"kind"`
155-
Resource string `yaml:"resource"`
156-
Version string `yaml:"version"`
157-
}
159+
kind := obj.GetObjectKind().GroupVersionKind().Kind
160+
if kind != "CustomResourceDefinition" {
161+
return nil, fmt.Errorf("unexpected kind %q: %w", kind, err)
162+
}
158163

159-
type OpenAPIMapping struct {
160-
Property string `yaml:"property"`
161-
Type string `yaml:"type,omitempty"`
164+
crd := &apiextensionsv1.CustomResourceDefinition{}
165+
err = sch.Convert(obj, crd, nil)
166+
if err != nil {
167+
return nil, fmt.Errorf("failed to convert CRD object: %w", err)
168+
}
169+
return crd, nil
162170
}
163171

164-
func parseAPIMapping(apiMappingsStr string) ([]ReferenceField, error) {
172+
func parseAPIMapping(apiMappingsStr string, schema *apiextensionsv1.JSONSchemaProps) ([]ReferenceField, error) {
165173
// Parse the api-mappings as a generic map to handle any nesting level
166174
var mapping map[string]any
167175
if err := yaml.Unmarshal([]byte(apiMappingsStr), &mapping); err != nil {
@@ -171,7 +179,7 @@ func parseAPIMapping(apiMappingsStr string) ([]ReferenceField, error) {
171179
var references []ReferenceField
172180

173181
// Recursively search for x-kubernetes-mapping
174-
findReferences(mapping, "", &references)
182+
findReferences(mapping, "", schema, &references)
175183

176184
return references, nil
177185
}
@@ -220,7 +228,7 @@ func processKubernetesMapping(v map[string]any, path string, references *[]Refer
220228
}
221229
}
222230

223-
func findReferences(data any, path string, references *[]ReferenceField) {
231+
func findReferences(data any, path string, schema *apiextensionsv1.JSONSchemaProps, references *[]ReferenceField) {
224232
switch v := data.(type) {
225233
case map[string]any:
226234
// Check if this is a kubernetes mapping and process it
@@ -232,16 +240,63 @@ func findReferences(data any, path string, references *[]ReferenceField) {
232240
newPath += "."
233241
}
234242
newPath += key
235-
findReferences(value, newPath, references)
243+
244+
// Resolve the child schema for this key and pass it into the recursive call
245+
required, childSchema := getSchemaForPathSegment(schema, key)
246+
_ = required
247+
findReferences(value, newPath, childSchema, references)
236248
}
237249
case []any:
238250
for i, item := range v {
239251
newPath := fmt.Sprintf("%s[%d]", path, i)
240-
findReferences(item, newPath, references)
252+
253+
// For arrays pass the items schema if available
254+
var childSchema *apiextensionsv1.JSONSchemaProps
255+
if schema != nil {
256+
var required bool
257+
required, childSchema = getSchemaForPathSegment(schema, "items")
258+
_ = required
259+
}
260+
findReferences(item, newPath, childSchema, references)
241261
}
242262
}
243263
}
244264

265+
// getSchemaForPathSegment returns the nested JSONSchemaProps for a given mapping key.
266+
// - "properties" returns the current schema (so inner property names will be looked up in schema.Properties)
267+
// - "items" returns the schema for array items (handles single Schema or first element of JSONSchemas)
268+
// - normal property names return schema.Properties[name] (strips trailing "[index]" if present)
269+
func getSchemaForPathSegment(schema *apiextensionsv1.JSONSchemaProps, key string) (bool, *apiextensionsv1.JSONSchemaProps) {
270+
if schema == nil {
271+
return false, nil
272+
}
273+
274+
if key == "properties" {
275+
// The next level will contain actual property names; keep the current schema so those names can be looked up in schema.Properties
276+
return false, schema
277+
}
278+
279+
required := false
280+
for _, req := range schema.Required {
281+
if req == key {
282+
required = true
283+
break
284+
}
285+
}
286+
287+
if key == "items" {
288+
return false, nil
289+
}
290+
291+
if schema.Properties != nil {
292+
if p, ok := schema.Properties[key]; ok {
293+
return required, &p
294+
}
295+
}
296+
297+
return false, nil
298+
}
299+
245300
func GenerateIndexers(resultPath, crdKind, indexerOutDir string) error {
246301
references, err := ParseReferenceFields(resultPath, crdKind)
247302
if err != nil {

0 commit comments

Comments
 (0)