Merge branch 'feature/dashboard-filestore' of github.com:influxdata/chronograf into feature/dashboard-filestore
commit
bbc792ac6e
|
@ -2,7 +2,6 @@ package bolt_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
@ -303,7 +302,6 @@ func TestUsersStore_Delete(t *testing.T) {
|
||||||
if tt.addFirst {
|
if tt.addFirst {
|
||||||
var err error
|
var err error
|
||||||
tt.args.user, err = s.Add(tt.args.ctx, tt.args.user)
|
tt.args.user, err = s.Add(tt.args.ctx, tt.args.user)
|
||||||
fmt.Println(err)
|
|
||||||
}
|
}
|
||||||
if err := s.Delete(tt.args.ctx, tt.args.user); (err != nil) != tt.wantErr {
|
if err := s.Delete(tt.args.ctx, tt.args.user); (err != nil) != tt.wantErr {
|
||||||
t.Errorf("%q. UsersStore.Delete() error = %v, wantErr %v", tt.name, err, tt.wantErr)
|
t.Errorf("%q. UsersStore.Delete() error = %v, wantErr %v", tt.name, err, tt.wantErr)
|
||||||
|
|
|
@ -17,6 +17,8 @@ const (
|
||||||
ErrUserNotFound = Error("user not found")
|
ErrUserNotFound = Error("user not found")
|
||||||
ErrLayoutInvalid = Error("layout is invalid")
|
ErrLayoutInvalid = Error("layout is invalid")
|
||||||
ErrDashboardInvalid = Error("dashboard is invalid")
|
ErrDashboardInvalid = Error("dashboard is invalid")
|
||||||
|
ErrSourceInvalid = Error("source is invalid")
|
||||||
|
ErrServerInvalid = Error("server is invalid")
|
||||||
ErrAlertNotFound = Error("alert not found")
|
ErrAlertNotFound = Error("alert not found")
|
||||||
ErrAuthentication = Error("user not authenticated")
|
ErrAuthentication = Error("user not authenticated")
|
||||||
ErrUninitialized = Error("client uninitialized. Call Open() method")
|
ErrUninitialized = Error("client uninitialized. Call Open() method")
|
||||||
|
|
|
@ -0,0 +1,184 @@
|
||||||
|
package filestore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/influxdata/chronograf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KapExt is the the file extension searched for in the directory for kapacitor files
|
||||||
|
const KapExt = ".kap"
|
||||||
|
|
||||||
|
var _ chronograf.ServersStore = &Kapacitors{}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
Logger chronograf.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKapacitors constructs a kapacitor store wrapping a file system directory
|
||||||
|
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,
|
||||||
|
Logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
kapacitors := []chronograf.Server{}
|
||||||
|
for _, file := range files {
|
||||||
|
if path.Ext(file.Name()) != KapExt {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var kapacitor chronograf.Server
|
||||||
|
if err := d.Load(path.Join(d.Dir, file.Name()), &kapacitor); err != nil {
|
||||||
|
continue // We want to load all files we can.
|
||||||
|
} else {
|
||||||
|
kapacitors = append(kapacitors, kapacitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
if err == chronograf.ErrServerNotFound {
|
||||||
|
d.Logger.
|
||||||
|
WithField("component", "kapacitor").
|
||||||
|
WithField("name", file).
|
||||||
|
Error("Unable to read file")
|
||||||
|
} else if err == chronograf.ErrServerInvalid {
|
||||||
|
d.Logger.
|
||||||
|
WithField("component", "kapacitor").
|
||||||
|
WithField("name", file).
|
||||||
|
Error("File is not a kapacitor")
|
||||||
|
}
|
||||||
|
return chronograf.Server{}, err
|
||||||
|
}
|
||||||
|
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
|
||||||
|
// to try to find the name of the file through matching the ID in the kapacitor
|
||||||
|
// content with the ID passed.
|
||||||
|
files, err := d.ReadDir(d.Dir)
|
||||||
|
if err != nil {
|
||||||
|
return chronograf.Server{}, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range files {
|
||||||
|
if path.Ext(f.Name()) != KapExt {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
file := path.Join(d.Dir, f.Name())
|
||||||
|
var kapacitor chronograf.Server
|
||||||
|
if err := d.Load(file, &kapacitor); err != nil {
|
||||||
|
return chronograf.Server{}, "", err
|
||||||
|
}
|
||||||
|
if kapacitor.ID == id {
|
||||||
|
return kapacitor, file, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return chronograf.Server{}, "", chronograf.ErrServerNotFound
|
||||||
|
}
|
|
@ -0,0 +1,184 @@
|
||||||
|
package filestore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/influxdata/chronograf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SrcExt is the the file extension searched for in the directory for source files
|
||||||
|
const SrcExt = ".src"
|
||||||
|
|
||||||
|
var _ chronograf.SourcesStore = &Sources{}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
Logger chronograf.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSources constructs a source store wrapping a file system directory
|
||||||
|
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,
|
||||||
|
Logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sources := []chronograf.Source{}
|
||||||
|
for _, file := range files {
|
||||||
|
if path.Ext(file.Name()) != SrcExt {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var source chronograf.Source
|
||||||
|
if err := d.Load(path.Join(d.Dir, file.Name()), &source); err != nil {
|
||||||
|
continue // We want to load all files we can.
|
||||||
|
} else {
|
||||||
|
sources = append(sources, source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
if err == chronograf.ErrSourceNotFound {
|
||||||
|
d.Logger.
|
||||||
|
WithField("component", "source").
|
||||||
|
WithField("name", file).
|
||||||
|
Error("Unable to read file")
|
||||||
|
} else if err == chronograf.ErrSourceInvalid {
|
||||||
|
d.Logger.
|
||||||
|
WithField("component", "source").
|
||||||
|
WithField("name", file).
|
||||||
|
Error("File is not a source")
|
||||||
|
}
|
||||||
|
return chronograf.Source{}, err
|
||||||
|
}
|
||||||
|
return board, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update replaces a source from the file system directory
|
||||||
|
func (d *Sources) Update(ctx context.Context, source chronograf.Source) error {
|
||||||
|
board, _, err := d.idToFile(source.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := d.Delete(ctx, board); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
file := sourceFile(d.Dir, source)
|
||||||
|
return d.Create(file, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
// idToFile takes an id and finds the associated filename
|
||||||
|
func (d *Sources) idToFile(id int) (chronograf.Source, string, error) {
|
||||||
|
// Because the entire source information is not known at this point, we need
|
||||||
|
// to try to find the name of the file through matching the ID in the source
|
||||||
|
// content with the ID passed.
|
||||||
|
files, err := d.ReadDir(d.Dir)
|
||||||
|
if err != nil {
|
||||||
|
return chronograf.Source{}, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range files {
|
||||||
|
if path.Ext(f.Name()) != SrcExt {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
file := path.Join(d.Dir, f.Name())
|
||||||
|
var source chronograf.Source
|
||||||
|
if err := d.Load(file, &source); err != nil {
|
||||||
|
return chronograf.Source{}, "", err
|
||||||
|
}
|
||||||
|
if source.ID == id {
|
||||||
|
return source, file, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return chronograf.Source{}, "", chronograf.ErrSourceNotFound
|
||||||
|
}
|
|
@ -53,6 +53,256 @@ func TestServer(t *testing.T) {
|
||||||
args args
|
args args
|
||||||
wants wants
|
wants wants
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
name: "GET /sources/5000",
|
||||||
|
subName: "Get specific source; including Canned source",
|
||||||
|
fields: fields{
|
||||||
|
Users: []chronograf.User{
|
||||||
|
{
|
||||||
|
ID: 1, // This is artificial, but should be reflective of the users actual ID
|
||||||
|
Name: "billibob",
|
||||||
|
Provider: "github",
|
||||||
|
Scheme: "oauth2",
|
||||||
|
SuperAdmin: true,
|
||||||
|
Roles: []chronograf.Role{
|
||||||
|
{
|
||||||
|
Name: "admin",
|
||||||
|
Organization: "default",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "viewer",
|
||||||
|
Organization: "howdy", // from canned testdata
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
server: &server.Server{
|
||||||
|
GithubClientID: "not empty",
|
||||||
|
GithubClientSecret: "not empty",
|
||||||
|
},
|
||||||
|
method: "GET",
|
||||||
|
path: "/chronograf/v1/sources/5000",
|
||||||
|
principal: oauth2.Principal{
|
||||||
|
Organization: "howdy",
|
||||||
|
Subject: "billibob",
|
||||||
|
Issuer: "github",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wants: wants{
|
||||||
|
statusCode: 200,
|
||||||
|
body: `
|
||||||
|
{
|
||||||
|
"id": "5000",
|
||||||
|
"name": "Influx 1",
|
||||||
|
"type": "influx-enterprise",
|
||||||
|
"username": "user1",
|
||||||
|
"url": "http://localhost:8086",
|
||||||
|
"metaUrl": "http://metaurl.com",
|
||||||
|
"default": true,
|
||||||
|
"telegraf": "telegraf",
|
||||||
|
"organization": "howdy",
|
||||||
|
"links": {
|
||||||
|
"self": "/chronograf/v1/sources/5000",
|
||||||
|
"kapacitors": "/chronograf/v1/sources/5000/kapacitors",
|
||||||
|
"proxy": "/chronograf/v1/sources/5000/proxy",
|
||||||
|
"queries": "/chronograf/v1/sources/5000/queries",
|
||||||
|
"write": "/chronograf/v1/sources/5000/write",
|
||||||
|
"permissions": "/chronograf/v1/sources/5000/permissions",
|
||||||
|
"users": "/chronograf/v1/sources/5000/users",
|
||||||
|
"roles": "/chronograf/v1/sources/5000/roles",
|
||||||
|
"databases": "/chronograf/v1/sources/5000/dbs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GET /sources/5000/kapacitors/5000",
|
||||||
|
subName: "Get specific kapacitors; including Canned kapacitors",
|
||||||
|
fields: fields{
|
||||||
|
Users: []chronograf.User{
|
||||||
|
{
|
||||||
|
ID: 1, // This is artificial, but should be reflective of the users actual ID
|
||||||
|
Name: "billibob",
|
||||||
|
Provider: "github",
|
||||||
|
Scheme: "oauth2",
|
||||||
|
SuperAdmin: true,
|
||||||
|
Roles: []chronograf.Role{
|
||||||
|
{
|
||||||
|
Name: "admin",
|
||||||
|
Organization: "default",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "viewer",
|
||||||
|
Organization: "howdy", // from canned testdata
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
server: &server.Server{
|
||||||
|
GithubClientID: "not empty",
|
||||||
|
GithubClientSecret: "not empty",
|
||||||
|
},
|
||||||
|
method: "GET",
|
||||||
|
path: "/chronograf/v1/sources/5000/kapacitors/5000",
|
||||||
|
principal: oauth2.Principal{
|
||||||
|
Organization: "howdy",
|
||||||
|
Subject: "billibob",
|
||||||
|
Issuer: "github",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wants: wants{
|
||||||
|
statusCode: 200,
|
||||||
|
body: `
|
||||||
|
{
|
||||||
|
"id": "5000",
|
||||||
|
"name": "Kapa 1",
|
||||||
|
"url": "http://localhost:9092",
|
||||||
|
"active": true,
|
||||||
|
"links": {
|
||||||
|
"proxy": "/chronograf/v1/sources/5000/kapacitors/5000/proxy",
|
||||||
|
"self": "/chronograf/v1/sources/5000/kapacitors/5000",
|
||||||
|
"rules": "/chronograf/v1/sources/5000/kapacitors/5000/rules",
|
||||||
|
"tasks": "/chronograf/v1/sources/5000/kapacitors/5000/proxy?path=/kapacitor/v1/tasks",
|
||||||
|
"ping": "/chronograf/v1/sources/5000/kapacitors/5000/proxy?path=/kapacitor/v1/ping"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GET /sources/5000/kapacitors",
|
||||||
|
subName: "Get all kapacitors; including Canned kapacitors",
|
||||||
|
fields: fields{
|
||||||
|
Users: []chronograf.User{
|
||||||
|
{
|
||||||
|
ID: 1, // This is artificial, but should be reflective of the users actual ID
|
||||||
|
Name: "billibob",
|
||||||
|
Provider: "github",
|
||||||
|
Scheme: "oauth2",
|
||||||
|
SuperAdmin: true,
|
||||||
|
Roles: []chronograf.Role{
|
||||||
|
{
|
||||||
|
Name: "admin",
|
||||||
|
Organization: "default",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "viewer",
|
||||||
|
Organization: "howdy", // from canned testdata
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
server: &server.Server{
|
||||||
|
GithubClientID: "not empty",
|
||||||
|
GithubClientSecret: "not empty",
|
||||||
|
},
|
||||||
|
method: "GET",
|
||||||
|
path: "/chronograf/v1/sources/5000/kapacitors",
|
||||||
|
principal: oauth2.Principal{
|
||||||
|
Organization: "howdy",
|
||||||
|
Subject: "billibob",
|
||||||
|
Issuer: "github",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wants: wants{
|
||||||
|
statusCode: 200,
|
||||||
|
body: `
|
||||||
|
{
|
||||||
|
"kapacitors": [
|
||||||
|
{
|
||||||
|
"id": "5000",
|
||||||
|
"name": "Kapa 1",
|
||||||
|
"url": "http://localhost:9092",
|
||||||
|
"active": true,
|
||||||
|
"links": {
|
||||||
|
"proxy": "/chronograf/v1/sources/5000/kapacitors/5000/proxy",
|
||||||
|
"self": "/chronograf/v1/sources/5000/kapacitors/5000",
|
||||||
|
"rules": "/chronograf/v1/sources/5000/kapacitors/5000/rules",
|
||||||
|
"tasks": "/chronograf/v1/sources/5000/kapacitors/5000/proxy?path=/kapacitor/v1/tasks",
|
||||||
|
"ping": "/chronograf/v1/sources/5000/kapacitors/5000/proxy?path=/kapacitor/v1/ping"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GET /sources",
|
||||||
|
subName: "Get all sources; including Canned sources",
|
||||||
|
fields: fields{
|
||||||
|
Users: []chronograf.User{
|
||||||
|
{
|
||||||
|
ID: 1, // This is artificial, but should be reflective of the users actual ID
|
||||||
|
Name: "billibob",
|
||||||
|
Provider: "github",
|
||||||
|
Scheme: "oauth2",
|
||||||
|
SuperAdmin: true,
|
||||||
|
Roles: []chronograf.Role{
|
||||||
|
{
|
||||||
|
Name: "admin",
|
||||||
|
Organization: "default",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "viewer",
|
||||||
|
Organization: "howdy", // from canned testdata
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
server: &server.Server{
|
||||||
|
GithubClientID: "not empty",
|
||||||
|
GithubClientSecret: "not empty",
|
||||||
|
},
|
||||||
|
method: "GET",
|
||||||
|
path: "/chronograf/v1/sources",
|
||||||
|
principal: oauth2.Principal{
|
||||||
|
Organization: "howdy",
|
||||||
|
Subject: "billibob",
|
||||||
|
Issuer: "github",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wants: wants{
|
||||||
|
statusCode: 200,
|
||||||
|
body: `
|
||||||
|
{
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"id": "5000",
|
||||||
|
"name": "Influx 1",
|
||||||
|
"type": "influx-enterprise",
|
||||||
|
"username": "user1",
|
||||||
|
"url": "http://localhost:8086",
|
||||||
|
"metaUrl": "http://metaurl.com",
|
||||||
|
"default": true,
|
||||||
|
"telegraf": "telegraf",
|
||||||
|
"organization": "howdy",
|
||||||
|
"links": {
|
||||||
|
"self": "/chronograf/v1/sources/5000",
|
||||||
|
"kapacitors": "/chronograf/v1/sources/5000/kapacitors",
|
||||||
|
"proxy": "/chronograf/v1/sources/5000/proxy",
|
||||||
|
"queries": "/chronograf/v1/sources/5000/queries",
|
||||||
|
"write": "/chronograf/v1/sources/5000/write",
|
||||||
|
"permissions": "/chronograf/v1/sources/5000/permissions",
|
||||||
|
"users": "/chronograf/v1/sources/5000/users",
|
||||||
|
"roles": "/chronograf/v1/sources/5000/roles",
|
||||||
|
"databases": "/chronograf/v1/sources/5000/dbs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "GET /organizations",
|
name: "GET /organizations",
|
||||||
subName: "Get all organizations; including Canned organization",
|
subName: "Get all organizations; including Canned organization",
|
||||||
|
@ -165,7 +415,7 @@ func TestServer(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "GET /dashboards/1000",
|
name: "GET /dashboards/1000",
|
||||||
subName: "Get specific in the default organization; Using Canned testdata",
|
subName: "Get specific in the howdy organization; Using Canned testdata",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
Users: []chronograf.User{
|
Users: []chronograf.User{
|
||||||
{
|
{
|
||||||
|
@ -177,7 +427,7 @@ func TestServer(t *testing.T) {
|
||||||
Roles: []chronograf.Role{
|
Roles: []chronograf.Role{
|
||||||
{
|
{
|
||||||
Name: "admin",
|
Name: "admin",
|
||||||
Organization: "default",
|
Organization: "howdy",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -191,7 +441,7 @@ func TestServer(t *testing.T) {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
path: "/chronograf/v1/dashboards/1000",
|
path: "/chronograf/v1/dashboards/1000",
|
||||||
principal: oauth2.Principal{
|
principal: oauth2.Principal{
|
||||||
Organization: "default",
|
Organization: "howdy",
|
||||||
Subject: "billibob",
|
Subject: "billibob",
|
||||||
Issuer: "github",
|
Issuer: "github",
|
||||||
},
|
},
|
||||||
|
@ -387,7 +637,7 @@ func TestServer(t *testing.T) {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"name": "Name This Dashboard",
|
"name": "Name This Dashboard",
|
||||||
"organization": "default",
|
"organization": "howdy",
|
||||||
"links": {
|
"links": {
|
||||||
"self": "/chronograf/v1/dashboards/1000",
|
"self": "/chronograf/v1/dashboards/1000",
|
||||||
"cells": "/chronograf/v1/dashboards/1000/cells",
|
"cells": "/chronograf/v1/dashboards/1000/cells",
|
||||||
|
@ -398,7 +648,7 @@ func TestServer(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "GET /dashboards",
|
name: "GET /dashboards",
|
||||||
subName: "Get all dashboards in the default organization; Using Canned testdata",
|
subName: "Get all dashboards in the howdy organization; Using Canned testdata",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
Users: []chronograf.User{
|
Users: []chronograf.User{
|
||||||
{
|
{
|
||||||
|
@ -412,6 +662,10 @@ func TestServer(t *testing.T) {
|
||||||
Name: "admin",
|
Name: "admin",
|
||||||
Organization: "default",
|
Organization: "default",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "admin",
|
||||||
|
Organization: "howdy",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -424,7 +678,7 @@ func TestServer(t *testing.T) {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
path: "/chronograf/v1/dashboards",
|
path: "/chronograf/v1/dashboards",
|
||||||
principal: oauth2.Principal{
|
principal: oauth2.Principal{
|
||||||
Organization: "default",
|
Organization: "howdy",
|
||||||
Subject: "billibob",
|
Subject: "billibob",
|
||||||
Issuer: "github",
|
Issuer: "github",
|
||||||
},
|
},
|
||||||
|
@ -622,7 +876,7 @@ func TestServer(t *testing.T) {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"name": "Name This Dashboard",
|
"name": "Name This Dashboard",
|
||||||
"organization": "default",
|
"organization": "howdy",
|
||||||
"links": {
|
"links": {
|
||||||
"self": "/chronograf/v1/dashboards/1000",
|
"self": "/chronograf/v1/dashboards/1000",
|
||||||
"cells": "/chronograf/v1/dashboards/1000/cells",
|
"cells": "/chronograf/v1/dashboards/1000/cells",
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"id": 5000,
|
||||||
|
"srcID": 5000,
|
||||||
|
"name": "Kapa 1",
|
||||||
|
"url": "http://localhost:9092",
|
||||||
|
"active": true,
|
||||||
|
"organization": "howdy"
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"id": "5000",
|
||||||
|
"name": "Influx 1",
|
||||||
|
"username": "user1",
|
||||||
|
"password": "pass1",
|
||||||
|
"url": "http://localhost:8086",
|
||||||
|
"metaUrl": "http://metaurl.com",
|
||||||
|
"type": "influx-enterprise",
|
||||||
|
"insecureSkipVerify": false,
|
||||||
|
"default": true,
|
||||||
|
"telegraf": "telegraf",
|
||||||
|
"sharedSecret": "cubeapples",
|
||||||
|
"organization": "howdy"
|
||||||
|
}
|
|
@ -181,5 +181,5 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"name": "Name This Dashboard",
|
"name": "Name This Dashboard",
|
||||||
"organization": "default"
|
"organization": "howdy"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/influxdata/chronograf"
|
"github.com/influxdata/chronograf"
|
||||||
"github.com/influxdata/chronograf/canned"
|
"github.com/influxdata/chronograf/canned"
|
||||||
"github.com/influxdata/chronograf/filestore"
|
"github.com/influxdata/chronograf/filestore"
|
||||||
|
@ -82,11 +84,19 @@ type MultiSourceBuilder struct {
|
||||||
InfluxDBURL string
|
InfluxDBURL string
|
||||||
InfluxDBUsername string
|
InfluxDBUsername string
|
||||||
InfluxDBPassword string
|
InfluxDBPassword string
|
||||||
|
|
||||||
|
Logger chronograf.Logger
|
||||||
|
ID chronograf.ID
|
||||||
|
Path string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build will return a MultiSourceStore
|
// Build will return a MultiSourceStore
|
||||||
func (fs *MultiSourceBuilder) Build(db chronograf.SourcesStore) (*multistore.SourcesStore, error) {
|
func (fs *MultiSourceBuilder) Build(db chronograf.SourcesStore) (*multistore.SourcesStore, error) {
|
||||||
stores := []chronograf.SourcesStore{db}
|
// These dashboards are those handled from a directory
|
||||||
|
files := filestore.NewSources(fs.Path, fs.ID, fs.Logger)
|
||||||
|
xs, err := files.All(context.Background())
|
||||||
|
|
||||||
|
stores := []chronograf.SourcesStore{db, files}
|
||||||
|
|
||||||
if fs.InfluxDBURL != "" {
|
if fs.InfluxDBURL != "" {
|
||||||
influxStore := &memdb.SourcesStore{
|
influxStore := &memdb.SourcesStore{
|
||||||
|
@ -118,11 +128,19 @@ type MultiKapacitorBuilder struct {
|
||||||
KapacitorURL string
|
KapacitorURL string
|
||||||
KapacitorUsername string
|
KapacitorUsername string
|
||||||
KapacitorPassword string
|
KapacitorPassword string
|
||||||
|
|
||||||
|
Logger chronograf.Logger
|
||||||
|
ID chronograf.ID
|
||||||
|
Path string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build will return a multistore facade KapacitorStore over memdb and bolt
|
// Build will return a multistore facade KapacitorStore over memdb and bolt
|
||||||
func (builder *MultiKapacitorBuilder) Build(db chronograf.ServersStore) (*multistore.KapacitorStore, error) {
|
func (builder *MultiKapacitorBuilder) Build(db chronograf.ServersStore) (*multistore.KapacitorStore, error) {
|
||||||
stores := []chronograf.ServersStore{db}
|
// These dashboards are those handled from a directory
|
||||||
|
files := filestore.NewKapacitors(builder.Path, builder.ID, builder.Logger)
|
||||||
|
|
||||||
|
stores := []chronograf.ServersStore{db, files}
|
||||||
|
|
||||||
if builder.KapacitorURL != "" {
|
if builder.KapacitorURL != "" {
|
||||||
memStore := &memdb.KapacitorStore{
|
memStore := &memdb.KapacitorStore{
|
||||||
Kapacitor: &chronograf.Server{
|
Kapacitor: &chronograf.Server{
|
||||||
|
|
|
@ -294,11 +294,17 @@ func (s *Server) newBuilders(logger chronograf.Logger) builders {
|
||||||
InfluxDBURL: s.InfluxDBURL,
|
InfluxDBURL: s.InfluxDBURL,
|
||||||
InfluxDBUsername: s.InfluxDBUsername,
|
InfluxDBUsername: s.InfluxDBUsername,
|
||||||
InfluxDBPassword: s.InfluxDBPassword,
|
InfluxDBPassword: s.InfluxDBPassword,
|
||||||
|
Logger: logger,
|
||||||
|
ID: idgen.NewTime(),
|
||||||
|
Path: s.CannedPath,
|
||||||
},
|
},
|
||||||
Kapacitors: &MultiKapacitorBuilder{
|
Kapacitors: &MultiKapacitorBuilder{
|
||||||
KapacitorURL: s.KapacitorURL,
|
KapacitorURL: s.KapacitorURL,
|
||||||
KapacitorUsername: s.KapacitorUsername,
|
KapacitorUsername: s.KapacitorUsername,
|
||||||
KapacitorPassword: s.KapacitorPassword,
|
KapacitorPassword: s.KapacitorPassword,
|
||||||
|
Logger: logger,
|
||||||
|
ID: idgen.NewTime(),
|
||||||
|
Path: s.CannedPath,
|
||||||
},
|
},
|
||||||
Organizations: &MultiOrganizationBuilder{
|
Organizations: &MultiOrganizationBuilder{
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
|
|
|
@ -2,7 +2,6 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
@ -341,7 +340,6 @@ func TestStore_OrganizationsAdd(t *testing.T) {
|
||||||
fields: fields{
|
fields: fields{
|
||||||
OrganizationsStore: &mocks.OrganizationsStore{
|
OrganizationsStore: &mocks.OrganizationsStore{
|
||||||
GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) {
|
GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) {
|
||||||
fmt.Println(*q.ID)
|
|
||||||
return &chronograf.Organization{
|
return &chronograf.Organization{
|
||||||
ID: "22",
|
ID: "22",
|
||||||
Name: "my sweet name",
|
Name: "my sweet name",
|
||||||
|
|
Loading…
Reference in New Issue