diff --git a/extensions/appprovider/cmd/appprovider/main.go b/extensions/appprovider/cmd/appprovider/main.go new file mode 100644 index 0000000000..decb2d8f3e --- /dev/null +++ b/extensions/appprovider/cmd/appprovider/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/appprovider/pkg/command" + "github.com/owncloud/ocis/extensions/appprovider/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/appprovider/pkg/command/command.go b/extensions/appprovider/pkg/command/command.go deleted file mode 100644 index a3abd22074..0000000000 --- a/extensions/appprovider/pkg/command/command.go +++ /dev/null @@ -1,165 +0,0 @@ -package command - -import ( - "context" - "flag" - "fmt" - "os" - "path" - - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/gofrs/uuid" - "github.com/oklog/run" - "github.com/owncloud/ocis/extensions/appprovider/pkg/config" - "github.com/owncloud/ocis/extensions/appprovider/pkg/config/parser" - "github.com/owncloud/ocis/extensions/appprovider/pkg/server/debug" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" -) - -// AppProvider is the entrypoint for the app provider command. -func AppProvider(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "app-provider", - Usage: "start appprovider for providing apps", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - uuid := uuid.Must(uuid.NewV4()) - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+uuid.String()+".pid") - - rcfg := appProviderConfigFromStruct(c, cfg) - - gr.Add(func() error { - runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) - return nil - }, func(_ error) { - logger.Info(). - Str("server", c.Command.Name). - Msg("Shutting down server") - - cancel() - }) - - debugServer, err := debug.Server( - debug.Logger(logger), - debug.Context(ctx), - debug.Config(cfg), - ) - - if err != nil { - logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") - return err - } - - gr.Add(debugServer.ListenAndServe, func(_ error) { - cancel() - }) - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() - }, - } -} - -// appProviderConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func appProviderConfigFromStruct(c *cli.Context, cfg *config.Config) map[string]interface{} { - - rcfg := map[string]interface{}{ - "core": map[string]interface{}{ - "tracing_enabled": cfg.Tracing.Enabled, - "tracing_endpoint": cfg.Tracing.Endpoint, - "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, - }, - "shared": map[string]interface{}{ - "jwt_secret": cfg.TokenManager.JWTSecret, - "gatewaysvc": cfg.Reva.Address, - }, - "grpc": map[string]interface{}{ - "network": cfg.GRPC.Protocol, - "address": cfg.GRPC.Addr, - // TODO build services dynamically - "services": map[string]interface{}{ - "appprovider": map[string]interface{}{ - "app_provider_url": cfg.ExternalAddr, - "driver": cfg.Driver, - "drivers": map[string]interface{}{ - "wopi": map[string]interface{}{ - "app_api_key": cfg.Drivers.WOPI.AppAPIKey, - "app_desktop_only": cfg.Drivers.WOPI.AppDesktopOnly, - "app_icon_uri": cfg.Drivers.WOPI.AppIconURI, - "app_int_url": cfg.Drivers.WOPI.AppInternalURL, - "app_name": cfg.Drivers.WOPI.AppName, - "app_url": cfg.Drivers.WOPI.AppURL, - "insecure_connections": cfg.Drivers.WOPI.Insecure, - "iop_secret": cfg.Drivers.WOPI.IopSecret, - "jwt_secret": cfg.TokenManager.JWTSecret, - "wopi_url": cfg.Drivers.WOPI.WopiURL, - }, - }, - }, - }, - }, - } - return rcfg -} - -// AppProviderSutureService allows for the app-provider command to be embedded and supervised by a suture supervisor tree. -type AppProviderSutureService struct { - cfg *config.Config -} - -// NewAppProvider creates a new store.AppProviderSutureService -func NewAppProvider(cfg *ociscfg.Config) suture.Service { - cfg.AppProvider.Commons = cfg.Commons - return AppProviderSutureService{ - cfg: cfg.AppProvider, - } -} - -func (s AppProviderSutureService) Serve(ctx context.Context) error { - cmd := AppProvider(s.cfg) - f := &flag.FlagSet{} - cmdFlags := cmd.Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if cmd.Before != nil { - if err := cmd.Before(cliCtx); err != nil { - return err - } - } - if err := cmd.Action(cliCtx); err != nil { - return err - } - - return nil -} diff --git a/extensions/appprovider/pkg/command/health.go b/extensions/appprovider/pkg/command/health.go new file mode 100644 index 0000000000..4306bbf926 --- /dev/null +++ b/extensions/appprovider/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/appprovider/pkg/config" + "github.com/owncloud/ocis/extensions/appprovider/pkg/config/parser" + "github.com/owncloud/ocis/extensions/appprovider/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/appprovider/pkg/command/root.go b/extensions/appprovider/pkg/command/root.go new file mode 100644 index 0000000000..6cede536d0 --- /dev/null +++ b/extensions/appprovider/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/appprovider/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-accounts command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "ocis-appprovider", + Usage: "Provide apps for oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the accounts command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new accounts.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.AppProvider.Commons = cfg.Commons + return SutureService{ + cfg: cfg.AppProvider, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/appprovider/pkg/command/server.go b/extensions/appprovider/pkg/command/server.go new file mode 100644 index 0000000000..282ee46cbc --- /dev/null +++ b/extensions/appprovider/pkg/command/server.go @@ -0,0 +1,107 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/appprovider/pkg/config" + "github.com/owncloud/ocis/extensions/appprovider/pkg/config/parser" + "github.com/owncloud/ocis/extensions/appprovider/pkg/logging" + "github.com/owncloud/ocis/extensions/appprovider/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/appprovider/pkg/server/debug" + "github.com/owncloud/ocis/extensions/appprovider/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/sync" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.AppProviderConfigFromStruct(cfg) + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + if err := external.RegisterGRPCEndpoint( + ctx, + cfg.GRPC.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.GRPC.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/appprovider/pkg/command/version.go b/extensions/appprovider/pkg/command/version.go new file mode 100644 index 0000000000..3558839521 --- /dev/null +++ b/extensions/appprovider/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/appprovider/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/appprovider/pkg/config/config.go b/extensions/appprovider/pkg/config/config.go index c8be7463a1..84e77c4d42 100644 --- a/extensions/appprovider/pkg/config/config.go +++ b/extensions/appprovider/pkg/config/config.go @@ -1,14 +1,17 @@ package config -import "github.com/owncloud/ocis/ocis-pkg/shared" +import ( + "context" + + "github.com/owncloud/ocis/ocis-pkg/shared" +) type Config struct { *shared.Commons `yaml:"-"` Service Service `yaml:"-"` Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` + Log *Log `yaml:"log"` Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` GRPC GRPCConfig `yaml:"grpc"` @@ -18,6 +21,9 @@ type Config struct { ExternalAddr string `yaml:"external_addr"` Driver string `yaml:"driver"` Drivers Drivers `yaml:"drivers"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` } type Tracing struct { @@ -27,7 +33,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;APP_PROVIDER_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;APP_PROVIDER_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;APP_PROVIDER_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;APP_PROVIDER_LOG_COLOR" desc:"Activates colorized log output."` @@ -46,8 +52,9 @@ type Debug struct { } type GRPCConfig struct { - Addr string `yaml:"addr" env:"APP_PROVIDER_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"APP_PROVIDER_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` + Addr string `yaml:"addr" env:"APP_PROVIDER_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"APP_PROVIDER_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` } type Drivers struct { diff --git a/extensions/appprovider/pkg/config/defaults/defaultconfig.go b/extensions/appprovider/pkg/config/defaults/defaultconfig.go index 978c6d2edb..c42611f739 100644 --- a/extensions/appprovider/pkg/config/defaults/defaultconfig.go +++ b/extensions/appprovider/pkg/config/defaults/defaultconfig.go @@ -21,8 +21,9 @@ func DefaultConfig() *config.Config { Zpages: false, }, GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9164", - Protocol: "tcp", + Addr: "127.0.0.1:9164", + Namespace: "com.owncloud.api", + Protocol: "tcp", }, Service: config.Service{ Name: "appprovider", @@ -39,15 +40,15 @@ func DefaultConfig() *config.Config { func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/appprovider/pkg/logging/logging.go b/extensions/appprovider/pkg/logging/logging.go new file mode 100644 index 0000000000..91ed77da75 --- /dev/null +++ b/extensions/appprovider/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/appprovider/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/appprovider/pkg/revaconfig/config.go b/extensions/appprovider/pkg/revaconfig/config.go new file mode 100644 index 0000000000..9d1ebf814f --- /dev/null +++ b/extensions/appprovider/pkg/revaconfig/config.go @@ -0,0 +1,47 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/extensions/appprovider/pkg/config" +) + +// AppProviderConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func AppProviderConfigFromStruct(cfg *config.Config) map[string]interface{} { + + rcfg := map[string]interface{}{ + "core": map[string]interface{}{ + "tracing_enabled": cfg.Tracing.Enabled, + "tracing_endpoint": cfg.Tracing.Endpoint, + "tracing_collector": cfg.Tracing.Collector, + "tracing_service_name": cfg.Service.Name, + }, + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, + }, + "grpc": map[string]interface{}{ + "network": cfg.GRPC.Protocol, + "address": cfg.GRPC.Addr, + "services": map[string]interface{}{ + "appprovider": map[string]interface{}{ + "app_provider_url": cfg.ExternalAddr, + "driver": cfg.Driver, + "drivers": map[string]interface{}{ + "wopi": map[string]interface{}{ + "app_api_key": cfg.Drivers.WOPI.AppAPIKey, + "app_desktop_only": cfg.Drivers.WOPI.AppDesktopOnly, + "app_icon_uri": cfg.Drivers.WOPI.AppIconURI, + "app_int_url": cfg.Drivers.WOPI.AppInternalURL, + "app_name": cfg.Drivers.WOPI.AppName, + "app_url": cfg.Drivers.WOPI.AppURL, + "insecure_connections": cfg.Drivers.WOPI.Insecure, + "iop_secret": cfg.Drivers.WOPI.IopSecret, + "jwt_secret": cfg.TokenManager.JWTSecret, + "wopi_url": cfg.Drivers.WOPI.WopiURL, + }, + }, + }, + }, + }, + } + return rcfg +} diff --git a/extensions/appprovider/pkg/tracing/tracing.go b/extensions/appprovider/pkg/tracing/tracing.go new file mode 100644 index 0000000000..a2f1e636e8 --- /dev/null +++ b/extensions/appprovider/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/appprovider/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the proxy service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/audit/pkg/command/root.go b/extensions/audit/pkg/command/root.go index d3cd62ee46..0920b22629 100644 --- a/extensions/audit/pkg/command/root.go +++ b/extensions/audit/pkg/command/root.go @@ -48,7 +48,7 @@ type SutureService struct { // NewSutureService creates a new audit.SutureService func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Settings.Commons = cfg.Commons + cfg.Audit.Commons = cfg.Commons return SutureService{ cfg: cfg.Audit, } diff --git a/extensions/auth-basic/cmd/auth-basic/main.go b/extensions/auth-basic/cmd/auth-basic/main.go new file mode 100644 index 0000000000..de391e6c67 --- /dev/null +++ b/extensions/auth-basic/cmd/auth-basic/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/auth-basic/pkg/command" + "github.com/owncloud/ocis/extensions/auth-basic/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/auth-basic/pkg/command/command.go b/extensions/auth-basic/pkg/command/command.go deleted file mode 100644 index ce77c11685..0000000000 --- a/extensions/auth-basic/pkg/command/command.go +++ /dev/null @@ -1,223 +0,0 @@ -package command - -import ( - "context" - "flag" - "fmt" - "os" - "path" - "path/filepath" - - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/gofrs/uuid" - "github.com/oklog/run" - "github.com/owncloud/ocis/extensions/auth-basic/pkg/config" - "github.com/owncloud/ocis/extensions/auth-basic/pkg/config/parser" - "github.com/owncloud/ocis/extensions/auth-basic/pkg/server/debug" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/ldap" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" -) - -// Command is the entrypoint for the auth-basic command. -func AuthBasic(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "auth-basic", - Usage: "start authprovider for basic auth", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // pre-create folders - if cfg.AuthProvider == "json" && cfg.AuthProviders.JSON.File != "" { - if err := os.MkdirAll(filepath.Dir(cfg.AuthProviders.JSON.File), os.FileMode(0700)); err != nil { - return err - } - } - - uuid := uuid.Must(uuid.NewV4()) - - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+uuid.String()+".pid") - - rcfg := authBasicConfigFromStruct(c, cfg) - logger.Debug(). - Str("server", "authbasic"). - Interface("reva-config", rcfg). - Msg("config") - - if cfg.AuthProvider == "ldap" { - ldapCfg := cfg.AuthProviders.LDAP - if err := ldap.WaitForCA(logger, ldapCfg.Insecure, ldapCfg.CACert); err != nil { - logger.Error().Err(err).Msg("The configured LDAP CA cert does not exist") - return err - } - } - - gr.Add(func() error { - runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) - return nil - }, func(_ error) { - logger.Info(). - Str("server", c.Command.Name). - Msg("Shutting down server") - - cancel() - }) - - debugServer, err := debug.Server( - debug.Logger(logger), - debug.Context(ctx), - debug.Config(cfg), - ) - - if err != nil { - logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") - return err - } - - gr.Add(debugServer.ListenAndServe, func(_ error) { - cancel() - }) - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() - }, - } -} - -// authBasicConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func authBasicConfigFromStruct(c *cli.Context, cfg *config.Config) map[string]interface{} { - rcfg := map[string]interface{}{ - "core": map[string]interface{}{ - "tracing_enabled": cfg.Tracing.Enabled, - "tracing_endpoint": cfg.Tracing.Endpoint, - "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, - }, - "shared": map[string]interface{}{ - "jwt_secret": cfg.TokenManager.JWTSecret, - "gatewaysvc": cfg.Reva.Address, - "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, - }, - "grpc": map[string]interface{}{ - "network": cfg.GRPC.Protocol, - "address": cfg.GRPC.Addr, - // TODO build services dynamically - "services": map[string]interface{}{ - "authprovider": map[string]interface{}{ - "auth_manager": cfg.AuthProvider, - "auth_managers": map[string]interface{}{ - "json": map[string]interface{}{ - "users": cfg.AuthProviders.JSON.File, - }, - "ldap": ldapConfigFromString(cfg.AuthProviders.LDAP), - "owncloudsql": map[string]interface{}{ - "dbusername": cfg.AuthProviders.OwnCloudSQL.DBUsername, - "dbpassword": cfg.AuthProviders.OwnCloudSQL.DBPassword, - "dbhost": cfg.AuthProviders.OwnCloudSQL.DBHost, - "dbport": cfg.AuthProviders.OwnCloudSQL.DBPort, - "dbname": cfg.AuthProviders.OwnCloudSQL.DBName, - "idp": cfg.AuthProviders.OwnCloudSQL.IDP, - "nobody": cfg.AuthProviders.OwnCloudSQL.Nobody, - "join_username": cfg.AuthProviders.OwnCloudSQL.JoinUsername, - "join_ownclouduuid": cfg.AuthProviders.OwnCloudSQL.JoinOwnCloudUUID, - }, - }, - }, - }, - }, - } - return rcfg -} - -// AuthBasicSutureService allows for the storage-authbasic command to be embedded and supervised by a suture supervisor tree. -type AuthBasicSutureService struct { - cfg *config.Config -} - -// NewAuthBasicSutureService creates a new store.AuthBasicSutureService -func NewAuthBasic(cfg *ociscfg.Config) suture.Service { - cfg.AuthBasic.Commons = cfg.Commons - return AuthBasicSutureService{ - cfg: cfg.AuthBasic, - } -} - -func (s AuthBasicSutureService) Serve(ctx context.Context) error { - f := &flag.FlagSet{} - cmdFlags := AuthBasic(s.cfg).Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if AuthBasic(s.cfg).Before != nil { - if err := AuthBasic(s.cfg).Before(cliCtx); err != nil { - return err - } - } - if err := AuthBasic(s.cfg).Action(cliCtx); err != nil { - return err - } - - return nil -} - -func ldapConfigFromString(cfg config.LDAPProvider) map[string]interface{} { - return map[string]interface{}{ - "uri": cfg.URI, - "cacert": cfg.CACert, - "insecure": cfg.Insecure, - "bind_username": cfg.BindDN, - "bind_password": cfg.BindPassword, - "user_base_dn": cfg.UserBaseDN, - "group_base_dn": cfg.GroupBaseDN, - "user_filter": cfg.UserFilter, - "group_filter": cfg.GroupFilter, - "user_scope": cfg.UserScope, - "group_scope": cfg.GroupScope, - "user_objectclass": cfg.UserObjectClass, - "group_objectclass": cfg.GroupObjectClass, - "login_attributes": cfg.LoginAttributes, - "idp": cfg.IDP, - "user_schema": map[string]interface{}{ - "id": cfg.UserSchema.ID, - "idIsOctetString": cfg.UserSchema.IDIsOctetString, - "mail": cfg.UserSchema.Mail, - "displayName": cfg.UserSchema.DisplayName, - "userName": cfg.UserSchema.Username, - }, - "group_schema": map[string]interface{}{ - "id": cfg.GroupSchema.ID, - "idIsOctetString": cfg.GroupSchema.IDIsOctetString, - "mail": cfg.GroupSchema.Mail, - "displayName": cfg.GroupSchema.DisplayName, - "groupName": cfg.GroupSchema.Groupname, - "member": cfg.GroupSchema.Member, - }, - } -} diff --git a/extensions/auth-basic/pkg/command/health.go b/extensions/auth-basic/pkg/command/health.go new file mode 100644 index 0000000000..877e1d0c1b --- /dev/null +++ b/extensions/auth-basic/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/auth-basic/pkg/config" + "github.com/owncloud/ocis/extensions/auth-basic/pkg/config/parser" + "github.com/owncloud/ocis/extensions/auth-basic/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/auth-basic/pkg/command/root.go b/extensions/auth-basic/pkg/command/root.go new file mode 100644 index 0000000000..3dff2f589d --- /dev/null +++ b/extensions/auth-basic/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/auth-basic/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-auth-basic command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "ocis-auth-basic", + Usage: "Provide basic authentication for oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the auth-basic command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new auth-basic.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.AuthBasic.Commons = cfg.Commons + return SutureService{ + cfg: cfg.AuthBasic, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/auth-basic/pkg/command/server.go b/extensions/auth-basic/pkg/command/server.go new file mode 100644 index 0000000000..642bb75903 --- /dev/null +++ b/extensions/auth-basic/pkg/command/server.go @@ -0,0 +1,120 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/auth-basic/pkg/config" + "github.com/owncloud/ocis/extensions/auth-basic/pkg/config/parser" + "github.com/owncloud/ocis/extensions/auth-basic/pkg/logging" + "github.com/owncloud/ocis/extensions/auth-basic/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/auth-basic/pkg/server/debug" + "github.com/owncloud/ocis/extensions/auth-basic/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/ldap" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/sync" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.AuthBasicConfigFromStruct(cfg) + + // the reva runtime calls os.Exit in the case of a failure and there is no way for the oCIS + // runtime to catch it and restart a reva service. Therefore we need to ensure the service has + // everything it needs, before starting the service. + // In this case: CA certificates + if cfg.AuthProvider == "ldap" { + ldapCfg := cfg.AuthProviders.LDAP + if err := ldap.WaitForCA(logger, ldapCfg.Insecure, ldapCfg.CACert); err != nil { + logger.Error().Err(err).Msg("The configured LDAP CA cert does not exist") + return err + } + } + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + if err := external.RegisterGRPCEndpoint( + ctx, + cfg.GRPC.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.GRPC.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/auth-basic/pkg/command/version.go b/extensions/auth-basic/pkg/command/version.go new file mode 100644 index 0000000000..f6c6415e74 --- /dev/null +++ b/extensions/auth-basic/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/auth-basic/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/auth-basic/pkg/config/config.go b/extensions/auth-basic/pkg/config/config.go index 8229ec1894..ad70ebbd78 100644 --- a/extensions/auth-basic/pkg/config/config.go +++ b/extensions/auth-basic/pkg/config/config.go @@ -1,14 +1,17 @@ package config -import "github.com/owncloud/ocis/ocis-pkg/shared" +import ( + "context" + + "github.com/owncloud/ocis/ocis-pkg/shared" +) type Config struct { *shared.Commons `yaml:"-"` Service Service `yaml:"-"` Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` + Log *Log `yaml:"log"` Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` GRPC GRPCConfig `yaml:"grpc"` @@ -18,6 +21,9 @@ type Config struct { SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token"` AuthProvider string `yaml:"auth_provider" env:"AUTH_BASIC_AUTH_PROVIDER" desc:"The auth provider which should be used by the service"` AuthProviders AuthProviders `yaml:"auth_providers"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;AUTH_BASIC_TRACING_ENABLED" desc:"Activates tracing."` @@ -26,7 +32,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;AUTH_BASIC_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;AUTH_BASIC_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;AUTH_BASIC_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;AUTH_BASIC_LOG_COLOR" desc:"Activates colorized log output."` @@ -45,8 +51,9 @@ type Debug struct { } type GRPCConfig struct { - Addr string `yaml:"addr" env:"AUTH_BASIC_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"AUTH_BASIC_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` + Addr string `yaml:"addr" env:"AUTH_BASIC_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"AUTH_BASIC_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` } type AuthProviders struct { diff --git a/extensions/auth-basic/pkg/config/defaults/defaultconfig.go b/extensions/auth-basic/pkg/config/defaults/defaultconfig.go index 3bfbaf800f..66e3d4d93a 100644 --- a/extensions/auth-basic/pkg/config/defaults/defaultconfig.go +++ b/extensions/auth-basic/pkg/config/defaults/defaultconfig.go @@ -24,6 +24,7 @@ func DefaultConfig() *config.Config { }, GRPC: config.GRPCConfig{ Addr: "127.0.0.1:9146", + Namespace: "com.owncloud.api", Protocol: "tcp", }, Service: config.Service{ @@ -80,15 +81,15 @@ func DefaultConfig() *config.Config { func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/auth-basic/pkg/logging/logging.go b/extensions/auth-basic/pkg/logging/logging.go new file mode 100644 index 0000000000..3f997d8502 --- /dev/null +++ b/extensions/auth-basic/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/auth-basic/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/auth-basic/pkg/revaconfig/config.go b/extensions/auth-basic/pkg/revaconfig/config.go new file mode 100644 index 0000000000..59ea585f76 --- /dev/null +++ b/extensions/auth-basic/pkg/revaconfig/config.go @@ -0,0 +1,83 @@ +package revaconfig + +import "github.com/owncloud/ocis/extensions/auth-basic/pkg/config" + +// AuthBasicConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func AuthBasicConfigFromStruct(cfg *config.Config) map[string]interface{} { + rcfg := map[string]interface{}{ + "core": map[string]interface{}{ + "tracing_enabled": cfg.Tracing.Enabled, + "tracing_endpoint": cfg.Tracing.Endpoint, + "tracing_collector": cfg.Tracing.Collector, + "tracing_service_name": cfg.Service.Name, + }, + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, + "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, + }, + "grpc": map[string]interface{}{ + "network": cfg.GRPC.Protocol, + "address": cfg.GRPC.Addr, + // TODO build services dynamically + "services": map[string]interface{}{ + "authprovider": map[string]interface{}{ + "auth_manager": cfg.AuthProvider, + "auth_managers": map[string]interface{}{ + "json": map[string]interface{}{ + "users": cfg.AuthProviders.JSON.File, + }, + "ldap": ldapConfigFromString(cfg.AuthProviders.LDAP), + "owncloudsql": map[string]interface{}{ + "dbusername": cfg.AuthProviders.OwnCloudSQL.DBUsername, + "dbpassword": cfg.AuthProviders.OwnCloudSQL.DBPassword, + "dbhost": cfg.AuthProviders.OwnCloudSQL.DBHost, + "dbport": cfg.AuthProviders.OwnCloudSQL.DBPort, + "dbname": cfg.AuthProviders.OwnCloudSQL.DBName, + "idp": cfg.AuthProviders.OwnCloudSQL.IDP, + "nobody": cfg.AuthProviders.OwnCloudSQL.Nobody, + "join_username": cfg.AuthProviders.OwnCloudSQL.JoinUsername, + "join_ownclouduuid": cfg.AuthProviders.OwnCloudSQL.JoinOwnCloudUUID, + }, + }, + }, + }, + }, + } + return rcfg +} + +func ldapConfigFromString(cfg config.LDAPProvider) map[string]interface{} { + return map[string]interface{}{ + "uri": cfg.URI, + "cacert": cfg.CACert, + "insecure": cfg.Insecure, + "bind_username": cfg.BindDN, + "bind_password": cfg.BindPassword, + "user_base_dn": cfg.UserBaseDN, + "group_base_dn": cfg.GroupBaseDN, + "user_filter": cfg.UserFilter, + "group_filter": cfg.GroupFilter, + "user_scope": cfg.UserScope, + "group_scope": cfg.GroupScope, + "user_objectclass": cfg.UserObjectClass, + "group_objectclass": cfg.GroupObjectClass, + "login_attributes": cfg.LoginAttributes, + "idp": cfg.IDP, + "user_schema": map[string]interface{}{ + "id": cfg.UserSchema.ID, + "idIsOctetString": cfg.UserSchema.IDIsOctetString, + "mail": cfg.UserSchema.Mail, + "displayName": cfg.UserSchema.DisplayName, + "userName": cfg.UserSchema.Username, + }, + "group_schema": map[string]interface{}{ + "id": cfg.GroupSchema.ID, + "idIsOctetString": cfg.GroupSchema.IDIsOctetString, + "mail": cfg.GroupSchema.Mail, + "displayName": cfg.GroupSchema.DisplayName, + "groupName": cfg.GroupSchema.Groupname, + "member": cfg.GroupSchema.Member, + }, + } +} diff --git a/extensions/auth-basic/pkg/tracing/tracing.go b/extensions/auth-basic/pkg/tracing/tracing.go new file mode 100644 index 0000000000..7f7180555e --- /dev/null +++ b/extensions/auth-basic/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/auth-basic/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the proxy service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/auth-bearer/cmd/auth-bearer/main.go b/extensions/auth-bearer/cmd/auth-bearer/main.go new file mode 100644 index 0000000000..024879e452 --- /dev/null +++ b/extensions/auth-bearer/cmd/auth-bearer/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/command" + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/auth-bearer/pkg/Makefile b/extensions/auth-bearer/pkg/Makefile deleted file mode 100644 index 51243d83fc..0000000000 --- a/extensions/auth-bearer/pkg/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -SHELL := bash -NAME := ocs - -include ../../.make/recursion.mk - -############ tooling ############ -ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI -include ../../.bingo/Variables.mk -endif - -############ go tooling ############ -include ../../.make/go.mk - -############ release ############ -include ../../.make/release.mk - -############ docs generate ############ -include ../../.make/docs.mk - -.PHONY: docs-generate -docs-generate: config-docs-generate - -############ generate ############ -include ../../.make/generate.mk - -.PHONY: ci-go-generate -ci-go-generate: # CI runs ci-node-generate automatically before this target - -.PHONY: ci-node-generate -ci-node-generate: - -############ licenses ############ -.PHONY: ci-node-check-licenses -ci-node-check-licenses: - -.PHONY: ci-node-save-licenses -ci-node-save-licenses: diff --git a/extensions/auth-bearer/pkg/command/command.go b/extensions/auth-bearer/pkg/command/command.go deleted file mode 100644 index c5204321bb..0000000000 --- a/extensions/auth-bearer/pkg/command/command.go +++ /dev/null @@ -1,160 +0,0 @@ -package command - -import ( - "context" - "flag" - "fmt" - "os" - "path" - - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/gofrs/uuid" - "github.com/oklog/run" - "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config" - "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config/parser" - "github.com/owncloud/ocis/extensions/auth-bearer/pkg/server/debug" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" -) - -// AuthBearer is the entrypoint for the auth-bearer command. -func AuthBearer(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "auth-bearer", - Usage: "start authprovider for bearer auth", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - uuid := uuid.Must(uuid.NewV4()) - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+uuid.String()+".pid") - rcfg := authBearerConfigFromStruct(c, cfg) - - gr.Add(func() error { - runtime.RunWithOptions( - rcfg, - pidFile, - runtime.WithLogger(&logger.Logger), - ) - return nil - }, func(_ error) { - logger.Info(). - Str("server", c.Command.Name). - Msg("Shutting down server") - - cancel() - }) - - debugServer, err := debug.Server( - debug.Logger(logger), - debug.Context(ctx), - debug.Config(cfg), - ) - - if err != nil { - logger.Info().Err(err).Str("server", "debug").Msg("failed to initialize server") - return err - } - - gr.Add(debugServer.ListenAndServe, func(_ error) { - cancel() - }) - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() - }, - } -} - -// authBearerConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func authBearerConfigFromStruct(c *cli.Context, cfg *config.Config) map[string]interface{} { - return map[string]interface{}{ - "core": map[string]interface{}{ - "tracing_enabled": cfg.Tracing.Enabled, - "tracing_endpoint": cfg.Tracing.Endpoint, - "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, - }, - "shared": map[string]interface{}{ - "jwt_secret": cfg.TokenManager.JWTSecret, - "gatewaysvc": cfg.Reva.Address, - "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, - }, - "grpc": map[string]interface{}{ - "network": cfg.GRPC.Protocol, - "address": cfg.GRPC.Addr, - "services": map[string]interface{}{ - "authprovider": map[string]interface{}{ - "auth_manager": "oidc", - "auth_managers": map[string]interface{}{ - "oidc": map[string]interface{}{ - "issuer": cfg.OIDC.Issuer, - "insecure": cfg.OIDC.Insecure, - "id_claim": cfg.OIDC.IDClaim, - "uid_claim": cfg.OIDC.UIDClaim, - "gid_claim": cfg.OIDC.GIDClaim, - }, - }, - }, - }, - }, - } -} - -// AuthBearerSutureService allows for the storage-gateway command to be embedded and supervised by a suture supervisor tree. -type AuthBearerSutureService struct { - cfg *config.Config -} - -// NewAuthBearerSutureService creates a new gateway.AuthBearerSutureService -func NewAuthBearer(cfg *ociscfg.Config) suture.Service { - cfg.AuthBearer.Commons = cfg.Commons - return AuthBearerSutureService{ - cfg: cfg.AuthBearer, - } -} - -func (s AuthBearerSutureService) Serve(ctx context.Context) error { - cmd := AuthBearer(s.cfg) - f := &flag.FlagSet{} - cmdFlags := cmd.Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if cmd.Before != nil { - if err := cmd.Before(cliCtx); err != nil { - return err - } - } - if err := cmd.Action(cliCtx); err != nil { - return err - } - - return nil -} diff --git a/extensions/auth-bearer/pkg/command/health.go b/extensions/auth-bearer/pkg/command/health.go new file mode 100644 index 0000000000..c38aefdc71 --- /dev/null +++ b/extensions/auth-bearer/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config" + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config/parser" + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/auth-bearer/pkg/command/root.go b/extensions/auth-bearer/pkg/command/root.go new file mode 100644 index 0000000000..4589c17c30 --- /dev/null +++ b/extensions/auth-bearer/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-auth-bearer command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "ocis-auth-bearer", + Usage: "Provide apps for oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the accounts command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new auth-bearer.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.AuthBearer.Commons = cfg.Commons + return SutureService{ + cfg: cfg.AuthBearer, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/auth-bearer/pkg/command/server.go b/extensions/auth-bearer/pkg/command/server.go new file mode 100644 index 0000000000..c4d5211180 --- /dev/null +++ b/extensions/auth-bearer/pkg/command/server.go @@ -0,0 +1,107 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config" + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config/parser" + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/logging" + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/server/debug" + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/sync" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.AuthBearerConfigFromStruct(cfg) + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + if err := external.RegisterGRPCEndpoint( + ctx, + cfg.GRPC.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.GRPC.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/auth-bearer/pkg/command/version.go b/extensions/auth-bearer/pkg/command/version.go new file mode 100644 index 0000000000..c90f60e08f --- /dev/null +++ b/extensions/auth-bearer/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/auth-bearer/pkg/config/config.go b/extensions/auth-bearer/pkg/config/config.go index 8af579f5c0..eb769e8be6 100644 --- a/extensions/auth-bearer/pkg/config/config.go +++ b/extensions/auth-bearer/pkg/config/config.go @@ -1,14 +1,17 @@ package config -import "github.com/owncloud/ocis/ocis-pkg/shared" +import ( + "context" + + "github.com/owncloud/ocis/ocis-pkg/shared" +) type Config struct { *shared.Commons `yaml:"-"` Service Service `yaml:"-"` Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` + Log *Log `yaml:"log"` Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` GRPC GRPCConfig `yaml:"grpc"` @@ -17,6 +20,9 @@ type Config struct { SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token"` OIDC OIDC `yaml:"oidc"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;AUTH_BEARER_TRACING_ENABLED" desc:"Activates tracing."` @@ -25,7 +31,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;AUTH_BEARER_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;AUTH_BEARER_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;AUTH_BEARER_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;AUTH_BEARER_LOG_COLOR" desc:"Activates colorized log output."` @@ -44,8 +50,9 @@ type Debug struct { } type GRPCConfig struct { - Addr string `yaml:"addr" env:"AUTH_BEARER_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"AUTH_BEARER_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` + Addr string `yaml:"addr" env:"AUTH_BEARER_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"AUTH_BEARER_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` } type OIDC struct { diff --git a/extensions/auth-bearer/pkg/config/defaults/defaultconfig.go b/extensions/auth-bearer/pkg/config/defaults/defaultconfig.go index 1e3d500cb5..6c37acaf77 100644 --- a/extensions/auth-bearer/pkg/config/defaults/defaultconfig.go +++ b/extensions/auth-bearer/pkg/config/defaults/defaultconfig.go @@ -20,8 +20,9 @@ func DefaultConfig() *config.Config { Zpages: false, }, GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9148", - Protocol: "tcp", + Addr: "127.0.0.1:9148", + Namespace: "com.owncloud.api", + Protocol: "tcp", }, Service: config.Service{ Name: "auth-bearer", @@ -39,15 +40,15 @@ func DefaultConfig() *config.Config { func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/auth-bearer/pkg/logging/logging.go b/extensions/auth-bearer/pkg/logging/logging.go new file mode 100644 index 0000000000..c92d63022c --- /dev/null +++ b/extensions/auth-bearer/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/auth-bearer/pkg/revaconfig/config.go b/extensions/auth-bearer/pkg/revaconfig/config.go new file mode 100644 index 0000000000..bb88e4b088 --- /dev/null +++ b/extensions/auth-bearer/pkg/revaconfig/config.go @@ -0,0 +1,38 @@ +package revaconfig + +import "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config" + +// AuthBearerConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func AuthBearerConfigFromStruct(cfg *config.Config) map[string]interface{} { + return map[string]interface{}{ + "core": map[string]interface{}{ + "tracing_enabled": cfg.Tracing.Enabled, + "tracing_endpoint": cfg.Tracing.Endpoint, + "tracing_collector": cfg.Tracing.Collector, + "tracing_service_name": cfg.Service.Name, + }, + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, + "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, + }, + "grpc": map[string]interface{}{ + "network": cfg.GRPC.Protocol, + "address": cfg.GRPC.Addr, + "services": map[string]interface{}{ + "authprovider": map[string]interface{}{ + "auth_manager": "oidc", + "auth_managers": map[string]interface{}{ + "oidc": map[string]interface{}{ + "issuer": cfg.OIDC.Issuer, + "insecure": cfg.OIDC.Insecure, + "id_claim": cfg.OIDC.IDClaim, + "uid_claim": cfg.OIDC.UIDClaim, + "gid_claim": cfg.OIDC.GIDClaim, + }, + }, + }, + }, + }, + } +} diff --git a/extensions/auth-bearer/pkg/tracing/tracing.go b/extensions/auth-bearer/pkg/tracing/tracing.go new file mode 100644 index 0000000000..74ff9ceca4 --- /dev/null +++ b/extensions/auth-bearer/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the proxy service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/auth-machine/cmd/auth-machine/main.go b/extensions/auth-machine/cmd/auth-machine/main.go new file mode 100644 index 0000000000..2a29a75e6e --- /dev/null +++ b/extensions/auth-machine/cmd/auth-machine/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/auth-machine/pkg/command" + "github.com/owncloud/ocis/extensions/auth-machine/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/auth-machine/pkg/Makefile b/extensions/auth-machine/pkg/Makefile deleted file mode 100644 index 51243d83fc..0000000000 --- a/extensions/auth-machine/pkg/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -SHELL := bash -NAME := ocs - -include ../../.make/recursion.mk - -############ tooling ############ -ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI -include ../../.bingo/Variables.mk -endif - -############ go tooling ############ -include ../../.make/go.mk - -############ release ############ -include ../../.make/release.mk - -############ docs generate ############ -include ../../.make/docs.mk - -.PHONY: docs-generate -docs-generate: config-docs-generate - -############ generate ############ -include ../../.make/generate.mk - -.PHONY: ci-go-generate -ci-go-generate: # CI runs ci-node-generate automatically before this target - -.PHONY: ci-node-generate -ci-node-generate: - -############ licenses ############ -.PHONY: ci-node-check-licenses -ci-node-check-licenses: - -.PHONY: ci-node-save-licenses -ci-node-save-licenses: diff --git a/extensions/auth-machine/pkg/command/command.go b/extensions/auth-machine/pkg/command/command.go deleted file mode 100644 index 1e79ac6689..0000000000 --- a/extensions/auth-machine/pkg/command/command.go +++ /dev/null @@ -1,158 +0,0 @@ -package command - -import ( - "context" - "flag" - "fmt" - "os" - "path" - - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/gofrs/uuid" - "github.com/oklog/run" - "github.com/owncloud/ocis/extensions/auth-machine/pkg/config" - "github.com/owncloud/ocis/extensions/auth-machine/pkg/config/parser" - "github.com/owncloud/ocis/extensions/auth-machine/pkg/server/debug" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" -) - -// AuthMachine is the entrypoint for the auth-machine command. -func AuthMachine(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "auth-machine", - Usage: "start authprovider for machine auth", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - uuid := uuid.Must(uuid.NewV4()) - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+uuid.String()+".pid") - rcfg := authMachineConfigFromStruct(c, cfg) - - gr.Add(func() error { - runtime.RunWithOptions( - rcfg, - pidFile, - runtime.WithLogger(&logger.Logger), - ) - return nil - }, func(_ error) { - logger.Info(). - Str("server", c.Command.Name). - Msg("Shutting down server") - - cancel() - }) - - debugServer, err := debug.Server( - debug.Logger(logger), - debug.Context(ctx), - debug.Config(cfg), - ) - - if err != nil { - logger.Info().Err(err).Str("server", "debug").Msg("failed to initialize server") - return err - } - - gr.Add(debugServer.ListenAndServe, func(_ error) { - cancel() - }) - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() - }, - } -} - -// authMachineConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func authMachineConfigFromStruct(c *cli.Context, cfg *config.Config) map[string]interface{} { - return map[string]interface{}{ - "core": map[string]interface{}{ - "tracing_enabled": cfg.Tracing.Enabled, - "tracing_endpoint": cfg.Tracing.Endpoint, - "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, - }, - "shared": map[string]interface{}{ - "jwt_secret": cfg.TokenManager.JWTSecret, - "gatewaysvc": cfg.Reva.Address, - "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, - }, - "grpc": map[string]interface{}{ - "network": cfg.GRPC.Protocol, - "address": cfg.GRPC.Addr, - "services": map[string]interface{}{ - "authprovider": map[string]interface{}{ - "auth_manager": "machine", - "auth_managers": map[string]interface{}{ - "machine": map[string]interface{}{ - "api_key": cfg.MachineAuthAPIKey, - "gateway_addr": cfg.Reva.Address, - }, - }, - }, - }, - }, - } -} - -// AuthMachineSutureService allows for the storage-gateway command to be embedded and supervised by a suture supervisor tree. -type AuthMachineSutureService struct { - cfg *config.Config -} - -// NewAuthMachineSutureService creates a new gateway.AuthMachineSutureService -func NewAuthMachine(cfg *ociscfg.Config) suture.Service { - cfg.AuthMachine.Commons = cfg.Commons - return AuthMachineSutureService{ - cfg: cfg.AuthMachine, - } -} - -func (s AuthMachineSutureService) Serve(ctx context.Context) error { - // s.cfg.Reva.AuthMachine.Context = ctx - cmd := AuthMachine(s.cfg) - f := &flag.FlagSet{} - cmdFlags := cmd.Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if cmd.Before != nil { - if err := cmd.Before(cliCtx); err != nil { - return err - } - } - if err := cmd.Action(cliCtx); err != nil { - return err - } - - return nil -} diff --git a/extensions/auth-machine/pkg/command/health.go b/extensions/auth-machine/pkg/command/health.go new file mode 100644 index 0000000000..ad86b25787 --- /dev/null +++ b/extensions/auth-machine/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/auth-machine/pkg/config" + "github.com/owncloud/ocis/extensions/auth-machine/pkg/config/parser" + "github.com/owncloud/ocis/extensions/auth-machine/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/auth-machine/pkg/command/root.go b/extensions/auth-machine/pkg/command/root.go new file mode 100644 index 0000000000..9c675862fa --- /dev/null +++ b/extensions/auth-machine/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/auth-machine/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-auth-machine command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "ocis-auth-machine", + Usage: "Provide apps for oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the auth-machine command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new auth-machine.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.AuthMachine.Commons = cfg.Commons + return SutureService{ + cfg: cfg.AuthMachine, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/auth-machine/pkg/command/server.go b/extensions/auth-machine/pkg/command/server.go new file mode 100644 index 0000000000..e65fc00041 --- /dev/null +++ b/extensions/auth-machine/pkg/command/server.go @@ -0,0 +1,107 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/auth-machine/pkg/config" + "github.com/owncloud/ocis/extensions/auth-machine/pkg/config/parser" + "github.com/owncloud/ocis/extensions/auth-machine/pkg/logging" + "github.com/owncloud/ocis/extensions/auth-machine/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/auth-machine/pkg/server/debug" + "github.com/owncloud/ocis/extensions/auth-machine/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/sync" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.AuthMachineConfigFromStruct(cfg) + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + if err := external.RegisterGRPCEndpoint( + ctx, + cfg.GRPC.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.GRPC.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/auth-machine/pkg/command/version.go b/extensions/auth-machine/pkg/command/version.go new file mode 100644 index 0000000000..b2977c0953 --- /dev/null +++ b/extensions/auth-machine/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/auth-machine/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/auth-machine/pkg/config/config.go b/extensions/auth-machine/pkg/config/config.go index 4d145eed03..2479227df0 100644 --- a/extensions/auth-machine/pkg/config/config.go +++ b/extensions/auth-machine/pkg/config/config.go @@ -1,14 +1,17 @@ package config -import "github.com/owncloud/ocis/ocis-pkg/shared" +import ( + "context" + + "github.com/owncloud/ocis/ocis-pkg/shared" +) type Config struct { *shared.Commons `yaml:"-"` Service Service `yaml:"-"` Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` + Log *Log `yaml:"log"` Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` GRPC GRPCConfig `yaml:"grpc"` @@ -17,6 +20,9 @@ type Config struct { SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token"` MachineAuthAPIKey string `yaml:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY;AUTH_MACHINE_API_KEY"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;AUTH_MACHINE_TRACING_ENABLED" desc:"Activates tracing."` @@ -25,7 +31,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;AUTH_MACHINE_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;AUTH_MACHINE_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;AUTH_MACHINE_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;AUTH_MACHINE_LOG_COLOR" desc:"Activates colorized log output."` @@ -44,6 +50,7 @@ type Debug struct { } type GRPCConfig struct { - Addr string `yaml:"addr" env:"AUTH_MACHINE_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"AUTH_MACHINE_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` + Addr string `yaml:"addr" env:"AUTH_MACHINE_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"AUTH_MACHINE_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` } diff --git a/extensions/auth-machine/pkg/config/defaults/defaultconfig.go b/extensions/auth-machine/pkg/config/defaults/defaultconfig.go index 7a102f4d26..6f156446b4 100644 --- a/extensions/auth-machine/pkg/config/defaults/defaultconfig.go +++ b/extensions/auth-machine/pkg/config/defaults/defaultconfig.go @@ -20,8 +20,9 @@ func DefaultConfig() *config.Config { Zpages: false, }, GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9166", - Protocol: "tcp", + Addr: "127.0.0.1:9166", + Namespace: "com.owncloud.api", + Protocol: "tcp", }, Service: config.Service{ Name: "auth-machine", @@ -34,15 +35,15 @@ func DefaultConfig() *config.Config { func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/auth-machine/pkg/logging/logging.go b/extensions/auth-machine/pkg/logging/logging.go new file mode 100644 index 0000000000..0d89a6f4b9 --- /dev/null +++ b/extensions/auth-machine/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/auth-machine/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/auth-machine/pkg/revaconfig/config.go b/extensions/auth-machine/pkg/revaconfig/config.go new file mode 100644 index 0000000000..c4a1514ec1 --- /dev/null +++ b/extensions/auth-machine/pkg/revaconfig/config.go @@ -0,0 +1,37 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/extensions/auth-machine/pkg/config" +) + +// AuthMachineConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func AuthMachineConfigFromStruct(cfg *config.Config) map[string]interface{} { + return map[string]interface{}{ + "core": map[string]interface{}{ + "tracing_enabled": cfg.Tracing.Enabled, + "tracing_endpoint": cfg.Tracing.Endpoint, + "tracing_collector": cfg.Tracing.Collector, + "tracing_service_name": cfg.Service.Name, + }, + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, + "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, + }, + "grpc": map[string]interface{}{ + "network": cfg.GRPC.Protocol, + "address": cfg.GRPC.Addr, + "services": map[string]interface{}{ + "authprovider": map[string]interface{}{ + "auth_manager": "machine", + "auth_managers": map[string]interface{}{ + "machine": map[string]interface{}{ + "api_key": cfg.MachineAuthAPIKey, + "gateway_addr": cfg.Reva.Address, + }, + }, + }, + }, + }, + } +} diff --git a/extensions/auth-machine/pkg/tracing/tracing.go b/extensions/auth-machine/pkg/tracing/tracing.go new file mode 100644 index 0000000000..87da6d0892 --- /dev/null +++ b/extensions/auth-machine/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/auth-machine/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the proxy service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/frontend/cmd/frontend/main.go b/extensions/frontend/cmd/frontend/main.go new file mode 100644 index 0000000000..a6135fa78a --- /dev/null +++ b/extensions/frontend/cmd/frontend/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/frontend/pkg/command" + "github.com/owncloud/ocis/extensions/frontend/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/frontend/pkg/Makefile b/extensions/frontend/pkg/Makefile deleted file mode 100644 index 51243d83fc..0000000000 --- a/extensions/frontend/pkg/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -SHELL := bash -NAME := ocs - -include ../../.make/recursion.mk - -############ tooling ############ -ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI -include ../../.bingo/Variables.mk -endif - -############ go tooling ############ -include ../../.make/go.mk - -############ release ############ -include ../../.make/release.mk - -############ docs generate ############ -include ../../.make/docs.mk - -.PHONY: docs-generate -docs-generate: config-docs-generate - -############ generate ############ -include ../../.make/generate.mk - -.PHONY: ci-go-generate -ci-go-generate: # CI runs ci-node-generate automatically before this target - -.PHONY: ci-node-generate -ci-node-generate: - -############ licenses ############ -.PHONY: ci-node-check-licenses -ci-node-check-licenses: - -.PHONY: ci-node-save-licenses -ci-node-save-licenses: diff --git a/extensions/frontend/pkg/command/health.go b/extensions/frontend/pkg/command/health.go new file mode 100644 index 0000000000..a4cedd367f --- /dev/null +++ b/extensions/frontend/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/frontend/pkg/config" + "github.com/owncloud/ocis/extensions/frontend/pkg/config/parser" + "github.com/owncloud/ocis/extensions/frontend/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/frontend/pkg/command/root.go b/extensions/frontend/pkg/command/root.go new file mode 100644 index 0000000000..afe7fa3ef3 --- /dev/null +++ b/extensions/frontend/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/frontend/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-frontend command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "ocis-frontend", + Usage: "Provide various ownCloud apis for oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the frontend command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new frontend.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.Frontend.Commons = cfg.Commons + return SutureService{ + cfg: cfg.Frontend, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/frontend/pkg/command/server.go b/extensions/frontend/pkg/command/server.go new file mode 100644 index 0000000000..88631228a2 --- /dev/null +++ b/extensions/frontend/pkg/command/server.go @@ -0,0 +1,107 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/frontend/pkg/config" + "github.com/owncloud/ocis/extensions/frontend/pkg/config/parser" + "github.com/owncloud/ocis/extensions/frontend/pkg/logging" + "github.com/owncloud/ocis/extensions/frontend/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/frontend/pkg/server/debug" + "github.com/owncloud/ocis/extensions/frontend/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/sync" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.FrontendConfigFromStruct(cfg) + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + if err := external.RegisterHTTPEndpoint( + ctx, + cfg.HTTP.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.HTTP.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the http endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/frontend/pkg/command/version.go b/extensions/frontend/pkg/command/version.go new file mode 100644 index 0000000000..d721db1236 --- /dev/null +++ b/extensions/frontend/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/frontend/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.HTTP.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/frontend/pkg/config/config.go b/extensions/frontend/pkg/config/config.go index a82e1fd42d..68bfe4cb18 100644 --- a/extensions/frontend/pkg/config/config.go +++ b/extensions/frontend/pkg/config/config.go @@ -1,14 +1,17 @@ package config -import "github.com/owncloud/ocis/ocis-pkg/shared" +import ( + "context" + + "github.com/owncloud/ocis/ocis-pkg/shared" +) type Config struct { *shared.Commons `yaml:"-"` Service Service `yaml:"-"` Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` + Log *Log `yaml:"log"` Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` HTTP HTTPConfig `yaml:"http"` @@ -38,6 +41,9 @@ type Config struct { Checksums Checksums `yaml:"checksums"` Middleware Middleware `yaml:"middleware"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;FRONTEND_TRACING_ENABLED" desc:"Activates tracing."` diff --git a/extensions/frontend/pkg/config/defaults/defaultconfig.go b/extensions/frontend/pkg/config/defaults/defaultconfig.go index a1067f1eb5..d9b05083c0 100644 --- a/extensions/frontend/pkg/config/defaults/defaultconfig.go +++ b/extensions/frontend/pkg/config/defaults/defaultconfig.go @@ -20,9 +20,10 @@ func DefaultConfig() *config.Config { Zpages: false, }, HTTP: config.HTTPConfig{ - Addr: "127.0.0.1:9140", - Protocol: "tcp", - Prefix: "", + Addr: "127.0.0.1:9140", + Namespace: "com.owncloud.web", + Protocol: "tcp", + Prefix: "", }, Service: config.Service{ Name: "frontend", @@ -72,15 +73,15 @@ func DefaultConfig() *config.Config { func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/frontend/pkg/logging/logging.go b/extensions/frontend/pkg/logging/logging.go new file mode 100644 index 0000000000..19c43464d9 --- /dev/null +++ b/extensions/frontend/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/frontend/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/frontend/pkg/command/command.go b/extensions/frontend/pkg/revaconfig/config.go similarity index 55% rename from extensions/frontend/pkg/command/command.go rename to extensions/frontend/pkg/revaconfig/config.go index ff2d2e636c..5fbb40c2ef 100644 --- a/extensions/frontend/pkg/command/command.go +++ b/extensions/frontend/pkg/revaconfig/config.go @@ -1,148 +1,62 @@ -package command +package revaconfig import ( - "context" - "flag" - "fmt" - "os" "path" "strconv" - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/gofrs/uuid" - "github.com/oklog/run" "github.com/owncloud/ocis/extensions/frontend/pkg/config" - "github.com/owncloud/ocis/extensions/frontend/pkg/config/parser" - "github.com/owncloud/ocis/extensions/frontend/pkg/server/debug" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" ) -// Frontend is the entrypoint for the frontend command. -func Frontend(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "frontend", - Usage: "start frontend service", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - //metrics = metrics.New() - - defer cancel() - - uuid := uuid.Must(uuid.NewV4()) - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+uuid.String()+".pid") - - archivers := []map[string]interface{}{ - { - "enabled": true, - "version": "2.0.0", - "formats": []string{"tar", "zip"}, - "archiver_url": path.Join("/", cfg.Archiver.Prefix), - "max_num_files": strconv.FormatInt(cfg.Archiver.MaxNumFiles, 10), - "max_size": strconv.FormatInt(cfg.Archiver.MaxSize, 10), - }, - } - - appProviders := []map[string]interface{}{ - { - "enabled": true, - "version": "1.0.0", - "apps_url": cfg.AppProvider.AppsURL, - "open_url": cfg.AppProvider.OpenURL, - "new_url": cfg.AppProvider.NewURL, - }, - } - - filesCfg := map[string]interface{}{ - "private_links": false, - "bigfilechunking": false, - "blacklisted_files": []string{}, - "undelete": true, - "versioning": true, - "archivers": archivers, - "app_providers": appProviders, - "favorites": cfg.EnableFavorites, - } - - if cfg.DefaultUploadProtocol == "tus" { - filesCfg["tus_support"] = map[string]interface{}{ - "version": "1.0.0", - "resumable": "1.0.0", - "extension": "creation,creation-with-upload", - "http_method_override": cfg.UploadHTTPMethodOverride, - "max_chunk_size": cfg.UploadMaxChunkSize, - } - } - - revaCfg := frontendConfigFromStruct(c, cfg, filesCfg) - - gr.Add(func() error { - runtime.RunWithOptions(revaCfg, pidFile, runtime.WithLogger(&logger.Logger)) - return nil - }, func(_ error) { - logger.Info().Str("server", c.Command.Name).Msg("Shutting down server") - cancel() - }) - - { - server, err := debug.Server( - debug.Logger(logger), - debug.Context(ctx), - debug.Config(cfg), - ) - - if err != nil { - logger.Info(). - Err(err). - Str("server", "debug"). - Msg("Failed to initialize server") - - return err - } - - gr.Add(server.ListenAndServe, func(_ error) { - cancel() - }) - } - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() +// FrontendConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func FrontendConfigFromStruct(cfg *config.Config) map[string]interface{} { + archivers := []map[string]interface{}{ + { + "enabled": true, + "version": "2.0.0", + "formats": []string{"tar", "zip"}, + "archiver_url": path.Join("/", cfg.Archiver.Prefix), + "max_num_files": strconv.FormatInt(cfg.Archiver.MaxNumFiles, 10), + "max_size": strconv.FormatInt(cfg.Archiver.MaxSize, 10), }, } -} -// frontendConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func frontendConfigFromStruct(c *cli.Context, cfg *config.Config, filesCfg map[string]interface{}) map[string]interface{} { + appProviders := []map[string]interface{}{ + { + "enabled": true, + "version": "1.0.0", + "apps_url": "/app/list", + "open_url": "/app/open", + "new_url": "/app/new", + }, + } + + filesCfg := map[string]interface{}{ + "private_links": false, + "bigfilechunking": false, + "blacklisted_files": []string{}, + "undelete": true, + "versioning": true, + "archivers": archivers, + "app_providers": appProviders, + "favorites": cfg.EnableFavorites, + } + + if cfg.DefaultUploadProtocol == "tus" { + filesCfg["tus_support"] = map[string]interface{}{ + "version": "1.0.0", + "resumable": "1.0.0", + "extension": "creation,creation-with-upload", + "http_method_override": cfg.UploadHTTPMethodOverride, + "max_chunk_size": cfg.UploadMaxChunkSize, + } + } + return map[string]interface{}{ "core": map[string]interface{}{ "tracing_enabled": cfg.Tracing.Enabled, "tracing_endpoint": cfg.Tracing.Endpoint, "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, + "tracing_service_name": cfg.Service.Name, }, "shared": map[string]interface{}{ "jwt_secret": cfg.TokenManager.JWTSecret, @@ -163,12 +77,6 @@ func frontendConfigFromStruct(c *cli.Context, cfg *config.Config, filesCfg map[s }, // TODO build services dynamically "services": map[string]interface{}{ - "appprovider": map[string]interface{}{ - "prefix": cfg.AppProvider.Prefix, - "transfer_shared_secret": cfg.TransferSecret, - "timeout": 86400, - "insecure": cfg.AppProvider.Insecure, - }, "archiver": map[string]interface{}{ "prefix": cfg.Archiver.Prefix, "timeout": 86400, @@ -303,39 +211,3 @@ func frontendConfigFromStruct(c *cli.Context, cfg *config.Config, filesCfg map[s }, } } - -// FrontendSutureService allows for the storage-frontend command to be embedded and supervised by a suture supervisor tree. -type FrontendSutureService struct { - cfg *config.Config -} - -// NewFrontend creates a new frontend.FrontendSutureService -func NewFrontend(cfg *ociscfg.Config) suture.Service { - cfg.Frontend.Commons = cfg.Commons - return FrontendSutureService{ - cfg: cfg.Frontend, - } -} - -func (s FrontendSutureService) Serve(ctx context.Context) error { - // s.cfg.Reva.Frontend.Context = ctx - cmd := Frontend(s.cfg) - f := &flag.FlagSet{} - cmdFlags := cmd.Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if cmd.Before != nil { - if err := cmd.Before(cliCtx); err != nil { - return err - } - } - if err := cmd.Action(cliCtx); err != nil { - return err - } - - return nil -} diff --git a/extensions/frontend/pkg/tracing/tracing.go b/extensions/frontend/pkg/tracing/tracing.go new file mode 100644 index 0000000000..c6490616bf --- /dev/null +++ b/extensions/frontend/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/frontend/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the proxy service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/gateway/cmd/gateway/main.go b/extensions/gateway/cmd/gateway/main.go new file mode 100644 index 0000000000..adcc697111 --- /dev/null +++ b/extensions/gateway/cmd/gateway/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/gateway/pkg/command" + "github.com/owncloud/ocis/extensions/gateway/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/gateway/pkg/Makefile b/extensions/gateway/pkg/Makefile deleted file mode 100644 index 51243d83fc..0000000000 --- a/extensions/gateway/pkg/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -SHELL := bash -NAME := ocs - -include ../../.make/recursion.mk - -############ tooling ############ -ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI -include ../../.bingo/Variables.mk -endif - -############ go tooling ############ -include ../../.make/go.mk - -############ release ############ -include ../../.make/release.mk - -############ docs generate ############ -include ../../.make/docs.mk - -.PHONY: docs-generate -docs-generate: config-docs-generate - -############ generate ############ -include ../../.make/generate.mk - -.PHONY: ci-go-generate -ci-go-generate: # CI runs ci-node-generate automatically before this target - -.PHONY: ci-node-generate -ci-node-generate: - -############ licenses ############ -.PHONY: ci-node-check-licenses -ci-node-check-licenses: - -.PHONY: ci-node-save-licenses -ci-node-save-licenses: diff --git a/extensions/gateway/pkg/command/health.go b/extensions/gateway/pkg/command/health.go new file mode 100644 index 0000000000..b9bda87a55 --- /dev/null +++ b/extensions/gateway/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/gateway/pkg/config" + "github.com/owncloud/ocis/extensions/gateway/pkg/config/parser" + "github.com/owncloud/ocis/extensions/gateway/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/gateway/pkg/command/root.go b/extensions/gateway/pkg/command/root.go new file mode 100644 index 0000000000..d064ab4713 --- /dev/null +++ b/extensions/gateway/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/gateway/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-gateway command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "ocis-gateway", + Usage: "Provide a CS3api gateway for oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the gateway command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new gateway.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.Gateway.Commons = cfg.Commons + return SutureService{ + cfg: cfg.Gateway, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/gateway/pkg/command/server.go b/extensions/gateway/pkg/command/server.go new file mode 100644 index 0000000000..7caccfbb3a --- /dev/null +++ b/extensions/gateway/pkg/command/server.go @@ -0,0 +1,102 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/gateway/pkg/config" + "github.com/owncloud/ocis/extensions/gateway/pkg/config/parser" + "github.com/owncloud/ocis/extensions/gateway/pkg/logging" + "github.com/owncloud/ocis/extensions/gateway/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/gateway/pkg/server/debug" + "github.com/owncloud/ocis/extensions/gateway/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.GatewayConfigFromStruct(cfg, logger) + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if err := external.RegisterGRPCEndpoint( + ctx, + cfg.GRPC.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.GRPC.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/gateway/pkg/command/version.go b/extensions/gateway/pkg/command/version.go new file mode 100644 index 0000000000..efe2adaf7d --- /dev/null +++ b/extensions/gateway/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/gateway/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/gateway/pkg/config/config.go b/extensions/gateway/pkg/config/config.go index ca3555e721..285c9793c1 100644 --- a/extensions/gateway/pkg/config/config.go +++ b/extensions/gateway/pkg/config/config.go @@ -1,15 +1,18 @@ package config -import "github.com/owncloud/ocis/ocis-pkg/shared" +import ( + "context" + + "github.com/owncloud/ocis/ocis-pkg/shared" +) type Config struct { *shared.Commons `yaml:"-"` - Service Service `yaml:"-"` - Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` - Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` + Service Service `yaml:"-"` + Tracing *Tracing `yaml:"tracing"` + Log *Log `yaml:"log"` + Debug Debug `yaml:"debug"` GRPC GRPCConfig `yaml:"grpc"` @@ -41,6 +44,9 @@ type Config struct { StorageRegistry StorageRegistry `yaml:"storage_registry"` AppRegistry AppRegistry `yaml:"app_registry"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;GATEWAY_TRACING_ENABLED" desc:"Activates tracing."` @@ -49,7 +55,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;GATEWAY_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;GATEWAY_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;GATEWAY_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;GATEWAY_LOG_COLOR" desc:"Activates colorized log output."` @@ -68,8 +74,9 @@ type Debug struct { } type GRPCConfig struct { - Addr string `yaml:"addr" env:"GATEWAY_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"GATEWAY_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` + Addr string `yaml:"addr" env:"GATEWAY_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"GATEWAY_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` } type StorageRegistry struct { diff --git a/extensions/gateway/pkg/config/defaults/defaultconfig.go b/extensions/gateway/pkg/config/defaults/defaultconfig.go index 21e3cc1862..56b07c1858 100644 --- a/extensions/gateway/pkg/config/defaults/defaultconfig.go +++ b/extensions/gateway/pkg/config/defaults/defaultconfig.go @@ -20,8 +20,9 @@ func DefaultConfig() *config.Config { Zpages: false, }, GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9142", - Protocol: "tcp", + Addr: "127.0.0.1:9142", + Namespace: "com.owncloud.api", + Protocol: "tcp", }, Service: config.Service{ Name: "gateway", @@ -62,15 +63,15 @@ func DefaultConfig() *config.Config { func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/gateway/pkg/logging/logging.go b/extensions/gateway/pkg/logging/logging.go new file mode 100644 index 0000000000..4623c743c5 --- /dev/null +++ b/extensions/gateway/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/gateway/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/gateway/pkg/command/command.go b/extensions/gateway/pkg/revaconfig/config.go similarity index 73% rename from extensions/gateway/pkg/command/command.go rename to extensions/gateway/pkg/revaconfig/config.go index 262f818858..5ff200644c 100644 --- a/extensions/gateway/pkg/command/command.go +++ b/extensions/gateway/pkg/revaconfig/config.go @@ -1,126 +1,25 @@ -package command +package revaconfig import ( - "context" "encoding/json" - "flag" - "fmt" "io/ioutil" - "os" - "path" "strings" - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/cs3org/reva/v2/pkg/utils" - "github.com/gofrs/uuid" - "github.com/mitchellh/mapstructure" - "github.com/oklog/run" - "github.com/owncloud/ocis/extensions/gateway/pkg/config" - "github.com/owncloud/ocis/extensions/gateway/pkg/config/parser" - "github.com/owncloud/ocis/extensions/gateway/pkg/server/debug" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/service/external" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - "github.com/owncloud/ocis/ocis-pkg/version" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" + + "github.com/cs3org/reva/v2/pkg/utils" + "github.com/mitchellh/mapstructure" + "github.com/owncloud/ocis/extensions/gateway/pkg/config" ) -// Gateway is the entrypoint for the gateway command. -func Gateway(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "gateway", - Usage: "start gateway", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - uuid := uuid.Must(uuid.NewV4()) - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+uuid.String()+".pid") - rcfg := gatewayConfigFromStruct(c, cfg, logger) - logger.Debug(). - Str("server", "gateway"). - Interface("reva-config", rcfg). - Msg("config") - - defer cancel() - - gr.Add(func() error { - err := external.RegisterGRPCEndpoint( - ctx, - "com.owncloud.storage", - uuid.String(), - cfg.GRPC.Addr, - version.String, - logger, - ) - - if err != nil { - return err - } - - runtime.RunWithOptions( - rcfg, - pidFile, - runtime.WithLogger(&logger.Logger), - ) - return nil - }, func(_ error) { - logger.Info(). - Str("server", c.Command.Name). - Msg("Shutting down server") - - cancel() - }) - - debugServer, err := debug.Server( - debug.Logger(logger), - debug.Context(ctx), - debug.Config(cfg), - ) - - if err != nil { - logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") - return err - } - - gr.Add(debugServer.ListenAndServe, func(_ error) { - cancel() - }) - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() - }, - } -} - -// gatewayConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func gatewayConfigFromStruct(c *cli.Context, cfg *config.Config, logger log.Logger) map[string]interface{} { +// GatewayConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func GatewayConfigFromStruct(cfg *config.Config, logger log.Logger) map[string]interface{} { rcfg := map[string]interface{}{ "core": map[string]interface{}{ "tracing_enabled": cfg.Tracing.Enabled, "tracing_endpoint": cfg.Tracing.Endpoint, "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, + "tracing_service_name": cfg.Service.Name, }, "shared": map[string]interface{}{ "jwt_secret": cfg.TokenManager.JWTSecret, @@ -381,38 +280,3 @@ func mimetypes(cfg *config.Config, logger log.Logger) []map[string]interface{} { return m } - -// GatewaySutureService allows for the storage-gateway command to be embedded and supervised by a suture supervisor tree. -type GatewaySutureService struct { - cfg *config.Config -} - -// NewGatewaySutureService creates a new gateway.GatewaySutureService -func NewGateway(cfg *ociscfg.Config) suture.Service { - cfg.Gateway.Commons = cfg.Commons - return GatewaySutureService{ - cfg: cfg.Gateway, - } -} - -func (s GatewaySutureService) Serve(ctx context.Context) error { - cmd := Gateway(s.cfg) - f := &flag.FlagSet{} - cmdFlags := cmd.Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if cmd.Before != nil { - if err := cmd.Before(cliCtx); err != nil { - return err - } - } - if err := cmd.Action(cliCtx); err != nil { - return err - } - - return nil -} diff --git a/extensions/gateway/pkg/tracing/tracing.go b/extensions/gateway/pkg/tracing/tracing.go new file mode 100644 index 0000000000..5f1af28563 --- /dev/null +++ b/extensions/gateway/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/gateway/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the proxy service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/group/cmd/group/main.go b/extensions/group/cmd/group/main.go new file mode 100644 index 0000000000..d459fa3291 --- /dev/null +++ b/extensions/group/cmd/group/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/group/pkg/command" + "github.com/owncloud/ocis/extensions/group/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/group/pkg/Makefile b/extensions/group/pkg/Makefile deleted file mode 100644 index 51243d83fc..0000000000 --- a/extensions/group/pkg/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -SHELL := bash -NAME := ocs - -include ../../.make/recursion.mk - -############ tooling ############ -ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI -include ../../.bingo/Variables.mk -endif - -############ go tooling ############ -include ../../.make/go.mk - -############ release ############ -include ../../.make/release.mk - -############ docs generate ############ -include ../../.make/docs.mk - -.PHONY: docs-generate -docs-generate: config-docs-generate - -############ generate ############ -include ../../.make/generate.mk - -.PHONY: ci-go-generate -ci-go-generate: # CI runs ci-node-generate automatically before this target - -.PHONY: ci-node-generate -ci-node-generate: - -############ licenses ############ -.PHONY: ci-node-check-licenses -ci-node-check-licenses: - -.PHONY: ci-node-save-licenses -ci-node-save-licenses: diff --git a/extensions/group/pkg/command/command.go b/extensions/group/pkg/command/command.go deleted file mode 100644 index f1f7da429c..0000000000 --- a/extensions/group/pkg/command/command.go +++ /dev/null @@ -1,222 +0,0 @@ -package command - -import ( - "context" - "flag" - "fmt" - "os" - "path" - "path/filepath" - - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/gofrs/uuid" - "github.com/oklog/run" - "github.com/owncloud/ocis/extensions/group/pkg/config" - "github.com/owncloud/ocis/extensions/group/pkg/config/parser" - "github.com/owncloud/ocis/extensions/group/pkg/server/debug" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/ldap" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" -) - -// Groups is the entrypoint for the sharing command. -func Groups(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "groups", - Usage: "start groups service", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // pre-create folders - if cfg.Driver == "json" && cfg.Drivers.JSON.File != "" { - if err := os.MkdirAll(filepath.Dir(cfg.Drivers.JSON.File), os.FileMode(0700)); err != nil { - return err - } - } - - cuuid := uuid.Must(uuid.NewV4()) - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+cuuid.String()+".pid") - - rcfg := groupsConfigFromStruct(c, cfg) - - if cfg.Driver == "ldap" { - if err := ldap.WaitForCA(logger, cfg.Drivers.LDAP.Insecure, cfg.Drivers.LDAP.CACert); err != nil { - logger.Error().Err(err).Msg("The configured LDAP CA cert does not exist") - return err - } - } - - gr.Add(func() error { - runtime.RunWithOptions( - rcfg, - pidFile, - runtime.WithLogger(&logger.Logger), - ) - return nil - }, func(_ error) { - logger.Info(). - Str("server", c.Command.Name). - Msg("Shutting down server") - - cancel() - }) - - debugServer, err := debug.Server( - debug.Logger(logger), - debug.Context(ctx), - debug.Config(cfg), - ) - - if err != nil { - logger.Info().Err(err).Str("server", c.Command.Name+"-debug").Msg("Failed to initialize server") - return err - } - - gr.Add(debugServer.ListenAndServe, func(_ error) { - cancel() - }) - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() - }, - } -} - -// groupsConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func groupsConfigFromStruct(c *cli.Context, cfg *config.Config) map[string]interface{} { - return map[string]interface{}{ - "core": map[string]interface{}{ - "tracing_enabled": cfg.Tracing.Enabled, - "tracing_endpoint": cfg.Tracing.Endpoint, - "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, - }, - "shared": map[string]interface{}{ - "jwt_secret": cfg.TokenManager.JWTSecret, - "gatewaysvc": cfg.Reva.Address, - "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, - }, - "grpc": map[string]interface{}{ - "network": cfg.GRPC.Protocol, - "address": cfg.GRPC.Addr, - // TODO build services dynamically - "services": map[string]interface{}{ - "groupprovider": map[string]interface{}{ - "driver": cfg.Driver, - "drivers": map[string]interface{}{ - "json": map[string]interface{}{ - "groups": cfg.Drivers.JSON.File, - }, - "ldap": ldapConfigFromString(cfg.Drivers.LDAP), - "rest": map[string]interface{}{ - "client_id": cfg.Drivers.REST.ClientID, - "client_secret": cfg.Drivers.REST.ClientSecret, - "redis_address": cfg.Drivers.REST.RedisAddr, - "redis_username": cfg.Drivers.REST.RedisUsername, - "redis_password": cfg.Drivers.REST.RedisPassword, - "group_members_cache_expiration": cfg.GroupMembersCacheExpiration, - "id_provider": cfg.Drivers.REST.IDProvider, - "api_base_url": cfg.Drivers.REST.APIBaseURL, - "oidc_token_endpoint": cfg.Drivers.REST.OIDCTokenEndpoint, - "target_api": cfg.Drivers.REST.TargetAPI, - }, - }, - }, - }, - }, - } -} - -// GroupSutureService allows for the storage-groupprovider command to be embedded and supervised by a suture supervisor tree. -type GroupSutureService struct { - cfg *config.Config -} - -// NewGroupProviderSutureService creates a new storage.GroupProvider -func NewGroupProvider(cfg *ociscfg.Config) suture.Service { - cfg.Group.Commons = cfg.Commons - return GroupSutureService{ - cfg: cfg.Group, - } -} - -func (s GroupSutureService) Serve(ctx context.Context) error { - // s.cfg.Reva.Groups.Context = ctx - f := &flag.FlagSet{} - cmdFlags := Groups(s.cfg).Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if Groups(s.cfg).Before != nil { - if err := Groups(s.cfg).Before(cliCtx); err != nil { - return err - } - } - if err := Groups(s.cfg).Action(cliCtx); err != nil { - return err - } - - return nil -} - -func ldapConfigFromString(cfg config.LDAPDriver) map[string]interface{} { - return map[string]interface{}{ - "uri": cfg.URI, - "cacert": cfg.CACert, - "insecure": cfg.Insecure, - "bind_username": cfg.BindDN, - "bind_password": cfg.BindPassword, - "user_base_dn": cfg.UserBaseDN, - "group_base_dn": cfg.GroupBaseDN, - "user_scope": cfg.UserScope, - "group_scope": cfg.GroupScope, - "user_filter": cfg.UserFilter, - "group_filter": cfg.GroupFilter, - "user_objectclass": cfg.UserObjectClass, - "group_objectclass": cfg.GroupObjectClass, - "login_attributes": cfg.LoginAttributes, - "idp": cfg.IDP, - "user_schema": map[string]interface{}{ - "id": cfg.UserSchema.ID, - "idIsOctetString": cfg.UserSchema.IDIsOctetString, - "mail": cfg.UserSchema.Mail, - "displayName": cfg.UserSchema.DisplayName, - "userName": cfg.UserSchema.Username, - }, - "group_schema": map[string]interface{}{ - "id": cfg.GroupSchema.ID, - "idIsOctetString": cfg.GroupSchema.IDIsOctetString, - "mail": cfg.GroupSchema.Mail, - "displayName": cfg.GroupSchema.DisplayName, - "groupName": cfg.GroupSchema.Groupname, - "member": cfg.GroupSchema.Member, - }, - } -} diff --git a/extensions/group/pkg/command/health.go b/extensions/group/pkg/command/health.go new file mode 100644 index 0000000000..6e6a01e7eb --- /dev/null +++ b/extensions/group/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/group/pkg/config" + "github.com/owncloud/ocis/extensions/group/pkg/config/parser" + "github.com/owncloud/ocis/extensions/group/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/group/pkg/command/root.go b/extensions/group/pkg/command/root.go new file mode 100644 index 0000000000..a4a3feaf37 --- /dev/null +++ b/extensions/group/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/group/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-group command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "ocis-group", + Usage: "Provide apps for oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the group command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new group.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.Group.Commons = cfg.Commons + return SutureService{ + cfg: cfg.Group, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/group/pkg/command/server.go b/extensions/group/pkg/command/server.go new file mode 100644 index 0000000000..2bf18e4748 --- /dev/null +++ b/extensions/group/pkg/command/server.go @@ -0,0 +1,120 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/group/pkg/config" + "github.com/owncloud/ocis/extensions/group/pkg/config/parser" + "github.com/owncloud/ocis/extensions/group/pkg/logging" + "github.com/owncloud/ocis/extensions/group/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/group/pkg/server/debug" + "github.com/owncloud/ocis/extensions/group/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/ldap" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/sync" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.GroupsConfigFromStruct(cfg) + + // the reva runtime calls os.Exit in the case of a failure and there is no way for the oCIS + // runtime to catch it and restart a reva service. Therefore we need to ensure the service has + // everything it needs, before starting the service. + // In this case: CA certificates + if cfg.Driver == "ldap" { + ldapCfg := cfg.Drivers.LDAP + if err := ldap.WaitForCA(logger, ldapCfg.Insecure, ldapCfg.CACert); err != nil { + logger.Error().Err(err).Msg("The configured LDAP CA cert does not exist") + return err + } + } + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + if err := external.RegisterGRPCEndpoint( + ctx, + cfg.GRPC.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.GRPC.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/group/pkg/command/version.go b/extensions/group/pkg/command/version.go new file mode 100644 index 0000000000..e3f5303faa --- /dev/null +++ b/extensions/group/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/group/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/group/pkg/config/config.go b/extensions/group/pkg/config/config.go index 5d485df52e..fc718d6540 100644 --- a/extensions/group/pkg/config/config.go +++ b/extensions/group/pkg/config/config.go @@ -1,14 +1,17 @@ package config -import "github.com/owncloud/ocis/ocis-pkg/shared" +import ( + "context" + + "github.com/owncloud/ocis/ocis-pkg/shared" +) type Config struct { *shared.Commons `yaml:"-"` Service Service `yaml:"-"` Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` + Log *Log `yaml:"log"` Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` GRPC GRPCConfig `yaml:"grpc"` @@ -19,6 +22,9 @@ type Config struct { GroupMembersCacheExpiration int `yaml:"group_members_cache_expiration"` Driver string `yaml:"driver"` Drivers Drivers `yaml:"drivers"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;GROUPS_TRACING_ENABLED" desc:"Activates tracing."` @@ -27,7 +33,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;GROUPS_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;GROUPS_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;GROUPS_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;GROUPS_LOG_COLOR" desc:"Activates colorized log output."` @@ -46,8 +52,9 @@ type Debug struct { } type GRPCConfig struct { - Addr string `yaml:"addr" env:"GROUPS_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"GROUPS_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` + Addr string `yaml:"addr" env:"GROUPS_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"GROUPS_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` } type Drivers struct { diff --git a/extensions/group/pkg/config/defaults/defaultconfig.go b/extensions/group/pkg/config/defaults/defaultconfig.go index 47c10b9f79..123b5e04d8 100644 --- a/extensions/group/pkg/config/defaults/defaultconfig.go +++ b/extensions/group/pkg/config/defaults/defaultconfig.go @@ -23,8 +23,9 @@ func DefaultConfig() *config.Config { Zpages: false, }, GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9160", - Protocol: "tcp", + Addr: "127.0.0.1:9160", + Namespace: "com.owncloud.api", + Protocol: "tcp", }, Service: config.Service{ Name: "group", @@ -86,15 +87,15 @@ func DefaultConfig() *config.Config { func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/group/pkg/logging/logging.go b/extensions/group/pkg/logging/logging.go new file mode 100644 index 0000000000..9aa3b08150 --- /dev/null +++ b/extensions/group/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/group/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/group/pkg/revaconfig/config.go b/extensions/group/pkg/revaconfig/config.go new file mode 100644 index 0000000000..736956060e --- /dev/null +++ b/extensions/group/pkg/revaconfig/config.go @@ -0,0 +1,85 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/extensions/group/pkg/config" +) + +// GroupsConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func GroupsConfigFromStruct(cfg *config.Config) map[string]interface{} { + return map[string]interface{}{ + "core": map[string]interface{}{ + "tracing_enabled": cfg.Tracing.Enabled, + "tracing_endpoint": cfg.Tracing.Endpoint, + "tracing_collector": cfg.Tracing.Collector, + "tracing_service_name": cfg.Service.Name, + }, + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, + "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, + }, + "grpc": map[string]interface{}{ + "network": cfg.GRPC.Protocol, + "address": cfg.GRPC.Addr, + // TODO build services dynamically + "services": map[string]interface{}{ + "groupprovider": map[string]interface{}{ + "driver": cfg.Driver, + "drivers": map[string]interface{}{ + "json": map[string]interface{}{ + "groups": cfg.Drivers.JSON.File, + }, + "ldap": ldapConfigFromString(cfg.Drivers.LDAP), + "rest": map[string]interface{}{ + "client_id": cfg.Drivers.REST.ClientID, + "client_secret": cfg.Drivers.REST.ClientSecret, + "redis_address": cfg.Drivers.REST.RedisAddr, + "redis_username": cfg.Drivers.REST.RedisUsername, + "redis_password": cfg.Drivers.REST.RedisPassword, + "group_members_cache_expiration": cfg.GroupMembersCacheExpiration, + "id_provider": cfg.Drivers.REST.IDProvider, + "api_base_url": cfg.Drivers.REST.APIBaseURL, + "oidc_token_endpoint": cfg.Drivers.REST.OIDCTokenEndpoint, + "target_api": cfg.Drivers.REST.TargetAPI, + }, + }, + }, + }, + }, + } +} + +func ldapConfigFromString(cfg config.LDAPDriver) map[string]interface{} { + return map[string]interface{}{ + "uri": cfg.URI, + "cacert": cfg.CACert, + "insecure": cfg.Insecure, + "bind_username": cfg.BindDN, + "bind_password": cfg.BindPassword, + "user_base_dn": cfg.UserBaseDN, + "group_base_dn": cfg.GroupBaseDN, + "user_scope": cfg.UserScope, + "group_scope": cfg.GroupScope, + "user_filter": cfg.UserFilter, + "group_filter": cfg.GroupFilter, + "user_objectclass": cfg.UserObjectClass, + "group_objectclass": cfg.GroupObjectClass, + "login_attributes": cfg.LoginAttributes, + "idp": cfg.IDP, + "user_schema": map[string]interface{}{ + "id": cfg.UserSchema.ID, + "idIsOctetString": cfg.UserSchema.IDIsOctetString, + "mail": cfg.UserSchema.Mail, + "displayName": cfg.UserSchema.DisplayName, + "userName": cfg.UserSchema.Username, + }, + "group_schema": map[string]interface{}{ + "id": cfg.GroupSchema.ID, + "idIsOctetString": cfg.GroupSchema.IDIsOctetString, + "mail": cfg.GroupSchema.Mail, + "displayName": cfg.GroupSchema.DisplayName, + "groupName": cfg.GroupSchema.Groupname, + "member": cfg.GroupSchema.Member, + }, + } +} diff --git a/extensions/group/pkg/tracing/tracing.go b/extensions/group/pkg/tracing/tracing.go new file mode 100644 index 0000000000..1dcb178061 --- /dev/null +++ b/extensions/group/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/group/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the proxy service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/nats/pkg/command/root.go b/extensions/nats/pkg/command/root.go index 44e27d1673..2a2bdffd6e 100644 --- a/extensions/nats/pkg/command/root.go +++ b/extensions/nats/pkg/command/root.go @@ -48,7 +48,7 @@ type SutureService struct { // NewSutureService creates a new nats.SutureService func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Settings.Commons = cfg.Commons + cfg.Nats.Commons = cfg.Commons return SutureService{ cfg: cfg.Nats, } diff --git a/extensions/ocdav/cmd/ocdav/main.go b/extensions/ocdav/cmd/ocdav/main.go new file mode 100644 index 0000000000..0b1035acc6 --- /dev/null +++ b/extensions/ocdav/cmd/ocdav/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/ocdav/pkg/command" + "github.com/owncloud/ocis/extensions/ocdav/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/ocdav/pkg/Makefile b/extensions/ocdav/pkg/Makefile deleted file mode 100644 index 51243d83fc..0000000000 --- a/extensions/ocdav/pkg/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -SHELL := bash -NAME := ocs - -include ../../.make/recursion.mk - -############ tooling ############ -ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI -include ../../.bingo/Variables.mk -endif - -############ go tooling ############ -include ../../.make/go.mk - -############ release ############ -include ../../.make/release.mk - -############ docs generate ############ -include ../../.make/docs.mk - -.PHONY: docs-generate -docs-generate: config-docs-generate - -############ generate ############ -include ../../.make/generate.mk - -.PHONY: ci-go-generate -ci-go-generate: # CI runs ci-node-generate automatically before this target - -.PHONY: ci-node-generate -ci-node-generate: - -############ licenses ############ -.PHONY: ci-node-check-licenses -ci-node-check-licenses: - -.PHONY: ci-node-save-licenses -ci-node-save-licenses: diff --git a/extensions/ocdav/pkg/command/health.go b/extensions/ocdav/pkg/command/health.go new file mode 100644 index 0000000000..ec3391a168 --- /dev/null +++ b/extensions/ocdav/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/ocdav/pkg/config" + "github.com/owncloud/ocis/extensions/ocdav/pkg/config/parser" + "github.com/owncloud/ocis/extensions/ocdav/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/ocdav/pkg/command/ocdav.go b/extensions/ocdav/pkg/command/ocdav.go deleted file mode 100644 index 3426e38957..0000000000 --- a/extensions/ocdav/pkg/command/ocdav.go +++ /dev/null @@ -1,142 +0,0 @@ -package command - -import ( - "context" - "flag" - "fmt" - - "github.com/cs3org/reva/v2/pkg/micro/ocdav" - "github.com/oklog/run" - "github.com/owncloud/ocis/extensions/ocdav/pkg/config" - "github.com/owncloud/ocis/extensions/ocdav/pkg/config/parser" - "github.com/owncloud/ocis/extensions/ocdav/pkg/server/debug" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" -) - -// OCDav is the entrypoint for the ocdav command. -// TODO move ocdav cmd to a separate service -func OCDav(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "ocdav", - Usage: "start ocdav service", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - //metrics = metrics.New() - - defer cancel() - - gr.Add(func() error { - s, err := ocdav.Service( - ocdav.Context(ctx), - ocdav.Logger(logger.Logger), - ocdav.Address(cfg.HTTP.Addr), - ocdav.FilesNamespace(cfg.FilesNamespace), - ocdav.WebdavNamespace(cfg.WebdavNamespace), - ocdav.SharesNamespace(cfg.SharesNamespace), - ocdav.Timeout(cfg.Timeout), - ocdav.Insecure(cfg.Insecure), - ocdav.PublicURL(cfg.PublicURL), - ocdav.Prefix(cfg.HTTP.Prefix), - ocdav.GatewaySvc(cfg.Reva.Address), - ocdav.JWTSecret(cfg.TokenManager.JWTSecret), - // ocdav.FavoriteManager() // FIXME needs a proper persistence implementation - // ocdav.LockSystem(), // will default to the CS3 lock system - // ocdav.TLSConfig() // tls config for the http server - ) - if err != nil { - return err - } - - return s.Run() - }, func(err error) { - logger.Info().Err(err).Str("server", c.Command.Name).Msg("Shutting down server") - cancel() - }) - - { - server, err := debug.Server( - debug.Logger(logger), - debug.Context(ctx), - debug.Config(cfg), - ) - - if err != nil { - logger.Info(). - Err(err). - Str("server", "debug"). - Msg("Failed to initialize server") - - return err - } - - gr.Add(server.ListenAndServe, func(_ error) { - cancel() - }) - } - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() - }, - } -} - -// OCDavSutureService allows for the ocdav command to be embedded and supervised by a suture supervisor tree. -type OCDavSutureService struct { - cfg *config.Config -} - -// NewOCDav creates a new ocdav.OCDavSutureService -func NewOCDav(cfg *ociscfg.Config) suture.Service { - cfg.OCDav.Commons = cfg.Commons - return OCDavSutureService{ - cfg: cfg.OCDav, - } -} - -func (s OCDavSutureService) Serve(ctx context.Context) error { - // s.cfg.Reva.Frontend.Context = ctx - cmd := OCDav(s.cfg) - f := &flag.FlagSet{} - cmdFlags := cmd.Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if cmd.Before != nil { - if err := cmd.Before(cliCtx); err != nil { - return err - } - } - if err := cmd.Action(cliCtx); err != nil { - return err - } - - return nil -} diff --git a/extensions/ocdav/pkg/command/root.go b/extensions/ocdav/pkg/command/root.go new file mode 100644 index 0000000000..e022dd0fec --- /dev/null +++ b/extensions/ocdav/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/ocdav/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-ocdav command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "ocis-ocdav", + Usage: "Provide a WebDav API for oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the ocdav command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new ocdav.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.OCDav.Commons = cfg.Commons + return SutureService{ + cfg: cfg.OCDav, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/ocdav/pkg/command/server.go b/extensions/ocdav/pkg/command/server.go new file mode 100644 index 0000000000..55f8a48e48 --- /dev/null +++ b/extensions/ocdav/pkg/command/server.go @@ -0,0 +1,98 @@ +package command + +import ( + "context" + "fmt" + + "github.com/cs3org/reva/v2/pkg/micro/ocdav" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/ocdav/pkg/config" + "github.com/owncloud/ocis/extensions/ocdav/pkg/config/parser" + "github.com/owncloud/ocis/extensions/ocdav/pkg/logging" + "github.com/owncloud/ocis/extensions/ocdav/pkg/server/debug" + "github.com/owncloud/ocis/extensions/ocdav/pkg/tracing" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + gr.Add(func() error { + s, err := ocdav.Service( + ocdav.Context(ctx), + ocdav.Logger(logger.Logger), + ocdav.Address(cfg.HTTP.Addr), + ocdav.FilesNamespace(cfg.FilesNamespace), + ocdav.WebdavNamespace(cfg.WebdavNamespace), + ocdav.SharesNamespace(cfg.SharesNamespace), + ocdav.Timeout(cfg.Timeout), + ocdav.Insecure(cfg.Insecure), + ocdav.PublicURL(cfg.PublicURL), + ocdav.Prefix(cfg.HTTP.Prefix), + ocdav.GatewaySvc(cfg.Reva.Address), + ocdav.JWTSecret(cfg.TokenManager.JWTSecret), + // ocdav.FavoriteManager() // FIXME needs a proper persistence implementation + // ocdav.LockSystem(), // will default to the CS3 lock system + // ocdav.TLSConfig() // tls config for the http server + ) + if err != nil { + return err + } + + return s.Run() + }, func(err error) { + logger.Info().Err(err).Str("server", c.Command.Name).Msg("Shutting down server") + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/ocdav/pkg/command/version.go b/extensions/ocdav/pkg/command/version.go new file mode 100644 index 0000000000..05bf397226 --- /dev/null +++ b/extensions/ocdav/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/ocdav/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.HTTP.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/ocdav/pkg/config/config.go b/extensions/ocdav/pkg/config/config.go index da510a3eff..2ed551fe6c 100644 --- a/extensions/ocdav/pkg/config/config.go +++ b/extensions/ocdav/pkg/config/config.go @@ -1,14 +1,17 @@ package config -import "github.com/owncloud/ocis/ocis-pkg/shared" +import ( + "context" + + "github.com/owncloud/ocis/ocis-pkg/shared" +) type Config struct { *shared.Commons `yaml:"-"` Service Service `yaml:"-"` Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` + Log *Log `yaml:"log"` Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` HTTP HTTPConfig `yaml:"http"` @@ -28,6 +31,8 @@ type Config struct { // Timeout in seconds when making requests to the gateway Timeout int64 `yaml:"timeout"` Middleware Middleware `yaml:"middleware"` + + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;OCDAV_TRACING_ENABLED" desc:"Activates tracing."` @@ -36,7 +41,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;OCDAV_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;OCDAV_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;OCDAV_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;OCDAV_LOG_COLOR" desc:"Activates colorized log output."` @@ -55,9 +60,10 @@ type Debug struct { } type HTTPConfig struct { - Addr string `yaml:"addr" env:"OCDAV_HTTP_ADDR" desc:"The address of the http service."` - Protocol string `yaml:"protocol" env:"OCDAV_HTTP_PROTOCOL" desc:"The transport protocol of the http service."` - Prefix string `yaml:"prefix"` + Addr string `yaml:"addr" env:"OCDAV_HTTP_ADDR" desc:"The address of the http service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"OCDAV_HTTP_PROTOCOL" desc:"The transport protocol of the http service."` + Prefix string `yaml:"prefix"` } // Middleware configures reva middlewares. diff --git a/extensions/ocdav/pkg/config/defaults/defaultconfig.go b/extensions/ocdav/pkg/config/defaults/defaultconfig.go index b55f9e6513..8567bbdb71 100644 --- a/extensions/ocdav/pkg/config/defaults/defaultconfig.go +++ b/extensions/ocdav/pkg/config/defaults/defaultconfig.go @@ -20,9 +20,10 @@ func DefaultConfig() *config.Config { Zpages: false, }, HTTP: config.HTTPConfig{ - Addr: "127.0.0.1:0", // :0 to pick any free local port - Protocol: "tcp", - Prefix: "", + Addr: "127.0.0.1:0", // :0 to pick any free local port + Namespace: "", //TODO: make this configurable for the reva micro service + Protocol: "tcp", + Prefix: "", }, Service: config.Service{ Name: "ocdav", @@ -46,15 +47,15 @@ func DefaultConfig() *config.Config { func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/ocdav/pkg/logging/logging.go b/extensions/ocdav/pkg/logging/logging.go new file mode 100644 index 0000000000..37a48c877e --- /dev/null +++ b/extensions/ocdav/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/ocdav/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/ocdav/pkg/tracing/tracing.go b/extensions/ocdav/pkg/tracing/tracing.go new file mode 100644 index 0000000000..45fc03ba67 --- /dev/null +++ b/extensions/ocdav/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/ocdav/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the proxy service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/sharing/cmd/sharing/main.go b/extensions/sharing/cmd/sharing/main.go new file mode 100644 index 0000000000..8f1936f293 --- /dev/null +++ b/extensions/sharing/cmd/sharing/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/sharing/pkg/command" + "github.com/owncloud/ocis/extensions/sharing/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/sharing/pkg/Makefile b/extensions/sharing/pkg/Makefile deleted file mode 100644 index 51243d83fc..0000000000 --- a/extensions/sharing/pkg/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -SHELL := bash -NAME := ocs - -include ../../.make/recursion.mk - -############ tooling ############ -ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI -include ../../.bingo/Variables.mk -endif - -############ go tooling ############ -include ../../.make/go.mk - -############ release ############ -include ../../.make/release.mk - -############ docs generate ############ -include ../../.make/docs.mk - -.PHONY: docs-generate -docs-generate: config-docs-generate - -############ generate ############ -include ../../.make/generate.mk - -.PHONY: ci-go-generate -ci-go-generate: # CI runs ci-node-generate automatically before this target - -.PHONY: ci-node-generate -ci-node-generate: - -############ licenses ############ -.PHONY: ci-node-check-licenses -ci-node-check-licenses: - -.PHONY: ci-node-save-licenses -ci-node-save-licenses: diff --git a/extensions/sharing/pkg/command/health.go b/extensions/sharing/pkg/command/health.go new file mode 100644 index 0000000000..a159314c37 --- /dev/null +++ b/extensions/sharing/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/sharing/pkg/config" + "github.com/owncloud/ocis/extensions/sharing/pkg/config/parser" + "github.com/owncloud/ocis/extensions/sharing/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/sharing/pkg/command/root.go b/extensions/sharing/pkg/command/root.go new file mode 100644 index 0000000000..30e24793fa --- /dev/null +++ b/extensions/sharing/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/sharing/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-sharing command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "ocis-sharing", + Usage: "Provide sharing for oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the sharing command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new sharing.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.Sharing.Commons = cfg.Commons + return SutureService{ + cfg: cfg.Sharing, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/sharing/pkg/command/server.go b/extensions/sharing/pkg/command/server.go new file mode 100644 index 0000000000..26c085082c --- /dev/null +++ b/extensions/sharing/pkg/command/server.go @@ -0,0 +1,107 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/sharing/pkg/config" + "github.com/owncloud/ocis/extensions/sharing/pkg/config/parser" + "github.com/owncloud/ocis/extensions/sharing/pkg/logging" + "github.com/owncloud/ocis/extensions/sharing/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/sharing/pkg/server/debug" + "github.com/owncloud/ocis/extensions/sharing/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/sync" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.SharingConfigFromStruct(cfg) + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + if err := external.RegisterGRPCEndpoint( + ctx, + cfg.GRPC.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.GRPC.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/sharing/pkg/command/version.go b/extensions/sharing/pkg/command/version.go new file mode 100644 index 0000000000..e54734d0ff --- /dev/null +++ b/extensions/sharing/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/sharing/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/sharing/pkg/config/config.go b/extensions/sharing/pkg/config/config.go index f81d37faa1..24dc40c980 100644 --- a/extensions/sharing/pkg/config/config.go +++ b/extensions/sharing/pkg/config/config.go @@ -1,14 +1,17 @@ package config -import "github.com/owncloud/ocis/ocis-pkg/shared" +import ( + "context" + + "github.com/owncloud/ocis/ocis-pkg/shared" +) type Config struct { *shared.Commons `yaml:"-"` Service Service `yaml:"-"` Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` + Log *Log `yaml:"log"` Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` GRPC GRPCConfig `yaml:"grpc"` @@ -21,6 +24,9 @@ type Config struct { PublicSharingDriver string `yaml:"public_sharing_driver"` PublicSharingDrivers PublicSharingDrivers `yaml:"public_sharing_drivers"` Events Events `yaml:"events"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;SHARING_TRACING_ENABLED" desc:"Activates tracing."` @@ -29,7 +35,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;SHARING_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;SHARING_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;SHARING_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;SHARING_LOG_COLOR" desc:"Activates colorized log output."` @@ -48,8 +54,9 @@ type Debug struct { } type GRPCConfig struct { - Addr string `yaml:"addr" env:"SHARING_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"SHARING_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` + Addr string `yaml:"addr" env:"SHARING_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"SHARING_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` } type UserSharingDrivers struct { diff --git a/extensions/sharing/pkg/config/defaults/defaultconfig.go b/extensions/sharing/pkg/config/defaults/defaultconfig.go index 924e432288..47f4f2e15a 100644 --- a/extensions/sharing/pkg/config/defaults/defaultconfig.go +++ b/extensions/sharing/pkg/config/defaults/defaultconfig.go @@ -23,8 +23,9 @@ func DefaultConfig() *config.Config { Zpages: false, }, GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9150", - Protocol: "tcp", + Addr: "127.0.0.1:9150", + Namespace: "com.owncloud.api", + Protocol: "tcp", }, Service: config.Service{ Name: "sharing", @@ -81,15 +82,15 @@ func DefaultConfig() *config.Config { func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/sharing/pkg/logging/logging.go b/extensions/sharing/pkg/logging/logging.go new file mode 100644 index 0000000000..364f2a76de --- /dev/null +++ b/extensions/sharing/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/sharing/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/sharing/pkg/command/command.go b/extensions/sharing/pkg/revaconfig/config.go similarity index 57% rename from extensions/sharing/pkg/command/command.go rename to extensions/sharing/pkg/revaconfig/config.go index 2e0eca8d14..a7c8710e78 100644 --- a/extensions/sharing/pkg/command/command.go +++ b/extensions/sharing/pkg/revaconfig/config.go @@ -1,117 +1,17 @@ -package command +package revaconfig import ( - "context" - "flag" - "fmt" - "os" - "path" - "path/filepath" - - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/gofrs/uuid" - "github.com/oklog/run" "github.com/owncloud/ocis/extensions/sharing/pkg/config" - "github.com/owncloud/ocis/extensions/sharing/pkg/config/parser" - "github.com/owncloud/ocis/extensions/sharing/pkg/server/debug" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" ) -// Sharing is the entrypoint for the sharing command. -func Sharing(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "sharing", - Usage: "start sharing service", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // precreate folders - if cfg.UserSharingDriver == "json" && cfg.UserSharingDrivers.JSON.File != "" { - if err := os.MkdirAll(filepath.Dir(cfg.UserSharingDrivers.JSON.File), os.FileMode(0700)); err != nil { - return err - } - } - if cfg.PublicSharingDriver == "json" && cfg.PublicSharingDrivers.JSON.File != "" { - if err := os.MkdirAll(filepath.Dir(cfg.PublicSharingDrivers.JSON.File), os.FileMode(0700)); err != nil { - return err - } - } - - uuid := uuid.Must(uuid.NewV4()) - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+uuid.String()+".pid") - - rcfg := sharingConfigFromStruct(c, cfg) - - gr.Add(func() error { - runtime.RunWithOptions( - rcfg, - pidFile, - runtime.WithLogger(&logger.Logger), - ) - return nil - }, func(_ error) { - logger.Info(). - Str("server", c.Command.Name). - Msg("Shutting down server") - - cancel() - }) - - debug, err := debug.Server( - debug.Logger(logger), - debug.Context(ctx), - debug.Config(cfg), - ) - - if err != nil { - logger.Info().Err(err).Str("server", c.Command.Name+"-debug").Msg("Failed to initialize server") - return err - } - - gr.Add(debug.ListenAndServe, func(_ error) { - cancel() - }) - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() - }, - } -} - -// sharingConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func sharingConfigFromStruct(c *cli.Context, cfg *config.Config) map[string]interface{} { +// SharingConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func SharingConfigFromStruct(cfg *config.Config) map[string]interface{} { rcfg := map[string]interface{}{ "core": map[string]interface{}{ "tracing_enabled": cfg.Tracing.Enabled, "tracing_endpoint": cfg.Tracing.Endpoint, "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, + "tracing_service_name": cfg.Service.Name, }, "shared": map[string]interface{}{ "jwt_secret": cfg.TokenManager.JWTSecret, @@ -205,39 +105,3 @@ func sharingConfigFromStruct(c *cli.Context, cfg *config.Config) map[string]inte } return rcfg } - -// SharingSutureService allows for the storage-sharing command to be embedded and supervised by a suture supervisor tree. -type SharingSutureService struct { - cfg *config.Config -} - -// NewSharingSutureService creates a new store.SharingSutureService -func NewSharing(cfg *ociscfg.Config) suture.Service { - cfg.Sharing.Commons = cfg.Commons - return SharingSutureService{ - cfg: cfg.Sharing, - } -} - -func (s SharingSutureService) Serve(ctx context.Context) error { - // s.cfg.Reva.Sharing.Context = ctx - cmd := Sharing(s.cfg) - f := &flag.FlagSet{} - cmdFlags := cmd.Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if cmd.Before != nil { - if err := cmd.Before(cliCtx); err != nil { - return err - } - } - if err := cmd.Action(cliCtx); err != nil { - return err - } - - return nil -} diff --git a/extensions/sharing/pkg/tracing/tracing.go b/extensions/sharing/pkg/tracing/tracing.go new file mode 100644 index 0000000000..443fbc88c6 --- /dev/null +++ b/extensions/sharing/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/sharing/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the proxy service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/storage-metadata/cmd/storage-metadata/main.go b/extensions/storage-metadata/cmd/storage-metadata/main.go new file mode 100644 index 0000000000..e52ef1a27b --- /dev/null +++ b/extensions/storage-metadata/cmd/storage-metadata/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/command" + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/storage-metadata/pkg/Makefile b/extensions/storage-metadata/pkg/Makefile deleted file mode 100644 index 51243d83fc..0000000000 --- a/extensions/storage-metadata/pkg/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -SHELL := bash -NAME := ocs - -include ../../.make/recursion.mk - -############ tooling ############ -ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI -include ../../.bingo/Variables.mk -endif - -############ go tooling ############ -include ../../.make/go.mk - -############ release ############ -include ../../.make/release.mk - -############ docs generate ############ -include ../../.make/docs.mk - -.PHONY: docs-generate -docs-generate: config-docs-generate - -############ generate ############ -include ../../.make/generate.mk - -.PHONY: ci-go-generate -ci-go-generate: # CI runs ci-node-generate automatically before this target - -.PHONY: ci-node-generate -ci-node-generate: - -############ licenses ############ -.PHONY: ci-node-check-licenses -ci-node-check-licenses: - -.PHONY: ci-node-save-licenses -ci-node-save-licenses: diff --git a/extensions/storage-metadata/pkg/command/command.go b/extensions/storage-metadata/pkg/command/command.go deleted file mode 100644 index 5d9f4d760b..0000000000 --- a/extensions/storage-metadata/pkg/command/command.go +++ /dev/null @@ -1,267 +0,0 @@ -package command - -import ( - "context" - "flag" - "fmt" - "os" - "path" - - userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/gofrs/uuid" - "github.com/oklog/run" - "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config" - "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config/parser" - "github.com/owncloud/ocis/extensions/storage-metadata/pkg/server/debug" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/service/external" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - "github.com/owncloud/ocis/ocis-pkg/version" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" -) - -// StorageMetadata the entrypoint for the storage-storage-metadata command. -// -// It provides a ocis-specific storage store metadata (shares,account,settings...) -func StorageMetadata(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "storage-metadata", - Usage: "start storage-metadata service", - Category: "extensions", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - - gr := run.Group{} - ctx, cancel := func() (context.Context, context.CancelFunc) { - if cfg.Context == nil { - return context.WithCancel(context.Background()) - } - return context.WithCancel(cfg.Context) - }() - - defer cancel() - - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") - rcfg := storageMetadataFromStruct(c, cfg) - - gr.Add(func() error { - runtime.RunWithOptions( - rcfg, - pidFile, - runtime.WithLogger(&logger.Logger), - ) - return nil - }, func(_ error) { - logger.Info(). - Str("server", c.Command.Name). - Msg("Shutting down server") - - cancel() - }) - - debugServer, err := debug.Server( - debug.Logger(logger), - debug.Context(ctx), - debug.Config(cfg), - ) - - if err != nil { - logger.Info(). - Err(err). - Str("server", c.Command.Name+"-debug"). - Msg("Failed to initialize server") - - return err - } - - gr.Add(func() error { - return debugServer.ListenAndServe() - }, func(_ error) { - _ = debugServer.Shutdown(ctx) - cancel() - }) - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - if err := external.RegisterGRPCEndpoint( - ctx, - "com.owncloud.storage.metadata", - uuid.Must(uuid.NewV4()).String(), - cfg.GRPC.Addr, - version.String, - logger, - ); err != nil { - logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") - } - - return gr.Run() - }, - } -} - -// storageMetadataFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func storageMetadataFromStruct(c *cli.Context, cfg *config.Config) map[string]interface{} { - rcfg := map[string]interface{}{ - "core": map[string]interface{}{ - "tracing_enabled": cfg.Tracing.Enabled, - "tracing_endpoint": cfg.Tracing.Endpoint, - "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, - }, - "shared": map[string]interface{}{ - "jwt_secret": cfg.TokenManager.JWTSecret, - "gatewaysvc": cfg.Reva.Address, - "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, - }, - "grpc": map[string]interface{}{ - "network": cfg.GRPC.Protocol, - "address": cfg.GRPC.Addr, - "services": map[string]interface{}{ - "gateway": map[string]interface{}{ - // registries are located on the gateway - "authregistrysvc": cfg.GRPC.Addr, - "storageregistrysvc": cfg.GRPC.Addr, - // user metadata is located on the users services - "userprovidersvc": cfg.GRPC.Addr, - "groupprovidersvc": cfg.GRPC.Addr, - "permissionssvc": cfg.GRPC.Addr, - // other - "disable_home_creation_on_login": true, // metadata manually creates a space - // metadata always uses the simple upload, so no transfer secret or datagateway needed - }, - "userprovider": map[string]interface{}{ - "driver": "memory", - "drivers": map[string]interface{}{ - "memory": map[string]interface{}{ - "users": map[string]interface{}{ - "serviceuser": map[string]interface{}{ - "id": map[string]interface{}{ - "opaqueId": cfg.MetadataUserID, - "idp": "internal", - "type": userpb.UserType_USER_TYPE_PRIMARY, - }, - "username": "serviceuser", - "display_name": "System User", - }, - }, - }, - }, - }, - "authregistry": map[string]interface{}{ - "driver": "static", - "drivers": map[string]interface{}{ - "static": map[string]interface{}{ - "rules": map[string]interface{}{ - "machine": cfg.GRPC.Addr, - }, - }, - }, - }, - "authprovider": map[string]interface{}{ - "auth_manager": "machine", - "auth_managers": map[string]interface{}{ - "machine": map[string]interface{}{ - "api_key": cfg.MachineAuthAPIKey, - "gateway_addr": cfg.GRPC.Addr, - }, - }, - }, - "permissions": map[string]interface{}{ - "driver": "demo", - "drivers": map[string]interface{}{ - "demo": map[string]interface{}{}, - }, - }, - "storageregistry": map[string]interface{}{ - "driver": "static", - "drivers": map[string]interface{}{ - "static": map[string]interface{}{ - "rules": map[string]interface{}{ - "/": map[string]interface{}{ - "address": cfg.GRPC.Addr, - }, - }, - }, - }, - }, - "storageprovider": map[string]interface{}{ - "driver": cfg.Driver, - "drivers": config.MetadataDrivers(cfg), - "data_server_url": cfg.DataServerURL, - "tmp_folder": cfg.TempFolder, - }, - }, - }, - "http": map[string]interface{}{ - "network": cfg.HTTP.Protocol, - "address": cfg.HTTP.Addr, - // no datagateway needed as the metadata clients directly talk to the dataprovider with the simple protocol - "services": map[string]interface{}{ - "dataprovider": map[string]interface{}{ - "prefix": "data", - "driver": cfg.Driver, - "drivers": config.MetadataDrivers(cfg), - "timeout": 86400, - "insecure": cfg.DataProviderInsecure, - "disable_tus": true, - }, - }, - }, - } - return rcfg -} - -// SutureService allows for the storage-metadata command to be embedded and supervised by a suture supervisor tree. -type MetadataSutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new storagemetadata.SutureService -func NewStorageMetadata(cfg *ociscfg.Config) suture.Service { - cfg.StorageMetadata.Commons = cfg.Commons - return MetadataSutureService{ - cfg: cfg.StorageMetadata, - } -} - -func (s MetadataSutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - f := &flag.FlagSet{} - cmdFlags := StorageMetadata(s.cfg).Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if StorageMetadata(s.cfg).Before != nil { - if err := StorageMetadata(s.cfg).Before(cliCtx); err != nil { - return err - } - } - if err := StorageMetadata(s.cfg).Action(cliCtx); err != nil { - return err - } - - return nil -} diff --git a/extensions/storage-metadata/pkg/command/health.go b/extensions/storage-metadata/pkg/command/health.go new file mode 100644 index 0000000000..66274f9e72 --- /dev/null +++ b/extensions/storage-metadata/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config" + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config/parser" + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/storage-metadata/pkg/command/root.go b/extensions/storage-metadata/pkg/command/root.go new file mode 100644 index 0000000000..8515d544ee --- /dev/null +++ b/extensions/storage-metadata/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-storage-metadata command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "ocis-storage-metadata", + Usage: "Provide apps for oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the storage-metadata command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new storage-metadata.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.StorageMetadata.Commons = cfg.Commons + return SutureService{ + cfg: cfg.StorageMetadata, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/storage-metadata/pkg/command/server.go b/extensions/storage-metadata/pkg/command/server.go new file mode 100644 index 0000000000..43b720eb29 --- /dev/null +++ b/extensions/storage-metadata/pkg/command/server.go @@ -0,0 +1,118 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config" + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config/parser" + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/logging" + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/server/debug" + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/sync" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.StorageMetadataFromStruct(cfg) + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + if err := external.RegisterGRPCEndpoint( + ctx, + cfg.GRPC.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.GRPC.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + } + + if err := external.RegisterHTTPEndpoint( + ctx, + cfg.HTTP.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.HTTP.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the http endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/storage-metadata/pkg/command/version.go b/extensions/storage-metadata/pkg/command/version.go new file mode 100644 index 0000000000..0f58c54990 --- /dev/null +++ b/extensions/storage-metadata/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/storage-metadata/pkg/config/config.go b/extensions/storage-metadata/pkg/config/config.go index 8c4475600f..8f90172995 100644 --- a/extensions/storage-metadata/pkg/config/config.go +++ b/extensions/storage-metadata/pkg/config/config.go @@ -10,15 +10,12 @@ type Config struct { *shared.Commons `yaml:"-"` Service Service `yaml:"-"` Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` + Log *Log `yaml:"log"` Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` GRPC GRPCConfig `yaml:"grpc"` HTTP HTTPConfig `yaml:"http"` - Context context.Context `yaml:"context"` - TokenManager *TokenManager `yaml:"token_manager"` Reva *Reva `yaml:"reva"` MachineAuthAPIKey string `yaml:"machine_auth_api_key" env:"STORAGE_METADATA_MACHINE_AUTH_API_KEY"` @@ -30,6 +27,9 @@ type Config struct { DataServerURL string `yaml:"data_server_url"` TempFolder string `yaml:"temp_folder"` DataProviderInsecure bool `yaml:"data_provider_insecure" env:"OCIS_INSECURE;STORAGE_METADATA_DATAPROVIDER_INSECURE"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;STORAGE_METADATA_TRACING_ENABLED" desc:"Activates tracing."` @@ -38,7 +38,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;STORAGE_METADATA_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;STORAGE_METADATA_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;STORAGE_METADATA_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;STORAGE_METADATA_LOG_COLOR" desc:"Activates colorized log output."` @@ -57,13 +57,15 @@ type Debug struct { } type GRPCConfig struct { - Addr string `yaml:"addr" env:"STORAGE_METADATA_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"STORAGE_METADATA_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` + Addr string `yaml:"addr" env:"STORAGE_METADATA_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"STORAGE_METADATA_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` } type HTTPConfig struct { - Addr string `yaml:"addr" env:"STORAGE_METADATA_HTTP_ADDR" desc:"The address of the http service."` - Protocol string `yaml:"protocol" env:"STORAGE_METADATA_HTTP_PROTOCOL" desc:"The transport protocol of the http service."` + Addr string `yaml:"addr" env:"STORAGE_METADATA_HTTP_ADDR" desc:"The address of the http service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"STORAGE_METADATA_HTTP_PROTOCOL" desc:"The transport protocol of the http service."` } type Drivers struct { diff --git a/extensions/storage-metadata/pkg/config/defaults/defaultconfig.go b/extensions/storage-metadata/pkg/config/defaults/defaultconfig.go index 4f274aa0ca..715ad43932 100644 --- a/extensions/storage-metadata/pkg/config/defaults/defaultconfig.go +++ b/extensions/storage-metadata/pkg/config/defaults/defaultconfig.go @@ -24,12 +24,14 @@ func DefaultConfig() *config.Config { Zpages: false, }, GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9215", - Protocol: "tcp", + Addr: "127.0.0.1:9215", + Namespace: "com.owncloud.api", + Protocol: "tcp", }, HTTP: config.HTTPConfig{ - Addr: "127.0.0.1:9216", - Protocol: "tcp", + Addr: "127.0.0.1:9216", + Namespace: "com.owncloud.web", + Protocol: "tcp", }, Service: config.Service{ Name: "storage-metadata", @@ -84,15 +86,15 @@ func DefaultConfig() *config.Config { func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/storage-metadata/pkg/logging/logging.go b/extensions/storage-metadata/pkg/logging/logging.go new file mode 100644 index 0000000000..feef4ad399 --- /dev/null +++ b/extensions/storage-metadata/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/storage-metadata/pkg/revaconfig/config.go b/extensions/storage-metadata/pkg/revaconfig/config.go new file mode 100644 index 0000000000..be7706efd0 --- /dev/null +++ b/extensions/storage-metadata/pkg/revaconfig/config.go @@ -0,0 +1,118 @@ +package revaconfig + +import ( + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config" +) + +// StorageMetadataFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func StorageMetadataFromStruct(cfg *config.Config) map[string]interface{} { + rcfg := map[string]interface{}{ + "core": map[string]interface{}{ + "tracing_enabled": cfg.Tracing.Enabled, + "tracing_endpoint": cfg.Tracing.Endpoint, + "tracing_collector": cfg.Tracing.Collector, + "tracing_service_name": cfg.Service.Name, + }, + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, + "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, + }, + "grpc": map[string]interface{}{ + "network": cfg.GRPC.Protocol, + "address": cfg.GRPC.Addr, + "services": map[string]interface{}{ + "gateway": map[string]interface{}{ + // registries are located on the gateway + "authregistrysvc": cfg.GRPC.Addr, + "storageregistrysvc": cfg.GRPC.Addr, + // user metadata is located on the users services + "userprovidersvc": cfg.GRPC.Addr, + "groupprovidersvc": cfg.GRPC.Addr, + "permissionssvc": cfg.GRPC.Addr, + // other + "disable_home_creation_on_login": true, // metadata manually creates a space + // metadata always uses the simple upload, so no transfer secret or datagateway needed + }, + "userprovider": map[string]interface{}{ + "driver": "memory", + "drivers": map[string]interface{}{ + "memory": map[string]interface{}{ + "users": map[string]interface{}{ + "serviceuser": map[string]interface{}{ + "id": map[string]interface{}{ + "opaqueId": cfg.MetadataUserID, + "idp": "internal", + "type": userpb.UserType_USER_TYPE_PRIMARY, + }, + "username": "serviceuser", + "display_name": "System User", + }, + }, + }, + }, + }, + "authregistry": map[string]interface{}{ + "driver": "static", + "drivers": map[string]interface{}{ + "static": map[string]interface{}{ + "rules": map[string]interface{}{ + "machine": cfg.GRPC.Addr, + }, + }, + }, + }, + "authprovider": map[string]interface{}{ + "auth_manager": "machine", + "auth_managers": map[string]interface{}{ + "machine": map[string]interface{}{ + "api_key": cfg.MachineAuthAPIKey, + "gateway_addr": cfg.GRPC.Addr, + }, + }, + }, + "permissions": map[string]interface{}{ + "driver": "demo", + "drivers": map[string]interface{}{ + "demo": map[string]interface{}{}, + }, + }, + "storageregistry": map[string]interface{}{ + "driver": "static", + "drivers": map[string]interface{}{ + "static": map[string]interface{}{ + "rules": map[string]interface{}{ + "/": map[string]interface{}{ + "address": cfg.GRPC.Addr, + }, + }, + }, + }, + }, + "storageprovider": map[string]interface{}{ + "driver": cfg.Driver, + "drivers": config.MetadataDrivers(cfg), + "data_server_url": cfg.DataServerURL, + "tmp_folder": cfg.TempFolder, + }, + }, + }, + "http": map[string]interface{}{ + "network": cfg.HTTP.Protocol, + "address": cfg.HTTP.Addr, + // no datagateway needed as the metadata clients directly talk to the dataprovider with the simple protocol + "services": map[string]interface{}{ + "dataprovider": map[string]interface{}{ + "prefix": "data", + "driver": cfg.Driver, + "drivers": config.MetadataDrivers(cfg), + "timeout": 86400, + "insecure": cfg.DataProviderInsecure, + "disable_tus": true, + }, + }, + }, + } + return rcfg +} diff --git a/extensions/storage-metadata/pkg/tracing/tracing.go b/extensions/storage-metadata/pkg/tracing/tracing.go new file mode 100644 index 0000000000..49a96c8451 --- /dev/null +++ b/extensions/storage-metadata/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the proxy service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/storage-publiclink/cmd/storage-publiclink/main.go b/extensions/storage-publiclink/cmd/storage-publiclink/main.go new file mode 100644 index 0000000000..55e9f26f95 --- /dev/null +++ b/extensions/storage-publiclink/cmd/storage-publiclink/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/command" + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/storage-publiclink/pkg/Makefile b/extensions/storage-publiclink/pkg/Makefile deleted file mode 100644 index 51243d83fc..0000000000 --- a/extensions/storage-publiclink/pkg/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -SHELL := bash -NAME := ocs - -include ../../.make/recursion.mk - -############ tooling ############ -ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI -include ../../.bingo/Variables.mk -endif - -############ go tooling ############ -include ../../.make/go.mk - -############ release ############ -include ../../.make/release.mk - -############ docs generate ############ -include ../../.make/docs.mk - -.PHONY: docs-generate -docs-generate: config-docs-generate - -############ generate ############ -include ../../.make/generate.mk - -.PHONY: ci-go-generate -ci-go-generate: # CI runs ci-node-generate automatically before this target - -.PHONY: ci-node-generate -ci-node-generate: - -############ licenses ############ -.PHONY: ci-node-check-licenses -ci-node-check-licenses: - -.PHONY: ci-node-save-licenses -ci-node-save-licenses: diff --git a/extensions/storage-publiclink/pkg/command/health.go b/extensions/storage-publiclink/pkg/command/health.go new file mode 100644 index 0000000000..f406daeb5f --- /dev/null +++ b/extensions/storage-publiclink/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config" + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config/parser" + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/storage-publiclink/pkg/command/root.go b/extensions/storage-publiclink/pkg/command/root.go new file mode 100644 index 0000000000..1b89e3d262 --- /dev/null +++ b/extensions/storage-publiclink/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-accounts command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "ocis-appprovider", + Usage: "Provide apps for oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the accounts command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new accounts.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.StoragePublicLink.Commons = cfg.Commons + return SutureService{ + cfg: cfg.StoragePublicLink, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/storage-publiclink/pkg/command/server.go b/extensions/storage-publiclink/pkg/command/server.go new file mode 100644 index 0000000000..eb0a938e63 --- /dev/null +++ b/extensions/storage-publiclink/pkg/command/server.go @@ -0,0 +1,107 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config" + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config/parser" + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/logging" + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/server/debug" + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/sync" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.StoragePublicLinkConfigFromStruct(cfg) + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + if err := external.RegisterGRPCEndpoint( + ctx, + cfg.GRPC.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.GRPC.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/storage-publiclink/pkg/command/storagepubliclink.go b/extensions/storage-publiclink/pkg/command/storagepubliclink.go deleted file mode 100644 index 78996e6597..0000000000 --- a/extensions/storage-publiclink/pkg/command/storagepubliclink.go +++ /dev/null @@ -1,165 +0,0 @@ -package command - -import ( - "context" - "flag" - "fmt" - "os" - "path" - - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/gofrs/uuid" - "github.com/oklog/run" - "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config" - "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config/parser" - "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/server/debug" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" -) - -// StoragePublicLink is the entrypoint for the reva-storage-public-link command. -func StoragePublicLink(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "storage-public-link", - Usage: "start storage-public-link service", - Category: "extensions", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") - rcfg := storagePublicLinkConfigFromStruct(c, cfg) - - gr.Add(func() error { - runtime.RunWithOptions( - rcfg, - pidFile, - runtime.WithLogger(&logger.Logger), - ) - return nil - }, func(_ error) { - logger.Info(). - Str("server", c.Command.Name). - Msg("Shutting down server") - - cancel() - }) - - debugServer, err := debug.Server( - debug.Logger(logger), - debug.Context(ctx), - debug.Config(cfg), - ) - - if err != nil { - logger.Info().Err(err).Str("server", c.Command.Name+"-debug").Msg("Failed to initialize server") - return err - } - - gr.Add(debugServer.ListenAndServe, func(_ error) { - cancel() - }) - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() - }, - } -} - -// storagePublicLinkConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func storagePublicLinkConfigFromStruct(c *cli.Context, cfg *config.Config) map[string]interface{} { - rcfg := map[string]interface{}{ - "core": map[string]interface{}{ - "tracing_enabled": cfg.Tracing.Enabled, - "tracing_endpoint": cfg.Tracing.Endpoint, - "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, - }, - "shared": map[string]interface{}{ - "jwt_secret": cfg.TokenManager.JWTSecret, - "gatewaysvc": cfg.Reva.Address, - "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, - }, - "grpc": map[string]interface{}{ - "network": cfg.GRPC.Protocol, - "address": cfg.GRPC.Addr, - "interceptors": map[string]interface{}{ - "log": map[string]interface{}{}, - }, - "services": map[string]interface{}{ - "publicstorageprovider": map[string]interface{}{ - "mount_id": cfg.StorageProvider.MountID, - "gateway_addr": cfg.StorageProvider.GatewayEndpoint, - }, - "authprovider": map[string]interface{}{ - "auth_manager": "publicshares", - "auth_managers": map[string]interface{}{ - "publicshares": map[string]interface{}{ - "gateway_addr": cfg.AuthProvider.GatewayEndpoint, - }, - }, - }, - }, - }, - } - return rcfg -} - -// StoragePublicLinkSutureService allows for the storage-public-link command to be embedded and supervised by a suture supervisor tree. -type StoragePublicLinkSutureService struct { - cfg *config.Config -} - -// NewStoragePublicLinkSutureService creates a new storage.StoragePublicLinkSutureService -func NewStoragePublicLink(cfg *ociscfg.Config) suture.Service { - cfg.StoragePublicLink.Commons = cfg.Commons - return StoragePublicLinkSutureService{ - cfg: cfg.StoragePublicLink, - } -} - -func (s StoragePublicLinkSutureService) Serve(ctx context.Context) error { - // s.cfg.Reva.StoragePublicLink.Context = ctx - cmd := StoragePublicLink(s.cfg) - f := &flag.FlagSet{} - cmdFlags := cmd.Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if cmd.Before != nil { - if err := cmd.Before(cliCtx); err != nil { - return err - } - } - if err := cmd.Action(cliCtx); err != nil { - return err - } - - return nil -} diff --git a/extensions/storage-publiclink/pkg/command/version.go b/extensions/storage-publiclink/pkg/command/version.go new file mode 100644 index 0000000000..f16c6dca20 --- /dev/null +++ b/extensions/storage-publiclink/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/storage-publiclink/pkg/config/config.go b/extensions/storage-publiclink/pkg/config/config.go index 92d412a690..e3cf134ab3 100644 --- a/extensions/storage-publiclink/pkg/config/config.go +++ b/extensions/storage-publiclink/pkg/config/config.go @@ -10,20 +10,20 @@ type Config struct { *shared.Commons `yaml:"-"` Service Service `yaml:"-"` Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` + Log *Log `yaml:"log"` Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` GRPC GRPCConfig `yaml:"grpc"` - Context context.Context `yaml:"context"` - TokenManager *TokenManager `yaml:"token_manager"` Reva *Reva `yaml:"reva"` SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token"` AuthProvider AuthProvider `yaml:"auth_provider"` StorageProvider StorageProvider `yaml:"storage_provider"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;STORAGE_PUBLICLINK_TRACING_ENABLED" desc:"Activates tracing."` @@ -32,7 +32,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;STORAGE_PUBLICLINK_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;STORAGE_PUBLICLINK_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;STORAGE_PUBLICLINK_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;STORAGE_PUBLICLINK_LOG_COLOR" desc:"Activates colorized log output."` @@ -51,8 +51,9 @@ type Debug struct { } type GRPCConfig struct { - Addr string `yaml:"addr" env:"STORAGE_PUBLICLINK_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"STORAGE_PUBLICLINK_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` + Addr string `yaml:"addr" env:"STORAGE_PUBLICLINK_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"STORAGE_PUBLICLINK_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` } type AuthProvider struct { diff --git a/extensions/storage-publiclink/pkg/config/defaults/defaultconfig.go b/extensions/storage-publiclink/pkg/config/defaults/defaultconfig.go index 47b729c05a..67dfbcedc6 100644 --- a/extensions/storage-publiclink/pkg/config/defaults/defaultconfig.go +++ b/extensions/storage-publiclink/pkg/config/defaults/defaultconfig.go @@ -20,8 +20,9 @@ func DefaultConfig() *config.Config { Zpages: false, }, GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9178", - Protocol: "tcp", + Addr: "127.0.0.1:9178", + Namespace: "com.owncloud.api", + Protocol: "tcp", }, Service: config.Service{ Name: "storage-publiclink", @@ -41,15 +42,15 @@ func DefaultConfig() *config.Config { func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/storage-publiclink/pkg/logging/logging.go b/extensions/storage-publiclink/pkg/logging/logging.go new file mode 100644 index 0000000000..a3dc14fdeb --- /dev/null +++ b/extensions/storage-publiclink/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/storage-publiclink/pkg/revaconfig/config.go b/extensions/storage-publiclink/pkg/revaconfig/config.go new file mode 100644 index 0000000000..fcab22c09d --- /dev/null +++ b/extensions/storage-publiclink/pkg/revaconfig/config.go @@ -0,0 +1,44 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config" +) + +// StoragePublicLinkConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func StoragePublicLinkConfigFromStruct(cfg *config.Config) map[string]interface{} { + rcfg := map[string]interface{}{ + "core": map[string]interface{}{ + "tracing_enabled": cfg.Tracing.Enabled, + "tracing_endpoint": cfg.Tracing.Endpoint, + "tracing_collector": cfg.Tracing.Collector, + "tracing_service_name": cfg.Service.Name, + }, + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, + "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, + }, + "grpc": map[string]interface{}{ + "network": cfg.GRPC.Protocol, + "address": cfg.GRPC.Addr, + "interceptors": map[string]interface{}{ + "log": map[string]interface{}{}, + }, + "services": map[string]interface{}{ + "publicstorageprovider": map[string]interface{}{ + "mount_id": cfg.StorageProvider.MountID, + "gateway_addr": cfg.StorageProvider.GatewayEndpoint, + }, + "authprovider": map[string]interface{}{ + "auth_manager": "publicshares", + "auth_managers": map[string]interface{}{ + "publicshares": map[string]interface{}{ + "gateway_addr": cfg.AuthProvider.GatewayEndpoint, + }, + }, + }, + }, + }, + } + return rcfg +} diff --git a/extensions/storage-publiclink/pkg/tracing/tracing.go b/extensions/storage-publiclink/pkg/tracing/tracing.go new file mode 100644 index 0000000000..753c732c60 --- /dev/null +++ b/extensions/storage-publiclink/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the proxy service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/storage-shares/cmd/storage-shares/main.go b/extensions/storage-shares/cmd/storage-shares/main.go new file mode 100644 index 0000000000..6fea0b0df3 --- /dev/null +++ b/extensions/storage-shares/cmd/storage-shares/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/storage-shares/pkg/command" + "github.com/owncloud/ocis/extensions/storage-shares/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/storage-shares/pkg/Makefile b/extensions/storage-shares/pkg/Makefile deleted file mode 100644 index 51243d83fc..0000000000 --- a/extensions/storage-shares/pkg/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -SHELL := bash -NAME := ocs - -include ../../.make/recursion.mk - -############ tooling ############ -ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI -include ../../.bingo/Variables.mk -endif - -############ go tooling ############ -include ../../.make/go.mk - -############ release ############ -include ../../.make/release.mk - -############ docs generate ############ -include ../../.make/docs.mk - -.PHONY: docs-generate -docs-generate: config-docs-generate - -############ generate ############ -include ../../.make/generate.mk - -.PHONY: ci-go-generate -ci-go-generate: # CI runs ci-node-generate automatically before this target - -.PHONY: ci-node-generate -ci-node-generate: - -############ licenses ############ -.PHONY: ci-node-check-licenses -ci-node-check-licenses: - -.PHONY: ci-node-save-licenses -ci-node-save-licenses: diff --git a/extensions/storage-shares/pkg/command/command.go b/extensions/storage-shares/pkg/command/command.go deleted file mode 100644 index ba9efc6457..0000000000 --- a/extensions/storage-shares/pkg/command/command.go +++ /dev/null @@ -1,161 +0,0 @@ -package command - -import ( - "context" - "flag" - "fmt" - "os" - "path" - - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/gofrs/uuid" - "github.com/oklog/run" - "github.com/owncloud/ocis/extensions/storage-shares/pkg/config" - "github.com/owncloud/ocis/extensions/storage-shares/pkg/config/parser" - "github.com/owncloud/ocis/extensions/storage-shares/pkg/server/debug" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" -) - -// StorageShares is the entrypoint for the storage-shares command. -func StorageShares(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "storage-shares", - Usage: "start storage-shares service", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - uuid := uuid.Must(uuid.NewV4()) - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+uuid.String()+".pid") - - rcfg := storageSharesConfigFromStruct(c, cfg) - - gr.Add(func() error { - runtime.RunWithOptions( - rcfg, - pidFile, - runtime.WithLogger(&logger.Logger), - ) - return nil - }, func(_ error) { - logger.Info(). - Str("server", c.Command.Name). - Msg("Shutting down server") - - cancel() - }) - - debugServer, err := debug.Server( - debug.Logger(logger), - debug.Context(ctx), - debug.Config(cfg), - ) - - if err != nil { - logger.Info().Err(err).Str("server", c.Command.Name+"-debug").Msg("Failed to initialize server") - return err - } - - gr.Add(debugServer.ListenAndServe, func(_ error) { - cancel() - }) - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() - }, - } -} - -// storageSharesConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func storageSharesConfigFromStruct(c *cli.Context, cfg *config.Config) map[string]interface{} { - rcfg := map[string]interface{}{ - "core": map[string]interface{}{ - "tracing_enabled": cfg.Tracing.Enabled, - "tracing_endpoint": cfg.Tracing.Endpoint, - "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, - }, - "shared": map[string]interface{}{ - "jwt_secret": cfg.TokenManager.JWTSecret, - "gatewaysvc": cfg.Reva.Address, - "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, - }, - "grpc": map[string]interface{}{ - "network": cfg.GRPC.Protocol, - "address": cfg.GRPC.Addr, - "services": map[string]interface{}{ - "sharesstorageprovider": map[string]interface{}{ - "usershareprovidersvc": cfg.SharesProviderEndpoint, - }, - }, - }, - } - if cfg.ReadOnly { - gcfg := rcfg["grpc"].(map[string]interface{}) - gcfg["interceptors"] = map[string]interface{}{ - "readonly": map[string]interface{}{}, - } - } - return rcfg -} - -// StorageSharesSutureService allows for the storage-shares command to be embedded and supervised by a suture supervisor tree. -type StorageSharesSutureService struct { - cfg *config.Config -} - -// NewStorageShares creates a new storage.StorageSharesSutureService -func NewStorageShares(cfg *ociscfg.Config) suture.Service { - cfg.StorageShares.Commons = cfg.Commons - return StorageSharesSutureService{ - cfg: cfg.StorageShares, - } -} - -func (s StorageSharesSutureService) Serve(ctx context.Context) error { - // s.cfg.Reva.StorageShares.Context = ctx - cmd := StorageShares(s.cfg) - f := &flag.FlagSet{} - cmdFlags := cmd.Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if cmd.Before != nil { - if err := cmd.Before(cliCtx); err != nil { - return err - } - } - if err := cmd.Action(cliCtx); err != nil { - return err - } - - return nil -} diff --git a/extensions/storage-shares/pkg/command/health.go b/extensions/storage-shares/pkg/command/health.go new file mode 100644 index 0000000000..8b5505e738 --- /dev/null +++ b/extensions/storage-shares/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/storage-shares/pkg/config" + "github.com/owncloud/ocis/extensions/storage-shares/pkg/config/parser" + "github.com/owncloud/ocis/extensions/storage-shares/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/storage-shares/pkg/command/root.go b/extensions/storage-shares/pkg/command/root.go new file mode 100644 index 0000000000..f055516f66 --- /dev/null +++ b/extensions/storage-shares/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/storage-shares/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the storage-shares command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "ocis-storage-shares", + Usage: "Provide a virtual storage for shares in oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the accounts command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new storage-shares.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.StorageShares.Commons = cfg.Commons + return SutureService{ + cfg: cfg.StorageShares, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/storage-shares/pkg/command/server.go b/extensions/storage-shares/pkg/command/server.go new file mode 100644 index 0000000000..16560c6e94 --- /dev/null +++ b/extensions/storage-shares/pkg/command/server.go @@ -0,0 +1,107 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/storage-shares/pkg/config" + "github.com/owncloud/ocis/extensions/storage-shares/pkg/config/parser" + "github.com/owncloud/ocis/extensions/storage-shares/pkg/logging" + "github.com/owncloud/ocis/extensions/storage-shares/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/storage-shares/pkg/server/debug" + "github.com/owncloud/ocis/extensions/storage-shares/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/sync" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.StorageSharesConfigFromStruct(cfg) + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + if err := external.RegisterGRPCEndpoint( + ctx, + cfg.GRPC.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.GRPC.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/storage-shares/pkg/command/version.go b/extensions/storage-shares/pkg/command/version.go new file mode 100644 index 0000000000..3687778691 --- /dev/null +++ b/extensions/storage-shares/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/storage-shares/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/storage-shares/pkg/config/config.go b/extensions/storage-shares/pkg/config/config.go index 4677edef2e..ac68d6e9ed 100644 --- a/extensions/storage-shares/pkg/config/config.go +++ b/extensions/storage-shares/pkg/config/config.go @@ -10,20 +10,20 @@ type Config struct { *shared.Commons `yaml:"-"` Service Service `yaml:"-"` Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` + Log *Log `yaml:"log"` Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` GRPC GRPCConfig `yaml:"grpc"` - HTTP HTTPConfig `yaml:"http"` TokenManager *TokenManager `yaml:"token_manager"` Reva *Reva `yaml:"reva"` - Context context.Context `yaml:"context"` - SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token"` - ReadOnly bool `yaml:"readonly"` - SharesProviderEndpoint string `yaml:"shares_provider_endpoint"` + SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token"` + ReadOnly bool `yaml:"readonly"` + SharesProviderEndpoint string `yaml:"shares_provider_endpoint"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;STORAGE_SHARES_TRACING_ENABLED" desc:"Activates tracing."` @@ -32,7 +32,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;STORAGE_SHARES_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;STORAGE_SHARES_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;STORAGE_SHARES_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;STORAGE_SHARES_LOG_COLOR" desc:"Activates colorized log output."` @@ -51,11 +51,7 @@ type Debug struct { } type GRPCConfig struct { - Addr string `yaml:"addr" env:"STORAGE_SHARES_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"STORAGE_SHARES_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` -} - -type HTTPConfig struct { - Addr string `yaml:"addr" env:"STORAGE_SHARES_HTTP_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"STORAGE_SHARES_HTTP_PROTOCOL" desc:"The transport protocol of the grpc service."` + Addr string `yaml:"addr" env:"STORAGE_SHARES_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"STORAGE_SHARES_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` } diff --git a/extensions/storage-shares/pkg/config/defaults/defaultconfig.go b/extensions/storage-shares/pkg/config/defaults/defaultconfig.go index 40aba54cfd..5b1ad7faa2 100644 --- a/extensions/storage-shares/pkg/config/defaults/defaultconfig.go +++ b/extensions/storage-shares/pkg/config/defaults/defaultconfig.go @@ -20,12 +20,9 @@ func DefaultConfig() *config.Config { Zpages: false, }, GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9154", - Protocol: "tcp", - }, - HTTP: config.HTTPConfig{ - Addr: "127.0.0.1:9155", - Protocol: "tcp", + Addr: "127.0.0.1:9154", + Namespace: "com.owncloud.api", + Protocol: "tcp", }, Service: config.Service{ Name: "storage-shares", @@ -40,15 +37,15 @@ func DefaultConfig() *config.Config { func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/storage-shares/pkg/logging/logging.go b/extensions/storage-shares/pkg/logging/logging.go new file mode 100644 index 0000000000..7769e10fe7 --- /dev/null +++ b/extensions/storage-shares/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/storage-shares/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/storage-shares/pkg/revaconfig/config.go b/extensions/storage-shares/pkg/revaconfig/config.go new file mode 100644 index 0000000000..be281c0553 --- /dev/null +++ b/extensions/storage-shares/pkg/revaconfig/config.go @@ -0,0 +1,38 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/extensions/storage-shares/pkg/config" +) + +// StorageSharesConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func StorageSharesConfigFromStruct(cfg *config.Config) map[string]interface{} { + rcfg := map[string]interface{}{ + "core": map[string]interface{}{ + "tracing_enabled": cfg.Tracing.Enabled, + "tracing_endpoint": cfg.Tracing.Endpoint, + "tracing_collector": cfg.Tracing.Collector, + "tracing_service_name": cfg.Service.Name, + }, + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, + "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, + }, + "grpc": map[string]interface{}{ + "network": cfg.GRPC.Protocol, + "address": cfg.GRPC.Addr, + "services": map[string]interface{}{ + "sharesstorageprovider": map[string]interface{}{ + "usershareprovidersvc": cfg.SharesProviderEndpoint, + }, + }, + }, + } + if cfg.ReadOnly { + gcfg := rcfg["grpc"].(map[string]interface{}) + gcfg["interceptors"] = map[string]interface{}{ + "readonly": map[string]interface{}{}, + } + } + return rcfg +} diff --git a/extensions/storage-shares/pkg/tracing/tracing.go b/extensions/storage-shares/pkg/tracing/tracing.go new file mode 100644 index 0000000000..16a443b084 --- /dev/null +++ b/extensions/storage-shares/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/storage-shares/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the proxy service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/storage-users/cmd/storage-users/main.go b/extensions/storage-users/cmd/storage-users/main.go new file mode 100644 index 0000000000..4e99f59377 --- /dev/null +++ b/extensions/storage-users/cmd/storage-users/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/storage-users/pkg/command" + "github.com/owncloud/ocis/extensions/storage-users/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/storage-users/pkg/Makefile b/extensions/storage-users/pkg/Makefile deleted file mode 100644 index 51243d83fc..0000000000 --- a/extensions/storage-users/pkg/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -SHELL := bash -NAME := ocs - -include ../../.make/recursion.mk - -############ tooling ############ -ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI -include ../../.bingo/Variables.mk -endif - -############ go tooling ############ -include ../../.make/go.mk - -############ release ############ -include ../../.make/release.mk - -############ docs generate ############ -include ../../.make/docs.mk - -.PHONY: docs-generate -docs-generate: config-docs-generate - -############ generate ############ -include ../../.make/generate.mk - -.PHONY: ci-go-generate -ci-go-generate: # CI runs ci-node-generate automatically before this target - -.PHONY: ci-node-generate -ci-node-generate: - -############ licenses ############ -.PHONY: ci-node-check-licenses -ci-node-check-licenses: - -.PHONY: ci-node-save-licenses -ci-node-save-licenses: diff --git a/extensions/storage-users/pkg/command/command.go b/extensions/storage-users/pkg/command/command.go deleted file mode 100644 index 766631f6a5..0000000000 --- a/extensions/storage-users/pkg/command/command.go +++ /dev/null @@ -1,189 +0,0 @@ -package command - -import ( - "context" - "flag" - "fmt" - "os" - "path" - - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/gofrs/uuid" - "github.com/oklog/run" - "github.com/owncloud/ocis/extensions/storage-users/pkg/config" - "github.com/owncloud/ocis/extensions/storage-users/pkg/config/parser" - "github.com/owncloud/ocis/extensions/storage-users/pkg/server/debug" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" -) - -// StorageUsers is the entrypoint for the storage-users command. -func StorageUsers(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "storage-users", - Usage: "start storage-users service", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - uuid := uuid.Must(uuid.NewV4()) - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+uuid.String()+".pid") - - rcfg := storageUsersConfigFromStruct(c, cfg) - - gr.Add(func() error { - runtime.RunWithOptions( - rcfg, - pidFile, - runtime.WithLogger(&logger.Logger), - ) - return nil - }, func(_ error) { - logger.Info(). - Str("server", c.Command.Name). - Msg("Shutting down server") - - cancel() - }) - - debugServer, err := debug.Server( - debug.Logger(logger), - debug.Context(ctx), - debug.Config(cfg), - ) - - if err != nil { - logger.Info().Err(err).Str("server", c.Command.Name+"-debug").Msg("Failed to initialize server") - return err - } - - gr.Add(debugServer.ListenAndServe, func(_ error) { - cancel() - }) - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() - }, - } -} - -// storageUsersConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func storageUsersConfigFromStruct(c *cli.Context, cfg *config.Config) map[string]interface{} { - rcfg := map[string]interface{}{ - "core": map[string]interface{}{ - "tracing_enabled": cfg.Tracing.Enabled, - "tracing_endpoint": cfg.Tracing.Endpoint, - "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, - }, - "shared": map[string]interface{}{ - "jwt_secret": cfg.TokenManager.JWTSecret, - "gatewaysvc": cfg.Reva.Address, - "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, - }, - "grpc": map[string]interface{}{ - "network": cfg.GRPC.Protocol, - "address": cfg.GRPC.Addr, - // TODO build services dynamically - "services": map[string]interface{}{ - "storageprovider": map[string]interface{}{ - "driver": cfg.Driver, - "drivers": config.UserDrivers(cfg), - "mount_id": cfg.MountID, - "expose_data_server": cfg.ExposeDataServer, - "data_server_url": cfg.DataServerURL, - "tmp_folder": cfg.TempFolder, - }, - }, - "interceptors": map[string]interface{}{ - "eventsmiddleware": map[string]interface{}{ - "group": "sharing", - "type": "nats", - "address": cfg.Events.Addr, - "clusterID": cfg.Events.ClusterID, - }, - }, - }, - "http": map[string]interface{}{ - "network": cfg.HTTP.Protocol, - "address": cfg.HTTP.Addr, - // TODO build services dynamically - "services": map[string]interface{}{ - "dataprovider": map[string]interface{}{ - "prefix": cfg.HTTP.Prefix, - "driver": cfg.Driver, - "drivers": config.UserDrivers(cfg), - "timeout": 86400, - "insecure": cfg.DataProviderInsecure, - "disable_tus": false, - }, - }, - }, - } - if cfg.ReadOnly { - gcfg := rcfg["grpc"].(map[string]interface{}) - gcfg["interceptors"] = map[string]interface{}{ - "readonly": map[string]interface{}{}, - } - } - return rcfg -} - -// StorageUsersSutureService allows for the storage-home command to be embedded and supervised by a suture supervisor tree. -type StorageUsersSutureService struct { - cfg *config.Config -} - -// NewStorageUsersSutureService creates a new storage.StorageUsersSutureService -func NewStorageUsers(cfg *ociscfg.Config) suture.Service { - cfg.StorageUsers.Commons = cfg.Commons - return StorageUsersSutureService{ - cfg: cfg.StorageUsers, - } -} - -func (s StorageUsersSutureService) Serve(ctx context.Context) error { - // s.cfg.Reva.StorageUsers.Context = ctx - cmd := StorageUsers(s.cfg) - f := &flag.FlagSet{} - cmdFlags := cmd.Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if cmd.Before != nil { - if err := cmd.Before(cliCtx); err != nil { - return err - } - } - if err := cmd.Action(cliCtx); err != nil { - return err - } - - return nil -} diff --git a/extensions/storage-users/pkg/command/health.go b/extensions/storage-users/pkg/command/health.go new file mode 100644 index 0000000000..baa4d1a8de --- /dev/null +++ b/extensions/storage-users/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/storage-users/pkg/config" + "github.com/owncloud/ocis/extensions/storage-users/pkg/config/parser" + "github.com/owncloud/ocis/extensions/storage-users/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/storage-users/pkg/command/root.go b/extensions/storage-users/pkg/command/root.go new file mode 100644 index 0000000000..1d98daeab8 --- /dev/null +++ b/extensions/storage-users/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/storage-users/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-storage-users command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "ocis-storage-users", + Usage: "Provide storage for users and projects in oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the accounts command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new storage-users.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.StorageUsers.Commons = cfg.Commons + return SutureService{ + cfg: cfg.StorageUsers, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/storage-users/pkg/command/server.go b/extensions/storage-users/pkg/command/server.go new file mode 100644 index 0000000000..ae83b2435f --- /dev/null +++ b/extensions/storage-users/pkg/command/server.go @@ -0,0 +1,107 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/storage-users/pkg/config" + "github.com/owncloud/ocis/extensions/storage-users/pkg/config/parser" + "github.com/owncloud/ocis/extensions/storage-users/pkg/logging" + "github.com/owncloud/ocis/extensions/storage-users/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/storage-users/pkg/server/debug" + "github.com/owncloud/ocis/extensions/storage-users/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/sync" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.StorageUsersConfigFromStruct(cfg) + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + if err := external.RegisterGRPCEndpoint( + ctx, + cfg.GRPC.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.GRPC.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/storage-users/pkg/command/version.go b/extensions/storage-users/pkg/command/version.go new file mode 100644 index 0000000000..597dcc14df --- /dev/null +++ b/extensions/storage-users/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/storage-users/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/storage-users/pkg/config/config.go b/extensions/storage-users/pkg/config/config.go index e47bd01794..732bc70c02 100644 --- a/extensions/storage-users/pkg/config/config.go +++ b/extensions/storage-users/pkg/config/config.go @@ -10,9 +10,8 @@ type Config struct { *shared.Commons `yaml:"-"` Service Service `yaml:"-"` Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` + Log *Log `yaml:"log"` Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` GRPC GRPCConfig `yaml:"grpc"` HTTP HTTPConfig `yaml:"http"` @@ -20,8 +19,6 @@ type Config struct { TokenManager *TokenManager `yaml:"token_manager"` Reva *Reva `yaml:"reva"` - Context context.Context `yaml:"context"` - SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token"` Driver string `yaml:"driver" env:"STORAGE_USERS_DRIVER" desc:"The storage driver which should be used by the service"` Drivers Drivers `yaml:"drivers"` @@ -32,6 +29,9 @@ type Config struct { MountID string `yaml:"mount_id"` ExposeDataServer bool `yaml:"expose_data_server"` ReadOnly bool `yaml:"readonly"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;STORAGE_USERS_TRACING_ENABLED" desc:"Activates tracing."` @@ -40,7 +40,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;STORAGE_USERS_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;STORAGE_USERS_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;STORAGE_USERS_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;STORAGE_USERS_LOG_COLOR" desc:"Activates colorized log output."` @@ -59,14 +59,16 @@ type Debug struct { } type GRPCConfig struct { - Addr string `yaml:"addr" env:"STORAGE_USERS_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"STORAGE_USERS_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` + Addr string `yaml:"addr" env:"STORAGE_USERS_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"STORAGE_USERS_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` } type HTTPConfig struct { - Addr string `yaml:"addr" env:"STORAGE_USERS_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"STORAGE_USERS_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` - Prefix string + Addr string `yaml:"addr" env:"STORAGE_USERS_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"STORAGE_USERS_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` + Prefix string } type Drivers struct { diff --git a/extensions/storage-users/pkg/config/defaults/defaultconfig.go b/extensions/storage-users/pkg/config/defaults/defaultconfig.go index 87c0bb4cd8..fb36a5073d 100644 --- a/extensions/storage-users/pkg/config/defaults/defaultconfig.go +++ b/extensions/storage-users/pkg/config/defaults/defaultconfig.go @@ -23,13 +23,15 @@ func DefaultConfig() *config.Config { Zpages: false, }, GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9157", - Protocol: "tcp", + Addr: "127.0.0.1:9157", + Namespace: "com.owncloud.api", + Protocol: "tcp", }, HTTP: config.HTTPConfig{ - Addr: "127.0.0.1:9158", - Protocol: "tcp", - Prefix: "data", + Addr: "127.0.0.1:9158", + Namespace: "com.owncloud.web", + Protocol: "tcp", + Prefix: "data", }, Service: config.Service{ Name: "storage-users", @@ -80,15 +82,15 @@ func DefaultConfig() *config.Config { func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/storage-users/pkg/logging/logging.go b/extensions/storage-users/pkg/logging/logging.go new file mode 100644 index 0000000000..3c9fb9c661 --- /dev/null +++ b/extensions/storage-users/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/storage-users/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/storage-users/pkg/revaconfig/config.go b/extensions/storage-users/pkg/revaconfig/config.go new file mode 100644 index 0000000000..7794794629 --- /dev/null +++ b/extensions/storage-users/pkg/revaconfig/config.go @@ -0,0 +1,67 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/extensions/storage-users/pkg/config" +) + +// StorageUsersConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func StorageUsersConfigFromStruct(cfg *config.Config) map[string]interface{} { + rcfg := map[string]interface{}{ + "core": map[string]interface{}{ + "tracing_enabled": cfg.Tracing.Enabled, + "tracing_endpoint": cfg.Tracing.Endpoint, + "tracing_collector": cfg.Tracing.Collector, + "tracing_service_name": cfg.Service.Name, + }, + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, + "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, + }, + "grpc": map[string]interface{}{ + "network": cfg.GRPC.Protocol, + "address": cfg.GRPC.Addr, + // TODO build services dynamically + "services": map[string]interface{}{ + "storageprovider": map[string]interface{}{ + "driver": cfg.Driver, + "drivers": UserDrivers(cfg), + "mount_id": cfg.MountID, + "expose_data_server": cfg.ExposeDataServer, + "data_server_url": cfg.DataServerURL, + "tmp_folder": cfg.TempFolder, + }, + }, + "interceptors": map[string]interface{}{ + "eventsmiddleware": map[string]interface{}{ + "group": "sharing", + "type": "nats", + "address": cfg.Events.Addr, + "clusterID": cfg.Events.ClusterID, + }, + }, + }, + "http": map[string]interface{}{ + "network": cfg.HTTP.Protocol, + "address": cfg.HTTP.Addr, + // TODO build services dynamically + "services": map[string]interface{}{ + "dataprovider": map[string]interface{}{ + "prefix": cfg.HTTP.Prefix, + "driver": cfg.Driver, + "drivers": UserDrivers(cfg), + "timeout": 86400, + "insecure": cfg.DataProviderInsecure, + "disable_tus": false, + }, + }, + }, + } + if cfg.ReadOnly { + gcfg := rcfg["grpc"].(map[string]interface{}) + gcfg["interceptors"] = map[string]interface{}{ + "readonly": map[string]interface{}{}, + } + } + return rcfg +} diff --git a/extensions/storage-users/pkg/config/user.go b/extensions/storage-users/pkg/revaconfig/user.go similarity index 97% rename from extensions/storage-users/pkg/config/user.go rename to extensions/storage-users/pkg/revaconfig/user.go index 677b7b35bd..a8c7d73363 100644 --- a/extensions/storage-users/pkg/config/user.go +++ b/extensions/storage-users/pkg/revaconfig/user.go @@ -1,6 +1,8 @@ -package config +package revaconfig -func UserDrivers(cfg *Config) map[string]interface{} { +import "github.com/owncloud/ocis/extensions/storage-users/pkg/config" + +func UserDrivers(cfg *config.Config) map[string]interface{} { return map[string]interface{}{ "eos": map[string]interface{}{ "namespace": cfg.Drivers.EOS.Root, diff --git a/extensions/storage-users/pkg/tracing/tracing.go b/extensions/storage-users/pkg/tracing/tracing.go new file mode 100644 index 0000000000..8e69e85058 --- /dev/null +++ b/extensions/storage-users/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/storage-users/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the proxy service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/user/cmd/user/main.go b/extensions/user/cmd/user/main.go new file mode 100644 index 0000000000..97d008cb00 --- /dev/null +++ b/extensions/user/cmd/user/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/user/pkg/command" + "github.com/owncloud/ocis/extensions/user/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/user/pkg/Makefile b/extensions/user/pkg/Makefile deleted file mode 100644 index 51243d83fc..0000000000 --- a/extensions/user/pkg/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -SHELL := bash -NAME := ocs - -include ../../.make/recursion.mk - -############ tooling ############ -ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI -include ../../.bingo/Variables.mk -endif - -############ go tooling ############ -include ../../.make/go.mk - -############ release ############ -include ../../.make/release.mk - -############ docs generate ############ -include ../../.make/docs.mk - -.PHONY: docs-generate -docs-generate: config-docs-generate - -############ generate ############ -include ../../.make/generate.mk - -.PHONY: ci-go-generate -ci-go-generate: # CI runs ci-node-generate automatically before this target - -.PHONY: ci-node-generate -ci-node-generate: - -############ licenses ############ -.PHONY: ci-node-check-licenses -ci-node-check-licenses: - -.PHONY: ci-node-save-licenses -ci-node-save-licenses: diff --git a/extensions/user/pkg/command/health.go b/extensions/user/pkg/command/health.go new file mode 100644 index 0000000000..1039a0efc0 --- /dev/null +++ b/extensions/user/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/user/pkg/config" + "github.com/owncloud/ocis/extensions/user/pkg/config/parser" + "github.com/owncloud/ocis/extensions/user/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/user/pkg/command/root.go b/extensions/user/pkg/command/root.go new file mode 100644 index 0000000000..23e45a2602 --- /dev/null +++ b/extensions/user/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/user/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-user command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "ocis-user", + Usage: "Provide users for oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the user command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new user.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.User.Commons = cfg.Commons + return SutureService{ + cfg: cfg.User, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/user/pkg/command/server.go b/extensions/user/pkg/command/server.go new file mode 100644 index 0000000000..97baa1f9c4 --- /dev/null +++ b/extensions/user/pkg/command/server.go @@ -0,0 +1,120 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/user/pkg/config" + "github.com/owncloud/ocis/extensions/user/pkg/config/parser" + "github.com/owncloud/ocis/extensions/user/pkg/logging" + "github.com/owncloud/ocis/extensions/user/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/user/pkg/server/debug" + "github.com/owncloud/ocis/extensions/user/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/ldap" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/sync" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.UsersConfigFromStruct(cfg) + + // the reva runtime calls os.Exit in the case of a failure and there is no way for the oCIS + // runtime to catch it and restart a reva service. Therefore we need to ensure the service has + // everything it needs, before starting the service. + // In this case: CA certificates + if cfg.Driver == "ldap" { + ldapCfg := cfg.Drivers.LDAP + if err := ldap.WaitForCA(logger, ldapCfg.Insecure, ldapCfg.CACert); err != nil { + logger.Error().Err(err).Msg("The configured LDAP CA cert does not exist") + return err + } + } + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + if err := external.RegisterGRPCEndpoint( + ctx, + cfg.GRPC.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.GRPC.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/user/pkg/command/version.go b/extensions/user/pkg/command/version.go new file mode 100644 index 0000000000..8330670428 --- /dev/null +++ b/extensions/user/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/user/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/user/pkg/config/config.go b/extensions/user/pkg/config/config.go index 15c0bcd121..7471b751b9 100644 --- a/extensions/user/pkg/config/config.go +++ b/extensions/user/pkg/config/config.go @@ -1,14 +1,17 @@ package config -import "github.com/owncloud/ocis/ocis-pkg/shared" +import ( + "context" + + "github.com/owncloud/ocis/ocis-pkg/shared" +) type Config struct { *shared.Commons `yaml:"-"` Service Service `yaml:"-"` Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` + Log *Log `yaml:"log"` Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` GRPC GRPCConfig `yaml:"grpc"` @@ -19,6 +22,9 @@ type Config struct { UsersCacheExpiration int `yaml:"users_cache_expiration"` Driver string `yaml:"driver"` Drivers Drivers `yaml:"drivers"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;USERS_TRACING_ENABLED" desc:"Activates tracing."` @@ -27,7 +33,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;USERS_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;USERS_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;USERS_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;USERS_LOG_COLOR" desc:"Activates colorized log output."` @@ -46,8 +52,9 @@ type Debug struct { } type GRPCConfig struct { - Addr string `yaml:"addr" env:"USERS_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"USERS_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` + Addr string `yaml:"addr" env:"USERS_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"USERS_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` } type Drivers struct { diff --git a/extensions/user/pkg/config/defaults/defaultconfig.go b/extensions/user/pkg/config/defaults/defaultconfig.go index b7212abd0b..8a304e74c7 100644 --- a/extensions/user/pkg/config/defaults/defaultconfig.go +++ b/extensions/user/pkg/config/defaults/defaultconfig.go @@ -23,8 +23,9 @@ func DefaultConfig() *config.Config { Zpages: false, }, GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9144", - Protocol: "tcp", + Addr: "127.0.0.1:9144", + Namespace: "com.owncloud.api", + Protocol: "tcp", }, Service: config.Service{ Name: "user", @@ -86,15 +87,15 @@ func DefaultConfig() *config.Config { func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/user/pkg/logging/logging.go b/extensions/user/pkg/logging/logging.go new file mode 100644 index 0000000000..d855cfeb94 --- /dev/null +++ b/extensions/user/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/user/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/user/pkg/command/command.go b/extensions/user/pkg/revaconfig/config.go similarity index 50% rename from extensions/user/pkg/command/command.go rename to extensions/user/pkg/revaconfig/config.go index 7214e12e21..b0cfc333c0 100644 --- a/extensions/user/pkg/command/command.go +++ b/extensions/user/pkg/revaconfig/config.go @@ -1,124 +1,17 @@ -package command +package revaconfig import ( - "context" - "flag" - "fmt" - "os" - "path" - "path/filepath" - - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/gofrs/uuid" - "github.com/oklog/run" "github.com/owncloud/ocis/extensions/user/pkg/config" - "github.com/owncloud/ocis/extensions/user/pkg/config/parser" - "github.com/owncloud/ocis/extensions/user/pkg/server/debug" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/ldap" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" ) -// User is the entrypoint for the user command. -func User(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "users", - Usage: "start users service", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // precreate folders - if cfg.Driver == "json" && cfg.Drivers.JSON.File != "" { - if err := os.MkdirAll(filepath.Dir(cfg.Drivers.JSON.File), os.FileMode(0700)); err != nil { - return err - } - } - - uuid := uuid.Must(uuid.NewV4()) - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+uuid.String()+".pid") - - rcfg := usersConfigFromStruct(c, cfg) - - logger.Debug(). - Str("server", "users"). - Interface("reva-config", rcfg). - Msg("config") - - if cfg.Driver == "ldap" { - if err := ldap.WaitForCA(logger, cfg.Drivers.LDAP.Insecure, cfg.Drivers.LDAP.CACert); err != nil { - logger.Error().Err(err).Msg("The configured LDAP CA cert does not exist") - return err - } - } - - gr.Add(func() error { - runtime.RunWithOptions( - rcfg, - pidFile, - runtime.WithLogger(&logger.Logger), - ) - return nil - }, func(_ error) { - logger.Info(). - Str("server", c.Command.Name). - Msg("Shutting down server") - - cancel() - }) - - debugServer, err := debug.Server( - debug.Logger(logger), - debug.Context(ctx), - debug.Config(cfg), - ) - - if err != nil { - logger.Info().Err(err).Str("server", c.Command.Name+"-debug").Msg("Failed to initialize server") - return err - } - - gr.Add(debugServer.ListenAndServe, func(_ error) { - cancel() - }) - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() - }, - } -} - -// usersConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func usersConfigFromStruct(c *cli.Context, cfg *config.Config) map[string]interface{} { +// UsersConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func UsersConfigFromStruct(cfg *config.Config) map[string]interface{} { rcfg := map[string]interface{}{ "core": map[string]interface{}{ "tracing_enabled": cfg.Tracing.Enabled, "tracing_endpoint": cfg.Tracing.Endpoint, "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, + "tracing_service_name": cfg.Service.Name, }, "shared": map[string]interface{}{ "jwt_secret": cfg.TokenManager.JWTSecret, @@ -169,42 +62,6 @@ func usersConfigFromStruct(c *cli.Context, cfg *config.Config) map[string]interf return rcfg } -// UserProviderSutureService allows for the storage-userprovider command to be embedded and supervised by a suture supervisor tree. -type UserProviderSutureService struct { - cfg *config.Config -} - -// NewUserProviderSutureService creates a new storage.UserProvider -func NewUserProvider(cfg *ociscfg.Config) suture.Service { - cfg.User.Commons = cfg.Commons - return UserProviderSutureService{ - cfg: cfg.User, - } -} - -func (s UserProviderSutureService) Serve(ctx context.Context) error { - // s.cfg.Reva.Users.Context = ctx - cmd := User(s.cfg) - f := &flag.FlagSet{} - cmdFlags := cmd.Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if cmd.Before != nil { - if err := cmd.Before(cliCtx); err != nil { - return err - } - } - if err := cmd.Action(cliCtx); err != nil { - return err - } - - return nil -} - func ldapConfigFromString(cfg config.LDAPDriver) map[string]interface{} { return map[string]interface{}{ "uri": cfg.URI, diff --git a/extensions/user/pkg/tracing/tracing.go b/extensions/user/pkg/tracing/tracing.go new file mode 100644 index 0000000000..497f15ed61 --- /dev/null +++ b/extensions/user/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/user/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the proxy service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/webdav/pkg/command/root.go b/extensions/webdav/pkg/command/root.go index fe2a7f3c20..d8b32188e5 100644 --- a/extensions/webdav/pkg/command/root.go +++ b/extensions/webdav/pkg/command/root.go @@ -48,7 +48,7 @@ type SutureService struct { // NewSutureService creates a new webdav.SutureService func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Proxy.Commons = cfg.Commons + cfg.WebDAV.Commons = cfg.Commons return SutureService{ cfg: cfg.WebDAV, } diff --git a/ocis-pkg/service/external/external.go b/ocis-pkg/service/external/external.go index 4ef64adacf..02310c3339 100644 --- a/ocis-pkg/service/external/external.go +++ b/ocis-pkg/service/external/external.go @@ -12,7 +12,6 @@ import ( // RegisterGRPCEndpoint publishes an arbitrary endpoint to the service-registry. This allows to query nodes of // non-micro GRPC-services like reva. No health-checks are done, thus the caller is responsible for canceling. -// func RegisterGRPCEndpoint(ctx context.Context, serviceID, uuid, addr string, version string, logger log.Logger) error { node := ®istry.Node{ Id: serviceID + "-" + uuid, @@ -66,3 +65,59 @@ func RegisterGRPCEndpoint(ctx context.Context, serviceID, uuid, addr string, ver return nil } + +// RegisterHTTPEndpoint publishes an arbitrary endpoint to the service-registry. This allows to query nodes of +// non-micro HTTP-services like reva. No health-checks are done, thus the caller is responsible for canceling. +func RegisterHTTPEndpoint(ctx context.Context, serviceID, uuid, addr string, version string, logger log.Logger) error { + node := ®istry.Node{ + Id: serviceID + "-" + uuid, + Address: addr, + Metadata: make(map[string]string), + } + ocisRegistry := oregistry.GetRegistry() + + node.Metadata["broker"] = broker.String() + node.Metadata["registry"] = ocisRegistry.String() + node.Metadata["server"] = "http" + node.Metadata["transport"] = "http" + node.Metadata["protocol"] = "http" + + service := ®istry.Service{ + Name: serviceID, + Version: version, + Nodes: []*registry.Node{node}, + Endpoints: make([]*registry.Endpoint, 0), + } + + logger.Info().Msgf("registering external service %v@%v", node.Id, node.Address) + + rOpts := []registry.RegisterOption{registry.RegisterTTL(time.Minute)} + if err := ocisRegistry.Register(service, rOpts...); err != nil { + logger.Fatal().Err(err).Msgf("Registration error for external service %v", serviceID) + } + + t := time.NewTicker(time.Second * 30) + + go func() { + for { + select { + case <-t.C: + logger.Debug().Interface("service", service).Msg("refreshing external service-registration") + err := ocisRegistry.Register(service, rOpts...) + if err != nil { + logger.Error().Err(err).Msgf("registration error for external service %v", serviceID) + } + case <-ctx.Done(): + logger.Debug().Interface("service", service).Msg("unregistering") + t.Stop() + err := ocisRegistry.Deregister(service) + if err != nil { + logger.Err(err).Msgf("Error unregistering external service %v", serviceID) + } + + } + } + }() + + return nil +} diff --git a/ocis/pkg/command/storageappprovider.go b/ocis/pkg/command/appprovider.go similarity index 56% rename from ocis/pkg/command/storageappprovider.go rename to ocis/pkg/command/appprovider.go index f44aa55fad..bf76eba894 100644 --- a/ocis/pkg/command/storageappprovider.go +++ b/ocis/pkg/command/appprovider.go @@ -10,11 +10,11 @@ import ( "github.com/urfave/cli/v2" ) -// StorageAppProviderCommand is the entrypoint for the reva-app-provider command. -func StorageAppProviderCommand(cfg *config.Config) *cli.Command { +// AppProviderCommand is the entrypoint for the app provider command. +func AppProviderCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "storage-app-provider", - Usage: "start storage app-provider service", + Name: cfg.AppProvider.Service.Name, + Usage: subcommandDescription(cfg.AppProvider.Service.Name), Category: "extensions", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg); err != nil { @@ -24,13 +24,10 @@ func StorageAppProviderCommand(cfg *config.Config) *cli.Command { cfg.AppProvider.Commons = cfg.Commons return nil }, - Action: func(c *cli.Context) error { - origCmd := command.AppProvider(cfg.AppProvider) - return handleOriginalAction(c, origCmd) - }, + Subcommands: command.GetCommands(cfg.AppProvider), } } func init() { - register.AddCommand(StorageAppProviderCommand) + register.AddCommand(AppProviderCommand) } diff --git a/ocis/pkg/command/storageauthbasic.go b/ocis/pkg/command/auth-basic.go similarity index 56% rename from ocis/pkg/command/storageauthbasic.go rename to ocis/pkg/command/auth-basic.go index 4c8dba5cdb..569ca434b6 100644 --- a/ocis/pkg/command/storageauthbasic.go +++ b/ocis/pkg/command/auth-basic.go @@ -10,11 +10,11 @@ import ( "github.com/urfave/cli/v2" ) -// StorageAuthBasicCommand is the entrypoint for the reva-auth-basic command. -func StorageAuthBasicCommand(cfg *config.Config) *cli.Command { +// AuthBasicCommand is the entrypoint for the AuthBasic command. +func AuthBasicCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "storage-auth-basic", - Usage: "start storage auth-basic service", + Name: cfg.AuthBasic.Service.Name, + Usage: subcommandDescription(cfg.AuthBasic.Service.Name), Category: "extensions", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg); err != nil { @@ -24,13 +24,10 @@ func StorageAuthBasicCommand(cfg *config.Config) *cli.Command { cfg.AuthBasic.Commons = cfg.Commons return nil }, - Action: func(c *cli.Context) error { - origCmd := command.AuthBasic(cfg.AuthBasic) - return handleOriginalAction(c, origCmd) - }, + Subcommands: command.GetCommands(cfg.AuthBasic), } } func init() { - register.AddCommand(StorageAuthBasicCommand) + register.AddCommand(AuthBasicCommand) } diff --git a/ocis/pkg/command/storageauthbearer.go b/ocis/pkg/command/auth-bearer.go similarity index 56% rename from ocis/pkg/command/storageauthbearer.go rename to ocis/pkg/command/auth-bearer.go index 20b641cda4..e44fcb88c0 100644 --- a/ocis/pkg/command/storageauthbearer.go +++ b/ocis/pkg/command/auth-bearer.go @@ -10,11 +10,11 @@ import ( "github.com/urfave/cli/v2" ) -// StorageAuthBearerCommand is the entrypoint for the reva-auth-bearer command. -func StorageAuthBearerCommand(cfg *config.Config) *cli.Command { +// AuthBearerCommand is the entrypoint for the AuthBearer command. +func AuthBearerCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "storage-auth-bearer", - Usage: "Start storage auth-bearer service", + Name: cfg.AuthBearer.Service.Name, + Usage: subcommandDescription(cfg.AuthBearer.Service.Name), Category: "extensions", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg); err != nil { @@ -24,13 +24,10 @@ func StorageAuthBearerCommand(cfg *config.Config) *cli.Command { cfg.AuthBearer.Commons = cfg.Commons return nil }, - Action: func(c *cli.Context) error { - origCmd := command.AuthBearer(cfg.AuthBearer) - return handleOriginalAction(c, origCmd) - }, + Subcommands: command.GetCommands(cfg.AuthBearer), } } func init() { - register.AddCommand(StorageAuthBearerCommand) + register.AddCommand(AuthBearerCommand) } diff --git a/ocis/pkg/command/storageauthmachine.go b/ocis/pkg/command/auth-machine.go similarity index 56% rename from ocis/pkg/command/storageauthmachine.go rename to ocis/pkg/command/auth-machine.go index f42ecb4b55..4ed22342f3 100644 --- a/ocis/pkg/command/storageauthmachine.go +++ b/ocis/pkg/command/auth-machine.go @@ -10,11 +10,11 @@ import ( "github.com/urfave/cli/v2" ) -// StorageAuthMachineCommand is the entrypoint for the reva-auth-machine command. -func StorageAuthMachineCommand(cfg *config.Config) *cli.Command { +// AuthMachineCommand is the entrypoint for the AuthMachine command. +func AuthMachineCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "storage-auth-machine", - Usage: "start storage auth-machine service", + Name: cfg.AuthMachine.Service.Name, + Usage: subcommandDescription(cfg.AuthMachine.Service.Name), Category: "extensions", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg); err != nil { @@ -24,13 +24,10 @@ func StorageAuthMachineCommand(cfg *config.Config) *cli.Command { cfg.AuthMachine.Commons = cfg.Commons return nil }, - Action: func(c *cli.Context) error { - origCmd := command.AuthMachine(cfg.AuthMachine) - return handleOriginalAction(c, origCmd) - }, + Subcommands: command.GetCommands(cfg.AuthMachine), } } func init() { - register.AddCommand(StorageAuthMachineCommand) + register.AddCommand(AuthMachineCommand) } diff --git a/ocis/pkg/command/common.go b/ocis/pkg/command/common.go index 77bfa381ed..dd65d0339d 100644 --- a/ocis/pkg/command/common.go +++ b/ocis/pkg/command/common.go @@ -2,21 +2,8 @@ package command import ( "fmt" - - "github.com/urfave/cli/v2" ) -func handleOriginalAction(c *cli.Context, cmd *cli.Command) error { - - if cmd.Before != nil { - if err := cmd.Before(c); err != nil { - return err - } - } - - return cli.HandleAction(cmd.Action, c) -} - func subcommandDescription(serviceName string) string { return fmt.Sprintf("%s extension commands", serviceName) } diff --git a/ocis/pkg/command/storagefrontend.go b/ocis/pkg/command/frontend.go similarity index 57% rename from ocis/pkg/command/storagefrontend.go rename to ocis/pkg/command/frontend.go index 05252414a9..0e8ad6142e 100644 --- a/ocis/pkg/command/storagefrontend.go +++ b/ocis/pkg/command/frontend.go @@ -10,11 +10,11 @@ import ( "github.com/urfave/cli/v2" ) -// StorageFrontendCommand is the entrypoint for the reva-frontend command. -func StorageFrontendCommand(cfg *config.Config) *cli.Command { +// FrontendCommand is the entrypoint for the Frontend command. +func FrontendCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "storage-frontend", - Usage: "start storage frontend", + Name: cfg.Frontend.Service.Name, + Usage: subcommandDescription(cfg.Frontend.Service.Name), Category: "extensions", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg); err != nil { @@ -24,13 +24,10 @@ func StorageFrontendCommand(cfg *config.Config) *cli.Command { cfg.Frontend.Commons = cfg.Commons return nil }, - Action: func(c *cli.Context) error { - origCmd := command.Frontend(cfg.Frontend) - return handleOriginalAction(c, origCmd) - }, + Subcommands: command.GetCommands(cfg.Frontend), } } func init() { - register.AddCommand(StorageFrontendCommand) + register.AddCommand(FrontendCommand) } diff --git a/ocis/pkg/command/storagegateway.go b/ocis/pkg/command/gateway.go similarity index 58% rename from ocis/pkg/command/storagegateway.go rename to ocis/pkg/command/gateway.go index 17047b6068..b6af96d067 100644 --- a/ocis/pkg/command/storagegateway.go +++ b/ocis/pkg/command/gateway.go @@ -10,11 +10,11 @@ import ( "github.com/urfave/cli/v2" ) -// StorageGatewayCommand is the entrypoint for the reva-gateway command. -func StorageGatewayCommand(cfg *config.Config) *cli.Command { +// GatewayCommand is the entrypoint for the Gateway command. +func GatewayCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "storage-gateway", - Usage: "start storage gateway", + Name: cfg.Gateway.Service.Name, + Usage: subcommandDescription(cfg.Gateway.Service.Name), Category: "extensions", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg); err != nil { @@ -24,13 +24,10 @@ func StorageGatewayCommand(cfg *config.Config) *cli.Command { cfg.Gateway.Commons = cfg.Commons return nil }, - Action: func(c *cli.Context) error { - origCmd := command.Gateway(cfg.Gateway) - return handleOriginalAction(c, origCmd) - }, + Subcommands: command.GetCommands(cfg.Gateway), } } func init() { - register.AddCommand(StorageGatewayCommand) + register.AddCommand(GatewayCommand) } diff --git a/ocis/pkg/command/storagegroupprovider.go b/ocis/pkg/command/group.go similarity index 55% rename from ocis/pkg/command/storagegroupprovider.go rename to ocis/pkg/command/group.go index c93de96c11..fe40ba15ce 100644 --- a/ocis/pkg/command/storagegroupprovider.go +++ b/ocis/pkg/command/group.go @@ -10,11 +10,11 @@ import ( "github.com/urfave/cli/v2" ) -// StorageGroupProviderCommand is the entrypoint for the storage-groupprovider command. -func StorageGroupProviderCommand(cfg *config.Config) *cli.Command { +// GroupCommand is the entrypoint for the Group command. +func GroupCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "storage-groupprovider", - Usage: "start storage groupprovider service", + Name: cfg.Group.Service.Name, + Usage: subcommandDescription(cfg.Group.Service.Name), Category: "extensions", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg); err != nil { @@ -24,13 +24,10 @@ func StorageGroupProviderCommand(cfg *config.Config) *cli.Command { cfg.Group.Commons = cfg.Commons return nil }, - Action: func(c *cli.Context) error { - origCmd := command.Groups(cfg.Group) - return handleOriginalAction(c, origCmd) - }, + Subcommands: command.GetCommands(cfg.Group), } } func init() { - register.AddCommand(StorageGroupProviderCommand) + register.AddCommand(GroupCommand) } diff --git a/ocis/pkg/command/ocdav.go b/ocis/pkg/command/ocdav.go index 85fdeb4570..b9adf24924 100644 --- a/ocis/pkg/command/ocdav.go +++ b/ocis/pkg/command/ocdav.go @@ -10,11 +10,11 @@ import ( "github.com/urfave/cli/v2" ) -// OCDavCommand is the entrypoint for the ocdav command. +// OCDavCommand is the entrypoint for the OCDav command. func OCDavCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "ocdav", - Usage: "start ocdav", + Name: cfg.OCDav.Service.Name, + Usage: subcommandDescription(cfg.OCDav.Service.Name), Category: "extensions", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg); err != nil { @@ -24,10 +24,7 @@ func OCDavCommand(cfg *config.Config) *cli.Command { cfg.OCDav.Commons = cfg.Commons return nil }, - Action: func(c *cli.Context) error { - origCmd := command.OCDav(cfg.OCDav) - return handleOriginalAction(c, origCmd) - }, + Subcommands: command.GetCommands(cfg.OCDav), } } diff --git a/ocis/pkg/command/storagesharing.go b/ocis/pkg/command/sharing.go similarity index 57% rename from ocis/pkg/command/storagesharing.go rename to ocis/pkg/command/sharing.go index 63c7e2aa06..de5b391a95 100644 --- a/ocis/pkg/command/storagesharing.go +++ b/ocis/pkg/command/sharing.go @@ -10,11 +10,11 @@ import ( "github.com/urfave/cli/v2" ) -// StorageSharingCommand is the entrypoint for the reva-sharing command. -func StorageSharingCommand(cfg *config.Config) *cli.Command { +// SharingCommand is the entrypoint for the Sharing command. +func SharingCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "storage-sharing", - Usage: "start storage sharing service", + Name: cfg.Sharing.Service.Name, + Usage: subcommandDescription(cfg.Sharing.Service.Name), Category: "extensions", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg); err != nil { @@ -24,13 +24,10 @@ func StorageSharingCommand(cfg *config.Config) *cli.Command { cfg.Sharing.Commons = cfg.Commons return nil }, - Action: func(c *cli.Context) error { - origCmd := command.Sharing(cfg.Sharing) - return handleOriginalAction(c, origCmd) - }, + Subcommands: command.GetCommands(cfg.Sharing), } } func init() { - register.AddCommand(StorageSharingCommand) + register.AddCommand(SharingCommand) } diff --git a/ocis/pkg/command/storagemetadata.go b/ocis/pkg/command/storage-metadata.go similarity index 67% rename from ocis/pkg/command/storagemetadata.go rename to ocis/pkg/command/storage-metadata.go index 2965c36cfc..e70c6a120d 100644 --- a/ocis/pkg/command/storagemetadata.go +++ b/ocis/pkg/command/storage-metadata.go @@ -10,11 +10,11 @@ import ( "github.com/urfave/cli/v2" ) -// StorageMetadataCommand is the entrypoint for the storage-metadata command. +// StorageMetadataCommand is the entrypoint for the StorageMetadata command. func StorageMetadataCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "storage-metadata", - Usage: "start storage and data service for metadata", + Name: cfg.StorageMetadata.Service.Name, + Usage: subcommandDescription(cfg.StorageMetadata.Service.Name), Category: "extensions", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg); err != nil { @@ -24,10 +24,7 @@ func StorageMetadataCommand(cfg *config.Config) *cli.Command { cfg.StorageMetadata.Commons = cfg.Commons return nil }, - Action: func(c *cli.Context) error { - origCmd := command.StorageMetadata(cfg.StorageMetadata) - return handleOriginalAction(c, origCmd) - }, + Subcommands: command.GetCommands(cfg.StorageMetadata), } } diff --git a/ocis/pkg/command/storagepubliclink.go b/ocis/pkg/command/storage-publiclink.go similarity index 67% rename from ocis/pkg/command/storagepubliclink.go rename to ocis/pkg/command/storage-publiclink.go index fe2f8b75e8..c084b66439 100644 --- a/ocis/pkg/command/storagepubliclink.go +++ b/ocis/pkg/command/storage-publiclink.go @@ -10,11 +10,11 @@ import ( "github.com/urfave/cli/v2" ) -// StoragePublicLinkCommand is the entrypoint for the reva-storage-oc command. +// StoragePublicLinkCommand is the entrypoint for the StoragePublicLink command. func StoragePublicLinkCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "storage-public-link", - Usage: "start storage public link storage", + Name: cfg.StoragePublicLink.Service.Name, + Usage: subcommandDescription(cfg.StoragePublicLink.Service.Name), Category: "extensions", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg); err != nil { @@ -24,10 +24,7 @@ func StoragePublicLinkCommand(cfg *config.Config) *cli.Command { cfg.StoragePublicLink.Commons = cfg.Commons return nil }, - Action: func(c *cli.Context) error { - origCmd := command.StoragePublicLink(cfg.StoragePublicLink) - return handleOriginalAction(c, origCmd) - }, + Subcommands: command.GetCommands(cfg.StoragePublicLink), } } diff --git a/ocis/pkg/command/storageshares.go b/ocis/pkg/command/storage-shares.go similarity index 67% rename from ocis/pkg/command/storageshares.go rename to ocis/pkg/command/storage-shares.go index 62f13c38a8..dd337dc5e9 100644 --- a/ocis/pkg/command/storageshares.go +++ b/ocis/pkg/command/storage-shares.go @@ -10,11 +10,11 @@ import ( "github.com/urfave/cli/v2" ) -// StorageSharesCommand is the entrypoint for the storage-shares command. +// StorageSharesCommand is the entrypoint for the StorageShares command. func StorageSharesCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "storage-shares", - Usage: "start storage and data provider for shares jail", + Name: cfg.StorageShares.Service.Name, + Usage: subcommandDescription(cfg.StorageShares.Service.Name), Category: "extensions", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg); err != nil { @@ -24,10 +24,7 @@ func StorageSharesCommand(cfg *config.Config) *cli.Command { cfg.StorageShares.Commons = cfg.Commons return nil }, - Action: func(c *cli.Context) error { - origCmd := command.StorageShares(cfg.StorageShares) - return handleOriginalAction(c, origCmd) - }, + Subcommands: command.GetCommands(cfg.StorageShares), } } diff --git a/ocis/pkg/command/storage-users.go b/ocis/pkg/command/storage-users.go new file mode 100644 index 0000000000..e1a4aaf3ea --- /dev/null +++ b/ocis/pkg/command/storage-users.go @@ -0,0 +1,33 @@ +package command + +import ( + "fmt" + + "github.com/owncloud/ocis/extensions/storage-users/pkg/command" + "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/parser" + "github.com/owncloud/ocis/ocis/pkg/register" + "github.com/urfave/cli/v2" +) + +// StorageUsersCommand is the entrypoint for the StorageUsers command. +func StorageUsersCommand(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: cfg.StorageUsers.Service.Name, + Usage: subcommandDescription(cfg.StorageUsers.Service.Name), + Category: "extensions", + Before: func(c *cli.Context) error { + if err := parser.ParseConfig(cfg); err != nil { + fmt.Printf("%v", err) + return err + } + cfg.StorageUsers.Commons = cfg.Commons + return nil + }, + Subcommands: command.GetCommands(cfg.StorageUsers), + } +} + +func init() { + register.AddCommand(StorageUsersCommand) +} diff --git a/ocis/pkg/command/storageusers.go b/ocis/pkg/command/storageusers.go deleted file mode 100644 index 4ca75f2061..0000000000 --- a/ocis/pkg/command/storageusers.go +++ /dev/null @@ -1,25 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/extensions/storage-users/pkg/command" - "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis/pkg/register" - "github.com/urfave/cli/v2" -) - -// StorageUsersCommand is the entrypoint for the storage-users command. -func StorageUsersCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "storage-users", - Usage: "start storage and data provider for /users mount", - Category: "extensions", - Action: func(c *cli.Context) error { - origCmd := command.StorageUsers(cfg.StorageUsers) - return handleOriginalAction(c, origCmd) - }, - } -} - -func init() { - register.AddCommand(StorageUsersCommand) -} diff --git a/ocis/pkg/command/storageuserprovider.go b/ocis/pkg/command/user.go similarity index 51% rename from ocis/pkg/command/storageuserprovider.go rename to ocis/pkg/command/user.go index 5e3af09569..7685f1fc42 100644 --- a/ocis/pkg/command/storageuserprovider.go +++ b/ocis/pkg/command/user.go @@ -10,27 +10,24 @@ import ( "github.com/urfave/cli/v2" ) -// StorageUserProviderCommand is the entrypoint for the storage-userprovider command. -func StorageUserProviderCommand(cfg *config.Config) *cli.Command { +// UserCommand is the entrypoint for the User command. +func UserCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "storage-userprovider", - Usage: "start storage userprovider service", + Name: cfg.User.Service.Name, + Usage: subcommandDescription(cfg.User.Service.Name), Category: "extensions", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg); err != nil { fmt.Printf("%v", err) return err } - cfg.StorageUsers.Commons = cfg.Commons + cfg.User.Commons = cfg.Commons return nil }, - Action: func(c *cli.Context) error { - origCmd := command.User(cfg.User) - return handleOriginalAction(c, origCmd) - }, + Subcommands: command.GetCommands(cfg.User), } } func init() { - register.AddCommand(StorageUserProviderCommand) + register.AddCommand(UserCommand) } diff --git a/ocis/pkg/init/init.go b/ocis/pkg/init/init.go index 4f8bf8a51a..3fc566f0e5 100644 --- a/ocis/pkg/init/init.go +++ b/ocis/pkg/init/init.go @@ -56,8 +56,7 @@ type IdmExtension struct { } type FrontendExtension struct { - Archiver InsecureExtension - AppProvider InsecureExtension `yaml:"app_provider"` + Archiver InsecureExtension } type AuthbasicExtension struct { @@ -256,9 +255,6 @@ func CreateConfig(insecure, forceOverwrite bool, configPath, adminPassword strin }, } cfg.Frontend = FrontendExtension{ - AppProvider: InsecureExtension{ - Insecure: true, - }, Archiver: InsecureExtension{ Insecure: true, }, diff --git a/ocis/pkg/runtime/service/service.go b/ocis/pkg/runtime/service/service.go index 99f947155b..7814989f4a 100644 --- a/ocis/pkg/runtime/service/service.go +++ b/ocis/pkg/runtime/service/service.go @@ -109,7 +109,7 @@ func NewService(options ...Option) (*Service, error) { s.ServicesRegistry[opts.Config.Settings.Service.Name] = settings.NewSutureService s.ServicesRegistry[opts.Config.Nats.Service.Name] = nats.NewSutureService - s.ServicesRegistry[opts.Config.StorageMetadata.Service.Name] = storagemetadata.NewStorageMetadata + s.ServicesRegistry[opts.Config.StorageMetadata.Service.Name] = storagemetadata.NewSutureService s.ServicesRegistry[opts.Config.GLAuth.Service.Name] = glauth.NewSutureService s.ServicesRegistry[opts.Config.Graph.Service.Name] = graph.NewSutureService s.ServicesRegistry[opts.Config.GraphExplorer.Service.Name] = graphExplorer.NewSutureService @@ -119,23 +119,23 @@ func NewService(options ...Option) (*Service, error) { s.ServicesRegistry[opts.Config.Thumbnails.Service.Name] = thumbnails.NewSutureService s.ServicesRegistry[opts.Config.Web.Service.Name] = web.NewSutureService s.ServicesRegistry[opts.Config.WebDAV.Service.Name] = webdav.NewSutureService - s.ServicesRegistry[opts.Config.Frontend.Service.Name] = frontend.NewFrontend - s.ServicesRegistry[opts.Config.OCDav.Service.Name] = ocdav.NewOCDav - s.ServicesRegistry[opts.Config.Gateway.Service.Name] = gateway.NewGateway - s.ServicesRegistry[opts.Config.User.Service.Name] = user.NewUserProvider - s.ServicesRegistry[opts.Config.Group.Service.Name] = group.NewGroupProvider - s.ServicesRegistry[opts.Config.AuthBasic.Service.Name] = authbasic.NewAuthBasic - s.ServicesRegistry[opts.Config.AuthBearer.Service.Name] = authbearer.NewAuthBearer - s.ServicesRegistry[opts.Config.AuthMachine.Service.Name] = authmachine.NewAuthMachine - s.ServicesRegistry[opts.Config.StorageUsers.Service.Name] = storageusers.NewStorageUsers - s.ServicesRegistry[opts.Config.StorageShares.Service.Name] = storageshares.NewStorageShares - s.ServicesRegistry[opts.Config.StoragePublicLink.Service.Name] = storagepublic.NewStoragePublicLink - s.ServicesRegistry[opts.Config.AppProvider.Service.Name] = appprovider.NewAppProvider + s.ServicesRegistry[opts.Config.Frontend.Service.Name] = frontend.NewSutureService + s.ServicesRegistry[opts.Config.OCDav.Service.Name] = ocdav.NewSutureService + s.ServicesRegistry[opts.Config.Gateway.Service.Name] = gateway.NewSutureService + s.ServicesRegistry[opts.Config.User.Service.Name] = user.NewSutureService + s.ServicesRegistry[opts.Config.Group.Service.Name] = group.NewSutureService + s.ServicesRegistry[opts.Config.AuthBasic.Service.Name] = authbasic.NewSutureService + s.ServicesRegistry[opts.Config.AuthBearer.Service.Name] = authbearer.NewSutureService + s.ServicesRegistry[opts.Config.AuthMachine.Service.Name] = authmachine.NewSutureService + s.ServicesRegistry[opts.Config.StorageUsers.Service.Name] = storageusers.NewSutureService + s.ServicesRegistry[opts.Config.StorageShares.Service.Name] = storageshares.NewSutureService + s.ServicesRegistry[opts.Config.StoragePublicLink.Service.Name] = storagepublic.NewSutureService + s.ServicesRegistry[opts.Config.AppProvider.Service.Name] = appprovider.NewSutureService s.ServicesRegistry[opts.Config.Notifications.Service.Name] = notifications.NewSutureService s.ServicesRegistry[opts.Config.Search.Service.Name] = search.NewSutureService // populate delayed services - s.Delayed[opts.Config.Sharing.Service.Name] = sharing.NewSharing + s.Delayed[opts.Config.Sharing.Service.Name] = sharing.NewSutureService s.Delayed[opts.Config.Accounts.Service.Name] = accounts.NewSutureService s.Delayed[opts.Config.Proxy.Service.Name] = proxy.NewSutureService s.Delayed[opts.Config.IDP.Service.Name] = idp.NewSutureService @@ -256,7 +256,7 @@ func (s *Service) generateRunSet(cfg *ociscfg.Config) { } for name := range s.ServicesRegistry { - // don't run glauth by default but keep the possiblity to start it via cfg.Runtime.Extensions for now + // don't run glauth by default but keep the possibility to start it via cfg.Runtime.Extensions for now if name == "glauth" { continue } @@ -264,7 +264,7 @@ func (s *Service) generateRunSet(cfg *ociscfg.Config) { } for name := range s.Delayed { - // don't run accounts by default but keep the possiblity to start it via cfg.Runtime.Extensions for now + // don't run accounts by default but keep the possibility to start it via cfg.Runtime.Extensions for now if name == "accounts" { continue }