From 26a92f2bad3dcfc8d0bab38738727b2ed927aa7f Mon Sep 17 00:00:00 2001 From: Willy Kloucek Date: Tue, 3 May 2022 12:04:59 +0200 Subject: [PATCH] add app-registry service --- .drone.star | 4 +- Makefile | 1 + docs/extensions/app-registry/_index.md | 16 ++ .../{appprovider => app-registry}/apps.md | 0 docs/extensions/app-registry/configuration.md | 15 ++ docs/extensions/port-ranges.md | 4 +- .../app-registry/cmd/app-registry/main.go | 14 ++ extensions/app-registry/pkg/command/health.go | 57 +++++++ extensions/app-registry/pkg/command/root.go | 64 +++++++ extensions/app-registry/pkg/command/server.go | 102 +++++++++++ .../app-registry/pkg/command/version.go | 50 ++++++ extensions/app-registry/pkg/config/config.go | 70 ++++++++ .../pkg/config/defaults/defaultconfig.go | 158 ++++++++++++++++++ .../app-registry/pkg/config/parser/parse.go | 46 +++++ extensions/app-registry/pkg/config/reva.go | 11 ++ .../app-registry/pkg/logging/logging.go | 17 ++ .../app-registry/pkg/revaconfig/config.go | 48 ++++++ .../app-registry/pkg/server/debug/option.go | 50 ++++++ .../app-registry/pkg/server/debug/server.go | 63 +++++++ .../app-registry/pkg/tracing/tracing.go | 18 ++ .../pkg/config/defaults/defaultconfig.go | 4 +- ocis-pkg/config/config.go | 38 +++-- ocis-pkg/config/defaultconfig.go | 3 + ocis/pkg/command/app-registry.go | 33 ++++ ocis/pkg/runtime/service/service.go | 2 + 25 files changed, 864 insertions(+), 24 deletions(-) create mode 100644 docs/extensions/app-registry/_index.md rename docs/extensions/{appprovider => app-registry}/apps.md (100%) create mode 100644 docs/extensions/app-registry/configuration.md create mode 100644 extensions/app-registry/cmd/app-registry/main.go create mode 100644 extensions/app-registry/pkg/command/health.go create mode 100644 extensions/app-registry/pkg/command/root.go create mode 100644 extensions/app-registry/pkg/command/server.go create mode 100644 extensions/app-registry/pkg/command/version.go create mode 100644 extensions/app-registry/pkg/config/config.go create mode 100644 extensions/app-registry/pkg/config/defaults/defaultconfig.go create mode 100644 extensions/app-registry/pkg/config/parser/parse.go create mode 100644 extensions/app-registry/pkg/config/reva.go create mode 100644 extensions/app-registry/pkg/logging/logging.go create mode 100644 extensions/app-registry/pkg/revaconfig/config.go create mode 100644 extensions/app-registry/pkg/server/debug/option.go create mode 100644 extensions/app-registry/pkg/server/debug/server.go create mode 100644 extensions/app-registry/pkg/tracing/tracing.go create mode 100644 ocis/pkg/command/app-registry.go diff --git a/.drone.star b/.drone.star index ce4ef53550..211d8bbdc4 100644 --- a/.drone.star +++ b/.drone.star @@ -44,6 +44,7 @@ config = { "modules": [ # if you add a module here please also add it to the root level Makefile "extensions/accounts", + "extensions/app-registry", "extensions/appprovider", "extensions/audit", "extensions/auth-basic", @@ -53,9 +54,9 @@ config = { "extensions/gateway", "extensions/glauth", "extensions/graph-explorer", + "extensions/graph", "extensions/group", "extensions/idm", - "extensions/graph", "extensions/idp", "extensions/nats", "extensions/notifications", @@ -68,7 +69,6 @@ config = { "extensions/storage-publiclink", "extensions/storage-shares", "extensions/storage-users", - "extensions/storage", "extensions/store", "extensions/thumbnails", "extensions/user", diff --git a/Makefile b/Makefile index c3c7197bdb..6d751c283d 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,7 @@ L10N_MODULES := $(shell find . -path '*.tx*' -name 'config' | sed 's|/[^/]*$$||' # if you add a module here please also add it to the .drone.star file OCIS_MODULES = \ extensions/accounts \ + extensions/app-registry \ extensions/appprovider \ extensions/audit \ extensions/auth-basic \ diff --git a/docs/extensions/app-registry/_index.md b/docs/extensions/app-registry/_index.md new file mode 100644 index 0000000000..9b79ea4a89 --- /dev/null +++ b/docs/extensions/app-registry/_index.md @@ -0,0 +1,16 @@ +--- +title: App Registry +date: 2022-03-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/app-registry +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/extensions/appprovider/apps.md b/docs/extensions/app-registry/apps.md similarity index 100% rename from docs/extensions/appprovider/apps.md rename to docs/extensions/app-registry/apps.md diff --git a/docs/extensions/app-registry/configuration.md b/docs/extensions/app-registry/configuration.md new file mode 100644 index 0000000000..f1a18da072 --- /dev/null +++ b/docs/extensions/app-registry/configuration.md @@ -0,0 +1,15 @@ +--- +title: Service Configuration +date: 2018-05-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/app-registry +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="extensions/_includes/app-registry-config-example.yaml" language="yaml" >}} + +{{< include file="extensions/_includes/app-registry_configvars.md" >}} diff --git a/docs/extensions/port-ranges.md b/docs/extensions/port-ranges.md index bc3143c08e..926fb7030b 100644 --- a/docs/extensions/port-ranges.md +++ b/docs/extensions/port-ranges.md @@ -17,7 +17,7 @@ Feel free to "reserve" a free port range when you're developing an extension by If you're developing a non-public extension, we recommend using ports outside of the ranges listed below. -We also suggest to use the last port in your extensions' range as a debug/metrics port. +We also suggest to use the last port in your extensions' range as a debug/metrics port.**** ## Allocations @@ -46,7 +46,7 @@ We also suggest to use the last port in your extensions' range as a debug/metric | 9225-9229 | photoprism (state: PoC) | | 9230-9234 | [nats](https://github.com/owncloud/ocis/tree/master/nats) | | 9235-9239 | idm TBD | -| 9240-9244 | FREE | +| 9240-9244 | [app-registry](https://github.com/owncloud/ocis/tree/master/extensions/app-registry) | | 9245-9249 | FREE | | 9250-9254 | oCIS Runtime | | 9255-9259 | FREE | diff --git a/extensions/app-registry/cmd/app-registry/main.go b/extensions/app-registry/cmd/app-registry/main.go new file mode 100644 index 0000000000..19565e1e81 --- /dev/null +++ b/extensions/app-registry/cmd/app-registry/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/app-registry/pkg/command" + "github.com/owncloud/ocis/extensions/app-registry/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/app-registry/pkg/command/health.go b/extensions/app-registry/pkg/command/health.go new file mode 100644 index 0000000000..c60fd662a9 --- /dev/null +++ b/extensions/app-registry/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/app-registry/pkg/config" + "github.com/owncloud/ocis/extensions/app-registry/pkg/config/parser" + "github.com/owncloud/ocis/extensions/app-registry/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/app-registry/pkg/command/root.go b/extensions/app-registry/pkg/command/root.go new file mode 100644 index 0000000000..df302358e1 --- /dev/null +++ b/extensions/app-registry/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/app-registry/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-app-registry command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "ocis-app-registry", + Usage: "Provide a app registry 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 app-registry command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new app-registry.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.AppRegistry.Commons = cfg.Commons + return SutureService{ + cfg: cfg.AppRegistry, + } +} + +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/app-registry/pkg/command/server.go b/extensions/app-registry/pkg/command/server.go new file mode 100644 index 0000000000..0ab6f3301c --- /dev/null +++ b/extensions/app-registry/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/app-registry/pkg/config" + "github.com/owncloud/ocis/extensions/app-registry/pkg/config/parser" + "github.com/owncloud/ocis/extensions/app-registry/pkg/logging" + "github.com/owncloud/ocis/extensions/app-registry/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/app-registry/pkg/server/debug" + "github.com/owncloud/ocis/extensions/app-registry/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.AppRegistryConfigFromStruct(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/app-registry/pkg/command/version.go b/extensions/app-registry/pkg/command/version.go new file mode 100644 index 0000000000..04b2d20c61 --- /dev/null +++ b/extensions/app-registry/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/app-registry/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/app-registry/pkg/config/config.go b/extensions/app-registry/pkg/config/config.go new file mode 100644 index 0000000000..d3efe36e50 --- /dev/null +++ b/extensions/app-registry/pkg/config/config.go @@ -0,0 +1,70 @@ +package config + +import ( + "context" + + "github.com/owncloud/ocis/ocis-pkg/shared" +) + +type Config struct { + *shared.Commons `yaml:"-"` + + Service Service `yaml:"-"` + Tracing *Tracing `yaml:"tracing"` + Log *Log `yaml:"log"` + Debug Debug `yaml:"debug"` + + GRPC GRPCConfig `yaml:"grpc"` + + TokenManager *TokenManager `yaml:"token_manager"` + Reva *Reva `yaml:"reva"` + + AppRegistry AppRegistry `yaml:"app_registry"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` +} +type Tracing struct { + Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;APP_REGISTRY_TRACING_ENABLED" desc:"Activates tracing."` + Type string `yaml:"type" env:"OCIS_TRACING_TYPE;APP_REGISTRY_TRACING_TYPE"` + Endpoint string `yaml:"endpoint" env:"OCIS_TRACING_ENDPOINT;APP_REGISTRY_TRACING_ENDPOINT" desc:"The endpoint to the tracing collector."` + Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;APP_REGISTRY_TRACING_COLLECTOR"` +} + +type Log struct { + Level string `yaml:"level" env:"OCIS_LOG_LEVEL;APP_REGISTRY_LOG_LEVEL" desc:"The log level."` + Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;APP_REGISTRY_LOG_PRETTY" desc:"Activates pretty log output."` + Color bool `yaml:"color" env:"OCIS_LOG_COLOR;APP_REGISTRY_LOG_COLOR" desc:"Activates colorized log output."` + File string `yaml:"file" env:"OCIS_LOG_FILE;APP_REGISTRY_LOG_FILE" desc:"The target log file."` +} + +type Service struct { + Name string `yaml:"-"` +} + +type Debug struct { + Addr string `yaml:"addr" env:"APP_REGISTRY_DEBUG_ADDR"` + Token string `yaml:"token" env:"APP_REGISTRY_DEBUG_TOKEN"` + Pprof bool `yaml:"pprof" env:"APP_REGISTRY_DEBUG_PPROF"` + Zpages bool `yaml:"zpages" env:"APP_REGISTRY_DEBUG_ZPAGES"` +} + +type GRPCConfig struct { + Addr string `yaml:"addr" env:"APP_REGISTRY_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"APP_REGISTRY_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` +} + +type AppRegistry struct { + MimeTypeConfig []MimeTypeConfig `yaml:"mimetypes"` +} + +type MimeTypeConfig struct { + MimeType string `yaml:"mime_type" mapstructure:"mime_type"` + Extension string `yaml:"extension" mapstructure:"extension"` + Name string `yaml:"name" mapstructure:"name"` + Description string `yaml:"description" mapstructure:"description"` + Icon string `yaml:"icon" mapstructure:"icon"` + DefaultApp string `yaml:"default_app" mapstructure:"default_app"` + AllowCreation bool `yaml:"allow_creation" mapstructure:"allow_creation"` +} diff --git a/extensions/app-registry/pkg/config/defaults/defaultconfig.go b/extensions/app-registry/pkg/config/defaults/defaultconfig.go new file mode 100644 index 0000000000..de2f4e137c --- /dev/null +++ b/extensions/app-registry/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,158 @@ +package defaults + +import ( + "github.com/owncloud/ocis/extensions/app-registry/pkg/config" +) + +func FullDefaultConfig() *config.Config { + cfg := DefaultConfig() + EnsureDefaults(cfg) + Sanitize(cfg) + return cfg +} + +func DefaultConfig() *config.Config { + return &config.Config{ + Debug: config.Debug{ + Addr: "127.0.0.1:9243", + Token: "", + Pprof: false, + Zpages: false, + }, + GRPC: config.GRPCConfig{ + Addr: "127.0.0.1:9242", + Namespace: "com.owncloud.api", + Protocol: "tcp", + }, + Service: config.Service{ + Name: "app-registry", + }, + Reva: &config.Reva{ + Address: "127.0.0.1:9142", + }, + AppRegistry: config.AppRegistry{ + MimeTypeConfig: defaultMimeTypeConfig(), + }, + } +} + +func defaultMimeTypeConfig() []config.MimeTypeConfig { + return []config.MimeTypeConfig{ + { + MimeType: "application/pdf", + Extension: "pdf", + Name: "PDF", + Description: "PDF document", + }, + { + MimeType: "application/vnd.oasis.opendocument.text", + Extension: "odt", + Name: "OpenDocument", + Description: "OpenDocument text document", + AllowCreation: true, + }, + { + MimeType: "application/vnd.oasis.opendocument.spreadsheet", + Extension: "ods", + Name: "OpenSpreadsheet", + Description: "OpenDocument spreadsheet document", + AllowCreation: true, + }, + { + MimeType: "application/vnd.oasis.opendocument.presentation", + Extension: "odp", + Name: "OpenPresentation", + Description: "OpenDocument presentation document", + AllowCreation: true, + }, + { + MimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + Extension: "docx", + Name: "Microsoft Word", + Description: "Microsoft Word document", + AllowCreation: true, + }, + { + MimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + Extension: "xlsx", + Name: "Microsoft Excel", + Description: "Microsoft Excel document", + AllowCreation: true, + }, + { + MimeType: "application/vnd.openxmlformats-officedocument.presentationml.presentation", + Extension: "pptx", + Name: "Microsoft PowerPoint", + Description: "Microsoft PowerPoint document", + AllowCreation: true, + }, + { + MimeType: "application/vnd.jupyter", + Extension: "ipynb", + Name: "Jupyter Notebook", + Description: "Jupyter Notebook", + }, + { + MimeType: "text/markdown", + Extension: "md", + Name: "Markdown file", + Description: "Markdown file", + AllowCreation: true, + }, + { + MimeType: "application/compressed-markdown", + Extension: "zmd", + Name: "Compressed markdown file", + Description: "Compressed markdown file", + }, + } +} + +func EnsureDefaults(cfg *config.Config) { + // provide with defaults for shared logging, since we need a valid destination address for BindEnv. + 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.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 { + cfg.Tracing = &config.Tracing{ + Enabled: cfg.Commons.Tracing.Enabled, + Type: cfg.Commons.Tracing.Type, + Endpoint: cfg.Commons.Tracing.Endpoint, + Collector: cfg.Commons.Tracing.Collector, + } + } else if cfg.Tracing == nil { + cfg.Tracing = &config.Tracing{} + } + + if cfg.Reva == nil && cfg.Commons != nil && cfg.Commons.Reva != nil { + cfg.Reva = &config.Reva{ + Address: cfg.Commons.Reva.Address, + } + } else if cfg.Reva == nil { + cfg.Reva = &config.Reva{} + } + + if cfg.TokenManager == nil && cfg.Commons != nil && cfg.Commons.TokenManager != nil { + cfg.TokenManager = &config.TokenManager{ + JWTSecret: cfg.Commons.TokenManager.JWTSecret, + } + } else if cfg.TokenManager == nil { + cfg.TokenManager = &config.TokenManager{} + } + + if cfg.TransferSecret == "" && cfg.Commons != nil && cfg.Commons.TransferSecret != "" { + cfg.TransferSecret = cfg.Commons.TransferSecret + } +} + +func Sanitize(cfg *config.Config) { + // nothing to sanitize here atm +} diff --git a/extensions/app-registry/pkg/config/parser/parse.go b/extensions/app-registry/pkg/config/parser/parse.go new file mode 100644 index 0000000000..894f938b6e --- /dev/null +++ b/extensions/app-registry/pkg/config/parser/parse.go @@ -0,0 +1,46 @@ +package parser + +import ( + "errors" + + "github.com/owncloud/ocis/extensions/app-registry/pkg/config" + "github.com/owncloud/ocis/extensions/app-registry/pkg/config/defaults" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/shared" + + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + if cfg.TokenManager.JWTSecret == "" { + return shared.MissingJWTTokenError(cfg.Service.Name) + } + + if cfg.TransferSecret == "" { + return shared.MissingRevaTransferSecretError(cfg.Service.Name) + } + + return nil +} diff --git a/extensions/app-registry/pkg/config/reva.go b/extensions/app-registry/pkg/config/reva.go new file mode 100644 index 0000000000..e1648edc4d --- /dev/null +++ b/extensions/app-registry/pkg/config/reva.go @@ -0,0 +1,11 @@ +package config + +// Reva defines all available REVA configuration. +type Reva struct { + Address string `yaml:"address" env:"REVA_GATEWAY"` +} + +// TokenManager is the config for using the reva token manager +type TokenManager struct { + JWTSecret string `yaml:"jwt_secret" env:"OCIS_JWT_SECRET;APP_REGISTRY_JWT_SECRET"` +} diff --git a/extensions/app-registry/pkg/logging/logging.go b/extensions/app-registry/pkg/logging/logging.go new file mode 100644 index 0000000000..f887afef4c --- /dev/null +++ b/extensions/app-registry/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/app-registry/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/app-registry/pkg/revaconfig/config.go b/extensions/app-registry/pkg/revaconfig/config.go new file mode 100644 index 0000000000..576f1643c6 --- /dev/null +++ b/extensions/app-registry/pkg/revaconfig/config.go @@ -0,0 +1,48 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/ocis-pkg/log" + + "github.com/mitchellh/mapstructure" + "github.com/owncloud/ocis/extensions/app-registry/pkg/config" +) + +// AppRegistryConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func AppRegistryConfigFromStruct(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": 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{}{ + "appregistry": map[string]interface{}{ + "driver": "static", + "drivers": map[string]interface{}{ + "static": map[string]interface{}{ + "mime_types": mimetypes(cfg, logger), + }, + }, + }, + }, + }, + } + return rcfg +} + +func mimetypes(cfg *config.Config, logger log.Logger) []map[string]interface{} { + var m []map[string]interface{} + if err := mapstructure.Decode(cfg.AppRegistry.MimeTypeConfig, &m); err != nil { + logger.Error().Err(err).Msg("Failed to decode appregistry mimetypes to mapstructure") + return nil + } + return m +} diff --git a/extensions/app-registry/pkg/server/debug/option.go b/extensions/app-registry/pkg/server/debug/option.go new file mode 100644 index 0000000000..2ccb53ed45 --- /dev/null +++ b/extensions/app-registry/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/extensions/app-registry/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Logger log.Logger + Context context.Context + Config *config.Config +} + +// newOptions initializes the available default options. +func newOptions(opts ...Option) Options { + opt := Options{} + + for _, o := range opts { + o(&opt) + } + + return opt +} + +// Logger provides a function to set the logger option. +func Logger(val log.Logger) Option { + return func(o *Options) { + o.Logger = val + } +} + +// Context provides a function to set the context option. +func Context(val context.Context) Option { + return func(o *Options) { + o.Context = val + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} diff --git a/extensions/app-registry/pkg/server/debug/server.go b/extensions/app-registry/pkg/server/debug/server.go new file mode 100644 index 0000000000..cf106ef99b --- /dev/null +++ b/extensions/app-registry/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/extensions/app-registry/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/service/debug" + "github.com/owncloud/ocis/ocis-pkg/version" +) + +// Server initializes the debug service and server. +func Server(opts ...Option) (*http.Server, error) { + options := newOptions(opts...) + + return debug.NewService( + debug.Logger(options.Logger), + debug.Name(options.Config.Service.Name), + debug.Version(version.String), + debug.Address(options.Config.Debug.Addr), + debug.Token(options.Config.Debug.Token), + debug.Pprof(options.Config.Debug.Pprof), + debug.Zpages(options.Config.Debug.Zpages), + debug.Health(health(options.Config)), + debug.Ready(ready(options.Config)), + //debug.CorsAllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins), + //debug.CorsAllowedMethods(options.Config.HTTP.CORS.AllowedMethods), + //debug.CorsAllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders), + //debug.CorsAllowCredentials(options.Config.HTTP.CORS.AllowCredentials), + ), nil +} + +// health implements the health check. +func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} + +// ready implements the ready check. +func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} diff --git a/extensions/app-registry/pkg/tracing/tracing.go b/extensions/app-registry/pkg/tracing/tracing.go new file mode 100644 index 0000000000..212c4f7c11 --- /dev/null +++ b/extensions/app-registry/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/app-registry/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-basic/pkg/config/defaults/defaultconfig.go b/extensions/auth-basic/pkg/config/defaults/defaultconfig.go index 66e3d4d93a..6224e1fb6b 100644 --- a/extensions/auth-basic/pkg/config/defaults/defaultconfig.go +++ b/extensions/auth-basic/pkg/config/defaults/defaultconfig.go @@ -23,9 +23,9 @@ func DefaultConfig() *config.Config { Zpages: false, }, GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9146", + Addr: "127.0.0.1:9146", Namespace: "com.owncloud.api", - Protocol: "tcp", + Protocol: "tcp", }, Service: config.Service{ Name: "auth-basic", diff --git a/ocis-pkg/config/config.go b/ocis-pkg/config/config.go index 8f6b1e0d83..40a1d9842b 100644 --- a/ocis-pkg/config/config.go +++ b/ocis-pkg/config/config.go @@ -4,6 +4,7 @@ import ( "github.com/owncloud/ocis/ocis-pkg/shared" accounts "github.com/owncloud/ocis/extensions/accounts/pkg/config" + appRegistry "github.com/owncloud/ocis/extensions/app-registry/pkg/config" appprovider "github.com/owncloud/ocis/extensions/appprovider/pkg/config" audit "github.com/owncloud/ocis/extensions/audit/pkg/config" authbasic "github.com/owncloud/ocis/extensions/auth-basic/pkg/config" @@ -71,35 +72,36 @@ type Config struct { MetadataUserID string `yaml:"metadata_user_id" env:"METADATA_USER_ID"` Runtime Runtime `yaml:"runtime"` - Audit *audit.Config `yaml:"audit"` Accounts *accounts.Config `yaml:"accounts"` - GLAuth *glauth.Config `yaml:"glauth"` - Graph *graph.Config `yaml:"graph"` - GraphExplorer *graphExplorer.Config `yaml:"graph-explorer"` - IDP *idp.Config `yaml:"idp"` - IDM *idm.Config `yaml:"idm"` - Nats *nats.Config `yaml:"nats"` - Notifications *notifications.Config `yaml:"notifications"` - OCS *ocs.Config `yaml:"ocs"` - Web *web.Config `yaml:"web"` - Proxy *proxy.Config `yaml:"proxy"` - Settings *settings.Config `yaml:"settings"` - Gateway *gateway.Config `yaml:"gateway"` - Frontend *frontend.Config `yaml:"frontend"` + AppProvider *appprovider.Config `yaml:"appprovider"` + AppRegistry *appRegistry.Config `yaml:"app-registry"` + Audit *audit.Config `yaml:"audit"` AuthBasic *authbasic.Config `yaml:"auth-basic"` AuthBearer *authbearer.Config `yaml:"auth-bearer"` AuthMachine *authmachine.Config `yaml:"auth-machine"` - User *user.Config `yaml:"user"` + Frontend *frontend.Config `yaml:"frontend"` + Gateway *gateway.Config `yaml:"gateway"` + GLAuth *glauth.Config `yaml:"glauth"` + Graph *graph.Config `yaml:"graph"` + GraphExplorer *graphExplorer.Config `yaml:"graph-explorer"` Group *group.Config `yaml:"group"` - AppProvider *appprovider.Config `yaml:"appprovider"` + IDM *idm.Config `yaml:"idm"` + IDP *idp.Config `yaml:"idp"` + Nats *nats.Config `yaml:"nats"` + Notifications *notifications.Config `yaml:"notifications"` + OCDav *ocdav.Config `yaml:"ocdav"` + OCS *ocs.Config `yaml:"ocs"` + Proxy *proxy.Config `yaml:"proxy"` + Settings *settings.Config `yaml:"settings"` Sharing *sharing.Config `yaml:"sharing"` StorageMetadata *storagemetadata.Config `yaml:"storage-metadata"` StoragePublicLink *storagepublic.Config `yaml:"storage-public"` - StorageUsers *storageusers.Config `yaml:"storage-users"` StorageShares *storageshares.Config `yaml:"storage-shares"` - OCDav *ocdav.Config `yaml:"ocdav"` + StorageUsers *storageusers.Config `yaml:"storage-users"` Store *store.Config `yaml:"store"` Thumbnails *thumbnails.Config `yaml:"thumbnails"` + User *user.Config `yaml:"user"` + Web *web.Config `yaml:"web"` WebDAV *webdav.Config `yaml:"webdav"` Search *search.Config `yaml:"search"` } diff --git a/ocis-pkg/config/defaultconfig.go b/ocis-pkg/config/defaultconfig.go index 8798918bfc..b6ee829a10 100644 --- a/ocis-pkg/config/defaultconfig.go +++ b/ocis-pkg/config/defaultconfig.go @@ -2,6 +2,7 @@ package config import ( accounts "github.com/owncloud/ocis/extensions/accounts/pkg/config/defaults" + appregistry "github.com/owncloud/ocis/extensions/app-registry/pkg/config/defaults" appprovider "github.com/owncloud/ocis/extensions/appprovider/pkg/config/defaults" audit "github.com/owncloud/ocis/extensions/audit/pkg/config/defaults" authbasic "github.com/owncloud/ocis/extensions/auth-basic/pkg/config/defaults" @@ -40,8 +41,10 @@ func DefaultConfig() *Config { Port: "9250", Host: "localhost", }, + Accounts: accounts.DefaultConfig(), AppProvider: appprovider.DefaultConfig(), + AppRegistry: appregistry.DefaultConfig(), Audit: audit.DefaultConfig(), AuthBasic: authbasic.DefaultConfig(), AuthBearer: authbearer.DefaultConfig(), diff --git a/ocis/pkg/command/app-registry.go b/ocis/pkg/command/app-registry.go new file mode 100644 index 0000000000..74dcb85ab5 --- /dev/null +++ b/ocis/pkg/command/app-registry.go @@ -0,0 +1,33 @@ +package command + +import ( + "fmt" + + "github.com/owncloud/ocis/extensions/app-registry/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" +) + +// AppRegistryCommand is the entrypoint for the AppRegistry command. +func AppRegistryCommand(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: cfg.AppRegistry.Service.Name, + Usage: subcommandDescription(cfg.AppRegistry.Service.Name), + Category: "extensions", + Before: func(c *cli.Context) error { + if err := parser.ParseConfig(cfg); err != nil { + fmt.Printf("%v", err) + return err + } + cfg.AppRegistry.Commons = cfg.Commons + return nil + }, + Subcommands: command.GetCommands(cfg.AppRegistry), + } +} + +func init() { + register.AddCommand(AppRegistryCommand) +} diff --git a/ocis/pkg/runtime/service/service.go b/ocis/pkg/runtime/service/service.go index 7814989f4a..612c67cab9 100644 --- a/ocis/pkg/runtime/service/service.go +++ b/ocis/pkg/runtime/service/service.go @@ -20,6 +20,7 @@ import ( "github.com/olekukonko/tablewriter" accounts "github.com/owncloud/ocis/extensions/accounts/pkg/command" + appregistry "github.com/owncloud/ocis/extensions/app-registry/pkg/command" appprovider "github.com/owncloud/ocis/extensions/appprovider/pkg/command" authbasic "github.com/owncloud/ocis/extensions/auth-basic/pkg/command" authbearer "github.com/owncloud/ocis/extensions/auth-bearer/pkg/command" @@ -122,6 +123,7 @@ func NewService(options ...Option) (*Service, error) { 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.AppRegistry.Service.Name] = appregistry.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