mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Backend image rendering as plugin (#11966)
* rendering: headless chrome progress * renderer: minor change * grpc: version hell * updated grpc libs * wip: minor progess * rendering: new image rendering plugin is starting to work * feat: now phantomjs works as well and updated alerting to use new rendering service * refactor: renamed renderer package and service to rendering to make renderer name less confusing (rendering is internal service that handles the renderer plugin now) * rendering: now render key is passed and render auth is working in plugin mode * removed unneeded lines from gitignore * rendering: now plugin mode supports waiting for all panels to complete rendering * fix: LastSeenAt fix for render calls, was not set which causes a lot of updates to Last Seen at during rendering, this should fix sqlite db locked issues in seen in previous releases * change: changed render tz url parameter to use proper timezone name as chrome does not handle UTC offset TZ values * fix: another update to tz param generation * renderer: added http mode to renderer service, new ini setting [rendering] server_url
This commit is contained in:
@@ -1,161 +0,0 @@
|
||||
package renderer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"strconv"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
type RenderOpts struct {
|
||||
Path string
|
||||
Width string
|
||||
Height string
|
||||
Timeout string
|
||||
OrgId int64
|
||||
UserId int64
|
||||
OrgRole models.RoleType
|
||||
Timezone string
|
||||
IsAlertContext bool
|
||||
Encoding string
|
||||
}
|
||||
|
||||
var ErrTimeout = errors.New("Timeout error. You can set timeout in seconds with &timeout url parameter")
|
||||
var rendererLog log.Logger = log.New("png-renderer")
|
||||
|
||||
func isoTimeOffsetToPosixTz(isoOffset string) string {
|
||||
// invert offset
|
||||
if strings.HasPrefix(isoOffset, "UTC+") {
|
||||
return strings.Replace(isoOffset, "UTC+", "UTC-", 1)
|
||||
}
|
||||
if strings.HasPrefix(isoOffset, "UTC-") {
|
||||
return strings.Replace(isoOffset, "UTC-", "UTC+", 1)
|
||||
}
|
||||
return isoOffset
|
||||
}
|
||||
|
||||
func appendEnviron(baseEnviron []string, name string, value string) []string {
|
||||
results := make([]string, 0)
|
||||
prefix := fmt.Sprintf("%s=", name)
|
||||
for _, v := range baseEnviron {
|
||||
if !strings.HasPrefix(v, prefix) {
|
||||
results = append(results, v)
|
||||
}
|
||||
}
|
||||
return append(results, fmt.Sprintf("%s=%s", name, value))
|
||||
}
|
||||
|
||||
func RenderToPng(params *RenderOpts) (string, error) {
|
||||
rendererLog.Info("Rendering", "path", params.Path)
|
||||
|
||||
var executable = "phantomjs"
|
||||
if runtime.GOOS == "windows" {
|
||||
executable = executable + ".exe"
|
||||
}
|
||||
|
||||
localDomain := "localhost"
|
||||
if setting.HttpAddr != setting.DEFAULT_HTTP_ADDR {
|
||||
localDomain = setting.HttpAddr
|
||||
}
|
||||
|
||||
// &render=1 signals to the legacy redirect layer to
|
||||
// avoid redirect these requests.
|
||||
url := fmt.Sprintf("%s://%s:%s/%s&render=1", setting.Protocol, localDomain, setting.HttpPort, params.Path)
|
||||
|
||||
binPath, _ := filepath.Abs(filepath.Join(setting.PhantomDir, executable))
|
||||
scriptPath, _ := filepath.Abs(filepath.Join(setting.PhantomDir, "render.js"))
|
||||
pngPath, _ := filepath.Abs(filepath.Join(setting.ImagesDir, util.GetRandomString(20)))
|
||||
pngPath = pngPath + ".png"
|
||||
|
||||
orgRole := params.OrgRole
|
||||
if params.IsAlertContext {
|
||||
orgRole = models.ROLE_ADMIN
|
||||
}
|
||||
renderKey := middleware.AddRenderAuthKey(params.OrgId, params.UserId, orgRole)
|
||||
defer middleware.RemoveRenderAuthKey(renderKey)
|
||||
|
||||
timeout, err := strconv.Atoi(params.Timeout)
|
||||
if err != nil {
|
||||
timeout = 15
|
||||
}
|
||||
|
||||
phantomDebugArg := "--debug=false"
|
||||
if log.GetLogLevelFor("png-renderer") >= log.LvlDebug {
|
||||
phantomDebugArg = "--debug=true"
|
||||
}
|
||||
|
||||
cmdArgs := []string{
|
||||
"--ignore-ssl-errors=true",
|
||||
"--web-security=false",
|
||||
phantomDebugArg,
|
||||
scriptPath,
|
||||
"url=" + url,
|
||||
"width=" + params.Width,
|
||||
"height=" + params.Height,
|
||||
"png=" + pngPath,
|
||||
"domain=" + localDomain,
|
||||
"timeout=" + strconv.Itoa(timeout),
|
||||
"renderKey=" + renderKey,
|
||||
}
|
||||
|
||||
if params.Encoding != "" {
|
||||
cmdArgs = append([]string{fmt.Sprintf("--output-encoding=%s", params.Encoding)}, cmdArgs...)
|
||||
}
|
||||
|
||||
cmd := exec.Command(binPath, cmdArgs...)
|
||||
output, err := cmd.StdoutPipe()
|
||||
|
||||
if err != nil {
|
||||
rendererLog.Error("Could not acquire stdout pipe", err)
|
||||
return "", err
|
||||
}
|
||||
cmd.Stderr = cmd.Stdout
|
||||
|
||||
if params.Timezone != "" {
|
||||
baseEnviron := os.Environ()
|
||||
cmd.Env = appendEnviron(baseEnviron, "TZ", isoTimeOffsetToPosixTz(params.Timezone))
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
rendererLog.Error("Could not start command", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
logWriter := log.NewLogWriter(rendererLog, log.LvlDebug, "[phantom] ")
|
||||
go io.Copy(logWriter, output)
|
||||
|
||||
done := make(chan error)
|
||||
go func() {
|
||||
if err := cmd.Wait(); err != nil {
|
||||
rendererLog.Error("failed to render an image", "error", err)
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-time.After(time.Duration(timeout) * time.Second):
|
||||
if err := cmd.Process.Kill(); err != nil {
|
||||
rendererLog.Error("failed to kill", "error", err)
|
||||
}
|
||||
return "", ErrTimeout
|
||||
case <-done:
|
||||
}
|
||||
|
||||
rendererLog.Debug("Image rendered", "path", pngPath)
|
||||
return pngPath, nil
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package renderer
|
||||
|
||||
//
|
||||
// import (
|
||||
// "io/ioutil"
|
||||
// "os"
|
||||
// "testing"
|
||||
//
|
||||
// . "github.com/smartystreets/goconvey/convey"
|
||||
// )
|
||||
//
|
||||
// func TestPhantomRender(t *testing.T) {
|
||||
//
|
||||
// Convey("Can render url", t, func() {
|
||||
// tempDir, _ := ioutil.TempDir("", "img")
|
||||
// ipng, err := RenderToPng("http://www.google.com")
|
||||
// So(err, ShouldBeNil)
|
||||
// So(exists(png), ShouldEqual, true)
|
||||
//
|
||||
// //_, err = os.Stat(store.getFilePathForDashboard("hello"))
|
||||
// //So(err, ShouldBeNil)
|
||||
// })
|
||||
//
|
||||
// }
|
||||
//
|
||||
// func exists(path string) bool {
|
||||
// _, err := os.Stat(path)
|
||||
// if err == nil {
|
||||
// return true
|
||||
// }
|
||||
// if os.IsNotExist(err) {
|
||||
// return false
|
||||
// }
|
||||
// return false
|
||||
// }
|
||||
Reference in New Issue
Block a user