2021-12-23 11:43:53 -06:00
package thumbs
import (
2022-02-10 12:45:00 -06:00
"context"
2022-02-09 03:23:32 -06:00
"errors"
2021-12-23 11:43:53 -06:00
"fmt"
"io"
2022-04-15 07:01:58 -05:00
"net/http"
2022-02-10 12:45:00 -06:00
"time"
2021-12-23 11:43:53 -06:00
2022-07-28 07:40:26 -05:00
"github.com/grafana/grafana/pkg/services/datasources/permissions"
"github.com/grafana/grafana/pkg/services/searchV2"
2022-05-17 13:52:22 -05:00
"github.com/segmentio/encoding/json"
2021-12-23 11:43:53 -06:00
"github.com/grafana/grafana/pkg/api/response"
2022-02-09 03:23:32 -06:00
"github.com/grafana/grafana/pkg/infra/log"
2022-02-10 12:45:00 -06:00
"github.com/grafana/grafana/pkg/infra/serverlock"
2021-12-23 11:43:53 -06:00
"github.com/grafana/grafana/pkg/models"
2022-04-28 04:06:49 -05:00
"github.com/grafana/grafana/pkg/registry"
2022-05-17 13:52:22 -05:00
"github.com/grafana/grafana/pkg/services/dashboards"
2022-01-26 11:44:20 -06:00
"github.com/grafana/grafana/pkg/services/featuremgmt"
2021-12-23 11:43:53 -06:00
"github.com/grafana/grafana/pkg/services/guardian"
2022-01-10 10:21:35 -06:00
"github.com/grafana/grafana/pkg/services/live"
2021-12-23 11:43:53 -06:00
"github.com/grafana/grafana/pkg/services/rendering"
2022-02-09 03:23:32 -06:00
"github.com/grafana/grafana/pkg/services/sqlstore"
2021-12-23 11:43:53 -06:00
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web"
)
type Service interface {
2022-04-28 04:06:49 -05:00
registry . ProvidesUsageStats
2022-02-10 12:45:00 -06:00
Run ( ctx context . Context ) error
2021-12-23 11:43:53 -06:00
Enabled ( ) bool
GetImage ( c * models . ReqContext )
2022-02-16 11:49:50 -06:00
GetDashboardPreviewsSetupSettings ( c * models . ReqContext ) dashboardPreviewsSetupConfig
2022-01-10 10:21:35 -06:00
2022-02-09 03:23:32 -06:00
// from dashboard page
SetImage ( c * models . ReqContext ) // form post
UpdateThumbnailState ( c * models . ReqContext )
2021-12-23 11:43:53 -06:00
// Must be admin
StartCrawler ( c * models . ReqContext ) response . Response
StopCrawler ( c * models . ReqContext ) response . Response
2022-01-10 10:21:35 -06:00
CrawlerStatus ( c * models . ReqContext ) response . Response
2021-12-23 11:43:53 -06:00
}
2022-02-10 12:45:00 -06:00
type thumbService struct {
scheduleOptions crawlerScheduleOptions
renderer dashRenderer
2022-02-16 11:49:50 -06:00
renderingService rendering . Service
2022-02-10 12:45:00 -06:00
thumbnailRepo thumbnailRepo
lockService * serverlock . ServerLockService
features featuremgmt . FeatureToggles
2022-03-21 08:15:31 -05:00
store sqlstore . Store
2022-02-10 12:45:00 -06:00
crawlLockServiceActionName string
log log . Logger
2022-04-24 16:55:10 -05:00
canRunCrawler bool
settings setting . DashboardPreviewsSettings
2022-05-17 13:52:22 -05:00
dashboardService dashboards . DashboardService
2022-07-28 07:40:26 -05:00
dsUidsLookup getDatasourceUidsForDashboard
dsPermissionsService permissions . DatasourcePermissionsService
licensing models . Licensing
searchService searchV2 . SearchService
2022-02-10 12:45:00 -06:00
}
type crawlerScheduleOptions struct {
crawlInterval time . Duration
tickerInterval time . Duration
maxCrawlDuration time . Duration
crawlerMode CrawlerMode
thumbnailKind models . ThumbnailKind
themes [ ] models . Theme
2022-04-12 12:34:04 -05:00
auth CrawlerAuth
2022-02-10 12:45:00 -06:00
}
2022-05-17 13:52:22 -05:00
func ProvideService ( cfg * setting . Cfg , features featuremgmt . FeatureToggles ,
lockService * serverlock . ServerLockService , renderService rendering . Service ,
gl * live . GrafanaLive , store * sqlstore . SQLStore , authSetupService CrawlerAuthSetupService ,
2022-07-28 07:40:26 -05:00
dashboardService dashboards . DashboardService , searchService searchV2 . SearchService ,
dsPermissionsService permissions . DatasourcePermissionsService , licensing models . Licensing ) Service {
2022-01-26 11:44:20 -06:00
if ! features . IsEnabled ( featuremgmt . FlagDashboardPreviews ) {
2021-12-23 11:43:53 -06:00
return & dummyService { }
}
2022-04-24 16:55:10 -05:00
logger := log . New ( "previews_service" )
2021-12-23 11:43:53 -06:00
2022-07-28 07:40:26 -05:00
thumbnailRepo := newThumbnailRepo ( store , searchService )
2022-02-10 12:45:00 -06:00
2022-04-24 16:55:10 -05:00
canRunCrawler := true
2022-05-16 20:09:46 -05:00
authSetupStarted := time . Now ( )
2022-04-12 12:34:04 -05:00
crawlerAuth , err := authSetupService . Setup ( context . Background ( ) )
2022-05-16 20:09:46 -05:00
2022-04-12 12:34:04 -05:00
if err != nil {
2022-05-16 20:09:46 -05:00
logger . Error ( "Crawler auth setup failed" , "err" , err , "crawlerAuthSetupTime" , time . Since ( authSetupStarted ) )
2022-04-24 16:55:10 -05:00
canRunCrawler = false
2022-05-16 20:09:46 -05:00
} else {
logger . Info ( "Crawler auth setup complete" , "crawlerAuthSetupTime" , time . Since ( authSetupStarted ) )
2022-02-10 12:45:00 -06:00
}
2022-05-16 20:09:46 -05:00
2022-07-28 07:40:26 -05:00
dsUidsLookup := & dsUidsLookup {
searchService : searchService ,
crawlerAuth : crawlerAuth ,
features : features ,
}
2022-04-13 04:54:49 -05:00
t := & thumbService {
2022-07-28 07:40:26 -05:00
licensing : licensing ,
2022-02-16 11:49:50 -06:00
renderingService : renderService ,
2022-07-28 07:40:26 -05:00
renderer : newSimpleCrawler ( renderService , gl , thumbnailRepo , cfg , cfg . DashboardPreviews , dsUidsLookup . getDatasourceUidsForDashboard ) ,
2022-02-10 12:45:00 -06:00
thumbnailRepo : thumbnailRepo ,
2022-03-21 08:15:31 -05:00
store : store ,
2022-02-10 12:45:00 -06:00
features : features ,
lockService : lockService ,
crawlLockServiceActionName : "dashboard-crawler" ,
2022-07-28 07:40:26 -05:00
searchService : searchService ,
2022-04-12 12:34:04 -05:00
log : logger ,
2022-04-24 16:55:10 -05:00
canRunCrawler : canRunCrawler ,
2022-07-28 07:40:26 -05:00
dsUidsLookup : dsUidsLookup . getDatasourceUidsForDashboard ,
2022-04-24 16:55:10 -05:00
settings : cfg . DashboardPreviews ,
2022-07-28 07:40:26 -05:00
dsPermissionsService : dsPermissionsService ,
2022-02-10 12:45:00 -06:00
scheduleOptions : crawlerScheduleOptions {
2022-04-24 16:55:10 -05:00
tickerInterval : 5 * time . Minute ,
crawlInterval : cfg . DashboardPreviews . SchedulerInterval ,
maxCrawlDuration : cfg . DashboardPreviews . MaxCrawlDuration ,
2022-02-10 12:45:00 -06:00
crawlerMode : CrawlerModeThumbs ,
thumbnailKind : models . ThumbnailKindDefault ,
themes : [ ] models . Theme { models . ThemeDark , models . ThemeLight } ,
2022-04-12 12:34:04 -05:00
auth : crawlerAuth ,
2022-02-10 12:45:00 -06:00
} ,
2022-05-17 13:52:22 -05:00
dashboardService : dashboardService ,
2021-12-23 11:43:53 -06:00
}
2022-04-13 04:54:49 -05:00
return t
}
2022-04-28 04:06:49 -05:00
func ( hs * thumbService ) GetUsageStats ( ctx context . Context ) map [ string ] interface { } {
s := hs . getDashboardPreviewsSetupSettings ( ctx )
2022-04-13 04:54:49 -05:00
2022-04-28 04:06:49 -05:00
stats := make ( map [ string ] interface { } )
2022-04-13 04:54:49 -05:00
2022-04-28 04:06:49 -05:00
if s . SystemRequirements . Met {
stats [ "stats.dashboard_previews.system_req_met.count" ] = 1
}
2022-04-13 04:54:49 -05:00
2022-04-28 04:06:49 -05:00
if s . ThumbnailsExist {
stats [ "stats.dashboard_previews.thumbnails_exist.count" ] = 1
}
2022-04-13 04:54:49 -05:00
2022-04-28 04:06:49 -05:00
return stats
2021-12-23 11:43:53 -06:00
}
func ( hs * thumbService ) Enabled ( ) bool {
2022-02-10 12:45:00 -06:00
return hs . features . IsEnabled ( featuremgmt . FlagDashboardPreviews )
2021-12-23 11:43:53 -06:00
}
func ( hs * thumbService ) parseImageReq ( c * models . ReqContext , checkSave bool ) * previewRequest {
params := web . Params ( c . Req )
2022-02-09 03:23:32 -06:00
kind , err := models . ParseThumbnailKind ( params [ ":kind" ] )
if err != nil {
2021-12-23 11:43:53 -06:00
c . JSON ( 400 , map [ string ] string { "error" : "invalid size" } )
return nil
}
2022-02-09 03:23:32 -06:00
theme , err := models . ParseTheme ( params [ ":theme" ] )
if err != nil {
2021-12-23 11:43:53 -06:00
c . JSON ( 400 , map [ string ] string { "error" : "invalid theme" } )
return nil
}
req := & previewRequest {
2022-08-11 06:28:55 -05:00
OrgID : c . OrgID ,
2021-12-23 11:43:53 -06:00
UID : params [ ":uid" ] ,
Theme : theme ,
2022-02-09 03:23:32 -06:00
Kind : kind ,
2021-12-23 11:43:53 -06:00
}
if len ( req . UID ) < 1 {
c . JSON ( 400 , map [ string ] string { "error" : "missing UID" } )
return nil
}
// Check permissions and status
status := hs . getStatus ( c , req . UID , checkSave )
if status != 200 {
c . JSON ( status , map [ string ] string { "error" : fmt . Sprintf ( "code: %d" , status ) } )
return nil
}
return req
}
2022-02-09 03:23:32 -06:00
type updateThumbnailStateRequest struct {
State models . ThumbnailState ` json:"state" binding:"Required" `
}
func ( hs * thumbService ) UpdateThumbnailState ( c * models . ReqContext ) {
req := hs . parseImageReq ( c , false )
if req == nil {
return // already returned value
}
var body = & updateThumbnailStateRequest { }
err := web . Bind ( c . Req , body )
if err != nil {
2022-02-10 12:45:00 -06:00
hs . log . Error ( "Error parsing update thumbnail state request" , "dashboardUid" , req . UID , "err" , err . Error ( ) )
2022-02-09 03:23:32 -06:00
c . JSON ( 500 , map [ string ] string { "dashboardUID" : req . UID , "error" : "unknown" } )
return
}
err = hs . thumbnailRepo . updateThumbnailState ( c . Req . Context ( ) , body . State , models . DashboardThumbnailMeta {
DashboardUID : req . UID ,
OrgId : req . OrgID ,
Theme : req . Theme ,
Kind : models . ThumbnailKindDefault ,
} )
if err != nil {
2022-02-10 12:45:00 -06:00
hs . log . Error ( "Error when trying to update thumbnail state" , "dashboardUid" , req . UID , "err" , err . Error ( ) , "newState" , body . State )
2022-02-09 03:23:32 -06:00
c . JSON ( 500 , map [ string ] string { "dashboardUID" : req . UID , "error" : "unknown" } )
return
}
2022-02-10 12:45:00 -06:00
hs . log . Info ( "Updated dashboard thumbnail state" , "dashboardUid" , req . UID , "theme" , req . Theme , "newState" , body . State )
2022-04-15 07:01:58 -05:00
c . JSON ( http . StatusOK , map [ string ] string { "success" : "true" } )
2022-02-09 03:23:32 -06:00
}
2021-12-23 11:43:53 -06:00
func ( hs * thumbService ) GetImage ( c * models . ReqContext ) {
req := hs . parseImageReq ( c , false )
if req == nil {
return // already returned value
}
2022-02-09 03:23:32 -06:00
res , err := hs . thumbnailRepo . getThumbnail ( c . Req . Context ( ) , models . DashboardThumbnailMeta {
DashboardUID : req . UID ,
OrgId : req . OrgID ,
Theme : req . Theme ,
Kind : models . ThumbnailKindDefault ,
} )
2022-06-30 08:31:54 -05:00
if errors . Is ( err , dashboards . ErrDashboardThumbnailNotFound ) {
2022-02-09 03:23:32 -06:00
c . Resp . WriteHeader ( 404 )
return
2021-12-23 11:43:53 -06:00
}
2022-06-17 09:02:03 -05:00
if err != nil || res == nil {
2022-02-10 12:45:00 -06:00
hs . log . Error ( "Error when retrieving thumbnail" , "dashboardUid" , req . UID , "err" , err . Error ( ) )
2022-02-09 03:23:32 -06:00
c . JSON ( 500 , map [ string ] string { "dashboardUID" : req . UID , "error" : "unknown" } )
2021-12-23 11:43:53 -06:00
return
}
2022-07-28 07:40:26 -05:00
if ! hs . hasAccessToPreview ( c , res , req ) {
return
}
2022-06-17 09:02:03 -05:00
currentEtag := fmt . Sprintf ( "%d" , res . Updated . Unix ( ) )
c . Resp . Header ( ) . Set ( "ETag" , currentEtag )
previousEtag := c . Req . Header . Get ( "If-None-Match" )
if previousEtag == currentEtag {
c . Resp . WriteHeader ( http . StatusNotModified )
return
}
2022-02-09 03:23:32 -06:00
c . Resp . Header ( ) . Set ( "Content-Type" , res . MimeType )
if _ , err := c . Resp . Write ( res . Image ) ; err != nil {
2022-02-10 12:45:00 -06:00
hs . log . Error ( "Error writing to response" , "dashboardUid" , req . UID , "err" , err )
2022-02-09 03:23:32 -06:00
}
2021-12-23 11:43:53 -06:00
}
2022-07-28 07:40:26 -05:00
func ( hs * thumbService ) hasAccessToPreview ( c * models . ReqContext , res * models . DashboardThumbnail , req * previewRequest ) bool {
if ! hs . licensing . FeatureEnabled ( "accesscontrol.enforcement" ) {
return true
}
if hs . searchService . IsDisabled ( ) {
c . JSON ( 404 , map [ string ] string { "dashboardUID" : req . UID , "error" : "unknown" } )
return false
}
if res . DsUIDs == "" {
hs . log . Debug ( "dashboard preview is stale; no datasource uids" , "dashboardUid" , req . UID )
c . JSON ( 404 , map [ string ] string { "dashboardUID" : req . UID , "error" : "unknown" } )
return false
}
var dsUids [ ] string
err := json . Unmarshal ( [ ] byte ( res . DsUIDs ) , & dsUids )
if err != nil {
hs . log . Error ( "Error when retrieving datasource uids" , "dashboardUid" , req . UID , "err" , err )
c . JSON ( 404 , map [ string ] string { "dashboardUID" : req . UID , "error" : "unknown" } )
return false
}
accessibleDatasources , err := hs . dsPermissionsService . FilterDatasourceUidsBasedOnQueryPermissions ( c . Req . Context ( ) , c . SignedInUser , dsUids )
if err != nil && ! errors . Is ( err , permissions . ErrNotImplemented ) {
hs . log . Error ( "Error when filtering datasource uids" , "dashboardUid" , req . UID , "err" , err )
c . JSON ( 500 , map [ string ] string { "dashboardUID" : req . UID , "error" : "unknown" } )
return false
}
if ! errors . Is ( err , permissions . ErrNotImplemented ) {
canQueryAllDatasources := len ( accessibleDatasources ) == len ( dsUids )
if ! canQueryAllDatasources {
hs . log . Info ( "Denied access to dashboard preview" , "dashboardUid" , req . UID , "err" , err , "dashboardDatasources" , dsUids , "accessibleDatasources" , accessibleDatasources )
c . JSON ( 404 , map [ string ] string { "dashboardUID" : req . UID , "error" : "unknown" } )
return false
}
}
return true
}
2022-02-16 11:49:50 -06:00
func ( hs * thumbService ) GetDashboardPreviewsSetupSettings ( c * models . ReqContext ) dashboardPreviewsSetupConfig {
2022-04-13 04:54:49 -05:00
return hs . getDashboardPreviewsSetupSettings ( c . Req . Context ( ) )
}
func ( hs * thumbService ) getDashboardPreviewsSetupSettings ( ctx context . Context ) dashboardPreviewsSetupConfig {
2022-09-02 07:20:10 -05:00
systemRequirements := hs . getSystemRequirements ( ctx )
2022-04-13 04:54:49 -05:00
thumbnailsExist , err := hs . thumbnailRepo . doThumbnailsExist ( ctx )
2022-02-16 11:49:50 -06:00
if err != nil {
return dashboardPreviewsSetupConfig {
SystemRequirements : systemRequirements ,
ThumbnailsExist : false ,
}
}
return dashboardPreviewsSetupConfig {
SystemRequirements : systemRequirements ,
ThumbnailsExist : thumbnailsExist ,
}
}
2022-09-02 07:20:10 -05:00
func ( hs * thumbService ) getSystemRequirements ( ctx context . Context ) dashboardPreviewsSystemRequirements {
res , err := hs . renderingService . HasCapability ( ctx , rendering . ScalingDownImages )
2022-02-16 11:49:50 -06:00
if err != nil {
hs . log . Error ( "Error when verifying dashboard previews system requirements thumbnail" , "err" , err . Error ( ) )
return dashboardPreviewsSystemRequirements {
Met : false ,
}
}
if ! res . IsSupported {
return dashboardPreviewsSystemRequirements {
Met : false ,
RequiredImageRendererPluginVersion : res . SemverConstraint ,
}
}
return dashboardPreviewsSystemRequirements {
Met : true ,
}
}
2022-01-10 10:21:35 -06:00
// Hack for now -- lets you upload images explicitly
2021-12-23 11:43:53 -06:00
func ( hs * thumbService ) SetImage ( c * models . ReqContext ) {
req := hs . parseImageReq ( c , false )
if req == nil {
return // already returned value
}
r := c . Req
// Parse our multipart form, 10 << 20 specifies a maximum
// upload of 10 MB files.
err := r . ParseMultipartForm ( 10 << 20 )
if err != nil {
c . JSON ( 400 , map [ string ] string { "error" : "invalid upload size" } )
return
}
// FormFile returns the first file for the given key `myFile`
// it also returns the FileHeader so we can get the Filename,
// the Header and the size of the file
file , handler , err := r . FormFile ( "file" )
if err != nil {
c . JSON ( 400 , map [ string ] string { "error" : "missing multi-part form field named 'file'" } )
fmt . Println ( "error" , err )
return
}
defer func ( ) {
_ = file . Close ( )
} ( )
2022-02-10 12:45:00 -06:00
hs . log . Info ( "Uploaded File: %+v\n" , handler . Filename )
hs . log . Info ( "File Size: %+v\n" , handler . Size )
hs . log . Info ( "MIME Header: %+v\n" , handler . Header )
2021-12-23 11:43:53 -06:00
2022-08-10 08:37:51 -05:00
fileBytes , err := io . ReadAll ( file )
2021-12-23 11:43:53 -06:00
if err != nil {
fmt . Println ( err )
2022-02-09 03:23:32 -06:00
c . JSON ( 400 , map [ string ] string { "error" : "error reading file" } )
2021-12-23 11:43:53 -06:00
return
}
2022-07-28 07:40:26 -05:00
dsUids , err := hs . dsUidsLookup ( c . Req . Context ( ) , req . UID , req . OrgID )
if err != nil {
hs . log . Error ( "error looking up datasource ids" , "err" , err , "dashboardUid" , req . UID )
c . JSON ( 500 , map [ string ] string { "error" : "internal server error" } )
return
}
2022-02-09 03:23:32 -06:00
_ , err = hs . thumbnailRepo . saveFromBytes ( c . Req . Context ( ) , fileBytes , getMimeType ( handler . Filename ) , models . DashboardThumbnailMeta {
DashboardUID : req . UID ,
OrgId : req . OrgID ,
Theme : req . Theme ,
Kind : req . Kind ,
2022-07-28 07:40:26 -05:00
} , models . DashboardVersionForManualThumbnailUpload , dsUids )
2022-02-09 03:23:32 -06:00
2021-12-23 11:43:53 -06:00
if err != nil {
2022-02-09 03:23:32 -06:00
c . JSON ( 400 , map [ string ] string { "error" : "error saving thumbnail file" } )
fmt . Println ( "error" , err )
2021-12-23 11:43:53 -06:00
return
}
2022-04-15 07:01:58 -05:00
c . JSON ( http . StatusOK , map [ string ] int { "OK" : len ( fileBytes ) } )
2021-12-23 11:43:53 -06:00
}
func ( hs * thumbService ) StartCrawler ( c * models . ReqContext ) response . Response {
body , err := io . ReadAll ( c . Req . Body )
if err != nil {
return response . Error ( 500 , "error reading bytes" , err )
}
cmd := & crawlCmd { }
err = json . Unmarshal ( body , cmd )
if err != nil {
return response . Error ( 500 , "error parsing bytes" , err )
}
2022-01-10 10:21:35 -06:00
if cmd . Mode == "" {
cmd . Mode = CrawlerModeThumbs
}
2022-02-10 12:45:00 -06:00
go hs . runOnDemandCrawl ( context . Background ( ) , cmd . Theme , cmd . Mode , models . ThumbnailKindDefault , rendering . AuthOpts {
2022-08-11 06:28:55 -05:00
OrgID : c . OrgID ,
UserID : c . UserID ,
2022-02-10 12:45:00 -06:00
OrgRole : c . OrgRole ,
} )
status , err := hs . renderer . Status ( )
2021-12-23 11:43:53 -06:00
if err != nil {
return response . Error ( 500 , "error starting" , err )
}
2022-02-10 12:45:00 -06:00
2022-04-15 07:01:58 -05:00
return response . JSON ( http . StatusOK , status )
2021-12-23 11:43:53 -06:00
}
func ( hs * thumbService ) StopCrawler ( c * models . ReqContext ) response . Response {
2022-01-10 10:21:35 -06:00
msg , err := hs . renderer . Stop ( )
2021-12-23 11:43:53 -06:00
if err != nil {
2022-01-10 10:21:35 -06:00
return response . Error ( 500 , "error starting" , err )
2021-12-23 11:43:53 -06:00
}
2022-04-15 07:01:58 -05:00
return response . JSON ( http . StatusOK , msg )
2022-01-10 10:21:35 -06:00
}
2021-12-23 11:43:53 -06:00
2022-01-10 10:21:35 -06:00
func ( hs * thumbService ) CrawlerStatus ( c * models . ReqContext ) response . Response {
msg , err := hs . renderer . Status ( )
if err != nil {
return response . Error ( 500 , "error starting" , err )
}
2022-04-15 07:01:58 -05:00
return response . JSON ( http . StatusOK , msg )
2021-12-23 11:43:53 -06:00
}
// Ideally this service would not require first looking up the full dashboard just to bet the id!
func ( hs * thumbService ) getStatus ( c * models . ReqContext , uid string , checkSave bool ) int {
2022-02-09 03:23:32 -06:00
dashboardID , err := hs . getDashboardId ( c , uid )
if err != nil {
return 404
2021-12-23 11:43:53 -06:00
}
2022-08-11 06:28:55 -05:00
guardian := guardian . New ( c . Req . Context ( ) , dashboardID , c . OrgID , c . SignedInUser )
2021-12-23 11:43:53 -06:00
if checkSave {
if canSave , err := guardian . CanSave ( ) ; err != nil || ! canSave {
return 403 // forbidden
}
return 200
}
if canView , err := guardian . CanView ( ) ; err != nil || ! canView {
return 403 // forbidden
}
return 200 // found and OK
}
2022-02-09 03:23:32 -06:00
func ( hs * thumbService ) getDashboardId ( c * models . ReqContext , uid string ) ( int64 , error ) {
2022-08-11 06:28:55 -05:00
query := models . GetDashboardQuery { Uid : uid , OrgId : c . OrgID }
2022-02-09 03:23:32 -06:00
2022-05-17 13:52:22 -05:00
if err := hs . dashboardService . GetDashboard ( c . Req . Context ( ) , & query ) ; err != nil {
2022-02-09 03:23:32 -06:00
return 0 , err
}
return query . Result . Id , nil
}
2022-02-10 12:45:00 -06:00
func ( hs * thumbService ) runOnDemandCrawl ( parentCtx context . Context , theme models . Theme , mode CrawlerMode , kind models . ThumbnailKind , authOpts rendering . AuthOpts ) {
2022-04-24 16:55:10 -05:00
if ! hs . canRunCrawler {
return
}
2022-02-10 12:45:00 -06:00
crawlerCtx , cancel := context . WithTimeout ( parentCtx , hs . scheduleOptions . maxCrawlDuration )
defer cancel ( )
// wait for at least a minute after the last completed run
interval := time . Minute
err := hs . lockService . LockAndExecute ( crawlerCtx , hs . crawlLockServiceActionName , interval , func ( ctx context . Context ) {
2022-04-12 12:34:04 -05:00
if err := hs . renderer . Run ( crawlerCtx , hs . scheduleOptions . auth , mode , theme , kind ) ; err != nil {
2022-02-10 12:45:00 -06:00
hs . log . Error ( "On demand crawl error" , "mode" , mode , "theme" , theme , "kind" , kind , "userId" , authOpts . UserID , "orgId" , authOpts . OrgID , "orgRole" , authOpts . OrgRole )
}
} )
if err != nil {
hs . log . Error ( "On demand crawl lock error" , "err" , err )
}
}
func ( hs * thumbService ) runScheduledCrawl ( parentCtx context . Context ) {
crawlerCtx , cancel := context . WithTimeout ( parentCtx , hs . scheduleOptions . maxCrawlDuration )
defer cancel ( )
err := hs . lockService . LockAndExecute ( crawlerCtx , hs . crawlLockServiceActionName , hs . scheduleOptions . crawlInterval , func ( ctx context . Context ) {
for _ , theme := range hs . scheduleOptions . themes {
if err := hs . renderer . Run ( crawlerCtx , hs . scheduleOptions . auth , hs . scheduleOptions . crawlerMode , theme , hs . scheduleOptions . thumbnailKind ) ; err != nil {
hs . log . Error ( "Scheduled crawl error" , "theme" , theme , "kind" , hs . scheduleOptions . thumbnailKind , "err" , err )
}
}
} )
if err != nil {
hs . log . Error ( "Scheduled crawl lock error" , "err" , err )
}
}
func ( hs * thumbService ) Run ( ctx context . Context ) error {
2022-04-24 16:55:10 -05:00
if ! hs . canRunCrawler {
2022-02-10 12:45:00 -06:00
return nil
}
gc := time . NewTicker ( hs . scheduleOptions . tickerInterval )
for {
select {
case <- gc . C :
go hs . runScheduledCrawl ( ctx )
case <- ctx . Done ( ) :
hs . log . Debug ( "Grafana is shutting down - stopping dashboard crawler" )
gc . Stop ( )
return nil
}
}
}