Skip to content

Commit 717b4ef

Browse files
helderjss-urbaniak
andauthored
CLOUDP-357676: (AutoGen) Group reconciliation (#2939)
* remove generics * add idea to ignore licenses * add group reconcialiation # Conflicts: # internal/generated/controller/group/handler_v20250312.go * fix linter * implement new crapi interface * return errors on reconciler registration * fix translator construction * fix controller registration --------- Co-authored-by: Sergiusz Urbaniak <[email protected]>
1 parent dfab2aa commit 717b4ef

File tree

18 files changed

+447
-200
lines changed

18 files changed

+447
-200
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,7 @@ validate-api-docs: api-docs
732732
.PHONY: addlicenses
733733
addlicense-check:
734734
addlicense -check -l apache -c "MongoDB Inc" \
735+
-ignore ".idea/*" \
735736
-ignore "**/*.md" \
736737
-ignore "**/*.yaml" \
737738
-ignore "**/*.yml" \

internal/controller/customresource/customresource.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func ValidateResourceVersion(ctx *workflow.Context, resource akov2.AtlasCustomRe
8585
return workflow.OK()
8686
}
8787

88-
func IsResourcePolicyKeepOrDefault(resource akov2.AtlasCustomResource, protectionFlag bool) bool {
88+
func IsResourcePolicyKeepOrDefault(resource metav1.Object, protectionFlag bool) bool {
8989
if policy, ok := resource.GetAnnotations()[ResourcePolicyAnnotation]; ok {
9090
return policy == ResourcePolicyKeep
9191
}

internal/controller/registry.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import (
4747
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/dryrun"
4848
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/featureflags"
4949
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/generated/controller/connectionsecret"
50+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/generated/controller/group"
5051
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/pointer"
5152
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/version"
5253
ctrlstate "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/state"
@@ -89,7 +90,9 @@ func NewRegistry(predicates []predicate.Predicate, deletionProtection bool, logg
8990
}
9091

9192
func (r *Registry) RegisterWithDryRunManager(mgr *dryrun.Manager, ap atlas.Provider) error {
92-
r.registerControllers(mgr, ap)
93+
if err := r.registerControllers(mgr, ap); err != nil {
94+
return fmt.Errorf("error registering controllers: %w", err)
95+
}
9396

9497
for _, reconciler := range r.reconcilers {
9598
mgr.SetupReconciler(reconciler)
@@ -99,7 +102,9 @@ func (r *Registry) RegisterWithDryRunManager(mgr *dryrun.Manager, ap atlas.Provi
99102
}
100103

101104
func (r *Registry) RegisterWithManager(mgr ctrl.Manager, skipNameValidation bool, ap atlas.Provider) error {
102-
r.registerControllers(mgr, ap)
105+
if err := r.registerControllers(mgr, ap); err != nil {
106+
return fmt.Errorf("error registering controllers: %w", err)
107+
}
103108

104109
for _, reconciler := range r.reconcilers {
105110
if err := reconciler.SetupWithManager(mgr, skipNameValidation); err != nil {
@@ -109,9 +114,9 @@ func (r *Registry) RegisterWithManager(mgr ctrl.Manager, skipNameValidation bool
109114
return nil
110115
}
111116

112-
func (r *Registry) registerControllers(c cluster.Cluster, ap atlas.Provider) {
117+
func (r *Registry) registerControllers(c cluster.Cluster, ap atlas.Provider) error {
113118
if len(r.reconcilers) > 0 {
114-
return
119+
return nil
115120
}
116121

117122
var reconcilers []Reconciler
@@ -138,8 +143,16 @@ func (r *Registry) registerControllers(c cluster.Cluster, ap atlas.Provider) {
138143
if version.IsExperimental() {
139144
// Add experimental controllers here
140145
reconcilers = append(reconcilers, connectionsecret.NewConnectionSecretReconciler(c, r.defaultPredicates(), ap, r.logger, r.globalSecretRef))
146+
groupReconciler, err := group.NewGroupReconciler(c, ap, r.logger, r.globalSecretRef, r.deletionProtection, true, r.defaultPredicates())
147+
if err != nil {
148+
return fmt.Errorf("error creating group reconciler: %w", err)
149+
}
150+
reconcilers = append(reconcilers, newCtrlStateReconciler(groupReconciler, r.maxConcurrentReconciles))
141151
}
152+
142153
r.reconcilers = reconcilers
154+
155+
return nil
143156
}
144157

145158
// deprecatedPredicates are to be phased out in favor of defaultPredicates

internal/crapi/crapi.go

Lines changed: 28 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,9 @@
1616
package crapi
1717

1818
import (
19-
"fmt"
20-
"reflect"
21-
22-
"github.com/getkin/kin-openapi/openapi3"
2319
"sigs.k8s.io/controller-runtime/pkg/client"
2420

2521
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/crapi/refs"
26-
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/crapi/unstructured"
2722
)
2823

2924
// Translator allows to translate back and forth between a CRD schema
@@ -36,140 +31,33 @@ type Translator interface {
3631

3732
// Mappings returns all the OpenAPi custom reference extensions, or an error
3833
Mappings() ([]*refs.Mapping, error)
39-
}
40-
41-
// APIImporter can translate itself into Kubernetes Objects.
42-
// Use to customize or accelerate translations ad-hoc
43-
type APIImporter[T any, P refs.PtrClientObj[T]] interface {
44-
FromAPI(tr Translator, target P, objs ...client.Object) ([]client.Object, error)
45-
}
46-
47-
// APIExporter can translate itself to an API Object.
48-
// Use to customize or accelerate translations ad-hoc
49-
type APIExporter[T any] interface {
50-
ToAPI(tr Translator, target *T, objs ...client.Object) error
51-
}
52-
53-
// ToAPI translates a source Kubernetes spec into a target API structure.
54-
// It uses the spec only to populate ethe API request, nothing from the status.
55-
// The target is set to a API request struct to be filled.
56-
// The source is set to the Kubernetes CR value. Only the spec data is used here.
57-
// The request includes the translator and the dependencies associated with the
58-
// source CR, usually Kubernetes secrets.
59-
func ToAPI[T any](tr Translator, target *T, source client.Object, objs ...client.Object) error {
60-
exporter, ok := (source).(APIExporter[T])
61-
if ok {
62-
return exporter.ToAPI(tr, target)
63-
}
64-
unstructuredSrc, err := unstructured.ToUnstructured(source)
65-
if err != nil {
66-
return fmt.Errorf("failed to convert k8s source value to unstructured: %w", err)
67-
}
68-
targetUnstructured := map[string]any{}
69-
70-
if err := collapseReferences(tr, unstructuredSrc, source, objs); err != nil {
71-
return fmt.Errorf("failed to process API mappings: %w", err)
72-
}
73-
74-
targetType := reflect.TypeOf(target).Elem()
75-
if targetType.Kind() != reflect.Struct {
76-
return fmt.Errorf("target must be a struct but got %v", targetType.Kind())
77-
}
78-
79-
value, err := unstructured.GetField[map[string]any](unstructuredSrc, "spec", tr.MajorVersion())
80-
if err != nil {
81-
return fmt.Errorf("failed to access source spec value: %w", err)
82-
}
83-
unstructured.CopyFields(targetUnstructured, value)
84-
rawEntry := value["entry"]
85-
if entry, ok := rawEntry.(map[string]any); ok {
86-
unstructured.CopyFields(targetUnstructured, entry)
87-
}
88-
if err := unstructured.FromUnstructured(target, targetUnstructured); err != nil {
89-
return fmt.Errorf("failed to set structured value from unstructured: %w", err)
90-
}
91-
return nil
92-
}
9334

94-
// FromAPI translates a source API structure into a Kubernetes object.
95-
// The API source is used to populate the Kubernetes spec, including the
96-
// spec.entry and status as well.
97-
// The target is set to CR value to be filled. Both spec and stuatus are filled.
98-
// The source is set to API response.
99-
// The request includes the translator and any dependencies associated with the
100-
// source CR.
101-
// Returns any extra objects extracted from the response as separate Kubernetes
102-
// objects, such as Kubernetes secrets, for instance. This list does not include
103-
// the mutated target, and will be empty if nothing else was extracted off the ç
104-
// response.
105-
func FromAPI[S any, T any, P refs.PtrClientObj[T]](tr Translator, target P, source *S, objs ...client.Object) ([]client.Object, error) {
106-
importer, ok := any(source).(APIImporter[T, P])
107-
if ok {
108-
return importer.FromAPI(tr, target, objs...)
109-
}
110-
sourceUnstructured, err := unstructured.ToUnstructured(source)
111-
if err != nil {
112-
return nil, fmt.Errorf("failed to convert API source value to unstructured: %w", err)
113-
}
114-
115-
targetUnstructured, err := unstructured.ToUnstructured(target)
116-
if err != nil {
117-
return nil, fmt.Errorf("failed to convert target value to unstructured: %w", err)
118-
}
119-
120-
versionedSpec, err := unstructured.GetOrCreateField(
121-
targetUnstructured, map[string]any{}, "spec", tr.MajorVersion())
122-
if err != nil {
123-
return nil, fmt.Errorf("failed to ensure versioned spec object in unstructured target: %w", err)
124-
}
125-
versionedStatus, err := unstructured.GetOrCreateField(
126-
targetUnstructured, map[string]any{}, "status", tr.MajorVersion())
127-
if err != nil {
128-
return nil, fmt.Errorf("failed to create versioned status object in unstructured target: %w", err)
129-
}
130-
131-
unstructured.CopyFields(versionedSpec, sourceUnstructured)
132-
versionedSpecEntry := map[string]any{}
133-
unstructured.CopyFields(versionedSpecEntry, sourceUnstructured)
134-
versionedSpec["entry"] = versionedSpecEntry
135-
unstructured.CopyFields(versionedStatus, sourceUnstructured)
136-
137-
extraObjects, err := expandReferences(tr, targetUnstructured, target, objs)
138-
if err != nil {
139-
return nil, fmt.Errorf("failed to process API mappings: %w", err)
140-
}
141-
if err := unstructured.FromUnstructured(target, targetUnstructured); err != nil {
142-
return nil, fmt.Errorf("failed set structured kubernetes object from unstructured: %w", err)
143-
}
144-
return extraObjects, nil
145-
}
146-
147-
// collapseReferences finds all Kubernetes references, solves them and collapses
148-
// them by replacing their values from the reference (e.g Kubernetes secret or
149-
// group), into the corresponding API request value
150-
func collapseReferences(tr Translator, req map[string]any, main client.Object, objs []client.Object) error {
151-
mappings, err := tr.Mappings()
152-
if err != nil {
153-
return fmt.Errorf("failed to extract mappings to collapse: %w", err)
154-
}
155-
return refs.CollapseAll(mappings, main, objs, req)
156-
}
157-
158-
// expandReferences finds all API fields that must be referenced, and expand
159-
// such reference, moving the value (e.g. sensitive field or group id) to a
160-
// referenced Kubernetes object (e.g. Kubernetes secret or Atlas Group)
161-
func expandReferences(tr Translator, cr map[string]any, main client.Object, objs []client.Object) ([]client.Object, error) {
162-
mappings, err := tr.Mappings()
163-
if err != nil {
164-
return nil, fmt.Errorf("failed to extract mappings to expand: %w", err)
165-
}
166-
return refs.ExpandAll(mappings, main, objs, cr)
167-
}
168-
169-
func propertyValueOrNil(schema *openapi3.Schema, propertyName string) *openapi3.Schema {
170-
if schema.Properties != nil &&
171-
schema.Properties[propertyName] != nil && schema.Properties[propertyName].Value != nil {
172-
return schema.Properties[propertyName].Value
173-
}
174-
return nil
35+
// ToAPI translates a source Kubernetes object into a target API structure.
36+
// It uses the spec only to populate ethe API request, nothing from the status.
37+
// The target is set to a API request struct to be filled.
38+
// The source is set to the Kubernetes CR value. Only the spec data is used here.
39+
// The request includes the translator and the dependencies associated with the
40+
// source CR, usually Kubernetes secrets.
41+
ToAPI(target any, source client.Object, objs ...client.Object) error
42+
43+
// FromAPI translates a source API structure into a Kubernetes object.
44+
// The API source is used to populate the Kubernetes spec, including the
45+
// spec.entry and status as well.
46+
// The target is set to CR value to be filled. Both spec and status are filled.
47+
// The source is set to API response.
48+
// The request includes the translator and any dependencies associated with the
49+
// source CR.
50+
// Returns any extra objects extracted from the response as separate Kubernetes
51+
// objects, such as Kubernetes secrets, for instance. This list does not include
52+
// the mutated target, and will be empty if nothing else was extracted off the ç
53+
// response.
54+
FromAPI(target client.Object, source any, objs ...client.Object) ([]client.Object, error)
55+
}
56+
57+
// Request is deprecated do not use
58+
//
59+
// Deprecated: request is no longer used in the ToAPI and FromAPI calls
60+
type Request struct {
61+
Translator Translator
62+
Dependencies []client.Object
17563
}

internal/crapi/crapi_test.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import (
3232

3333
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/crapi"
3434
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/crapi/crds"
35-
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/crapi/refs"
3635
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/crapi/testdata"
3736
v1 "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/crapi/testdata/samples/v1"
3837
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/pointer"
@@ -401,13 +400,13 @@ func TestFromAPI(t *testing.T) {
401400
}
402401
}
403402

404-
func testFromAPI[S any, T any, P refs.PtrClientObj[T]](t *testing.T, kind string, target P, input *S, want client.Object, wantDeps ...client.Object) {
403+
func testFromAPI[S any](t *testing.T, kind string, target client.Object, input *S, want client.Object, wantDeps ...client.Object) {
405404
crdsYML := bytes.NewBuffer(testdata.SampleCRDs)
406405
crd, err := extractCRD(kind, bufio.NewScanner(crdsYML))
407406
require.NoError(t, err)
408407
tr, err := crapi.NewTranslator(crd, version, sdkVersion)
409408
require.NoError(t, err)
410-
results, err := crapi.FromAPI(tr, target, input)
409+
results, err := tr.FromAPI(target, input)
411410
require.NoError(t, err)
412411
assert.Equal(t, want, target)
413412
assert.Equal(t, wantDeps, results)
@@ -577,7 +576,7 @@ func TestToAPIAllRefs(t *testing.T) {
577576
require.NoError(t, err)
578577
tr, err := crapi.NewTranslator(crd, version, sdkVersion)
579578
require.NoError(t, err)
580-
require.NoError(t, crapi.ToAPI(tr, &tc.target, tc.input, tc.deps...))
579+
require.NoError(t, tr.ToAPI(&tc.target, tc.input, tc.deps...))
581580
assert.Equal(t, tc.want, tc.target)
582581
})
583582
}
@@ -2005,7 +2004,7 @@ func testToAPI[T any](t *testing.T, kind string, input client.Object, objs []cli
20052004
require.NoError(t, err)
20062005
tr := trs[sdkVersion]
20072006
require.NotNil(t, tr)
2008-
require.NoError(t, crapi.ToAPI(tr, target, input, objs...))
2007+
require.NoError(t, tr.ToAPI(target, input, objs...))
20092008
assert.Equal(t, want, target)
20102009
}
20112010

internal/crapi/refs/ref.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,6 @@ const (
3434
propertySelectorSuffix = ".#"
3535
)
3636

37-
// PtrClientObj is a pointer type implementing client.Object.
38-
// It represents a Kubernetes object for the controller-runtime library.
39-
type PtrClientObj[T any] interface {
40-
*T
41-
client.Object
42-
}
43-
4437
// Mapping encodes a reference mapping consisting of two extensions,
4538
// a x-kubernetes-mapping and a x-openapi-mapping
4639
type Mapping struct {

internal/crapi/refs/resolver.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func newReferenceResolver() *resolver {
6868
}
6969
}
7070

71-
func newKubeObjectFactory[T any, P PtrClientObj[T]]() func(map[string]any) (client.Object, error) {
71+
func newKubeObjectFactory[T any]() func(map[string]any) (client.Object, error) {
7272
return func(unstructured map[string]any) (client.Object, error) {
7373
obj := new(T)
7474
initializedObj, err := initObject(obj, unstructured)

0 commit comments

Comments
 (0)