From 99e99922b4476fcd0d85371bc566a6c644a23fe1 Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 10 Aug 2016 17:27:39 +0200 Subject: [PATCH] feat(alerting): add support for uploading images to webdav. closes #5770 --- conf/defaults.ini | 13 ++- conf/sample.ini | 16 ++++ pkg/components/imguploader/imguploader.go | 90 +++++++++---------- .../imguploader/imguploader_test.go | 53 +++++++++++ pkg/components/imguploader/s3uploader.go | 53 +++++++++++ pkg/components/imguploader/webdavuploader.go | 51 +++++++++++ .../imguploader/webdavuploader_test.go | 18 ++++ pkg/services/alerting/notifier.go | 7 +- pkg/setting/setting.go | 8 +- 9 files changed, 253 insertions(+), 56 deletions(-) create mode 100644 pkg/components/imguploader/imguploader_test.go create mode 100644 pkg/components/imguploader/s3uploader.go create mode 100644 pkg/components/imguploader/webdavuploader.go create mode 100644 pkg/components/imguploader/webdavuploader_test.go diff --git a/conf/defaults.ini b/conf/defaults.ini index 11c9ca667c4..e1619b57006 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -383,8 +383,17 @@ interval_seconds = 60 [grafana_net] url = https://grafana.net -#################################### S3 Temp Store ########################## -[s3-temp-image-store] +#################################### External image storage ########################## +[external_image_storage] +# You can choose between (s3, webdav or internal) +provider = s3 + +[external_image_storage.s3] bucket_url = access_key = secret_key = + +[external_image_storage.webdav] +url = +username = +password = diff --git a/conf/sample.ini b/conf/sample.ini index 80ca033f035..ee333be85c0 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -312,3 +312,19 @@ enabled = true # Url used to to import dashboards directly from Grafana.net [grafana_net] url = https://grafana.net + +#################################### External image storage ########################## +[external_image_storage] +# Used for uploading images to public servers so they can be included in slack/email messages. +# you can choose between (s3, webdav or internal) +provider = s3 + +[external_image_storage.s3] +bucket_url = +access_key = +secret_key = + +[external_image_storage.webdav] +url = +username = +password = diff --git a/pkg/components/imguploader/imguploader.go b/pkg/components/imguploader/imguploader.go index 9d3a571d9ad..5b383e69f34 100644 --- a/pkg/components/imguploader/imguploader.go +++ b/pkg/components/imguploader/imguploader.go @@ -1,57 +1,57 @@ package imguploader import ( - "io/ioutil" - "net/http" + "fmt" - "github.com/grafana/grafana/pkg/util" - "github.com/kr/s3/s3util" + "github.com/grafana/grafana/pkg/setting" ) -type Uploader interface { +type ImageUploader interface { Upload(path string) (string, error) } -type S3Uploader struct { - bucket string - secretKey string - accessKey string -} - -func NewS3Uploader(bucket, accessKey, secretKey string) *S3Uploader { - return &S3Uploader{ - bucket: bucket, - accessKey: accessKey, - secretKey: secretKey, +func NewImageUploader() (ImageUploader, error) { + + switch setting.ImageUploadProvider { + case "s3": + s3sec, err := setting.Cfg.GetSection("external_image_storage.s3") + if err != nil { + return nil, err + } + + bucket := s3sec.Key("secret_key").String() + accessKey := s3sec.Key("access_key").String() + secretKey := s3sec.Key("secret_key").String() + + if bucket == "" { + return nil, fmt.Errorf("Could not find bucket setting for image.uploader.s3") + } + + if accessKey == "" { + return nil, fmt.Errorf("Could not find accessKey setting for image.uploader.s3") + } + + if secretKey == "" { + return nil, fmt.Errorf("Could not find secretKey setting for image.uploader.s3") + } + + return NewS3Uploader(bucket, accessKey, secretKey), nil + case "webdav": + webdavSec, err := setting.Cfg.GetSection("external_image_storage.webdav") + if err != nil { + return nil, err + } + + url := webdavSec.Key("url").String() + if url == "" { + return nil, fmt.Errorf("Could not find url key for image.uploader.webdav") + } + + username := webdavSec.Key("username").String() + password := webdavSec.Key("password").String() + + return NewWebdavImageUploader(url, username, password) } -} -func (u *S3Uploader) Upload(path string) (string, error) { - - s3util.DefaultConfig.AccessKey = u.accessKey - s3util.DefaultConfig.SecretKey = u.secretKey - - header := make(http.Header) - header.Add("x-amz-acl", "public-read") - header.Add("Content-Type", "image/png") - - fullUrl := u.bucket + util.GetRandomString(20) + ".png" - writer, err := s3util.Create(fullUrl, header, nil) - if err != nil { - return "", err - } - - defer writer.Close() - - imgData, err := ioutil.ReadFile(path) - if err != nil { - return "", err - } - - _, err = writer.Write(imgData) - if err != nil { - return "", err - } - - return fullUrl, nil + return nil, fmt.Errorf("could not find specified provider") } diff --git a/pkg/components/imguploader/imguploader_test.go b/pkg/components/imguploader/imguploader_test.go new file mode 100644 index 00000000000..d12464dae69 --- /dev/null +++ b/pkg/components/imguploader/imguploader_test.go @@ -0,0 +1,53 @@ +package imguploader + +import ( + "reflect" + "testing" + + "github.com/grafana/grafana/pkg/setting" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestImageUploaderFactory(t *testing.T) { + Convey("Can create image uploader for ", t, func() { + Convey("S3ImageUploader", func() { + var err error + setting.NewConfigContext(&setting.CommandLineArgs{ + HomePath: "../../../", + }) + + setting.ImageUploadProvider = "s3" + + s3sec, err := setting.Cfg.GetSection("external_image_storage.s3") + s3sec.NewKey("bucket_url", "bucket_url") + s3sec.NewKey("access_key", "access_key") + s3sec.NewKey("secret_key", "secret_key") + + uploader, err := NewImageUploader() + + So(err, ShouldBeNil) + So(reflect.TypeOf(uploader), ShouldEqual, reflect.TypeOf(&S3Uploader{})) + }) + + Convey("Webdav uploader", func() { + var err error + + setting.NewConfigContext(&setting.CommandLineArgs{ + HomePath: "../../../", + }) + + setting.ImageUploadProvider = "webdav" + + webdavSec, err := setting.Cfg.GetSection("external_image_storage.webdav") + webdavSec.NewKey("url", "webdavUrl") + webdavSec.NewKey("username", "username") + webdavSec.NewKey("password", "password") + + uploader, err := NewImageUploader() + + So(err, ShouldBeNil) + So(reflect.TypeOf(uploader), ShouldEqual, reflect.TypeOf(&WebdavUploader{})) + }) + }) +} diff --git a/pkg/components/imguploader/s3uploader.go b/pkg/components/imguploader/s3uploader.go new file mode 100644 index 00000000000..af995d566c1 --- /dev/null +++ b/pkg/components/imguploader/s3uploader.go @@ -0,0 +1,53 @@ +package imguploader + +import ( + "io/ioutil" + "net/http" + + "github.com/grafana/grafana/pkg/util" + "github.com/kr/s3/s3util" +) + +type S3Uploader struct { + bucket string + secretKey string + accessKey string +} + +func NewS3Uploader(bucket, accessKey, secretKey string) *S3Uploader { + return &S3Uploader{ + bucket: bucket, + accessKey: accessKey, + secretKey: secretKey, + } +} + +func (u *S3Uploader) Upload(path string) (string, error) { + + s3util.DefaultConfig.AccessKey = u.accessKey + s3util.DefaultConfig.SecretKey = u.secretKey + + header := make(http.Header) + header.Add("x-amz-acl", "public-read") + header.Add("Content-Type", "image/png") + + fullUrl := u.bucket + util.GetRandomString(20) + ".png" + writer, err := s3util.Create(fullUrl, header, nil) + if err != nil { + return "", err + } + + defer writer.Close() + + imgData, err := ioutil.ReadFile(path) + if err != nil { + return "", err + } + + _, err = writer.Write(imgData) + if err != nil { + return "", err + } + + return fullUrl, nil +} diff --git a/pkg/components/imguploader/webdavuploader.go b/pkg/components/imguploader/webdavuploader.go new file mode 100644 index 00000000000..74444b1b123 --- /dev/null +++ b/pkg/components/imguploader/webdavuploader.go @@ -0,0 +1,51 @@ +package imguploader + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "path" + "time" + + "github.com/grafana/grafana/pkg/log" + "github.com/grafana/grafana/pkg/util" +) + +type WebdavUploader struct { + url string + username string + password string +} + +func (u *WebdavUploader) Upload(pa string) (string, error) { + log.Error2("Hej") + client := http.Client{Timeout: time.Duration(10 * time.Second)} + + url, _ := url.Parse(u.url) + url.Path = path.Join(url.Path, util.GetRandomString(20)+".png") + + imgData, err := ioutil.ReadFile(pa) + req, err := http.NewRequest("PUT", url.String(), bytes.NewReader(imgData)) + res, err := client.Do(req) + + if err != nil { + return "", err + } + + if res.StatusCode != http.StatusCreated { + body, _ := ioutil.ReadAll(res.Body) + return "", fmt.Errorf("Failed to upload image. Returned statuscode %v body %s", res.StatusCode, body) + } + + return url.String(), nil +} + +func NewWebdavImageUploader(url, username, passwrod string) (*WebdavUploader, error) { + return &WebdavUploader{ + url: url, + username: username, + password: passwrod, + }, nil +} diff --git a/pkg/components/imguploader/webdavuploader_test.go b/pkg/components/imguploader/webdavuploader_test.go new file mode 100644 index 00000000000..273cd1c2a86 --- /dev/null +++ b/pkg/components/imguploader/webdavuploader_test.go @@ -0,0 +1,18 @@ +package imguploader + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestUploadToWebdav(t *testing.T) { + webdavUploader, _ := NewWebdavImageUploader("http://localhost:9998/dav/", "username", "password") + + SkipConvey("[Integration test] for external_image_store.webdav", t, func() { + path, err := webdavUploader.Upload("../../../public/img/logo_transparent_400x.png") + + So(err, ShouldBeNil) + So(path, ShouldNotEqual, "") + }) +} diff --git a/pkg/services/alerting/notifier.go b/pkg/services/alerting/notifier.go index 28a3a331dd0..c345594de2c 100644 --- a/pkg/services/alerting/notifier.go +++ b/pkg/services/alerting/notifier.go @@ -8,7 +8,6 @@ import ( "github.com/grafana/grafana/pkg/components/renderer" "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/setting" ) type RootNotifier struct { @@ -54,10 +53,8 @@ func (n *RootNotifier) Notify(context *EvalContext) { } func (n *RootNotifier) uploadImage(context *EvalContext) error { - uploader := imguploader.NewS3Uploader( - setting.S3TempImageStoreBucketUrl, - setting.S3TempImageStoreAccessKey, - setting.S3TempImageStoreSecretKey) + + uploader, _ := imguploader.NewImageUploader() imageUrl, err := context.GetImageUrl() if err != nil { diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index e1c8cdf1c7d..2dda02cfa6c 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -153,6 +153,8 @@ var ( S3TempImageStoreBucketUrl string S3TempImageStoreAccessKey string S3TempImageStoreSecretKey string + + ImageUploadProvider string ) type CommandLineArgs struct { @@ -539,10 +541,8 @@ func NewConfigContext(args *CommandLineArgs) error { GrafanaNetUrl = Cfg.Section("grafana.net").Key("url").MustString("https://grafana.net") - s3temp := Cfg.Section("s3-temp-image-store") - S3TempImageStoreBucketUrl = s3temp.Key("bucket_url").String() - S3TempImageStoreAccessKey = s3temp.Key("access_key").String() - S3TempImageStoreSecretKey = s3temp.Key("secret_key").String() + imageUploadingSection := Cfg.Section("external_image_storage") + ImageUploadProvider = imageUploadingSection.Key("provider").MustString("internal") return nil }