From d470723fafa942e112d7fa3d184d1020a3805d43 Mon Sep 17 00:00:00 2001 From: greg linton Date: Thu, 16 Jan 2020 14:09:56 -0700 Subject: [PATCH] chore: tidy and document filestore functionality --- chronograf.go | 6 - filestore/apps.go | 111 +++------------- filestore/apps_test.go | 199 ++--------------------------- filestore/dashboards.go | 150 ++++++---------------- filestore/environ.go | 24 ---- filestore/environ_test.go | 29 ----- filestore/filestore.go | 100 +++++++++++++++ filestore/kapacitors.go | 123 ++++++------------ filestore/organizations.go | 60 +++++---- filestore/protoboards.go | 3 + filestore/sources.go | 99 +++++---------- filestore/templates.go | 28 ----- filestore/templates_test.go | 23 ++++ kv/bolt/build_test.go | 5 - kv/bolt/client.go | 10 -- kv/kv_test.go | 5 - kv/layouts.go | 63 ---------- kv/layouts_test.go | 244 ++++-------------------------------- memdb/kapacitors.go | 6 + mocks/layouts.go | 19 +-- multistore/layouts.go | 38 ------ multistore/organizations.go | 1 - multistore/protoboards.go | 16 --- 23 files changed, 318 insertions(+), 1044 deletions(-) delete mode 100644 filestore/environ.go delete mode 100644 filestore/environ_test.go create mode 100644 filestore/filestore.go delete mode 100644 filestore/templates.go diff --git a/chronograf.go b/chronograf.go index a09e0b95a..289151901 100644 --- a/chronograf.go +++ b/chronograf.go @@ -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 diff --git a/filestore/apps.go b/filestore/apps.go index f9bfac7e7..93af235f6 100644 --- a/filestore/apps.go +++ b/filestore/apps.go @@ -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 diff --git a/filestore/apps_test.go b/filestore/apps_test.go index c3e2351ed..f6a1bb5b3 100644 --- a/filestore/apps_test.go +++ b/filestore/apps_test.go @@ -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 +} diff --git a/filestore/dashboards.go b/filestore/dashboards.go index 9b8fc6578..02dd3b4d7 100644 --- a/filestore/dashboards.go +++ b/filestore/dashboards.go @@ -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") +} diff --git a/filestore/environ.go b/filestore/environ.go deleted file mode 100644 index 091e179e8..000000000 --- a/filestore/environ.go +++ /dev/null @@ -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 -} diff --git a/filestore/environ_test.go b/filestore/environ_test.go deleted file mode 100644 index 689484806..000000000 --- a/filestore/environ_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/filestore/filestore.go b/filestore/filestore.go new file mode 100644 index 000000000..e71219cfc --- /dev/null +++ b/filestore/filestore.go @@ -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 +} diff --git a/filestore/kapacitors.go b/filestore/kapacitors.go index 1e0ec1ea8..4be14bc80 100644 --- a/filestore/kapacitors.go +++ b/filestore/kapacitors.go @@ -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") +} diff --git a/filestore/organizations.go b/filestore/organizations.go index 6231a9696..29917f50e 100644 --- a/filestore/organizations.go +++ b/filestore/organizations.go @@ -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") +} diff --git a/filestore/protoboards.go b/filestore/protoboards.go index 9f564b84a..709cecab4 100644 --- a/filestore/protoboards.go +++ b/filestore/protoboards.go @@ -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 diff --git a/filestore/sources.go b/filestore/sources.go index 09ce0a2a4..9c5f9b14a 100644 --- a/filestore/sources.go +++ b/filestore/sources.go @@ -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") +} diff --git a/filestore/templates.go b/filestore/templates.go deleted file mode 100644 index fc0e1ffc4..000000000 --- a/filestore/templates.go +++ /dev/null @@ -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...) -} diff --git a/filestore/templates_test.go b/filestore/templates_test.go index 5d5b82f5d..8e5428ba8 100644 --- a/filestore/templates_test.go +++ b/filestore/templates_test.go @@ -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) + } + }) + } +} diff --git a/kv/bolt/build_test.go b/kv/bolt/build_test.go index 5fe149e3b..a9d768212 100644 --- a/kv/bolt/build_test.go +++ b/kv/bolt/build_test.go @@ -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 diff --git a/kv/bolt/client.go b/kv/bolt/client.go index 832e87326..20dd00fab 100644 --- a/kv/bolt/client.go +++ b/kv/bolt/client.go @@ -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) { diff --git a/kv/kv_test.go b/kv/kv_test.go index 1b77ed830..20487cd88 100644 --- a/kv/kv_test.go +++ b/kv/kv_test.go @@ -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 { diff --git a/kv/layouts.go b/kv/layouts.go index 4709e6623..d89037af8 100644 --- a/kv/layouts.go +++ b/kv/layouts.go @@ -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 -} diff --git a/kv/layouts_test.go b/kv/layouts_test.go index 75da5a3a0..2e275d729 100644 --- a/kv/layouts_test.go +++ b/kv/layouts_test.go @@ -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 - } - }) - } } diff --git a/memdb/kapacitors.go b/memdb/kapacitors.go index f9440d19b..d5e9b38bc 100644 --- a/memdb/kapacitors.go +++ b/memdb/kapacitors.go @@ -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 ( diff --git a/mocks/layouts.go b/mocks/layouts.go index 9a8eea319..b90c192c8 100644 --- a/mocks/layouts.go +++ b/mocks/layouts.go @@ -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) -} diff --git a/multistore/layouts.go b/multistore/layouts.go index 7f002243e..613f42105 100644 --- a/multistore/layouts.go +++ b/multistore/layouts.go @@ -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 -} diff --git a/multistore/organizations.go b/multistore/organizations.go index 7f0ea0b71..1e14b8c13 100644 --- a/multistore/organizations.go +++ b/multistore/organizations.go @@ -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, " ")) - } diff --git a/multistore/protoboards.go b/multistore/protoboards.go index a778b0405..4d0b997c0 100644 --- a/multistore/protoboards.go +++ b/multistore/protoboards.go @@ -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") -}