Add OrganizationConfigStore & refactor org config to be per org
As previously implemented, OrganizationConfig was a global object. This refactor adds the organization id to context for every request, even when auth is disabled, so that org id can be used to get/update an organization config. Along those lines, this also removes OrganizationConfigStore .Initialize and replaces .Get with .FindOrCreate, handling the creation of organization configs upon first attempted access. Co-authored-by: Jared Scheib <jared.scheib@gmail.com>pull/3806/head
parent
ecb5347597
commit
ae475c7ae8
|
@ -33,15 +33,16 @@ type Client struct {
|
|||
Now func() time.Time
|
||||
LayoutIDs chronograf.ID
|
||||
|
||||
BuildStore *BuildStore
|
||||
SourcesStore *SourcesStore
|
||||
ServersStore *ServersStore
|
||||
LayoutsStore *LayoutsStore
|
||||
DashboardsStore *DashboardsStore
|
||||
UsersStore *UsersStore
|
||||
OrganizationsStore *OrganizationsStore
|
||||
ConfigStore *ConfigStore
|
||||
MappingsStore *MappingsStore
|
||||
BuildStore *BuildStore
|
||||
SourcesStore *SourcesStore
|
||||
ServersStore *ServersStore
|
||||
LayoutsStore *LayoutsStore
|
||||
DashboardsStore *DashboardsStore
|
||||
UsersStore *UsersStore
|
||||
OrganizationsStore *OrganizationsStore
|
||||
ConfigStore *ConfigStore
|
||||
MappingsStore *MappingsStore
|
||||
OrganizationConfigStore *OrganizationConfigStore
|
||||
}
|
||||
|
||||
// NewClient initializes all stores
|
||||
|
@ -62,6 +63,7 @@ func NewClient() *Client {
|
|||
c.OrganizationsStore = &OrganizationsStore{client: c}
|
||||
c.ConfigStore = &ConfigStore{client: c}
|
||||
c.MappingsStore = &MappingsStore{client: c}
|
||||
c.OrganizationConfigStore = &OrganizationConfigStore{client: c}
|
||||
return c
|
||||
}
|
||||
|
||||
|
@ -161,6 +163,10 @@ func (c *Client) initialize(ctx context.Context) error {
|
|||
if _, err := tx.CreateBucketIfNotExists(MappingsBucket); err != nil {
|
||||
return err
|
||||
}
|
||||
// Always create OrganizationConfig bucket.
|
||||
if _, err := tx.CreateBucketIfNotExists(OrganizationConfigBucket); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
|
@ -197,6 +203,9 @@ func (c *Client) migrate(ctx context.Context, build chronograf.BuildInfo) error
|
|||
if err := c.MappingsStore.Migrate(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.OrganizationConfigStore.Migrate(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
MigrateAll(c)
|
||||
}
|
||||
|
|
|
@ -749,9 +749,9 @@ func UnmarshalConfigPB(data []byte, c *Config) error {
|
|||
|
||||
// MarshalOrganizationConfig encodes a config to binary protobuf format.
|
||||
func MarshalOrganizationConfig(c *chronograf.OrganizationConfig) ([]byte, error) {
|
||||
var lv chronograf.LogViewerConfig
|
||||
columns := make([]*LogViewerColumn, len(lv.Columns))
|
||||
for i, column := range lv.Columns {
|
||||
columns := make([]*LogViewerColumn, len(c.LogViewer.Columns))
|
||||
|
||||
for i, column := range c.LogViewer.Columns {
|
||||
encodings := make([]*ColumnEncoding, len(column.Encodings))
|
||||
|
||||
for j, e := range column.Encodings {
|
||||
|
@ -768,7 +768,9 @@ func MarshalOrganizationConfig(c *chronograf.OrganizationConfig) ([]byte, error)
|
|||
Encodings: encodings,
|
||||
}
|
||||
}
|
||||
|
||||
return MarshalOrganizationConfigPB(&OrganizationConfig{
|
||||
OrganizationID: c.OrganizationID,
|
||||
LogViewer: &LogViewerConfig{
|
||||
Columns: columns,
|
||||
},
|
||||
|
@ -783,13 +785,22 @@ func MarshalOrganizationConfigPB(c *OrganizationConfig) ([]byte, error) {
|
|||
// UnmarshalOrganizationConfig decodes a config from binary protobuf data.
|
||||
func UnmarshalOrganizationConfig(data []byte, c *chronograf.OrganizationConfig) error {
|
||||
var pb OrganizationConfig
|
||||
|
||||
if err := UnmarshalOrganizationConfigPB(data, &pb); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if pb.OrganizationID == "" {
|
||||
return fmt.Errorf("Organization ID on organization config is nil")
|
||||
}
|
||||
if pb.LogViewer == nil {
|
||||
return fmt.Errorf("Log Viewer config is nil")
|
||||
}
|
||||
if pb.LogViewer.Columns == nil {
|
||||
return fmt.Errorf("Log Viewer config Columns is nil")
|
||||
}
|
||||
|
||||
c.OrganizationID = pb.OrganizationID
|
||||
|
||||
columns := make([]chronograf.LogViewerColumn, len(pb.LogViewer.Columns))
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -215,7 +215,8 @@ message AuthConfig {
|
|||
}
|
||||
|
||||
message OrganizationConfig {
|
||||
LogViewerConfig LogViewer = 1; // LogViewer is the organization configuration for log viewer
|
||||
string OrganizationID = 1; // OrganizationID is the ID of the organization this config belogs to
|
||||
LogViewerConfig LogViewer = 2; // LogViewer is the organization configuration for log viewer
|
||||
}
|
||||
|
||||
message LogViewerConfig {
|
||||
|
|
|
@ -12,145 +12,26 @@ import (
|
|||
// Ensure OrganizationConfigStore implements chronograf.OrganizationConfigStore.
|
||||
var _ chronograf.OrganizationConfigStore = &OrganizationConfigStore{}
|
||||
|
||||
// ConfigBucket is used to store chronograf application state
|
||||
var ConfigBucket = []byte("ConfigV1")
|
||||
// OrganizationConfigBucket is used to store chronograf organization configurations
|
||||
var OrganizationConfigBucket = []byte("OrganizationConfigV1")
|
||||
|
||||
// configID is the boltDB key where the configuration object is stored
|
||||
var configID = []byte("config/v1")
|
||||
|
||||
// OrganizationConfigStore uses bolt to store and retrieve global
|
||||
// application configuration
|
||||
// OrganizationConfigStore uses bolt to store and retrieve organization configurations
|
||||
type OrganizationConfigStore struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
func (s *OrganizationConfigStore) Migrate(ctx context.Context) error {
|
||||
if _, err := s.Get(ctx); err != nil {
|
||||
return s.Initialize(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *OrganizationConfigStore) Initialize(ctx context.Context) error {
|
||||
cfg := chronograf.OrganizationConfig{
|
||||
OrganizationConfig: chronograf.OrganizationConfig{
|
||||
|
||||
LogViewer: chronograf.LogViewer{
|
||||
Columns: []chronograf.LogViewerColumn{
|
||||
{
|
||||
Name: "time",
|
||||
Position: 0,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "hidden",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "severity",
|
||||
Position: 1,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "label",
|
||||
Value: "icon",
|
||||
},
|
||||
{
|
||||
Type: "label",
|
||||
Value: "text",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "timestamp",
|
||||
Position: 2,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "message",
|
||||
Position: 3,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "facility",
|
||||
Position: 4,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "procid",
|
||||
Position: 5,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "displayName",
|
||||
Value: "Proc ID",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "appname",
|
||||
Position: 6,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "displayName",
|
||||
Value: "Application",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "host",
|
||||
Position: 7,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return s.Update(ctx, &cfg)
|
||||
}
|
||||
|
||||
func (s *OrganizationConfigStore) Get(ctx context.Context) (*chronograf.OrganizationConfig, error) {
|
||||
// FindOrCreate gets an OrganizationConfig from the store or creates one if none exists for this organization
|
||||
func (s *OrganizationConfigStore) FindOrCreate(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) {
|
||||
var cfg chronograf.OrganizationConfig
|
||||
err := s.client.db.View(func(tx *bolt.Tx) error {
|
||||
v := tx.Bucket(ConfigBucket).Get(configID)
|
||||
v := tx.Bucket(OrganizationConfigBucket).Get([]byte(orgID))
|
||||
if v == nil {
|
||||
return chronograf.ErrConfigNotFound
|
||||
cfg = newOrganizationConfig(orgID)
|
||||
return nil
|
||||
}
|
||||
return internal.UnmarshalOrganizationConfig(v, &cfg)
|
||||
})
|
||||
|
@ -161,6 +42,7 @@ func (s *OrganizationConfigStore) Get(ctx context.Context) (*chronograf.Organiza
|
|||
return &cfg, nil
|
||||
}
|
||||
|
||||
// Update replaces the OrganizationConfig in the store
|
||||
func (s *OrganizationConfigStore) Update(ctx context.Context, cfg *chronograf.OrganizationConfig) error {
|
||||
if cfg == nil {
|
||||
return fmt.Errorf("config provided was nil")
|
||||
|
@ -168,9 +50,120 @@ func (s *OrganizationConfigStore) Update(ctx context.Context, cfg *chronograf.Or
|
|||
return s.client.db.Update(func(tx *bolt.Tx) error {
|
||||
if v, err := internal.MarshalOrganizationConfig(cfg); err != nil {
|
||||
return err
|
||||
} else if err := tx.Bucket(ConfigBucket).Put(configID, v); err != nil {
|
||||
} else if err := tx.Bucket(OrganizationConfigBucket).Put([]byte(cfg.OrganizationID), v); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func newOrganizationConfig(orgID string) chronograf.OrganizationConfig {
|
||||
return chronograf.OrganizationConfig{
|
||||
OrganizationID: orgID,
|
||||
LogViewer: chronograf.LogViewerConfig{
|
||||
Columns: []chronograf.LogViewerColumn{
|
||||
{
|
||||
Name: "time",
|
||||
Position: 0,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "hidden",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "severity",
|
||||
Position: 1,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "label",
|
||||
Value: "icon",
|
||||
},
|
||||
{
|
||||
Type: "label",
|
||||
Value: "text",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "timestamp",
|
||||
Position: 2,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "message",
|
||||
Position: 3,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "facility",
|
||||
Position: 4,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "procid",
|
||||
Position: 5,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "displayName",
|
||||
Value: "Proc ID",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "appname",
|
||||
Position: 6,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "displayName",
|
||||
Value: "Application",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "host",
|
||||
Position: 7,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,19 +8,29 @@ import (
|
|||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
func TestConfig_Get(t *testing.T) {
|
||||
func TestOrganizationConfig_FindOrCreate(t *testing.T) {
|
||||
type args struct {
|
||||
organizationID string
|
||||
}
|
||||
type wants struct {
|
||||
config *chronograf.OrganizationConfig
|
||||
err error
|
||||
organizationConfig *chronograf.OrganizationConfig
|
||||
err error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
wants wants
|
||||
name string
|
||||
args args
|
||||
addFirst bool
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "Get config",
|
||||
name: "Get non-existent default config from default org",
|
||||
args: args{
|
||||
organizationID: "default",
|
||||
},
|
||||
addFirst: false,
|
||||
wants: wants{
|
||||
config: &chronograf.OrganizationConfig{
|
||||
organizationConfig: &chronograf.OrganizationConfig{
|
||||
OrganizationID: "default",
|
||||
LogViewer: chronograf.LogViewerConfig{
|
||||
Columns: []chronograf.LogViewerColumn{
|
||||
{
|
||||
|
@ -129,64 +139,40 @@ func TestConfig_Get(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
client, err := NewTestClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
s := client.OrganizationConfigStore
|
||||
got, err := s.Get(context.Background())
|
||||
if (tt.wants.err != nil) != (err != nil) {
|
||||
t.Errorf("%q. ConfigStore.Get() error = %v, wantErr %v", tt.name, err, tt.wants.err)
|
||||
continue
|
||||
}
|
||||
if diff := cmp.Diff(got, tt.wants.config); diff != "" {
|
||||
t.Errorf("%q. ConfigStore.Get():\n-got/+want\ndiff %s", tt.name, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfig_Update(t *testing.T) {
|
||||
type args struct {
|
||||
config *chronograf.OrganizationConfig
|
||||
}
|
||||
type wants struct {
|
||||
config *chronograf.OrganizationConfig
|
||||
err error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "Set config",
|
||||
name: "Get non-existent default config from non-default org",
|
||||
args: args{
|
||||
config: &chronograf.OrganizationConfig{
|
||||
organizationID: "1",
|
||||
},
|
||||
addFirst: false,
|
||||
wants: wants{
|
||||
organizationConfig: &chronograf.OrganizationConfig{
|
||||
OrganizationID: "1",
|
||||
LogViewer: chronograf.LogViewerConfig{
|
||||
Columns: []chronograf.LogViewerColumn{
|
||||
{
|
||||
Name: "time",
|
||||
Position: 1,
|
||||
Position: 0,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
Value: "hidden",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "severity",
|
||||
Position: 0,
|
||||
Position: 1,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "label",
|
||||
Value: "icon",
|
||||
},
|
||||
{
|
||||
Type: "label",
|
||||
Value: "text",
|
||||
|
@ -237,7 +223,7 @@ func TestConfig_Update(t *testing.T) {
|
|||
},
|
||||
{
|
||||
Type: "displayName",
|
||||
Value: "Milkshake",
|
||||
Value: "Proc ID",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -269,8 +255,16 @@ func TestConfig_Update(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Get existing/modified config from default org",
|
||||
args: args{
|
||||
organizationID: "default",
|
||||
},
|
||||
addFirst: true,
|
||||
wants: wants{
|
||||
config: &chronograf.OrganizationConfig{
|
||||
organizationConfig: &chronograf.OrganizationConfig{
|
||||
OrganizationID: "default",
|
||||
LogViewer: chronograf.LogViewerConfig{
|
||||
Columns: []chronograf.LogViewerColumn{
|
||||
{
|
||||
|
@ -279,7 +273,7 @@ func TestConfig_Update(t *testing.T) {
|
|||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
Value: "hidden",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -290,7 +284,11 @@ func TestConfig_Update(t *testing.T) {
|
|||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
Value: "hidden",
|
||||
},
|
||||
{
|
||||
Type: "label",
|
||||
Value: "icon",
|
||||
},
|
||||
{
|
||||
Type: "label",
|
||||
|
@ -342,7 +340,124 @@ func TestConfig_Update(t *testing.T) {
|
|||
},
|
||||
{
|
||||
Type: "displayName",
|
||||
Value: "Milkshake",
|
||||
Value: "Proc ID",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "appname",
|
||||
Position: 6,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "displayName",
|
||||
Value: "Application",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "host",
|
||||
Position: 7,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Get existing/modified config from non-default org",
|
||||
args: args{
|
||||
organizationID: "1",
|
||||
},
|
||||
addFirst: true,
|
||||
wants: wants{
|
||||
organizationConfig: &chronograf.OrganizationConfig{
|
||||
OrganizationID: "1",
|
||||
LogViewer: chronograf.LogViewerConfig{
|
||||
Columns: []chronograf.LogViewerColumn{
|
||||
{
|
||||
Name: "time",
|
||||
Position: 1,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "hidden",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "severity",
|
||||
Position: 0,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "hidden",
|
||||
},
|
||||
{
|
||||
Type: "label",
|
||||
Value: "icon",
|
||||
},
|
||||
{
|
||||
Type: "label",
|
||||
Value: "text",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "timestamp",
|
||||
Position: 2,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "message",
|
||||
Position: 3,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "facility",
|
||||
Position: 4,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "procid",
|
||||
Position: 5,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "displayName",
|
||||
Value: "Proc ID",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -384,20 +499,494 @@ func TestConfig_Update(t *testing.T) {
|
|||
defer client.Close()
|
||||
|
||||
s := client.OrganizationConfigStore
|
||||
err = s.Update(context.Background(), tt.args.config)
|
||||
if (tt.wants.err != nil) != (err != nil) {
|
||||
t.Errorf("%q. ConfigStore.Get() error = %v, wantErr %v", tt.name, err, tt.wants.err)
|
||||
continue
|
||||
|
||||
if tt.addFirst {
|
||||
if err := s.Update(context.Background(), tt.wants.organizationConfig); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
got, _ := s.Get(context.Background())
|
||||
got, err := s.FindOrCreate(context.Background(), tt.args.organizationID)
|
||||
|
||||
if (tt.wants.err != nil) != (err != nil) {
|
||||
t.Errorf("%q. ConfigStore.Get() error = %v, wantErr %v", tt.name, err, tt.wants.err)
|
||||
t.Errorf("%q. OrganizationConfigStore.FindOrCreate() error = %v, wantErr %v", tt.name, err, tt.wants.err)
|
||||
continue
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(got, tt.wants.config); diff != "" {
|
||||
t.Errorf("%q. ConfigStore.Get():\n-got/+want\ndiff %s", tt.name, diff)
|
||||
if diff := cmp.Diff(got, tt.wants.organizationConfig); diff != "" {
|
||||
t.Errorf("%q. OrganizationConfigStore.FindOrCreate():\n-got/+want\ndiff %s", tt.name, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrganizationConfig_Update(t *testing.T) {
|
||||
type args struct {
|
||||
organizationConfig *chronograf.OrganizationConfig
|
||||
organizationID string
|
||||
}
|
||||
type wants struct {
|
||||
organizationConfig *chronograf.OrganizationConfig
|
||||
err error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "Set default org config",
|
||||
args: args{
|
||||
organizationConfig: &chronograf.OrganizationConfig{
|
||||
OrganizationID: "default",
|
||||
LogViewer: chronograf.LogViewerConfig{
|
||||
Columns: []chronograf.LogViewerColumn{
|
||||
{
|
||||
Name: "time",
|
||||
Position: 1,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "severity",
|
||||
Position: 0,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "label",
|
||||
Value: "text",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "timestamp",
|
||||
Position: 2,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "message",
|
||||
Position: 3,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "facility",
|
||||
Position: 4,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "procid",
|
||||
Position: 5,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "displayName",
|
||||
Value: "Milkshake",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "appname",
|
||||
Position: 6,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "displayName",
|
||||
Value: "Application",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "host",
|
||||
Position: 7,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
organizationID: "default",
|
||||
},
|
||||
wants: wants{
|
||||
organizationConfig: &chronograf.OrganizationConfig{
|
||||
LogViewer: chronograf.LogViewerConfig{
|
||||
Columns: []chronograf.LogViewerColumn{
|
||||
{
|
||||
Name: "time",
|
||||
Position: 1,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "severity",
|
||||
Position: 0,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "label",
|
||||
Value: "text",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "timestamp",
|
||||
Position: 2,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "message",
|
||||
Position: 3,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "facility",
|
||||
Position: 4,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "procid",
|
||||
Position: 5,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "displayName",
|
||||
Value: "Milkshake",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "appname",
|
||||
Position: 6,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "displayName",
|
||||
Value: "Application",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "host",
|
||||
Position: 7,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
OrganizationID: "default",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Set non-default org config",
|
||||
args: args{
|
||||
organizationConfig: &chronograf.OrganizationConfig{
|
||||
OrganizationID: "1337",
|
||||
LogViewer: chronograf.LogViewerConfig{
|
||||
Columns: []chronograf.LogViewerColumn{
|
||||
{
|
||||
Name: "time",
|
||||
Position: 1,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "severity",
|
||||
Position: 0,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "label",
|
||||
Value: "text",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "timestamp",
|
||||
Position: 2,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "message",
|
||||
Position: 3,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "facility",
|
||||
Position: 4,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "procid",
|
||||
Position: 5,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "displayName",
|
||||
Value: "Milkshake",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "appname",
|
||||
Position: 6,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "displayName",
|
||||
Value: "Application",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "host",
|
||||
Position: 7,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
organizationID: "1337",
|
||||
},
|
||||
wants: wants{
|
||||
organizationConfig: &chronograf.OrganizationConfig{
|
||||
LogViewer: chronograf.LogViewerConfig{
|
||||
Columns: []chronograf.LogViewerColumn{
|
||||
{
|
||||
Name: "time",
|
||||
Position: 1,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "severity",
|
||||
Position: 0,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "label",
|
||||
Value: "text",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "timestamp",
|
||||
Position: 2,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "message",
|
||||
Position: 3,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "facility",
|
||||
Position: 4,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "procid",
|
||||
Position: 5,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "displayName",
|
||||
Value: "Milkshake",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "appname",
|
||||
Position: 6,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "displayName",
|
||||
Value: "Application",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "host",
|
||||
Position: 7,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
OrganizationID: "1337",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
client, err := NewTestClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
s := client.OrganizationConfigStore
|
||||
err = s.Update(context.Background(), tt.args.organizationConfig)
|
||||
if (tt.wants.err != nil) != (err != nil) {
|
||||
t.Errorf("%q. OrganizationConfigStore.Update() error = %v, wantErr %v", tt.name, err, tt.wants.err)
|
||||
continue
|
||||
}
|
||||
|
||||
got, _ := s.FindOrCreate(context.Background(), tt.args.organizationID)
|
||||
if (tt.wants.err != nil) != (err != nil) {
|
||||
t.Errorf("%q. OrganizationConfigStore.Update() error = %v, wantErr %v", tt.name, err, tt.wants.err)
|
||||
continue
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(got, tt.wants.organizationConfig); diff != "" {
|
||||
t.Errorf("%q. OrganizationConfigStore.Update():\n-got/+want\ndiff %s", tt.name, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,35 +9,36 @@ import (
|
|||
|
||||
// General errors.
|
||||
const (
|
||||
ErrUpstreamTimeout = Error("request to backend timed out")
|
||||
ErrSourceNotFound = Error("source not found")
|
||||
ErrServerNotFound = Error("server not found")
|
||||
ErrLayoutNotFound = Error("layout not found")
|
||||
ErrDashboardNotFound = Error("dashboard not found")
|
||||
ErrUserNotFound = Error("user not found")
|
||||
ErrLayoutInvalid = Error("layout is invalid")
|
||||
ErrDashboardInvalid = Error("dashboard is invalid")
|
||||
ErrSourceInvalid = Error("source is invalid")
|
||||
ErrServerInvalid = Error("server is invalid")
|
||||
ErrAlertNotFound = Error("alert not found")
|
||||
ErrAuthentication = Error("user not authenticated")
|
||||
ErrUninitialized = Error("client uninitialized. Call Open() method")
|
||||
ErrInvalidAxis = Error("Unexpected axis in cell. Valid axes are 'x', 'y', and 'y2'")
|
||||
ErrInvalidColorType = Error("Invalid color type. Valid color types are 'min', 'max', 'threshold', 'text', and 'background'")
|
||||
ErrInvalidColor = Error("Invalid color. Accepted color format is #RRGGBB")
|
||||
ErrInvalidLegend = Error("Invalid legend. Both type and orientation must be set")
|
||||
ErrInvalidLegendType = Error("Invalid legend type. Valid legend type is 'static'")
|
||||
ErrInvalidLegendOrient = Error("Invalid orientation type. Valid orientation types are 'top', 'bottom', 'right', 'left'")
|
||||
ErrUserAlreadyExists = Error("user already exists")
|
||||
ErrOrganizationNotFound = Error("organization not found")
|
||||
ErrMappingNotFound = Error("mapping not found")
|
||||
ErrOrganizationAlreadyExists = Error("organization already exists")
|
||||
ErrCannotDeleteDefaultOrganization = Error("cannot delete default organization")
|
||||
ErrConfigNotFound = Error("cannot find configuration")
|
||||
ErrAnnotationNotFound = Error("annotation not found")
|
||||
ErrInvalidCellOptionsText = Error("invalid text wrapping option. Valid wrappings are 'truncate', 'wrap', and 'single line'")
|
||||
ErrInvalidCellOptionsSort = Error("cell options sortby cannot be empty'")
|
||||
ErrInvalidCellOptionsColumns = Error("cell options columns cannot be empty'")
|
||||
ErrUpstreamTimeout = Error("request to backend timed out")
|
||||
ErrSourceNotFound = Error("source not found")
|
||||
ErrServerNotFound = Error("server not found")
|
||||
ErrLayoutNotFound = Error("layout not found")
|
||||
ErrDashboardNotFound = Error("dashboard not found")
|
||||
ErrUserNotFound = Error("user not found")
|
||||
ErrLayoutInvalid = Error("layout is invalid")
|
||||
ErrDashboardInvalid = Error("dashboard is invalid")
|
||||
ErrSourceInvalid = Error("source is invalid")
|
||||
ErrServerInvalid = Error("server is invalid")
|
||||
ErrAlertNotFound = Error("alert not found")
|
||||
ErrAuthentication = Error("user not authenticated")
|
||||
ErrUninitialized = Error("client uninitialized. Call Open() method")
|
||||
ErrInvalidAxis = Error("Unexpected axis in cell. Valid axes are 'x', 'y', and 'y2'")
|
||||
ErrInvalidColorType = Error("Invalid color type. Valid color types are 'min', 'max', 'threshold', 'text', and 'background'")
|
||||
ErrInvalidColor = Error("Invalid color. Accepted color format is #RRGGBB")
|
||||
ErrInvalidLegend = Error("Invalid legend. Both type and orientation must be set")
|
||||
ErrInvalidLegendType = Error("Invalid legend type. Valid legend type is 'static'")
|
||||
ErrInvalidLegendOrient = Error("Invalid orientation type. Valid orientation types are 'top', 'bottom', 'right', 'left'")
|
||||
ErrUserAlreadyExists = Error("user already exists")
|
||||
ErrOrganizationNotFound = Error("organization not found")
|
||||
ErrMappingNotFound = Error("mapping not found")
|
||||
ErrOrganizationAlreadyExists = Error("organization already exists")
|
||||
ErrCannotDeleteDefaultOrganization = Error("cannot delete default organization")
|
||||
ErrConfigNotFound = Error("cannot find configuration")
|
||||
ErrAnnotationNotFound = Error("annotation not found")
|
||||
ErrInvalidCellOptionsText = Error("invalid text wrapping option. Valid wrappings are 'truncate', 'wrap', and 'single line'")
|
||||
ErrInvalidCellOptionsSort = Error("cell options sortby cannot be empty'")
|
||||
ErrInvalidCellOptionsColumns = Error("cell options columns cannot be empty'")
|
||||
ErrOrganizationConfigFindOrCreateFailed = Error("failed to find or create organization config")
|
||||
)
|
||||
|
||||
// Error is a domain error encountered while processing chronograf requests
|
||||
|
@ -760,7 +761,8 @@ type ConfigStore interface {
|
|||
// OrganizationConfig is the organization config for parameters that can
|
||||
// be set via API, with different sections, such as LogViewer
|
||||
type OrganizationConfig struct {
|
||||
LogViewer LogViewerConfig `json:"logViewer"`
|
||||
OrganizationID string `json:"organization"`
|
||||
LogViewer LogViewerConfig `json:"logViewer"`
|
||||
}
|
||||
|
||||
// LogViewerConfig is the configuration settings for the Log Viewer UI
|
||||
|
@ -784,12 +786,10 @@ type ColumnEncoding struct {
|
|||
|
||||
// OrganizationConfigStore is the storage and retrieval of organization Configs
|
||||
type OrganizationConfigStore interface {
|
||||
// Initialize creates the initial configuration
|
||||
Initialize(context.Context) error
|
||||
// Get retrieves the whole Config from the OrganizationConfigStore
|
||||
Get(context.Context) (*Config, error)
|
||||
// Update updates the whole Config in the OrganizationConfigStore
|
||||
Update(context.Context, *Config) error
|
||||
// FindOrCreate gets an existing OrganizationConfig and creates one if none exists
|
||||
FindOrCreate(ctx context.Context, orgID string) (*OrganizationConfig, error)
|
||||
// Update updates the whole organization config in the OrganizationConfigStore
|
||||
Update(context.Context, *OrganizationConfig) error
|
||||
}
|
||||
|
||||
// BuildInfo is sent to the usage client to track versions and commits
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package mocks
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
var _ chronograf.OrganizationConfigStore = &OrganizationConfigStore{}
|
||||
|
||||
type OrganizationConfigStore struct {
|
||||
//InitializeF func(ctx context.Context) error
|
||||
//GetF func(ctx context.Context, id chronograf.OrganizationID) (*chronograf.OrganizationConfig, error)
|
||||
FindOrCreateF func(ctx context.Context, id string) (*chronograf.OrganizationConfig, error)
|
||||
UpdateF func(ctx context.Context, target *chronograf.OrganizationConfig) error
|
||||
}
|
||||
|
||||
func (oc *OrganizationConfigStore) FindOrCreate(ctx context.Context, id string) (*chronograf.OrganizationConfig, error) {
|
||||
return oc.FindOrCreateF(ctx, id)
|
||||
}
|
||||
|
||||
func (oc *OrganizationConfigStore) Update(ctx context.Context, target *chronograf.OrganizationConfig) error {
|
||||
return oc.UpdateF(ctx, target)
|
||||
}
|
|
@ -8,14 +8,15 @@ import (
|
|||
|
||||
// Store is a server.DataStore
|
||||
type Store struct {
|
||||
SourcesStore chronograf.SourcesStore
|
||||
MappingsStore chronograf.MappingsStore
|
||||
ServersStore chronograf.ServersStore
|
||||
LayoutsStore chronograf.LayoutsStore
|
||||
UsersStore chronograf.UsersStore
|
||||
DashboardsStore chronograf.DashboardsStore
|
||||
OrganizationsStore chronograf.OrganizationsStore
|
||||
ConfigStore chronograf.ConfigStore
|
||||
SourcesStore chronograf.SourcesStore
|
||||
MappingsStore chronograf.MappingsStore
|
||||
ServersStore chronograf.ServersStore
|
||||
LayoutsStore chronograf.LayoutsStore
|
||||
UsersStore chronograf.UsersStore
|
||||
DashboardsStore chronograf.DashboardsStore
|
||||
OrganizationsStore chronograf.OrganizationsStore
|
||||
ConfigStore chronograf.ConfigStore
|
||||
OrganizationConfigStore chronograf.OrganizationConfigStore
|
||||
}
|
||||
|
||||
func (s *Store) Sources(ctx context.Context) chronograf.SourcesStore {
|
||||
|
@ -48,3 +49,7 @@ func (s *Store) Dashboards(ctx context.Context) chronograf.DashboardsStore {
|
|||
func (s *Store) Config(ctx context.Context) chronograf.ConfigStore {
|
||||
return s.ConfigStore
|
||||
}
|
||||
|
||||
func (s *Store) OrganizationConfig(ctx context.Context) chronograf.OrganizationConfigStore {
|
||||
return s.OrganizationConfigStore
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package noop
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
// ensure OrganizationConfigStore implements chronograf.OrganizationConfigStore
|
||||
var _ chronograf.OrganizationConfigStore = &OrganizationConfigStore{}
|
||||
|
||||
type OrganizationConfigStore struct{}
|
||||
|
||||
func (s *OrganizationConfigStore) FindOrCreate(context.Context, string) (*chronograf.OrganizationConfig, error) {
|
||||
return nil, chronograf.ErrOrganizationConfigFindOrCreateFailed
|
||||
}
|
||||
|
||||
func (s *OrganizationConfigStore) Update(context.Context, *chronograf.OrganizationConfig) error {
|
||||
return fmt.Errorf("cannot update conifg")
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package organizations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
// ensure that OrganizationConfig implements chronograf.OrganizationConfigStore
|
||||
var _ chronograf.OrganizationConfigStore = &OrganizationConfigStore{}
|
||||
|
||||
// OrganizationConfigStore facade on a OrganizationConfig that filters OrganizationConfigs by organization.
|
||||
type OrganizationConfigStore struct {
|
||||
store chronograf.OrganizationConfigStore
|
||||
organization string
|
||||
}
|
||||
|
||||
// NewOrganizationConfigStore creates a new OrganizationConfigStore from an existing
|
||||
// chronograf.OrganizationConfigStore and an organization string
|
||||
func NewOrganizationConfigStore(s chronograf.OrganizationConfigStore, orgID string) *OrganizationConfigStore {
|
||||
return &OrganizationConfigStore{
|
||||
store: s,
|
||||
organization: orgID,
|
||||
}
|
||||
}
|
||||
|
||||
// FindOrCreate gets an organization's config or creates one if none exists
|
||||
func (s *OrganizationConfigStore) FindOrCreate(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) {
|
||||
var err = validOrganization(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oc, err := s.store.FindOrCreate(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return oc, nil
|
||||
|
||||
}
|
||||
|
||||
// Update the OrganizationConfig in OrganizationConfigStore.
|
||||
func (s *OrganizationConfigStore) Update(ctx context.Context, oc *chronograf.OrganizationConfig) error {
|
||||
err := validOrganization(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = s.store.FindOrCreate(ctx, oc.OrganizationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.store.Update(ctx, oc)
|
||||
}
|
|
@ -95,13 +95,8 @@ func AuthorizedUser(
|
|||
next http.HandlerFunc,
|
||||
) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !useAuth {
|
||||
ctx := r.Context()
|
||||
// If there is no auth, then give the user raw access to the DataStore
|
||||
r = r.WithContext(serverContext(ctx))
|
||||
next(w, r)
|
||||
return
|
||||
}
|
||||
ctx := r.Context()
|
||||
serverCtx := serverContext(ctx)
|
||||
|
||||
log := logger.
|
||||
WithField("component", "role_auth").
|
||||
|
@ -109,8 +104,24 @@ func AuthorizedUser(
|
|||
WithField("method", r.Method).
|
||||
WithField("url", r.URL)
|
||||
|
||||
ctx := r.Context()
|
||||
serverCtx := serverContext(ctx)
|
||||
defaultOrg, err := store.Organizations(serverCtx).DefaultOrganization(serverCtx)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Failed to retrieve the default organization: %v", err))
|
||||
Error(w, http.StatusForbidden, "User is not authorized", logger)
|
||||
return
|
||||
}
|
||||
|
||||
if !useAuth {
|
||||
// If there is no auth, then set the organization id to be the default org id on context
|
||||
// so that calls like hasOrganizationContext as used in Organization Config service
|
||||
// method OrganizationConfig can successfully get the organization id
|
||||
ctx = context.WithValue(ctx, organizations.ContextKey, defaultOrg.ID)
|
||||
|
||||
// And if there is no auth, then give the user raw access to the DataStore
|
||||
r = r.WithContext(serverContext(ctx))
|
||||
next(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
p, err := getValidPrincipal(ctx)
|
||||
if err != nil {
|
||||
|
@ -127,12 +138,6 @@ func AuthorizedUser(
|
|||
|
||||
// This is as if the user was logged into the default organization
|
||||
if p.Organization == "" {
|
||||
defaultOrg, err := store.Organizations(serverCtx).DefaultOrganization(serverCtx)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Failed to retrieve the default organization: %v", err))
|
||||
Error(w, http.StatusForbidden, "User is not authorized", logger)
|
||||
return
|
||||
}
|
||||
p.Organization = defaultOrg.ID
|
||||
}
|
||||
|
||||
|
|
|
@ -63,7 +63,6 @@ func TestAuthorizedToken(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthorizedUser(t *testing.T) {
|
||||
type fields struct {
|
||||
UsersStore chronograf.UsersStore
|
||||
|
@ -102,7 +101,7 @@ func TestAuthorizedUser(t *testing.T) {
|
|||
args: args{
|
||||
useAuth: false,
|
||||
},
|
||||
hasOrganizationContext: false,
|
||||
hasOrganizationContext: true,
|
||||
hasSuperAdminContext: false,
|
||||
hasRoleContext: false,
|
||||
hasServerContext: true,
|
||||
|
@ -1047,6 +1046,11 @@ func TestAuthorizedUser(t *testing.T) {
|
|||
Name: "The ShillBillThrilliettas",
|
||||
}, nil
|
||||
},
|
||||
DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) {
|
||||
return &chronograf.Organization{
|
||||
ID: "0",
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
Logger: clog.New(clog.DebugLevel),
|
||||
},
|
||||
|
|
|
@ -22,17 +22,17 @@ func newOrganizationConfigResponse(config chronograf.OrganizationConfig) *organi
|
|||
}
|
||||
}
|
||||
|
||||
type logViewerOrganizationConfigResponse struct {
|
||||
type logViewerConfigResponse struct {
|
||||
Links selfLinks `json:"links"`
|
||||
chronograf.LogViewerOrganizationConfig
|
||||
chronograf.LogViewerConfig
|
||||
}
|
||||
|
||||
func newAuthConfigResponse(config chronograf.OrganizationConfig) *logViewerOrganizationConfigResponse {
|
||||
return &logViewerOrganizationConfigResponse{
|
||||
func newLogViewerConfigResponse(config chronograf.OrganizationConfig) *logViewerConfigResponse {
|
||||
return &logViewerConfigResponse{
|
||||
Links: selfLinks{
|
||||
Self: "/chronograf/v1/org_config/logviewer",
|
||||
},
|
||||
LogViewerOrganizationConfig: config.LogViewer,
|
||||
LogViewerConfig: config.LogViewer,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,14 +40,20 @@ func newAuthConfigResponse(config chronograf.OrganizationConfig) *logViewerOrgan
|
|||
func (s *Service) OrganizationConfig(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
config, err := s.Store.OrganizationConfig(ctx).Get(ctx)
|
||||
orgID, ok := hasOrganizationContext(ctx)
|
||||
if !ok {
|
||||
Error(w, http.StatusBadRequest, "Organization not found on context", s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
config, err := s.Store.OrganizationConfig(ctx).FindOrCreate(ctx, orgID)
|
||||
if err != nil {
|
||||
Error(w, http.StatusBadRequest, err.Error(), s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
if config == nil {
|
||||
Error(w, http.StatusBadRequest, "Configuration object was nil", s.Logger)
|
||||
Error(w, http.StatusBadRequest, "Organization configuration object was nil", s.Logger)
|
||||
return
|
||||
}
|
||||
res := newOrganizationConfigResponse(*config)
|
||||
|
@ -59,14 +65,20 @@ func (s *Service) OrganizationConfig(w http.ResponseWriter, r *http.Request) {
|
|||
func (s *Service) LogViewerOrganizationConfig(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
config, err := s.Store.OrganizationConfig(ctx).Get(ctx)
|
||||
orgID, ok := hasOrganizationContext(ctx)
|
||||
if !ok {
|
||||
Error(w, http.StatusBadRequest, "Organization not found on context", s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
config, err := s.Store.OrganizationConfig(ctx).FindOrCreate(ctx, orgID)
|
||||
if err != nil {
|
||||
Error(w, http.StatusBadRequest, err.Error(), s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
if config == nil {
|
||||
Error(w, http.StatusBadRequest, "Configuration object was nil", s.Logger)
|
||||
Error(w, http.StatusBadRequest, "Organization configuration object was nil", s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -79,6 +91,12 @@ func (s *Service) LogViewerOrganizationConfig(w http.ResponseWriter, r *http.Req
|
|||
func (s *Service) ReplaceLogViewerOrganizationConfig(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
orgID, ok := hasOrganizationContext(ctx)
|
||||
if !ok {
|
||||
Error(w, http.StatusBadRequest, "Organization not found on context", s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
var logViewerConfig chronograf.LogViewerConfig
|
||||
if err := json.NewDecoder(r.Body).Decode(&logViewerConfig); err != nil {
|
||||
invalidJSON(w, s.Logger)
|
||||
|
@ -89,13 +107,13 @@ func (s *Service) ReplaceLogViewerOrganizationConfig(w http.ResponseWriter, r *h
|
|||
return
|
||||
}
|
||||
|
||||
config, err := s.Store.OrganizationConfig(ctx).Get(ctx)
|
||||
config, err := s.Store.OrganizationConfig(ctx).FindOrCreate(ctx, orgID)
|
||||
if err != nil {
|
||||
Error(w, http.StatusBadRequest, err.Error(), s.Logger)
|
||||
return
|
||||
}
|
||||
if config == nil {
|
||||
Error(w, http.StatusBadRequest, "Configuration object was nil", s.Logger)
|
||||
Error(w, http.StatusBadRequest, "Organization configuration object was nil", s.Logger)
|
||||
return
|
||||
}
|
||||
config.LogViewer = logViewerConfig
|
||||
|
|
|
@ -2,6 +2,7 @@ package server
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http/httptest"
|
||||
|
@ -10,11 +11,15 @@ import (
|
|||
"github.com/influxdata/chronograf"
|
||||
"github.com/influxdata/chronograf/log"
|
||||
"github.com/influxdata/chronograf/mocks"
|
||||
"github.com/influxdata/chronograf/organizations"
|
||||
)
|
||||
|
||||
func TestOrganizationConfig(t *testing.T) {
|
||||
type args struct {
|
||||
organizationID string
|
||||
}
|
||||
type fields struct {
|
||||
OrganizationConfigStore chronograf.OrganizationConfigStore
|
||||
organizationConfigStore chronograf.OrganizationConfigStore
|
||||
}
|
||||
type wants struct {
|
||||
statusCode int
|
||||
|
@ -24,126 +29,138 @@ func TestOrganizationConfig(t *testing.T) {
|
|||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
fields fields
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "Get organization configuration settings",
|
||||
name: "Get organization configuration",
|
||||
args: args{
|
||||
organizationID: "default",
|
||||
},
|
||||
fields: fields{
|
||||
OrganizationConfigStore: &mocks.OrganizationConfigStore{
|
||||
OrganizationConfig: &chronograf.OrganizationConfig{
|
||||
LogViewer: chronograf.LogViewer{
|
||||
Columns: []chronograf.LogViewerColumn{
|
||||
{
|
||||
Name: "time",
|
||||
Position: 0,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
organizationConfigStore: &mocks.OrganizationConfigStore{
|
||||
FindOrCreateF: func(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) {
|
||||
switch orgID {
|
||||
case "default":
|
||||
return &chronograf.OrganizationConfig{
|
||||
OrganizationID: "default",
|
||||
LogViewer: chronograf.LogViewerConfig{
|
||||
Columns: []chronograf.LogViewerColumn{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "hidden",
|
||||
Name: "time",
|
||||
Position: 0,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "hidden",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "severity",
|
||||
Position: 1,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Name: "severity",
|
||||
Position: 1,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "label",
|
||||
Value: "icon",
|
||||
},
|
||||
{
|
||||
Type: "label",
|
||||
Value: "text",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "label",
|
||||
Value: "icon",
|
||||
},
|
||||
{
|
||||
Type: "label",
|
||||
Value: "text",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "timestamp",
|
||||
Position: 2,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
Name: "timestamp",
|
||||
Position: 2,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "message",
|
||||
Position: 3,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Name: "message",
|
||||
Position: 3,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "facility",
|
||||
Position: 4,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Name: "facility",
|
||||
Position: 4,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "procid",
|
||||
Position: 5,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Name: "procid",
|
||||
Position: 5,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "displayName",
|
||||
Value: "Proc ID",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "displayName",
|
||||
Value: "Proc ID",
|
||||
Name: "appname",
|
||||
Position: 6,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "displayName",
|
||||
Value: "Application",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "host",
|
||||
Position: 7,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "appname",
|
||||
Position: 6,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "displayName",
|
||||
Value: "Application",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "host",
|
||||
Position: 7,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
default:
|
||||
return nil, chronograf.ErrOrganizationConfigFindOrCreateFailed
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
statusCode: 200,
|
||||
contentType: "application/json",
|
||||
body: `{"links":{"self":"/chronograf/v1/config"},"auth":{"superAdminNewUsers":false}}`,
|
||||
body: `{"links":{"self":"/chronograf/v1/org_config"},"organization":"default","logViewer":{"columns":[{"name":"time","position":0,"encodings":[{"type":"visibility","value":"hidden"}]},{"name":"severity","position":1,"encodings":[{"type":"visibility","value":"visible"},{"type":"label","value":"icon"},{"type":"label","value":"text"}]},{"name":"timestamp","position":2,"encodings":[{"type":"visibility","value":"visible"}]},{"name":"message","position":3,"encodings":[{"type":"visibility","value":"visible"}]},{"name":"facility","position":4,"encodings":[{"type":"visibility","value":"visible"}]},{"name":"procid","position":5,"encodings":[{"type":"visibility","value":"visible"},{"type":"displayName","value":"Proc ID"}]},{"name":"appname","position":6,"encodings":[{"type":"visibility","value":"visible"},{"type":"displayName","value":"Application"}]},{"name":"host","position":7,"encodings":[{"type":"visibility","value":"visible"}]}]}}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -152,36 +169,41 @@ func TestOrganizationConfig(t *testing.T) {
|
|||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := &Service{
|
||||
Store: &mocks.Store{
|
||||
OrganizationConfigStore: tt.fields.OrganizationConfigStore,
|
||||
OrganizationConfigStore: tt.fields.organizationConfigStore,
|
||||
},
|
||||
Logger: log.New(log.DebugLevel),
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("GET", "http://any.url", nil)
|
||||
ctx := context.WithValue(r.Context(), organizations.ContextKey, tt.args.organizationID)
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
s.Config(w, r)
|
||||
s.OrganizationConfig(w, r)
|
||||
|
||||
resp := w.Result()
|
||||
content := resp.Header.Get("Content-Type")
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != tt.wants.statusCode {
|
||||
t.Errorf("%q. Config() = %v, want %v", tt.name, resp.StatusCode, tt.wants.statusCode)
|
||||
t.Errorf("%q. OrganizationConfig() = %v, want %v", tt.name, resp.StatusCode, tt.wants.statusCode)
|
||||
}
|
||||
if tt.wants.contentType != "" && content != tt.wants.contentType {
|
||||
t.Errorf("%q. Config() = %v, want %v", tt.name, content, tt.wants.contentType)
|
||||
t.Errorf("%q. OrganizationConfig() = %v, want %v", tt.name, content, tt.wants.contentType)
|
||||
}
|
||||
if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq {
|
||||
t.Errorf("%q. Config() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body)
|
||||
t.Errorf("%q. OrganizationConfig() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogViewerOrganizationConfig(t *testing.T) {
|
||||
type args struct {
|
||||
organizationID string
|
||||
}
|
||||
type fields struct {
|
||||
OrganizationConfigStore chronograf.OrganizationConfigStore
|
||||
organizationConfigStore chronograf.OrganizationConfigStore
|
||||
}
|
||||
type wants struct {
|
||||
statusCode int
|
||||
|
@ -191,45 +213,56 @@ func TestLogViewerOrganizationConfig(t *testing.T) {
|
|||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
fields fields
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "Get log viewer configuration",
|
||||
args: args{
|
||||
organizationID: "default",
|
||||
},
|
||||
fields: fields{
|
||||
OrganizationConfigStore: &mocks.OrganizationConfigStore{
|
||||
OrganizationConfig: &chronograf.OrganizationConfig{
|
||||
LogViewer: chronograf.LogViewerConfig{
|
||||
Columns: []chronograf.LogViewerColumn{
|
||||
{
|
||||
Name: "severity",
|
||||
Position: 0,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
organizationConfigStore: &mocks.OrganizationConfigStore{
|
||||
FindOrCreateF: func(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) {
|
||||
switch orgID {
|
||||
case "default":
|
||||
return &chronograf.OrganizationConfig{
|
||||
LogViewer: chronograf.LogViewerConfig{
|
||||
Columns: []chronograf.LogViewerColumn{
|
||||
{
|
||||
Type: "color",
|
||||
Value: "emergency",
|
||||
Name: "ruby",
|
||||
},
|
||||
{
|
||||
Type: "color",
|
||||
Value: "info",
|
||||
Name: "rainforest",
|
||||
},
|
||||
{
|
||||
Type: "displayName",
|
||||
Value: "Log Severity",
|
||||
Name: "severity",
|
||||
Position: 0,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "color",
|
||||
Value: "emergency",
|
||||
Name: "ruby",
|
||||
},
|
||||
{
|
||||
Type: "color",
|
||||
Value: "info",
|
||||
Name: "rainforest",
|
||||
},
|
||||
{
|
||||
Type: "displayName",
|
||||
Value: "Log Severity",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
default:
|
||||
return nil, chronograf.ErrOrganizationConfigFindOrCreateFailed
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
statusCode: 200,
|
||||
contentType: "application/json",
|
||||
body: `{"links":{"self":"/chronograf/v1/config/logviewer"},"columns":[{"name":"severity","position":0,"encodings":[{"type":"color","value":"emergency","name":"ruby"},{"type":"color","value":"info","name":"rainforest"},{"type":"displayName","value":"Log Severity"}]}]}`,
|
||||
body: `{"links":{"self":"/chronograf/v1/org_config/logviewer"},"columns":[{"name":"severity","position":0,"encodings":[{"type":"color","value":"emergency","name":"ruby"},{"type":"color","value":"info","name":"rainforest"},{"type":"displayName","value":"Log Severity"}]}]}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -238,13 +271,15 @@ func TestLogViewerOrganizationConfig(t *testing.T) {
|
|||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := &Service{
|
||||
Store: &mocks.Store{
|
||||
ConfigStore: tt.fields.ConfigStore,
|
||||
OrganizationConfigStore: tt.fields.organizationConfigStore,
|
||||
},
|
||||
Logger: log.New(log.DebugLevel),
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("GET", "http://any.url", nil)
|
||||
ctx := context.WithValue(r.Context(), organizations.ContextKey, tt.args.organizationID)
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
s.LogViewerOrganizationConfig(w, r)
|
||||
|
||||
|
@ -267,10 +302,11 @@ func TestLogViewerOrganizationConfig(t *testing.T) {
|
|||
|
||||
func TestReplaceLogViewerOrganizationConfig(t *testing.T) {
|
||||
type fields struct {
|
||||
ConfigStore chronograf.OrganizationConfigStore
|
||||
organizationConfigStore chronograf.OrganizationConfigStore
|
||||
}
|
||||
type args struct {
|
||||
payload interface{} // expects JSON serializable struct
|
||||
payload interface{} // expects JSON serializable struct
|
||||
organizationID string
|
||||
}
|
||||
type wants struct {
|
||||
statusCode int
|
||||
|
@ -287,31 +323,41 @@ func TestReplaceLogViewerOrganizationConfig(t *testing.T) {
|
|||
{
|
||||
name: "Set log viewer configuration",
|
||||
fields: fields{
|
||||
ConfigStore: &mocks.OrganizationConfigStore{
|
||||
OrganizationConfig: &chronograf.OrganizationConfig{
|
||||
LogViewer: chronograf.LogViewerConfig{
|
||||
Columns: []chronograf.LogViewerColumn{
|
||||
{
|
||||
Name: "severity",
|
||||
Position: 0,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
organizationConfigStore: &mocks.OrganizationConfigStore{
|
||||
FindOrCreateF: func(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) {
|
||||
switch orgID {
|
||||
case "1337":
|
||||
return &chronograf.OrganizationConfig{
|
||||
LogViewer: chronograf.LogViewerConfig{
|
||||
Columns: []chronograf.LogViewerColumn{
|
||||
{
|
||||
Type: "color",
|
||||
Value: "info",
|
||||
Name: "rainforest",
|
||||
},
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "label",
|
||||
Value: "icon",
|
||||
Name: "severity",
|
||||
Position: 0,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "color",
|
||||
Value: "info",
|
||||
Name: "rainforest",
|
||||
},
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
{
|
||||
Type: "label",
|
||||
Value: "icon",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
default:
|
||||
return nil, chronograf.ErrOrganizationConfigFindOrCreateFailed
|
||||
}
|
||||
},
|
||||
UpdateF: func(ctx context.Context, target *chronograf.OrganizationConfig) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -358,41 +404,52 @@ func TestReplaceLogViewerOrganizationConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
organizationID: "1337",
|
||||
},
|
||||
wants: wants{
|
||||
statusCode: 200,
|
||||
contentType: "application/json",
|
||||
body: `{"links":{"self":"/chronograf/v1/config/logviewer"},"columns":[{"name":"severity","position":1,"encodings":[{"type":"color","value":"info","name":"pineapple"},{"type":"color","value":"emergency","name":"ruby"},{"type":"visibility","value":"visible"},{"type":"label","value":"icon"}]},{"name":"messages","position":0,"encodings":[{"type":"displayName","value":"Log Messages"},{"type":"visibility","value":"visible"}]}]}`,
|
||||
body: `{"links":{"self":"/chronograf/v1/org_config/logviewer"},"columns":[{"name":"severity","position":1,"encodings":[{"type":"color","value":"info","name":"pineapple"},{"type":"color","value":"emergency","name":"ruby"},{"type":"visibility","value":"visible"},{"type":"label","value":"icon"}]},{"name":"messages","position":0,"encodings":[{"type":"displayName","value":"Log Messages"},{"type":"visibility","value":"visible"}]}]}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Set invalid log viewer configuration – empty",
|
||||
fields: fields{
|
||||
ConfigStore: &mocks.OrganizationConfigStore{
|
||||
OrganizationConfig: &chronograf.OrganizationConfig{
|
||||
LogViewer: chronograf.LogViewerConfig{
|
||||
Columns: []chronograf.LogViewerColumn{
|
||||
{
|
||||
Name: "severity",
|
||||
Position: 0,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
organizationConfigStore: &mocks.OrganizationConfigStore{
|
||||
FindOrCreateF: func(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) {
|
||||
switch orgID {
|
||||
case "1337":
|
||||
return &chronograf.OrganizationConfig{
|
||||
LogViewer: chronograf.LogViewerConfig{
|
||||
Columns: []chronograf.LogViewerColumn{
|
||||
{
|
||||
Type: "color",
|
||||
Value: "info",
|
||||
Name: "rainforest",
|
||||
},
|
||||
{
|
||||
Type: "label",
|
||||
Value: "icon",
|
||||
},
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
Name: "severity",
|
||||
Position: 0,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "color",
|
||||
Value: "info",
|
||||
Name: "rainforest",
|
||||
},
|
||||
{
|
||||
Type: "label",
|
||||
Value: "icon",
|
||||
},
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
default:
|
||||
return nil, chronograf.ErrOrganizationConfigFindOrCreateFailed
|
||||
}
|
||||
},
|
||||
UpdateF: func(ctx context.Context, target *chronograf.OrganizationConfig) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -400,6 +457,7 @@ func TestReplaceLogViewerOrganizationConfig(t *testing.T) {
|
|||
payload: chronograf.LogViewerConfig{
|
||||
Columns: []chronograf.LogViewerColumn{},
|
||||
},
|
||||
organizationID: "1337",
|
||||
},
|
||||
wants: wants{
|
||||
statusCode: 400,
|
||||
|
@ -410,22 +468,32 @@ func TestReplaceLogViewerOrganizationConfig(t *testing.T) {
|
|||
{
|
||||
name: "Set invalid log viewer configuration - duplicate column name",
|
||||
fields: fields{
|
||||
ConfigStore: &mocks.OrganizationConfigStore{
|
||||
OrganizationConfig: &chronograf.OrganizationConfig{
|
||||
LogViewer: chronograf.LogViewerConfig{
|
||||
Columns: []chronograf.LogViewerColumn{
|
||||
{
|
||||
Name: "procid",
|
||||
Position: 0,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
organizationConfigStore: &mocks.OrganizationConfigStore{
|
||||
FindOrCreateF: func(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) {
|
||||
switch orgID {
|
||||
case "1337":
|
||||
return &chronograf.OrganizationConfig{
|
||||
LogViewer: chronograf.LogViewerConfig{
|
||||
Columns: []chronograf.LogViewerColumn{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "hidden",
|
||||
Name: "procid",
|
||||
Position: 0,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "hidden",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
default:
|
||||
return nil, chronograf.ErrOrganizationConfigFindOrCreateFailed
|
||||
}
|
||||
},
|
||||
UpdateF: func(ctx context.Context, target *chronograf.OrganizationConfig) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -454,6 +522,7 @@ func TestReplaceLogViewerOrganizationConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
organizationID: "1337",
|
||||
},
|
||||
wants: wants{
|
||||
statusCode: 400,
|
||||
|
@ -464,22 +533,32 @@ func TestReplaceLogViewerOrganizationConfig(t *testing.T) {
|
|||
{
|
||||
name: "Set invalid log viewer configuration - multiple columns with same position value",
|
||||
fields: fields{
|
||||
ConfigStore: &mocks.OrganizationConfigStore{
|
||||
OrganizationConfig: &chronograf.OrganizationConfig{
|
||||
LogViewer: chronograf.LogViewerConfig{
|
||||
Columns: []chronograf.LogViewerColumn{
|
||||
{
|
||||
Name: "procid",
|
||||
Position: 0,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
organizationConfigStore: &mocks.OrganizationConfigStore{
|
||||
FindOrCreateF: func(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) {
|
||||
switch orgID {
|
||||
case "1337":
|
||||
return &chronograf.OrganizationConfig{
|
||||
LogViewer: chronograf.LogViewerConfig{
|
||||
Columns: []chronograf.LogViewerColumn{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "hidden",
|
||||
Name: "procid",
|
||||
Position: 0,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "visibility",
|
||||
Value: "hidden",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
default:
|
||||
return nil, chronograf.ErrOrganizationConfigFindOrCreateFailed
|
||||
}
|
||||
},
|
||||
UpdateF: func(ctx context.Context, target *chronograf.OrganizationConfig) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -508,6 +587,7 @@ func TestReplaceLogViewerOrganizationConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
organizationID: "1337",
|
||||
},
|
||||
wants: wants{
|
||||
statusCode: 400,
|
||||
|
@ -518,27 +598,37 @@ func TestReplaceLogViewerOrganizationConfig(t *testing.T) {
|
|||
{
|
||||
name: "Set invalid log viewer configuration – no visibility",
|
||||
fields: fields{
|
||||
ConfigStore: &mocks.OrganizationConfigStore{
|
||||
OrganizationConfig: &chronograf.OrganizationConfig{
|
||||
LogViewer: chronograf.LogViewerConfig{
|
||||
Columns: []chronograf.LogViewerColumn{
|
||||
{
|
||||
Name: "severity",
|
||||
Position: 0,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
organizationConfigStore: &mocks.OrganizationConfigStore{
|
||||
FindOrCreateF: func(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) {
|
||||
switch orgID {
|
||||
case "1337":
|
||||
return &chronograf.OrganizationConfig{
|
||||
LogViewer: chronograf.LogViewerConfig{
|
||||
Columns: []chronograf.LogViewerColumn{
|
||||
{
|
||||
Type: "color",
|
||||
Value: "info",
|
||||
Name: "rainforest",
|
||||
},
|
||||
{
|
||||
Type: "label",
|
||||
Value: "icon",
|
||||
Name: "severity",
|
||||
Position: 0,
|
||||
Encodings: []chronograf.ColumnEncoding{
|
||||
{
|
||||
Type: "color",
|
||||
Value: "info",
|
||||
Name: "rainforest",
|
||||
},
|
||||
{
|
||||
Type: "label",
|
||||
Value: "icon",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
default:
|
||||
return nil, chronograf.ErrOrganizationConfigFindOrCreateFailed
|
||||
}
|
||||
},
|
||||
UpdateF: func(ctx context.Context, target *chronograf.OrganizationConfig) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -567,6 +657,7 @@ func TestReplaceLogViewerOrganizationConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
organizationID: "1337",
|
||||
},
|
||||
wants: wants{
|
||||
statusCode: 400,
|
||||
|
@ -580,13 +671,15 @@ func TestReplaceLogViewerOrganizationConfig(t *testing.T) {
|
|||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := &Service{
|
||||
Store: &mocks.Store{
|
||||
OrganizationConfigStore: tt.fields.OrganizationConfigStore,
|
||||
OrganizationConfigStore: tt.fields.organizationConfigStore,
|
||||
},
|
||||
Logger: log.New(log.DebugLevel),
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("GET", "http://any.url", nil)
|
||||
ctx := context.WithValue(r.Context(), organizations.ContextKey, tt.args.organizationID)
|
||||
r = r.WithContext(ctx)
|
||||
buf, _ := json.Marshal(tt.args.payload)
|
||||
r.Body = ioutil.NopCloser(bytes.NewReader(buf))
|
||||
|
||||
|
|
|
@ -486,14 +486,15 @@ func openService(ctx context.Context, buildInfo chronograf.BuildInfo, boltPath s
|
|||
return Service{
|
||||
TimeSeriesClient: &InfluxClient{},
|
||||
Store: &Store{
|
||||
LayoutsStore: layouts,
|
||||
DashboardsStore: dashboards,
|
||||
SourcesStore: sources,
|
||||
ServersStore: kapacitors,
|
||||
OrganizationsStore: organizations,
|
||||
UsersStore: db.UsersStore,
|
||||
ConfigStore: db.ConfigStore,
|
||||
MappingsStore: db.MappingsStore,
|
||||
LayoutsStore: layouts,
|
||||
DashboardsStore: dashboards,
|
||||
SourcesStore: sources,
|
||||
ServersStore: kapacitors,
|
||||
OrganizationsStore: organizations,
|
||||
UsersStore: db.UsersStore,
|
||||
ConfigStore: db.ConfigStore,
|
||||
MappingsStore: db.MappingsStore,
|
||||
OrganizationConfigStore: db.OrganizationConfigStore,
|
||||
},
|
||||
Logger: logger,
|
||||
UseAuth: useAuth,
|
||||
|
|
|
@ -91,6 +91,7 @@ type DataStore interface {
|
|||
Mappings(ctx context.Context) chronograf.MappingsStore
|
||||
Dashboards(ctx context.Context) chronograf.DashboardsStore
|
||||
Config(ctx context.Context) chronograf.ConfigStore
|
||||
OrganizationConfig(ctx context.Context) chronograf.OrganizationConfigStore
|
||||
}
|
||||
|
||||
// ensure that Store implements a DataStore
|
||||
|
@ -98,18 +99,19 @@ var _ DataStore = &Store{}
|
|||
|
||||
// Store implements the DataStore interface
|
||||
type Store struct {
|
||||
SourcesStore chronograf.SourcesStore
|
||||
ServersStore chronograf.ServersStore
|
||||
LayoutsStore chronograf.LayoutsStore
|
||||
UsersStore chronograf.UsersStore
|
||||
DashboardsStore chronograf.DashboardsStore
|
||||
MappingsStore chronograf.MappingsStore
|
||||
OrganizationsStore chronograf.OrganizationsStore
|
||||
ConfigStore chronograf.ConfigStore
|
||||
SourcesStore chronograf.SourcesStore
|
||||
ServersStore chronograf.ServersStore
|
||||
LayoutsStore chronograf.LayoutsStore
|
||||
UsersStore chronograf.UsersStore
|
||||
DashboardsStore chronograf.DashboardsStore
|
||||
MappingsStore chronograf.MappingsStore
|
||||
OrganizationsStore chronograf.OrganizationsStore
|
||||
ConfigStore chronograf.ConfigStore
|
||||
OrganizationConfigStore chronograf.OrganizationConfigStore
|
||||
}
|
||||
|
||||
// Sources returns a noop.SourcesStore if the context has no organization specified
|
||||
// and a organization.SourcesStore otherwise.
|
||||
// and an organization.SourcesStore otherwise.
|
||||
func (s *Store) Sources(ctx context.Context) chronograf.SourcesStore {
|
||||
if isServer := hasServerContext(ctx); isServer {
|
||||
return s.SourcesStore
|
||||
|
@ -122,7 +124,7 @@ func (s *Store) Sources(ctx context.Context) chronograf.SourcesStore {
|
|||
}
|
||||
|
||||
// Servers returns a noop.ServersStore if the context has no organization specified
|
||||
// and a organization.ServersStore otherwise.
|
||||
// and an organization.ServersStore otherwise.
|
||||
func (s *Store) Servers(ctx context.Context) chronograf.ServersStore {
|
||||
if isServer := hasServerContext(ctx); isServer {
|
||||
return s.ServersStore
|
||||
|
@ -157,7 +159,7 @@ func (s *Store) Users(ctx context.Context) chronograf.UsersStore {
|
|||
}
|
||||
|
||||
// Dashboards returns a noop.DashboardsStore if the context has no organization specified
|
||||
// and a organization.DashboardsStore otherwise.
|
||||
// and an organization.DashboardsStore otherwise.
|
||||
func (s *Store) Dashboards(ctx context.Context) chronograf.DashboardsStore {
|
||||
if isServer := hasServerContext(ctx); isServer {
|
||||
return s.DashboardsStore
|
||||
|
@ -169,17 +171,17 @@ func (s *Store) Dashboards(ctx context.Context) chronograf.DashboardsStore {
|
|||
return &noop.DashboardsStore{}
|
||||
}
|
||||
|
||||
// Settings returns a noop.SettingsStore if the context has no organization specified
|
||||
// and a organization.SettingsStore otherwise.
|
||||
func (s *Store) Settings(ctx context.Context) chronograf.SettingsStore {
|
||||
// OrganizationConfig returns a noop.OrganizationConfigStore if the context has no organization specified
|
||||
// and an organization.OrganizationConfigStore otherwise.
|
||||
func (s *Store) OrganizationConfig(ctx context.Context) chronograf.OrganizationConfigStore {
|
||||
if isServer := hasServerContext(ctx); isServer {
|
||||
return s.SettingsStore
|
||||
return s.OrganizationConfigStore
|
||||
}
|
||||
if org, ok := hasOrganizationContext(ctx); ok {
|
||||
return organizations.NewSettingsStore(s.SettingsStore, org)
|
||||
if orgID, ok := hasOrganizationContext(ctx); ok {
|
||||
return organizations.NewOrganizationConfigStore(s.OrganizationConfigStore, orgID)
|
||||
}
|
||||
|
||||
return &noop.SettingsStore{}
|
||||
return &noop.OrganizationConfigStore{}
|
||||
}
|
||||
|
||||
// Organizations returns the underlying OrganizationsStore.
|
||||
|
|
Loading…
Reference in New Issue