renderer.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. package renderer
  2. import (
  3. "errors"
  4. "fmt"
  5. "io"
  6. "os"
  7. "os/exec"
  8. "path/filepath"
  9. "runtime"
  10. "time"
  11. "strconv"
  12. "strings"
  13. "github.com/grafana/grafana/pkg/log"
  14. "github.com/grafana/grafana/pkg/middleware"
  15. "github.com/grafana/grafana/pkg/models"
  16. "github.com/grafana/grafana/pkg/setting"
  17. "github.com/grafana/grafana/pkg/util"
  18. )
  19. type RenderOpts struct {
  20. Path string
  21. Width string
  22. Height string
  23. Timeout string
  24. OrgId int64
  25. UserId int64
  26. OrgRole models.RoleType
  27. Timezone string
  28. IsAlertContext bool
  29. }
  30. var ErrTimeout = errors.New("Timeout error. You can set timeout in seconds with &timeout url parameter")
  31. var rendererLog log.Logger = log.New("png-renderer")
  32. func isoTimeOffsetToPosixTz(isoOffset string) string {
  33. // invert offset
  34. if strings.HasPrefix(isoOffset, "UTC+") {
  35. return strings.Replace(isoOffset, "UTC+", "UTC-", 1)
  36. }
  37. if strings.HasPrefix(isoOffset, "UTC-") {
  38. return strings.Replace(isoOffset, "UTC-", "UTC+", 1)
  39. }
  40. return isoOffset
  41. }
  42. func appendEnviron(baseEnviron []string, name string, value string) []string {
  43. results := make([]string, 0)
  44. prefix := fmt.Sprintf("%s=", name)
  45. for _, v := range baseEnviron {
  46. if !strings.HasPrefix(v, prefix) {
  47. results = append(results, v)
  48. }
  49. }
  50. return append(results, fmt.Sprintf("%s=%s", name, value))
  51. }
  52. func RenderToPng(params *RenderOpts) (string, error) {
  53. rendererLog.Info("Rendering", "path", params.Path)
  54. var executable = "phantomjs"
  55. if runtime.GOOS == "windows" {
  56. executable = executable + ".exe"
  57. }
  58. localDomain := "localhost"
  59. if setting.HttpAddr != setting.DEFAULT_HTTP_ADDR {
  60. localDomain = setting.HttpAddr
  61. }
  62. url := fmt.Sprintf("%s://%s:%s/%s", setting.Protocol, localDomain, setting.HttpPort, params.Path)
  63. binPath, _ := filepath.Abs(filepath.Join(setting.PhantomDir, executable))
  64. scriptPath, _ := filepath.Abs(filepath.Join(setting.PhantomDir, "render.js"))
  65. pngPath, _ := filepath.Abs(filepath.Join(setting.ImagesDir, util.GetRandomString(20)))
  66. pngPath = pngPath + ".png"
  67. orgRole := params.OrgRole
  68. if params.IsAlertContext {
  69. orgRole = models.ROLE_ADMIN
  70. }
  71. renderKey := middleware.AddRenderAuthKey(params.OrgId, params.UserId, orgRole)
  72. defer middleware.RemoveRenderAuthKey(renderKey)
  73. timeout, err := strconv.Atoi(params.Timeout)
  74. if err != nil {
  75. timeout = 15
  76. }
  77. cmdArgs := []string{
  78. "--ignore-ssl-errors=true",
  79. "--web-security=false",
  80. scriptPath,
  81. "url=" + url,
  82. "width=" + params.Width,
  83. "height=" + params.Height,
  84. "png=" + pngPath,
  85. "domain=" + localDomain,
  86. "timeout=" + strconv.Itoa(timeout),
  87. "renderKey=" + renderKey,
  88. }
  89. cmd := exec.Command(binPath, cmdArgs...)
  90. stdout, err := cmd.StdoutPipe()
  91. if err != nil {
  92. return "", err
  93. }
  94. stderr, err := cmd.StderrPipe()
  95. if err != nil {
  96. return "", err
  97. }
  98. if params.Timezone != "" {
  99. baseEnviron := os.Environ()
  100. cmd.Env = appendEnviron(baseEnviron, "TZ", isoTimeOffsetToPosixTz(params.Timezone))
  101. }
  102. err = cmd.Start()
  103. if err != nil {
  104. return "", err
  105. }
  106. go io.Copy(os.Stdout, stdout)
  107. go io.Copy(os.Stdout, stderr)
  108. done := make(chan error)
  109. go func() {
  110. if err := cmd.Wait(); err != nil {
  111. rendererLog.Error("failed to render an image", "error", err)
  112. }
  113. close(done)
  114. }()
  115. select {
  116. case <-time.After(time.Duration(timeout) * time.Second):
  117. if err := cmd.Process.Kill(); err != nil {
  118. rendererLog.Error("failed to kill", "error", err)
  119. }
  120. return "", ErrTimeout
  121. case <-done:
  122. }
  123. rendererLog.Debug("Image rendered", "path", pngPath)
  124. return pngPath, nil
  125. }