Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow ChildReconciler to manage resource in other namespaces #236

Merged
merged 1 commit into from May 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
43 changes: 42 additions & 1 deletion reconcilers/reconcilers.go
Expand Up @@ -746,6 +746,20 @@ type ChildReconciler struct {
// func(a1, a2 client.Object) bool
SemanticEquals interface{}

// ListOptions allows custom options to be use when listing potential child resources. Each
// resource retrieved as part of the listing is confirmed via OurChild.
//
// Defaults to filtering by the parent's namespace:
// []client.ListOption{
// client.InNamespace(parent.GetNamespace()),
// }
//
// Expected function signature:
// func(ctx context.Context, parent client.Object) []client.ListOption
//
// +optional
ListOptions interface{}

// OurChild is used when there are multiple ChildReconciler for the same ChildType
// controlled by the same parent object. The function return true for child resources
// managed by this ChildReconciler. Objects returned from the DesiredChild function
Expand Down Expand Up @@ -880,6 +894,19 @@ func (r *ChildReconciler) validate(ctx context.Context) error {
}
}

// validate ListOptions function signature:
// nil
// func(ctx context.Context, parent client.Object) []client.ListOption
if r.ListOptions != nil {
fn := reflect.TypeOf(r.ListOptions)
if fn.NumIn() != 2 || fn.NumOut() != 1 ||
!reflect.TypeOf((*context.Context)(nil)).Elem().AssignableTo(fn.In(0)) ||
!reflect.TypeOf(castParentType).AssignableTo(fn.In(1)) ||
!reflect.TypeOf([]client.ListOption{}).AssignableTo(fn.Out(0)) {
return fmt.Errorf("ChildReconciler %q must implement ListOptions: nil | func(context.Context, %s) []client.ListOption, found: %s", r.Name, reflect.TypeOf(castParentType), fn)
}
}

// validate OurChild function signature:
// nil
// func(parent, child client.Object) bool
Expand Down Expand Up @@ -954,7 +981,7 @@ func (r *ChildReconciler) reconcile(ctx context.Context, parent client.Object) (

actual := newEmpty(r.ChildType).(client.Object)
children := newEmpty(r.ChildListType).(client.ObjectList)
if err := c.List(ctx, children, client.InNamespace(parent.GetNamespace())); err != nil {
if err := c.List(ctx, children, r.listOptions(ctx, parent)...); err != nil {
return nil, err
}
items := r.filterChildren(parent, children)
Expand Down Expand Up @@ -1180,6 +1207,20 @@ func (r *ChildReconciler) filterChildren(parent client.Object, children client.O
return items
}

func (r *ChildReconciler) listOptions(ctx context.Context, parent client.Object) []client.ListOption {
if r.ListOptions == nil {
return []client.ListOption{
client.InNamespace(parent.GetNamespace()),
}
}
fn := reflect.ValueOf(r.ListOptions)
out := fn.Call([]reflect.Value{
reflect.ValueOf(ctx),
reflect.ValueOf(parent),
})
return out[0].Interface().([]client.ListOption)
}

func (r *ChildReconciler) ourChild(parent, obj client.Object) bool {
if !metav1.IsControlledBy(obj, parent) {
return false
Expand Down
26 changes: 26 additions & 0 deletions reconcilers/reconcilers_test.go
Expand Up @@ -884,6 +884,32 @@ func TestChildReconciler(t *testing.T) {
return defaultChildReconciler(c)
},
},
}, {
Name: "child is in sync, in a different namespace",
Parent: resourceReady.
SpecDie(func(d *dies.TestResourceSpecDie) {
d.AddField("foo", "bar")
}).
StatusDie(func(d *dies.TestResourceStatusDie) {
d.AddField("foo", "bar")
}),
GivenObjects: []client.Object{
configMapGiven.
MetadataDie(func(d *diemetav1.ObjectMetaDie) {
d.Namespace("other-ns")
}),
},
Metadata: map[string]interface{}{
"SubReconciler": func(t *testing.T, c reconcilers.Config) reconcilers.SubReconciler {
r := defaultChildReconciler(c)
r.ListOptions = func(ctx context.Context, parent *resources.TestResource) []client.ListOption {
return []client.ListOption{
client.InNamespace("other-ns"),
}
}
return r
},
},
}, {
Name: "create child",
Parent: resource.
Expand Down
88 changes: 88 additions & 0 deletions reconcilers/reconcilers_validate_test.go
Expand Up @@ -755,6 +755,94 @@ func TestChildReconciler_validate(t *testing.T) {
},
shouldErr: `ChildReconciler "HarmonizeImmutableFields num out" must implement HarmonizeImmutableFields: nil | func(*v1.Pod, *v1.Pod), found: func(*v1.Pod, *v1.Pod) error`,
},
{
name: "ListOptions",
parent: &corev1.ConfigMap{},
reconciler: &ChildReconciler{
ChildType: &corev1.Pod{},
ChildListType: &corev1.PodList{},
DesiredChild: func(ctx context.Context, parent *corev1.ConfigMap) (*corev1.Pod, error) { return nil, nil },
ReflectChildStatusOnParent: func(parent *corev1.ConfigMap, child *corev1.Pod, err error) {},
MergeBeforeUpdate: func(current, desired *corev1.Pod) {},
SemanticEquals: func(a1, a2 *corev1.Pod) bool { return false },
ListOptions: func(ctx context.Context, parent *corev1.ConfigMap) []client.ListOption { return []client.ListOption{} },
},
},
{
name: "ListOptions num in",
parent: &corev1.ConfigMap{},
reconciler: &ChildReconciler{
Name: "ListOptions num in",
ChildType: &corev1.Pod{},
ChildListType: &corev1.PodList{},
DesiredChild: func(ctx context.Context, parent *corev1.ConfigMap) (*corev1.Pod, error) { return nil, nil },
ReflectChildStatusOnParent: func(parent *corev1.ConfigMap, child *corev1.Pod, err error) {},
MergeBeforeUpdate: func(current, desired *corev1.Pod) {},
SemanticEquals: func(a1, a2 *corev1.Pod) bool { return false },
ListOptions: func() []client.ListOption { return []client.ListOption{} },
},
shouldErr: `ChildReconciler "ListOptions num in" must implement ListOptions: nil | func(context.Context, *v1.ConfigMap) []client.ListOption, found: func() []client.ListOption`,
},
{
name: "ListOptions in 1",
parent: &corev1.ConfigMap{},
reconciler: &ChildReconciler{
Name: "ListOptions in 1",
ChildType: &corev1.Pod{},
ChildListType: &corev1.PodList{},
DesiredChild: func(ctx context.Context, parent *corev1.ConfigMap) (*corev1.Pod, error) { return nil, nil },
ReflectChildStatusOnParent: func(parent *corev1.ConfigMap, child *corev1.Pod, err error) {},
MergeBeforeUpdate: func(current, desired *corev1.Pod) {},
SemanticEquals: func(a1, a2 *corev1.Pod) bool { return false },
ListOptions: func(child *corev1.Secret, parent *corev1.ConfigMap) []client.ListOption { return []client.ListOption{} },
},
shouldErr: `ChildReconciler "ListOptions in 1" must implement ListOptions: nil | func(context.Context, *v1.ConfigMap) []client.ListOption, found: func(*v1.Secret, *v1.ConfigMap) []client.ListOption`,
},
{
name: "ListOptions in 2",
parent: &corev1.ConfigMap{},
reconciler: &ChildReconciler{
Name: "ListOptions in 2",
ChildType: &corev1.Pod{},
ChildListType: &corev1.PodList{},
DesiredChild: func(ctx context.Context, parent *corev1.ConfigMap) (*corev1.Pod, error) { return nil, nil },
ReflectChildStatusOnParent: func(parent *corev1.ConfigMap, child *corev1.Pod, err error) {},
MergeBeforeUpdate: func(current, desired *corev1.Pod) {},
SemanticEquals: func(a1, a2 *corev1.Pod) bool { return false },
ListOptions: func(ctx context.Context, parent *corev1.Secret) []client.ListOption { return []client.ListOption{} },
},
shouldErr: `ChildReconciler "ListOptions in 2" must implement ListOptions: nil | func(context.Context, *v1.ConfigMap) []client.ListOption, found: func(context.Context, *v1.Secret) []client.ListOption`,
},
{
name: "ListOptions num out",
parent: &corev1.ConfigMap{},
reconciler: &ChildReconciler{
Name: "ListOptions num out",
ChildType: &corev1.Pod{},
ChildListType: &corev1.PodList{},
DesiredChild: func(ctx context.Context, parent *corev1.ConfigMap) (*corev1.Pod, error) { return nil, nil },
ReflectChildStatusOnParent: func(parent *corev1.ConfigMap, child *corev1.Pod, err error) {},
MergeBeforeUpdate: func(current, desired *corev1.Pod) {},
SemanticEquals: func(a1, a2 *corev1.Pod) bool { return false },
ListOptions: func(ctx context.Context, parent *corev1.ConfigMap) {},
},
shouldErr: `ChildReconciler "ListOptions num out" must implement ListOptions: nil | func(context.Context, *v1.ConfigMap) []client.ListOption, found: func(context.Context, *v1.ConfigMap)`,
},
{
name: "ListOptions out 1",
parent: &corev1.ConfigMap{},
reconciler: &ChildReconciler{
Name: "ListOptions out 1",
ChildType: &corev1.Pod{},
ChildListType: &corev1.PodList{},
DesiredChild: func(ctx context.Context, parent *corev1.ConfigMap) (*corev1.Pod, error) { return nil, nil },
ReflectChildStatusOnParent: func(parent *corev1.ConfigMap, child *corev1.Pod, err error) {},
MergeBeforeUpdate: func(current, desired *corev1.Pod) {},
SemanticEquals: func(a1, a2 *corev1.Pod) bool { return false },
ListOptions: func(ctx context.Context, parent *corev1.ConfigMap) client.ListOptions { return client.ListOptions{} },
},
shouldErr: `ChildReconciler "ListOptions out 1" must implement ListOptions: nil | func(context.Context, *v1.ConfigMap) []client.ListOption, found: func(context.Context, *v1.ConfigMap) client.ListOptions`,
},
{
name: "OurChild",
parent: &corev1.ConfigMap{},
Expand Down