diff --git a/pkg/services/pluginsintegration/plugins_integration_test.go b/pkg/services/pluginsintegration/plugintest/plugins_test.go similarity index 74% rename from pkg/services/pluginsintegration/plugins_integration_test.go rename to pkg/services/pluginsintegration/plugintest/plugins_test.go index 1dce4a244a2..9b5b32244de 100644 --- a/pkg/services/pluginsintegration/plugins_integration_test.go +++ b/pkg/services/pluginsintegration/plugintest/plugins_test.go @@ -1,8 +1,10 @@ -package pluginsintegration +package plugintest import ( "context" "encoding/json" + "fmt" + "net/http" "path/filepath" "testing" "time" @@ -15,14 +17,18 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/fs" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/org" + "github.com/grafana/grafana/pkg/services/pluginsintegration" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" "github.com/grafana/grafana/pkg/services/searchV2" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/tests/testinfra" "github.com/grafana/grafana/pkg/tests/testsuite" "github.com/grafana/grafana/pkg/tsdb/azuremonitor" cloudmonitoring "github.com/grafana/grafana/pkg/tsdb/cloud-monitoring" @@ -49,11 +55,79 @@ func TestMain(m *testing.M) { testsuite.Run(m) } +// This test should run before TestIntegrationPluginManager because this test relies on having a pre-existing Admin user +// and because the SQLStore instance is shared between tests, this test does all the necessary setup +func TestIntegrationPluginDashboards(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ + AnonymousUserRole: org.RoleAdmin, + }) + + appProvisioningPath := filepath.Join(".", "testdata", "provisioning", "apps.yaml") + err := fs.CopyRecursive(appProvisioningPath, filepath.Join(dir, "conf", "provisioning", "plugins", "apps.yaml")) + require.NoError(t, err) + + pluginPath := filepath.Join("testdata", "test-app") + err = fs.CopyRecursive(pluginPath, filepath.Join(dir, "plugins", "test-app")) + require.NoError(t, err) + + grafanaListedAddr, _ := testinfra.StartGrafanaEnv(t, dir, path) + + t.Run("Load plugin and test HTTP API", func(t *testing.T) { + resp, err := http.Get(fmt.Sprintf("http://%s/public/plugins/test-app/dashboards/dashboard.json", grafanaListedAddr)) + require.NoError(t, err) + require.NotNil(t, resp) + t.Cleanup(func() { + err := resp.Body.Close() + require.NoError(t, err) + }) + require.Equal(t, http.StatusOK, resp.StatusCode) + + resp, err = http.Get(fmt.Sprintf("http://%s/api/plugins/test-app/settings", grafanaListedAddr)) + require.NoError(t, err) + require.NotNil(t, resp) + t.Cleanup(func() { + err := resp.Body.Close() + require.NoError(t, err) + }) + require.Equal(t, http.StatusOK, resp.StatusCode) + + resp, err = http.Get(fmt.Sprintf("http://%s/api/plugins/test-app/dashboards", grafanaListedAddr)) + require.NoError(t, err) + require.NotNil(t, resp) + t.Cleanup(func() { + err := resp.Body.Close() + require.NoError(t, err) + }) + + resp, err = http.Post(fmt.Sprintf("http://admin:admin@%s/api/admin/provisioning/plugins/reload", grafanaListedAddr), "", nil) + require.NoError(t, err) + require.NotNil(t, resp) + t.Cleanup(func() { + err := resp.Body.Close() + require.NoError(t, err) + }) + require.Equal(t, http.StatusOK, resp.StatusCode) + + resp, err = http.Get(fmt.Sprintf("http://admin:admin@%s/api/dashboards/uid/wiwhfsg", grafanaListedAddr)) + require.NoError(t, err) + require.NotNil(t, resp) + t.Cleanup(func() { + err := resp.Body.Close() + require.NoError(t, err) + }) + require.Equal(t, http.StatusOK, resp.StatusCode) + }) +} + func TestIntegrationPluginManager(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } - staticRootPath, err := filepath.Abs("../../../public/") + staticRootPath, err := filepath.Abs("../../../../public/") require.NoError(t, err) features := featuremgmt.WithFeatures() @@ -63,7 +137,7 @@ func TestIntegrationPluginManager(t *testing.T) { Azure: &azsettings.AzureSettings{}, PluginSettings: map[string]map[string]string{ "test-app": { - "path": "../../plugins/manager/testdata/test-app", + "path": "../../../plugins/manager/testdata/test-app", }, "test-panel": { "not": "included", @@ -97,7 +171,7 @@ func TestIntegrationPluginManager(t *testing.T) { jaeger := jaeger.ProvideService(hcp) coreRegistry := coreplugin.ProvideCoreRegistry(tracing.InitializeTracerForTest(), am, cw, cm, es, grap, idb, lk, otsdb, pr, tmpo, td, pg, my, ms, graf, pyroscope, parca, zipkin, jaeger) - testCtx := CreateIntegrationTestCtx(t, cfg, coreRegistry) + testCtx := pluginsintegration.CreateIntegrationTestCtx(t, cfg, coreRegistry) ctx := context.Background() verifyCorePluginCatalogue(t, ctx, testCtx.PluginStore) diff --git a/pkg/services/pluginsintegration/plugintest/testdata/provisioning/apps.yaml b/pkg/services/pluginsintegration/plugintest/testdata/provisioning/apps.yaml new file mode 100644 index 00000000000..3dd20fcf5f1 --- /dev/null +++ b/pkg/services/pluginsintegration/plugintest/testdata/provisioning/apps.yaml @@ -0,0 +1,11 @@ +apiVersion: 1 + +apps: + - type: test-app + org_id: 1 + org_name: Main Org. + disabled: false + jsonData: + apiKey: "test-api-key" + secureJsonData: + secretKey: "test-secret-key" diff --git a/pkg/services/pluginsintegration/plugintest/testdata/test-app/dashboards/dashboard.json b/pkg/services/pluginsintegration/plugintest/testdata/test-app/dashboards/dashboard.json new file mode 100644 index 00000000000..d2d4fba74cd --- /dev/null +++ b/pkg/services/pluginsintegration/plugintest/testdata/test-app/dashboards/dashboard.json @@ -0,0 +1,134 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "000000036" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0-89438", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "000000036" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 1 + } + ], + "title": "New panel", + "type": "timeseries" + } + ], + "preload": false, + "schemaVersion": 41, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "wb-temp", + "uid": "wiwhfsg", + "version": 1 +} diff --git a/pkg/services/pluginsintegration/plugintest/testdata/test-app/plugin.json b/pkg/services/pluginsintegration/plugintest/testdata/test-app/plugin.json new file mode 100644 index 00000000000..f1a6b80fe73 --- /dev/null +++ b/pkg/services/pluginsintegration/plugintest/testdata/test-app/plugin.json @@ -0,0 +1,33 @@ +{ + "type": "app", + "name": "Test App", + "id": "test-app", + "info": { + "description": "Official Grafana Test App & Dashboard bundle", + "author": { + "name": "Test Inc.", + "url": "http://test.com" + }, + "keywords": ["test"], + "logos": { + "small": "img/logo_small.png", + "large": "img/logo_large.png" + }, + "screenshots": [ + {"name": "img1", "path": "img/screenshot1.png"} + ], + "links": [ + {"name": "Project site", "url": "http://project.com"}, + {"name": "License & Terms", "url": "http://license.com"} + ], + "version": "1.0.0", + "updated": "2015-02-10" + }, + "includes": [ + {"type": "dashboard", "name": "Dashboard", "path": "dashboards/dashboard.json"} + ], + "dependencies": { + "grafanaDependency": ">=12.x", + "plugins": [] + } +} diff --git a/pkg/services/provisioning/provisioning.go b/pkg/services/provisioning/provisioning.go index c4bd5b68eee..31400e856d3 100644 --- a/pkg/services/provisioning/provisioning.go +++ b/pkg/services/provisioning/provisioning.go @@ -254,6 +254,8 @@ func (ps *ProvisioningServiceImpl) ProvisionDatasources(ctx context.Context) err } func (ps *ProvisioningServiceImpl) ProvisionPlugins(ctx context.Context) error { + ps.mutex.Lock() + defer ps.mutex.Unlock() appPath := filepath.Join(ps.Cfg.ProvisioningPath, "plugins") if err := ps.provisionPlugins(ctx, appPath, ps.pluginStore, ps.pluginsSettings, ps.orgService); err != nil { err = fmt.Errorf("%v: %w", "app provisioning error", err)