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