Skip to content

Commit f5f2192

Browse files
marefraknuds1
andauthored
Adds support for Grafana v2 backend plugins (#128)
Add plugin v2 protobuf definitions. Add node-plugin mimicking hashicorp/go-plugin. Implement plugin v1 and v2 so that you can run plugin using Grafana `<7.0.0` (v1) and `7.x.x and up` (v2). Support device scale factor and accept-language. Improve handling of validating/default render opts. Improve handling of env variables for plugin v2 coming from plugin host (grafana). Fixes #45 Co-authored-by: Arve Knudsen <[email protected]>
1 parent 1e2488c commit f5f2192

File tree

16 files changed

+928
-130
lines changed

16 files changed

+928
-130
lines changed

default.json

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
"service": {
33
"host": null,
44
"port": 8081,
5+
56
"metrics": {
67
"enabled": false,
78
"collectDefaultMetrics": true,
89
"requestDurationBuckets": [1, 5, 7, 9, 11, 13, 15, 20, 30]
910
},
11+
1012
"logging": {
1113
"level": "info",
1214
"console": {
@@ -16,18 +18,28 @@
1618
}
1719
},
1820
"rendering": {
19-
"timezone": null,
2021
"chromeBin": null,
22+
"args": [
23+
"--no-sandbox"
24+
],
2125
"ignoresHttpsErrors": false,
26+
27+
"timezone": null,
28+
"acceptLanguage": null,
29+
"width": 1000,
30+
"height": 500,
31+
"deviceScaleFactor": 1,
32+
"maxWidth": 3080,
33+
"maxHeight": 3000,
34+
"maxDeviceScaleFactor": 3,
35+
2236
"mode": "default",
2337
"clustering": {
2438
"mode": "browser",
2539
"maxConcurrency": 5
2640
},
41+
2742
"verboseLogging": false,
28-
"dumpio": false,
29-
"args": [
30-
"--no-sandbox"
31-
]
43+
"dumpio": false
3244
}
3345
}

dev.json

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
"service": {
33
"host": "localhost",
44
"port": 8081,
5+
56
"metrics": {
67
"enabled": true,
78
"collectDefaultMetrics": true,
89
"requestDurationBuckets": [1, 5, 7, 9, 11, 13, 15, 20, 30]
910
},
11+
1012
"logging": {
1113
"level": "debug",
1214
"console": {
@@ -16,19 +18,29 @@
1618
}
1719
},
1820
"rendering": {
19-
"timezone": null,
2021
"chromeBin": null,
22+
"args": [
23+
"--no-sandbox",
24+
"--disable-setuid-sandbox"
25+
],
2126
"ignoresHttpsErrors": false,
27+
28+
"timezone": null,
29+
"acceptLanguage": null,
30+
"width": 1000,
31+
"height": 500,
32+
"deviceScaleFactor": 2,
33+
"maxWidth": 1980,
34+
"maxHeight": 8000,
35+
"maxDeviceScaleFactor": 10,
36+
2237
"mode": "default",
2338
"clustering": {
2439
"mode": "browser",
2540
"maxConcurrency": 5
2641
},
42+
2743
"verboseLogging": false,
28-
"dumpio": true,
29-
"args": [
30-
"--no-sandbox",
31-
"--disable-setuid-sandbox"
32-
]
44+
"dumpio": true
3345
}
3446
}

proto/pluginv2.proto

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
syntax = "proto3";
2+
package pluginv2;
3+
4+
option go_package = ".;pluginv2";
5+
6+
//-----------------------------------------------
7+
// Common
8+
//-----------------------------------------------
9+
10+
message DataSourceConfig {
11+
int64 id = 1;
12+
string name = 2;
13+
string url = 3;
14+
string user = 4;
15+
string database = 5;
16+
bool basicAuthEnabled = 6;
17+
string basicAuthUser = 7;
18+
19+
// from [data_source.json_data] field in the database
20+
bytes jsonData = 8;
21+
22+
// from [data_source.secure_json_data] field in the database
23+
map<string,string> decryptedSecureJsonData = 9;
24+
int64 lastUpdatedMS = 10;
25+
}
26+
27+
message PluginConfig {
28+
int64 orgId = 1;
29+
string pluginId = 2;
30+
31+
// from [plugin_setting.json_data] field in the database
32+
bytes jsonData = 3;
33+
34+
// from [plugin_setting.secure_json_data] field in the database
35+
map<string,string> decryptedSecureJsonData = 4;
36+
int64 lastUpdatedMS = 5;
37+
38+
DataSourceConfig datasourceConfig = 6;
39+
}
40+
41+
message User {
42+
string login = 1;
43+
string name = 2;
44+
string email = 3;
45+
string role = 4;
46+
}
47+
48+
//---------------------------------------------------------
49+
// Resource service enables HTTP-style requests over gRPC.
50+
//---------------------------------------------------------
51+
52+
service Resource {
53+
rpc CallResource(CallResourceRequest) returns (stream CallResourceResponse);
54+
}
55+
56+
message StringList {
57+
repeated string values = 1;
58+
}
59+
60+
message CallResourceRequest {
61+
PluginConfig config = 1;
62+
User user = 2;
63+
string path = 3;
64+
string method = 4;
65+
string url = 5;
66+
map<string,StringList> headers = 6;
67+
bytes body = 7;
68+
}
69+
70+
message CallResourceResponse {
71+
int32 code = 1;
72+
map<string,StringList> headers = 2;
73+
bytes body = 3;
74+
}
75+
76+
//-----------------------------------------------
77+
// Data
78+
//-----------------------------------------------
79+
80+
service Data {
81+
rpc QueryData(QueryDataRequest) returns (QueryDataResponse);
82+
}
83+
84+
message TimeRange {
85+
int64 fromEpochMS = 1;
86+
int64 toEpochMS = 2;
87+
}
88+
89+
message DataQuery {
90+
string refId = 1;
91+
int64 maxDataPoints = 2;
92+
int64 intervalMS = 3;
93+
TimeRange timeRange = 4;
94+
bytes json = 5;
95+
}
96+
97+
// QueryDataRequest
98+
message QueryDataRequest {
99+
// Plugin Configuration
100+
PluginConfig config = 1;
101+
102+
//Info about the user who calls the plugin.
103+
User user = 2;
104+
105+
// Environment info
106+
map<string,string> headers = 3;
107+
108+
// List of data queries
109+
repeated DataQuery queries = 4;
110+
}
111+
112+
message QueryDataResponse {
113+
// Map of refId to response
114+
map<string,DataResponse> responses = 1;
115+
}
116+
117+
message DataResponse {
118+
// Arrow encoded DataFrames
119+
// Frame has its own meta, warnings, and repeats refId
120+
repeated bytes frames = 1;
121+
string error = 2;
122+
bytes jsonMeta = 3; // Warning: Current ignored by frontend. Would be for metadata about the query.
123+
}
124+
125+
//-----------------------------------------------
126+
// Diagnostics
127+
//-----------------------------------------------
128+
129+
service Diagnostics {
130+
rpc CheckHealth(CheckHealthRequest) returns (CheckHealthResponse);
131+
rpc CollectMetrics(CollectMetricsRequest) returns (CollectMetricsResponse);
132+
}
133+
134+
message CollectMetricsRequest {
135+
}
136+
137+
message CollectMetricsResponse {
138+
message Payload {
139+
bytes prometheus = 1;
140+
}
141+
142+
Payload metrics = 1;
143+
}
144+
145+
message CheckHealthRequest {
146+
PluginConfig config = 1;
147+
}
148+
149+
message CheckHealthResponse {
150+
enum HealthStatus {
151+
UNKNOWN = 0;
152+
OK = 1;
153+
ERROR = 2;
154+
}
155+
156+
HealthStatus status = 1;
157+
string message = 2;
158+
bytes jsonDetails = 3;
159+
}
160+
161+
//-----------------------------------------------
162+
// Transform - Very experimental
163+
//-----------------------------------------------
164+
165+
service Transform {
166+
rpc TransformData(QueryDataRequest) returns (QueryDataResponse);
167+
}
168+
169+
service TransformDataCallBack {
170+
rpc QueryData(QueryDataRequest) returns (QueryDataResponse);
171+
}

proto/rendererv2.proto

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
syntax = "proto3";
2+
package pluginextensionv2;
3+
4+
option go_package = ".;pluginextensionv2";
5+
6+
message StringList {
7+
repeated string values = 1;
8+
}
9+
10+
message RenderRequest {
11+
string url = 1;
12+
int32 width = 2;
13+
int32 height = 3;
14+
float deviceScaleFactor = 4;
15+
string filePath = 5;
16+
string renderKey = 6;
17+
string domain = 7;
18+
int32 timeout = 8;
19+
string timezone = 9;
20+
map<string, StringList> headers = 10;
21+
}
22+
23+
message RenderResponse {
24+
string error = 1;
25+
}
26+
27+
service Renderer {
28+
rpc Render(RenderRequest) returns (RenderResponse);
29+
}

src/app.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
import * as path from 'path';
22
import * as puppeteer from 'puppeteer';
33
import * as _ from 'lodash';
4-
import { GrpcPlugin } from './plugin/grpc-plugin';
4+
import { RenderGRPCPluginV1 } from './plugin/v1/grpc_plugin';
5+
import { RenderGRPCPluginV2 } from './plugin/v2/grpc_plugin';
56
import { HttpServer } from './service/http-server';
67
import { ConsoleLogger, PluginLogger } from './logger';
78
import { createBrowser } from './browser';
89
import * as minimist from 'minimist';
910
import { defaultPluginConfig, defaultServiceConfig, readJSONFileSync, PluginConfig, ServiceConfig } from './config';
11+
import { serve } from './node-plugin';
1012

1113
async function main() {
1214
const argv = minimist(process.argv.slice(2));
1315
const env = Object.assign({}, process.env);
1416
const command = argv._[0];
1517

1618
if (command === undefined) {
19+
const logger = new PluginLogger();
1720
const config: PluginConfig = defaultPluginConfig;
1821
populatePluginConfigFromEnv(config, env);
1922

@@ -26,10 +29,24 @@ async function main() {
2629
config.rendering.chromeBin = [path.dirname(process.execPath), ...parts].join(path.sep);
2730
}
2831

29-
const logger = new PluginLogger();
30-
const browser = createBrowser(config.rendering, logger);
31-
const plugin = new GrpcPlugin(config, logger, browser);
32-
plugin.start();
32+
serve({
33+
handshakeConfig: {
34+
protocolVersion: 2,
35+
magicCookieKey: 'grafana_plugin_type',
36+
magicCookieValue: 'datasource',
37+
},
38+
versionedPlugins: {
39+
1: {
40+
'grafana-image-renderer': new RenderGRPCPluginV1(config, logger),
41+
},
42+
2: {
43+
renderer: new RenderGRPCPluginV2(config, logger),
44+
},
45+
},
46+
logger: logger,
47+
grpcHost: config.plugin.grpc.host,
48+
grpcPort: config.plugin.grpc.port,
49+
});
3350
} else if (command === 'server') {
3451
let config: ServiceConfig = defaultServiceConfig;
3552

@@ -61,6 +78,7 @@ main().catch(err => {
6178
});
6279

6380
function populatePluginConfigFromEnv(config: PluginConfig, env: NodeJS.ProcessEnv) {
81+
// Plugin v1 legacy env variables
6482
if (env['GF_RENDERER_PLUGIN_TZ']) {
6583
config.rendering.timezone = env['GF_RENDERER_PLUGIN_TZ'];
6684
} else {
@@ -82,6 +100,15 @@ function populatePluginConfigFromEnv(config: PluginConfig, env: NodeJS.ProcessEn
82100
if (env['GF_RENDERER_PLUGIN_VERBOSE_LOGGING']) {
83101
config.rendering.verboseLogging = env['GF_RENDERER_PLUGIN_VERBOSE_LOGGING'] === 'true';
84102
}
103+
104+
// Plugin v2 env variables needs to be initiated early
105+
if (env['GF_PLUGIN_GRPC_HOST']) {
106+
config.plugin.grpc.host = env['GF_PLUGIN_GRPC_HOST'] as string;
107+
}
108+
109+
if (env['GF_PLUGIN_GRPC_PORT']) {
110+
config.plugin.grpc.port = parseInt(env['GF_PLUGIN_GRPC_PORT'] as string, 10);
111+
}
85112
}
86113

87114
function populateServiceConfigFromEnv(config: ServiceConfig, env: NodeJS.ProcessEnv) {

0 commit comments

Comments
 (0)