diff --git a/internal/test/scorecard/clients.go b/internal/test/scorecard/clients.go index 108394d3..6d786fa0 100644 --- a/internal/test/scorecard/clients.go +++ b/internal/test/scorecard/clients.go @@ -602,6 +602,29 @@ type GrafanaClient struct { *commonCryostatRESTClient } +func (client *GrafanaClient) GetDashboardByUID(ctx context.Context, uid string) (*DashBoard, error) { + url := client.Base.JoinPath(client.BasePath, "api/dashboards/uid", uid) + header := make(http.Header) + header.Add("Accept", "*/*") + + resp, err := SendRequest(ctx, client.Client, http.MethodGet, url.String(), nil, header) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if !StatusOK(resp.StatusCode) { + return nil, fmt.Errorf("API request failed with status code: %d, response body: %s, and headers:\n%s", resp.StatusCode, ReadError(resp), ReadHeader(resp)) + } + + dashboard := &DashBoard{} + if err = ReadJSON(resp, dashboard); err != nil { + return nil, fmt.Errorf("failed to read response body: %s", err.Error()) + } + + return dashboard, nil +} + func (client *GrafanaClient) GetDatasourceByName(ctx context.Context, name string) (*DataSource, error) { url := client.Base.JoinPath(client.BasePath, "api/datasources/name", GRAFANA_DATASOURCE_NAME) header := make(http.Header) diff --git a/internal/test/scorecard/tests.go b/internal/test/scorecard/tests.go index ad6c986e..cf0e2687 100644 --- a/internal/test/scorecard/tests.go +++ b/internal/test/scorecard/tests.go @@ -366,5 +366,14 @@ func CryostatGrafanaTest(bundle *apimanifests.Bundle, namespace string, openShif return r.fail(fmt.Sprintf("datasource %s is invalid: %s", GRAFANA_DATASOURCE_NAME, err.Error())) } + dashboard, err := apiClient.Grafana().GetDashboardByUID(context.Background(), GRAFANA_DASHBOARD_UID) + if err != nil { + return r.fail(fmt.Sprintf("failed to get dashboard %s: %s", GRAFANA_DASHBOARD_UID, err.Error())) + } + + if err = dashboard.Valid(); err != nil { + return r.fail(fmt.Sprintf("dashboard %s is invalid: %s", GRAFANA_DASHBOARD_UID, err.Error())) + } + return r.TestResult } diff --git a/internal/test/scorecard/types.go b/internal/test/scorecard/types.go index e8b9cf72..132d356d 100644 --- a/internal/test/scorecard/types.go +++ b/internal/test/scorecard/types.go @@ -164,7 +164,7 @@ const ( // DataSource represents a Grafana data source type DataSource struct { - ID int64 `json:"id"` + ID uint32 `json:"id"` UID string `json:"uid"` Name string `json:"name"` @@ -199,3 +199,59 @@ func (ds *DataSource) Valid() error { return nil } + +// DashBoard represents a Grafana dashboard +type DashBoard struct { + DashBoardMeta `json:"meta"` + DashBoardInfo `json:"dashboard"` +} + +type DashBoardMeta struct { + Slug string `json:"slug"` + URL string `json:"url"` + Provisioned bool `json:"provisioned"` +} + +type DashBoardInfo struct { + UID string `json:"uid"` + Title string `json:"title"` + Annotations map[string]interface{} `json:"annotations"` + Panels []Panel `json:"panels"` +} + +// Panel represents a Grafana panel. +// A panel can be used either for displaying data or separating groups +type Panel struct { + ID uint32 `json:"id"` + Title string `json:"title"` + Type string `json:"type"` + Targets []PanelQuery `json:"targets"` + Panels []Panel `json:"panels"` +} + +type PanelQuery struct { + RawQuery bool `json:"rawQuery"` + RefID string `json:"refId"` + Target string `json:"target"` + Type string `json:"table"` +} + +func (db *DashBoard) Valid() error { + if db.UID != GRAFANA_DASHBOARD_UID { + return fmt.Errorf("expected dashboard uid %s, but got %s", GRAFANA_DASHBOARD_UID, db.UID) + } + + if db.Title != GRAFANA_DASHBOARD_TITLE { + return fmt.Errorf("expected dashboard title %s, but got %s", GRAFANA_DASHBOARD_TITLE, db.Title) + } + + if !db.Provisioned { + return errors.New("expected dashboard to be provisioned, but got unprovisioned") + } + + if len(db.Panels) == 0 { + return errors.New("expected dashboard to have panels, but got 0") + } + + return nil +}