Commit 871ad734 by Marcus Efraimsson Committed by GitHub

Backend plugins: Renderer v2 plugin (#23625)

grafana-plugin-model is legacy and is replaced by new backend 
plugins SDK and architecture. Renderer is not part of SDK and 
we want to keep it that way for now since it's highly unlikely there 
will be more than one kind of renderer plugin.
So this PR adds support for renderer plugin v2.
Also adds support sending a Device Scale Factor parameter to the 
plugin v2 remote rendering service and by that replaces #22474.
Adds support sending a Headers parameter to the plugin v2 and
remote rendering service which for now only include 
Accect-Language header (the user locale in browser when using 
Grafana), ref grafana/grafana-image-renderer#45.
Fixes health check json details response.
Adds image renderer plugin configuration settings in defaults.ini 
and sample.ini.

Co-Authored-By: Arve Knudsen <arve.knudsen@gmail.com>
parent 97bb3dcf
......@@ -4,7 +4,7 @@
-include local/Makefile
.PHONY: all deps-go deps-js deps build-go build-server build-cli build-js build build-docker-dev build-docker-full lint-go gosec revive golangci-lint go-vet test-go test-js test run run-frontend clean devenv devenv-down revive-alerting help
.PHONY: all deps-go deps-js deps build-go build-server build-cli build-js build build-docker-dev build-docker-full lint-go gosec revive golangci-lint go-vet test-go test-js test run run-frontend clean devenv devenv-down revive-alerting protobuf help
GO = GO111MODULE=on go
GO_FILES ?= ./pkg/...
......@@ -161,6 +161,16 @@ devenv-down: ## Stop optional services.
##@ Helpers
# We separate the protobuf generation because most development tasks on
# Grafana do not involve changing protobuf files and protoc is not a
# go-gettable dependency and so getting it installed can be inconvenient.
#
# If you are working on changes to protobuf interfaces you may either use
# this target or run the individual scripts below directly.
protobuf: ## Compile protobuf definitions
bash scripts/protobuf-check.sh
bash pkg/plugins/backendplugin/pluginextensionv2/generate.sh
clean: ## Clean up intermediate build artifacts.
@echo "cleaning"
rm -rf node_modules
......
......@@ -693,6 +693,66 @@ disable_sanitize_html = false
enable_alpha = false
app_tls_skip_verify_insecure = false
#################################### Grafana Image Renderer Plugin ##########################
[plugin.grafana-image-renderer]
# Instruct headless browser instance to use a default timezone when not provided by Grafana, e.g. when rendering panel image of alert.
# See ICU’s metaZones.txt (https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt) for a list of supported
# timezone IDs. Fallbacks to TZ environment variable if not set.
rendering_timezone =
# Instruct headless browser instance to use a default language when not provided by Grafana, e.g. when rendering panel image of alert.
# Please refer to the HTTP header Accept-Language to understand how to format this value, e.g. 'fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5'.
rendering_language =
# Instruct headless browser instance to use a default device scale factor when not provided by Grafana, e.g. when rendering panel image of alert.
# Default is 1. Using a higher value will produce more detailed images (higher DPI), but will require more disk space to store an image.
rendering_viewport_device_scale_factor =
# Instruct headless browser instance whether to ignore HTTPS errors during navigation. Per default HTTPS errors are not ignored. Due to
# the security risk it's not recommended to ignore HTTPS errors.
rendering_ignore_https_errors =
# Instruct headless browser instance whether to capture and log verbose information when rendering an image. Default is false and will
# only capture and log error messages. When enabled, debug messages are captured and logged as well.
# For the verbose information to be included in the Grafana server log you have to adjust the rendering log level to debug, configure
# [log].filter = rendering:debug.
rendering_verbose_logging =
# Instruct headless browser instance whether to output its debug and error messages into running process of remote rendering service.
# Default is false. This can be useful to enable (true) when troubleshooting.
rendering_dumpio =
# Additional arguments to pass to the headless browser instance. Default is --no-sandbox. The list of Chromium flags can be found
# here (https://peter.sh/experiments/chromium-command-line-switches/). Multiple arguments is separated with comma-character.
rendering_args =
# You can configure the plugin to use a different browser binary instead of the pre-packaged version of Chromium.
# Please note that this is not recommended, since you may encounter problems if the installed version of Chrome/Chromium is not
# compatible with the plugin.
rendering_chrome_bin =
# Instruct how headless browser instances are created. Default is 'default' and will create a new browser instance on each request.
# Mode 'clustered' will make sure that only a maximum of browsers/incognito pages can execute concurrently.
# Mode 'reusable' will have one browser instance and will create a new incognito page on each request.
rendering_mode =
# When rendering_mode = clustered you can instruct how many browsers or incognito pages can execute concurrently. Default is 'browser'
# and will cluster using browser instances.
# Mode 'context' will cluster using incognito pages.
rendering_clustering_mode =
# When rendering_mode = clustered you can define maximum number of browser instances/incognito pages that can execute concurrently..
rendering_clustering_max_concurrency =
# Limit the maxiumum viewport width, height and device scale factor that can be requested.
rendering_viewport_max_width =
rendering_viewport_max_height =
rendering_viewport_max_device_scale_factor =
# Change the listening host and port of the gRPC server. Default host is 127.0.0.1 and default port is 0 and will automatically assign
# a port not in use.
grpc_host =
grpc_port =
[enterprise]
license_path =
......
......@@ -682,6 +682,66 @@
;enable_alpha = false
;app_tls_skip_verify_insecure = false
#################################### Grafana Image Renderer Plugin ##########################
[plugin.grafana-image-renderer]
# Instruct headless browser instance to use a default timezone when not provided by Grafana, e.g. when rendering panel image of alert.
# See ICU’s metaZones.txt (https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt) for a list of supported
# timezone IDs. Fallbacks to TZ environment variable if not set.
;rendering_timezone =
# Instruct headless browser instance to use a default language when not provided by Grafana, e.g. when rendering panel image of alert.
# Please refer to the HTTP header Accept-Language to understand how to format this value, e.g. 'fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5'.
;rendering_language =
# Instruct headless browser instance to use a default device scale factor when not provided by Grafana, e.g. when rendering panel image of alert.
# Default is 1. Using a higher value will produce more detailed images (higher DPI), but will require more disk space to store an image.
;rendering_viewport_device_scale_factor =
# Instruct headless browser instance whether to ignore HTTPS errors during navigation. Per default HTTPS errors are not ignored. Due to
# the security risk it's not recommended to ignore HTTPS errors.
;rendering_ignore_https_errors =
# Instruct headless browser instance whether to capture and log verbose information when rendering an image. Default is false and will
# only capture and log error messages. When enabled, debug messages are captured and logged as well.
# For the verbose information to be included in the Grafana server log you have to adjust the rendering log level to debug, configure
# [log].filter = rendering:debug.
;rendering_verbose_logging =
# Instruct headless browser instance whether to output its debug and error messages into running process of remote rendering service.
# Default is false. This can be useful to enable (true) when troubleshooting.
;rendering_dumpio =
# Additional arguments to pass to the headless browser instance. Default is --no-sandbox. The list of Chromium flags can be found
# here (https://peter.sh/experiments/chromium-command-line-switches/). Multiple arguments is separated with comma-character.
;rendering_args =
# You can configure the plugin to use a different browser binary instead of the pre-packaged version of Chromium.
# Please note that this is not recommended, since you may encounter problems if the installed version of Chrome/Chromium is not
# compatible with the plugin.
;rendering_chrome_bin =
# Instruct how headless browser instances are created. Default is 'default' and will create a new browser instance on each request.
# Mode 'clustered' will make sure that only a maximum of browsers/incognito pages can execute concurrently.
# Mode 'reusable' will have one browser instance and will create a new incognito page on each request.
;rendering_mode =
# When rendering_mode = clustered you can instruct how many browsers or incognito pages can execute concurrently. Default is 'browser'
# and will cluster using browser instances.
# Mode 'context' will cluster using incognito pages.
;rendering_clustering_mode =
# When rendering_mode = clustered you can define maximum number of browser instances/incognito pages that can execute concurrently..
;rendering_clustering_max_concurrency =
# Limit the maxiumum viewport width, height and device scale factor that can be requested.
;rendering_viewport_max_width =
;rendering_viewport_max_height =
;rendering_viewport_max_device_scale_factor =
# Change the listening host and port of the gRPC server. Default host is 127.0.0.1 and default port is 0 and will automatically assign
# a port not in use.
;grpc_host =
;grpc_port =
[enterprise]
# Path to a valid Grafana Enterprise license.jwt file
;license_path =
......
......@@ -26,6 +26,7 @@ require (
github.com/go-sql-driver/mysql v1.4.1
github.com/go-stack/stack v1.8.0
github.com/gobwas/glob v0.2.3
github.com/golang/protobuf v1.3.4
github.com/google/go-cmp v0.3.1
github.com/gorilla/websocket v1.4.1
github.com/gosimple/slug v1.4.2
......
......@@ -386,15 +386,14 @@ func (hs *HTTPServer) CheckDatasourceHealth(c *models.ReqContext) {
return
}
var jsonDetails map[string]interface{}
payload := map[string]interface{}{
"status": resp.Status.String(),
"message": resp.Message,
"details": jsonDetails,
}
// Unmarshal JSONDetails if it's not empty.
if len(resp.JSONDetails) > 0 {
var jsonDetails map[string]interface{}
err = json.Unmarshal(resp.JSONDetails, &jsonDetails)
if err != nil {
c.JsonApiErr(500, "Failed to unmarshal detailed response from backend plugin", err)
......
package api
import (
"encoding/json"
"errors"
"net/http"
"sort"
......@@ -317,9 +318,19 @@ func (hs *HTTPServer) CheckHealth(c *models.ReqContext) Response {
}
payload := map[string]interface{}{
"status": resp.Status.String(),
"message": resp.Message,
"jsonDetails": resp.JSONDetails,
"status": resp.Status.String(),
"message": resp.Message,
}
// Unmarshal JSONDetails if it's not empty.
if len(resp.JSONDetails) > 0 {
var jsonDetails map[string]interface{}
err = json.Unmarshal(resp.JSONDetails, &jsonDetails)
if err != nil {
return Error(500, "Failed to unmarshal detailed response from backend plugin", err)
}
payload["details"] = jsonDetails
}
if resp.Status != backendplugin.HealthStatusOk {
......
......@@ -40,18 +40,32 @@ func (hs *HTTPServer) RenderToPng(c *models.ReqContext) {
return
}
scale, err := strconv.ParseFloat(queryReader.Get("scale", "1"), 64)
if err != nil {
c.Handle(400, "Render parameters error", fmt.Errorf("Cannot parse scale as float: %s", err))
return
}
headers := http.Header{}
acceptLanguageHeader := c.Req.Header.Values("Accept-Language")
if len(acceptLanguageHeader) > 0 {
headers["Accept-Language"] = acceptLanguageHeader
}
maxConcurrentLimitForApiCalls := 30
result, err := hs.RenderService.Render(c.Req.Context(), rendering.Opts{
Width: width,
Height: height,
Timeout: time.Duration(timeout) * time.Second,
OrgId: c.OrgId,
UserId: c.UserId,
OrgRole: c.OrgRole,
Path: c.Params("*") + queryParams,
Timezone: queryReader.Get("tz", ""),
Encoding: queryReader.Get("encoding", ""),
ConcurrentLimit: maxConcurrentLimitForApiCalls,
Width: width,
Height: height,
Timeout: time.Duration(timeout) * time.Second,
OrgId: c.OrgId,
UserId: c.UserId,
OrgRole: c.OrgRole,
Path: c.Params("*") + queryParams,
Timezone: queryReader.Get("tz", ""),
Encoding: queryReader.Get("encoding", ""),
ConcurrentLimit: maxConcurrentLimitForApiCalls,
DeviceScaleFactor: scale,
Headers: headers,
})
if err != nil && err == rendering.ErrTimeout {
......
......@@ -6,15 +6,15 @@ import (
"net/http"
"time"
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
datasourceV1 "github.com/grafana/grafana-plugin-model/go/datasource"
rendererV1 "github.com/grafana/grafana-plugin-model/go/renderer"
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2"
"github.com/grafana/grafana/pkg/util/errutil"
plugin "github.com/hashicorp/go-plugin"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// BackendPlugin a registered backend plugin.
......@@ -61,6 +61,11 @@ func (p *BackendPlugin) start(ctx context.Context) error {
return err
}
rawRenderer, err := rpcClient.Dispense("renderer")
if err != nil {
return err
}
if rawDiagnostics != nil {
if plugin, ok := rawDiagnostics.(DiagnosticsPlugin); ok {
p.diagnostics = plugin
......@@ -86,6 +91,12 @@ func (p *BackendPlugin) start(ctx context.Context) error {
client.TransformPlugin = plugin
}
}
if rawRenderer != nil {
if plugin, ok := rawRenderer.(pluginextensionv2.RendererPlugin); ok {
client.RendererPlugin = plugin
}
}
} else {
raw, err := rpcClient.Dispense(p.id)
if err != nil {
......
......@@ -3,11 +3,11 @@ package backendplugin
import (
"os/exec"
"github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin"
"github.com/grafana/grafana/pkg/infra/log"
datasourceV1 "github.com/grafana/grafana-plugin-model/go/datasource"
rendererV1 "github.com/grafana/grafana-plugin-model/go/renderer"
"github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2"
goplugin "github.com/hashicorp/go-plugin"
)
......@@ -64,6 +64,17 @@ type PluginDescriptor struct {
startFns PluginStartFuncs
}
// getV2PluginSet returns list of plugins supported on v2.
func getV2PluginSet() goplugin.PluginSet {
return goplugin.PluginSet{
"diagnostics": &grpcplugin.DiagnosticsGRPCPlugin{},
"resource": &grpcplugin.ResourceGRPCPlugin{},
"data": &grpcplugin.DataGRPCPlugin{},
"transform": &grpcplugin.TransformGRPCPlugin{},
"renderer": &pluginextensionv2.RendererGRPCPlugin{},
}
}
// NewBackendPluginDescriptor creates a new backend plugin descriptor
// used for registering a backend datasource plugin.
func NewBackendPluginDescriptor(pluginID, executablePath string, startFns PluginStartFuncs) PluginDescriptor {
......@@ -75,12 +86,7 @@ func NewBackendPluginDescriptor(pluginID, executablePath string, startFns Plugin
DefaultProtocolVersion: {
pluginID: &datasourceV1.DatasourcePluginImpl{},
},
grpcplugin.ProtocolVersion: {
"diagnostics": &grpcplugin.DiagnosticsGRPCPlugin{},
"resource": &grpcplugin.ResourceGRPCPlugin{},
"data": &grpcplugin.DataGRPCPlugin{},
"transform": &grpcplugin.TransformGRPCPlugin{},
},
grpcplugin.ProtocolVersion: getV2PluginSet(),
},
startFns: startFns,
}
......@@ -97,6 +103,7 @@ func NewRendererPluginDescriptor(pluginID, executablePath string, startFns Plugi
DefaultProtocolVersion: {
pluginID: &rendererV1.RendererPluginImpl{},
},
grpcplugin.ProtocolVersion: getV2PluginSet(),
},
startFns: startFns,
}
......@@ -129,4 +136,5 @@ type Client struct {
ResourcePlugin ResourcePlugin
DataPlugin DataPlugin
TransformPlugin TransformPlugin
RendererPlugin pluginextensionv2.RendererPlugin
}
#!/bin/bash
# To compile all protobuf files in this repository, run
# "make protobuf" at the top-level.
set -eu
DST_DIR=../genproto/pluginv2
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
cd "$DIR"
protoc -I ./ rendererv2.proto --go_out=plugins=grpc:./
\ No newline at end of file
package pluginextensionv2
import (
"context"
"github.com/hashicorp/go-plugin"
"google.golang.org/grpc"
)
type RendererPlugin interface {
RendererClient
}
type RendererGRPCPlugin struct {
plugin.NetRPCUnsupportedPlugin
}
func (p *RendererGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error {
return nil
}
func (p *RendererGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) {
return &RendererGRPCClient{NewRendererClient(c)}, nil
}
type RendererGRPCClient struct {
RendererClient
}
func (m *RendererGRPCClient) Render(ctx context.Context, req *RenderRequest, opts ...grpc.CallOption) (*RenderResponse, error) {
return m.RendererClient.Render(ctx, req)
}
var _ RendererClient = &RendererGRPCClient{}
var _ plugin.GRPCPlugin = &RendererGRPCPlugin{}
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: rendererv2.proto
package pluginextensionv2
import (
context "context"
fmt "fmt"
proto "github.com/golang/protobuf/proto"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type StringList struct {
Values []string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *StringList) Reset() { *m = StringList{} }
func (m *StringList) String() string { return proto.CompactTextString(m) }
func (*StringList) ProtoMessage() {}
func (*StringList) Descriptor() ([]byte, []int) {
return fileDescriptor_412d7c60977d55a2, []int{0}
}
func (m *StringList) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_StringList.Unmarshal(m, b)
}
func (m *StringList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_StringList.Marshal(b, m, deterministic)
}
func (m *StringList) XXX_Merge(src proto.Message) {
xxx_messageInfo_StringList.Merge(m, src)
}
func (m *StringList) XXX_Size() int {
return xxx_messageInfo_StringList.Size(m)
}
func (m *StringList) XXX_DiscardUnknown() {
xxx_messageInfo_StringList.DiscardUnknown(m)
}
var xxx_messageInfo_StringList proto.InternalMessageInfo
func (m *StringList) GetValues() []string {
if m != nil {
return m.Values
}
return nil
}
type RenderRequest struct {
Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
Width int32 `protobuf:"varint,2,opt,name=width,proto3" json:"width,omitempty"`
Height int32 `protobuf:"varint,3,opt,name=height,proto3" json:"height,omitempty"`
DeviceScaleFactor float32 `protobuf:"fixed32,4,opt,name=deviceScaleFactor,proto3" json:"deviceScaleFactor,omitempty"`
FilePath string `protobuf:"bytes,5,opt,name=filePath,proto3" json:"filePath,omitempty"`
RenderKey string `protobuf:"bytes,6,opt,name=renderKey,proto3" json:"renderKey,omitempty"`
Domain string `protobuf:"bytes,7,opt,name=domain,proto3" json:"domain,omitempty"`
Timeout int32 `protobuf:"varint,8,opt,name=timeout,proto3" json:"timeout,omitempty"`
Timezone string `protobuf:"bytes,9,opt,name=timezone,proto3" json:"timezone,omitempty"`
Headers map[string]*StringList `protobuf:"bytes,10,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RenderRequest) Reset() { *m = RenderRequest{} }
func (m *RenderRequest) String() string { return proto.CompactTextString(m) }
func (*RenderRequest) ProtoMessage() {}
func (*RenderRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_412d7c60977d55a2, []int{1}
}
func (m *RenderRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RenderRequest.Unmarshal(m, b)
}
func (m *RenderRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_RenderRequest.Marshal(b, m, deterministic)
}
func (m *RenderRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_RenderRequest.Merge(m, src)
}
func (m *RenderRequest) XXX_Size() int {
return xxx_messageInfo_RenderRequest.Size(m)
}
func (m *RenderRequest) XXX_DiscardUnknown() {
xxx_messageInfo_RenderRequest.DiscardUnknown(m)
}
var xxx_messageInfo_RenderRequest proto.InternalMessageInfo
func (m *RenderRequest) GetUrl() string {
if m != nil {
return m.Url
}
return ""
}
func (m *RenderRequest) GetWidth() int32 {
if m != nil {
return m.Width
}
return 0
}
func (m *RenderRequest) GetHeight() int32 {
if m != nil {
return m.Height
}
return 0
}
func (m *RenderRequest) GetDeviceScaleFactor() float32 {
if m != nil {
return m.DeviceScaleFactor
}
return 0
}
func (m *RenderRequest) GetFilePath() string {
if m != nil {
return m.FilePath
}
return ""
}
func (m *RenderRequest) GetRenderKey() string {
if m != nil {
return m.RenderKey
}
return ""
}
func (m *RenderRequest) GetDomain() string {
if m != nil {
return m.Domain
}
return ""
}
func (m *RenderRequest) GetTimeout() int32 {
if m != nil {
return m.Timeout
}
return 0
}
func (m *RenderRequest) GetTimezone() string {
if m != nil {
return m.Timezone
}
return ""
}
func (m *RenderRequest) GetHeaders() map[string]*StringList {
if m != nil {
return m.Headers
}
return nil
}
type RenderResponse struct {
Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RenderResponse) Reset() { *m = RenderResponse{} }
func (m *RenderResponse) String() string { return proto.CompactTextString(m) }
func (*RenderResponse) ProtoMessage() {}
func (*RenderResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_412d7c60977d55a2, []int{2}
}
func (m *RenderResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RenderResponse.Unmarshal(m, b)
}
func (m *RenderResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_RenderResponse.Marshal(b, m, deterministic)
}
func (m *RenderResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_RenderResponse.Merge(m, src)
}
func (m *RenderResponse) XXX_Size() int {
return xxx_messageInfo_RenderResponse.Size(m)
}
func (m *RenderResponse) XXX_DiscardUnknown() {
xxx_messageInfo_RenderResponse.DiscardUnknown(m)
}
var xxx_messageInfo_RenderResponse proto.InternalMessageInfo
func (m *RenderResponse) GetError() string {
if m != nil {
return m.Error
}
return ""
}
func init() {
proto.RegisterType((*StringList)(nil), "pluginextensionv2.StringList")
proto.RegisterType((*RenderRequest)(nil), "pluginextensionv2.RenderRequest")
proto.RegisterMapType((map[string]*StringList)(nil), "pluginextensionv2.RenderRequest.HeadersEntry")
proto.RegisterType((*RenderResponse)(nil), "pluginextensionv2.RenderResponse")
}
func init() {
proto.RegisterFile("rendererv2.proto", fileDescriptor_412d7c60977d55a2)
}
var fileDescriptor_412d7c60977d55a2 = []byte{
// 380 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x92, 0x5f, 0x6f, 0xd3, 0x30,
0x14, 0xc5, 0x95, 0x86, 0xa4, 0xcd, 0x2d, 0xa0, 0xd6, 0xfc, 0x91, 0x55, 0x81, 0x14, 0x2a, 0x84,
0xf2, 0x00, 0x79, 0x48, 0x5f, 0x10, 0xbc, 0x21, 0xf1, 0x47, 0x02, 0x24, 0xe4, 0x3e, 0x95, 0xb7,
0xac, 0xb9, 0x6b, 0xac, 0xa6, 0x76, 0xe7, 0x38, 0xd9, 0xb2, 0x6f, 0xb4, 0x6f, 0x39, 0xc5, 0x4e,
0xd6, 0x4d, 0x9d, 0xb6, 0xb7, 0xfb, 0xf3, 0x3d, 0xbe, 0xc7, 0x3e, 0x36, 0x4c, 0x14, 0x8a, 0x0c,
0x15, 0xaa, 0x3a, 0x89, 0xf7, 0x4a, 0x6a, 0x49, 0xa6, 0xfb, 0xa2, 0xda, 0x70, 0x81, 0x17, 0x1a,
0x45, 0xc9, 0xa5, 0xa8, 0x93, 0xf9, 0x7b, 0x80, 0xa5, 0x56, 0x5c, 0x6c, 0xfe, 0xf0, 0x52, 0x93,
0xd7, 0xe0, 0xd7, 0x69, 0x51, 0x61, 0x49, 0x9d, 0xd0, 0x8d, 0x02, 0xd6, 0xd1, 0xfc, 0xca, 0x85,
0x67, 0xcc, 0x4c, 0x63, 0x78, 0x56, 0x61, 0xa9, 0xc9, 0x04, 0xdc, 0x4a, 0x15, 0xd4, 0x09, 0x9d,
0x28, 0x60, 0x6d, 0x49, 0x5e, 0x82, 0x77, 0xce, 0x33, 0x9d, 0xd3, 0x41, 0xe8, 0x44, 0x1e, 0xb3,
0xd0, 0x4e, 0xcc, 0x91, 0x6f, 0x72, 0x4d, 0x5d, 0xb3, 0xdc, 0x11, 0xf9, 0x08, 0xd3, 0x0c, 0x6b,
0xbe, 0xc6, 0xe5, 0x3a, 0x2d, 0xf0, 0x47, 0xba, 0xd6, 0x52, 0xd1, 0x27, 0xa1, 0x13, 0x0d, 0xd8,
0x71, 0x83, 0xcc, 0x60, 0x74, 0xca, 0x0b, 0xfc, 0x97, 0xea, 0x9c, 0x7a, 0xc6, 0xf2, 0x86, 0xc9,
0x1b, 0x08, 0xec, 0x45, 0x7f, 0x63, 0x43, 0x7d, 0xd3, 0x3c, 0x2c, 0xb4, 0xfe, 0x99, 0xdc, 0xa5,
0x5c, 0xd0, 0xa1, 0x69, 0x75, 0x44, 0x28, 0x0c, 0x35, 0xdf, 0xa1, 0xac, 0x34, 0x1d, 0x99, 0x83,
0xf5, 0xd8, 0x7a, 0xb5, 0xe5, 0xa5, 0x14, 0x48, 0x03, 0xeb, 0xd5, 0x33, 0xf9, 0x09, 0xc3, 0x1c,
0xd3, 0x0c, 0x55, 0x49, 0x21, 0x74, 0xa3, 0x71, 0xf2, 0x29, 0x3e, 0x8a, 0x34, 0xbe, 0x13, 0x54,
0xfc, 0xcb, 0xea, 0xbf, 0x0b, 0xad, 0x1a, 0xd6, 0xef, 0x9e, 0xad, 0xe0, 0xe9, 0xed, 0x46, 0x1b,
0xe7, 0x16, 0x9b, 0x3e, 0xce, 0x2d, 0x36, 0x64, 0x01, 0x9e, 0x09, 0xdf, 0xc4, 0x39, 0x4e, 0xde,
0xde, 0x63, 0x74, 0x78, 0x38, 0x66, 0xb5, 0x5f, 0x06, 0x9f, 0x9d, 0xf9, 0x07, 0x78, 0xde, 0x9f,
0xa0, 0xdc, 0x4b, 0x51, 0x62, 0xfb, 0x32, 0xa8, 0x94, 0x54, 0xdd, 0x78, 0x0b, 0xc9, 0x0a, 0x46,
0xac, 0xfb, 0x20, 0xe4, 0x2f, 0xf8, 0xb6, 0x26, 0xe1, 0x63, 0x17, 0x9a, 0xbd, 0x7b, 0x40, 0x61,
0x0d, 0xbf, 0xbd, 0xfa, 0xff, 0x22, 0xfe, 0x7a, 0xa4, 0x3a, 0xf1, 0xcd, 0x2f, 0x5c, 0x5c, 0x07,
0x00, 0x00, 0xff, 0xff, 0x36, 0x87, 0xfd, 0x2d, 0x99, 0x02, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConnInterface
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion6
// RendererClient is the client API for Renderer service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type RendererClient interface {
Render(ctx context.Context, in *RenderRequest, opts ...grpc.CallOption) (*RenderResponse, error)
}
type rendererClient struct {
cc grpc.ClientConnInterface
}
func NewRendererClient(cc grpc.ClientConnInterface) RendererClient {
return &rendererClient{cc}
}
func (c *rendererClient) Render(ctx context.Context, in *RenderRequest, opts ...grpc.CallOption) (*RenderResponse, error) {
out := new(RenderResponse)
err := c.cc.Invoke(ctx, "/pluginextensionv2.Renderer/Render", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// RendererServer is the server API for Renderer service.
type RendererServer interface {
Render(context.Context, *RenderRequest) (*RenderResponse, error)
}
// UnimplementedRendererServer can be embedded to have forward compatible implementations.
type UnimplementedRendererServer struct {
}
func (*UnimplementedRendererServer) Render(ctx context.Context, req *RenderRequest) (*RenderResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Render not implemented")
}
func RegisterRendererServer(s *grpc.Server, srv RendererServer) {
s.RegisterService(&_Renderer_serviceDesc, srv)
}
func _Renderer_Render_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RenderRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RendererServer).Render(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/pluginextensionv2.Renderer/Render",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RendererServer).Render(ctx, req.(*RenderRequest))
}
return interceptor(ctx, in, info, handler)
}
var _Renderer_serviceDesc = grpc.ServiceDesc{
ServiceName: "pluginextensionv2.Renderer",
HandlerType: (*RendererServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Render",
Handler: _Renderer_Render_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "rendererv2.proto",
}
syntax = "proto3";
package pluginextensionv2;
option go_package = ".;pluginextensionv2";
message StringList {
repeated string values = 1;
}
message RenderRequest {
string url = 1;
int32 width = 2;
int32 height = 3;
float deviceScaleFactor = 4;
string filePath = 5;
string renderKey = 6;
string domain = 7;
int32 timeout = 8;
string timezone = 9;
map<string, StringList> headers = 10;
}
message RenderResponse {
string error = 1;
}
service Renderer {
rpc Render(RenderRequest) returns (RenderResponse);
}
......@@ -8,6 +8,7 @@ import (
pluginModel "github.com/grafana/grafana-plugin-model/go/renderer"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2"
"github.com/grafana/grafana/pkg/util/errutil"
)
......@@ -15,7 +16,8 @@ type RendererPlugin struct {
PluginBase
Executable string `json:"executable,omitempty"`
GrpcPlugin pluginModel.RendererPlugin
GrpcPluginV1 pluginModel.RendererPlugin
GrpcPluginV2 pluginextensionv2.RendererPlugin
backendPluginManager backendplugin.Manager
}
......@@ -34,6 +36,7 @@ func (r *RendererPlugin) Load(decoder *json.Decoder, pluginDir string, backendPl
fullpath := path.Join(r.PluginDir, cmd)
descriptor := backendplugin.NewRendererPluginDescriptor(r.Id, fullpath, backendplugin.PluginStartFuncs{
OnLegacyStart: r.onLegacyPluginStart,
OnStart: r.onPluginStart,
})
if err := backendPluginManager.Register(descriptor); err != nil {
return errutil.Wrapf(err, "Failed to register backend plugin")
......@@ -52,6 +55,11 @@ func (r *RendererPlugin) Start(ctx context.Context) error {
}
func (r *RendererPlugin) onLegacyPluginStart(pluginID string, client *backendplugin.LegacyClient, logger log.Logger) error {
r.GrpcPlugin = client.RendererPlugin
r.GrpcPluginV1 = client.RendererPlugin
return nil
}
func (r *RendererPlugin) onPluginStart(pluginID string, client *backendplugin.Client, logger log.Logger) error {
r.GrpcPluginV2 = client.RendererPlugin
return nil
}
......@@ -46,6 +46,7 @@ func (rs *RenderingService) renderViaHttp(ctx context.Context, renderKey string,
queryParams.Add("timezone", isoTimeOffsetToPosixTz(opts.Timezone))
queryParams.Add("encoding", opts.Encoding)
queryParams.Add("timeout", strconv.Itoa(int(opts.Timeout.Seconds())))
queryParams.Add("deviceScaleFactor", fmt.Sprintf("%f", opts.DeviceScaleFactor))
rendererUrl.RawQuery = queryParams.Encode()
req, err := http.NewRequest("GET", rendererUrl.String(), nil)
......@@ -55,6 +56,10 @@ func (rs *RenderingService) renderViaHttp(ctx context.Context, renderKey string,
req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion))
for k, v := range opts.Headers {
req.Header[k] = v
}
// gives service some additional time to timeout and return possible errors.
reqContext, cancel := context.WithTimeout(ctx, opts.Timeout+time.Second*2)
defer cancel()
......
......@@ -13,16 +13,18 @@ var ErrNoRenderer = errors.New("No renderer plugin found nor is an external rend
var ErrPhantomJSNotInstalled = errors.New("PhantomJS executable not found")
type Opts struct {
Width int
Height int
Timeout time.Duration
OrgId int64
UserId int64
OrgRole models.RoleType
Path string
Encoding string
Timezone string
ConcurrentLimit int
Width int
Height int
Timeout time.Duration
OrgId int64
UserId int64
OrgRole models.RoleType
Path string
Encoding string
Timezone string
ConcurrentLimit int
DeviceScaleFactor float64
Headers map[string][]string
}
type RenderResult struct {
......
......@@ -6,6 +6,7 @@ import (
"time"
pluginModel "github.com/grafana/grafana-plugin-model/go/renderer"
"github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2"
)
func (rs *RenderingService) startPlugin(ctx context.Context) error {
......@@ -13,15 +14,23 @@ func (rs *RenderingService) startPlugin(ctx context.Context) error {
}
func (rs *RenderingService) renderViaPlugin(ctx context.Context, renderKey string, opts Opts) (*RenderResult, error) {
// gives plugin some additional time to timeout and return possible errors.
ctx, cancel := context.WithTimeout(ctx, opts.Timeout+time.Second*2)
defer cancel()
if rs.pluginInfo.GrpcPluginV2 != nil {
return rs.renderViaPluginV2(ctx, renderKey, opts)
}
return rs.renderViaPluginV1(ctx, renderKey, opts)
}
func (rs *RenderingService) renderViaPluginV1(ctx context.Context, renderKey string, opts Opts) (*RenderResult, error) {
pngPath, err := rs.getFilePathForNewImage()
if err != nil {
return nil, err
}
// gives plugin some additional time to timeout and return possible errors.
ctx, cancel := context.WithTimeout(ctx, opts.Timeout+time.Second*2)
defer cancel()
req := &pluginModel.RenderRequest{
Url: rs.getURL(opts.Path),
Width: int32(opts.Width),
......@@ -35,7 +44,46 @@ func (rs *RenderingService) renderViaPlugin(ctx context.Context, renderKey strin
}
rs.log.Debug("calling renderer plugin", "req", req)
rsp, err := rs.pluginInfo.GrpcPlugin.Render(ctx, req)
rsp, err := rs.pluginInfo.GrpcPluginV1.Render(ctx, req)
if err != nil {
return nil, err
}
if rsp.Error != "" {
return nil, fmt.Errorf("rendering failed: %v", rsp.Error)
}
return &RenderResult{FilePath: pngPath}, nil
}
func (rs *RenderingService) renderViaPluginV2(ctx context.Context, renderKey string, opts Opts) (*RenderResult, error) {
pngPath, err := rs.getFilePathForNewImage()
if err != nil {
return nil, err
}
headers := map[string]*pluginextensionv2.StringList{}
for k, values := range opts.Headers {
headers[k] = &pluginextensionv2.StringList{
Values: values,
}
}
req := &pluginextensionv2.RenderRequest{
Url: rs.getURL(opts.Path),
Width: int32(opts.Width),
Height: int32(opts.Height),
DeviceScaleFactor: float32(opts.DeviceScaleFactor),
FilePath: pngPath,
Timeout: int32(opts.Timeout.Seconds()),
RenderKey: renderKey,
Timezone: isoTimeOffsetToPosixTz(opts.Timezone),
Domain: rs.domain,
Headers: headers,
}
rs.log.Debug("Calling renderer plugin", "req", req)
rsp, err := rs.pluginInfo.GrpcPluginV2.Render(ctx, req)
if err != nil {
return nil, err
}
......
......@@ -3,6 +3,7 @@ package rendering
import (
"context"
"fmt"
"math"
"net/url"
"os"
"path/filepath"
......@@ -115,23 +116,27 @@ func (rs *RenderingService) Render(ctx context.Context, opts Opts) (*RenderResul
}, nil
}
if rs.renderAction != nil {
rs.log.Info("Rendering", "path", opts.Path)
renderKey, err := rs.generateAndStoreRenderKey(opts.OrgId, opts.UserId, opts.OrgRole)
if err != nil {
return nil, err
}
if rs.renderAction == nil {
return nil, fmt.Errorf("no renderer found")
}
defer rs.deleteRenderKey(renderKey)
rs.log.Info("Rendering", "path", opts.Path)
if math.IsInf(opts.DeviceScaleFactor, 0) || math.IsNaN(opts.DeviceScaleFactor) || opts.DeviceScaleFactor <= 0 {
opts.DeviceScaleFactor = 1
}
renderKey, err := rs.generateAndStoreRenderKey(opts.OrgId, opts.UserId, opts.OrgRole)
if err != nil {
return nil, err
}
defer func() {
rs.inProgressCount--
}()
defer rs.deleteRenderKey(renderKey)
rs.inProgressCount++
return rs.renderAction(ctx, renderKey, opts)
}
return nil, fmt.Errorf("No renderer found")
defer func() {
rs.inProgressCount--
}()
rs.inProgressCount++
return rs.renderAction(ctx, renderKey, opts)
}
func (rs *RenderingService) GetRenderUser(key string) (*RenderUser, bool) {
......
#!/usr/bin/env bash
# Check whether protobuf & go plugin are installed
PROTOC_HELP_URL="http://google.github.io/proto-lens/installing-protoc.html"
PROTOC_GEN_GO_HELP_URL="https://github.com/golang/protobuf/tree/v1.3.4#installation"
EXIT_CODE=0
if ! [ -x "$(command -v protoc)" ]; then
echo "Protocol Buffers not found."
echo "Please install Protocol Buffers and ensure 'protoc' is available in your PATH."
echo "See ${PROTOC_HELP_URL} for more."
echo
EXIT_CODE=1
fi
if ! [ -x "$(command -v protoc-gen-go)" ]; then
echo "Protocol Buffers Go plugin not found."
echo "Please install the plugin and ensure 'protoc-gen-go' is available in your PATH."
echo "See ${PROTOC_GEN_GO_HELP_URL} for more."
echo
EXIT_CODE=1
fi
exit $EXIT_CODE
\ No newline at end of file
......@@ -144,6 +144,7 @@ github.com/gobwas/glob/syntax/lexer
github.com/gobwas/glob/util/runes
github.com/gobwas/glob/util/strings
# github.com/golang/protobuf v1.3.4
## explicit
github.com/golang/protobuf/proto
github.com/golang/protobuf/protoc-gen-go/descriptor
github.com/golang/protobuf/ptypes
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment