chore: tidy and document filestore functionality

pull/5356/head
greg linton 2020-01-16 14:09:56 -07:00 committed by Greg
parent 3e5e8c5c55
commit d470723faf
23 changed files with 318 additions and 1044 deletions

View File

@ -700,14 +700,8 @@ type Layout struct {
type LayoutsStore interface {
// All returns all dashboards in the store
All(context.Context) ([]Layout, error)
// Add creates a new dashboard in the LayoutsStore
Add(context.Context, Layout) (Layout, error)
// Delete the dashboard from the store
Delete(context.Context, Layout) error
// Get retrieves Layout if `ID` exists
Get(ctx context.Context, ID string) (Layout, error)
// Update the dashboard in the store.
Update(context.Context, Layout) error
}
// ProtoboardMeta is the metadata of a Protoboard

View File

@ -3,7 +3,6 @@ package filestore
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path"
@ -14,64 +13,43 @@ import (
// AppExt is the the file extension searched for in the directory for layout files
const AppExt = ".json"
// Verify apps (layouts) implements layoutsStore interface.
var _ chronograf.LayoutsStore = (*Apps)(nil)
// 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
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
ReadDir func(dirname string) ([]os.FileInfo, error) // ReadDir reads the directory named by dirname and returns a list of directory entries sorted by filename.
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,
Dir: dir,
Load: loadFile,
ReadDir: ioutil.ReadDir,
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)
@ -93,51 +71,6 @@ func (a *Apps) All(ctx context.Context) ([]chronograf.Layout, error) {
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)
@ -162,20 +95,6 @@ func (a *Apps) Get(ctx context.Context, ID string) (chronograf.Layout, error) {
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

View File

@ -55,109 +55,6 @@ func TestAll(t *testing.T) {
}
}
func TestAdd(t *testing.T) {
t.Parallel()
var tests = []struct {
Existing []chronograf.Layout
Add chronograf.Layout
ExpectedID string
Err error
}{
{
Existing: []chronograf.Layout{
{ID: "1",
Application: "howdy",
},
{ID: "2",
Application: "doody",
},
},
Add: chronograf.Layout{
Application: "newbie",
},
ExpectedID: "3",
Err: nil,
},
{
Existing: []chronograf.Layout{},
Add: chronograf.Layout{
Application: "newbie",
},
ExpectedID: "1",
Err: nil,
},
{
Existing: nil,
Add: chronograf.Layout{
Application: "newbie",
},
ExpectedID: "",
Err: errors.New("Error"),
},
}
for i, test := range tests {
apps, _ := MockApps(test.Existing, test.Err)
layout, err := apps.Add(context.Background(), test.Add)
if err != test.Err {
t.Errorf("Test %d: apps add error expected: %v; actual: %v", i, test.Err, err)
}
if layout.ID != test.ExpectedID {
t.Errorf("Test %d: Layout ID should be equal; expected %s; actual %s", i, test.ExpectedID, layout.ID)
}
}
}
func TestDelete(t *testing.T) {
t.Parallel()
var tests = []struct {
Existing []chronograf.Layout
DeleteID string
Expected map[string]chronograf.Layout
Err error
}{
{
Existing: []chronograf.Layout{
{ID: "1",
Application: "howdy",
},
{ID: "2",
Application: "doody",
},
},
DeleteID: "1",
Expected: map[string]chronograf.Layout{
"dir/2.json": {ID: "2",
Application: "doody",
},
},
Err: nil,
},
{
Existing: []chronograf.Layout{},
DeleteID: "1",
Expected: map[string]chronograf.Layout{},
Err: chronograf.ErrLayoutNotFound,
},
{
Existing: nil,
DeleteID: "1",
Expected: map[string]chronograf.Layout{},
Err: errors.New("Error"),
},
}
for i, test := range tests {
apps, actual := MockApps(test.Existing, test.Err)
err := apps.Delete(context.Background(), chronograf.Layout{ID: test.DeleteID})
if err != test.Err {
t.Errorf("Test %d: apps delete error expected: %v; actual: %v", i, test.Err, err)
}
if !reflect.DeepEqual(*actual, test.Expected) {
t.Errorf("Test %d: Layouts should be equal; expected %v; actual %v", i, test.Expected, actual)
}
}
}
func TestGet(t *testing.T) {
t.Parallel()
var tests = []struct {
@ -207,68 +104,6 @@ func TestGet(t *testing.T) {
}
}
func TestUpdate(t *testing.T) {
t.Parallel()
var tests = []struct {
Existing []chronograf.Layout
Update chronograf.Layout
Expected map[string]chronograf.Layout
Err error
}{
{
Existing: []chronograf.Layout{
{ID: "1",
Application: "howdy",
},
{ID: "2",
Application: "doody",
},
},
Update: chronograf.Layout{
ID: "1",
Application: "hello",
Measurement: "measurement",
},
Expected: map[string]chronograf.Layout{
"dir/1.json": {ID: "1",
Application: "hello",
Measurement: "measurement",
},
"dir/2.json": {ID: "2",
Application: "doody",
},
},
Err: nil,
},
{
Existing: []chronograf.Layout{},
Update: chronograf.Layout{
ID: "1",
},
Expected: map[string]chronograf.Layout{},
Err: chronograf.ErrLayoutNotFound,
},
{
Existing: nil,
Update: chronograf.Layout{
ID: "1",
},
Expected: map[string]chronograf.Layout{},
Err: chronograf.ErrLayoutNotFound,
},
}
for i, test := range tests {
apps, actual := MockApps(test.Existing, test.Err)
err := apps.Update(context.Background(), test.Update)
if err != test.Err {
t.Errorf("Test %d: Layouts get error expected: %v; actual: %v", i, test.Err, err)
}
if !reflect.DeepEqual(*actual, test.Expected) {
t.Errorf("Test %d: Layouts should be equal; expected %v; actual %v", i, test.Expected, actual)
}
}
}
type MockFileInfo struct {
name string
}
@ -321,7 +156,7 @@ func MockApps(existing []chronograf.Layout, expected error) (filestore.Apps, *ma
for _, l := range existing {
layouts[fileName(dir, l)] = l
}
load := func(file string) (chronograf.Layout, error) {
loadLayout := func(file string) (chronograf.Layout, error) {
if expected != nil {
return chronograf.Layout{}, expected
}
@ -333,14 +168,6 @@ func MockApps(existing []chronograf.Layout, expected error) (filestore.Apps, *ma
return l, nil
}
create := func(file string, layout chronograf.Layout) error {
if expected != nil {
return expected
}
layouts[file] = layout
return nil
}
readDir := func(dirname string) ([]os.FileInfo, error) {
if expected != nil {
return nil, expected
@ -353,27 +180,17 @@ func MockApps(existing []chronograf.Layout, expected error) (filestore.Apps, *ma
return info, nil
}
remove := func(name string) error {
if expected != nil {
return expected
}
if _, ok := layouts[name]; !ok {
return chronograf.ErrLayoutNotFound
}
delete(layouts, name)
return nil
}
return filestore.Apps{
Dir: dir,
Load: load,
Filename: fileName,
Create: create,
ReadDir: readDir,
Remove: remove,
Dir: dir,
Load: loadLayout,
ReadDir: readDir,
IDs: &MockID{
id: len(existing),
},
Logger: clog.New(clog.ParseLevel("debug")),
}, &layouts
}
type apps struct {
filestore.Apps
}

View File

@ -2,12 +2,10 @@ package filestore
import (
"context"
"encoding/json"
"fmt"
"errors"
"io/ioutil"
"os"
"path"
"strconv"
"github.com/influxdata/chronograf"
)
@ -15,13 +13,12 @@ import (
// DashExt is the the file extension searched for in the directory for dashboard files
const DashExt = ".dashboard"
var _ chronograf.DashboardsStore = &Dashboards{}
// Verify dashboards implements dashboardsStore interface.
var _ chronograf.DashboardsStore = (*Dashboards)(nil)
// Dashboards are JSON dashboards stored in the filesystem
type Dashboards struct {
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
@ -32,8 +29,6 @@ type Dashboards struct {
func NewDashboards(dir string, ids chronograf.ID, logger chronograf.Logger) chronograf.DashboardsStore {
return &Dashboards{
Dir: dir,
Load: load,
Create: create,
ReadDir: ioutil.ReadDir,
Remove: os.Remove,
IDs: ids,
@ -41,36 +36,6 @@ func NewDashboards(dir string, ids chronograf.ID, logger chronograf.Logger) chro
}
}
func dashboardFile(dir string, dashboard chronograf.Dashboard) string {
base := fmt.Sprintf("%s%s", dashboard.Name, DashExt)
return path.Join(dir, base)
}
func load(name string, resource interface{}) error {
octets, err := templatedFromEnv(name)
if err != nil {
return fmt.Errorf("resource %s not found", name)
}
return json.Unmarshal(octets, resource)
}
func create(file string, resource interface{}) error {
h, err := os.Create(file)
if err != nil {
return err
}
defer h.Close()
octets, err := json.MarshalIndent(resource, " ", " ")
if err != nil {
return err
}
_, err = h.Write(octets)
return err
}
// All returns all dashboards from the directory
func (d *Dashboards) All(ctx context.Context) ([]chronograf.Dashboard, error) {
files, err := d.ReadDir(d.Dir)
@ -84,7 +49,7 @@ func (d *Dashboards) All(ctx context.Context) ([]chronograf.Dashboard, error) {
continue
}
var dashboard chronograf.Dashboard
if err := d.Load(path.Join(d.Dir, file.Name()), &dashboard); err != nil {
if err := load(path.Join(d.Dir, file.Name()), &dashboard); err != nil {
continue // We want to load all files we can.
} else {
dashboards = append(dashboards, dashboard)
@ -93,61 +58,6 @@ func (d *Dashboards) All(ctx context.Context) ([]chronograf.Dashboard, error) {
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)
file := dashboardFile(d.Dir, dashboard)
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)
@ -168,20 +78,6 @@ func (d *Dashboards) Get(ctx context.Context, id chronograf.DashboardID) (chrono
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
}
file := dashboardFile(d.Dir, dashboard)
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
@ -198,7 +94,7 @@ func (d *Dashboards) idToFile(id chronograf.DashboardID) (chronograf.Dashboard,
}
file := path.Join(d.Dir, f.Name())
var dashboard chronograf.Dashboard
if err := d.Load(file, &dashboard); err != nil {
if err := load(file, &dashboard); err != nil {
return chronograf.Dashboard{}, "", err
}
if dashboard.ID == id {
@ -208,3 +104,39 @@ func (d *Dashboards) idToFile(id chronograf.DashboardID) (chronograf.Dashboard,
return chronograf.Dashboard{}, "", chronograf.ErrDashboardNotFound
}
// 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")
}

View File

@ -1,24 +0,0 @@
package filestore
import (
"os"
"strings"
)
var env map[string]string
// environ returns a map of all environment variables in the running process
func environ() map[string]string {
if env == nil {
env = make(map[string]string)
envVars := os.Environ()
for _, envVar := range envVars {
kv := strings.SplitN(envVar, "=", 2)
if len(kv) != 2 {
continue
}
env[kv[0]] = kv[1]
}
}
return env
}

View File

@ -1,29 +0,0 @@
package filestore
import (
"os"
"testing"
)
func Test_environ(t *testing.T) {
tests := []struct {
name string
key string
value string
}{
{
name: "environment variable is returned",
key: "CHRONOGRAF_TEST_ENVIRON",
value: "howdy",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
os.Setenv(tt.key, tt.value)
got := environ()
if v, ok := got[tt.key]; !ok || v != tt.value {
t.Errorf("environ() = %v, want %v", v, tt.value)
}
})
}
}

100
filestore/filestore.go Normal file
View File

@ -0,0 +1,100 @@
// Package filestore provides the ability to read pre-defined resources from
// a specified directory (--canned-path). This also provides a means to make
// updates to certain resources. Adding new resources is not supported as it
// paves the way for too much unexpected behaviors. Filestore is used in
// conjunction with a 'multistore' and is usually tried last. By supporting
// add functionality, a resource may mistakenly be saved to the filesystem
// if the write to the db fails.
//
// Resources that are storable in a file are:
// (CRUD refers to create, read, update, delete. An '_' means not supported)
// Apps(layouts) - _R__
// Dashboards - _RUD
// Kapacitors - _RUD
// Organizations - _R__
// Protoboards - _R__
// Sources - _RUD
//
// Caution should be taken when editing resources provided via the filestore,
// especially in a distributed environment as unexpected behavior may occur.
package filestore
import (
"bytes"
"encoding/json"
"fmt"
"html/template"
"os"
"path"
"strings"
)
func create(file string, resource interface{}) error {
h, err := os.Create(file)
if err != nil {
return err
}
defer h.Close()
octets, err := json.MarshalIndent(resource, " ", " ")
if err != nil {
return err
}
_, err = h.Write(octets)
return err
}
func file(dir, name, ext string) string {
base := fmt.Sprintf("%s%s", name, ext)
return path.Join(dir, base)
}
func load(name string, resource interface{}) error {
octets, err := templatedFromEnv(name)
if err != nil {
return fmt.Errorf("resource %s not found", name)
}
return json.Unmarshal(octets, resource)
}
var env map[string]string
// templatedFromEnv returns all files templated against environment variables
func templatedFromEnv(filenames ...string) ([]byte, error) {
return templated(environ(), filenames...)
}
// templated returns all files templated using data
func templated(data interface{}, filenames ...string) ([]byte, error) {
t, err := template.ParseFiles(filenames...)
if err != nil {
return nil, err
}
var b bytes.Buffer
// If a key in the file exists but is not in the data we
// immediately fail with a missing key error
err = t.Option("missingkey=error").Execute(&b, data)
if err != nil {
return nil, err
}
return b.Bytes(), nil
}
// environ returns a map of all environment variables in the running process
func environ() map[string]string {
if env == nil {
env = make(map[string]string)
envVars := os.Environ()
for _, envVar := range envVars {
kv := strings.SplitN(envVar, "=", 2)
if len(kv) != 2 {
continue
}
env[kv[0]] = kv[1]
}
}
return env
}

View File

@ -2,11 +2,11 @@ package filestore
import (
"context"
"errors"
"fmt"
"io/ioutil"
"os"
"path"
"strconv"
"github.com/influxdata/chronograf"
)
@ -14,13 +14,12 @@ import (
// KapExt is the the file extension searched for in the directory for kapacitor files
const KapExt = ".kap"
var _ chronograf.ServersStore = &Kapacitors{}
// Verify Kapacitors implements serverStore interface.
var _ chronograf.ServersStore = (*Kapacitors)(nil)
// Kapacitors are JSON kapacitors stored in the filesystem
type Kapacitors struct {
Dir string // Dir is the directory containing the kapacitors.
Load func(string, interface{}) error // Load loads string name and dashbaord passed in as interface
Create func(string, interface{}) error // Create will write kapacitor 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 kapacitors
@ -31,8 +30,6 @@ type Kapacitors struct {
func NewKapacitors(dir string, ids chronograf.ID, logger chronograf.Logger) chronograf.ServersStore {
return &Kapacitors{
Dir: dir,
Load: load,
Create: create,
ReadDir: ioutil.ReadDir,
Remove: os.Remove,
IDs: ids,
@ -40,11 +37,6 @@ func NewKapacitors(dir string, ids chronograf.ID, logger chronograf.Logger) chro
}
}
func kapacitorFile(dir string, kapacitor chronograf.Server) string {
base := fmt.Sprintf("%s%s", kapacitor.Name, KapExt)
return path.Join(dir, base)
}
// All returns all kapacitors from the directory
func (d *Kapacitors) All(ctx context.Context) ([]chronograf.Server, error) {
files, err := d.ReadDir(d.Dir)
@ -58,7 +50,7 @@ func (d *Kapacitors) All(ctx context.Context) ([]chronograf.Server, error) {
continue
}
var kapacitor chronograf.Server
if err := d.Load(path.Join(d.Dir, file.Name()), &kapacitor); err != nil {
if err := load(path.Join(d.Dir, file.Name()), &kapacitor); err != nil {
var fmtErr = fmt.Errorf("Error loading kapacitor configuration from %v:\n%v", path.Join(d.Dir, file.Name()), err)
d.Logger.Error(fmtErr)
continue // We want to load all files we can.
@ -69,61 +61,6 @@ func (d *Kapacitors) All(ctx context.Context) ([]chronograf.Server, error) {
return kapacitors, nil
}
// Add creates a new kapacitor within the directory
func (d *Kapacitors) Add(ctx context.Context, kapacitor chronograf.Server) (chronograf.Server, error) {
genID, err := d.IDs.Generate()
if err != nil {
d.Logger.
WithField("component", "kapacitor").
Error("Unable to generate ID")
return chronograf.Server{}, err
}
id, err := strconv.Atoi(genID)
if err != nil {
d.Logger.
WithField("component", "kapacitor").
Error("Unable to convert ID")
return chronograf.Server{}, err
}
kapacitor.ID = id
file := kapacitorFile(d.Dir, kapacitor)
if err = d.Create(file, kapacitor); err != nil {
if err == chronograf.ErrServerInvalid {
d.Logger.
WithField("component", "kapacitor").
WithField("name", file).
Error("Invalid Server: ", err)
} else {
d.Logger.
WithField("component", "kapacitor").
WithField("name", file).
Error("Unable to write kapacitor:", err)
}
return chronograf.Server{}, err
}
return kapacitor, nil
}
// Delete removes a kapacitor file from the directory
func (d *Kapacitors) Delete(ctx context.Context, kapacitor chronograf.Server) error {
_, file, err := d.idToFile(kapacitor.ID)
if err != nil {
return err
}
if err := d.Remove(file); err != nil {
d.Logger.
WithField("component", "kapacitor").
WithField("name", file).
Error("Unable to remove kapacitor:", err)
return err
}
return nil
}
// Get returns a kapacitor file from the kapacitor directory
func (d *Kapacitors) Get(ctx context.Context, id int) (chronograf.Server, error) {
board, file, err := d.idToFile(id)
@ -144,20 +81,6 @@ func (d *Kapacitors) Get(ctx context.Context, id int) (chronograf.Server, error)
return board, nil
}
// Update replaces a kapacitor from the file system directory
func (d *Kapacitors) Update(ctx context.Context, kapacitor chronograf.Server) error {
board, _, err := d.idToFile(kapacitor.ID)
if err != nil {
return err
}
if err := d.Delete(ctx, board); err != nil {
return err
}
file := kapacitorFile(d.Dir, kapacitor)
return d.Create(file, kapacitor)
}
// idToFile takes an id and finds the associated filename
func (d *Kapacitors) idToFile(id int) (chronograf.Server, string, error) {
// Because the entire kapacitor information is not known at this point, we need
@ -174,7 +97,7 @@ func (d *Kapacitors) idToFile(id int) (chronograf.Server, string, error) {
}
file := path.Join(d.Dir, f.Name())
var kapacitor chronograf.Server
if err := d.Load(file, &kapacitor); err != nil {
if err := load(file, &kapacitor); err != nil {
return chronograf.Server{}, "", err
}
if kapacitor.ID == id {
@ -184,3 +107,39 @@ func (d *Kapacitors) idToFile(id int) (chronograf.Server, string, error) {
return chronograf.Server{}, "", chronograf.ErrServerNotFound
}
// Update replaces a kapacitor from the file system directory
func (d *Kapacitors) Update(ctx context.Context, kapacitor chronograf.Server) error {
board, _, err := d.idToFile(kapacitor.ID)
if err != nil {
return err
}
if err := d.Delete(ctx, board); err != nil {
return err
}
file := file(d.Dir, kapacitor.Name, KapExt)
return create(file, kapacitor)
}
// Delete removes a kapacitor file from the directory
func (d *Kapacitors) Delete(ctx context.Context, kapacitor chronograf.Server) error {
_, file, err := d.idToFile(kapacitor.ID)
if err != nil {
return err
}
if err := d.Remove(file); err != nil {
d.Logger.
WithField("component", "kapacitor").
WithField("name", file).
Error("Unable to remove kapacitor:", err)
return err
}
return nil
}
// Add creates a new kapacitor within the directory
func (d *Kapacitors) Add(ctx context.Context, kapacitor chronograf.Server) (chronograf.Server, error) {
return chronograf.Server{}, errors.New("adding a server to a filestore is not supported")
}

View File

@ -2,7 +2,7 @@ package filestore
import (
"context"
"fmt"
"errors"
"io/ioutil"
"os"
"path"
@ -13,7 +13,8 @@ import (
// OrgExt is the the file extension searched for in the directory for org files
const OrgExt = ".org"
var _ chronograf.OrganizationsStore = &Organizations{}
// Verify organizations implements organizationsStore interface.
var _ chronograf.OrganizationsStore = (*Organizations)(nil)
// Organizations are JSON orgs stored in the filesystem
type Organizations struct {
@ -33,11 +34,6 @@ func NewOrganizations(dir string, logger chronograf.Logger) chronograf.Organizat
}
}
func orgFile(dir string, org chronograf.Organization) string {
base := fmt.Sprintf("%s%s", org.Name, OrgExt)
return path.Join(dir, base)
}
// All returns all orgs from the directory
func (o *Organizations) All(ctx context.Context) ([]chronograf.Organization, error) {
files, err := o.ReadDir(o.Dir)
@ -66,31 +62,6 @@ func (o *Organizations) Get(ctx context.Context, query chronograf.OrganizationQu
return org, err
}
// Add is not allowed for the filesystem organization store
func (o *Organizations) Add(ctx context.Context, org *chronograf.Organization) (*chronograf.Organization, error) {
return nil, fmt.Errorf("unable to add organizations to the filesystem")
}
// Delete is not allowed for the filesystem organization store
func (o *Organizations) Delete(ctx context.Context, org *chronograf.Organization) error {
return fmt.Errorf("unable to delete an organization from the filesystem")
}
// Update is not allowed for the filesystem organization store
func (o *Organizations) Update(ctx context.Context, org *chronograf.Organization) error {
return fmt.Errorf("unable to update organizations on the filesystem")
}
// CreateDefault is not allowed for the filesystem organization store
func (o *Organizations) CreateDefault(ctx context.Context) error {
return fmt.Errorf("unable to create default organizations on the filesystem")
}
// DefaultOrganization is not allowed for the filesystem organization store
func (o *Organizations) DefaultOrganization(ctx context.Context) (*chronograf.Organization, error) {
return nil, fmt.Errorf("unable to get default organizations from the filestore")
}
// findOrg takes an OrganizationQuery and finds the associated filename
func (o *Organizations) findOrg(query chronograf.OrganizationQuery) (*chronograf.Organization, string, error) {
// Because the entire org information is not known at this point, we need
@ -120,3 +91,28 @@ func (o *Organizations) findOrg(query chronograf.OrganizationQuery) (*chronograf
return nil, "", chronograf.ErrOrganizationNotFound
}
// Add is not allowed for the filesystem organization store
func (o *Organizations) Add(ctx context.Context, org *chronograf.Organization) (*chronograf.Organization, error) {
return nil, errors.New("unable to add organizations to the filesystem")
}
// Delete is not allowed for the filesystem organization store
func (o *Organizations) Delete(ctx context.Context, org *chronograf.Organization) error {
return errors.New("unable to delete an organization from the filesystem")
}
// Update is not allowed for the filesystem organization store
func (o *Organizations) Update(ctx context.Context, org *chronograf.Organization) error {
return errors.New("unable to update organizations on the filesystem")
}
// CreateDefault is not allowed for the filesystem organization store
func (o *Organizations) CreateDefault(ctx context.Context) error {
return errors.New("unable to create default organizations on the filesystem")
}
// DefaultOrganization is not allowed for the filesystem organization store
func (o *Organizations) DefaultOrganization(ctx context.Context) (*chronograf.Organization, error) {
return nil, errors.New("unable to get default organizations from the filestore")
}

View File

@ -13,6 +13,9 @@ import (
// ProtoboardExt is the the file extension searched for in the directory for protoboard files
const ProtoboardExt = ".json"
// Verify Protoboards implements protoboardStore interface.
var _ chronograf.ProtoboardsStore = (*Protoboards)(nil)
// Protoboards are instantiable JSON representation of dashbards. Implements ProtoboardsStore.
type Protoboards struct {
Dir string // Dir is the directory containing protoboard json definitions

View File

@ -2,11 +2,11 @@ package filestore
import (
"context"
"errors"
"fmt"
"io/ioutil"
"os"
"path"
"strconv"
"github.com/influxdata/chronograf"
)
@ -14,13 +14,12 @@ import (
// SrcExt is the the file extension searched for in the directory for source files
const SrcExt = ".src"
var _ chronograf.SourcesStore = &Sources{}
// Verify sources implements sourcesStore interface.
var _ chronograf.SourcesStore = (*Sources)(nil)
// Sources are JSON sources stored in the filesystem
type Sources struct {
Dir string // Dir is the directory containing the sources.
Load func(string, interface{}) error // Load loads string name and dashbaord passed in as interface
Create func(string, interface{}) error // Create will write source 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 sources
@ -31,8 +30,6 @@ type Sources struct {
func NewSources(dir string, ids chronograf.ID, logger chronograf.Logger) chronograf.SourcesStore {
return &Sources{
Dir: dir,
Load: load,
Create: create,
ReadDir: ioutil.ReadDir,
Remove: os.Remove,
IDs: ids,
@ -40,11 +37,6 @@ func NewSources(dir string, ids chronograf.ID, logger chronograf.Logger) chronog
}
}
func sourceFile(dir string, source chronograf.Source) string {
base := fmt.Sprintf("%s%s", source.Name, SrcExt)
return path.Join(dir, base)
}
// All returns all sources from the directory
func (d *Sources) All(ctx context.Context) ([]chronograf.Source, error) {
files, err := d.ReadDir(d.Dir)
@ -58,7 +50,7 @@ func (d *Sources) All(ctx context.Context) ([]chronograf.Source, error) {
continue
}
var source chronograf.Source
if err := d.Load(path.Join(d.Dir, file.Name()), &source); err != nil {
if err := load(path.Join(d.Dir, file.Name()), &source); err != nil {
var fmtErr = fmt.Errorf("Error loading source configuration from %v:\n%v", path.Join(d.Dir, file.Name()), err)
d.Logger.Error(fmtErr)
continue // We want to load all files we can.
@ -69,61 +61,6 @@ func (d *Sources) All(ctx context.Context) ([]chronograf.Source, error) {
return sources, nil
}
// Add creates a new source within the directory
func (d *Sources) Add(ctx context.Context, source chronograf.Source) (chronograf.Source, error) {
genID, err := d.IDs.Generate()
if err != nil {
d.Logger.
WithField("component", "source").
Error("Unable to generate ID")
return chronograf.Source{}, err
}
id, err := strconv.Atoi(genID)
if err != nil {
d.Logger.
WithField("component", "source").
Error("Unable to convert ID")
return chronograf.Source{}, err
}
source.ID = id
file := sourceFile(d.Dir, source)
if err = d.Create(file, source); err != nil {
if err == chronograf.ErrSourceInvalid {
d.Logger.
WithField("component", "source").
WithField("name", file).
Error("Invalid Source: ", err)
} else {
d.Logger.
WithField("component", "source").
WithField("name", file).
Error("Unable to write source:", err)
}
return chronograf.Source{}, err
}
return source, nil
}
// Delete removes a source file from the directory
func (d *Sources) Delete(ctx context.Context, source chronograf.Source) error {
_, file, err := d.idToFile(source.ID)
if err != nil {
return err
}
if err := d.Remove(file); err != nil {
d.Logger.
WithField("component", "source").
WithField("name", file).
Error("Unable to remove source:", err)
return err
}
return nil
}
// Get returns a source file from the source directory
func (d *Sources) Get(ctx context.Context, id int) (chronograf.Source, error) {
board, file, err := d.idToFile(id)
@ -154,8 +91,25 @@ func (d *Sources) Update(ctx context.Context, source chronograf.Source) error {
if err := d.Delete(ctx, board); err != nil {
return err
}
file := sourceFile(d.Dir, source)
return d.Create(file, source)
file := file(d.Dir, source.Name, SrcExt)
return create(file, source)
}
// Delete removes a source file from the directory
func (d *Sources) Delete(ctx context.Context, source chronograf.Source) error {
_, file, err := d.idToFile(source.ID)
if err != nil {
return err
}
if err := d.Remove(file); err != nil {
d.Logger.
WithField("component", "source").
WithField("name", file).
Error("Unable to remove source:", err)
return err
}
return nil
}
// idToFile takes an id and finds the associated filename
@ -174,7 +128,7 @@ func (d *Sources) idToFile(id int) (chronograf.Source, string, error) {
}
file := path.Join(d.Dir, f.Name())
var source chronograf.Source
if err := d.Load(file, &source); err != nil {
if err := load(file, &source); err != nil {
return chronograf.Source{}, "", err
}
if source.ID == id {
@ -184,3 +138,8 @@ func (d *Sources) idToFile(id int) (chronograf.Source, string, error) {
return chronograf.Source{}, "", chronograf.ErrSourceNotFound
}
// Add creates a new source within the directory
func (d *Sources) Add(ctx context.Context, source chronograf.Source) (chronograf.Source, error) {
return chronograf.Source{}, errors.New("adding a source to a filestore is not supported")
}

View File

@ -1,28 +0,0 @@
package filestore
import (
"bytes"
"html/template"
)
// templated returns all files templated using data
func templated(data interface{}, filenames ...string) ([]byte, error) {
t, err := template.ParseFiles(filenames...)
if err != nil {
return nil, err
}
var b bytes.Buffer
// If a key in the file exists but is not in the data we
// immediately fail with a missing key error
err = t.Option("missingkey=error").Execute(&b, data)
if err != nil {
return nil, err
}
return b.Bytes(), nil
}
// templatedFromEnv returns all files templated against environment variables
func templatedFromEnv(filenames ...string) ([]byte, error) {
return templated(environ(), filenames...)
}

View File

@ -62,3 +62,26 @@ func Test_templated(t *testing.T) {
})
}
}
func Test_environ(t *testing.T) {
tests := []struct {
name string
key string
value string
}{
{
name: "environment variable is returned",
key: "CHRONOGRAF_TEST_ENVIRON",
value: "howdy",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
os.Setenv(tt.key, tt.value)
got := environ()
if v, ok := got[tt.key]; !ok || v != tt.value {
t.Errorf("environ() = %v, want %v", v, tt.value)
}
})
}
}

View File

@ -6,15 +6,11 @@ import (
"io/ioutil"
"os"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/influxdata/chronograf"
)
// TestNow is a set time for testing.
var TestNow = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)
// TestClient wraps *bolt.Client.
type TestClient struct {
Client *client
@ -32,7 +28,6 @@ func NewTestClient() (*TestClient, error) {
ctx := context.TODO()
b, err := NewClient(ctx,
WithPath(f.Name()),
WithNow(func() time.Time { return TestNow }),
)
if err != nil {
return nil, err

View File

@ -39,7 +39,6 @@ type client struct {
db *bolt.DB
isNew bool
logger chronograf.Logger
now func() time.Time
path string
}
@ -49,7 +48,6 @@ func NewClient(ctx context.Context, opts ...Option) (*client, error) {
buildInfo: defaultBuildInfo,
path: defaultBoltPath,
logger: mocks.NewLogger(),
now: time.Now,
}
for i := range opts {
@ -90,14 +88,6 @@ func WithPath(path string) Option {
}
}
// WithNow sets the function to use for the current time.
func WithNow(fn func() time.Time) Option {
return func(c *client) error {
c.now = fn
return nil
}
}
// Open opens or creates the boltDB file.
func (c *client) open(ctx context.Context) error {
if _, err := os.Stat(c.path); os.IsNotExist(err) {

View File

@ -4,16 +4,12 @@ import (
"context"
"errors"
"io/ioutil"
"time"
"github.com/influxdata/chronograf"
"github.com/influxdata/chronograf/kv"
"github.com/influxdata/chronograf/kv/bolt"
)
// TestNow is a set time for testing.
var TestNow = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)
// NewTestClient creates new *bolt.Client with a set time and temp path.
func NewTestClient() (*kv.Service, error) {
f, err := ioutil.TempFile("", "chronograf-bolt-")
@ -30,7 +26,6 @@ func NewTestClient() (*kv.Service, error) {
ctx := context.TODO()
b, err := bolt.NewClient(ctx,
bolt.WithPath(f.Name()),
bolt.WithNow(func() time.Time { return TestNow }),
bolt.WithBuildInfo(build),
)
if err != nil {

View File

@ -39,47 +39,6 @@ func (s *layoutsStore) All(ctx context.Context) ([]chronograf.Layout, error) {
}
// Add creates a new Layout in the layoutsStore.
func (s *layoutsStore) Add(ctx context.Context, src chronograf.Layout) (chronograf.Layout, error) {
if err := s.client.kv.Update(ctx, func(tx Tx) error {
b := tx.Bucket(layoutsBucket)
id, err := s.IDs.Generate()
if err != nil {
return err
}
src.ID = id
if v, err := internal.MarshalLayout(src); err != nil {
return err
} else if err := b.Put([]byte(src.ID), v); err != nil {
return err
}
return nil
}); err != nil {
return chronograf.Layout{}, err
}
return src, nil
}
// Delete removes the Layout from the layoutsStore
func (s *layoutsStore) Delete(ctx context.Context, src chronograf.Layout) error {
_, err := s.Get(ctx, src.ID)
if err != nil {
return err
}
if err := s.client.kv.Update(ctx, func(tx Tx) error {
if err := tx.Bucket(layoutsBucket).Delete([]byte(src.ID)); err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}
// Get returns a Layout if the id exists.
func (s *layoutsStore) Get(ctx context.Context, id string) (chronograf.Layout, error) {
var src chronograf.Layout
@ -96,25 +55,3 @@ func (s *layoutsStore) Get(ctx context.Context, id string) (chronograf.Layout, e
return src, nil
}
// Update a Layout
func (s *layoutsStore) Update(ctx context.Context, src chronograf.Layout) error {
if err := s.client.kv.Update(ctx, func(tx Tx) error {
// Get an existing layout with the same ID.
b := tx.Bucket(layoutsBucket)
if v, err := b.Get([]byte(src.ID)); v == nil || err != nil {
return chronograf.ErrLayoutNotFound
}
if v, err := internal.MarshalLayout(src); err != nil {
return err
} else if err := b.Put([]byte(src.ID), v); err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}

View File

@ -2,10 +2,12 @@ package kv_test
import (
"context"
"errors"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/influxdata/chronograf"
"github.com/influxdata/chronograf/mocks"
"github.com/stretchr/testify/require"
)
@ -45,21 +47,10 @@ func TestLayoutStore_All(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client, err := NewTestClient()
if err != nil {
t.Fatal(err)
}
defer client.Close()
s := client.LayoutsStore()
s := mocksLayout(&tt.fields.layouts)
ctx := context.Background()
for _, layout := range tt.fields.layouts {
s.Add(ctx, layout)
}
got, err := s.All(ctx)
if (err != nil) != (tt.wants.err != nil) {
t.Errorf("LayoutsStore.All() error = %v, want error %v", err, tt.wants.err)
return
@ -75,137 +66,6 @@ func TestLayoutStore_All(t *testing.T) {
}
}
func TestLayoutStore_Add(t *testing.T) {
type args struct {
layout chronograf.Layout
}
type wants struct {
layout chronograf.Layout
err error
}
tests := []struct {
name string
args args
wants wants
}{
{
name: "simple",
args: args{
layout: chronograf.Layout{
Application: "test",
Measurement: "test",
},
},
wants: wants{
layout: chronograf.Layout{
Application: "test",
Measurement: "test",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client, err := NewTestClient()
if err != nil {
t.Fatal(err)
}
defer client.Close()
s := client.LayoutsStore()
ctx := context.Background()
l, err := s.Add(ctx, tt.args.layout)
if (err != nil) != (tt.wants.err != nil) {
t.Errorf("LayoutsStore.Add() error = %v, want error %v", err, tt.wants.err)
return
}
got, err := s.Get(ctx, l.ID)
if err != nil {
t.Fatalf("failed to get layout: %v", err)
return
}
if diff := cmp.Diff(got.Application, tt.wants.layout.Application); diff != "" {
t.Errorf("LayoutStore.Add():\n-got/+want\ndiff %s", diff)
return
}
})
}
}
func TestLayoutStore_Delete(t *testing.T) {
type fields struct {
layouts []chronograf.Layout
}
type wants struct {
err error
}
tests := []struct {
name string
fields fields
wants wants
}{
{
name: "simple",
fields: fields{
layouts: []chronograf.Layout{
{
Application: "test",
Measurement: "test",
},
},
},
wants: wants{
err: nil,
},
},
{
name: "layout not found",
fields: fields{
layouts: []chronograf.Layout{
{
Application: "test",
Measurement: "test",
},
},
},
wants: wants{
err: chronograf.ErrLayoutNotFound,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client, err := NewTestClient()
if err != nil {
t.Fatal(err)
}
defer client.Close()
s := client.LayoutsStore()
ctx := context.Background()
var l chronograf.Layout
for _, layout := range tt.fields.layouts {
l, _ = s.Add(ctx, layout)
}
err = s.Delete(ctx, l)
if (err != nil) != (tt.wants.err != nil) {
err = s.Delete(ctx, l)
if (err != nil) != (tt.wants.err != nil) {
t.Errorf("LayoutsStore.Delete() error = %v, want error %v", err, tt.wants.err)
return
}
}
})
}
}
func TestLayoutStore_Get(t *testing.T) {
type fields struct {
layouts []chronograf.Layout
@ -224,10 +84,12 @@ func TestLayoutStore_Get(t *testing.T) {
fields: fields{
layouts: []chronograf.Layout{
{
ID: "A",
Application: "test",
Measurement: "test",
},
{
ID: "B",
Application: "test2",
Measurement: "test2",
},
@ -235,6 +97,7 @@ func TestLayoutStore_Get(t *testing.T) {
},
wants: wants{
layout: chronograf.Layout{
ID: "B",
Application: "test2",
Measurement: "test2",
},
@ -259,25 +122,15 @@ func TestLayoutStore_Get(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client, err := NewTestClient()
if err != nil {
t.Fatal(err)
}
defer client.Close()
s := client.LayoutsStore()
s := mocksLayout(&tt.fields.layouts)
ctx := context.Background()
var l chronograf.Layout
for _, layout := range tt.fields.layouts {
l, _ = s.Add(ctx, layout)
}
id := tt.fields.layouts[len(tt.fields.layouts)-1].ID
if tt.wants.err != nil {
s.Delete(ctx, l)
tt.fields.layouts = tt.fields.layouts[:len(tt.fields.layouts)-1]
}
got, err := s.Get(ctx, l.ID)
got, err := s.Get(ctx, id)
if (err != nil) != (tt.wants.err != nil) {
t.Errorf("LayoutsStore.Get() error = %v, want error %v", err, tt.wants.err)
return
@ -291,71 +144,18 @@ func TestLayoutStore_Get(t *testing.T) {
}
}
func TestLayoutStore_Update(t *testing.T) {
type fields struct {
layouts []chronograf.Layout
}
type wants struct {
layout chronograf.Layout
err error
}
tests := []struct {
name string
fields fields
wants wants
}{
{
name: "simple",
fields: fields{
layouts: []chronograf.Layout{
{
Application: "test",
Measurement: "test",
},
{
Application: "test2",
Measurement: "test2",
},
},
},
wants: wants{
layout: chronograf.Layout{
Application: "test3",
Measurement: "test3",
},
err: nil,
},
func mocksLayout(layouts *[]chronograf.Layout) mocks.LayoutsStore {
return mocks.LayoutsStore{
AllF: func(ctx context.Context) ([]chronograf.Layout, error) {
return *layouts, nil
},
GetF: func(ctx context.Context, id string) (chronograf.Layout, error) {
for _, l := range *layouts {
if l.ID == id {
return l, nil
}
}
return chronograf.Layout{}, errors.New("no layout found")
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client, err := NewTestClient()
if err != nil {
t.Fatal(err)
}
defer client.Close()
s := client.LayoutsStore()
ctx := context.Background()
var l chronograf.Layout
for _, layout := range tt.fields.layouts {
l, _ = s.Add(ctx, layout)
}
l.Application = "test3"
l.Measurement = "test3"
err = s.Update(ctx, l)
if (err != nil) != (tt.wants.err != nil) {
t.Errorf("LayoutsStore.Update() error = %v, want error %v", err, tt.wants.err)
return
}
if diff := cmp.Diff(l.Application, tt.wants.layout.Application); diff != "" {
t.Errorf("LayoutStore.Update():\n-got/+want\ndiff %s", diff)
return
}
})
}
}

View File

@ -1,3 +1,9 @@
// Package memdb provides a transient layer to store the InfluxDB and Kapacitor
// configured via flags at Chronograf start.
// Caution should be taken when editing resources generated from cli flags,
// especially in a distributed environment as unexpected behavior may occur.
// Instead, it is suggested that chronograf be restarted to pick up the new
// flag/evar changes.
package memdb
import (

View File

@ -9,29 +9,14 @@ import (
var _ chronograf.LayoutsStore = &LayoutsStore{}
type LayoutsStore struct {
AddF func(ctx context.Context, layout chronograf.Layout) (chronograf.Layout, error)
AllF func(ctx context.Context) ([]chronograf.Layout, error)
DeleteF func(ctx context.Context, layout chronograf.Layout) error
GetF func(ctx context.Context, id string) (chronograf.Layout, error)
UpdateF func(ctx context.Context, layout chronograf.Layout) error
}
func (s *LayoutsStore) Add(ctx context.Context, layout chronograf.Layout) (chronograf.Layout, error) {
return s.AddF(ctx, layout)
AllF func(ctx context.Context) ([]chronograf.Layout, error)
GetF func(ctx context.Context, id string) (chronograf.Layout, error)
}
func (s *LayoutsStore) All(ctx context.Context) ([]chronograf.Layout, error) {
return s.AllF(ctx)
}
func (s *LayoutsStore) Delete(ctx context.Context, layout chronograf.Layout) error {
return s.DeleteF(ctx, layout)
}
func (s *LayoutsStore) Get(ctx context.Context, id string) (chronograf.Layout, error) {
return s.GetF(ctx, id)
}
func (s *LayoutsStore) Update(ctx context.Context, layout chronograf.Layout) error {
return s.UpdateF(ctx, layout)
}

View File

@ -42,32 +42,6 @@ func (s *Layouts) All(ctx context.Context) ([]chronograf.Layout, error) {
return all, nil
}
// Add creates a new dashboard in the LayoutsStore. Tries each store sequentially until success.
func (s *Layouts) Add(ctx context.Context, layout chronograf.Layout) (chronograf.Layout, error) {
var err error
for _, store := range s.Stores {
var l chronograf.Layout
l, err = store.Add(ctx, layout)
if err == nil {
return l, nil
}
}
return chronograf.Layout{}, err
}
// Delete the dashboard from the store. Searches through all stores to find Layout and
// then deletes from that store.
func (s *Layouts) Delete(ctx context.Context, layout chronograf.Layout) error {
var err error
for _, store := range s.Stores {
err = store.Delete(ctx, layout)
if err == nil {
return nil
}
}
return err
}
// Get retrieves Layout if `ID` exists. Searches through each store sequentially until success.
func (s *Layouts) Get(ctx context.Context, ID string) (chronograf.Layout, error) {
var err error
@ -80,15 +54,3 @@ func (s *Layouts) Get(ctx context.Context, ID string) (chronograf.Layout, error)
}
return chronograf.Layout{}, err
}
// Update the dashboard in the store. Searches through each store sequentially until success.
func (s *Layouts) Update(ctx context.Context, layout chronograf.Layout) error {
var err error
for _, store := range s.Stores {
err = store.Update(ctx, layout)
if err == nil {
return nil
}
}
return err
}

View File

@ -125,5 +125,4 @@ func (multi *OrganizationsStore) DefaultOrganization(ctx context.Context) (*chro
errors = append(errors, err.Error())
}
return nil, fmt.Errorf("Unknown error while getting default organization: %s", strings.Join(errors, " "))
}

View File

@ -2,7 +2,6 @@ package multistore
import (
"context"
"fmt"
"github.com/influxdata/chronograf"
)
@ -55,18 +54,3 @@ func (s *Protoboards) Get(ctx context.Context, ID string) (chronograf.Protoboard
}
return chronograf.Protoboard{}, err
}
// Add creates a new protoboard in the protoboardsStore.
func (s *Protoboards) Add(ctx context.Context, protoboard chronograf.Protoboard) (chronograf.Protoboard, error) {
return chronograf.Protoboard{}, fmt.Errorf("Add to multistore/protoboards not supported")
}
// Delete the protoboard from the store.
func (s *Protoboards) Delete(ctx context.Context, protoboard chronograf.Protoboard) error {
return fmt.Errorf("Delete to multistore/protoboards not supported")
}
// Update the protoboard in the store.
func (s *Protoboards) Update(ctx context.Context, protoboard chronograf.Protoboard) error {
return fmt.Errorf("Update to multistore/protoboards not supported")
}