API: prevent provisioned dashboard from being updated (#41894)
This commit is contained in:
@@ -7,12 +7,16 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/services/search"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/tests/testinfra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -92,3 +96,133 @@ func createUser(t *testing.T, store *sqlstore.SQLStore, cmd models.CreateUserCom
|
||||
require.NoError(t, err)
|
||||
return u.Id
|
||||
}
|
||||
|
||||
func TestProvisionioningDashboards(t *testing.T) {
|
||||
// Setup Grafana and its Database
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
DisableAnonymous: true,
|
||||
})
|
||||
|
||||
provDashboardsDir := filepath.Join(dir, "conf", "provisioning", "dashboards")
|
||||
provDashboardsCfg := filepath.Join(provDashboardsDir, "dev.yaml")
|
||||
blob := []byte(fmt.Sprintf(`
|
||||
apiVersion: 1
|
||||
|
||||
providers:
|
||||
- name: 'provisioned dashboards'
|
||||
type: file
|
||||
allowUiUpdates: false
|
||||
options:
|
||||
path: %s`, provDashboardsDir))
|
||||
err := os.WriteFile(provDashboardsCfg, blob, 0644)
|
||||
require.NoError(t, err)
|
||||
input, err := ioutil.ReadFile(filepath.Join("./home.json"))
|
||||
require.NoError(t, err)
|
||||
provDashboardFile := filepath.Join(provDashboardsDir, "home.json")
|
||||
err = ioutil.WriteFile(provDashboardFile, input, 0644)
|
||||
require.NoError(t, err)
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
// Create user
|
||||
createUser(t, store, models.CreateUserCommand{
|
||||
DefaultOrgRole: string(models.ROLE_ADMIN),
|
||||
Password: "admin",
|
||||
Login: "admin",
|
||||
})
|
||||
|
||||
type errorResponseBody struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
t.Run("when provisioned directory is not empty, dashboard should be created", func(t *testing.T) {
|
||||
title := "Grafana Dev Overview & Home"
|
||||
u := fmt.Sprintf("http://admin:admin@%s/api/search?query=%s", grafanaListedAddr, url.QueryEscape(title))
|
||||
// nolint:gosec
|
||||
resp, err := http.Get(u)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
dashboardList := &search.HitList{}
|
||||
err = json.Unmarshal(b, dashboardList)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, dashboardList.Len())
|
||||
var dashboardUID string
|
||||
var dashboardID int64
|
||||
for _, d := range *dashboardList {
|
||||
dashboardUID = d.UID
|
||||
dashboardID = d.ID
|
||||
}
|
||||
assert.Equal(t, int64(1), dashboardID)
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
dashboardData string
|
||||
}{
|
||||
{
|
||||
desc: "when updating provisioned dashboard using ID it should fail",
|
||||
dashboardData: fmt.Sprintf(`{"title":"just testing", "id": %d, "version": 1}`, dashboardID),
|
||||
},
|
||||
{
|
||||
desc: "when updating provisioned dashboard using UID is should fail",
|
||||
dashboardData: fmt.Sprintf(`{"title":"just testing", "uid": %q, "version": 1}`, dashboardUID),
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
u := fmt.Sprintf("http://admin:admin@%s/api/dashboards/db", grafanaListedAddr)
|
||||
// nolint:gosec
|
||||
dashboardData, err := simplejson.NewJson([]byte(tc.dashboardData))
|
||||
require.NoError(t, err)
|
||||
buf := &bytes.Buffer{}
|
||||
err = json.NewEncoder(buf).Encode(models.SaveDashboardCommand{
|
||||
Dashboard: dashboardData,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// nolint:gosec
|
||||
resp, err := http.Post(u, "application/json", buf)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
dashboardErr := &errorResponseBody{}
|
||||
err = json.Unmarshal(b, dashboardErr)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, models.ErrDashboardCannotSaveProvisionedDashboard.Reason, dashboardErr.Message)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("deleting provisioned dashboard should fail", func(t *testing.T) {
|
||||
u := fmt.Sprintf("http://admin:admin@%s/api/dashboards/uid/%s", grafanaListedAddr, dashboardUID)
|
||||
req, err := http.NewRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
dashboardErr := &errorResponseBody{}
|
||||
err = json.Unmarshal(b, dashboardErr)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, models.ErrDashboardCannotDeleteProvisionedDashboard.Reason, dashboardErr.Message)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": "-- Grafana --",
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"target": {
|
||||
"limit": 100,
|
||||
"matchAny": false,
|
||||
"tags": [],
|
||||
"type": "dashboard"
|
||||
},
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"graphTooltip": 0,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"gridPos": {
|
||||
"h": 26,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 7,
|
||||
"links": [],
|
||||
"options": {
|
||||
"maxItems": 100,
|
||||
"query": "",
|
||||
"showHeadings": true,
|
||||
"showRecentlyViewed": true,
|
||||
"showSearch": false,
|
||||
"showStarred": true,
|
||||
"tags": []
|
||||
},
|
||||
"pluginVersion": "8.1.0-pre",
|
||||
"tags": [],
|
||||
"title": "Starred",
|
||||
"type": "dashlist"
|
||||
},
|
||||
{
|
||||
"gridPos": {
|
||||
"h": 13,
|
||||
"w": 6,
|
||||
"x": 6,
|
||||
"y": 0
|
||||
},
|
||||
"id": 2,
|
||||
"links": [],
|
||||
"options": {
|
||||
"maxItems": 1000,
|
||||
"query": "",
|
||||
"showHeadings": false,
|
||||
"showRecentlyViewed": false,
|
||||
"showSearch": true,
|
||||
"showStarred": false,
|
||||
"tags": [
|
||||
"panel-tests"
|
||||
]
|
||||
},
|
||||
"pluginVersion": "8.1.0-pre",
|
||||
"tags": [
|
||||
"panel-tests"
|
||||
],
|
||||
"title": "tag: panel-tests",
|
||||
"type": "dashlist"
|
||||
},
|
||||
{
|
||||
"gridPos": {
|
||||
"h": 13,
|
||||
"w": 6,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
},
|
||||
"id": 3,
|
||||
"links": [],
|
||||
"options": {
|
||||
"maxItems": 1000,
|
||||
"query": "",
|
||||
"showHeadings": false,
|
||||
"showRecentlyViewed": false,
|
||||
"showSearch": true,
|
||||
"showStarred": false,
|
||||
"tags": [
|
||||
"gdev",
|
||||
"demo"
|
||||
]
|
||||
},
|
||||
"pluginVersion": "8.1.0-pre",
|
||||
"tags": [
|
||||
"gdev",
|
||||
"demo"
|
||||
],
|
||||
"title": "tag: dashboard-demo",
|
||||
"type": "dashlist"
|
||||
},
|
||||
{
|
||||
"gridPos": {
|
||||
"h": 26,
|
||||
"w": 6,
|
||||
"x": 18,
|
||||
"y": 0
|
||||
},
|
||||
"id": 5,
|
||||
"links": [],
|
||||
"options": {
|
||||
"maxItems": 1000,
|
||||
"query": "",
|
||||
"showHeadings": false,
|
||||
"showRecentlyViewed": false,
|
||||
"showSearch": true,
|
||||
"showStarred": false,
|
||||
"tags": [
|
||||
"gdev",
|
||||
"datasource-test"
|
||||
]
|
||||
},
|
||||
"pluginVersion": "8.1.0-pre",
|
||||
"tags": [
|
||||
"gdev",
|
||||
"datasource-test"
|
||||
],
|
||||
"title": "Data source tests",
|
||||
"type": "dashlist"
|
||||
},
|
||||
{
|
||||
"gridPos": {
|
||||
"h": 13,
|
||||
"w": 6,
|
||||
"x": 6,
|
||||
"y": 13
|
||||
},
|
||||
"id": 4,
|
||||
"links": [],
|
||||
"options": {
|
||||
"maxItems": 1000,
|
||||
"query": "",
|
||||
"showHeadings": false,
|
||||
"showRecentlyViewed": false,
|
||||
"showSearch": true,
|
||||
"showStarred": false,
|
||||
"tags": [
|
||||
"templating",
|
||||
"gdev"
|
||||
]
|
||||
},
|
||||
"pluginVersion": "8.1.0-pre",
|
||||
"tags": [
|
||||
"templating",
|
||||
"gdev"
|
||||
],
|
||||
"title": "tag: templating ",
|
||||
"type": "dashlist"
|
||||
},
|
||||
{
|
||||
"gridPos": {
|
||||
"h": 13,
|
||||
"w": 6,
|
||||
"x": 12,
|
||||
"y": 13
|
||||
},
|
||||
"id": 8,
|
||||
"links": [],
|
||||
"options": {
|
||||
"maxItems": 1000,
|
||||
"query": "",
|
||||
"showHeadings": false,
|
||||
"showRecentlyViewed": false,
|
||||
"showSearch": true,
|
||||
"showStarred": false,
|
||||
"tags": [
|
||||
"gdev",
|
||||
"transform"
|
||||
]
|
||||
},
|
||||
"pluginVersion": "8.1.0-pre",
|
||||
"tags": [
|
||||
"gdev",
|
||||
"demo"
|
||||
],
|
||||
"title": "tag: transforms",
|
||||
"type": "dashlist"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 30,
|
||||
"style": "dark",
|
||||
"tags": [],
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {
|
||||
"refresh_intervals": [
|
||||
"5s",
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"1d"
|
||||
],
|
||||
"time_options": [
|
||||
"5m",
|
||||
"15m",
|
||||
"1h",
|
||||
"6h",
|
||||
"12h",
|
||||
"24h",
|
||||
"2d",
|
||||
"7d",
|
||||
"30d"
|
||||
]
|
||||
},
|
||||
"timezone": "",
|
||||
"title": "Grafana Dev Overview & Home",
|
||||
"uid": "j6T00KRZz",
|
||||
"version": 2
|
||||
}
|
||||
Reference in New Issue
Block a user