From 3f04adf4ff75cf8284f0d0ba0703005a913fd8db Mon Sep 17 00:00:00 2001 From: Benedikt Kulmann Date: Tue, 30 Jun 2020 12:56:46 +0200 Subject: [PATCH] Set up http server - set up http server for a) handling the service requests and b) serving the static assets for the ocis-web extension - align the server command with other ocis-extensions, including config variable naming --- pkg/command/server.go | 103 +++++++++++++++++++++++++++----------- pkg/config/config.go | 15 ++++++ pkg/flagset/flagset.go | 55 ++++++++++++-------- pkg/metrics/metrics.go | 22 ++++++++ pkg/server/grpc/option.go | 23 +++------ pkg/server/grpc/server.go | 10 ++-- pkg/server/http/option.go | 76 ++++++++++++++++++++++++++++ pkg/server/http/server.go | 71 ++++++++++++++++++++++++++ 8 files changed, 306 insertions(+), 69 deletions(-) create mode 100644 pkg/metrics/metrics.go create mode 100644 pkg/server/http/option.go create mode 100644 pkg/server/http/server.go diff --git a/pkg/command/server.go b/pkg/command/server.go index 57ba1f522..7343e93ae 100644 --- a/pkg/command/server.go +++ b/pkg/command/server.go @@ -2,14 +2,17 @@ package command import ( "context" - "syscall" - - "github.com/owncloud/ocis-accounts/pkg/flagset" + "os" + "os/signal" + "strings" "github.com/micro/cli/v2" "github.com/oklog/run" "github.com/owncloud/ocis-accounts/pkg/config" + "github.com/owncloud/ocis-accounts/pkg/flagset" + "github.com/owncloud/ocis-accounts/pkg/metrics" "github.com/owncloud/ocis-accounts/pkg/server/grpc" + "github.com/owncloud/ocis-accounts/pkg/server/http" svc "github.com/owncloud/ocis-accounts/pkg/service/v0" ) @@ -20,41 +23,83 @@ func Server(cfg *config.Config) *cli.Command { Usage: "Start ocis accounts service", Description: "uses an LDAP server as the storage backend", Flags: flagset.ServerWithConfig(cfg), - Before: func(c *cli.Context) error { - return ParseConfig(c, cfg) + Before: func(ctx *cli.Context) error { + if cfg.HTTP.Root != "/" { + cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") + } + + // When running on single binary mode the before hook from the root command won't get called. We manually + // call this before hook from ocis command, so the configuration can be loaded. + return ParseConfig(ctx, cfg) }, Action: func(c *cli.Context) error { logger := NewLogger(cfg) - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - service := grpc.NewService( - grpc.Logger(logger), - grpc.Context(ctx), - grpc.Config(cfg), - grpc.Name(cfg.Server.Name), - grpc.Namespace(cfg.Server.Namespace), - grpc.Address(cfg.Server.Address), - grpc.Flags(flagset.RootWithConfig(config.New())), + var ( + gr = run.Group{} + ctx, cancel = context.WithCancel(context.Background()) + mtrcs = metrics.New() ) - gr.Add(func() error { - logger.Info().Str("service", service.Name()).Msg("Reporting settings bundle to account service") - go svc.RegisterSettingsBundles(&logger) - return service.Run() - }, func(err error) { - if err != nil { - logger.Error().Err(err).Msg("account service died") - } else { + defer cancel() + + { + server := http.Server( + http.Logger(logger), + http.Name(cfg.Server.Name), + http.Context(ctx), + http.Config(cfg), + http.Metrics(mtrcs), + http.Flags(flagset.RootWithConfig(cfg)), + http.Flags(flagset.ServerWithConfig(cfg)), + ) + + gr.Add(server.Run, func(_ error) { logger.Info(). - Str("service", service.Name()). + Str("server", "http"). Msg("Shutting down server") - } - cancel() - }) + + cancel() + }) + } + + { + server := grpc.Server( + grpc.Logger(logger), + grpc.Name(cfg.Server.Name), + grpc.Context(ctx), + grpc.Config(cfg), + grpc.Metrics(mtrcs), + ) + + gr.Add(func() error { + logger.Info().Str("service", server.Name()).Msg("Reporting settings bundles to settings service") + go svc.RegisterSettingsBundles(&logger) + return server.Run() + }, func(_ error) { + logger.Info(). + Str("server", "grpc"). + Msg("Shutting down server") + + cancel() + }) + } + + { + stop := make(chan os.Signal, 1) + + gr.Add(func() error { + signal.Notify(stop, os.Interrupt) + + <-stop + + return nil + }, func(err error) { + close(stop) + cancel() + }) + } - run.SignalHandler(ctx, syscall.SIGKILL) return gr.Run() }, } diff --git a/pkg/config/config.go b/pkg/config/config.go index 2cafaddae..5e164c5e9 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -24,6 +24,19 @@ type LDAPSchema struct { Groups string } +// HTTP defines the available http configuration. +type HTTP struct { + Addr string + Namespace string + Root string +} + +// GRPC defines the available grpc configuration. +type GRPC struct { + Addr string + Namespace string +} + // Server configures a server. type Server struct { Name string @@ -42,6 +55,8 @@ type Log struct { // Config merges all Account config parameters. type Config struct { LDAP LDAP + HTTP HTTP + GRPC GRPC Server Server Log Log } diff --git a/pkg/flagset/flagset.go b/pkg/flagset/flagset.go index 3be48c473..c52509a7b 100644 --- a/pkg/flagset/flagset.go +++ b/pkg/flagset/flagset.go @@ -35,36 +35,51 @@ func RootWithConfig(cfg *config.Config) []cli.Flag { // ServerWithConfig applies cfg to the root flagset func ServerWithConfig(cfg *config.Config) []cli.Flag { return []cli.Flag{ + &cli.StringFlag{ + Name: "http-namespace", + Value: "com.owncloud.web", + Usage: "Set the base namespace for the http namespace", + EnvVars: []string{"ACCOUNTS_HTTP_NAMESPACE"}, + Destination: &cfg.HTTP.Namespace, + }, + &cli.StringFlag{ + Name: "http-addr", + Value: "localhost:9181", + Usage: "Address to bind http server", + EnvVars: []string{"ACCOUNTS_HTTP_ADDR"}, + Destination: &cfg.HTTP.Addr, + }, + &cli.StringFlag{ + Name: "http-root", + Value: "/", + Usage: "Root path of http server", + EnvVars: []string{"ACCOUNTS_HTTP_ROOT"}, + Destination: &cfg.HTTP.Root, + }, + &cli.StringFlag{ + Name: "grpc-namespace", + Value: "com.owncloud.api", + Usage: "Set the base namespace for the grpc namespace", + EnvVars: []string{"ACCOUNTS_GRPC_NAMESPACE"}, + Destination: &cfg.GRPC.Namespace, + }, + &cli.StringFlag{ + Name: "grpc-addr", + Value: "localhost:9180", + Usage: "Address to bind grpc server", + EnvVars: []string{"ACCOUNTS_GRPC_ADDR"}, + Destination: &cfg.GRPC.Addr, + }, &cli.StringFlag{ Name: "name", Value: "accounts", - DefaultText: "accounts", Usage: "service name", EnvVars: []string{"ACCOUNTS_NAME"}, Destination: &cfg.Server.Name, }, - &cli.StringFlag{ - Name: "namespace", - Aliases: []string{"ns"}, - Value: "com.owncloud.api", - DefaultText: "com.owncloud.api", - Usage: "namespace", - EnvVars: []string{"ACCOUNTS_NAMESPACE"}, - Destination: &cfg.Server.Namespace, - }, - &cli.StringFlag{ - Name: "address", - Aliases: []string{"addr"}, - Value: "localhost:9180", - DefaultText: "localhost:9180", - Usage: "service endpoint", - EnvVars: []string{"ACCOUNTS_ADDRESS"}, - Destination: &cfg.Server.Address, - }, &cli.StringFlag{ Name: "accounts-data-path", Value: "/var/tmp/ocis-accounts", - DefaultText: "/var/tmp/ocis-accounts", Usage: "accounts folder", EnvVars: []string{"ACCOUNTS_DATA_PATH"}, Destination: &cfg.Server.AccountsDataPath, diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go new file mode 100644 index 000000000..071272427 --- /dev/null +++ b/pkg/metrics/metrics.go @@ -0,0 +1,22 @@ +package metrics + +var ( + // Namespace defines the namespace for the defines metrics. + Namespace = "ocis" + + // Subsystem defines the subsystem for the defines metrics. + Subsystem = "accounts" +) + +// Metrics defines the available metrics of this service. +type Metrics struct { + // Counter *prometheus.CounterVec +} + +// New initializes the available metrics. +func New() *Metrics { + m := &Metrics{ + } + // TODO: implement metrics + return m +} diff --git a/pkg/server/grpc/option.go b/pkg/server/grpc/option.go index 8ccb9c4bf..06e8538e9 100644 --- a/pkg/server/grpc/option.go +++ b/pkg/server/grpc/option.go @@ -5,6 +5,7 @@ import ( "github.com/micro/cli/v2" "github.com/owncloud/ocis-accounts/pkg/config" + "github.com/owncloud/ocis-accounts/pkg/metrics" "github.com/owncloud/ocis-pkg/v2/log" ) @@ -14,11 +15,10 @@ type Option func(o *Options) // Options defines the available options for this package. type Options struct { Name string - Address string Logger log.Logger Context context.Context Config *config.Config - Namespace string + Metrics *metrics.Metrics Flags []cli.Flag } @@ -33,13 +33,6 @@ func newOptions(opts ...Option) Options { return opt } -// Logger provides a function to set the logger option. -func Logger(val log.Logger) Option { - return func(o *Options) { - o.Logger = val - } -} - // Name provides a name for the service. func Name(val string) Option { return func(o *Options) { @@ -47,10 +40,10 @@ func Name(val string) Option { } } -// Address provides an address for the service. -func Address(val string) Option { +// Logger provides a function to set the logger option. +func Logger(val log.Logger) Option { return func(o *Options) { - o.Address = val + o.Logger = val } } @@ -68,10 +61,10 @@ func Config(val *config.Config) Option { } } -// Namespace provides a function to set the namespace option. -func Namespace(val string) Option { +// Metrics provides a function to set the metrics option. +func Metrics(val *metrics.Metrics) Option { return func(o *Options) { - o.Namespace = val + o.Metrics = val } } diff --git a/pkg/server/grpc/server.go b/pkg/server/grpc/server.go index 6f8d34212..675286286 100644 --- a/pkg/server/grpc/server.go +++ b/pkg/server/grpc/server.go @@ -6,15 +6,15 @@ import ( "github.com/owncloud/ocis-pkg/v2/service/grpc" ) -// NewService initializes a new go-micro service ready to run -func NewService(opts ...Option) grpc.Service { +// Server initializes a new go-micro service ready to run +func Server(opts ...Option) grpc.Service { options := newOptions(opts...) service := grpc.NewService( - grpc.Name(options.Name), + grpc.Name(options.Config.Server.Name), grpc.Context(options.Context), - grpc.Address(options.Address), - grpc.Namespace(options.Namespace), + grpc.Address(options.Config.GRPC.Addr), + grpc.Namespace(options.Config.GRPC.Namespace), grpc.Logger(options.Logger), grpc.Flags(options.Flags...), ) diff --git a/pkg/server/http/option.go b/pkg/server/http/option.go new file mode 100644 index 000000000..3687b735b --- /dev/null +++ b/pkg/server/http/option.go @@ -0,0 +1,76 @@ +package http + +import ( + "context" + + "github.com/micro/cli/v2" + "github.com/owncloud/ocis-accounts/pkg/config" + "github.com/owncloud/ocis-accounts/pkg/metrics" + "github.com/owncloud/ocis-pkg/v2/log" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Name string + Logger log.Logger + Context context.Context + Config *config.Config + Metrics *metrics.Metrics + Flags []cli.Flag +} + +// newOptions initializes the available default options. +func newOptions(opts ...Option) Options { + opt := Options{} + + for _, o := range opts { + o(&opt) + } + + return opt +} + +// Name provides a name for the service. +func Name(val string) Option { + return func(o *Options) { + o.Name = val + } +} + +// Logger provides a function to set the logger option. +func Logger(val log.Logger) Option { + return func(o *Options) { + o.Logger = val + } +} + +// Context provides a function to set the context option. +func Context(val context.Context) Option { + return func(o *Options) { + o.Context = val + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} + +// Metrics provides a function to set the metrics option. +func Metrics(val *metrics.Metrics) Option { + return func(o *Options) { + o.Metrics = val + } +} + +// Flags provides a function to set the flags option. +func Flags(val []cli.Flag) Option { + return func(o *Options) { + o.Flags = append(o.Flags, val...) + } +} diff --git a/pkg/server/http/server.go b/pkg/server/http/server.go new file mode 100644 index 000000000..fdd58b8c6 --- /dev/null +++ b/pkg/server/http/server.go @@ -0,0 +1,71 @@ +package http + +import ( + "github.com/go-chi/chi" + "github.com/owncloud/ocis-accounts/pkg/assets" + "github.com/owncloud/ocis-accounts/pkg/proto/v0" + svc "github.com/owncloud/ocis-accounts/pkg/service/v0" + "github.com/owncloud/ocis-accounts/pkg/version" + "github.com/owncloud/ocis-pkg/v2/middleware" + "github.com/owncloud/ocis-pkg/v2/service/http" +) + +// Server initializes the http service and server. +func Server(opts ...Option) http.Service { + options := newOptions(opts...) + + service := http.NewService( + http.Logger(options.Logger), + http.Name(options.Name), + http.Version(version.String), + http.Address(options.Config.HTTP.Addr), + http.Namespace(options.Config.HTTP.Namespace), + http.Context(options.Context), + http.Flags(options.Flags...), + ) + + handler, err := svc.New(svc.Logger(options.Logger), svc.Config(options.Config)) + if err != nil { + options.Logger.Fatal().Err(err).Msg("could not initialize service handler") + } + + mux := chi.NewMux() + + mux.Use(middleware.RealIP) + mux.Use(middleware.RequestID) + mux.Use(middleware.Cache) + mux.Use(middleware.Cors) + mux.Use(middleware.Secure) + + mux.Use(middleware.Version( + options.Name, + version.String, + )) + + mux.Use(middleware.Logger( + options.Logger, + )) + + mux.Use(middleware.Static( + options.Config.HTTP.Root, + assets.New( + assets.Logger(options.Logger), + assets.Config(options.Config), + ), + )) + + mux.Route(options.Config.HTTP.Root, func(r chi.Router) { + proto.RegisterAccountsServiceWeb(r, handler) + proto.RegisterGroupsServiceWeb(r, handler) + }) + + service.Handle( + "/", + mux, + ) + + if err := service.Init(); err != nil { + panic(err) + } + return service +}