diff --git a/go.mod b/go.mod index 617335c05..8a2c149c2 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,11 @@ module github.com/owncloud/ocis-accounts go 1.13 require ( + github.com/coreos/etcd v3.3.18+incompatible github.com/golang/protobuf v1.3.2 github.com/google/uuid v1.1.1 github.com/micro/cli v0.2.0 + github.com/micro/cli/v2 v2.1.1 github.com/micro/go-micro v1.18.0 github.com/micro/go-micro/v2 v2.0.0 github.com/oklog/run v1.1.0 diff --git a/go.sum b/go.sum index f8748c4b5..0d49beb03 100644 --- a/go.sum +++ b/go.sum @@ -381,6 +381,7 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/joncalhoun/qson v0.0.0-20170526102502-8a9cab3a62b1/go.mod h1:DFXrEwSRX0p/aSvxE21319menCBFeQO0jXpRj7LEZUA= github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= diff --git a/pkg/account/accounts.go b/pkg/account/accounts.go new file mode 100644 index 000000000..4fa0fb09b --- /dev/null +++ b/pkg/account/accounts.go @@ -0,0 +1,30 @@ +package account + +import "github.com/owncloud/ocis-accounts/pkg/config" + +var ( + // Registry uses the strategy pattern as a registry + Registry = map[string]RegisterFunc{} + + // DefaultManager defines the default accounts manager + DefaultManager = "filesystem" +) + +// RegisterFunc stores store constructors +type RegisterFunc func(*config.Config) Manager + +// Manager is an accounts service interface +type Manager interface { + // Read a record + Read(key string) *Record + // Write a record + Write(*Record) *Record + // List all records + List() []*Record +} + +// Record is an entry in the account storage +type Record struct { + Key string + Value []byte +} diff --git a/pkg/command/root.go b/pkg/command/root.go index 972cdd2cb..66369097c 100644 --- a/pkg/command/root.go +++ b/pkg/command/root.go @@ -3,9 +3,12 @@ package command import ( "os" - "github.com/micro/cli" - _ "github.com/owncloud/ocis-accounts/pkg/registry" + "github.com/micro/cli/v2" + "github.com/owncloud/ocis-accounts/pkg/config" "github.com/owncloud/ocis-hello/pkg/version" + + // init store manager + _ "github.com/owncloud/ocis-accounts/pkg/store" ) // Execute is the entry point for the ocis-accounts command. @@ -15,15 +18,15 @@ func Execute() error { Version: version.String, Usage: "Example service for Reva/oCIS", - Authors: []cli.Author{ + Authors: []*cli.Author{ { Name: "ownCloud GmbH", Email: "support@owncloud.com", }, }, - Commands: []cli.Command{ - Server(), + Commands: []*cli.Command{ + Server(config.New()), }, } diff --git a/pkg/command/server.go b/pkg/command/server.go index 9ed90f398..d27b9d01d 100644 --- a/pkg/command/server.go +++ b/pkg/command/server.go @@ -3,29 +3,51 @@ package command import ( "context" "fmt" + "os" + "path/filepath" "syscall" - "github.com/micro/cli" + "github.com/micro/cli/v2" "github.com/oklog/run" + "github.com/owncloud/ocis-accounts/pkg/config" "github.com/owncloud/ocis-accounts/pkg/micro/grpc" ) // Server is the entry point for the server command. -func Server() cli.Command { - return cli.Command{ +func Server(cfg *config.Config) *cli.Command { + baseDir, _ := filepath.Abs(filepath.Dir(os.Args[0])) + + return &cli.Command{ Name: "server", Usage: "Start accounts service", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "manager", + DefaultText: "filesystem", + Usage: "store controller driver. eg: filesystem", + Value: "filesystem", + EnvVars: []string{"OCIS_ACCOUNTS_MANAGER"}, + Destination: &cfg.Manager, + }, + &cli.StringFlag{ + Name: "mount-path", + DefaultText: "binary default running location", + Usage: "where to mount the ocis accounts store", + Value: baseDir, + EnvVars: []string{"OCIS_ACCOUNTS_MOUNT_PATH"}, + Destination: &cfg.MountPath, + }, + }, Action: func(c *cli.Context) error { gr := run.Group{} ctx, cancel := context.WithCancel(context.Background()) defer cancel() - service := grpc.NewService(ctx) + service := grpc.NewService(ctx, cfg) gr.Add(func() error { return service.Run() }, func(_ error) { - fmt.Println("shutting down grpc server") cancel() }) diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 000000000..68577874d --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,13 @@ +// Package config should be moved to internal +package config + +// Config captures ocis-accounts configuration parameters +type Config struct { + MountPath string + Manager string +} + +// New returns a new config +func New() *Config { + return &Config{} +} diff --git a/pkg/micro/grpc/grpc.go b/pkg/micro/grpc/grpc.go index 20e9b41a2..be538834c 100644 --- a/pkg/micro/grpc/grpc.go +++ b/pkg/micro/grpc/grpc.go @@ -1,25 +1,25 @@ package grpc -// package grpc uses `ocis-pkg` to start a go-micro service import ( "context" + "github.com/owncloud/ocis-accounts/pkg/config" "github.com/owncloud/ocis-accounts/pkg/proto/v0" svc "github.com/owncloud/ocis-accounts/pkg/service/v0" "github.com/owncloud/ocis-pkg/service/grpc" ) -// NewService initializes a new go-micro service ready to run -func NewService(c context.Context) grpc.Service { +// NewService creates a grpc service +func NewService(c context.Context, cfg *config.Config) grpc.Service { service := grpc.NewService( + // TODO options come from configuration grpc.Name("accounts"), grpc.Namespace("com.owncloud"), grpc.Address("localhost:9999"), grpc.Context(c), ) - // add a handler to the service - hdlr := svc.New() + hdlr := svc.New(cfg) proto.RegisterSettingsServiceHandler(service.Server(), hdlr) service.Init() diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go deleted file mode 100644 index 1dae9ba8f..000000000 --- a/pkg/registry/registry.go +++ /dev/null @@ -1,21 +0,0 @@ -// Package registry provides accessors to runtime services -package registry - -import ( - "sync" - - mstore "github.com/micro/go-micro/v2/store" - store "github.com/owncloud/ocis-accounts/pkg/store/filesystem" -) - -var ( - once *sync.Once = &sync.Once{} - // Store is a micro store implementation - Store mstore.Store -) - -func init() { - once.Do(func() { - Store = store.New() - }) -} diff --git a/pkg/service/v0/service.go b/pkg/service/v0/service.go index 79b4bb448..0a432c1e2 100644 --- a/pkg/service/v0/service.go +++ b/pkg/service/v0/service.go @@ -5,21 +5,35 @@ import ( "encoding/json" "github.com/golang/protobuf/ptypes/empty" - mstore "github.com/micro/go-micro/v2/store" + "github.com/owncloud/ocis-accounts/pkg/account" + "github.com/owncloud/ocis-accounts/pkg/config" "github.com/owncloud/ocis-accounts/pkg/proto/v0" - - "github.com/owncloud/ocis-accounts/pkg/registry" + olog "github.com/owncloud/ocis-pkg/log" ) // New returns a new instance of Service -func New() Service { - return Service{} +func New(cfg *config.Config) Service { + s := Service{ + Config: cfg, + } + + if newReg, ok := account.Registry[cfg.Manager]; ok { + s.Manager = newReg(cfg) + } else { + l := olog.NewLogger(olog.Name("ocis-accounts")) + l.Fatal().Msgf("unknown manager: %v", cfg.Manager) + } + + return s } -// Service implements the SettingsServiceHandler interface generated on accounts.pb.micro.go -type Service struct{} +// Service implements the SettingsServiceHandler interface +type Service struct { + Config *config.Config + Manager account.Manager +} -// Set implements the SettingsServiceHandler interface generated on accounts.pb.micro.go +// Set implements the SettingsServiceHandler interface // This implementation replaces the existent data with the requested. It does not calculate diff func (s Service) Set(c context.Context, req *proto.Record, res *proto.Record) error { settingsJSON, err := json.Marshal(req.Payload) @@ -27,45 +41,40 @@ func (s Service) Set(c context.Context, req *proto.Record, res *proto.Record) er return err } - record := mstore.Record{ + s.Manager.Write(&account.Record{ Key: req.Key, Value: settingsJSON, - } + }) - return registry.Store.Write(&record) + return nil } -// Get implements the SettingsServiceHandler interface generated on accounts.pb.micro.go +// Get implements the SettingsServiceHandler interface func (s Service) Get(c context.Context, req *proto.Query, res *proto.Record) error { - contents, err := registry.Store.Read(req.Key) - if err != nil { - return err - } + contents := s.Manager.Read(req.Key) - if len(contents) > 0 { - r := &proto.Payload{} - json.Unmarshal(contents[0].Value, r) - res.Payload = r - } + r := &proto.Payload{} + json.Unmarshal(contents.Value, r) + res.Payload = r return nil } -// List implements the SettingsServiceHandler interface generated on accounts.pb.micro.go +// List implements the SettingsServiceHandler interface func (s Service) List(ctx context.Context, in *empty.Empty, res *proto.Records) error { - r := &proto.Records{} - contents, err := registry.Store.List() - if err != nil { - return err - } + // r := &proto.Records{} + // contents, err := registry.Store.List() + // if err != nil { + // return err + // } - for _, v := range contents { - r.Records = append(r.Records, &proto.Record{ - Key: v.Key, - }) - } + // for _, v := range contents { + // r.Records = append(r.Records, &proto.Record{ + // Key: v.Key, + // }) + // } - res.Records = r.Records + // res.Records = r.Records return nil } diff --git a/pkg/store/filesystem/store.go b/pkg/store/filesystem/store.go index 2b2691494..a4a5a09d4 100644 --- a/pkg/store/filesystem/store.go +++ b/pkg/store/filesystem/store.go @@ -2,18 +2,23 @@ package store import ( - "fmt" "io/ioutil" "os" "path" "path/filepath" - mstore "github.com/micro/go-micro/v2/store" + "github.com/owncloud/ocis-accounts/pkg/account" + "github.com/owncloud/ocis-accounts/pkg/config" olog "github.com/owncloud/ocis-pkg/log" ) +var ( + // StoreName is the default name for the accounts store + StoreName string = "ocis-store" + managerName = "filesystem" +) + // StoreName is the default name for the store container -var StoreName string = "ocis-store" // Store interacts with the filesystem to manage account information type Store struct { @@ -21,90 +26,75 @@ type Store struct { Logger olog.Logger } -// New returns a new stor. TODO add mountPath as a flag. Accept a *config argument -func New() *Store { +// New creates a new store +func New(cfg *config.Config) account.Manager { s := Store{ - Logger: olog.NewLogger(), + Logger: olog.NewLogger(olog.Name("ocis-accounts")), } - // default to the current working directory if not configured - dir, err := filepath.Abs(filepath.Dir(os.Args[0])) - if err != nil { - s.Logger.Err(err).Msg("initializing accounts store") - } - - dest := filepath.Join(dir, StoreName) + dest := filepath.Join(cfg.MountPath, StoreName) if _, err := os.Stat(dest); err != nil { s.Logger.Info().Msgf("creating container on %v", dest) - os.Mkdir(dest, 0700) + err := os.MkdirAll(dest, 0700) + if err != nil { + s.Logger.Err(err).Msgf("providing container on %v", dest) + } } s.mountPath = dest return &s } -// Init implements the store interface -func (s Store) Init(...mstore.Option) error { - return nil -} - // List returns all the identities in the mountPath folder -func (s Store) List() ([]*mstore.Record, error) { - records := []*mstore.Record{} +func (s Store) List() []*account.Record { + records := []*account.Record{} identities, err := ioutil.ReadDir(s.mountPath) if err != nil { s.Logger.Err(err).Msgf("error reading %v", s.mountPath) + return records } s.Logger.Info().Msg("listing identities") for _, v := range identities { - records = append(records, &mstore.Record{ + records = append(records, &account.Record{ Key: v.Name(), }) } - return records, nil + return records } // Read implements the store interface. This implementation only reads by id. -func (s Store) Read(key string, opts ...mstore.ReadOption) ([]*mstore.Record, error) { +func (s Store) Read(key string) *account.Record { contents, err := ioutil.ReadFile(path.Join(s.mountPath, key)) if err != nil { s.Logger.Err(err).Msgf("error reading contents of key %v: file not found", key) - return []*mstore.Record{}, err + return &account.Record{} } - return []*mstore.Record{ - &mstore.Record{ - Key: key, - Value: contents, - }, - }, nil + return &account.Record{ + Key: key, + Value: contents, + } } // Write implements the store interface -func (s Store) Write(rec *mstore.Record) error { +func (s Store) Write(rec *account.Record) *account.Record { path := filepath.Join(s.mountPath, rec.Key) if len(rec.Key) < 1 { s.Logger.Error().Msg("key cannot be empty") - return fmt.Errorf("%v", "key is empty") + return &account.Record{} } if err := ioutil.WriteFile(path, rec.Value, 0644); err != nil { - return err + return &account.Record{} } s.Logger.Info().Msgf("%v bytes written to %v", len(rec.Value), path) - return nil + return rec } -// Delete implements the store interface -func (s Store) Delete(key string) error { - return nil -} - -// String implements the store interface, and the stringer interface -func (s Store) String() string { - return "store" +func init() { + account.Registry[managerName] = New } diff --git a/pkg/store/registry.go b/pkg/store/registry.go new file mode 100644 index 000000000..e4a32c66d --- /dev/null +++ b/pkg/store/registry.go @@ -0,0 +1,5 @@ +package store + +import ( + _ "github.com/owncloud/ocis-accounts/pkg/store/filesystem" +)