diff --git a/store/cloudflare/cloudflare.go b/store/cloudflare/cloudflare.go index 3b9a5e8c..20ce2bac 100644 --- a/store/cloudflare/cloudflare.go +++ b/store/cloudflare/cloudflare.go @@ -17,7 +17,6 @@ import ( "strconv" "time" - "github.com/micro/go-micro/config/options" "github.com/micro/go-micro/store" "github.com/pkg/errors" ) @@ -27,7 +26,7 @@ const ( ) type workersKV struct { - options.Options + options store.Options // cf account id account string // cf api token @@ -291,38 +290,25 @@ func (w *workersKV) request(ctx context.Context, method, path string, body inter // CF_API_TOKEN to a cloudflare API token scoped to Workers KV. // CF_ACCOUNT_ID to contain a string with your cloudflare account ID. // KV_NAMESPACE_ID to contain the namespace UUID for your KV storage. -func NewStore(opts ...options.Option) store.Store { - // create new Options - options := options.NewOptions(opts...) +func NewStore(opts ...store.Option) store.Store { + var options store.Options + for _, o := range opts { + o(&options) + } - // get values from the environment + // get options from environment account, token, namespace := getOptions() - // set api token from options if exists - if apiToken, ok := options.Values().Get("CF_API_TOKEN"); ok { - tk, ok := apiToken.(string) - if !ok { - log.Fatal("Store: Option CF_API_TOKEN contains a non-string") - } - token = tk + if len(account) == 0 { + account = getAccount(options.Context) } - // set account id from options if exists - if accountID, ok := options.Values().Get("CF_ACCOUNT_ID"); ok { - id, ok := accountID.(string) - if !ok { - log.Fatal("Store: Option CF_ACCOUNT_ID contains a non-string") - } - account = id + if len(token) == 0 { + token = getToken(options.Context) } - // set namespace from options if exists - if uuid, ok := options.Values().Get("KV_NAMESPACE_ID"); ok { - ns, ok := uuid.(string) - if !ok { - log.Fatal("Store: Option KV_NAMESPACE_ID contains a non-string") - } - namespace = ns + if len(namespace) == 0 { + namespace = getNamespace(options.Context) } // validate options are not blank or log.Fatal @@ -332,7 +318,7 @@ func NewStore(opts ...options.Option) store.Store { account: account, namespace: namespace, token: token, - Options: options, + options: options, httpClient: &http.Client{}, } } diff --git a/store/cloudflare/options.go b/store/cloudflare/options.go index 36dc7c10..d9670ff4 100644 --- a/store/cloudflare/options.go +++ b/store/cloudflare/options.go @@ -1,23 +1,60 @@ package cloudflare import ( - "github.com/micro/go-micro/config/options" + "context" + + "github.com/micro/go-micro/store" ) +func getOption(ctx context.Context, key string) string { + if ctx == nil { + return "" + } + val, ok := ctx.Value(key).(string) + if !ok { + return "" + } + return val +} + +func getToken(ctx context.Context) string { + return getOption(ctx, "CF_API_TOKEN") +} + +func getAccount(ctx context.Context) string { + return getOption(ctx, "CF_ACCOUNT_ID") +} + +func getNamespace(ctx context.Context) string { + return getOption(ctx, "KV_NAMESPACE_ID") +} + // Token sets the cloudflare api token -func Token(t string) options.Option { - // TODO: change to store.cf.api_token - return options.WithValue("CF_API_TOKEN", t) +func Token(t string) store.Option { + return func(o *store.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, "CF_API_TOKEN", t) + } } // Account sets the cloudflare account id -func Account(id string) options.Option { - // TODO: change to store.cf.account_id - return options.WithValue("CF_ACCOUNT_ID", id) +func Account(id string) store.Option { + return func(o *store.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, "CF_ACCOUNT_ID", id) + } } // Namespace sets the KV namespace -func Namespace(ns string) options.Option { - // TODO: change to store.cf.namespace - return options.WithValue("KV_NAMESPACE_ID", ns) +func Namespace(ns string) store.Option { + return func(o *store.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, "KV_NAMESPACE_ID", ns) + } } diff --git a/store/etcd/etcd.go b/store/etcd/etcd.go index 8ffa2d7d..c9bc4047 100644 --- a/store/etcd/etcd.go +++ b/store/etcd/etcd.go @@ -7,13 +7,12 @@ import ( client "github.com/coreos/etcd/clientv3" "github.com/coreos/etcd/mvcc/mvccpb" - "github.com/micro/go-micro/config/options" "github.com/micro/go-micro/store" ) type ekv struct { - options.Options - kv client.KV + options store.Options + kv client.KV } func (e *ekv) Read(keys ...string) ([]*store.Record, error) { @@ -91,15 +90,15 @@ func (e *ekv) String() string { return "etcd" } -func NewStore(opts ...options.Option) store.Store { - options := options.NewOptions(opts...) - - var endpoints []string - - if e, ok := options.Values().Get("store.nodes"); ok { - endpoints = e.([]string) +func NewStore(opts ...store.Option) store.Store { + var options store.Options + for _, o := range opts { + o(&options) } + // get the endpoints + endpoints := options.Nodes + if len(endpoints) == 0 { endpoints = []string{"http://127.0.0.1:2379"} } @@ -113,7 +112,7 @@ func NewStore(opts ...options.Option) store.Store { } return &ekv{ - Options: options, + options: options, kv: client.NewKV(c), } } diff --git a/store/memory/memory.go b/store/memory/memory.go index 01d1a44c..78e85e82 100644 --- a/store/memory/memory.go +++ b/store/memory/memory.go @@ -5,12 +5,11 @@ import ( "sync" "time" - "github.com/micro/go-micro/config/options" "github.com/micro/go-micro/store" ) type memoryStore struct { - options.Options + options store.Options sync.RWMutex values map[string]*memoryRecord @@ -110,11 +109,14 @@ func (m *memoryStore) Delete(keys ...string) error { } // NewStore returns a new store.Store -func NewStore(opts ...options.Option) store.Store { - options := options.NewOptions(opts...) +func NewStore(opts ...store.Option) store.Store { + var options store.Options + for _, o := range opts { + o(&options) + } return &memoryStore{ - Options: options, + options: options, values: make(map[string]*memoryRecord), } } diff --git a/store/options.go b/store/options.go index f5dbf7e2..0161ed20 100644 --- a/store/options.go +++ b/store/options.go @@ -1,7 +1,7 @@ package store import ( - "github.com/micro/go-micro/config/options" + "context" ) type Options struct { @@ -11,20 +11,30 @@ type Options struct { Namespace string // Prefix of the keys used Prefix string + // Alternative options + Context context.Context } +type Option func(o *Options) + // Nodes is a list of nodes used to back the store -func Nodes(a ...string) options.Option { - return options.WithValue("store.nodes", a) +func Nodes(a ...string) Option { + return func(o *Options) { + o.Nodes = a + } } // Prefix sets a prefix to any key ids used -func Prefix(p string) options.Option { - return options.WithValue("store.prefix", p) +func Prefix(p string) Option { + return func(o *Options) { + o.Prefix = p + } } // Namespace offers a way to have multiple isolated // stores in the same backend, if supported. -func Namespace(n string) options.Option { - return options.WithValue("store.namespace", n) +func Namespace(ns string) Option { + return func(o *Options) { + o.Namespace = ns + } } diff --git a/store/postgresql/postgresql.go b/store/postgresql/postgresql.go index 4162e1e1..84c07d58 100644 --- a/store/postgresql/postgresql.go +++ b/store/postgresql/postgresql.go @@ -4,15 +4,13 @@ package postgresql import ( "database/sql" "fmt" - "strings" "time" "unicode" "github.com/lib/pq" - "github.com/pkg/errors" - - "github.com/micro/go-micro/config/options" "github.com/micro/go-micro/store" + "github.com/micro/go-micro/util/log" + "github.com/pkg/errors" ) // DefaultNamespace is the namespace that the sql store @@ -27,7 +25,8 @@ type sqlStore struct { database string table string - options.Options + + options store.Options } // List all the known records @@ -151,38 +150,16 @@ func (s *sqlStore) Delete(keys ...string) error { return nil } -func (s *sqlStore) initDB(options options.Options) error { - // Get the store.namespace option, or use sql.DefaultNamespace - namespaceOpt, found := options.Values().Get("store.namespace") - if !found { - s.database = DefaultNamespace - } else { - if namespace, ok := namespaceOpt.(string); ok { - s.database = namespace - } else { - return errors.New("store.namespace option must be a string") - } - } - // Get the store.namespace option, or use sql.DefaultNamespace - prefixOpt, found := options.Values().Get("store.prefix") - if !found { - s.table = DefaultPrefix - } else { - if prefix, ok := prefixOpt.(string); ok { - s.table = prefix - } else { - return errors.New("store.namespace option must be a string") - } - } - +func (s *sqlStore) initDB() { // Create "micro" schema schema, err := s.db.Prepare(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s ;", s.database)) if err != nil { - return err + log.Fatal(err) } + _, err = schema.Exec() if err != nil { - return errors.Wrap(err, "Couldn't create database") + log.Fatal(errors.Wrap(err, "Couldn't create database")) } // Create a table for the Store namespace @@ -194,73 +171,59 @@ func (s *sqlStore) initDB(options options.Options) error { CONSTRAINT %s_pkey PRIMARY KEY (key) );`, s.database, s.table, s.table)) if err != nil { - return errors.Wrap(err, "SQL statement preparation failed") - } - _, err = tableq.Exec() - if err != nil { - return errors.Wrap(err, "Couldn't create table") + log.Fatal(errors.Wrap(err, "SQL statement preparation failed")) } - return nil + _, err = tableq.Exec() + if err != nil { + log.Fatal(errors.Wrap(err, "Couldn't create table")) + } } // New returns a new micro Store backed by sql -func New(opts ...options.Option) (store.Store, error) { - options := options.NewOptions(opts...) - driver, dataSourceName, err := validateOptions(options) +func New(opts ...store.Option) store.Store { + var options store.Options + for _, o := range opts { + o(&options) + } + + nodes := options.Nodes + if len(nodes) == 0 { + nodes = []string{"localhost:26257"} + } + + namespace := options.Namespace + if len(namespace) == 0 { + namespace = DefaultNamespace + } + + prefix := options.Prefix + if len(prefix) == 0 { + prefix = DefaultPrefix + } + + for _, r := range namespace { + if !unicode.IsLetter(r) { + log.Fatal("store.namespace must only contain letters") + } + } + + // create source from first node + source := fmt.Sprintf("host=%s", nodes[0]) + db, err := sql.Open("pq", source) if err != nil { - return nil, err - } - if !strings.Contains(dataSourceName, " ") { - dataSourceName = fmt.Sprintf("host=%s", dataSourceName) - } - db, err := sql.Open(driver, dataSourceName) - if err != nil { - return nil, err + log.Fatal(err) } + if err := db.Ping(); err != nil { - return nil, err + log.Fatal(err) } + s := &sqlStore{ - db: db, + db: db, + database: namespace, + table: prefix, } - return s, s.initDB(options) -} - -// validateOptions checks whether the provided options are valid, then returns the driver -// and data source name. -func validateOptions(options options.Options) (driver, dataSourceName string, err error) { - driverOpt, found := options.Values().Get("store.sql.driver") - if !found { - return "", "", errors.New("No store.sql.driver option specified") - } - nodesOpt, found := options.Values().Get("store.nodes") - if !found { - return "", "", errors.New("No store.nodes option specified (expected a database connection string)") - } - driver, ok := driverOpt.(string) - if !ok { - return "", "", errors.New("store.sql.driver option must be a string") - } - nodes, ok := nodesOpt.([]string) - if !ok { - return "", "", errors.New("store.nodes option must be a []string") - } - if len(nodes) != 1 { - return "", "", errors.New("expected only 1 store.nodes option") - } - namespaceOpt, found := options.Values().Get("store.namespace") - if found { - namespace, ok := namespaceOpt.(string) - if !ok { - return "", "", errors.New("store.namespace must me a string") - } - for _, r := range namespace { - if !unicode.IsLetter(r) { - return "", "", errors.New("store.namespace must only contain letters") - } - } - } - return driver, nodes[0], nil + return s } diff --git a/store/postgresql/postgresql_test.go b/store/postgresql/postgresql_test.go index fffc974e..692aebeb 100644 --- a/store/postgresql/postgresql_test.go +++ b/store/postgresql/postgresql_test.go @@ -27,13 +27,10 @@ func TestSQL(t *testing.T) { } db.Close() - sqlStore, err := New( + sqlStore := New( store.Namespace("testsql"), store.Nodes(connection), ) - if err != nil { - t.Fatal(err.Error()) - } records, err := sqlStore.List() if err != nil { diff --git a/store/service/service.go b/store/service/service.go index e0432016..37a7a030 100644 --- a/store/service/service.go +++ b/store/service/service.go @@ -7,14 +7,13 @@ import ( "time" "github.com/micro/go-micro/client" - "github.com/micro/go-micro/config/options" "github.com/micro/go-micro/metadata" "github.com/micro/go-micro/store" pb "github.com/micro/go-micro/store/service/proto" ) type serviceStore struct { - options.Options + options store.Options // Namespace to use Namespace string @@ -123,33 +122,17 @@ func (s *serviceStore) Delete(keys ...string) error { } // NewStore returns a new store service implementation -func NewStore(opts ...options.Option) store.Store { - options := options.NewOptions(opts...) - - var nodes []string - var namespace string - var prefix string - - n, ok := options.Values().Get("store.nodes") - if ok { - nodes = n.([]string) - } - - ns, ok := options.Values().Get("store.namespace") - if ok { - namespace = ns.(string) - } - - prx, ok := options.Values().Get("store.prefix") - if ok { - prefix = prx.(string) +func NewStore(opts ...store.Option) store.Store { + var options store.Options + for _, o := range opts { + o(&options) } service := &serviceStore{ - Options: options, - Namespace: namespace, - Prefix: prefix, - Nodes: nodes, + options: options, + Namespace: options.Namespace, + Prefix: options.Prefix, + Nodes: options.Nodes, Client: pb.NewStoreService("go.micro.store", client.DefaultClient), }