Add bolt implementation of chronograf.ConfigStore

pull/2355/head
Michael Desa 2017-12-13 10:38:01 -08:00
parent 4496c361d7
commit d875757ce3
8 changed files with 1034 additions and 82 deletions

View File

@ -23,6 +23,7 @@ type Client struct {
DashboardsStore *DashboardsStore
UsersStore *UsersStore
OrganizationsStore *OrganizationsStore
ConfigStore *ConfigStore
}
// NewClient initializes all stores
@ -40,6 +41,7 @@ func NewClient() *Client {
}
c.UsersStore = &UsersStore{client: c}
c.OrganizationsStore = &OrganizationsStore{client: c}
c.ConfigStore = &ConfigStore{client: c}
return c
}
@ -77,6 +79,10 @@ func (c *Client) Open(ctx context.Context) error {
if _, err := tx.CreateBucketIfNotExists(UsersBucket); err != nil {
return err
}
// Always create Config bucket.
if _, err := tx.CreateBucketIfNotExists(ConfigBucket); err != nil {
return err
}
return nil
}); err != nil {
return err
@ -98,6 +104,9 @@ func (c *Client) Open(ctx context.Context) error {
if err := c.DashboardsStore.Migrate(ctx); err != nil {
return err
}
if err := c.ConfigStore.Migrate(ctx); err != nil {
return err
}
return nil
}

67
bolt/config.go Normal file
View File

@ -0,0 +1,67 @@
package bolt
import (
"context"
"fmt"
"github.com/boltdb/bolt"
"github.com/influxdata/chronograf"
"github.com/influxdata/chronograf/bolt/internal"
)
// Ensure ConfigStore implements chronograf.ConfigStore.
var _ chronograf.ConfigStore = &ConfigStore{}
// ConfigBucket is used to store chronograf application state
var ConfigBucket = []byte("ConfigV1")
// configID is the boltDB key where the configuration object is stored
var configID = []byte("config/v1")
// ConfigStore uses bolt to store and retrieve global
// application configuration
type ConfigStore struct {
client *Client
}
func (s *ConfigStore) Migrate(ctx context.Context) error {
if _, err := s.Get(ctx); err != nil {
return s.Initialize(ctx)
}
return nil
}
func (s *ConfigStore) Initialize(ctx context.Context) error {
var cfg chronograf.Config
return s.Update(ctx, &cfg)
}
func (s *ConfigStore) Get(ctx context.Context) (*chronograf.Config, error) {
var cfg chronograf.Config
err := s.client.db.View(func(tx *bolt.Tx) error {
v := tx.Bucket(ConfigBucket).Get(configID)
if v == nil {
return chronograf.ErrConfigNotFound
}
return internal.UnmarshalConfig(v, &cfg)
})
if err != nil {
return nil, err
}
return &cfg, nil
}
func (s *ConfigStore) Update(ctx context.Context, cfg *chronograf.Config) error {
if cfg == nil {
return fmt.Errorf("config provided was nil")
}
return s.client.db.Update(func(tx *bolt.Tx) error {
if v, err := internal.MarshalConfig(cfg); err != nil {
return err
} else if err := tx.Bucket(ConfigBucket).Put(configID, v); err != nil {
return err
}
return nil
})
}

111
bolt/config_test.go Normal file
View File

@ -0,0 +1,111 @@
package bolt_test
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/influxdata/chronograf"
)
func TestConfig_Get(t *testing.T) {
type wants struct {
config *chronograf.Config
err error
}
tests := []struct {
name string
wants wants
}{
{
name: "Get config",
wants: wants{
config: &chronograf.Config{
Auth: chronograf.AuthConfig{
SuperAdminFirstUserOnly: false,
},
},
},
},
}
for _, tt := range tests {
client, err := NewTestClient()
if err != nil {
t.Fatal(err)
}
if err := client.Open(context.TODO()); err != nil {
t.Fatal(err)
}
defer client.Close()
s := client.ConfigStore
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.Config
}
type wants struct {
config *chronograf.Config
err error
}
tests := []struct {
name string
args args
wants wants
}{
{
name: "Set config",
args: args{
config: &chronograf.Config{
Auth: chronograf.AuthConfig{
SuperAdminFirstUserOnly: true,
},
},
},
wants: wants{
config: &chronograf.Config{
Auth: chronograf.AuthConfig{
SuperAdminFirstUserOnly: true,
},
},
},
},
}
for _, tt := range tests {
client, err := NewTestClient()
if err != nil {
t.Fatal(err)
}
if err := client.Open(context.TODO()); err != nil {
t.Fatal(err)
}
defer client.Close()
s := client.ConfigStore
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
}
got, _ := 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)
}
}
}

View File

@ -2,6 +2,7 @@ package internal
import (
"encoding/json"
"fmt"
"github.com/gogo/protobuf/proto"
"github.com/influxdata/chronograf"
@ -591,3 +592,39 @@ func UnmarshalOrganizationPB(data []byte, o *Organization) error {
}
return nil
}
// MarshalConfig encodes a config to binary protobuf format.
func MarshalConfig(c *chronograf.Config) ([]byte, error) {
return MarshalConfigPB(&Config{
Auth: &AuthConfig{
SuperAdminFirstUserOnly: c.Auth.SuperAdminFirstUserOnly,
},
})
}
// MarshalConfigPB encodes a config to binary protobuf format.
func MarshalConfigPB(c *Config) ([]byte, error) {
return proto.Marshal(c)
}
// UnmarshalConfig decodes a config from binary protobuf data.
func UnmarshalConfig(data []byte, c *chronograf.Config) error {
var pb Config
if err := UnmarshalConfigPB(data, &pb); err != nil {
return err
}
if pb.Auth == nil {
return fmt.Errorf("Auth config is nil")
}
c.Auth.SuperAdminFirstUserOnly = pb.Auth.SuperAdminFirstUserOnly
return nil
}
// UnmarshalConfigPB decodes a config from binary protobuf data.
func UnmarshalConfigPB(data []byte, c *Config) error {
if err := proto.Unmarshal(data, c); err != nil {
return err
}
return nil
}

File diff suppressed because it is too large Load Diff

View File

@ -164,6 +164,14 @@ message Organization {
bool Public = 4; // Public specifies that users must be explicitly added to the organization
}
message Config {
AuthConfig Auth = 1; // Auth is the configuration for options that auth related
}
message AuthConfig {
bool SuperAdminFirstUserOnly = 1; // SuperAdminFirstUserOnly configuration option that specifies which users will auto become super admin
}
// The following is a vim modeline, it autoconfigures vim to have the
// appropriate tabbing and whitespace management to edit this file
//

View File

@ -26,6 +26,7 @@ const (
ErrOrganizationNotFound = Error("organization not found")
ErrOrganizationAlreadyExists = Error("organization already exists")
ErrCannotDeleteDefaultOrganization = Error("cannot delete default organization")
ErrConfigNotFound = Error("cannot find configuration")
)
// Error is a domain error encountered while processing chronograf requests
@ -618,6 +619,8 @@ type Config struct {
// ConfigStore is the storage and retrieval of global application Config
type ConfigStore interface {
// Initialize creates the initial configuration
Initialize(context.Context) error
// Get retrieves the whole Config from the ConfigStore
Get(context.Context) (*Config, error)
// Update updates the whole Config in the ConfigStore

View File

@ -11,6 +11,11 @@ type ConfigStore struct {
Config *chronograf.Config
}
// Initialize is noop in mocks store
func (c ConfigStore) Initialize(ctx context.Context) error {
return nil
}
// Get returns the whole global application configuration
func (c ConfigStore) Get(ctx context.Context) (*chronograf.Config, error) {
return c.Config, nil