diff --git a/Makefile b/Makefile index 321739e68..4d637906d 100644 --- a/Makefile +++ b/Makefile @@ -46,7 +46,7 @@ docker: dep assets docker-${BINARY} assets: .jssrc .bindata -.bindata: server/swagger_gen.go canned/bin_gen.go dist/dist_gen.go +.bindata: server/swagger_gen.go canned/bin_gen.go protoboards/bin_gen.go dist/dist_gen.go @touch .bindata dist/dist_gen.go: $(UISOURCES) @@ -57,6 +57,9 @@ server/swagger_gen.go: server/swagger.json canned/bin_gen.go: canned/*.json go generate -x ./canned + +protoboards/bin_gen.go: protoboards/*.json + go generate -x ./protoboards .jssrc: $(UISOURCES) cd ui && yarn run clean && yarn run build @@ -112,7 +115,7 @@ clean: if [ -f ${BINARY} ] ; then rm ${BINARY} ; fi cd ui && yarn run clean cd ui && rm -rf node_modules - rm -f dist/dist_gen.go canned/bin_gen.go server/swagger_gen.go + rm -f dist/dist_gen.go canned/bin_gen.go protoboards/bin_gen.go server/swagger_gen.go @rm -f .godep .jsdep .jssrc .bindata ctags: diff --git a/multistore/protoboards.go b/multistore/protoboards.go new file mode 100644 index 000000000..a778b0405 --- /dev/null +++ b/multistore/protoboards.go @@ -0,0 +1,72 @@ +package multistore + +import ( + "context" + "fmt" + + "github.com/influxdata/chronograf" +) + +// Protoboards is a ProtoboardsStore that contains multiple ProtoboardsStores +// The All method will return the set of all Protoboards. +// Each method will be tried against the Stores slice serially. +type Protoboards struct { + Stores []chronograf.ProtoboardsStore +} + +// All returns the set of all protoboards +func (s *Protoboards) All(ctx context.Context) ([]chronograf.Protoboard, error) { + all := []chronograf.Protoboard{} + protoboardSet := map[string]chronograf.Protoboard{} + ok := false + var err error + for _, store := range s.Stores { + var protoboards []chronograf.Protoboard + protoboards, err = store.All(ctx) + if err != nil { + // Try to load as many protoboards as possible + continue + } + ok = true + for _, l := range protoboards { + // Enforce that the protoboard has a unique ID + // If the protoboard has been seen before then skip + if _, okay := protoboardSet[l.ID]; !okay { + protoboardSet[l.ID] = l + all = append(all, l) + } + } + } + if !ok { + return nil, err + } + return all, nil +} + +// Get retrieves protoboard if `ID` exists. Searches through each store sequentially until success. +func (s *Protoboards) Get(ctx context.Context, ID string) (chronograf.Protoboard, error) { + var err error + for _, store := range s.Stores { + var l chronograf.Protoboard + l, err = store.Get(ctx, ID) + if err == nil { + return l, nil + } + } + 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") +} diff --git a/protoboards/bin.go b/protoboards/bin.go new file mode 100644 index 000000000..9b57536b2 --- /dev/null +++ b/protoboards/bin.go @@ -0,0 +1,83 @@ +package protoboards + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/influxdata/chronograf" +) + +//go:generate go-bindata -o bin_gen.go -ignore README|apps|.sh|go -pkg protoboards . + +// BinProtoboardsStore represents a protoboards store using data generated by go-bindata +type BinProtoboardsStore struct { + Logger chronograf.Logger +} + +// All returns the set of all protoboards +func (s *BinProtoboardsStore) All(ctx context.Context) ([]chronograf.Protoboard, error) { + names := AssetNames() + protoboards := make([]chronograf.Protoboard, len(names)) + for i, name := range names { + octets, err := Asset(name) + if err != nil { + s.Logger. + WithField("component", "protoboards"). + WithField("name", name). + Error("Invalid protoboard: ", err) + return nil, chronograf.ErrProtoboardInvalid + } + + var protoboard chronograf.Protoboard + if err = json.Unmarshal(octets, &protoboard); err != nil { + s.Logger. + WithField("component", "protoboards"). + WithField("name", name). + Error("Unable to read protoboard:", err) + return nil, chronograf.ErrProtoboardInvalid + } + protoboards[i] = protoboard + } + + return protoboards, nil +} + +// Add is not support by BinProtoboardsStore +func (s *BinProtoboardsStore) Add(ctx context.Context, protoboard chronograf.Protoboard) (chronograf.Protoboard, error) { + return chronograf.Protoboard{}, fmt.Errorf("Add to BinProtoboardsStore not supported") +} + +// Delete is not support by BinProtoboardsStore +func (s *BinProtoboardsStore) Delete(ctx context.Context, protoboard chronograf.Protoboard) error { + return fmt.Errorf("Delete to BinProtoboardsStore not supported") +} + +// Get retrieves protoboard if `ID` exists. +func (s *BinProtoboardsStore) Get(ctx context.Context, ID string) (chronograf.Protoboard, error) { + protoboards, err := s.All(ctx) + if err != nil { + s.Logger. + WithField("component", "protoboards"). + WithField("name", ID). + Error("Invalid protoboard: ", err) + return chronograf.Protoboard{}, chronograf.ErrProtoboardInvalid + } + + for _, protoboard := range protoboards { + if protoboard.ID == ID { + return protoboard, nil + } + } + + s.Logger. + WithField("component", "protoboards"). + WithField("name", ID). + Error("protoboard not found") + return chronograf.Protoboard{}, chronograf.ErrProtoboardNotFound +} + +// Update not supported +func (s *BinProtoboardsStore) Update(ctx context.Context, protoboard chronograf.Protoboard) error { + return fmt.Errorf("Update to BinProtoboardsStore not supported") +} diff --git a/server/builders.go b/server/builders.go index b8b0ffad5..fcbc80ae6 100644 --- a/server/builders.go +++ b/server/builders.go @@ -6,6 +6,7 @@ import ( "github.com/influxdata/chronograf/filestore" "github.com/influxdata/chronograf/memdb" "github.com/influxdata/chronograf/multistore" + "github.com/influxdata/chronograf/protoboards" ) // LayoutBuilder is responsible for building Layouts @@ -43,6 +44,40 @@ func (builder *MultiLayoutBuilder) Build(db chronograf.LayoutsStore) (*multistor return layouts, nil } +// ProtoboardsBuilder is responsible for building Protoboards +type ProtoboardsBuilder interface { + Build() (*multistore.Protoboards, error) +} + +// MultiProtoboardsBuilder implements LayoutBuilder and will return a Layouts +type MultiProtoboardsBuilder struct { + Logger chronograf.Logger + UUID chronograf.ID + ProtoboardsPath string +} + +// Build will construct a Layouts of canned and db-backed personalized +// layouts +func (builder *MultiProtoboardsBuilder) Build() (*multistore.Protoboards, error) { + // These apps are those handled from a directory + filesystemPBs := filestore.NewProtoboards(builder.ProtoboardsPath, builder.UUID, builder.Logger) + // These apps are statically compiled into chronograf + binPBs := &protoboards.BinProtoboardsStore{ + Logger: builder.Logger, + } + // Acts as a front-end to both the bolt layouts, filesystem layouts and binary statically compiled layouts. + // The idea here is that these stores form a hierarchy in which each is tried sequentially until + // the operation has success. So, the database is preferred over filesystem over binary data. + protoboards := &multistore.Protoboards{ + Stores: []chronograf.ProtoboardsStore{ + filesystemPBs, + binPBs, + }, + } + + return protoboards, nil +} + // DashboardBuilder is responsible for building dashboards type DashboardBuilder interface { Build(chronograf.DashboardsStore) (*multistore.DashboardsStore, error) diff --git a/server/server.go b/server/server.go index 6bb758bb5..1e4acde31 100644 --- a/server/server.go +++ b/server/server.go @@ -18,7 +18,6 @@ import ( "github.com/influxdata/chronograf" "github.com/influxdata/chronograf/bolt" - "github.com/influxdata/chronograf/filestore" idgen "github.com/influxdata/chronograf/id" "github.com/influxdata/chronograf/influx" clog "github.com/influxdata/chronograf/log" @@ -286,6 +285,7 @@ type builders struct { Kapacitors KapacitorBuilder Dashboards DashboardBuilder Organizations OrganizationBuilder + Protoboards ProtoboardsBuilder } func (s *Server) newBuilders(logger chronograf.Logger) builders { @@ -320,6 +320,11 @@ func (s *Server) newBuilders(logger chronograf.Logger) builders { Logger: logger, Path: s.ResourcesPath, }, + Protoboards: &MultiProtoboardsBuilder{ + Logger: logger, + UUID: &idgen.UUID{}, + ProtoboardsPath: s.ProtoboardsPath, + }, } } @@ -488,7 +493,13 @@ func openService(ctx context.Context, buildInfo chronograf.BuildInfo, boltPath s os.Exit(1) } - protoboards := filestore.NewProtoboards(protoboardsPath, idgen.NewTime(), logger) + protoboards, err := builder.Protoboards.Build() + if err != nil { + logger. + WithField("component", "LayoutsStore"). + Error("Unable to construct a MultiLayoutsStore", err) + os.Exit(1) + } return Service{ TimeSeriesClient: &InfluxClient{},