Initial QSfera import
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
SHELL := bash
|
||||
NAME := storage-system
|
||||
|
||||
ifneq (, $(shell command -v go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI
|
||||
include ../../.bingo/Variables.mk
|
||||
endif
|
||||
|
||||
include ../../.make/default.mk
|
||||
include ../../.make/go.mk
|
||||
include ../../.make/release.mk
|
||||
include ../../.make/docs.mk
|
||||
@@ -0,0 +1,22 @@
|
||||
# Storage-System
|
||||
|
||||
The КуСфера Storage-System service persists and caches user related data that is defined via КуСфера. This can be among other data role assignments, user settings and users shares.
|
||||
|
||||
## Caching
|
||||
|
||||
The `storage-system` service caches file metadata via the configured store in `STORAGE_SYSTEM_CACHE_STORE`. Possible stores are:
|
||||
- `memory`: Basic in-memory store and the default.
|
||||
- `redis-sentinel`: Stores data in a configured Redis Sentinel cluster.
|
||||
- `nats-js-kv`: Stores data using key-value-store feature of [nats jetstream](https://docs.nats.io/nats-concepts/jetstream/key-value-store)
|
||||
- `noop`: Stores nothing. Useful for testing. Not recommended in production environments.
|
||||
|
||||
Other store types may work but are not supported currently.
|
||||
|
||||
Note: The service can only be scaled if not using `memory` store and the stores are configured identically over all instances!
|
||||
|
||||
Note that if you have used one of the deprecated stores, you should reconfigure to one of the supported ones as the deprecated stores will be removed in a later version.
|
||||
|
||||
Store specific notes:
|
||||
- When using `redis-sentinel`, the Redis master to use is configured via e.g. `OC_CACHE_STORE_NODES` in the form of `<sentinel-host>:<sentinel-port>/<redis-master>` like `10.10.0.200:26379/mymaster`.
|
||||
- When using `nats-js-kv` it is recommended to set `OC_CACHE_STORE_NODES` to the same value as `OC_EVENTS_ENDPOINT`. That way the cache uses the same nats instance as the event bus.
|
||||
- When using the `nats-js-kv` store, it is possible to set `OC_CACHE_DISABLE_PERSISTENCE` to instruct nats to not persist cache data on disc.
|
||||
@@ -0,0 +1,54 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/qsfera/server/pkg/config/configlog"
|
||||
"github.com/qsfera/server/pkg/log"
|
||||
"github.com/qsfera/server/services/storage-system/pkg/config"
|
||||
"github.com/qsfera/server/services/storage-system/pkg/config/parser"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// Health is the entrypoint for the health command.
|
||||
func Health(cfg *config.Config) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "health",
|
||||
Short: "check health status",
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return configlog.ReturnError(parser.ParseConfig(cfg))
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
logger := log.Configure(cfg.Service.Name, cfg.Commons, cfg.LogLevel)
|
||||
|
||||
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
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/qsfera/server/pkg/clihelper"
|
||||
"github.com/qsfera/server/services/storage-system/pkg/config"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// GetCommands provides all commands for this service
|
||||
func GetCommands(cfg *config.Config) []*cobra.Command {
|
||||
return []*cobra.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-system command.
|
||||
func Execute(cfg *config.Config) error {
|
||||
app := clihelper.DefaultApp(&cobra.Command{
|
||||
Use: "storage-system",
|
||||
Short: "Provide system storage for КуСфера",
|
||||
})
|
||||
app.AddCommand(GetCommands(cfg)...)
|
||||
app.SetArgs(os.Args[1:])
|
||||
|
||||
return app.ExecuteContext(cfg.Context)
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/signal"
|
||||
|
||||
"github.com/qsfera/server/pkg/config/configlog"
|
||||
"github.com/qsfera/server/pkg/log"
|
||||
"github.com/qsfera/server/pkg/registry"
|
||||
"github.com/qsfera/server/pkg/runner"
|
||||
"github.com/qsfera/server/pkg/tracing"
|
||||
"github.com/qsfera/server/pkg/version"
|
||||
"github.com/qsfera/server/services/storage-system/pkg/config"
|
||||
"github.com/qsfera/server/services/storage-system/pkg/config/parser"
|
||||
"github.com/qsfera/server/services/storage-system/pkg/revaconfig"
|
||||
"github.com/qsfera/server/services/storage-system/pkg/server/debug"
|
||||
"github.com/opencloud-eu/reva/v2/cmd/revad/runtime"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// Server is the entry point for the server command.
|
||||
func Server(cfg *config.Config) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "server",
|
||||
Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name),
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return configlog.ReturnFatal(parser.ParseConfig(cfg))
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
logger := log.Configure(cfg.Service.Name, cfg.Commons, cfg.LogLevel)
|
||||
traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var cancel context.CancelFunc
|
||||
if cfg.Context == nil {
|
||||
cfg.Context, cancel = signal.NotifyContext(context.Background(), runner.StopSignals...)
|
||||
defer cancel()
|
||||
}
|
||||
ctx := cfg.Context
|
||||
|
||||
gr := runner.NewGroup()
|
||||
|
||||
{
|
||||
// run the appropriate reva servers based on the config
|
||||
rCfg := revaconfig.StorageSystemFromStruct(cfg)
|
||||
if rServer := runtime.NewDrivenHTTPServerWithOptions(rCfg,
|
||||
runtime.WithLogger(&logger.Logger),
|
||||
runtime.WithRegistry(registry.GetRegistry()),
|
||||
runtime.WithTraceProvider(traceProvider),
|
||||
); rServer != nil {
|
||||
gr.Add(runner.NewRevaServiceRunner(cfg.Service.Name+".rhttp", rServer))
|
||||
}
|
||||
if rServer := runtime.NewDrivenGRPCServerWithOptions(rCfg,
|
||||
runtime.WithLogger(&logger.Logger),
|
||||
runtime.WithRegistry(registry.GetRegistry()),
|
||||
runtime.WithTraceProvider(traceProvider),
|
||||
); rServer != nil {
|
||||
gr.Add(runner.NewRevaServiceRunner(cfg.Service.Name+".rgrpc", rServer))
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
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(runner.NewGolangHttpServerRunner("storage-system_debug", debugServer))
|
||||
}
|
||||
|
||||
grpcSvc := registry.BuildGRPCService(cfg.GRPC.Namespace+"."+cfg.Service.Name, cfg.GRPC.Protocol, cfg.GRPC.Addr, version.GetString())
|
||||
if err := registry.RegisterService(ctx, logger, grpcSvc, cfg.Debug.Addr); err != nil {
|
||||
logger.Fatal().Err(err).Msg("failed to register the grpc service")
|
||||
}
|
||||
|
||||
httpSvc := registry.BuildHTTPService(cfg.HTTP.Namespace+"."+cfg.Service.Name, cfg.HTTP.Addr, version.GetString())
|
||||
if err := registry.RegisterService(ctx, logger, httpSvc, cfg.Debug.Addr); err != nil {
|
||||
logger.Fatal().Err(err).Msg("failed to register the http service")
|
||||
}
|
||||
|
||||
grResults := gr.Run(ctx)
|
||||
|
||||
// return the first non-nil error found in the results
|
||||
for _, grResult := range grResults {
|
||||
if grResult.RunnerError != nil {
|
||||
return grResult.RunnerError
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qsfera/server/pkg/registry"
|
||||
"github.com/qsfera/server/pkg/version"
|
||||
"github.com/qsfera/server/services/storage-system/pkg/config"
|
||||
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"github.com/olekukonko/tablewriter/tw"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// Version prints the service versions of all running instances.
|
||||
func Version(cfg *config.Config) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "print the version of this binary and the running service instances",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
fmt.Println("Version: " + version.GetString())
|
||||
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 := tablewriter.NewTable(os.Stdout, tablewriter.WithHeaderAutoFormat(tw.Off))
|
||||
table.Header([]string{"Version", "Address", "Id"})
|
||||
for _, s := range services {
|
||||
for _, n := range s.Nodes {
|
||||
table.Append([]string{s.Version, n.Address, n.Id})
|
||||
}
|
||||
}
|
||||
table.Render()
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/qsfera/server/pkg/shared"
|
||||
)
|
||||
|
||||
// Config holds Config config
|
||||
type Config struct {
|
||||
Commons *shared.Commons `yaml:"-"` // don't use this directly as configuration for a service
|
||||
Service Service `yaml:"-"`
|
||||
LogLevel string `yaml:"loglevel" env:"OC_LOG_LEVEL;STORAGE_SYSTEM_LOG_LEVEL" desc:"The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'." introductionVersion:"1.0.0"`
|
||||
Debug Debug `yaml:"debug"`
|
||||
|
||||
GRPC GRPCConfig `yaml:"grpc"`
|
||||
HTTP HTTPConfig `yaml:"http"`
|
||||
|
||||
TokenManager *TokenManager `yaml:"token_manager"`
|
||||
Reva *shared.Reva `yaml:"reva"`
|
||||
|
||||
SystemUserID string `yaml:"system_user_id" env:"OC_SYSTEM_USER_ID" desc:"ID of the КуСфера storage-system system user. Admins need to set the ID for the STORAGE-SYSTEM system user in this config option which is then used to reference the user. Any reasonable long string is possible, preferably this would be an UUIDv4 format." introductionVersion:"1.0.0"`
|
||||
SystemUserAPIKey string `yaml:"system_user_api_key" env:"OC_SYSTEM_USER_API_KEY" desc:"API key for the STORAGE-SYSTEM system user." introductionVersion:"1.0.0"`
|
||||
|
||||
SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"STORAGE_SYSTEM_SKIP_USER_GROUPS_IN_TOKEN" desc:"Disables the loading of user's group memberships from the reva access token." introductionVersion:"1.0.0"`
|
||||
|
||||
FileMetadataCache Cache `yaml:"cache"`
|
||||
Driver string `yaml:"driver" env:"STORAGE_SYSTEM_DRIVER" desc:"The driver which should be used by the service. The only supported driver is 'decomposed'. For backwards compatibility reasons it's also possible to use the 'ocis' driver and configure it using the 'decomposed' options. " introductionVersion:"1.0.0"`
|
||||
Drivers Drivers `yaml:"drivers"`
|
||||
DataServerURL string `yaml:"data_server_url" env:"STORAGE_SYSTEM_DATA_SERVER_URL" desc:"URL of the data server, needs to be reachable by other services using this service." introductionVersion:"1.0.0"`
|
||||
|
||||
Context context.Context `yaml:"-"`
|
||||
}
|
||||
|
||||
// Service holds Service config
|
||||
type Service struct {
|
||||
Name string `yaml:"-"`
|
||||
}
|
||||
|
||||
// Debug holds Debug config
|
||||
type Debug struct {
|
||||
Addr string `yaml:"addr" env:"STORAGE_SYSTEM_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed." introductionVersion:"1.0.0"`
|
||||
Token string `yaml:"token" env:"STORAGE_SYSTEM_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint" introductionVersion:"1.0.0"`
|
||||
Pprof bool `yaml:"pprof" env:"STORAGE_SYSTEM_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling" introductionVersion:"1.0.0"`
|
||||
Zpages bool `yaml:"zpages" env:"STORAGE_SYSTEM_DEBUG_ZPAGES" desc:"Enables zpages, which can be used for collecting and viewing in-memory traces." introductionVersion:"1.0.0"`
|
||||
}
|
||||
|
||||
// GRPCConfig holds GRPCConfig config
|
||||
type GRPCConfig struct {
|
||||
Addr string `yaml:"addr" env:"STORAGE_SYSTEM_GRPC_ADDR" desc:"The bind address of the GRPC service." introductionVersion:"1.0.0"`
|
||||
TLS *shared.GRPCServiceTLS `yaml:"tls"`
|
||||
Namespace string `yaml:"-"`
|
||||
Protocol string `yaml:"protocol" env:"OC_GRPC_PROTOCOL;STORAGE_SYSTEM_GRPC_PROTOCOL" desc:"The transport protocol of the GPRC service." introductionVersion:"1.0.0"`
|
||||
}
|
||||
|
||||
// HTTPConfig holds HTTPConfig config
|
||||
type HTTPConfig struct {
|
||||
Addr string `yaml:"addr" env:"STORAGE_SYSTEM_HTTP_ADDR" desc:"The bind address of the HTTP service." introductionVersion:"1.0.0"`
|
||||
Namespace string `yaml:"-"`
|
||||
Protocol string `yaml:"protocol" env:"STORAGE_SYSTEM_HTTP_PROTOCOL" desc:"The transport protocol of the HTTP service." introductionVersion:"1.0.0"`
|
||||
}
|
||||
|
||||
// Drivers holds Drivers config
|
||||
type Drivers struct {
|
||||
Decomposed DecomposedDriver `yaml:"decomposed"`
|
||||
}
|
||||
|
||||
// DecomposedDriver holds the decomposed Driver config
|
||||
type DecomposedDriver struct {
|
||||
// Root is the absolute path to the location of the data
|
||||
Root string `yaml:"root" env:"STORAGE_SYSTEM_OC_ROOT" desc:"Path for the directory where the STORAGE-SYSTEM service stores it's persistent data. If not defined, the root directory derives from $OC_BASE_DATA_PATH/storage." introductionVersion:"1.0.0"`
|
||||
|
||||
MaxAcquireLockCycles int `yaml:"max_acquire_lock_cycles" env:"STORAGE_SYSTEM_OC_MAX_ACQUIRE_LOCK_CYCLES" desc:"When trying to lock files, КуСфера will try this amount of times to acquire the lock before failing. After each try it will wait for an increasing amount of time. Values of 0 or below will be ignored and the default value of 20 will be used." introductionVersion:"1.0.0"`
|
||||
LockCycleDurationFactor int `yaml:"lock_cycle_duration_factor" env:"STORAGE_SYSTEM_OC_LOCK_CYCLE_DURATION_FACTOR" desc:"When trying to lock files, КуСфера will multiply the cycle with this factor and use it as a millisecond timeout. Values of 0 or below will be ignored and the default value of 30 will be used." introductionVersion:"1.0.0"`
|
||||
}
|
||||
|
||||
// Cache holds cache config
|
||||
type Cache struct {
|
||||
Store string `yaml:"store" env:"OC_CACHE_STORE;STORAGE_SYSTEM_CACHE_STORE" desc:"The type of the cache store. Supported values are: 'memory', 'redis-sentinel', 'nats-js-kv', 'noop'. See the text description for details." introductionVersion:"1.0.0"`
|
||||
Nodes []string `yaml:"nodes" env:"OC_CACHE_STORE_NODES;STORAGE_SYSTEM_CACHE_STORE_NODES" desc:"A list of nodes to access the configured store. This has no effect when 'memory' store is configured. Note that the behaviour how nodes are used is dependent on the library of the configured store. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
|
||||
Database string `yaml:"database" env:"OC_CACHE_DATABASE" desc:"The database name the configured store should use." introductionVersion:"1.0.0"`
|
||||
TTL time.Duration `yaml:"ttl" env:"OC_CACHE_TTL;STORAGE_SYSTEM_CACHE_TTL" desc:"Default time to live for user info in the user info cache. Only applied when access tokens has no expiration. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
|
||||
DisablePersistence bool `yaml:"disable_persistence" env:"OC_CACHE_DISABLE_PERSISTENCE;STORAGE_SYSTEM_CACHE_DISABLE_PERSISTENCE" desc:"Disables persistence of the cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false." introductionVersion:"1.0.0"`
|
||||
AuthUsername string `yaml:"auth_username" env:"OC_CACHE_AUTH_USERNAME;STORAGE_SYSTEM_CACHE_AUTH_USERNAME" desc:"Username for the configured store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
|
||||
AuthPassword string `yaml:"auth_password" env:"OC_CACHE_AUTH_PASSWORD;STORAGE_SYSTEM_CACHE_AUTH_PASSWORD" desc:"Password for the configured store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package defaults
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/qsfera/server/pkg/config/defaults"
|
||||
"github.com/qsfera/server/pkg/shared"
|
||||
"github.com/qsfera/server/pkg/structs"
|
||||
"github.com/qsfera/server/services/storage-system/pkg/config"
|
||||
)
|
||||
|
||||
// FullDefaultConfig returns a fully initialized default configuration
|
||||
func FullDefaultConfig() *config.Config {
|
||||
cfg := DefaultConfig()
|
||||
EnsureDefaults(cfg)
|
||||
Sanitize(cfg)
|
||||
return cfg
|
||||
}
|
||||
|
||||
// DefaultConfig returns a basic default configuration
|
||||
func DefaultConfig() *config.Config {
|
||||
return &config.Config{
|
||||
Debug: config.Debug{
|
||||
Addr: "127.0.0.1:9217",
|
||||
Token: "",
|
||||
Pprof: false,
|
||||
Zpages: false,
|
||||
},
|
||||
GRPC: config.GRPCConfig{
|
||||
Addr: "127.0.0.1:9215",
|
||||
Namespace: "qsfera.api",
|
||||
Protocol: "tcp",
|
||||
},
|
||||
HTTP: config.HTTPConfig{
|
||||
Addr: "127.0.0.1:9216",
|
||||
Namespace: "qsfera.web",
|
||||
Protocol: "tcp",
|
||||
},
|
||||
Service: config.Service{
|
||||
Name: "storage-system",
|
||||
},
|
||||
Reva: shared.DefaultRevaConfig(),
|
||||
DataServerURL: "http://localhost:9216/data",
|
||||
Driver: "decomposed",
|
||||
Drivers: config.Drivers{
|
||||
Decomposed: config.DecomposedDriver{
|
||||
Root: filepath.Join(defaults.BaseDataPath(), "storage", "metadata"),
|
||||
MaxAcquireLockCycles: 20,
|
||||
LockCycleDurationFactor: 30,
|
||||
},
|
||||
},
|
||||
FileMetadataCache: config.Cache{
|
||||
Store: "memory",
|
||||
Nodes: []string{"127.0.0.1:9233"},
|
||||
Database: "storage-system",
|
||||
TTL: 24 * time.Hour,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// EnsureDefaults adds default values to the configuration if they are not set yet
|
||||
func EnsureDefaults(cfg *config.Config) {
|
||||
if cfg.LogLevel == "" {
|
||||
cfg.LogLevel = "error"
|
||||
}
|
||||
if cfg.Reva == nil && cfg.Commons != nil {
|
||||
cfg.Reva = structs.CopyOrZeroValue(cfg.Commons.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.SystemUserAPIKey == "" && cfg.Commons != nil && cfg.Commons.SystemUserAPIKey != "" {
|
||||
cfg.SystemUserAPIKey = cfg.Commons.SystemUserAPIKey
|
||||
}
|
||||
|
||||
if cfg.SystemUserID == "" && cfg.Commons != nil && cfg.Commons.SystemUserID != "" {
|
||||
cfg.SystemUserID = cfg.Commons.SystemUserID
|
||||
}
|
||||
|
||||
if cfg.GRPC.TLS == nil && cfg.Commons != nil {
|
||||
cfg.GRPC.TLS = structs.CopyOrZeroValue(cfg.Commons.GRPCServiceTLS)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Sanitize sanitized the configuration
|
||||
func Sanitize(cfg *config.Config) {
|
||||
// nothing to sanitize here atm
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
occfg "github.com/qsfera/server/pkg/config"
|
||||
"github.com/qsfera/server/pkg/shared"
|
||||
"github.com/qsfera/server/services/storage-system/pkg/config"
|
||||
"github.com/qsfera/server/services/storage-system/pkg/config/defaults"
|
||||
|
||||
"github.com/qsfera/server/pkg/config/envdecode"
|
||||
)
|
||||
|
||||
// ParseConfig loads configuration from known paths.
|
||||
func ParseConfig(cfg *config.Config) error {
|
||||
err := occfg.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.SystemUserAPIKey == "" {
|
||||
return shared.MissingSystemUserApiKeyError(cfg.Service.Name)
|
||||
}
|
||||
|
||||
if cfg.SystemUserID == "" {
|
||||
return shared.MissingSystemUserID(cfg.Service.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package config
|
||||
|
||||
// TokenManager is the config for using the reva token manager
|
||||
type TokenManager struct {
|
||||
JWTSecret string `yaml:"jwt_secret" env:"OC_JWT_SECRET;STORAGE_SYSTEM_JWT_SECRET" desc:"The secret to mint and validate jwt tokens." introductionVersion:"1.0.0"`
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package revaconfig
|
||||
|
||||
import (
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
pkgconfig "github.com/qsfera/server/pkg/config"
|
||||
"github.com/qsfera/server/services/storage-system/pkg/config"
|
||||
)
|
||||
|
||||
// StorageSystemFromStruct will adapt a КуСфера config struct into a reva mapstructure to start a reva service.
|
||||
func StorageSystemFromStruct(cfg *config.Config) map[string]any {
|
||||
localEndpoint := pkgconfig.LocalEndpoint(cfg.GRPC.Protocol, cfg.GRPC.Addr)
|
||||
|
||||
rcfg := map[string]any{
|
||||
"shared": map[string]any{
|
||||
"jwt_secret": cfg.TokenManager.JWTSecret,
|
||||
"gatewaysvc": cfg.Reva.Address,
|
||||
"skip_user_groups_in_token": cfg.SkipUserGroupsInToken,
|
||||
"grpc_client_options": cfg.Reva.GetGRPCClientConfig(),
|
||||
"multi_tenant_enabled": cfg.Commons.MultiTenantEnabled,
|
||||
},
|
||||
"grpc": map[string]any{
|
||||
"network": cfg.GRPC.Protocol,
|
||||
"address": cfg.GRPC.Addr,
|
||||
"tls_settings": map[string]any{
|
||||
"enabled": cfg.GRPC.TLS.Enabled,
|
||||
"certificate": cfg.GRPC.TLS.Cert,
|
||||
"key": cfg.GRPC.TLS.Key,
|
||||
},
|
||||
"services": map[string]any{
|
||||
"gateway": map[string]any{
|
||||
// registries are located on the gateway
|
||||
"authregistrysvc": localEndpoint,
|
||||
"storageregistrysvc": localEndpoint,
|
||||
// user metadata is located on the users services
|
||||
"userprovidersvc": localEndpoint,
|
||||
"groupprovidersvc": localEndpoint,
|
||||
"permissionssvc": localEndpoint,
|
||||
// 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
|
||||
"cache_store": "noop",
|
||||
"cache_database": "system",
|
||||
},
|
||||
"userprovider": map[string]any{
|
||||
"driver": "memory",
|
||||
"drivers": map[string]any{
|
||||
"memory": map[string]any{
|
||||
"users": map[string]any{
|
||||
"serviceuser": map[string]any{
|
||||
"id": map[string]any{
|
||||
"opaqueId": cfg.SystemUserID,
|
||||
"idp": "internal",
|
||||
"type": userpb.UserType_USER_TYPE_SERVICE,
|
||||
},
|
||||
"username": "serviceuser",
|
||||
"display_name": "System User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"authregistry": map[string]any{
|
||||
"driver": "static",
|
||||
"drivers": map[string]any{
|
||||
"static": map[string]any{
|
||||
"rules": map[string]any{
|
||||
"machine": localEndpoint,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"authprovider": map[string]any{
|
||||
"auth_manager": "machine",
|
||||
"auth_managers": map[string]any{
|
||||
"machine": map[string]any{
|
||||
"api_key": cfg.SystemUserAPIKey,
|
||||
"gateway_addr": localEndpoint,
|
||||
},
|
||||
},
|
||||
},
|
||||
"permissions": map[string]any{
|
||||
"driver": "demo",
|
||||
"drivers": map[string]any{
|
||||
"demo": map[string]any{},
|
||||
},
|
||||
},
|
||||
"storageregistry": map[string]any{
|
||||
"driver": "static",
|
||||
"drivers": map[string]any{
|
||||
"static": map[string]any{
|
||||
"rules": map[string]any{
|
||||
"/": map[string]any{
|
||||
"address": localEndpoint,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"storageprovider": map[string]any{
|
||||
"driver": cfg.Driver,
|
||||
"drivers": metadataDrivers(localEndpoint, cfg),
|
||||
"data_server_url": cfg.DataServerURL,
|
||||
},
|
||||
},
|
||||
"interceptors": map[string]any{
|
||||
"prometheus": map[string]any{
|
||||
"namespace": "qsfera",
|
||||
"subsystem": "storage_system",
|
||||
},
|
||||
},
|
||||
},
|
||||
"http": map[string]any{
|
||||
"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]any{
|
||||
"dataprovider": map[string]any{
|
||||
"prefix": "data",
|
||||
"driver": cfg.Driver,
|
||||
"drivers": metadataDrivers(localEndpoint, cfg),
|
||||
"data_txs": map[string]any{
|
||||
"simple": map[string]any{
|
||||
"cache_store": "noop",
|
||||
"cache_database": "system",
|
||||
"cache_table": "stat",
|
||||
},
|
||||
"spaces": map[string]any{
|
||||
"cache_store": "noop",
|
||||
"cache_database": "system",
|
||||
"cache_table": "stat",
|
||||
},
|
||||
"tus": map[string]any{
|
||||
"cache_store": "noop",
|
||||
"cache_database": "system",
|
||||
"cache_table": "stat",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"middlewares": map[string]any{
|
||||
"prometheus": map[string]any{
|
||||
"namespace": "qsfera",
|
||||
"subsystem": "storage_system",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return rcfg
|
||||
}
|
||||
|
||||
func metadataDrivers(localEndpoint string, cfg *config.Config) map[string]any {
|
||||
m := map[string]any{
|
||||
"metadata_backend": "messagepack",
|
||||
"root": cfg.Drivers.Decomposed.Root,
|
||||
"user_layout": "{{.Id.OpaqueId}}",
|
||||
"treetime_accounting": false,
|
||||
"treesize_accounting": false,
|
||||
"permissionssvc": localEndpoint,
|
||||
"max_acquire_lock_cycles": cfg.Drivers.Decomposed.MaxAcquireLockCycles,
|
||||
"lock_cycle_duration_factor": cfg.Drivers.Decomposed.LockCycleDurationFactor,
|
||||
"multi_tenant_enabled": false, // storage-system doesn't use tenants, even if it's enabled for storage-users
|
||||
"disable_versioning": true,
|
||||
"statcache": map[string]any{
|
||||
"cache_store": "noop",
|
||||
"cache_database": "system",
|
||||
},
|
||||
"filemetadatacache": map[string]any{
|
||||
"cache_store": cfg.FileMetadataCache.Store,
|
||||
"cache_nodes": cfg.FileMetadataCache.Nodes,
|
||||
"cache_database": cfg.FileMetadataCache.Database,
|
||||
"cache_ttl": cfg.FileMetadataCache.TTL,
|
||||
"cache_disable_persistence": cfg.FileMetadataCache.DisablePersistence,
|
||||
"cache_auth_username": cfg.FileMetadataCache.AuthUsername,
|
||||
"cache_auth_password": cfg.FileMetadataCache.AuthPassword,
|
||||
},
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"ocis": m, // deprecated: use decomposed
|
||||
"decomposed": m,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package debug
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/qsfera/server/pkg/log"
|
||||
"github.com/qsfera/server/services/storage-system/pkg/config"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package debug
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/qsfera/server/pkg/service/debug"
|
||||
"github.com/qsfera/server/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.GetString()),
|
||||
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.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
|
||||
}
|
||||
Reference in New Issue
Block a user