renderer.go 3.6 KB

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