2017-12-15 04:10:17 +00:00
package filestore
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path"
"strconv"
2019-01-08 00:37:16 +00:00
"github.com/influxdata/influxdb/chronograf"
2017-12-15 04:10:17 +00:00
)
// DashExt is the the file extension searched for in the directory for dashboard files
const DashExt = ".dashboard"
var _ chronograf . DashboardsStore = & Dashboards { }
// 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.
Load func ( string , interface { } ) error // Load loads string name and dashbaord passed in as interface
Create func ( string , interface { } ) error // Create will write dashboard to file.
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
IDs chronograf . ID // IDs generate unique ids for new dashboards
Logger chronograf . Logger
2017-12-15 04:10:17 +00:00
}
// NewDashboards constructs a dashboard store wrapping a file system directory
func NewDashboards ( dir string , ids chronograf . ID , logger chronograf . Logger ) chronograf . DashboardsStore {
return & Dashboards {
2017-12-15 19:36:14 +00:00
Dir : dir ,
Load : load ,
Create : create ,
ReadDir : ioutil . ReadDir ,
Remove : os . Remove ,
IDs : ids ,
Logger : logger ,
2017-12-15 04:10:17 +00:00
}
}
func dashboardFile ( dir string , dashboard chronograf . Dashboard ) string {
base := fmt . Sprintf ( "%s%s" , dashboard . Name , DashExt )
return path . Join ( dir , base )
}
2017-12-15 19:36:14 +00:00
func load ( name string , resource interface { } ) error {
2017-12-19 21:06:24 +00:00
octets , err := templatedFromEnv ( name )
2017-12-15 04:10:17 +00:00
if err != nil {
2017-12-15 19:36:14 +00:00
return fmt . Errorf ( "resource %s not found" , name )
2017-12-15 04:10:17 +00:00
}
2017-12-19 21:06:24 +00:00
2017-12-15 19:36:14 +00:00
return json . Unmarshal ( octets , resource )
2017-12-15 04:10:17 +00:00
}
2017-12-15 19:36:14 +00:00
func create ( file string , resource interface { } ) error {
2017-12-15 04:10:17 +00:00
h , err := os . Create ( file )
if err != nil {
return err
}
defer h . Close ( )
2017-12-15 19:36:14 +00:00
octets , err := json . MarshalIndent ( resource , " " , " " )
if err != nil {
2017-12-15 04:10:17 +00:00
return err
}
2017-12-15 19:36:14 +00:00
_ , err = h . Write ( octets )
return err
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
if err := d . Load ( path . Join ( d . Dir , file . Name ( ) ) , & dashboard ) ; err != nil {
2017-12-15 04:10:17 +00:00
continue // We want to load all files we can.
} else {
dashboards = append ( dashboards , dashboard )
}
}
return dashboards , nil
}
// Add creates a new dashboard within the directory
func ( d * Dashboards ) Add ( ctx context . Context , dashboard chronograf . Dashboard ) ( chronograf . Dashboard , error ) {
genID , err := d . IDs . Generate ( )
if err != nil {
d . Logger .
WithField ( "component" , "dashboard" ) .
Error ( "Unable to generate ID" )
return chronograf . Dashboard { } , err
}
id , err := strconv . Atoi ( genID )
if err != nil {
d . Logger .
WithField ( "component" , "dashboard" ) .
Error ( "Unable to convert ID" )
return chronograf . Dashboard { } , err
}
dashboard . ID = chronograf . DashboardID ( id )
2017-12-15 19:36:14 +00:00
file := dashboardFile ( d . Dir , dashboard )
2017-12-15 04:10:17 +00:00
if err = d . Create ( file , dashboard ) ; err != nil {
if err == chronograf . ErrDashboardInvalid {
d . Logger .
WithField ( "component" , "dashboard" ) .
WithField ( "name" , file ) .
Error ( "Invalid Dashboard: " , err )
} else {
d . Logger .
WithField ( "component" , "dashboard" ) .
WithField ( "name" , file ) .
Error ( "Unable to write dashboard:" , err )
}
return chronograf . Dashboard { } , err
}
return dashboard , nil
}
// 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
}
// 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
}
// 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
}
2017-12-15 19:36:14 +00:00
file := dashboardFile ( d . Dir , dashboard )
2017-12-15 04:10:17 +00:00
return d . Create ( file , dashboard )
}
// 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
2017-12-18 23:56:23 +00:00
if err := d . 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
}