Skip to content

Commit

Permalink
feat: deploy a default grafana instance
Browse files Browse the repository at this point in the history
This commit deploys a default Grafana instance in the
monitoring-stack-operator namespace. The instance is configured to
collect all dashboards with the label:
app.kubernetes.io/part-of: monitoring-stack-operator.
  • Loading branch information
fpetkovski authored Nov 5, 2021
1 parent c20a566 commit b1455bd
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 36 deletions.
10 changes: 10 additions & 0 deletions deploy/operator/monitoring-stack-operator-cluster-role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,16 @@ rules:
- list
- update
- watch
- apiGroups:
- integreatly.org
resources:
- grafanas
verbs:
- create
- get
- list
- update
- watch
- apiGroups:
- operators.coreos.com
resources:
Expand Down
141 changes: 127 additions & 14 deletions pkg/controllers/grafana-operator/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,20 @@ import (
"context"
"fmt"
"rhobs/monitoring-stack-operator/pkg/eventsource"
"time"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

networkingv1 "k8s.io/api/networking/v1"

integreatlyv1alpha1 "github.com/grafana-operator/grafana-operator/v4/api/integreatly/v1alpha1"
v1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"

"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
"sigs.k8s.io/controller-runtime/pkg/predicate"
Expand All @@ -34,7 +44,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/handler"

"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
controllerruntime "sigs.k8s.io/controller-runtime"
Expand All @@ -47,19 +56,22 @@ const (
Namespace = "monitoring-stack-operator"
subscriptionName = "monitoring-stack-operator-grafana-operator"
operatorGroupName = "monitoring-stack-operator-grafana-operator"
grafanaName = "monitoring-stack-operator-grafana"
)

type reconciler struct {
k8sClient client.Client
scheme *runtime.Scheme
logger logr.Logger
olmClientset *versioned.Clientset
k8sClientset *kubernetes.Clientset
k8sClient client.Client
scheme *runtime.Scheme
logger logr.Logger
olmClientset *versioned.Clientset
k8sClientset *kubernetes.Clientset
grafanaClientset rest.Interface
}

//+kubebuilder:rbac:groups=operators.coreos.com,resources=subscriptions;operatorgroups,verbs=get;list;watch;create;update;patch,namespace=monitoring-stack-operator
//+kubebuilder:rbac:groups="",resources=namespaces,verbs=list;create
//+kubebuilder:rbac:groups="",resources=namespaces,verbs=get;watch,resourceNames=monitoring-stack-operator
//+kubebuilder:rbac:groups="",resources=namespaces,verbs=list;create
//+kubebuilder:rbac:groups=operators.coreos.com,resources=subscriptions;operatorgroups,verbs=get;list;watch;create;update;patch,namespace=monitoring-stack-operator
//+kubebuilder:rbac:groups=integreatly.org,namespace=monitoring-stack-operator,resources=grafanas,verbs=get;list;watch;create;update

// RegisterWithManager registers the controller with Manager
func RegisterWithManager(mgr controllerruntime.Manager) error {
Expand All @@ -73,13 +85,20 @@ func RegisterWithManager(mgr controllerruntime.Manager) error {
return fmt.Errorf("could not create new Kubernetes clientset: %w", err)
}

grafanaGVK := integreatlyv1alpha1.GroupVersion.WithKind("Grafana")
grafanaClientset, err := apiutil.RESTClientForGVK(grafanaGVK, false, mgr.GetConfig(), serializer.NewCodecFactory(mgr.GetScheme()))
if err != nil {
return fmt.Errorf("could not create new Grafana clientset: %w", err)
}

logger := controllerruntime.Log.WithName("grafana-operator")
r := &reconciler{
k8sClient: mgr.GetClient(),
scheme: mgr.GetScheme(),
logger: logger,
olmClientset: olmClientset,
k8sClientset: k8sClientset,
k8sClient: mgr.GetClient(),
scheme: mgr.GetScheme(),
logger: logger,
olmClientset: olmClientset,
k8sClientset: k8sClientset,
grafanaClientset: grafanaClientset,
}

ctrl, err := controller.New("grafana-operator", mgr, controller.Options{
Expand All @@ -91,7 +110,7 @@ func RegisterWithManager(mgr controllerruntime.Manager) error {
return err
}

ticker := eventsource.NewTickerSource()
ticker := eventsource.NewTickerSource(30 * time.Minute)
go ticker.Run()
if err := ctrl.Watch(ticker, &handler.EnqueueRequestForObject{}); err != nil {
return err
Expand Down Expand Up @@ -121,6 +140,14 @@ func RegisterWithManager(mgr controllerruntime.Manager) error {
return err
}

grafanaInformer := r.grafanaInformer()
go grafanaInformer.Run(nil)
if err := ctrl.Watch(&source.Informer{
Informer: grafanaInformer,
}, &handler.EnqueueRequestForObject{}, predicate.GenerationChangedPredicate{}); err != nil {
return err
}

return nil
}

Expand All @@ -140,6 +167,11 @@ func (r *reconciler) Reconcile(ctx context.Context, _ controllerruntime.Request)
return controllerruntime.Result{}, err
}

r.logger.V(10).Info("Reconciling Grafana instance")
if err := r.reconcileGrafana(ctx); err != nil {
return controllerruntime.Result{}, err
}

return controllerruntime.Result{}, nil
}

Expand Down Expand Up @@ -204,6 +236,25 @@ func (r *reconciler) reconcileSubscription(ctx context.Context) error {
return err
}

func (r *reconciler) reconcileGrafana(ctx context.Context) error {
var grafana integreatlyv1alpha1.Grafana
key := types.NamespacedName{Name: grafanaName, Namespace: Namespace}
err := r.k8sClient.Get(ctx, key, &grafana)

if err == nil {
r.logger.Info("Updating Grafana instance")
grafana.Spec = newGrafana().Spec
return r.k8sClient.Update(ctx, &grafana)
}

if errors.IsNotFound(err) {
r.logger.Info("Creating Grafana instance")
return r.k8sClient.Create(ctx, newGrafana())
}

return err
}

func NewNamespace() *corev1.Namespace {
return &corev1.Namespace{
TypeMeta: metav1.TypeMeta{
Expand Down Expand Up @@ -255,6 +306,64 @@ func NewOperatorGroup() *operatorsv1.OperatorGroup {
}
}

func newGrafana() *integreatlyv1alpha1.Grafana {
flagTrue := true

replicas := int32(1)
maxUnavailable := intstr.FromInt(0)
maxSurge := intstr.FromInt(1)
return &integreatlyv1alpha1.Grafana{
TypeMeta: metav1.TypeMeta{
APIVersion: "integreatly.org/v1alpha1",
Kind: "Grafana",
},
ObjectMeta: metav1.ObjectMeta{
Name: grafanaName,
Namespace: Namespace,
},
Spec: integreatlyv1alpha1.GrafanaSpec{
Ingress: &integreatlyv1alpha1.GrafanaIngress{
Enabled: true,
PathType: string(networkingv1.PathTypePrefix),
Path: "/",
},
Deployment: &integreatlyv1alpha1.GrafanaDeployment{
Replicas: &replicas,
Strategy: &v1.DeploymentStrategy{
Type: v1.RollingUpdateDeploymentStrategyType,
RollingUpdate: &v1.RollingUpdateDeployment{
MaxUnavailable: &maxUnavailable,
MaxSurge: &maxSurge,
},
},
},
DashboardLabelSelector: []*metav1.LabelSelector{
{
MatchLabels: map[string]string{
"app.kubernetes.io/part-of": "monitoring-stack-operator",
},
},
},
Config: integreatlyv1alpha1.GrafanaConfig{
Log: &integreatlyv1alpha1.GrafanaConfigLog{
Mode: "console",
Level: "error",
},
Auth: &integreatlyv1alpha1.GrafanaConfigAuth{
DisableLoginForm: &flagTrue,
DisableSignoutMenu: &flagTrue,
},
AuthAnonymous: &integreatlyv1alpha1.GrafanaConfigAuthAnonymous{
Enabled: &flagTrue,
},
Users: &integreatlyv1alpha1.GrafanaConfigUsers{
ViewersCanEdit: &flagTrue,
},
},
},
}
}

func (r *reconciler) namespaceInformer() cache.SharedIndexInformer {
clientset := r.k8sClientset.CoreV1().RESTClient()
return singleResourceInformer(Namespace, "", "namespaces", &corev1.Namespace{}, clientset)
Expand All @@ -270,6 +379,10 @@ func (r *reconciler) operatorGroupInformer() cache.SharedIndexInformer {
return singleResourceInformer(operatorGroupName, Namespace, "operatorgroups", &operatorsv1.OperatorGroup{}, clientset)
}

func (r *reconciler) grafanaInformer() cache.SharedIndexInformer {
return singleResourceInformer(grafanaName, Namespace, "grafanas", &integreatlyv1alpha1.Grafana{}, r.grafanaClientset)
}

func singleResourceInformer(name string, namespace string, resource string, object runtime.Object, clientset rest.Interface) cache.SharedIndexInformer {
listWatcher := cache.NewListWatchFromClient(
clientset,
Expand Down
4 changes: 2 additions & 2 deletions pkg/eventsource/ticker.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ type TickerSource struct {
}

// NewTickerSource creates a new TickerSource
func NewTickerSource() *TickerSource {
func NewTickerSource(interval time.Duration) *TickerSource {
channel := make(chan event.GenericEvent, 1)
return &TickerSource{
Channel: source.Channel{
Source: channel,
},
ticker: time.NewTicker(30 * time.Second),
ticker: time.NewTicker(interval),
channel: channel,
}
}
Expand Down
21 changes: 1 addition & 20 deletions test/e2e/grafana_operator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"github.com/grafana-operator/grafana-operator/v4/api/integreatly/v1alpha1"
appsv1 "k8s.io/api/apps/v1"

apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
Expand Down Expand Up @@ -69,26 +68,8 @@ func TestControllerRestoresDeletedResources(t *testing.T) {
}
}

func TestGrafanaOperatorForResourcesInOwnNamespace(t *testing.T) {
resources := []client.Object{
newGrafana(operatorNamespace),
}
defer deleteResources(resources...)

func TestDefaultGrafanaInstanceIsCreated(t *testing.T) {
ts := []testCase{
{
name: "Operator should create Grafana Operator CRDs",
scenario: func(t *testing.T) {
f.AssertResourceEventuallyExists("grafanadashboards.integreatly.org", "", &apiextensionsv1.CustomResourceDefinition{})(t)
f.AssertResourceEventuallyExists("grafanadatasources.integreatly.org", "", &apiextensionsv1.CustomResourceDefinition{})(t)
f.AssertResourceEventuallyExists("grafananotificationchannels.integreatly.org", "", &apiextensionsv1.CustomResourceDefinition{})(t)
f.AssertResourceEventuallyExists("grafanas.integreatly.org", "", &apiextensionsv1.CustomResourceDefinition{})(t)
},
},
{
name: "Create grafana operator resources",
scenario: createResources(resources...),
},
{
name: "Operator should reconcile resources in its own namespace",
scenario: func(t *testing.T) {
Expand Down

0 comments on commit b1455bd

Please sign in to comment.