2016-10-10 15:45:35 +00:00
package canned
import (
2016-10-25 15:20:06 +00:00
"context"
2016-10-10 15:45:35 +00:00
"encoding/json"
2016-10-11 17:52:16 +00:00
"fmt"
2016-10-10 15:45:35 +00:00
"io/ioutil"
"os"
"path"
2016-10-20 14:38:23 +00:00
"github.com/influxdata/chronograf"
2016-10-10 15:45:35 +00:00
)
2017-03-06 16:11:52 +00:00
// AppExt is the the file extension searched for in the directory for layout files
2016-10-10 15:45:35 +00:00
const AppExt = ".json"
2016-10-11 17:52:16 +00:00
// Apps are canned JSON layouts. Implements LayoutStore.
2016-10-10 15:45:35 +00:00
type Apps struct {
Dir string // Dir is the directory contained the pre-canned applications.
2016-10-20 14:38:23 +00:00
Load func ( string ) ( chronograf . Layout , error ) // Load loads string name and return a Layout
Filename func ( string , chronograf . Layout ) string // Filename takes dir and layout and returns loadable file
Create func ( string , chronograf . Layout ) error // Create will write layout to file.
2016-10-10 15:45:35 +00:00
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
2016-10-20 14:38:23 +00:00
IDs chronograf . ID // IDs generate unique ids for new application layouts
2016-11-07 16:27:35 +00:00
Logger chronograf . Logger
2016-10-10 15:45:35 +00:00
}
2017-03-06 16:11:52 +00:00
// NewApps constructs a layout store wrapping a file system directory
2016-11-07 16:10:26 +00:00
func NewApps ( dir string , ids chronograf . ID , logger chronograf . Logger ) chronograf . LayoutStore {
2016-10-10 15:45:35 +00:00
return & Apps {
Dir : dir ,
Load : loadFile ,
Filename : fileName ,
Create : createLayout ,
ReadDir : ioutil . ReadDir ,
Remove : os . Remove ,
2016-10-11 00:34:05 +00:00
IDs : ids ,
2016-11-07 16:27:35 +00:00
Logger : logger ,
2016-10-10 15:45:35 +00:00
}
}
2016-10-20 14:38:23 +00:00
func fileName ( dir string , layout chronograf . Layout ) string {
2016-11-07 16:27:35 +00:00
base := fmt . Sprintf ( "%s%s" , layout . Measurement , AppExt )
2016-10-11 17:52:16 +00:00
return path . Join ( dir , base )
2016-10-10 15:45:35 +00:00
}
2016-10-20 14:38:23 +00:00
func loadFile ( name string ) ( chronograf . Layout , error ) {
2016-10-10 15:45:35 +00:00
octets , err := ioutil . ReadFile ( name )
if err != nil {
2016-11-07 16:10:26 +00:00
return chronograf . Layout { } , chronograf . ErrLayoutNotFound
2016-10-10 15:45:35 +00:00
}
2016-10-20 14:38:23 +00:00
var layout chronograf . Layout
2016-10-10 15:45:35 +00:00
if err = json . Unmarshal ( octets , & layout ) ; err != nil {
2016-11-07 16:10:26 +00:00
return chronograf . Layout { } , chronograf . ErrLayoutInvalid
2016-10-10 15:45:35 +00:00
}
return layout , nil
}
2016-10-20 14:38:23 +00:00
func createLayout ( file string , layout chronograf . Layout ) error {
2016-10-10 15:45:35 +00:00
h , err := os . Create ( file )
if err != nil {
return err
}
defer h . Close ( )
2016-10-11 00:34:05 +00:00
if octets , err := json . MarshalIndent ( layout , " " , " " ) ; err != nil {
2016-11-07 16:10:26 +00:00
return chronograf . ErrLayoutInvalid
2017-03-06 16:11:52 +00:00
} else if _ , err := h . Write ( octets ) ; err != nil {
return err
2016-10-10 15:45:35 +00:00
}
2017-03-06 16:11:52 +00:00
2016-10-10 15:45:35 +00:00
return nil
}
2017-03-06 16:11:52 +00:00
// All returns all layouts from the directory
2016-10-20 14:38:23 +00:00
func ( a * Apps ) All ( ctx context . Context ) ( [ ] chronograf . Layout , error ) {
2016-10-10 15:45:35 +00:00
files , err := a . ReadDir ( a . Dir )
if err != nil {
return nil , err
}
2016-10-20 14:38:23 +00:00
layouts := [ ] chronograf . Layout { }
2016-10-10 15:45:35 +00:00
for _ , file := range files {
if path . Ext ( file . Name ( ) ) != AppExt {
continue
}
2016-10-11 00:34:05 +00:00
if layout , err := a . Load ( path . Join ( a . Dir , file . Name ( ) ) ) ; err != nil {
2016-10-10 15:45:35 +00:00
continue // We want to load all files we can.
} else {
layouts = append ( layouts , layout )
}
}
return layouts , nil
}
2017-03-06 16:11:52 +00:00
// Add creates a new layout within the directory
2016-10-20 14:38:23 +00:00
func ( a * Apps ) Add ( ctx context . Context , layout chronograf . Layout ) ( chronograf . Layout , error ) {
2016-10-11 00:34:05 +00:00
var err error
layout . ID , err = a . IDs . Generate ( )
2016-12-20 20:59:56 +00:00
if err != nil {
a . Logger .
WithField ( "component" , "apps" ) .
Error ( "Unable to generate ID" )
return chronograf . Layout { } , err
}
2016-10-10 15:45:35 +00:00
file := a . Filename ( a . Dir , layout )
if err = a . Create ( file , layout ) ; err != nil {
2016-11-07 16:10:26 +00:00
if err == chronograf . ErrLayoutInvalid {
2016-11-07 16:27:35 +00:00
a . Logger .
2016-11-07 16:10:26 +00:00
WithField ( "component" , "apps" ) .
WithField ( "name" , file ) .
Error ( "Invalid Layout: " , err )
} else {
2016-11-07 16:27:35 +00:00
a . Logger .
2016-11-07 16:10:26 +00:00
WithField ( "component" , "apps" ) .
WithField ( "name" , file ) .
Error ( "Unable to write layout:" , err )
}
2016-10-20 14:38:23 +00:00
return chronograf . Layout { } , err
2016-10-10 15:45:35 +00:00
}
return layout , nil
}
2017-03-06 16:11:52 +00:00
// Delete removes a layout file from the directory
2016-10-20 14:38:23 +00:00
func ( a * Apps ) Delete ( ctx context . Context , layout chronograf . Layout ) error {
2016-11-07 16:10:26 +00:00
_ , file , err := a . idToFile ( layout . ID )
2016-10-11 17:52:16 +00:00
if err != nil {
return err
}
2016-10-10 15:45:35 +00:00
if err := a . Remove ( file ) ; err != nil {
2016-11-07 16:27:35 +00:00
a . Logger .
2016-10-10 15:45:35 +00:00
WithField ( "component" , "apps" ) .
WithField ( "name" , file ) .
Error ( "Unable to remove layout:" , err )
return err
}
return nil
}
2017-03-06 16:11:52 +00:00
// Get returns an app file from the layout directory
2016-10-20 14:38:23 +00:00
func ( a * Apps ) Get ( ctx context . Context , ID string ) ( chronograf . Layout , error ) {
2016-11-07 16:10:26 +00:00
l , file , err := a . idToFile ( ID )
2016-10-11 17:52:16 +00:00
if err != nil {
2016-10-20 14:38:23 +00:00
return chronograf . Layout { } , err
2016-10-11 17:52:16 +00:00
}
2016-11-07 16:10:26 +00:00
2016-10-10 15:45:35 +00:00
if err != nil {
2016-11-07 16:10:26 +00:00
if err == chronograf . ErrLayoutNotFound {
2016-11-07 16:27:35 +00:00
a . Logger .
2016-11-07 16:10:26 +00:00
WithField ( "component" , "apps" ) .
WithField ( "name" , file ) .
Error ( "Unable to read file" )
} else if err == chronograf . ErrLayoutInvalid {
2016-11-07 16:27:35 +00:00
a . Logger .
2016-11-07 16:10:26 +00:00
WithField ( "component" , "apps" ) .
WithField ( "name" , file ) .
Error ( "File is not a layout" )
}
return chronograf . Layout { } , err
2016-10-10 15:45:35 +00:00
}
return l , nil
}
2017-03-06 16:11:52 +00:00
// Update replaces a layout from the file system directory
2016-10-20 14:38:23 +00:00
func ( a * Apps ) Update ( ctx context . Context , layout chronograf . Layout ) error {
2016-12-20 20:59:56 +00:00
l , _ , err := a . idToFile ( layout . ID )
2016-10-11 17:52:16 +00:00
if err != nil {
2016-10-10 15:45:35 +00:00
return err
}
2016-10-11 17:52:16 +00:00
if err := a . Delete ( ctx , l ) ; err != nil {
return err
}
2016-12-20 20:59:56 +00:00
file := a . Filename ( a . Dir , layout )
2016-10-10 15:45:35 +00:00
return a . Create ( file , layout )
}
2016-10-11 17:52:16 +00:00
// idToFile takes an id and finds the associated filename
2016-11-07 16:10:26 +00:00
func ( a * Apps ) idToFile ( ID string ) ( chronograf . Layout , string , error ) {
2016-10-11 17:52:16 +00:00
// Because the entire layout information is not known at this point, we need
2016-11-07 16:10:26 +00:00
// to try to find the name of the file through matching the ID in the layout
// content with the ID passed.
2016-10-11 17:52:16 +00:00
files , err := a . ReadDir ( a . Dir )
if err != nil {
2016-11-07 16:10:26 +00:00
return chronograf . Layout { } , "" , err
2016-10-11 17:52:16 +00:00
}
for _ , f := range files {
2016-11-07 16:10:26 +00:00
if path . Ext ( f . Name ( ) ) != AppExt {
continue
}
file := path . Join ( a . Dir , f . Name ( ) )
layout , err := a . Load ( file )
if err != nil {
return chronograf . Layout { } , "" , err
}
if layout . ID == ID {
return layout , file , nil
2016-10-11 17:52:16 +00:00
}
}
2016-11-07 16:10:26 +00:00
return chronograf . Layout { } , "" , chronograf . ErrLayoutNotFound
2016-10-11 17:52:16 +00:00
}