diff --git a/api/v1beta1/grafana_types.go b/api/v1beta1/grafana_types.go
index 347c626d8..430192f2a 100644
--- a/api/v1beta1/grafana_types.go
+++ b/api/v1beta1/grafana_types.go
@@ -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.
@@ -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=""
diff --git a/config/crd/bases/grafana.integreatly.org_grafanas.yaml b/config/crd/bases/grafana.integreatly.org_grafanas.yaml
index 231754236..975e9afcf 100644
--- a/config/crd/bases/grafana.integreatly.org_grafanas.yaml
+++ b/config/crd/bases/grafana.integreatly.org_grafanas.yaml
@@ -15,6 +15,9 @@ spec:
scope: Namespaced
versions:
- additionalPrinterColumns:
+ - jsonPath: .status.version
+ name: Version
+ type: string
- jsonPath: .status.stage
name: Stage
type: string
@@ -3976,6 +3979,8 @@ spec:
x-kubernetes-map-type: atomic
type: array
type: object
+ version:
+ type: string
type: object
status:
properties:
@@ -3999,6 +4004,8 @@ spec:
type: string
stageStatus:
type: string
+ version:
+ type: string
type: object
type: object
served: true
diff --git a/config/grafana.integreatly.org_grafanas.yaml b/config/grafana.integreatly.org_grafanas.yaml
index b5c2a7d1b..bbdb39558 100644
--- a/config/grafana.integreatly.org_grafanas.yaml
+++ b/config/grafana.integreatly.org_grafanas.yaml
@@ -15,6 +15,9 @@ spec:
scope: Namespaced
versions:
- additionalPrinterColumns:
+ - jsonPath: .status.version
+ name: Version
+ type: string
- jsonPath: .status.stage
name: Stage
type: string
@@ -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
@@ -10061,6 +10069,8 @@ spec:
type: string
stageStatus:
type: string
+ version:
+ type: string
type: object
type: object
served: true
diff --git a/controllers/client/grafana_client.go b/controllers/client/grafana_client.go
index a69343a48..8dd249f51 100644
--- a/controllers/client/grafana_client.go
+++ b/controllers/client/grafana_client.go
@@ -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)
@@ -136,17 +136,23 @@ 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
diff --git a/controllers/grafana_controller.go b/controllers/grafana_controller.go
index 71c457450..950a771d8 100644
--- a/controllers/grafana_controller.go
+++ b/controllers/grafana_controller.go
@@ -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"
@@ -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 (
@@ -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)
@@ -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)
@@ -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,
diff --git a/controllers/reconcilers/grafana/deployment_reconciler.go b/controllers/reconcilers/grafana/deployment_reconciler.go
index 671670170..a686c9945 100644
--- a/controllers/reconcilers/grafana/deployment_reconciler.go
+++ b/controllers/reconcilers/grafana/deployment_reconciler.go
@@ -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)
@@ -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
diff --git a/controllers/reconcilers/grafana/deployment_reconciler_test.go b/controllers/reconcilers/grafana/deployment_reconciler_test.go
index 37f6f77be..12f0d1e5c 100644
--- a/controllers/reconcilers/grafana/deployment_reconciler_test.go
+++ b/controllers/reconcilers/grafana/deployment_reconciler_test.go
@@ -4,20 +4,45 @@ import (
"fmt"
"testing"
+ "github.com/grafana/grafana-operator/v5/api/v1beta1"
config2 "github.com/grafana/grafana-operator/v5/controllers/config"
"github.com/stretchr/testify/assert"
)
func Test_getGrafanaImage(t *testing.T) {
+ cr := &v1beta1.Grafana{
+ Spec: v1beta1.GrafanaSpec{
+ Version: "",
+ },
+ }
+
expectedDeploymentImage := fmt.Sprintf("%s:%s", config2.GrafanaImage, config2.GrafanaVersion)
- assert.Equal(t, expectedDeploymentImage, getGrafanaImage())
+ assert.Equal(t, expectedDeploymentImage, getGrafanaImage(cr))
+}
+
+func Test_getGrafanaImage_specificVersion(t *testing.T) {
+ cr := &v1beta1.Grafana{
+ Spec: v1beta1.GrafanaSpec{
+ Version: "10.4.0",
+ },
+ }
+
+ expectedDeploymentImage := fmt.Sprintf("%s:10.4.0", config2.GrafanaImage)
+
+ assert.Equal(t, expectedDeploymentImage, getGrafanaImage(cr))
}
func Test_getGrafanaImage_withEnvironmentOverride(t *testing.T) {
+ cr := &v1beta1.Grafana{
+ Spec: v1beta1.GrafanaSpec{
+ Version: "",
+ },
+ }
+
expectedDeploymentImage := "I want this grafana image"
t.Setenv("RELATED_IMAGE_GRAFANA", expectedDeploymentImage)
- assert.Equal(t, expectedDeploymentImage, getGrafanaImage())
+ assert.Equal(t, expectedDeploymentImage, getGrafanaImage(cr))
}
diff --git a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanas.yaml b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanas.yaml
index 231754236..975e9afcf 100644
--- a/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanas.yaml
+++ b/deploy/helm/grafana-operator/crds/grafana.integreatly.org_grafanas.yaml
@@ -15,6 +15,9 @@ spec:
scope: Namespaced
versions:
- additionalPrinterColumns:
+ - jsonPath: .status.version
+ name: Version
+ type: string
- jsonPath: .status.stage
name: Stage
type: string
@@ -3976,6 +3979,8 @@ spec:
x-kubernetes-map-type: atomic
type: array
type: object
+ version:
+ type: string
type: object
status:
properties:
@@ -3999,6 +4004,8 @@ spec:
type: string
stageStatus:
type: string
+ version:
+ type: string
type: object
type: object
served: true
diff --git a/deploy/kustomize/base/crds.yaml b/deploy/kustomize/base/crds.yaml
index c70fb10ea..d7a34369a 100644
--- a/deploy/kustomize/base/crds.yaml
+++ b/deploy/kustomize/base/crds.yaml
@@ -960,6 +960,9 @@ spec:
scope: Namespaced
versions:
- additionalPrinterColumns:
+ - jsonPath: .status.version
+ name: Version
+ type: string
- jsonPath: .status.stage
name: Stage
type: string
@@ -10982,6 +10985,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
@@ -11006,6 +11014,8 @@ spec:
type: string
stageStatus:
type: string
+ version:
+ type: string
type: object
type: object
served: true
diff --git a/docs/docs/api.md b/docs/docs/api.md
index 1bcae062c..e3f8100a3 100644
--- a/docs/docs/api.md
+++ b/docs/docs/api.md
@@ -2235,6 +2235,13 @@ GrafanaSpec defines the desired state of Grafana
ServiceAccount sets how the ServiceAccount object should look like with your grafana instance, contains a number of defaults.