Skip to content

Commit 6cdf340

Browse files
committed
CLOUDP-365467: Add Schema to Translator
Signed-off-by: jose.vazquez <[email protected]>
1 parent 1e205cd commit 6cdf340

File tree

8 files changed

+86
-23
lines changed

8 files changed

+86
-23
lines changed

internal/crapi/crapi.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package crapi
1717

1818
import (
19+
"k8s.io/apimachinery/pkg/runtime"
1920
"sigs.k8s.io/controller-runtime/pkg/client"
2021

2122
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/crapi/refs"
@@ -26,6 +27,9 @@ import (
2627
// A translator is an immutable configuration object, it can be safely shared
2728
// across goroutines
2829
type Translator interface {
30+
// Scheme returns the Kubernetes scheme used to translate the CRD.
31+
Scheme() *runtime.Scheme
32+
2933
// MajorVersion returns the pinned SDK major version
3034
MajorVersion() string
3135

internal/crapi/crapi_test.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,11 @@ import (
2828
corev1 "k8s.io/api/core/v1"
2929
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
3030
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31+
"k8s.io/apimachinery/pkg/runtime"
32+
akoscheme "k8s.io/client-go/kubernetes/scheme"
3133
"sigs.k8s.io/controller-runtime/pkg/client"
3234

35+
akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1"
3336
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/crapi"
3437
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/crapi/crds"
3538
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/crapi/testdata"
@@ -401,10 +404,11 @@ func TestFromAPI(t *testing.T) {
401404
}
402405

403406
func testFromAPI[S any](t *testing.T, kind string, target client.Object, input *S, want client.Object, wantDeps ...client.Object) {
407+
scheme := testScheme(t)
404408
crdsYML := bytes.NewBuffer(testdata.SampleCRDs)
405409
crd, err := extractCRD(kind, bufio.NewScanner(crdsYML))
406410
require.NoError(t, err)
407-
tr, err := crapi.NewTranslator(crd, version, sdkVersion)
411+
tr, err := crapi.NewTranslator(scheme, crd, version, sdkVersion)
408412
require.NoError(t, err)
409413
results, err := tr.FromAPI(target, input)
410414
require.NoError(t, err)
@@ -517,8 +521,7 @@ func TestToAPIAllRefs(t *testing.T) {
517521
"webhookSecret": ([]byte)("sample-webhook-secret"),
518522
},
519523
},
520-
&corev1.Secret{
521-
TypeMeta: metav1.TypeMeta{Kind: "Secret", APIVersion: "v1"},
524+
&corev1.Secret{ // works without type meta set as well
522525
ObjectMeta: metav1.ObjectMeta{Name: "alert-secrets-1", Namespace: "ns"},
523526
Data: map[string][]byte{
524527
"webhookUrl": ([]byte)("sample-webhook-url"),
@@ -571,10 +574,11 @@ func TestToAPIAllRefs(t *testing.T) {
571574
},
572575
} {
573576
t.Run(tc.name, func(t *testing.T) {
577+
scheme := testScheme(t)
574578
crdsYML := bytes.NewBuffer(testdata.SampleCRDs)
575579
crd, err := extractCRD(tc.crd, bufio.NewScanner(crdsYML))
576580
require.NoError(t, err)
577-
tr, err := crapi.NewTranslator(crd, version, sdkVersion)
581+
tr, err := crapi.NewTranslator(scheme, crd, version, sdkVersion)
578582
require.NoError(t, err)
579583
require.NoError(t, tr.ToAPI(&tc.target, tc.input, tc.deps...))
580584
assert.Equal(t, tc.want, tc.target)
@@ -2015,3 +2019,12 @@ func extractCRD(kind string, scanner *bufio.Scanner) (*apiextensionsv1.CustomRes
20152019
}
20162020
}
20172021
}
2022+
2023+
func testScheme(t *testing.T) *runtime.Scheme {
2024+
t.Helper()
2025+
2026+
scheme := runtime.NewScheme()
2027+
require.NoError(t, akov2.AddToScheme(scheme))
2028+
require.NoError(t, akoscheme.AddToScheme(scheme))
2029+
return scheme
2030+
}

internal/crapi/refs/kube.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,13 @@ func (km KubeMapping) fetchReferencedValue(mc *kubeset, target string, reference
7575
return nil, fmt.Errorf("failed to find Kubernetes resource %q: %w", refName, err)
7676
}
7777
gvk := resource.GetObjectKind().GroupVersionKind()
78+
if gvk.Kind == "" || gvk.GroupVersion().String() == "" {
79+
gvks, _, err := mc.scheme.ObjectKinds(resource)
80+
if err != nil || len(gvks) == 0 {
81+
return nil, fmt.Errorf("failed to infer GroupVersionKind for resource %q from scheme: %w", refName, err)
82+
}
83+
gvk = gvks[0]
84+
}
7885
if km.Type.Kind != "" && !km.equal(gvk) {
7986
return nil, fmt.Errorf("resource %q had to be a %q but got %q", refName, km.gvk(), gvk)
8087
}

internal/crapi/refs/kubeset.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,26 @@
1414

1515
package refs
1616

17-
import "sigs.k8s.io/controller-runtime/pkg/client"
17+
import (
18+
"k8s.io/apimachinery/pkg/runtime"
19+
"sigs.k8s.io/controller-runtime/pkg/client"
20+
)
1821

1922
// kubeset holds to the main Kubernetes object being translated,
2023
// and related existing & added Kubernetes dependencies
2124
type kubeset struct {
22-
main client.Object
23-
m map[client.ObjectKey]client.Object
24-
added []client.Object
25+
scheme *runtime.Scheme
26+
main client.Object
27+
m map[client.ObjectKey]client.Object
28+
added []client.Object
2529
}
2630

27-
func newKubeset(main client.Object, deps []client.Object) *kubeset {
31+
func newKubeset(scheme *runtime.Scheme, main client.Object, deps []client.Object) *kubeset {
2832
m := map[client.ObjectKey]client.Object{}
2933
for _, obj := range deps {
3034
m[client.ObjectKeyFromObject(obj)] = obj
3135
}
32-
return &kubeset{main: main, m: m}
36+
return &kubeset{scheme: scheme, main: main, m: m}
3337
}
3438

3539
func (mc *kubeset) find(name string) client.Object {

internal/crapi/refs/kubeset_test.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ import (
2121
"github.com/stretchr/testify/require"
2222
corev1 "k8s.io/api/core/v1"
2323
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24+
"k8s.io/apimachinery/pkg/runtime"
25+
akoscheme "k8s.io/client-go/kubernetes/scheme"
2426
"sigs.k8s.io/controller-runtime/pkg/client"
27+
28+
akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1"
2529
)
2630

2731
var (
@@ -38,7 +42,7 @@ var (
3842
)
3943

4044
func TestNewKubeset(t *testing.T) {
41-
ctx := newKubeset(mainObj, []client.Object{dep1, dep2})
45+
ctx := newKubeset(testScheme(t), mainObj, []client.Object{dep1, dep2})
4246

4347
require.NotNil(t, ctx, "context should not be nil")
4448
assert.Equal(t, mainObj, ctx.main, "main object should be set correctly")
@@ -53,7 +57,7 @@ func TestNewKubeset(t *testing.T) {
5357
}
5458

5559
func TestKubesetFindAndHas(t *testing.T) {
56-
ctx := newKubeset(mainObj, []client.Object{dep1})
60+
ctx := newKubeset(testScheme(t), mainObj, []client.Object{dep1})
5761

5862
testCases := []struct {
5963
name string
@@ -93,7 +97,7 @@ func TestKubesetFindAndHas(t *testing.T) {
9397
}
9498

9599
func TestKubesetAdd(t *testing.T) {
96-
ctx := newKubeset(mainObj, []client.Object{dep1})
100+
ctx := newKubeset(testScheme(t), mainObj, []client.Object{dep1})
97101
require.Len(t, ctx.m, 1, "pre-condition failed: map should have 1 item")
98102
require.Len(t, ctx.added, 0, "pre-condition failed: added slice should be empty")
99103

@@ -122,3 +126,12 @@ func TestKubesetAdd(t *testing.T) {
122126
assert.Contains(t, ctx.added, newDep, "added slice should still contain the first added object")
123127
assert.Contains(t, ctx.added, anotherDep, "added slice should contain the second added object")
124128
}
129+
130+
func testScheme(t *testing.T) *runtime.Scheme {
131+
t.Helper()
132+
133+
scheme := runtime.NewScheme()
134+
require.NoError(t, akov2.AddToScheme(scheme))
135+
require.NoError(t, akoscheme.AddToScheme(scheme))
136+
return scheme
137+
}

internal/crapi/refs/ref.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"sort"
2222

2323
"github.com/getkin/kin-openapi/openapi3"
24+
"k8s.io/apimachinery/pkg/runtime"
2425
"sigs.k8s.io/controller-runtime/pkg/client"
2526

2627
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/crapi/unstructured"
@@ -63,8 +64,8 @@ func FindMappings(schema *openapi3.Schema, path []string) ([]*Mapping, error) {
6364
// ExpandAll takes a list of mappings and expands the ones found in the given
6465
// CR unstructured object. CR corresponds to the main typed object and deps
6566
// are other kubernetes objects related with such main object
66-
func ExpandAll(mappings []*Mapping, main client.Object, deps []client.Object, cr map[string]any) ([]client.Object, error) {
67-
ks := newKubeset(main, deps)
67+
func ExpandAll(scheme *runtime.Scheme, mappings []*Mapping, main client.Object, deps []client.Object, cr map[string]any) ([]client.Object, error) {
68+
ks := newKubeset(scheme, main, deps)
6869
for _, mapping := range mappings {
6970
if err := mapping.expand(ks, cr); err != nil {
7071
return nil, fmt.Errorf("failed to expand reference for %q: %w", mapping.propertyName, err)
@@ -77,8 +78,8 @@ func ExpandAll(mappings []*Mapping, main client.Object, deps []client.Object, cr
7778
// given request unstructured object. The SDK request must map to the main typed
7879
//
7980
// object and deps are other kubernetes objects related with such main object
80-
func CollapseAll(mappings []*Mapping, main client.Object, deps []client.Object, req map[string]any) error {
81-
ks := newKubeset(main, deps)
81+
func CollapseAll(scheme *runtime.Scheme, mappings []*Mapping, main client.Object, deps []client.Object, req map[string]any) error {
82+
ks := newKubeset(scheme, main, deps)
8283
for _, mapping := range mappings {
8384
if err := mapping.collapse(ks, req); err != nil {
8485
return fmt.Errorf("failed to expand reference for %q: %w", mapping.propertyName, err)

internal/crapi/refs/ref_test.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,11 @@ import (
2727
corev1 "k8s.io/api/core/v1"
2828
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2929
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30+
"k8s.io/apimachinery/pkg/runtime"
31+
akoscheme "k8s.io/client-go/kubernetes/scheme"
3032
"sigs.k8s.io/controller-runtime/pkg/client"
3133

34+
akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1"
3235
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/crapi"
3336
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/crapi/crds"
3437
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/crapi/refs"
@@ -90,10 +93,11 @@ func TestMappings(t *testing.T) {
9093
},
9194
} {
9295
t.Run(tc.title, func(t *testing.T) {
96+
scheme := testScheme(t)
9397
crdsYML := bytes.NewBuffer(testdata.SampleCRDs)
9498
crd, err := extractCRD(tc.kind, bufio.NewScanner(crdsYML))
9599
require.NoError(t, err)
96-
tr, err := crapi.NewTranslator(crd, version, sdkVersion)
100+
tr, err := crapi.NewTranslator(scheme, crd, version, sdkVersion)
97101
require.NoError(t, err)
98102
got, err := tr.Mappings()
99103
require.NoError(t, err)
@@ -364,7 +368,7 @@ func TestExpandAll(t *testing.T) {
364368
require.NoError(t, err)
365369
require.NotEmpty(t, refMappings)
366370

367-
added, err := refs.ExpandAll(refMappings, mainObj, tc.deps, tc.obj)
371+
added, err := refs.ExpandAll(testScheme(t), refMappings, mainObj, tc.deps, tc.obj)
368372

369373
if tc.wantErr != "" {
370374
assert.ErrorContains(t, err, tc.wantErr)
@@ -571,7 +575,7 @@ func TestCollapseReferences(t *testing.T) {
571575
refMappings, err := refs.FindMappings(schema, []string{})
572576
require.NoError(t, err)
573577

574-
err = refs.CollapseAll(refMappings, mainObj, tc.deps, tc.obj)
578+
err = refs.CollapseAll(testScheme(t), refMappings, mainObj, tc.deps, tc.obj)
575579

576580
if tc.wantErr != "" {
577581
assert.ErrorContains(t, err, tc.wantErr)
@@ -594,3 +598,12 @@ func mappingSchemaFrom(obj map[string]any) (*openapi3.Schema, error) {
594598
}
595599
return &mappingSchema, nil
596600
}
601+
602+
func testScheme(t *testing.T) *runtime.Scheme {
603+
t.Helper()
604+
605+
scheme := runtime.NewScheme()
606+
require.NoError(t, akov2.AddToScheme(scheme))
607+
require.NoError(t, akoscheme.AddToScheme(scheme))
608+
return scheme
609+
}

internal/crapi/translator.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
"github.com/getkin/kin-openapi/openapi3"
2424
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
25+
"k8s.io/apimachinery/pkg/runtime"
2526
"sigs.k8s.io/controller-runtime/pkg/client"
2627
"sigs.k8s.io/yaml"
2728

@@ -33,6 +34,7 @@ import (
3334
// translator implements Translator to translate from a given CRD to and from
3435
// a given SDK version using the same upstream OpenAPI schema
3536
type translator struct {
37+
scheme *runtime.Scheme
3638
majorVersion string
3739
mappingSchema *openapi3.SchemaRef
3840
}
@@ -130,6 +132,11 @@ func (tr *translator) MajorVersion() string {
130132
return tr.majorVersion
131133
}
132134

135+
// Scheme returns the Kubernetes scheme used to translate the CRD.
136+
func (tr *translator) Scheme() *runtime.Scheme {
137+
return tr.scheme
138+
}
139+
133140
// NewTranslator creates a translator for a particular CRD version. It is also
134141
// locked into a particular API majorVersion.
135142
//
@@ -143,7 +150,7 @@ func (tr *translator) MajorVersion() string {
143150
// v20250312:
144151
//
145152
// In the above case crdVersion is "v1" and majorVersion is "v20250312".
146-
func NewTranslator(crd *apiextensionsv1.CustomResourceDefinition, crdVersion string, majorVersion string) (Translator, error) {
153+
func NewTranslator(scheme *runtime.Scheme, crd *apiextensionsv1.CustomResourceDefinition, crdVersion string, majorVersion string) (Translator, error) {
147154
specVersion := crds.SelectVersion(&crd.Spec, crdVersion)
148155
if err := crds.AssertMajorVersion(specVersion, crd.Spec.Names.Kind, majorVersion); err != nil {
149156
return nil, fmt.Errorf("failed to assert major version %s in CRD: %w", majorVersion, err)
@@ -161,6 +168,7 @@ func NewTranslator(crd *apiextensionsv1.CustomResourceDefinition, crdVersion str
161168
}
162169

163170
return &translator{
171+
scheme: scheme,
164172
majorVersion: majorVersion,
165173
mappingSchema: &openapi3.SchemaRef{Value: &mappingSchema},
166174
}, nil
@@ -216,7 +224,7 @@ func collapseReferences(tr Translator, req map[string]any, main client.Object, o
216224
if err != nil {
217225
return fmt.Errorf("failed to extract mappings to collapse: %w", err)
218226
}
219-
return refs.CollapseAll(mappings, main, objs, req)
227+
return refs.CollapseAll(tr.Scheme(), mappings, main, objs, req)
220228
}
221229

222230
// expandReferences finds all API fields that must be referenced, and expand
@@ -227,7 +235,7 @@ func expandReferences(tr Translator, cr map[string]any, main client.Object, objs
227235
if err != nil {
228236
return nil, fmt.Errorf("failed to extract mappings to expand: %w", err)
229237
}
230-
return refs.ExpandAll(mappings, main, objs, cr)
238+
return refs.ExpandAll(tr.Scheme(), mappings, main, objs, cr)
231239
}
232240

233241
func propertyValueOrNil(schema *openapi3.Schema, propertyName string) *openapi3.Schema {

0 commit comments

Comments
 (0)