Skip to content

Commit 44a7b1e

Browse files
committed
tools/openapi2crd: add reference rendering for entry fields
1 parent c13420a commit 44a7b1e

File tree

11 files changed

+341
-97
lines changed

11 files changed

+341
-97
lines changed

config/openapi2crd.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ spec:
4646
- read_only_properties
4747
- read_write_properties
4848
- references
49-
- references_metadata
49+
- reference_extensions
5050
- mutual_exclusive_major_versions
5151
- atlas_sdk_version
5252

tools/openapi2crd/config.example.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ spec:
3333
- read_only_properties
3434
- read_write_properties
3535
- references
36-
- references_metadata
36+
- reference_extensions
3737
- mutual_exclusive_major_versions
3838
- atlas_sdk_version
3939

tools/openapi2crd/config.sample.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ spec:
3333
- read_only_properties
3434
- read_write_properties
3535
- references
36-
- references_metadata
36+
- reference_extensions
3737
- mutual_exclusive_major_versions
3838
- atlas_sdk_version
3939

tools/openapi2crd/pkg/plugins/catalog.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ func NewCatalog() *Catalog {
151151
"read_write_properties": &ReadWriteProperties{},
152152
},
153153
extension: map[string]ExtensionPlugin{
154-
"atlas_sdk_version": &AtlasSdkVersionPlugin{},
155-
"references_metadata": &ReferencesMetadata{},
154+
"atlas_sdk_version": &AtlasSdkVersionPlugin{},
155+
"reference_extensions": &ReferenceExtensions{},
156156
},
157157
}
158158
}

tools/openapi2crd/pkg/plugins/catalog_test.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@ import (
1919
"errors"
2020
"testing"
2121

22-
"github.com/stretchr/testify/assert"
23-
2422
configv1alpha1 "github.com/mongodb/mongodb-atlas-kubernetes/tools/openapi2crd/pkg/apis/config/v1alpha1"
23+
"github.com/stretchr/testify/assert"
2524
)
2625

2726
func TestBuildSets(t *testing.T) {
@@ -75,7 +74,7 @@ func TestBuildSets(t *testing.T) {
7574
{
7675
Name: "set-1",
7776
Default: true,
78-
Plugins: []string{"base", "entry", "status", "references_metadata"},
77+
Plugins: []string{"base", "entry", "status", "reference_extensions"},
7978
},
8079
},
8180
expectedSet: []Set{
@@ -85,15 +84,15 @@ func TestBuildSets(t *testing.T) {
8584
CRD: []CRDPlugin{&Base{}},
8685
Mapping: []MappingPlugin{&Entry{}, &Status{}},
8786
Property: []PropertyPlugin{},
88-
Extension: []ExtensionPlugin{&ReferencesMetadata{}},
87+
Extension: []ExtensionPlugin{&ReferenceExtensions{}},
8988
},
9089
{
9190
Name: "set-2",
9291
Default: false,
9392
CRD: []CRDPlugin{&Base{}},
9493
Mapping: []MappingPlugin{&Entry{}, &Status{}, &References{}},
9594
Property: []PropertyPlugin{&ReadOnlyProperties{}, &ReadWriteProperties{}},
96-
Extension: []ExtensionPlugin{&ReferencesMetadata{}},
95+
Extension: []ExtensionPlugin{&ReferenceExtensions{}},
9796
},
9897
},
9998
expectedErr: nil,

tools/openapi2crd/pkg/plugins/plugin.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,9 @@ package plugins
1717

1818
import (
1919
"github.com/getkin/kin-openapi/openapi3"
20-
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
21-
2220
configv1alpha1 "github.com/mongodb/mongodb-atlas-kubernetes/tools/openapi2crd/pkg/apis/config/v1alpha1"
2321
"github.com/mongodb/mongodb-atlas-kubernetes/tools/openapi2crd/pkg/converter"
22+
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
2423
)
2524

2625
type CRDProcessorRequest struct {
@@ -72,4 +71,4 @@ var _ PropertyPlugin = &ReadWriteProperties{}
7271
var _ PropertyPlugin = &SensitiveProperties{}
7372
var _ PropertyPlugin = &SkippedProperties{}
7473
var _ ExtensionPlugin = &AtlasSdkVersionPlugin{}
75-
var _ ExtensionPlugin = &ReferencesMetadata{}
74+
var _ ExtensionPlugin = &ReferenceExtensions{}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright 2025 MongoDB Inc
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
16+
package plugins
17+
18+
import (
19+
"errors"
20+
21+
"github.com/getkin/kin-openapi/openapi3"
22+
configv1alpha1 "github.com/mongodb/mongodb-atlas-kubernetes/tools/openapi2crd/pkg/apis/config/v1alpha1"
23+
)
24+
25+
type ReferenceExtensions struct{}
26+
27+
func (r *ReferenceExtensions) Name() string {
28+
return "reference_metadata"
29+
}
30+
31+
func (r *ReferenceExtensions) Process(req *ExtensionProcessorRequest) error {
32+
for _, ref := range req.MappingConfig.ParametersMapping.References {
33+
schemaRef, err := r.newMappingExtensions(ref)
34+
if err != nil {
35+
return err
36+
}
37+
38+
req.ExtensionsSchema.Properties["spec"].Value.Properties[req.MappingConfig.MajorVersion].Value.Properties[ref.Name] = schemaRef
39+
}
40+
41+
for _, ref := range req.MappingConfig.EntryMapping.References {
42+
schemaRef, err := r.newMappingExtensions(ref)
43+
if err != nil {
44+
return err
45+
}
46+
47+
req.ExtensionsSchema.Properties["spec"].Value.Properties[req.MappingConfig.MajorVersion].Value.Properties["entry"].Value.Properties[ref.Name] = schemaRef
48+
}
49+
50+
return nil
51+
}
52+
53+
func (r *ReferenceExtensions) newMappingExtensions(ref configv1alpha1.Reference) (*openapi3.SchemaRef, error) {
54+
if len(ref.Target.Properties) == 0 {
55+
return nil, errors.New("reference target must have at least one property defined")
56+
}
57+
58+
schema := openapi3.NewSchema()
59+
schema.Extensions = map[string]interface{}{}
60+
schema.Extensions["x-kubernetes-mapping"] = map[string]interface{}{
61+
"type": map[string]interface{}{"kind": ref.Target.Type.Kind, "group": ref.Target.Type.Group, "version": ref.Target.Type.Version, "resource": ref.Target.Type.Resource},
62+
"nameSelector": ".name",
63+
"properties": ref.Target.Properties,
64+
}
65+
66+
schema.Extensions["x-openapi-mapping"] = map[string]interface{}{
67+
"property": ref.Property,
68+
}
69+
schemaRef := openapi3.NewSchemaRef("", schema)
70+
71+
return schemaRef, nil
72+
}

tools/openapi2crd/pkg/plugins/references_metadata_test.go renamed to tools/openapi2crd/pkg/plugins/reference_extensions_test.go

Lines changed: 91 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,12 @@ import (
2020
"testing"
2121

2222
"github.com/getkin/kin-openapi/openapi3"
23-
"github.com/stretchr/testify/assert"
24-
2523
configv1alpha1 "github.com/mongodb/mongodb-atlas-kubernetes/tools/openapi2crd/pkg/apis/config/v1alpha1"
24+
"github.com/stretchr/testify/assert"
2625
)
2726

2827
func TestReferenceMetadataName(t *testing.T) {
29-
p := &ReferencesMetadata{}
28+
p := &ReferenceExtensions{}
3029
assert.Equal(t, "reference_metadata", p.Name())
3130
}
3231

@@ -35,6 +34,8 @@ func TestReferenceMetadataProcess(t *testing.T) {
3534
request *ExtensionProcessorRequest
3635
expectedExtensions map[string]interface{}
3736
expectedErr error
37+
refName string
38+
isEntryRef bool
3839
}{
3940
"do nothing when no references": {
4041
request: &ExtensionProcessorRequest{
@@ -61,6 +62,8 @@ func TestReferenceMetadataProcess(t *testing.T) {
6162
},
6263
expectedExtensions: nil,
6364
expectedErr: nil,
65+
refName: "",
66+
isEntryRef: false,
6467
},
6568
"add reference metadata": {
6669
request: &ExtensionProcessorRequest{
@@ -120,6 +123,8 @@ func TestReferenceMetadataProcess(t *testing.T) {
120123
},
121124
},
122125
expectedErr: nil,
126+
refName: "myRef",
127+
isEntryRef: false,
123128
},
124129
"error when reference target has no properties": {
125130
request: &ExtensionProcessorRequest{
@@ -163,17 +168,96 @@ func TestReferenceMetadataProcess(t *testing.T) {
163168
},
164169
expectedExtensions: nil,
165170
expectedErr: errors.New("reference target must have at least one property defined"),
171+
refName: "",
172+
isEntryRef: false,
173+
},
174+
"add reference metadata to entry": {
175+
request: &ExtensionProcessorRequest{
176+
MappingConfig: &configv1alpha1.CRDMapping{
177+
MajorVersion: "v20250312",
178+
EntryMapping: configv1alpha1.PropertyMapping{
179+
References: []configv1alpha1.Reference{
180+
{
181+
Name: "clusterRef",
182+
Property: "spec.clusterName",
183+
Target: configv1alpha1.Target{
184+
Type: configv1alpha1.Type{
185+
Kind: "Cluster",
186+
Group: "atlas.generated.mongodb.com",
187+
Version: "v1",
188+
Resource: "clusters",
189+
},
190+
Properties: []string{"status.name"},
191+
},
192+
},
193+
},
194+
},
195+
},
196+
ExtensionsSchema: &openapi3.Schema{
197+
Properties: map[string]*openapi3.SchemaRef{
198+
"spec": {
199+
Value: &openapi3.Schema{
200+
Type: &openapi3.Types{"object"},
201+
Properties: map[string]*openapi3.SchemaRef{
202+
"v20250312": {
203+
Value: &openapi3.Schema{
204+
Type: &openapi3.Types{"object"},
205+
Properties: map[string]*openapi3.SchemaRef{
206+
"entry": {
207+
Value: &openapi3.Schema{
208+
Type: &openapi3.Types{"object"},
209+
Properties: map[string]*openapi3.SchemaRef{},
210+
},
211+
},
212+
},
213+
},
214+
},
215+
},
216+
},
217+
},
218+
},
219+
},
220+
},
221+
expectedExtensions: map[string]interface{}{
222+
"x-kubernetes-mapping": map[string]interface{}{
223+
"type": map[string]interface{}{
224+
"kind": "Cluster",
225+
"group": "atlas.generated.mongodb.com",
226+
"version": "v1",
227+
"resource": "clusters",
228+
},
229+
"nameSelector": ".name",
230+
"properties": []string{
231+
"status.name",
232+
},
233+
},
234+
"x-openapi-mapping": map[string]interface{}{
235+
"property": "spec.clusterName",
236+
},
237+
},
238+
expectedErr: nil,
239+
refName: "clusterRef",
240+
isEntryRef: true,
166241
},
167242
}
168243
for name, tt := range tests {
169244
t.Run(name, func(t *testing.T) {
170-
p := &ReferencesMetadata{}
245+
p := &ReferenceExtensions{}
171246
err := p.Process(tt.request)
172247
assert.Equal(t, tt.expectedErr, err)
173248
if tt.expectedExtensions != nil {
174-
assert.Equal(t, tt.expectedExtensions, tt.request.ExtensionsSchema.Properties["spec"].Value.Properties[tt.request.MappingConfig.MajorVersion].Value.Properties["myRef"].Value.Extensions)
175-
} else {
176-
assert.Empty(t, tt.request.ExtensionsSchema.Properties["spec"].Value.Properties[tt.request.MappingConfig.MajorVersion].Value.Properties)
249+
majorVersionProps := tt.request.ExtensionsSchema.Properties["spec"].Value.Properties[tt.request.MappingConfig.MajorVersion].Value.Properties
250+
if tt.isEntryRef {
251+
assert.Equal(t, tt.expectedExtensions, majorVersionProps["entry"].Value.Properties[tt.refName].Value.Extensions)
252+
} else {
253+
assert.Equal(t, tt.expectedExtensions, majorVersionProps[tt.refName].Value.Extensions)
254+
}
255+
} else if tt.refName == "" {
256+
if tt.isEntryRef {
257+
assert.Empty(t, tt.request.ExtensionsSchema.Properties["spec"].Value.Properties[tt.request.MappingConfig.MajorVersion].Value.Properties["entry"].Value.Properties)
258+
} else {
259+
assert.Empty(t, tt.request.ExtensionsSchema.Properties["spec"].Value.Properties[tt.request.MappingConfig.MajorVersion].Value.Properties)
260+
}
177261
}
178262
})
179263
}

tools/openapi2crd/pkg/plugins/references.go

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"slices"
2222
"strings"
2323

24+
configv1alpha1 "github.com/mongodb/mongodb-atlas-kubernetes/tools/openapi2crd/pkg/apis/config/v1alpha1"
2425
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
2526
"k8s.io/apimachinery/pkg/util/sets"
2627
)
@@ -37,37 +38,55 @@ func (r *References) Process(req *MappingProcessorRequest) error {
3738
majorVersionSpec := req.CRD.Spec.Validation.OpenAPIV3Schema.Properties["spec"].Properties[req.MappingConfig.MajorVersion]
3839

3940
for _, ref := range req.MappingConfig.ParametersMapping.References {
40-
var refProp apiextensions.JSONSchemaProps
41-
42-
openApiPropertyPath := strings.Split(ref.Property, ".")
43-
openApiProperty := openApiPropertyPath[len(openApiPropertyPath)-1]
44-
refProp.Type = "object"
45-
46-
switch len(ref.Target.Properties) {
47-
case 0:
48-
return errors.New("reference target must have at least one property defined")
49-
case 1:
50-
refProp.Description = fmt.Sprintf("A reference to a %q resource.\nThe value of %q will be used to set %q.\nMutually exclusive with the %q property.", ref.Target.Type.Kind, ref.Target.Properties[0], openApiProperty, openApiProperty)
51-
default:
52-
bulleted := "- " + strings.Join(ref.Target.Properties, "\n- ")
53-
refProp.Description = fmt.Sprintf("A reference to a %q resource.\nOne of the following mutually exclusive values will be used to retrieve the %q value:\n\n%s\n\nMutually exclusive with the %q property.", ref.Target.Type.Kind, openApiProperty, bulleted, openApiProperty)
41+
err := r.addReference(ref, &majorVersionSpec)
42+
if err != nil {
43+
return err
5444
}
45+
req.CRD.Spec.Validation.OpenAPIV3Schema.Properties["spec"].Properties[req.MappingConfig.MajorVersion] = majorVersionSpec
46+
}
5547

56-
refProp.Properties = map[string]apiextensions.JSONSchemaProps{
57-
"name": {
58-
Type: "string",
59-
Description: fmt.Sprintf(`Name of the %q resource.`, ref.Target.Type.Kind),
60-
},
48+
entrySpec := req.CRD.Spec.Validation.OpenAPIV3Schema.Properties["spec"].Properties[req.MappingConfig.MajorVersion].Properties["entry"]
49+
for _, ref := range req.MappingConfig.EntryMapping.References {
50+
err := r.addReference(ref, &entrySpec)
51+
if err != nil {
52+
return err
6153
}
54+
req.CRD.Spec.Validation.OpenAPIV3Schema.Properties["spec"].Properties[req.MappingConfig.MajorVersion].Properties["entry"] = entrySpec
55+
}
6256

63-
required := sets.New(majorVersionSpec.Required...)
64-
required.Delete(openApiProperty)
65-
majorVersionSpec.Required = required.UnsortedList()
66-
slices.Sort(majorVersionSpec.Required)
57+
return nil
58+
}
6759

68-
majorVersionSpec.Properties[ref.Name] = refProp
69-
req.CRD.Spec.Validation.OpenAPIV3Schema.Properties["spec"].Properties[req.MappingConfig.MajorVersion] = majorVersionSpec
60+
func (r *References) addReference(ref configv1alpha1.Reference, targetSchema *apiextensions.JSONSchemaProps) error {
61+
var referenceSchema apiextensions.JSONSchemaProps
62+
63+
openApiPropertyPath := strings.Split(ref.Property, ".")
64+
openApiProperty := openApiPropertyPath[len(openApiPropertyPath)-1]
65+
referenceSchema.Type = "object"
66+
67+
switch len(ref.Target.Properties) {
68+
case 0:
69+
return errors.New("reference target must have at least one property defined")
70+
case 1:
71+
referenceSchema.Description = fmt.Sprintf("A reference to a %q resource.\nThe value of %q will be used to set %q.\nMutually exclusive with the %q property.", ref.Target.Type.Kind, ref.Target.Properties[0], openApiProperty, openApiProperty)
72+
default:
73+
bulleted := "- " + strings.Join(ref.Target.Properties, "\n- ")
74+
referenceSchema.Description = fmt.Sprintf("A reference to a %q resource.\nOne of the following mutually exclusive values will be used to retrieve the %q value:\n\n%s\n\nMutually exclusive with the %q property.", ref.Target.Type.Kind, openApiProperty, bulleted, openApiProperty)
75+
}
76+
77+
referenceSchema.Properties = map[string]apiextensions.JSONSchemaProps{
78+
"name": {
79+
Type: "string",
80+
Description: fmt.Sprintf(`Name of the %q resource.`, ref.Target.Type.Kind),
81+
},
7082
}
7183

84+
required := sets.New(targetSchema.Required...)
85+
required.Delete(openApiProperty)
86+
targetSchema.Required = required.UnsortedList()
87+
slices.Sort(targetSchema.Required)
88+
89+
targetSchema.Properties[ref.Name] = referenceSchema
90+
7291
return nil
7392
}

0 commit comments

Comments
 (0)