Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 94 additions & 3 deletions controllers/contactpoint_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,22 @@ import (
kuberr "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

simplejson "github.com/bitly/go-simplejson"
genapi "github.com/grafana/grafana-openapi-client-go/client"
"github.com/grafana/grafana-openapi-client-go/client/provisioning"
"github.com/grafana/grafana-openapi-client-go/models"
grafanav1beta1 "github.com/grafana/grafana-operator/v5/api/v1beta1"
client2 "github.com/grafana/grafana-operator/v5/controllers/client"
corev1 "k8s.io/api/core/v1"
)

const (
Expand Down Expand Up @@ -260,9 +265,95 @@ func (r *GrafanaContactPointReconciler) finalize(ctx context.Context, contactPoi
}

// SetupWithManager sets up the controller with the Manager.
func (r *GrafanaContactPointReconciler) SetupWithManager(mgr ctrl.Manager) error {
func (r *GrafanaContactPointReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error {
const (
secretIndexKey string = ".metadata.secret"
configMapIndexKey string = ".metadata.configMap"
)

// Index the contact points by the Secret references they (may) point at.
if err := mgr.GetCache().IndexField(ctx, &grafanav1beta1.GrafanaContactPoint{}, secretIndexKey,
r.indexSecretSource()); err != nil {
return fmt.Errorf("failed setting secret index fields: %w", err)
}

// Index the contact points by the ConfigMap references they (may) point at.
if err := mgr.GetCache().IndexField(ctx, &grafanav1beta1.GrafanaContactPoint{}, configMapIndexKey,
r.indexConfigMapSource()); err != nil {
return fmt.Errorf("failed setting configmap index fields: %w", err)
}

return ctrl.NewControllerManagedBy(mgr).
For(&grafanav1beta1.GrafanaContactPoint{}).
WithEventFilter(ignoreStatusUpdates()).
For(&grafanav1beta1.GrafanaContactPoint{}, builder.WithPredicates(
ignoreStatusUpdates(),
)).
Watches(
&corev1.Secret{},
handler.EnqueueRequestsFromMapFunc(r.requestsForChangeByField(secretIndexKey)),
).
Watches(
&corev1.ConfigMap{},
handler.EnqueueRequestsFromMapFunc(r.requestsForChangeByField(configMapIndexKey)),
).
Complete(r)
}

func (r *GrafanaContactPointReconciler) indexSecretSource() func(o client.Object) []string {
return func(o client.Object) []string {
contactPoint, ok := o.(*grafanav1beta1.GrafanaContactPoint)
if !ok {
panic(fmt.Sprintf("Expected a GrafanaContactPoint, got %T", o))
}

var secretRefs []string

for _, valueFrom := range contactPoint.Spec.ValuesFrom {
if valueFrom.ValueFrom.SecretKeyRef != nil {
secretRefs = append(secretRefs, fmt.Sprintf("%s/%s", contactPoint.Namespace, valueFrom.ValueFrom.SecretKeyRef.Name))
}
}

return secretRefs
}
}

func (r *GrafanaContactPointReconciler) indexConfigMapSource() func(o client.Object) []string {
return func(o client.Object) []string {
contactPoint, ok := o.(*grafanav1beta1.GrafanaContactPoint)
if !ok {
panic(fmt.Sprintf("Expected a GrafanaContactPoint, got %T", o))
}

var configMapRefs []string

for _, valueFrom := range contactPoint.Spec.ValuesFrom {
if valueFrom.ValueFrom.ConfigMapKeyRef != nil {
configMapRefs = append(configMapRefs, fmt.Sprintf("%s/%s", contactPoint.Namespace, valueFrom.ValueFrom.ConfigMapKeyRef.Name))
}
}

return configMapRefs
}
}

func (r *GrafanaContactPointReconciler) requestsForChangeByField(indexKey string) handler.MapFunc {
return func(ctx context.Context, o client.Object) []reconcile.Request {
var list grafanav1beta1.GrafanaContactPointList
if err := r.List(ctx, &list, client.MatchingFields{
indexKey: fmt.Sprintf("%s/%s", o.GetNamespace(), o.GetName()),
}); err != nil {
logf.FromContext(ctx).Error(err, "failed to list contact points for watch mapping")
return nil
}

var reqs []reconcile.Request
for _, contactPoint := range list.Items {
reqs = append(reqs, reconcile.Request{NamespacedName: types.NamespacedName{
Namespace: contactPoint.Namespace,
Name: contactPoint.Name,
}})
}

return reqs
}
}
163 changes: 163 additions & 0 deletions controllers/contactpoint_controller_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package controllers

import (
"testing"

"github.com/grafana/grafana-operator/v5/api/v1beta1"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

. "github.com/onsi/ginkgo/v2"
"github.com/stretchr/testify/require"
)

var _ = Describe("ContactPoint Reconciler: Provoke Conditions", func() {
Expand Down Expand Up @@ -114,3 +117,163 @@ var _ = Describe("ContactPoint Reconciler: Provoke Conditions", func() {
})
}
})

func TestContactPointIndexing(t *testing.T) {
reconciler := &GrafanaContactPointReconciler{
Client: k8sClient,
}

t.Run("indexSecretSource returns correct secret references", func(t *testing.T) {
cp := &v1beta1.GrafanaContactPoint{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test-namespace",
Name: "test-contactpoint",
},
Spec: v1beta1.GrafanaContactPointSpec{
ValuesFrom: []v1beta1.ValueFrom{
{
ValueFrom: v1beta1.ValueFromSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "secret1",
},
Key: "key1",
},
},
},
{
ValueFrom: v1beta1.ValueFromSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "secret2",
},
Key: "key2",
},
},
},
{
ValueFrom: v1beta1.ValueFromSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "configmap1",
},
Key: "key1",
},
},
},
},
},
}

indexFunc := reconciler.indexSecretSource()
result := indexFunc(cp)

expected := []string{"test-namespace/secret1", "test-namespace/secret2"}
require.Equal(t, expected, result)
})

t.Run("indexConfigMapSource returns correct configmap references", func(t *testing.T) {
cp := &v1beta1.GrafanaContactPoint{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test-namespace",
Name: "test-contactpoint",
},
Spec: v1beta1.GrafanaContactPointSpec{
ValuesFrom: []v1beta1.ValueFrom{
{
ValueFrom: v1beta1.ValueFromSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "secret1",
},
Key: "key1",
},
},
},
{
ValueFrom: v1beta1.ValueFromSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "configmap1",
},
Key: "key1",
},
},
},
{
ValueFrom: v1beta1.ValueFromSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "configmap2",
},
Key: "key2",
},
},
},
},
},
}

indexFunc := reconciler.indexConfigMapSource()
result := indexFunc(cp)

expected := []string{"test-namespace/configmap1", "test-namespace/configmap2"}
require.Equal(t, expected, result)
})

t.Run("indexSecretSource returns empty slice when no secret references", func(t *testing.T) {
cp := &v1beta1.GrafanaContactPoint{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test-namespace",
Name: "test-contactpoint",
},
Spec: v1beta1.GrafanaContactPointSpec{
ValuesFrom: []v1beta1.ValueFrom{
{
ValueFrom: v1beta1.ValueFromSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "configmap1",
},
Key: "key1",
},
},
},
},
},
}

indexFunc := reconciler.indexSecretSource()
result := indexFunc(cp)

require.Empty(t, result)
})

t.Run("indexConfigMapSource returns empty slice when no configmap references", func(t *testing.T) {
cp := &v1beta1.GrafanaContactPoint{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test-namespace",
Name: "test-contactpoint",
},
Spec: v1beta1.GrafanaContactPointSpec{
ValuesFrom: []v1beta1.ValueFrom{
{
ValueFrom: v1beta1.ValueFromSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "secret1",
},
Key: "key1",
},
},
},
},
},
}

indexFunc := reconciler.indexConfigMapSource()
result := indexFunc(cp)

require.Empty(t, result)
})
}
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ func main() { // nolint:gocyclo
if err = (&controllers.GrafanaContactPointReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
}).SetupWithManager(ctx, mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "GrafanaContactPoint")
os.Exit(1)
}
Expand Down
Loading