2017-12-15 04:10:17 +00:00
package filestore
import (
"context"
2020-01-16 21:09:56 +00:00
"errors"
2017-12-15 04:10:17 +00:00
"io/ioutil"
"os"
"path"
"github.com/influxdata/chronograf"
)
// DashExt is the the file extension searched for in the directory for dashboard files
const DashExt = ".dashboard"
2020-01-16 21:09:56 +00:00
// Verify dashboards implements dashboardsStore interface.
var _ chronograf . DashboardsStore = ( * Dashboards ) ( nil )
2017-12-15 04:10:17 +00:00
// Dashboards are JSON dashboards stored in the filesystem
type Dashboards struct {
2017-12-15 19:36:14 +00:00
Dir string // Dir is the directory containing the dashboards.
ReadDir func ( dirname string ) ( [ ] os . FileInfo , error ) // ReadDir reads the directory named by dirname and returns a list of directory entries sorted by filename.
Remove func ( name string ) error // Remove file
Logger chronograf . Logger
2017-12-15 04:10:17 +00:00
}
// NewDashboards constructs a dashboard store wrapping a file system directory
2020-02-05 23:55:18 +00:00
func NewDashboards ( dir string , logger chronograf . Logger ) chronograf . DashboardsStore {
2017-12-15 04:10:17 +00:00
return & Dashboards {
2017-12-15 19:36:14 +00:00
Dir : dir ,
ReadDir : ioutil . ReadDir ,
Remove : os . Remove ,
Logger : logger ,
2017-12-15 04:10:17 +00:00
}
}
// All returns all dashboards from the directory
func ( d * Dashboards ) All ( ctx context . Context ) ( [ ] chronograf . Dashboard , error ) {
files , err := d . ReadDir ( d . Dir )
if err != nil {
return nil , err
}
dashboards := [ ] chronograf . Dashboard { }
for _ , file := range files {
if path . Ext ( file . Name ( ) ) != DashExt {
continue
}
2017-12-15 19:36:14 +00:00
var dashboard chronograf . Dashboard
2021-05-26 10:30:39 +00:00
file := path . Join ( d . Dir , file . Name ( ) )
if err := load ( file , & dashboard ) ; err != nil {
d . Logger . WithField ( "file" , file ) . Error ( "Dashboard file skipped, loading error: " , err )
2017-12-15 04:10:17 +00:00
continue // We want to load all files we can.
} else {
2021-05-26 10:30:39 +00:00
if dashboard . Name == "" {
// https://github.com/influxdata/chronograf/issues/5756
// exported DASHBOARD is wrapped, it looks like: {meta:{}, dashboard:DASHBOARD}
var exportedDashboard struct {
Dashboard chronograf . Dashboard ` json:"dashboard" `
}
if err := load ( file , & exportedDashboard ) ; err != nil {
d . Logger . WithField ( "file" , file ) . Error ( "Dashboard file skipped, loading error: " , err )
continue // We want to load all files we can.
}
dashboard = exportedDashboard . Dashboard
}
2017-12-15 04:10:17 +00:00
dashboards = append ( dashboards , dashboard )
}
}
return dashboards , nil
}
// Get returns a dashboard file from the dashboard directory
func ( d * Dashboards ) Get ( ctx context . Context , id chronograf . DashboardID ) ( chronograf . Dashboard , error ) {
board , file , err := d . idToFile ( id )
if err != nil {
if err == chronograf . ErrDashboardNotFound {
d . Logger .
WithField ( "component" , "dashboard" ) .
WithField ( "name" , file ) .
Error ( "Unable to read file" )
} else if err == chronograf . ErrDashboardInvalid {
d . Logger .
WithField ( "component" , "dashboard" ) .
WithField ( "name" , file ) .
Error ( "File is not a dashboard" )
}
return chronograf . Dashboard { } , err
}
return board , nil
}
// idToFile takes an id and finds the associated filename
func ( d * Dashboards ) idToFile ( id chronograf . DashboardID ) ( chronograf . Dashboard , string , error ) {
// Because the entire dashboard information is not known at this point, we need
// to try to find the name of the file through matching the ID in the dashboard
// content with the ID passed.
files , err := d . ReadDir ( d . Dir )
if err != nil {
return chronograf . Dashboard { } , "" , err
}
for _ , f := range files {
if path . Ext ( f . Name ( ) ) != DashExt {
continue
}
file := path . Join ( d . Dir , f . Name ( ) )
2017-12-15 19:36:14 +00:00
var dashboard chronograf . Dashboard
2020-01-16 21:09:56 +00:00
if err := load ( file , & dashboard ) ; err != nil {
2017-12-15 04:10:17 +00:00
return chronograf . Dashboard { } , "" , err
}
if dashboard . ID == id {
return dashboard , file , nil
}
}
return chronograf . Dashboard { } , "" , chronograf . ErrDashboardNotFound
}
2020-01-16 21:09:56 +00:00
// Update replaces a dashboard from the file system directory
func ( d * Dashboards ) Update ( ctx context . Context , dashboard chronograf . Dashboard ) error {
board , _ , err := d . idToFile ( dashboard . ID )
if err != nil {
return err
}
if err := d . Delete ( ctx , board ) ; err != nil {
return err
}
file := file ( d . Dir , dashboard . Name , DashExt )
return create ( file , dashboard )
}
// Delete removes a dashboard file from the directory
func ( d * Dashboards ) Delete ( ctx context . Context , dashboard chronograf . Dashboard ) error {
_ , file , err := d . idToFile ( dashboard . ID )
if err != nil {
return err
}
if err := d . Remove ( file ) ; err != nil {
d . Logger .
WithField ( "component" , "dashboard" ) .
WithField ( "name" , file ) .
Error ( "Unable to remove dashboard:" , err )
return err
}
return nil
}
// Add creates a new dashboard within the directory
func ( d * Dashboards ) Add ( ctx context . Context , dashboard chronograf . Dashboard ) ( chronograf . Dashboard , error ) {
return chronograf . Dashboard { } , errors . New ( "adding a dashboard to a filestore is not supported" )
}