Skip to content

Commit

Permalink
feat: expose grafana version in spec & status
Browse files Browse the repository at this point in the history
  • Loading branch information
theSuess committed Mar 29, 2024
1 parent 76a7113 commit 31e410c
Show file tree
Hide file tree
Showing 11 changed files with 128 additions and 9 deletions.
4 changes: 4 additions & 0 deletions api/v1beta1/grafana_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ type GrafanaSpec struct {
Route *RouteOpenshiftV1 `json:"route,omitempty"`
// Service sets how the service object should look like with your grafana instance, contains a number of defaults.
Service *ServiceV1 `json:"service,omitempty"`
// Version specifies the version of Grafana to use for this deployment. It follows the same format as the docker.io/grafana/grafana tags
Version string `json:"version,omitempty"`
// Deployment sets how the deployment object should look like with your grafana instance, contains a number of defaults.
Deployment *DeploymentV1 `json:"deployment,omitempty"`
// PersistentVolumeClaim creates a PVC if you need to attach one to your grafana instance.
Expand Down Expand Up @@ -116,12 +118,14 @@ type GrafanaStatus struct {
Dashboards NamespacedResourceList `json:"dashboards,omitempty"`
Datasources NamespacedResourceList `json:"datasources,omitempty"`
Folders NamespacedResourceList `json:"folders,omitempty"`
Version string `json:"version,omitempty"`
}

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status

// Grafana is the Schema for the grafanas API
// +kubebuilder:printcolumn:name="Version",type="string",JSONPath=".status.version",description=""
// +kubebuilder:printcolumn:name="Stage",type="string",JSONPath=".status.stage",description=""
// +kubebuilder:printcolumn:name="Stage status",type="string",JSONPath=".status.stageStatus",description=""
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""
Expand Down
7 changes: 7 additions & 0 deletions config/crd/bases/grafana.integreatly.org_grafanas.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ spec:
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .status.version
name: Version
type: string
- jsonPath: .status.stage
name: Stage
type: string
Expand Down Expand Up @@ -3976,6 +3979,8 @@ spec:
x-kubernetes-map-type: atomic
type: array
type: object
version:
type: string
type: object
status:
properties:
Expand All @@ -3999,6 +4004,8 @@ spec:
type: string
stageStatus:
type: string
version:
type: string
type: object
type: object
served: true
Expand Down
10 changes: 10 additions & 0 deletions config/grafana.integreatly.org_grafanas.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ spec:
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .status.version
name: Version
type: string
- jsonPath: .status.stage
name: Stage
type: string
Expand Down Expand Up @@ -10037,6 +10040,11 @@ spec:
x-kubernetes-map-type: atomic
type: array
type: object
version:
description: Version specifies the version of Grafana to use for this
deployment. It follows the same format as the docker.io/grafana/grafana
tags
type: string
type: object
status:
description: GrafanaStatus defines the observed state of Grafana
Expand All @@ -10061,6 +10069,8 @@ spec:
type: string
stageStatus:
type: string
version:
type: string
type: object
type: object
served: true
Expand Down
17 changes: 12 additions & 5 deletions controllers/client/grafana_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func getAdminCredentials(ctx context.Context, c client.Client, grafana *v1beta1.
return credentials, nil
}

func NewGrafanaClient(ctx context.Context, c client.Client, grafana *v1beta1.Grafana) (*grapi.Client, error) {
func NewHTTPClient(grafana *v1beta1.Grafana) *http.Client {
var timeout time.Duration
if grafana.Spec.Client != nil && grafana.Spec.Client.TimeoutSeconds != nil {
timeout = time.Duration(*grafana.Spec.Client.TimeoutSeconds)
Expand All @@ -136,17 +136,24 @@ func NewGrafanaClient(ctx context.Context, c client.Client, grafana *v1beta1.Gra
timeout = 10
}

return &http.Client{
Transport: NewInstrumentedRoundTripper(grafana.Name, metrics.GrafanaApiRequests, grafana.IsExternal()),
Timeout: time.Second * timeout,
}
}

func NewGrafanaClient(ctx context.Context, c client.Client, grafana *v1beta1.Grafana) (*grapi.Client, error) {

credentials, err := getAdminCredentials(ctx, c, grafana)
if err != nil {
return nil, err
}

client := NewHTTPClient(grafana)

clientConfig := grapi.Config{
HTTPHeaders: nil,
Client: &http.Client{
Transport: NewInstrumentedRoundTripper(grafana.Name, metrics.GrafanaApiRequests, grafana.IsExternal()),
Timeout: time.Second * timeout,
},
Client: client,
// TODO populate me
OrgID: 0,
// TODO populate me
Expand Down
47 changes: 47 additions & 0 deletions controllers/grafana_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ package controllers

import (
"context"
"encoding/json"
"fmt"
"reflect"
"time"

"github.com/go-logr/logr"
"github.com/grafana/grafana-operator/v5/controllers/config"
"github.com/grafana/grafana-operator/v5/controllers/metrics"
"github.com/grafana/grafana-operator/v5/controllers/reconcilers"
"github.com/grafana/grafana-operator/v5/controllers/reconcilers/grafana"
Expand All @@ -36,6 +39,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log"

grafanav1beta1 "github.com/grafana/grafana-operator/v5/api/v1beta1"
client2 "github.com/grafana/grafana-operator/v5/controllers/client"
)

const (
Expand Down Expand Up @@ -86,9 +90,21 @@ func (r *GrafanaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
nextStatus.Stage = grafanav1beta1.OperatorStageComplete
nextStatus.StageStatus = grafanav1beta1.OperatorStageResultSuccess
nextStatus.AdminUrl = grafana.Spec.External.URL
v, err := r.getVersion(grafana)
if err != nil {
controllerLog.Error(err, "failed to get version from external instance")
}
nextStatus.Version = v
return r.updateStatus(grafana, nextStatus)
}

if grafana.Spec.Version == "" {
grafana.Spec.Version = config.GrafanaVersion
if err := r.Client.Update(ctx, grafana); err != nil {
return ctrl.Result{}, fmt.Errorf("updating grafana version in spec: %w", err)
}
}

for _, stage := range stages {
controllerLog.Info("running stage", "stage", stage)

Expand Down Expand Up @@ -120,12 +136,36 @@ func (r *GrafanaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
}

if finished {
v, err := r.getVersion(grafana)
if err != nil {
controllerLog.Error(err, "failed to get version from instance")
}
nextStatus.Version = v
controllerLog.Info("grafana installation complete")
}

return r.updateStatus(grafana, nextStatus)
}

func (r *GrafanaReconciler) getVersion(cr *grafanav1beta1.Grafana) (string, error) {
cl := client2.NewHTTPClient(cr)

resp, err := cl.Get(cr.Status.AdminUrl + grafana.GrafanaHealthEndpoint)
if err != nil {
return "", fmt.Errorf("fetching version: %w", err)
}
data := struct {
Version string `json:"version"`
}{}
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
return "", fmt.Errorf("parsing health endpoint data: %w", err)
}
if data.Version == "" {
return "", fmt.Errorf("empty version received from server")
}
return data.Version, nil
}

func (r *GrafanaReconciler) updateStatus(cr *grafanav1beta1.Grafana, nextStatus *grafanav1beta1.GrafanaStatus) (ctrl.Result, error) {
if !reflect.DeepEqual(&cr.Status, nextStatus) {
nextStatus.DeepCopyInto(&cr.Status)
Expand All @@ -144,6 +184,13 @@ func (r *GrafanaReconciler) updateStatus(cr *grafanav1beta1.Grafana, nextStatus
RequeueAfter: RequeueDelay,
}, nil
}
if cr.Status.Version == "" {
r.Log.Info("version not yet found, requeuing")
return ctrl.Result{
Requeue: true,
RequeueAfter: RequeueDelay,
}, nil
}

return ctrl.Result{
Requeue: false,
Expand Down
7 changes: 5 additions & 2 deletions controllers/reconcilers/grafana/deployment_reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,10 @@ func getVolumeMounts(cr *v1beta1.Grafana, scheme *runtime.Scheme) []v1.VolumeMou
return mounts
}

func getGrafanaImage() string {
func getGrafanaImage(cr *v1beta1.Grafana) string {
if cr.Spec.Version != "" {
return fmt.Sprintf("%s:%s", config2.GrafanaImage, cr.Spec.Version)
}
grafanaImg := os.Getenv("RELATED_IMAGE_GRAFANA")
if grafanaImg == "" {
grafanaImg = fmt.Sprintf("%s:%s", config2.GrafanaImage, config2.GrafanaVersion)
Expand All @@ -144,7 +147,7 @@ func getGrafanaImage() string {
func getContainers(cr *v1beta1.Grafana, scheme *runtime.Scheme, vars *v1beta1.OperatorReconcileVars, openshiftPlatform bool) []v1.Container {
var containers []v1.Container

image := getGrafanaImage()
image := getGrafanaImage(cr)
plugins := model.GetPluginsConfigMap(cr, scheme)

// env var to restart containers if plugins change
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ spec:
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .status.version
name: Version
type: string
- jsonPath: .status.stage
name: Stage
type: string
Expand Down Expand Up @@ -3976,6 +3979,8 @@ spec:
x-kubernetes-map-type: atomic
type: array
type: object
version:
type: string
type: object
status:
properties:
Expand All @@ -3999,6 +4004,8 @@ spec:
type: string
stageStatus:
type: string
version:
type: string
type: object
type: object
served: true
Expand Down
15 changes: 14 additions & 1 deletion tests/e2e/example-test/00-assert.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,29 @@ apiVersion: grafana.integreatly.org/v1beta1
kind: Grafana
metadata:
name: grafana
spec:
version: 9.5.17
status:
(wildcard('http://grafana-service.*:3000', adminUrl || '')): true
stage: complete
stageStatus: success
version: 9.5.17
---
apiVersion: grafana.integreatly.org/v1beta1
kind: Grafana
metadata:
name: external-grafana
status:
adminUrl: http://grafana-internal-service
adminUrl: (join('',['http://grafana-internal-service.',$namespace,':3000']))
stage: complete
stageStatus: success
version: 9.5.17
---
apiVersion: grafana.integreatly.org/v1beta1
kind: Grafana
metadata:
name: grafana-ten
status:
stage: complete
stageStatus: success
version: 10.3.5
2 changes: 1 addition & 1 deletion tests/e2e/example-test/00-create-external-grafana.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ metadata:
dashboards: "external-grafana"
spec:
external:
url: http://grafana-internal-service
url: (join('',['http://grafana-internal-service.',$namespace,':3000']))
adminPassword:
name: grafana-internal-admin-credentials
key: GF_SECURITY_ADMIN_PASSWORD
Expand Down
18 changes: 18 additions & 0 deletions tests/e2e/example-test/00-create-versioned-grafana.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
apiVersion: grafana.integreatly.org/v1beta1
kind: Grafana
metadata:
name: grafana-ten
labels:
dashboards: "grafana"
spec:
client:
preferIngress: false
version: 10.3.5
config:
log:
mode: "console"
auth:
disable_login_form: "false"
security:
admin_user: root
admin_password: secret
3 changes: 3 additions & 0 deletions tests/e2e/example-test/chainsaw-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ kind: Test
metadata:
name: example-test
spec:
template: true
steps:
- name: step-00
try:
- apply:
file: 00-create-external-grafana.yaml
- apply:
file: 00-create-grafana.yaml
- apply:
file: 00-create-versioned-grafana.yaml
- assert:
file: 00-assert.yaml
- name: step-01
Expand Down

0 comments on commit 31e410c

Please sign in to comment.