diff --git a/accounts/pkg/command/add_account.go b/accounts/pkg/command/add_account.go index 4003c46ee..1e904c5b8 100644 --- a/accounts/pkg/command/add_account.go +++ b/accounts/pkg/command/add_account.go @@ -16,10 +16,11 @@ func AddAccount(cfg *config.Config) *cli.Command { PasswordProfile: &accounts.PasswordProfile{}, } return &cli.Command{ - Name: "add", - Usage: "Create a new account", - Aliases: []string{"create", "a"}, - Flags: flagset.AddAccountWithConfig(cfg, a), + Name: "add", + Usage: "create a new account", + Category: "account management", + Aliases: []string{"create", "a"}, + Flags: flagset.AddAccountWithConfig(cfg, a), Before: func(c *cli.Context) error { // Write value of username to the flags beneath, as preferred name // and on-premises-sam-account-name is probably confusing for users. @@ -41,7 +42,7 @@ func AddAccount(cfg *config.Config) *cli.Command { }, Action: func(c *cli.Context) error { - accSvcID := cfg.GRPC.Namespace + "." + cfg.Server.Name + accSvcID := cfg.GRPC.Namespace + "." + cfg.Service.Name accSvc := accounts.NewAccountsService(accSvcID, grpc.NewClient()) _, err := accSvc.CreateAccount(c.Context, &accounts.CreateAccountRequest{ Account: a, diff --git a/accounts/pkg/command/health.go b/accounts/pkg/command/health.go new file mode 100644 index 000000000..bba020b88 --- /dev/null +++ b/accounts/pkg/command/health.go @@ -0,0 +1,53 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/accounts/pkg/config" + "github.com/owncloud/ocis/accounts/pkg/config/parser" + "github.com/owncloud/ocis/accounts/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 { + return parser.ParseConfig(cfg) + }, + 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/accounts/pkg/command/inspect_account.go b/accounts/pkg/command/inspect_account.go index 1ea262c31..5e2009497 100644 --- a/accounts/pkg/command/inspect_account.go +++ b/accounts/pkg/command/inspect_account.go @@ -18,11 +18,12 @@ import ( func InspectAccount(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "inspect", - Usage: "Show detailed data on an existing account", + Usage: "show detailed data on an existing account", + Category: "account management", ArgsUsage: "id", Flags: flagset.InspectAccountWithConfig(cfg), Action: func(c *cli.Context) error { - accServiceID := cfg.GRPC.Namespace + "." + cfg.Server.Name + accServiceID := cfg.GRPC.Namespace + "." + cfg.Service.Name if c.NArg() != 1 { fmt.Println("Please provide a user-id") os.Exit(1) diff --git a/accounts/pkg/command/list_accounts.go b/accounts/pkg/command/list_accounts.go index 87727b576..3c15a1275 100644 --- a/accounts/pkg/command/list_accounts.go +++ b/accounts/pkg/command/list_accounts.go @@ -17,12 +17,13 @@ import ( // ListAccounts command lists all accounts func ListAccounts(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "list", - Usage: "List existing accounts", - Aliases: []string{"ls"}, - Flags: flagset.ListAccountsWithConfig(cfg), + Name: "list", + Usage: "list existing accounts", + Category: "account management", + Aliases: []string{"ls"}, + Flags: flagset.ListAccountsWithConfig(cfg), Action: func(c *cli.Context) error { - accSvcID := cfg.GRPC.Namespace + "." + cfg.Server.Name + accSvcID := cfg.GRPC.Namespace + "." + cfg.Service.Name accSvc := accounts.NewAccountsService(accSvcID, grpc.NewClient()) resp, err := accSvc.ListAccounts(c.Context, &accounts.ListAccountsRequest{}) diff --git a/accounts/pkg/command/rebuild_index.go b/accounts/pkg/command/rebuild_index.go index b643ec04e..d2eb1d2de 100644 --- a/accounts/pkg/command/rebuild_index.go +++ b/accounts/pkg/command/rebuild_index.go @@ -14,9 +14,10 @@ import ( // RebuildIndex rebuilds the entire configured index. func RebuildIndex(cdf *config.Config) *cli.Command { return &cli.Command{ - Name: "rebuildIndex", - Usage: "Rebuilds the service's index, i.e. deleting and then re-adding all existing documents", - Aliases: []string{"rebuild", "ri"}, + Name: "rebuildIndex", + Usage: "rebuilds the service's index, i.e. deleting and then re-adding all existing documents", + Category: "account management", + Aliases: []string{"rebuild", "ri"}, Action: func(ctx *cli.Context) error { idxSvcID := "com.owncloud.api.accounts" idxSvc := index.NewIndexService(idxSvcID, grpc.NewClient()) diff --git a/accounts/pkg/command/remove_account.go b/accounts/pkg/command/remove_account.go index dfb723db9..ee18eede4 100644 --- a/accounts/pkg/command/remove_account.go +++ b/accounts/pkg/command/remove_account.go @@ -16,12 +16,13 @@ import ( func RemoveAccount(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "remove", - Usage: "Removes an existing account", + Usage: "removes an existing account", + Category: "account management", ArgsUsage: "id", Aliases: []string{"rm"}, Flags: flagset.RemoveAccountWithConfig(cfg), Action: func(c *cli.Context) error { - accServiceID := cfg.GRPC.Namespace + "." + cfg.Server.Name + accServiceID := cfg.GRPC.Namespace + "." + cfg.Service.Name if c.NArg() != 1 { fmt.Println("Please provide a user-id") os.Exit(1) diff --git a/accounts/pkg/command/root.go b/accounts/pkg/command/root.go index 19eae93b2..fb0bf2883 100644 --- a/accounts/pkg/command/root.go +++ b/accounts/pkg/command/root.go @@ -4,84 +4,49 @@ import ( "context" "os" - "github.com/owncloud/ocis/ocis-pkg/shared" - "github.com/owncloud/ocis/accounts/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/version" "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 + AddAccount(cfg), + UpdateAccount(cfg), + ListAccounts(cfg), + InspectAccount(cfg), + RemoveAccount(cfg), + RebuildIndex(cfg), + + // 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 := &cli.App{ + app := clihelper.DefaultApp(&cli.App{ Name: "ocis-accounts", - Version: version.String, Usage: "Provide accounts and groups for oCIS", - Compiled: version.Compiled(), - Authors: []*cli.Author{ - { - Name: "ownCloud GmbH", - Email: "support@owncloud.com", - }, - }, - Before: func(c *cli.Context) error { - cfg.Server.Version = version.String - return ParseConfig(c, cfg) - }, - - Commands: []*cli.Command{ - Server(cfg), - AddAccount(cfg), - UpdateAccount(cfg), - ListAccounts(cfg), - InspectAccount(cfg), - RemoveAccount(cfg), - PrintVersion(cfg), - RebuildIndex(cfg), - }, - } + Commands: GetCommands(cfg), + }) cli.HelpFlag = &cli.BoolFlag{ Name: "help,h", Usage: "Show the help", } - cli.VersionFlag = &cli.BoolFlag{ - Name: "version,v", - Usage: "Print the version", - } - return app.Run(os.Args) } -// ParseConfig loads accounts configuration from known paths. -func ParseConfig(c *cli.Context, cfg *config.Config) error { - conf, err := ociscfg.BindSourcesToStructs("accounts", cfg) - if err != nil { - return err - } - - // 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 = &shared.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.Commons == nil { - cfg.Log = &shared.Log{} - } - - // load all env variables relevant to the config in the current context. - conf.LoadOSEnv(config.GetEnv(cfg), false) - - bindings := config.StructMappings(cfg) - return ociscfg.BindEnv(conf, bindings) -} - // SutureService allows for the accounts command to be embedded and supervised by a suture supervisor tree. type SutureService struct { cfg *config.Config diff --git a/accounts/pkg/command/server.go b/accounts/pkg/command/server.go index e4e6f92e6..52fd784ee 100644 --- a/accounts/pkg/command/server.go +++ b/accounts/pkg/command/server.go @@ -2,41 +2,33 @@ package command import ( "context" - "strings" - - "github.com/owncloud/ocis/ocis-pkg/log" + "fmt" "github.com/oklog/run" "github.com/owncloud/ocis/accounts/pkg/config" + "github.com/owncloud/ocis/accounts/pkg/config/parser" + "github.com/owncloud/ocis/accounts/pkg/logging" "github.com/owncloud/ocis/accounts/pkg/metrics" + "github.com/owncloud/ocis/accounts/pkg/server/debug" "github.com/owncloud/ocis/accounts/pkg/server/grpc" "github.com/owncloud/ocis/accounts/pkg/server/http" svc "github.com/owncloud/ocis/accounts/pkg/service/v0" "github.com/owncloud/ocis/accounts/pkg/tracing" + "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: "Start ocis accounts service", - Description: "uses an LDAP server as the storage backend", - Before: func(ctx *cli.Context) error { - if cfg.HTTP.Root != "/" { - cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") - } - - cfg.Repo.Backend = strings.ToLower(cfg.Repo.Backend) - - if err := ParseConfig(ctx, cfg); err != nil { - return err - } - - return nil + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + return parser.ParseConfig(cfg) }, Action: func(c *cli.Context) error { - logger := log.LoggerFromConfig("accounts", *cfg.Log) + logger := logging.Configure(cfg.Service.Name, cfg.Log) err := tracing.Configure(cfg) if err != nil { return err @@ -47,7 +39,7 @@ func Server(cfg *config.Config) *cli.Command { defer cancel() - mtrcs.BuildInfo.WithLabelValues(cfg.Server.Version).Set(1) + mtrcs.BuildInfo.WithLabelValues(version.String).Set(1) handler, err := svc.New(svc.Logger(logger), svc.Config(cfg)) if err != nil { @@ -58,7 +50,7 @@ func Server(cfg *config.Config) *cli.Command { httpServer := http.Server( http.Config(cfg), http.Logger(logger), - http.Name(cfg.Server.Name), + http.Name(cfg.Service.Name), http.Context(ctx), http.Metrics(mtrcs), http.Handler(handler), @@ -72,7 +64,7 @@ func Server(cfg *config.Config) *cli.Command { grpcServer := grpc.Server( grpc.Config(cfg), grpc.Logger(logger), - grpc.Name(cfg.Server.Name), + grpc.Name(cfg.Service.Name), grpc.Context(ctx), grpc.Metrics(mtrcs), grpc.Handler(handler), @@ -83,6 +75,18 @@ func Server(cfg *config.Config) *cli.Command { cancel() }) + // prepare a debug server and add it to the group run. + debugServer, err := debug.Server(debug.Logger(logger), debug.Context(ctx), debug.Config(cfg)) + if err != nil { + logger.Error().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + _ = debugServer.Shutdown(ctx) + cancel() + }) + return gr.Run() }, } diff --git a/accounts/pkg/command/update_account.go b/accounts/pkg/command/update_account.go index 71f8c2c66..cf2e68950 100644 --- a/accounts/pkg/command/update_account.go +++ b/accounts/pkg/command/update_account.go @@ -21,6 +21,7 @@ func UpdateAccount(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "update", Usage: "Make changes to an existing account", + Category: "account management", ArgsUsage: "id", Flags: flagset.UpdateAccountWithConfig(cfg, a), Before: func(c *cli.Context) error { @@ -40,7 +41,7 @@ func UpdateAccount(cfg *config.Config) *cli.Command { }, Action: func(c *cli.Context) error { a.Id = c.Args().First() - accSvcID := cfg.GRPC.Namespace + "." + cfg.Server.Name + accSvcID := cfg.GRPC.Namespace + "." + cfg.Service.Name accSvc := accounts.NewAccountsService(accSvcID, grpc.NewClient()) _, err := accSvc.UpdateAccount(c.Context, &accounts.UpdateAccountRequest{ Account: a, diff --git a/accounts/pkg/command/version.go b/accounts/pkg/command/version.go index e61530d16..a77668937 100644 --- a/accounts/pkg/command/version.go +++ b/accounts/pkg/command/version.go @@ -5,27 +5,33 @@ import ( "os" "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" tw "github.com/olekukonko/tablewriter" "github.com/owncloud/ocis/accounts/pkg/config" "github.com/urfave/cli/v2" ) -// PrintVersion prints the service versions of all running instances. -func PrintVersion(cfg *config.Config) *cli.Command { +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "version", - Usage: "Print the versions of the running instances", + 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.Server.Name) + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) if err != nil { - fmt.Println(fmt.Errorf("could not get accounts services from the registry: %v", err)) + 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 accounts service found.") + fmt.Println("No running " + cfg.Service.Name + " service found.") return nil } diff --git a/accounts/pkg/config/config.go b/accounts/pkg/config/config.go index 21bcd3dc2..e21073616 100644 --- a/accounts/pkg/config/config.go +++ b/accounts/pkg/config/config.go @@ -1,221 +1,86 @@ -// Package config should be moved to internal package config import ( "context" - "path" "github.com/owncloud/ocis/ocis-pkg/shared" - - "github.com/owncloud/ocis/ocis-pkg/config/defaults" ) -// LDAP defines the available ldap configuration. -type LDAP struct { - Hostname string `ocisConfig:"hostname"` - Port int `ocisConfig:"port"` - BaseDN string `ocisConfig:"base_dn"` - UserFilter string `ocisConfig:"user_filter"` - GroupFilter string `ocisConfig:"group_filter"` - BindDN string `ocisConfig:"bind_dn"` - BindPassword string `ocisConfig:"bind_password"` - IDP string `ocisConfig:"idp"` - Schema LDAPSchema `ocisConfig:"schema"` -} +// Config combines all available configuration parts. +type Config struct { + *shared.Commons -// LDAPSchema defines the available ldap schema configuration. -type LDAPSchema struct { - AccountID string `ocisConfig:"account_id"` - Identities string `ocisConfig:"identities"` - Username string `ocisConfig:"username"` - DisplayName string `ocisConfig:"display_name"` - Mail string `ocisConfig:"mail"` - Groups string `ocisConfig:"groups"` -} + Service Service -// CORS defines the available cors configuration. -type CORS struct { - AllowedOrigins []string `ocisConfig:"allowed_origins"` - AllowedMethods []string `ocisConfig:"allowed_methods"` - AllowedHeaders []string `ocisConfig:"allowed_headers"` - AllowCredentials bool `ocisConfig:"allowed_credentials"` -} + Tracing Tracing `ocisConfig:"tracing"` + Log *Log `ocisConfig:"log"` + Debug Debug `ocisConfig:"debug"` -// HTTP defines the available http configuration. -type HTTP struct { - Addr string `ocisConfig:"addr"` - Namespace string `ocisConfig:"namespace"` - Root string `ocisConfig:"root"` - CacheTTL int `ocisConfig:"cache_ttl"` - CORS CORS `ocisConfig:"cors"` -} + HTTP HTTP `ocisConfig:"http"` + GRPC GRPC `ocisConfig:"grpc"` -// GRPC defines the available grpc configuration. -type GRPC struct { - Addr string `ocisConfig:"addr"` - Namespace string `ocisConfig:"namespace"` -} + TokenManager TokenManager `ocisConfig:"token_manager"` -// Server configures a server. -type Server struct { - Version string `ocisConfig:"version"` - Name string `ocisConfig:"name"` - HashDifficulty int `ocisConfig:"hash_difficulty"` - DemoUsersAndGroups bool `ocisConfig:"demo_users_and_groups"` + Asset Asset `ocisConfig:"asset"` + Repo Repo `ocisConfig:"repo"` + Index Index `ocisConfig:"index"` + ServiceUser ServiceUser `ocisConfig:"service_user"` + HashDifficulty int `ocisConfig:"hash_difficulty" env:"ACCOUNTS_HASH_DIFFICULTY"` + DemoUsersAndGroups bool `ocisConfig:"demo_users_and_groups" env:"ACCOUNTS_DEMO_USERS_AND_GROUPS"` + + Context context.Context } // Asset defines the available asset configuration. type Asset struct { - Path string `ocisConfig:"path"` + Path string `ocisConfig:"path" env:"ACCOUNTS_ASSET_PATH"` } // TokenManager is the config for using the reva token manager type TokenManager struct { - JWTSecret string `ocisConfig:"jwt_secret"` + JWTSecret string `ocisConfig:"jwt_secret" env:"OCIS_JWT_SECRET;ACCOUNTS_JWT_SECRET"` } // Repo defines which storage implementation is to be used. type Repo struct { - Backend string `ocisConfig:"backend"` + Backend string `ocisConfig:"backend" env:"ACCOUNTS_STORAGE_BACKEND"` Disk Disk `ocisConfig:"disk"` CS3 CS3 `ocisConfig:"cs3"` } // Disk is the local disk implementation of the storage. type Disk struct { - Path string `ocisConfig:"path"` + Path string `ocisConfig:"path" env:"ACCOUNTS_STORAGE_DISK_PATH"` } // CS3 is the cs3 implementation of the storage. type CS3 struct { - ProviderAddr string `ocisConfig:"provider_addr"` - JWTSecret string `ocisConfig:"jwt_secret"` + ProviderAddr string `ocisConfig:"provider_addr" env:"ACCOUNTS_STORAGE_CS3_PROVIDER_ADDR"` + JWTSecret string `ocisConfig:"jwt_secret" env:"ACCOUNTS_STORAGE_CS3_JWT_SECRET"` } // ServiceUser defines the user required for EOS. type ServiceUser struct { - UUID string `ocisConfig:"uuid"` - Username string `ocisConfig:"username"` - UID int64 `ocisConfig:"uid"` - GID int64 `ocisConfig:"gid"` + UUID string `ocisConfig:"uuid" env:"ACCOUNTS_SERVICE_USER_UUID"` + Username string `ocisConfig:"username" env:"ACCOUNTS_SERVICE_USER_USERNAME"` + UID int64 `ocisConfig:"uid" env:"ACCOUNTS_SERVICE_USER_UID"` + GID int64 `ocisConfig:"gid" env:"ACCOUNTS_SERVICE_USER_GID"` } // Index defines config for indexes. type Index struct { - UID Bound `ocisConfig:"uid"` - GID Bound `ocisConfig:"gid"` + UID UIDBound `ocisConfig:"uid"` + GID GIDBound `ocisConfig:"gid"` } -// Bound defines a lower and upper bound. -type Bound struct { - Lower int64 `ocisConfig:"lower"` - Upper int64 `ocisConfig:"upper"` +// GIDBound defines a lower and upper bound. +type GIDBound struct { + Lower int64 `ocisConfig:"lower" env:"ACCOUNTS_GID_INDEX_LOWER_BOUND"` + Upper int64 `ocisConfig:"upper" env:"ACCOUNTS_GID_INDEX_UPPER_BOUND"` } -// Tracing defines the available tracing configuration. -type Tracing struct { - Enabled bool `ocisConfig:"enabled"` - Type string `ocisConfig:"type"` - Endpoint string `ocisConfig:"endpoint"` - Collector string `ocisConfig:"collector"` - Service string `ocisConfig:"service"` -} - -// Config merges all Account config parameters. -type Config struct { - *shared.Commons - - LDAP LDAP `ocisConfig:"ldap"` - HTTP HTTP `ocisConfig:"http"` - GRPC GRPC `ocisConfig:"grpc"` - Server Server `ocisConfig:"server"` - Asset Asset `ocisConfig:"asset"` - Log *shared.Log `ocisConfig:"log"` - TokenManager TokenManager `ocisConfig:"token_manager"` - Repo Repo `ocisConfig:"repo"` - Index Index `ocisConfig:"index"` - ServiceUser ServiceUser `ocisConfig:"service_user"` - Tracing Tracing `ocisConfig:"tracing"` - - Context context.Context - Supervised bool -} - -// New returns a new config. -func New() *Config { - return &Config{ - Log: &shared.Log{}, - } -} - -func DefaultConfig() *Config { - return &Config{ - LDAP: LDAP{}, - HTTP: HTTP{ - Addr: "127.0.0.1:9181", - Namespace: "com.owncloud.web", - Root: "/", - CacheTTL: 604800, // 7 days - CORS: CORS{ - AllowedOrigins: []string{"*"}, - AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, - AllowedHeaders: []string{"Authorization", "Origin", "Content-Type", "Accept", "X-Requested-With"}, - AllowCredentials: true, - }, - }, - GRPC: GRPC{ - Addr: "127.0.0.1:9180", - Namespace: "com.owncloud.api", - }, - Server: Server{ - Name: "accounts", - HashDifficulty: 11, - DemoUsersAndGroups: true, - }, - Asset: Asset{}, - TokenManager: TokenManager{ - JWTSecret: "Pive-Fumkiu4", - }, - Repo: Repo{ - Backend: "CS3", - Disk: Disk{ - Path: path.Join(defaults.BaseDataPath(), "accounts"), - }, - CS3: CS3{ - ProviderAddr: "localhost:9215", - JWTSecret: "Pive-Fumkiu4", - }, - }, - Index: Index{ - UID: Bound{ - Lower: 0, - Upper: 1000, - }, - GID: Bound{ - Lower: 0, - Upper: 1000, - }, - }, - ServiceUser: ServiceUser{ - UUID: "95cb8724-03b2-11eb-a0a6-c33ef8ef53ad", - Username: "", - UID: 0, - GID: 0, - }, - Tracing: Tracing{ - Type: "jaeger", - Service: "accounts", - }, - } -} - -// GetEnv fetches a list of known env variables for this extension. It is to be used by gookit, as it provides a list -// with all the environment variables an extension supports. -func GetEnv(cfg *Config) []string { - var r = make([]string, len(structMappings(cfg))) - for i := range structMappings(cfg) { - r = append(r, structMappings(cfg)[i].EnvVars...) - } - - return r +// UIDBound defines a lower and upper bound. +type UIDBound struct { + Lower int64 `ocisConfig:"lower" env:"ACCOUNTS_UID_INDEX_LOWER_BOUND"` + Upper int64 `ocisConfig:"upper" env:"ACCOUNTS_UID_INDEX_UPPER_BOUND"` } diff --git a/accounts/pkg/config/debug.go b/accounts/pkg/config/debug.go new file mode 100644 index 000000000..539b8faba --- /dev/null +++ b/accounts/pkg/config/debug.go @@ -0,0 +1,9 @@ +package config + +// Debug defines the available debug configuration. +type Debug struct { + Addr string `ocisConfig:"addr" env:"ACCOUNTS_DEBUG_ADDR"` + Token string `ocisConfig:"token" env:"ACCOUNTS_DEBUG_TOKEN"` + Pprof bool `ocisConfig:"pprof" env:"ACCOUNTS_DEBUG_PPROF"` + Zpages bool `ocisConfig:"zpages" env:"ACCOUNTS_DEBUG_ZPAGES"` +} diff --git a/accounts/pkg/config/defaultconfig.go b/accounts/pkg/config/defaultconfig.go new file mode 100644 index 000000000..2c69305bb --- /dev/null +++ b/accounts/pkg/config/defaultconfig.go @@ -0,0 +1,69 @@ +package config + +import ( + "path" + + "github.com/owncloud/ocis/ocis-pkg/config/defaults" +) + +func DefaultConfig() *Config { + return &Config{ + Debug: Debug{ + Addr: "127.0.0.1:9182", + Token: "", + Pprof: false, + Zpages: false, + }, + HTTP: HTTP{ + Addr: "127.0.0.1:9181", + Namespace: "com.owncloud.web", + Root: "/", + CacheTTL: 604800, // 7 days + CORS: CORS{ + AllowedOrigins: []string{"*"}, + AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, + AllowedHeaders: []string{"Authorization", "Origin", "Content-Type", "Accept", "X-Requested-With"}, + AllowCredentials: true, + }, + }, + GRPC: GRPC{ + Addr: "127.0.0.1:9180", + Namespace: "com.owncloud.api", + }, + Service: Service{ + Name: "accounts", + }, + Asset: Asset{}, + TokenManager: TokenManager{ + JWTSecret: "Pive-Fumkiu4", + }, + HashDifficulty: 11, + DemoUsersAndGroups: true, + Repo: Repo{ + Backend: "CS3", + Disk: Disk{ + Path: path.Join(defaults.BaseDataPath(), "accounts"), + }, + CS3: CS3{ + ProviderAddr: "localhost:9215", + JWTSecret: "Pive-Fumkiu4", + }, + }, + Index: Index{ + UID: UIDBound{ + Lower: 0, + Upper: 1000, + }, + GID: GIDBound{ + Lower: 0, + Upper: 1000, + }, + }, + ServiceUser: ServiceUser{ + UUID: "95cb8724-03b2-11eb-a0a6-c33ef8ef53ad", + Username: "", + UID: 0, + GID: 0, + }, + } +} diff --git a/accounts/pkg/config/grpc.go b/accounts/pkg/config/grpc.go new file mode 100644 index 000000000..f16de42f2 --- /dev/null +++ b/accounts/pkg/config/grpc.go @@ -0,0 +1,7 @@ +package config + +// GRPC defines the available grpc configuration. +type GRPC struct { + Addr string `ocisConfig:"addr" env:"ACCOUNTS_GRPC_ADDR"` + Namespace string +} diff --git a/accounts/pkg/config/http.go b/accounts/pkg/config/http.go new file mode 100644 index 000000000..c8c7ab628 --- /dev/null +++ b/accounts/pkg/config/http.go @@ -0,0 +1,18 @@ +package config + +// HTTP defines the available http configuration. +type HTTP struct { + Addr string `ocisConfig:"addr" env:"ACCOUNTS_HTTP_ADDR"` + Namespace string + Root string `ocisConfig:"root" env:"ACCOUNTS_HTTP_ROOT"` + CacheTTL int `ocisConfig:"cache_ttl" env:"ACCOUNTS_CACHE_TTL"` + CORS CORS `ocisConfig:"cors"` +} + +// CORS defines the available cors configuration. +type CORS struct { + AllowedOrigins []string `ocisConfig:"allowed_origins"` + AllowedMethods []string `ocisConfig:"allowed_methods"` + AllowedHeaders []string `ocisConfig:"allowed_headers"` + AllowCredentials bool `ocisConfig:"allowed_credentials"` +} diff --git a/accounts/pkg/config/log.go b/accounts/pkg/config/log.go new file mode 100644 index 000000000..6ada8a7dd --- /dev/null +++ b/accounts/pkg/config/log.go @@ -0,0 +1,9 @@ +package config + +// Log defines the available log configuration. +type Log struct { + Level string `mapstructure:"level" env:"OCIS_LOG_LEVEL;ACCOUNTS_LOG_LEVEL"` + Pretty bool `mapstructure:"pretty" env:"OCIS_LOG_PRETTY;ACCOUNTS_LOG_PRETTY"` + Color bool `mapstructure:"color" env:"OCIS_LOG_COLOR;ACCOUNTS_LOG_COLOR"` + File string `mapstructure:"file" env:"OCIS_LOG_FILE;ACCOUNTS_LOG_FILE"` +} diff --git a/accounts/pkg/config/mappings.go b/accounts/pkg/config/mappings.go deleted file mode 100644 index 00b13818b..000000000 --- a/accounts/pkg/config/mappings.go +++ /dev/null @@ -1,140 +0,0 @@ -package config - -import "github.com/owncloud/ocis/ocis-pkg/shared" - -// StructMappings binds a set of environment variables to a destination on cfg. Iterating over this set and editing the -// Destination value of a binding will alter the original value, as it is a pointer to its memory address. This lets -// us propagate changes easier. -func StructMappings(cfg *Config) []shared.EnvBinding { - return structMappings(cfg) -} - -// structMappings binds a set of environment variables to a destination on cfg. -func structMappings(cfg *Config) []shared.EnvBinding { - return []shared.EnvBinding{ - { - EnvVars: []string{"OCIS_LOG_FILE", "ACCOUNTS_LOG_FILE"}, - Destination: &cfg.Log.File, - }, - { - EnvVars: []string{"OCIS_LOG_COLOR", "ACCOUNTS_LOG_COLOR"}, - Destination: &cfg.Log.Color, - }, - { - EnvVars: []string{"OCIS_LOG_PRETTY", "ACCOUNTS_LOG_PRETTY"}, - Destination: &cfg.Log.Pretty, - }, - { - EnvVars: []string{"OCIS_TRACING_ENABLED", "ACCOUNTS_TRACING_ENABLED"}, - Destination: &cfg.Tracing.Enabled, - }, - { - EnvVars: []string{"OCIS_TRACING_TYPE", "ACCOUNTS_TRACING_TYPE"}, - Destination: &cfg.Tracing.Type, - }, - { - EnvVars: []string{"OCIS_TRACING_ENDPOINT", "ACCOUNTS_TRACING_ENDPOINT"}, - Destination: &cfg.Tracing.Endpoint, - }, - { - EnvVars: []string{"OCIS_TRACING_COLLECTOR", "ACCOUNTS_TRACING_COLLECTOR"}, - Destination: &cfg.Tracing.Collector, - }, - { - EnvVars: []string{"ACCOUNTS_TRACING_SERVICE"}, - Destination: &cfg.Tracing.Service, - }, - { - EnvVars: []string{"ACCOUNTS_HTTP_NAMESPACE"}, - Destination: &cfg.HTTP.Namespace, - }, - { - EnvVars: []string{"ACCOUNTS_HTTP_ADDR"}, - Destination: &cfg.HTTP.Addr, - }, - { - EnvVars: []string{"ACCOUNTS_HTTP_ROOT"}, - Destination: &cfg.HTTP.Root, - }, - { - EnvVars: []string{"ACCOUNTS_CACHE_TTL"}, - Destination: &cfg.HTTP.CacheTTL, - }, - { - EnvVars: []string{"ACCOUNTS_GRPC_NAMESPACE"}, - Destination: &cfg.GRPC.Namespace, - }, - { - EnvVars: []string{"ACCOUNTS_GRPC_ADDR"}, - Destination: &cfg.GRPC.Addr, - }, - { - EnvVars: []string{"ACCOUNTS_NAME"}, - Destination: &cfg.Server.Name, - }, - { - EnvVars: []string{"ACCOUNTS_HASH_DIFFICULTY"}, - Destination: &cfg.Server.HashDifficulty, - }, - { - EnvVars: []string{"ACCOUNTS_DEMO_USERS_AND_GROUPS"}, - Destination: &cfg.Server.DemoUsersAndGroups, - }, - { - EnvVars: []string{"ACCOUNTS_ASSET_PATH"}, - Destination: &cfg.Asset.Path, - }, - { - EnvVars: []string{"OCIS_JWT_SECRET", "ACCOUNTS_JWT_SECRET"}, - Destination: &cfg.TokenManager.JWTSecret, - }, - { - EnvVars: []string{"ACCOUNTS_STORAGE_BACKEND"}, - Destination: &cfg.Repo.Backend, - }, - { - EnvVars: []string{"ACCOUNTS_STORAGE_DISK_PATH"}, - Destination: &cfg.Repo.Disk.Path, - }, - { - EnvVars: []string{"ACCOUNTS_STORAGE_CS3_PROVIDER_ADDR"}, - Destination: &cfg.Repo.CS3.ProviderAddr, - }, - { - EnvVars: []string{"OCIS_JWT_SECRET", "ACCOUNTS_STORAGE_CS3_JWT_SECRET"}, - Destination: &cfg.Repo.CS3.JWTSecret, - }, - { - EnvVars: []string{"ACCOUNTS_SERVICE_USER_UUID"}, - Destination: &cfg.ServiceUser.UUID, - }, - { - EnvVars: []string{"ACCOUNTS_SERVICE_USER_USERNAME"}, - Destination: &cfg.ServiceUser.Username, - }, - { - EnvVars: []string{"ACCOUNTS_SERVICE_USER_UID"}, - Destination: &cfg.ServiceUser.UID, - }, - { - EnvVars: []string{"ACCOUNTS_SERVICE_USER_GID"}, - Destination: &cfg.ServiceUser.GID, - }, - { - EnvVars: []string{"ACCOUNTS_UID_INDEX_LOWER_BOUND"}, - Destination: &cfg.Index.UID.Lower, - }, - { - EnvVars: []string{"ACCOUNTS_GID_INDEX_LOWER_BOUND"}, - Destination: &cfg.Index.GID.Lower, - }, - { - EnvVars: []string{"ACCOUNTS_UID_INDEX_UPPER_BOUND"}, - Destination: &cfg.Index.UID.Upper, - }, - { - EnvVars: []string{"ACCOUNTS_GID_INDEX_UPPER_BOUND"}, - Destination: &cfg.Index.GID.Upper, - }, - } -} diff --git a/accounts/pkg/config/parser/parse.go b/accounts/pkg/config/parser/parse.go new file mode 100644 index 000000000..dee2dc13b --- /dev/null +++ b/accounts/pkg/config/parser/parse.go @@ -0,0 +1,47 @@ +package parser + +import ( + "errors" + "strings" + + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + + "github.com/owncloud/ocis/accounts/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" +) + +// ParseConfig loads accounts configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + // 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.Commons == nil { + cfg.Log = &config.Log{} + } + + // 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 + } + } + + // sanitize config + if cfg.HTTP.Root != "/" { + cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") + } + cfg.Repo.Backend = strings.ToLower(cfg.Repo.Backend) + + return nil +} diff --git a/accounts/pkg/config/service.go b/accounts/pkg/config/service.go new file mode 100644 index 000000000..f98aa3d27 --- /dev/null +++ b/accounts/pkg/config/service.go @@ -0,0 +1,6 @@ +package config + +// Service defines the available service configuration. +type Service struct { + Name string +} diff --git a/accounts/pkg/config/tracing.go b/accounts/pkg/config/tracing.go new file mode 100644 index 000000000..fc673f824 --- /dev/null +++ b/accounts/pkg/config/tracing.go @@ -0,0 +1,9 @@ +package config + +// Tracing defines the available tracing configuration. +type Tracing struct { + Enabled bool `ocisConfig:"enabled" env:"OCIS_TRACING_ENABLED;ACCOUNTS_TRACING_ENABLED"` + Type string `ocisConfig:"type" env:"OCIS_TRACING_TYPE;ACCOUNTS_TRACING_TYPE"` + Endpoint string `ocisConfig:"endpoint" env:"OCIS_TRACING_ENDPOINT;ACCOUNTS_TRACING_ENDPOINT"` + Collector string `ocisConfig:"collector" env:"OCIS_TRACING_COLLECTOR;ACCOUNTS_TRACING_COLLECTOR"` +} diff --git a/accounts/pkg/flagset/flagset.go b/accounts/pkg/flagset/flagset.go index 3815f0784..d661508e2 100644 --- a/accounts/pkg/flagset/flagset.go +++ b/accounts/pkg/flagset/flagset.go @@ -23,10 +23,10 @@ func UpdateAccountWithConfig(cfg *config.Config, a *accounts.Account) []cli.Flag }, &cli.StringFlag{ Name: "name", - Value: flags.OverrideDefaultString(cfg.Server.Name, "accounts"), + Value: flags.OverrideDefaultString(cfg.Service.Name, "accounts"), Usage: "service name", EnvVars: []string{"ACCOUNTS_NAME"}, - Destination: &cfg.Server.Name, + Destination: &cfg.Service.Name, }, &cli.BoolFlag{ Name: "enabled", @@ -107,10 +107,10 @@ func AddAccountWithConfig(cfg *config.Config, a *accounts.Account) []cli.Flag { }, &cli.StringFlag{ Name: "name", - Value: flags.OverrideDefaultString(cfg.Server.Name, "accounts"), + Value: flags.OverrideDefaultString(cfg.Service.Name, "accounts"), Usage: "service name", EnvVars: []string{"ACCOUNTS_NAME"}, - Destination: &cfg.Server.Name, + Destination: &cfg.Service.Name, }, &cli.BoolFlag{ Name: "enabled", @@ -191,10 +191,10 @@ func ListAccountsWithConfig(cfg *config.Config) []cli.Flag { }, &cli.StringFlag{ Name: "name", - Value: flags.OverrideDefaultString(cfg.Server.Name, "accounts"), + Value: flags.OverrideDefaultString(cfg.Service.Name, "accounts"), Usage: "service name", EnvVars: []string{"ACCOUNTS_NAME"}, - Destination: &cfg.Server.Name, + Destination: &cfg.Service.Name, }, } } @@ -211,10 +211,10 @@ func RemoveAccountWithConfig(cfg *config.Config) []cli.Flag { }, &cli.StringFlag{ Name: "name", - Value: flags.OverrideDefaultString(cfg.Server.Name, "accounts"), + Value: flags.OverrideDefaultString(cfg.Service.Name, "accounts"), Usage: "service name", EnvVars: []string{"ACCOUNTS_NAME"}, - Destination: &cfg.Server.Name, + Destination: &cfg.Service.Name, }, } } @@ -231,10 +231,10 @@ func InspectAccountWithConfig(cfg *config.Config) []cli.Flag { }, &cli.StringFlag{ Name: "name", - Value: flags.OverrideDefaultString(cfg.Server.Name, "accounts"), + Value: flags.OverrideDefaultString(cfg.Service.Name, "accounts"), Usage: "service name", EnvVars: []string{"ACCOUNTS_NAME"}, - Destination: &cfg.Server.Name, + Destination: &cfg.Service.Name, }, } } diff --git a/accounts/pkg/logging/logging.go b/accounts/pkg/logging/logging.go new file mode 100644 index 000000000..4836eaec4 --- /dev/null +++ b/accounts/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/accounts/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/accounts/pkg/proto/v0/accounts.pb.micro_test.go b/accounts/pkg/proto/v0/accounts.pb.micro_test.go index 89cf6c346..003746f4f 100644 --- a/accounts/pkg/proto/v0/accounts.pb.micro_test.go +++ b/accounts/pkg/proto/v0/accounts.pb.micro_test.go @@ -13,9 +13,9 @@ import ( mgrpcc "github.com/asim/go-micro/plugins/client/grpc/v4" empty "github.com/golang/protobuf/ptypes/empty" "github.com/owncloud/ocis/accounts/pkg/config" + "github.com/owncloud/ocis/accounts/pkg/logging" "github.com/owncloud/ocis/accounts/pkg/proto/v0" svc "github.com/owncloud/ocis/accounts/pkg/service/v0" - oclog "github.com/owncloud/ocis/ocis-pkg/log" "github.com/owncloud/ocis/ocis-pkg/service/grpc" settings "github.com/owncloud/ocis/settings/pkg/proto/v0" "github.com/stretchr/testify/assert" @@ -78,14 +78,21 @@ func init() { grpc.Address("localhost:9180"), ) - cfg := config.New() + cfg := config.DefaultConfig() cfg.Repo.Backend = "disk" cfg.Repo.Disk.Path = dataPath - cfg.Server.DemoUsersAndGroups = true + cfg.DemoUsersAndGroups = true + cfg.Log = &config.Log{} var hdlr *svc.Service var err error - if hdlr, err = svc.New(svc.Logger(oclog.LoggerFromConfig("accounts", *cfg.Log)), svc.Config(cfg), svc.RoleService(buildRoleServiceMock())); err != nil { + hdlr, err = svc.New( + svc.Logger(logging.Configure(cfg.Service.Name, cfg.Log)), + svc.Config(cfg), + svc.RoleService(buildRoleServiceMock()), + ) + + if err != nil { log.Fatalf("Could not create new service") } @@ -493,7 +500,7 @@ func TestUpdateAccount(t *testing.T) { GidNumber: 30001, Mail: "एलिस@उदाहरण.com", }, - merrors.BadRequest(".", "preferred_name 'अद्भुत-एलिस' must be at least the local part of an email"), + merrors.BadRequest("com.owncloud.api.accounts", "preferred_name 'अद्भुत-एलिस' must be at least the local part of an email"), }, { "Update user with empty data values", @@ -505,7 +512,7 @@ func TestUpdateAccount(t *testing.T) { GidNumber: 0, Mail: "", }, - merrors.BadRequest(".", "preferred_name '' must be at least the local part of an email"), + merrors.BadRequest("com.owncloud.api.accounts", "preferred_name '' must be at least the local part of an email"), }, { "Update user with strange data", @@ -517,7 +524,7 @@ func TestUpdateAccount(t *testing.T) { GidNumber: 1000, Mail: "1.2@3.c_@", }, - merrors.BadRequest(".", "mail '1.2@3.c_@' must be a valid email"), + merrors.BadRequest("com.owncloud.api.accounts", "mail '1.2@3.c_@' must be a valid email"), }, } diff --git a/accounts/pkg/server/debug/option.go b/accounts/pkg/server/debug/option.go new file mode 100644 index 000000000..0fd139d24 --- /dev/null +++ b/accounts/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/accounts/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/accounts/pkg/server/debug/server.go b/accounts/pkg/server/debug/server.go new file mode 100644 index 000000000..cabc14464 --- /dev/null +++ b/accounts/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/accounts/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/accounts/pkg/server/grpc/server.go b/accounts/pkg/server/grpc/server.go index 216b13e2e..8670c5a5c 100644 --- a/accounts/pkg/server/grpc/server.go +++ b/accounts/pkg/server/grpc/server.go @@ -3,6 +3,7 @@ package grpc import ( "github.com/owncloud/ocis/accounts/pkg/proto/v0" "github.com/owncloud/ocis/ocis-pkg/service/grpc" + "github.com/owncloud/ocis/ocis-pkg/version" ) // Server initializes a new go-micro service ready to run @@ -11,13 +12,13 @@ func Server(opts ...Option) grpc.Service { handler := options.Handler service := grpc.NewService( - grpc.Name(options.Config.Server.Name), + grpc.Name(options.Config.Service.Name), grpc.Context(options.Context), grpc.Address(options.Config.GRPC.Addr), grpc.Namespace(options.Config.GRPC.Namespace), grpc.Logger(options.Logger), grpc.Flags(options.Flags...), - grpc.Version(options.Config.Server.Version), + grpc.Version(version.String), ) if err := proto.RegisterAccountsServiceHandler(service.Server(), handler); err != nil { diff --git a/accounts/pkg/server/http/server.go b/accounts/pkg/server/http/server.go index 21487a2d6..4fb764fcb 100644 --- a/accounts/pkg/server/http/server.go +++ b/accounts/pkg/server/http/server.go @@ -21,7 +21,7 @@ func Server(opts ...Option) http.Service { service := http.NewService( http.Logger(options.Logger), http.Name(options.Name), - http.Version(options.Config.Server.Version), + http.Version(version.String), http.Address(options.Config.HTTP.Addr), http.Namespace(options.Config.HTTP.Namespace), http.Context(options.Context), diff --git a/accounts/pkg/service/v0/accounts.go b/accounts/pkg/service/v0/accounts.go index 805f06c37..d7425dce3 100644 --- a/accounts/pkg/service/v0/accounts.go +++ b/accounts/pkg/service/v0/accounts.go @@ -381,7 +381,7 @@ func (s Service) CreateAccount(ctx context.Context, in *proto.CreateAccountReque if out.PasswordProfile != nil { if out.PasswordProfile.Password != "" { // encrypt password - hashed, err := bcrypt.GenerateFromPassword([]byte(in.Account.PasswordProfile.Password), s.Config.Server.HashDifficulty) + hashed, err := bcrypt.GenerateFromPassword([]byte(in.Account.PasswordProfile.Password), s.Config.HashDifficulty) if err != nil { s.log.Error().Err(err).Str("id", id).Msg("could not hash password") return merrors.InternalServerError(s.id, "could not hash password: %v", err.Error()) @@ -572,7 +572,7 @@ func (s Service) UpdateAccount(ctx context.Context, in *proto.UpdateAccountReque } if in.Account.PasswordProfile.Password != "" { // encrypt password - hashed, err := bcrypt.GenerateFromPassword([]byte(in.Account.PasswordProfile.Password), s.Config.Server.HashDifficulty) + hashed, err := bcrypt.GenerateFromPassword([]byte(in.Account.PasswordProfile.Password), s.Config.HashDifficulty) if err != nil { in.Account.PasswordProfile.Password = "" s.log.Error().Err(err).Str("id", id).Msg("could not hash password") diff --git a/accounts/pkg/service/v0/accounts_permission_test.go b/accounts/pkg/service/v0/accounts_permission_test.go index 477369b66..0cb477468 100644 --- a/accounts/pkg/service/v0/accounts_permission_test.go +++ b/accounts/pkg/service/v0/accounts_permission_test.go @@ -31,8 +31,7 @@ var ( ) func init() { - cfg := config.New() - cfg.Server.Name = "accounts" + cfg := config.DefaultConfig() cfg.Repo.Backend = "disk" cfg.Repo.Disk.Path = dataPath logger := olog.NewLogger(olog.Color(true), olog.Pretty(true)) diff --git a/accounts/pkg/service/v0/service.go b/accounts/pkg/service/v0/service.go index cfea0f220..18703e805 100644 --- a/accounts/pkg/service/v0/service.go +++ b/accounts/pkg/service/v0/service.go @@ -8,8 +8,6 @@ import ( "strings" "time" - "github.com/owncloud/ocis/ocis-pkg/shared" - "github.com/pkg/errors" "github.com/owncloud/ocis/ocis-pkg/service/grpc" @@ -57,7 +55,7 @@ func New(opts ...Option) (s *Service, err error) { } s = &Service{ - id: cfg.GRPC.Namespace + "." + cfg.Server.Name, + id: cfg.GRPC.Namespace + "." + cfg.Service.Name, log: logger, Config: cfg, RoleService: roleService, @@ -81,11 +79,11 @@ func New(opts ...Option) (s *Service, err error) { return nil, err } - if err = s.createDefaultAccounts(cfg.Server.DemoUsersAndGroups); err != nil { + if err = s.createDefaultAccounts(cfg.DemoUsersAndGroups); err != nil { return nil, err } - if err = s.createDefaultGroups(cfg.Server.DemoUsersAndGroups); err != nil { + if err = s.createDefaultGroups(cfg.DemoUsersAndGroups); err != nil { return nil, err } return @@ -112,7 +110,7 @@ func configFromSvc(cfg *config.Config) (*idxcfg.Config, error) { c := idxcfg.New() if cfg.Log == nil { - cfg.Log = &shared.Log{} + cfg.Log = &config.Log{} } defer func(cfg *config.Config) { diff --git a/accounts/pkg/tracing/tracing.go b/accounts/pkg/tracing/tracing.go index 4da38009f..5f9bd3b65 100644 --- a/accounts/pkg/tracing/tracing.go +++ b/accounts/pkg/tracing/tracing.go @@ -14,7 +14,7 @@ var ( func Configure(cfg *config.Config) error { var err error if cfg.Tracing.Enabled { - if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, "accounts", cfg.Tracing.Type); err != nil { + if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { return err } } diff --git a/changelog/unreleased/change-unify-configuration-and-commands.md b/changelog/unreleased/change-unify-configuration-and-commands.md new file mode 100644 index 000000000..0447d5487 --- /dev/null +++ b/changelog/unreleased/change-unify-configuration-and-commands.md @@ -0,0 +1,7 @@ +Change: Unify configuration and commands + +We've unified the configuration and commands of all non storage services. This also +includes the change, that environment variables are now defined on the config struct +as tags instead in a separate mapping. + +https://github.com/owncloud/ocis/pull/2818 diff --git a/deployments/examples/oc10_ocis_parallel/docker-compose.yml b/deployments/examples/oc10_ocis_parallel/docker-compose.yml index 3469715c0..ec5185b86 100644 --- a/deployments/examples/oc10_ocis_parallel/docker-compose.yml +++ b/deployments/examples/oc10_ocis_parallel/docker-compose.yml @@ -33,7 +33,7 @@ services: - "certs:/certs" labels: - "traefik.enable=${TRAEFIK_DASHBOARD:-false}" - - "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_BASIC_AUTH_USERS:-admin:$apr1$4vqie50r$YQAmQdtmz5n9rEALhxJ4l.}" # defaults to admin:admin + - "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_BASIC_AUTH_USERS:-admin:$$apr1$$4vqie50r$$YQAmQdtmz5n9rEALhxJ4l.}" # defaults to admin:admin - "traefik.http.routers.traefik.entrypoints=https" - "traefik.http.routers.traefik.rule=Host(`${TRAEFIK_DOMAIN:-traefik.owncloud.test}`)" - "traefik.http.routers.traefik.middlewares=traefik-auth" diff --git a/deployments/examples/ocis_hello/docker-compose.yml b/deployments/examples/ocis_hello/docker-compose.yml index 19aac6991..c539ed817 100644 --- a/deployments/examples/ocis_hello/docker-compose.yml +++ b/deployments/examples/ocis_hello/docker-compose.yml @@ -32,7 +32,7 @@ services: - "certs:/certs" labels: - "traefik.enable=${TRAEFIK_DASHBOARD:-false}" - - "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_BASIC_AUTH_USERS:-admin:$apr1$4vqie50r$YQAmQdtmz5n9rEALhxJ4l.}" # defaults to admin:admin + - "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_BASIC_AUTH_USERS:-admin:$$apr1$$4vqie50r$$YQAmQdtmz5n9rEALhxJ4l.}" # defaults to admin:admin - "traefik.http.routers.traefik.entrypoints=https" - "traefik.http.routers.traefik.rule=Host(`${TRAEFIK_DOMAIN:-traefik.owncloud.test}`)" - "traefik.http.routers.traefik.middlewares=traefik-auth" diff --git a/deployments/examples/ocis_keycloak/docker-compose.yml b/deployments/examples/ocis_keycloak/docker-compose.yml index dd2be4da7..ec11d218f 100644 --- a/deployments/examples/ocis_keycloak/docker-compose.yml +++ b/deployments/examples/ocis_keycloak/docker-compose.yml @@ -33,7 +33,7 @@ services: - "certs:/certs" labels: - "traefik.enable=${TRAEFIK_DASHBOARD:-false}" - - "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_BASIC_AUTH_USERS:-admin:$apr1$4vqie50r$YQAmQdtmz5n9rEALhxJ4l.}" # defaults to admin:admin + - "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_BASIC_AUTH_USERS:-admin:$$apr1$$4vqie50r$$YQAmQdtmz5n9rEALhxJ4l.}" # defaults to admin:admin - "traefik.http.routers.traefik.entrypoints=https" - "traefik.http.routers.traefik.rule=Host(`${TRAEFIK_DOMAIN:-traefik.owncloud.test}`)" - "traefik.http.routers.traefik.middlewares=traefik-auth" diff --git a/deployments/examples/ocis_ldap/docker-compose.yml b/deployments/examples/ocis_ldap/docker-compose.yml index 4386238ec..e8cd83e62 100644 --- a/deployments/examples/ocis_ldap/docker-compose.yml +++ b/deployments/examples/ocis_ldap/docker-compose.yml @@ -32,7 +32,7 @@ services: - "certs:/certs" labels: - "traefik.enable=${TRAEFIK_DASHBOARD:-false}" - - "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_BASIC_AUTH_USERS:-admin:$apr1$4vqie50r$YQAmQdtmz5n9rEALhxJ4l.}" # defaults to admin:admin + - "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_BASIC_AUTH_USERS:-admin:$$apr1$$4vqie50r$$YQAmQdtmz5n9rEALhxJ4l.}" # defaults to admin:admin - "traefik.http.routers.traefik.entrypoints=https" - "traefik.http.routers.traefik.rule=Host(`${TRAEFIK_DOMAIN:-traefik.owncloud.test}`)" - "traefik.http.routers.traefik.middlewares=traefik-auth" diff --git a/deployments/examples/ocis_s3/docker-compose.yml b/deployments/examples/ocis_s3/docker-compose.yml index 996262072..aed2b45f4 100644 --- a/deployments/examples/ocis_s3/docker-compose.yml +++ b/deployments/examples/ocis_s3/docker-compose.yml @@ -32,7 +32,7 @@ services: - "certs:/certs" labels: - "traefik.enable=${TRAEFIK_DASHBOARD:-false}" - - "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_BASIC_AUTH_USERS:-admin:$apr1$4vqie50r$YQAmQdtmz5n9rEALhxJ4l.}" # defaults to admin:admin + - "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_BASIC_AUTH_USERS:-admin:$$apr1$$4vqie50r$$YQAmQdtmz5n9rEALhxJ4l.}" # defaults to admin:admin - "traefik.http.routers.traefik.entrypoints=https" - "traefik.http.routers.traefik.rule=Host(`${TRAEFIK_DOMAIN:-traefik.owncloud.test}`)" - "traefik.http.routers.traefik.middlewares=traefik-auth" diff --git a/deployments/examples/ocis_traefik/docker-compose.yml b/deployments/examples/ocis_traefik/docker-compose.yml index 53b8ca154..06a5e7a45 100644 --- a/deployments/examples/ocis_traefik/docker-compose.yml +++ b/deployments/examples/ocis_traefik/docker-compose.yml @@ -32,7 +32,7 @@ services: - "certs:/certs" labels: - "traefik.enable=${TRAEFIK_DASHBOARD:-false}" - - "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_BASIC_AUTH_USERS:-admin:$apr1$4vqie50r$YQAmQdtmz5n9rEALhxJ4l.}" # defaults to admin:admin + - "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_BASIC_AUTH_USERS:-admin:$$apr1$$4vqie50r$$YQAmQdtmz5n9rEALhxJ4l.}" # defaults to admin:admin - "traefik.http.routers.traefik.entrypoints=https" - "traefik.http.routers.traefik.rule=Host(`${TRAEFIK_DOMAIN:-traefik.owncloud.test}`)" - "traefik.http.routers.traefik.middlewares=traefik-auth" diff --git a/deployments/examples/ocis_wopi/docker-compose.yml b/deployments/examples/ocis_wopi/docker-compose.yml index 1cb1cf0a2..e9d0b4f6e 100644 --- a/deployments/examples/ocis_wopi/docker-compose.yml +++ b/deployments/examples/ocis_wopi/docker-compose.yml @@ -36,7 +36,7 @@ services: - "certs:/certs" labels: - "traefik.enable=${TRAEFIK_DASHBOARD:-false}" - - "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_BASIC_AUTH_USERS:-admin:$apr1$4vqie50r$YQAmQdtmz5n9rEALhxJ4l.}" # defaults to admin:admin + - "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_BASIC_AUTH_USERS:-admin:$$apr1$$4vqie50r$$YQAmQdtmz5n9rEALhxJ4l.}" # defaults to admin:admin - "traefik.http.routers.traefik.entrypoints=https" - "traefik.http.routers.traefik.rule=Host(`${TRAEFIK_DOMAIN:-traefik.owncloud.test}`)" - "traefik.http.routers.traefik.middlewares=traefik-auth" diff --git a/docs/ocis/config.md b/docs/ocis/config.md index aa6a367bf..02df18b9f 100644 --- a/docs/ocis/config.md +++ b/docs/ocis/config.md @@ -19,7 +19,7 @@ In order to simplify deployments and development the configuration model from oC ## In-depth configuration -Since we include a set of predefined extensions within the single binary, configuring an extension can be done in a variety of ways. Since we work with complex types, having as many cli per config value scales poorly, so we limited the options to config files and environment variables, leaving cli flags for common values, such as logging (`--log-level`, `--log-pretty`, `--log-file` or `--log-color`). +Since we include a set of predefined extensions within the single binary, configuring an extension can be done in a variety of ways. Since we work with complex types, having as many cli per config value scales poorly, so we limited the options to config files and environment variables. The hierarchy is clear enough, leaving us with: diff --git a/glauth/cmd/glauth/main.go b/glauth/cmd/glauth/main.go index f2abd5e20..6a2a83249 100644 --- a/glauth/cmd/glauth/main.go +++ b/glauth/cmd/glauth/main.go @@ -8,7 +8,7 @@ import ( ) func main() { - if err := command.Execute(config.New()); err != nil { + if err := command.Execute(config.DefaultConfig()); err != nil { os.Exit(1) } } diff --git a/glauth/pkg/command/health.go b/glauth/pkg/command/health.go index 1ee1362ec..edc25b50a 100644 --- a/glauth/pkg/command/health.go +++ b/glauth/pkg/command/health.go @@ -5,19 +5,22 @@ import ( "net/http" "github.com/owncloud/ocis/glauth/pkg/config" + "github.com/owncloud/ocis/glauth/pkg/config/parser" + "github.com/owncloud/ocis/glauth/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", + Name: "health", + Usage: "check health status", + Category: "info", Before: func(c *cli.Context) error { - return ParseConfig(c, cfg) + return parser.ParseConfig(cfg) }, Action: func(c *cli.Context) error { - logger := NewLogger(cfg) + logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( fmt.Sprintf( @@ -34,7 +37,7 @@ func Health(cfg *config.Config) *cli.Command { defer resp.Body.Close() - if resp.StatusCode != 200 { + if resp.StatusCode != http.StatusOK { logger.Fatal(). Int("code", resp.StatusCode). Msg("Health seems to be in bad state") diff --git a/glauth/pkg/command/root.go b/glauth/pkg/command/root.go index e6017cfef..7b1dc51d9 100644 --- a/glauth/pkg/command/root.go +++ b/glauth/pkg/command/root.go @@ -5,81 +5,42 @@ import ( "os" "github.com/owncloud/ocis/glauth/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - oclog "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/shared" - "github.com/owncloud/ocis/ocis-pkg/version" "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-glauth command. func Execute(cfg *config.Config) error { - app := &cli.App{ + app := clihelper.DefaultApp(&cli.App{ Name: "ocis-glauth", - Version: version.String, Usage: "Serve GLAuth API for oCIS", - Compiled: version.Compiled(), - Authors: []*cli.Author{ - { - Name: "ownCloud GmbH", - Email: "support@owncloud.com", - }, - }, - Before: func(c *cli.Context) error { - cfg.Version = version.String - return nil - }, - Commands: []*cli.Command{ - Server(cfg), - Health(cfg), - }, - } + Commands: GetCommands(cfg), + }) cli.HelpFlag = &cli.BoolFlag{ Name: "help,h", Usage: "Show the help", } - cli.VersionFlag = &cli.BoolFlag{ - Name: "version,v", - Usage: "Print the version", - } - return app.Run(os.Args) } -// NewLogger initializes a service-specific logger instance. -func NewLogger(cfg *config.Config) log.Logger { - return oclog.LoggerFromConfig("glauth", *cfg.Log) -} - -// ParseConfig loads glauth configuration from known paths. -func ParseConfig(c *cli.Context, cfg *config.Config) error { - conf, err := ociscfg.BindSourcesToStructs("glauth", cfg) - if err != nil { - return err - } - - // 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 = &shared.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.Commons == nil { - cfg.Log = &shared.Log{} - } - - // load all env variables relevant to the config in the current context. - conf.LoadOSEnv(config.GetEnv(cfg), false) - bindings := config.StructMappings(cfg) - return ociscfg.BindEnv(conf, bindings) -} - // SutureService allows for the glauth command to be embedded and supervised by a suture supervisor tree. type SutureService struct { cfg *config.Config diff --git a/glauth/pkg/command/server.go b/glauth/pkg/command/server.go index d9cbb5f1c..75a27f846 100644 --- a/glauth/pkg/command/server.go +++ b/glauth/pkg/command/server.go @@ -2,41 +2,37 @@ package command import ( "context" - "strings" + "fmt" glauthcfg "github.com/glauth/glauth/v2/pkg/config" "github.com/oklog/run" accounts "github.com/owncloud/ocis/accounts/pkg/proto/v0" "github.com/owncloud/ocis/glauth/pkg/config" + "github.com/owncloud/ocis/glauth/pkg/config/parser" + "github.com/owncloud/ocis/glauth/pkg/logging" "github.com/owncloud/ocis/glauth/pkg/metrics" "github.com/owncloud/ocis/glauth/pkg/server/debug" "github.com/owncloud/ocis/glauth/pkg/server/glauth" "github.com/owncloud/ocis/glauth/pkg/tracing" pkgcrypto "github.com/owncloud/ocis/ocis-pkg/crypto" "github.com/owncloud/ocis/ocis-pkg/service/grpc" + "github.com/owncloud/ocis/ocis-pkg/version" "github.com/urfave/cli/v2" ) // Server is the entrypoint for the server command. func Server(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "server", - Usage: "Start integrated server", - Before: func(ctx *cli.Context) error { - if cfg.HTTP.Root != "/" { - cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") - } - - if err := ParseConfig(ctx, cfg); err != nil { - return err - } - - return nil + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + return parser.ParseConfig(cfg) }, Action: func(c *cli.Context) error { - logger := NewLogger(cfg) - - if err := tracing.Configure(cfg); err != nil { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg) + if err != nil { return err } @@ -51,7 +47,7 @@ func Server(cfg *config.Config) *cli.Command { defer cancel() - metrics.BuildInfo.WithLabelValues(cfg.Version).Set(1) + metrics.BuildInfo.WithLabelValues(version.String).Set(1) { diff --git a/glauth/pkg/command/version.go b/glauth/pkg/command/version.go new file mode 100644 index 000000000..c8e149daf --- /dev/null +++ b/glauth/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/glauth/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.Ldap.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/glauth/pkg/config/config.go b/glauth/pkg/config/config.go index d547180d7..3d687319c 100644 --- a/glauth/pkg/config/config.go +++ b/glauth/pkg/config/config.go @@ -2,49 +2,29 @@ package config import ( "context" - "path" "github.com/owncloud/ocis/ocis-pkg/shared" - - "github.com/owncloud/ocis/ocis-pkg/config/defaults" ) -// Debug defines the available debug configuration. -type Debug struct { - Addr string `ocisConfig:"addr"` - Token string `ocisConfig:"token"` - Pprof bool `ocisConfig:"pprof"` - Zpages bool `ocisConfig:"zpages"` -} +// Config combines all available configuration parts. +type Config struct { + *shared.Commons -// HTTP defines the available http configuration. -type HTTP struct { - Addr string `ocisConfig:"addr"` - Namespace string `ocisConfig:"namespace"` - Root string `ocisConfig:"root"` -} + Service Service -// Tracing defines the available tracing configuration. -type Tracing struct { - Enabled bool `ocisConfig:"enabled"` - Type string `ocisConfig:"type"` - Endpoint string `ocisConfig:"endpoint"` - Collector string `ocisConfig:"collector"` - Service string `ocisConfig:"service"` -} + Tracing Tracing `ocisConfig:"tracing"` + Log *Log `ocisConfig:"log"` + Debug Debug `ocisConfig:"debug"` -// Ldap defined the available LDAP configuration. -type Ldap struct { - Enabled bool `ocisConfig:"enabled"` - Addr string `ocisConfig:"addr"` -} + Ldap Ldap `ocisConfig:"ldap"` + Ldaps Ldaps `ocisConfig:"ldaps"` -// Ldaps defined the available LDAPS configuration. -type Ldaps struct { - Addr string `ocisConfig:"addr"` - Enabled bool `ocisConfig:"enabled"` - Cert string `ocisConfig:"cert"` - Key string `ocisConfig:"key"` + Backend Backend `ocisConfig:"backend"` + Fallback FallbackBackend `ocisConfig:"fallback"` + + RoleBundleUUID string `ocisConfig:"role_bundle_uuid" env:"GLAUTH_ROLE_BUNDLE_ID"` + + Context context.Context } // Backend defined the available backend configuration. @@ -59,71 +39,14 @@ type Backend struct { UseGraphAPI bool `ocisConfig:"use_graph_api"` } -// Config combines all available configuration parts. -type Config struct { - *shared.Commons - - File string `ocisConfig:"file"` - Log *shared.Log `ocisConfig:"log"` - Debug Debug `ocisConfig:"debug"` - HTTP HTTP `ocisConfig:"http"` - Tracing Tracing `ocisConfig:"tracing"` - Ldap Ldap `ocisConfig:"ldap"` - Ldaps Ldaps `ocisConfig:"ldaps"` - Backend Backend `ocisConfig:"backend"` - Fallback Backend `ocisConfig:"fallback"` - Version string `ocisConfig:"version"` - RoleBundleUUID string `ocisConfig:"role_bundle_uuid"` - - Context context.Context - Supervised bool -} - -// New initializes a new configuration with or without defaults. -func New() *Config { - return &Config{} -} - -func DefaultConfig() *Config { - return &Config{ - Debug: Debug{ - Addr: "127.0.0.1:9129", - }, - HTTP: HTTP{}, - Tracing: Tracing{ - Type: "jaeger", - Service: "glauth", - }, - Ldap: Ldap{ - Enabled: true, - Addr: "127.0.0.1:9125", - }, - Ldaps: Ldaps{ - Addr: "127.0.0.1:9126", - Enabled: true, - Cert: path.Join(defaults.BaseDataPath(), "ldap", "ldap.crt"), - Key: path.Join(defaults.BaseDataPath(), "ldap", "ldap.key"), - }, - Backend: Backend{ - Datastore: "accounts", - BaseDN: "dc=ocis,dc=test", - Insecure: false, - NameFormat: "cn", - GroupFormat: "ou", - Servers: nil, - SSHKeyAttr: "sshPublicKey", - UseGraphAPI: true, - }, - Fallback: Backend{ - Datastore: "", - BaseDN: "dc=ocis,dc=test", - Insecure: false, - NameFormat: "cn", - GroupFormat: "ou", - Servers: nil, - SSHKeyAttr: "sshPublicKey", - UseGraphAPI: true, - }, - RoleBundleUUID: "71881883-1768-46bd-a24d-a356a2afdf7f", // BundleUUIDRoleAdmin - } +// FallbackBackend defined the available fallback backend configuration. +type FallbackBackend struct { + Datastore string `ocisConfig:"datastore"` + BaseDN string `ocisConfig:"base_dn"` + Insecure bool `ocisConfig:"insecure"` + NameFormat string `ocisConfig:"name_format"` + GroupFormat string `ocisConfig:"group_format"` + Servers []string `ocisConfig:"servers"` + SSHKeyAttr string `ocisConfig:"ssh_key_attr"` + UseGraphAPI bool `ocisConfig:"use_graph_api"` } diff --git a/glauth/pkg/config/debug.go b/glauth/pkg/config/debug.go new file mode 100644 index 000000000..1d612c88d --- /dev/null +++ b/glauth/pkg/config/debug.go @@ -0,0 +1,9 @@ +package config + +// Debug defines the available debug configuration. +type Debug struct { + Addr string `ocisConfig:"addr" env:"GLAUTH_DEBUG_ADDR"` + Token string `ocisConfig:"token" env:"GLAUTH_DEBUG_TOKEN"` + Pprof bool `ocisConfig:"pprof" env:"GLAUTH_DEBUG_PPROF"` + Zpages bool `ocisConfig:"zpages" env:"GLAUTH_DEBUG_ZPAGES"` +} diff --git a/glauth/pkg/config/defaultconfig.go b/glauth/pkg/config/defaultconfig.go new file mode 100644 index 000000000..becff0bb9 --- /dev/null +++ b/glauth/pkg/config/defaultconfig.go @@ -0,0 +1,57 @@ +package config + +import ( + "path" + + "github.com/owncloud/ocis/ocis-pkg/config/defaults" +) + +func DefaultConfig() *Config { + return &Config{ + Debug: Debug{ + Addr: "127.0.0.1:9129", + }, + Tracing: Tracing{ + Enabled: false, + Type: "jaeger", + Endpoint: "", + Collector: "", + }, + Service: Service{ + Name: "glauth", + }, + Ldap: Ldap{ + Enabled: true, + Addr: "127.0.0.1:9125", + Namespace: "com.owncloud.ldap", + }, + Ldaps: Ldaps{ + Enabled: true, + Addr: "127.0.0.1:9126", + Namespace: "com.owncloud.ldaps", + Cert: path.Join(defaults.BaseDataPath(), "ldap", "ldap.crt"), + Key: path.Join(defaults.BaseDataPath(), "ldap", "ldap.key"), + }, + Backend: Backend{ + Datastore: "accounts", + BaseDN: "dc=ocis,dc=test", + Insecure: false, + NameFormat: "cn", + GroupFormat: "ou", + Servers: nil, + SSHKeyAttr: "sshPublicKey", + UseGraphAPI: true, + }, + Fallback: FallbackBackend{ + Datastore: "", + BaseDN: "dc=ocis,dc=test", + Insecure: false, + NameFormat: "cn", + GroupFormat: "ou", + Servers: nil, + SSHKeyAttr: "sshPublicKey", + UseGraphAPI: true, + }, + RoleBundleUUID: "71881883-1768-46bd-a24d-a356a2afdf7f", // BundleUUIDRoleAdmin + } +} diff --git a/glauth/pkg/config/ldap.go b/glauth/pkg/config/ldap.go new file mode 100644 index 000000000..b0780084a --- /dev/null +++ b/glauth/pkg/config/ldap.go @@ -0,0 +1,8 @@ +package config + +// Ldap defines the available LDAP configuration. +type Ldap struct { + Enabled bool `ocisConfig:"enabled" env:"GLAUTH_LDAP_ENABLED"` + Addr string `ocisConfig:"addr" env:"GLAUTH_LDAP_ADDR"` + Namespace string +} diff --git a/glauth/pkg/config/ldaps.go b/glauth/pkg/config/ldaps.go new file mode 100644 index 000000000..2c09f2530 --- /dev/null +++ b/glauth/pkg/config/ldaps.go @@ -0,0 +1,10 @@ +package config + +// Ldaps defined the available LDAPS configuration. +type Ldaps struct { + Enabled bool `ocisConfig:"enabled" env:"GLAUTH_LDAPS_ENABLED"` + Addr string `ocisConfig:"addr" env:"GLAUTH_LDAPS_ADDR"` + Namespace string + Cert string `ocisConfig:"cert" env:"GLAUTH_LDAPS_CERT"` + Key string `ocisConfig:"key" env:"GLAUTH_LDAPS_KEY"` +} diff --git a/glauth/pkg/config/log.go b/glauth/pkg/config/log.go new file mode 100644 index 000000000..2ce88369b --- /dev/null +++ b/glauth/pkg/config/log.go @@ -0,0 +1,9 @@ +package config + +// Log defines the available log configuration. +type Log struct { + Level string `mapstructure:"level" env:"OCIS_LOG_LEVEL;GLAUTH_LOG_LEVEL"` + Pretty bool `mapstructure:"pretty" env:"OCIS_LOG_PRETTY;GLAUTH_LOG_PRETTY"` + Color bool `mapstructure:"color" env:"OCIS_LOG_COLOR;GLAUTH_LOG_COLOR"` + File string `mapstructure:"file" env:"OCIS_LOG_FILE;GLAUTH_LOG_FILE"` +} diff --git a/glauth/pkg/config/mappings.go b/glauth/pkg/config/mappings.go deleted file mode 100644 index 4867fc9ea..000000000 --- a/glauth/pkg/config/mappings.go +++ /dev/null @@ -1,167 +0,0 @@ -package config - -import "github.com/owncloud/ocis/ocis-pkg/shared" - -// GetEnv fetches a list of known env variables for this extension. It is to be used by gookit, as it provides a list -// with all the environment variables an extension supports. -func GetEnv(cfg *Config) []string { - var r = make([]string, len(structMappings(cfg))) - for i := range structMappings(cfg) { - r = append(r, structMappings(cfg)[i].EnvVars...) - } - - return r -} - -// StructMappings binds a set of environment variables to a destination on cfg. Iterating over this set and editing the -// Destination value of a binding will alter the original value, as it is a pointer to its memory address. This lets -// us propagate changes easier. -func StructMappings(cfg *Config) []shared.EnvBinding { - return structMappings(cfg) -} - -// structMappings binds a set of environment variables to a destination on cfg. -func structMappings(cfg *Config) []shared.EnvBinding { - return []shared.EnvBinding{ - { - EnvVars: []string{"OCIS_LOG_LEVEL", "GLAUTH_LOG_LEVEL"}, - Destination: &cfg.Log.Level, - }, - { - EnvVars: []string{"OCIS_LOG_PRETTY", "GLAUTH_LOG_PRETTY"}, - Destination: &cfg.Log.Pretty, - }, - { - EnvVars: []string{"OCIS_LOG_COLOR", "GLAUTH_LOG_COLOR"}, - Destination: &cfg.Log.Color, - }, - { - EnvVars: []string{"OCIS_LOG_FILE", "GLAUTH_LOG_FILE"}, - Destination: &cfg.Log.File, - }, - { - EnvVars: []string{"GLAUTH_CONFIG_FILE"}, - Destination: &cfg.File, - }, - { - EnvVars: []string{"OCIS_TRACING_ENABLED", "GLAUTH_TRACING_ENABLED"}, - Destination: &cfg.Tracing.Enabled, - }, - { - EnvVars: []string{"OCIS_TRACING_TYPE", "GLAUTH_TRACING_TYPE"}, - Destination: &cfg.Tracing.Type, - }, - { - EnvVars: []string{"OCIS_TRACING_ENDPOINT", "GLAUTH_TRACING_ENDPOINT"}, - Destination: &cfg.Tracing.Endpoint, - }, - { - EnvVars: []string{"OCIS_TRACING_COLLECTOR", "GLAUTH_TRACING_COLLECTOR"}, - Destination: &cfg.Tracing.Collector, - }, - { - EnvVars: []string{"GLAUTH_TRACING_SERVICE"}, - Destination: &cfg.Tracing.Service, - }, - { - EnvVars: []string{"GLAUTH_DEBUG_ADDR"}, - Destination: &cfg.Debug.Addr, - }, - { - EnvVars: []string{"GLAUTH_DEBUG_TOKEN"}, - Destination: &cfg.Debug.Token, - }, - { - EnvVars: []string{"GLAUTH_DEBUG_PPROF"}, - Destination: &cfg.Debug.Pprof, - }, - { - EnvVars: []string{"GLAUTH_DEBUG_ZPAGES"}, - Destination: &cfg.Debug.Zpages, - }, - { - EnvVars: []string{"GLAUTH_ROLE_BUNDLE_ID"}, - Destination: &cfg.RoleBundleUUID, - }, - { - EnvVars: []string{"GLAUTH_LDAP_ADDR"}, - Destination: &cfg.Ldap.Addr, - }, - { - EnvVars: []string{"GLAUTH_LDAP_ENABLED"}, - Destination: &cfg.Ldap.Enabled, - }, - { - EnvVars: []string{"GLAUTH_LDAPS_ADDR"}, - Destination: &cfg.Ldaps.Addr, - }, - { - EnvVars: []string{"GLAUTH_LDAPS_ENABLED"}, - Destination: &cfg.Ldaps.Enabled, - }, - { - EnvVars: []string{"GLAUTH_LDAPS_CERT"}, - Destination: &cfg.Ldaps.Cert, - }, - { - EnvVars: []string{"GLAUTH_LDAPS_KEY"}, - Destination: &cfg.Ldaps.Key, - }, - { - EnvVars: []string{"GLAUTH_BACKEND_BASEDN"}, - Destination: &cfg.Backend.BaseDN, - }, - { - EnvVars: []string{"GLAUTH_BACKEND_NAME_FORMAT"}, - Destination: &cfg.Backend.NameFormat, - }, - { - EnvVars: []string{"GLAUTH_BACKEND_GROUP_FORMAT"}, - Destination: &cfg.Backend.GroupFormat, - }, - { - EnvVars: []string{"GLAUTH_BACKEND_SSH_KEY_ATTR"}, - Destination: &cfg.Backend.SSHKeyAttr, - }, - { - EnvVars: []string{"GLAUTH_BACKEND_DATASTORE"}, - Destination: &cfg.Backend.Datastore, - }, - { - EnvVars: []string{"GLAUTH_BACKEND_INSECURE"}, - Destination: &cfg.Backend.Insecure, - }, - { - EnvVars: []string{"GLAUTH_BACKEND_USE_GRAPHAPI"}, - Destination: &cfg.Backend.UseGraphAPI, - }, - { - EnvVars: []string{"GLAUTH_FALLBACK_BASEDN"}, - Destination: &cfg.Fallback.BaseDN, - }, - { - EnvVars: []string{"GLAUTH_FALLBACK_NAME_FORMAT"}, - Destination: &cfg.Fallback.NameFormat, - }, - { - EnvVars: []string{"GLAUTH_FALLBACK_GROUP_FORMAT"}, - Destination: &cfg.Fallback.GroupFormat, - }, - { - EnvVars: []string{"GLAUTH_FALLBACK_SSH_KEY_ATTR"}, - Destination: &cfg.Fallback.SSHKeyAttr, - }, - { - EnvVars: []string{"GLAUTH_FALLBACK_DATASTORE"}, - Destination: &cfg.Fallback.Datastore, - }, - { - EnvVars: []string{"GLAUTH_FALLBACK_INSECURE"}, - Destination: &cfg.Fallback.Insecure, - }, - { - EnvVars: []string{"GLAUTH_FALLBACK_USE_GRAPHAPI"}, - Destination: &cfg.Fallback.UseGraphAPI, - }, - } -} diff --git a/glauth/pkg/config/parser/parse.go b/glauth/pkg/config/parser/parse.go new file mode 100644 index 000000000..b2a18b2a9 --- /dev/null +++ b/glauth/pkg/config/parser/parse.go @@ -0,0 +1,42 @@ +package parser + +import ( + "errors" + + "github.com/owncloud/ocis/glauth/pkg/config" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" +) + +// ParseConfig loads accounts configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + // 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.Commons == nil { + cfg.Log = &config.Log{} + } + + // 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 + } + } + + // sanitize config + + return nil +} diff --git a/glauth/pkg/config/service.go b/glauth/pkg/config/service.go new file mode 100644 index 000000000..f98aa3d27 --- /dev/null +++ b/glauth/pkg/config/service.go @@ -0,0 +1,6 @@ +package config + +// Service defines the available service configuration. +type Service struct { + Name string +} diff --git a/glauth/pkg/config/tracing.go b/glauth/pkg/config/tracing.go new file mode 100644 index 000000000..52733d097 --- /dev/null +++ b/glauth/pkg/config/tracing.go @@ -0,0 +1,9 @@ +package config + +// Tracing defines the available tracing configuration. +type Tracing struct { + Enabled bool `ocisConfig:"enabled" env:"OCIS_TRACING_ENABLED;GLAUTH_TRACING_ENABLED"` + Type string `ocisConfig:"type" env:"OCIS_TRACING_TYPE;GLAUTH_TRACING_TYPE"` + Endpoint string `ocisConfig:"endpoint" env:"OCIS_TRACING_ENDPOINT;GLAUTH_TRACING_ENDPOINT"` + Collector string `ocisConfig:"collector" env:"OCIS_TRACING_COLLECTOR;GLAUTH_TRACING_COLLECTOR"` +} diff --git a/glauth/pkg/logging/logging.go b/glauth/pkg/logging/logging.go new file mode 100644 index 000000000..5282014a6 --- /dev/null +++ b/glauth/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/glauth/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/glauth/pkg/server/debug/server.go b/glauth/pkg/server/debug/server.go index d6373ba1e..5cc9fab7b 100644 --- a/glauth/pkg/server/debug/server.go +++ b/glauth/pkg/server/debug/server.go @@ -15,7 +15,7 @@ func Server(opts ...Option) (*http.Server, error) { return debug.NewService( debug.Logger(options.Logger), - debug.Name("glauth"), + debug.Name(options.Config.Service.Name), debug.Version(version.String), debug.Address(options.Config.Debug.Addr), debug.Token(options.Config.Debug.Token), @@ -32,7 +32,7 @@ func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) - // TODO(tboerger): check if services are up and running + // 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. @@ -48,7 +48,7 @@ func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) - // TODO(tboerger): check if services are up and running + // 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. diff --git a/glauth/pkg/tracing/tracing.go b/glauth/pkg/tracing/tracing.go index a02161687..734fd1d52 100644 --- a/glauth/pkg/tracing/tracing.go +++ b/glauth/pkg/tracing/tracing.go @@ -14,7 +14,7 @@ var ( func Configure(cfg *config.Config) error { var err error if cfg.Tracing.Enabled { - if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, "glauth", cfg.Tracing.Type); err != nil { + if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { return err } } diff --git a/graph-explorer/pkg/command/health.go b/graph-explorer/pkg/command/health.go index 60f4cc925..b25e795c2 100644 --- a/graph-explorer/pkg/command/health.go +++ b/graph-explorer/pkg/command/health.go @@ -5,19 +5,22 @@ import ( "net/http" "github.com/owncloud/ocis/graph-explorer/pkg/config" + "github.com/owncloud/ocis/graph-explorer/pkg/config/parser" + "github.com/owncloud/ocis/graph-explorer/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", + Name: "health", + Usage: "check health status", + Category: "info", Before: func(c *cli.Context) error { - return ParseConfig(c, cfg) + return parser.ParseConfig(cfg) }, Action: func(c *cli.Context) error { - logger := NewLogger(cfg) + logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( fmt.Sprintf( @@ -34,7 +37,7 @@ func Health(cfg *config.Config) *cli.Command { defer resp.Body.Close() - if resp.StatusCode != 200 { + if resp.StatusCode != http.StatusOK { logger.Fatal(). Int("code", resp.StatusCode). Msg("Health seems to be in bad state") diff --git a/graph-explorer/pkg/command/root.go b/graph-explorer/pkg/command/root.go index 2f5f679a4..f2acd3460 100644 --- a/graph-explorer/pkg/command/root.go +++ b/graph-explorer/pkg/command/root.go @@ -5,70 +5,42 @@ import ( "os" "github.com/owncloud/ocis/graph-explorer/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/version" "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 graph-explorer command. func Execute(cfg *config.Config) error { - app := &cli.App{ + app := clihelper.DefaultApp(&cli.App{ Name: "graph-explorer", - Version: version.String, Usage: "Serve Graph-Explorer for oCIS", - Compiled: version.Compiled(), - Authors: []*cli.Author{ - { - Name: "ownCloud GmbH", - Email: "support@owncloud.com", - }, - }, - Before: func(c *cli.Context) error { - cfg.Server.Version = version.String - return ParseConfig(c, cfg) - }, - Commands: []*cli.Command{ - Server(cfg), - Health(cfg), - }, - } + Commands: GetCommands(cfg), + }) + cli.HelpFlag = &cli.BoolFlag{ Name: "help,h", Usage: "Show the help", } - cli.VersionFlag = &cli.BoolFlag{ - Name: "version,v", - Usage: "Print the version", - } return app.Run(os.Args) } -// NewLogger initializes a service-specific logger instance. -func NewLogger(cfg *config.Config) log.Logger { - return log.NewLogger( - log.Name("graph-explorer"), - log.Level(cfg.Log.Level), - log.Pretty(cfg.Log.Pretty), - log.Color(cfg.Log.Color), - log.File(cfg.Log.File), - ) -} - -// ParseConfig loads graph configuration from known paths. -func ParseConfig(c *cli.Context, cfg *config.Config) error { - conf, err := ociscfg.BindSourcesToStructs("graph-explorer", cfg) - if err != nil { - return err - } - - conf.LoadOSEnv(config.GetEnv(), false) - bindings := config.StructMappings(cfg) - return ociscfg.BindEnv(conf, bindings) -} - // SutureService allows for the graph-explorer command to be embedded and supervised by a suture supervisor tree. type SutureService struct { cfg *config.Config @@ -76,7 +48,7 @@ type SutureService struct { // NewSutureService creates a new graph-explorer.SutureService func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.GraphExplorer.Log = cfg.Log + cfg.GraphExplorer.Commons = cfg.Commons return SutureService{ cfg: cfg.GraphExplorer, } diff --git a/graph-explorer/pkg/command/server.go b/graph-explorer/pkg/command/server.go index 86dabfe32..711ae3a0b 100644 --- a/graph-explorer/pkg/command/server.go +++ b/graph-explorer/pkg/command/server.go @@ -2,33 +2,35 @@ package command import ( "context" - "strings" + "fmt" "github.com/oklog/run" "github.com/owncloud/ocis/graph-explorer/pkg/config" + "github.com/owncloud/ocis/graph-explorer/pkg/config/parser" + "github.com/owncloud/ocis/graph-explorer/pkg/logging" "github.com/owncloud/ocis/graph-explorer/pkg/metrics" "github.com/owncloud/ocis/graph-explorer/pkg/server/debug" "github.com/owncloud/ocis/graph-explorer/pkg/server/http" "github.com/owncloud/ocis/graph-explorer/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/version" "github.com/urfave/cli/v2" ) // Server is the entrypoint for the server command. func Server(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "server", - Usage: "Start integrated server", + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", Before: func(ctx *cli.Context) error { - if cfg.HTTP.Root != "/" { - cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") - } - - return ParseConfig(ctx, cfg) + return parser.ParseConfig(cfg) }, Action: func(c *cli.Context) error { - logger := NewLogger(cfg) - tracing.Configure(cfg) - + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg) + if err != nil { + return err + } var ( gr = run.Group{} ctx, cancel = func() (context.Context, context.CancelFunc) { @@ -42,7 +44,7 @@ func Server(cfg *config.Config) *cli.Command { defer cancel() - mtrcs.BuildInfo.WithLabelValues(cfg.Server.Version).Set(1) + mtrcs.BuildInfo.WithLabelValues(version.String).Set(1) { server, err := http.Server( diff --git a/graph-explorer/pkg/command/version.go b/graph-explorer/pkg/command/version.go new file mode 100644 index 000000000..39b1cadb4 --- /dev/null +++ b/graph-explorer/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/graph-explorer/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/graph-explorer/pkg/config/config.go b/graph-explorer/pkg/config/config.go index c25d415de..d84c239fd 100644 --- a/graph-explorer/pkg/config/config.go +++ b/graph-explorer/pkg/config/config.go @@ -6,101 +6,27 @@ import ( "github.com/owncloud/ocis/ocis-pkg/shared" ) -// Debug defines the available debug configuration. -type Debug struct { - Addr string `ocisConfig:"addr"` - Token string `ocisConfig:"token"` - Pprof bool `ocisConfig:"pprof"` - Zpages bool `ocisConfig:"zpages"` -} +// Config combines all available configuration parts. +type Config struct { + *shared.Commons -// HTTP defines the available http configuration. -type HTTP struct { - Addr string `ocisConfig:"addr"` - Root string `ocisConfig:"root"` - Namespace string `ocisConfig:"namespace"` -} + Service Service -// Server configures a server. -type Server struct { - Version string `ocisConfig:"version"` - Name string `ocisConfig:"name"` -} + Tracing Tracing `ocisConfig:"tracing"` + Log *Log `ocisConfig:"log"` + Debug Debug `ocisConfig:"debug"` -// Tracing defines the available tracing configuration. -type Tracing struct { - Enabled bool `ocisConfig:"enabled"` - Type string `ocisConfig:"type"` - Endpoint string `ocisConfig:"endpoint"` - Collector string `ocisConfig:"collector"` - Service string `ocisConfig:"service"` + HTTP HTTP `ocisConfig:"http"` + + GraphExplorer GraphExplorer `ocisConfig:"graph_explorer"` + + Context context.Context } // GraphExplorer defines the available graph-explorer configuration. type GraphExplorer struct { - ClientID string `ocisConfig:"client_id"` - Issuer string `ocisConfig:"issuer"` - GraphURLBase string `ocisConfig:"graph_url_base"` - GraphURLPath string `ocisConfig:"graph_url_path"` -} - -// Config combines all available configuration parts. -type Config struct { - File string `ocisConfig:"file"` - Log shared.Log `ocisConfig:"log"` - Debug Debug `ocisConfig:"debug"` - HTTP HTTP `ocisConfig:"http"` - Server Server `ocisConfig:"server"` - Tracing Tracing `ocisConfig:"tracing"` - GraphExplorer GraphExplorer `ocisConfig:"graph_explorer"` - - Context context.Context - Supervised bool -} - -// New initializes a new configuration with or without defaults. -func New() *Config { - return &Config{} -} - -// DefaultConfig provides with a working version of a config. -func DefaultConfig() *Config { - return &Config{ - Log: shared.Log{}, - Debug: Debug{ - Addr: "127.0.0.1:9136", - Token: "", - Pprof: false, - Zpages: false, - }, - HTTP: HTTP{ - Addr: "127.0.0.1:9135", - Root: "/graph-explorer", - Namespace: "com.owncloud.web", - }, - Server: Server{}, - Tracing: Tracing{ - Type: "jaeger", - Endpoint: "", - Collector: "", - Service: "graph-explorer", - }, - GraphExplorer: GraphExplorer{ - ClientID: "ocis-explorer.js", - Issuer: "https://localhost:9200", - GraphURLBase: "https://localhost:9200", - GraphURLPath: "/graph", - }, - } -} - -// GetEnv fetches a list of known env variables for this extension. It is to be used by gookit, as it provides a list -// with all the environment variables an extension supports. -func GetEnv() []string { - var r = make([]string, len(structMappings(&Config{}))) - for i := range structMappings(&Config{}) { - r = append(r, structMappings(&Config{})[i].EnvVars...) - } - - return r + ClientID string `ocisConfig:"client_id" env:"GRAPH_EXPLORER_CLIENT_ID"` + Issuer string `ocisConfig:"issuer" env:"OCIS_URL;GRAPH_EXPLORER_ISSUER"` + GraphURLBase string `ocisConfig:"graph_url_base" env:"OCIS_URL;GRAPH_EXPLORER_GRAPH_URL_BASE"` + GraphURLPath string `ocisConfig:"graph_url_path" env:"GRAPH_EXPLORER_GRAPH_URL_PATH"` } diff --git a/graph-explorer/pkg/config/debug.go b/graph-explorer/pkg/config/debug.go new file mode 100644 index 000000000..3dfc27f7b --- /dev/null +++ b/graph-explorer/pkg/config/debug.go @@ -0,0 +1,9 @@ +package config + +// Debug defines the available debug configuration. +type Debug struct { + Addr string `ocisConfig:"addr" env:"GRAPH_EXPLORER_DEBUG_ADDR"` + Token string `ocisConfig:"token" env:"GRAPH_EXPLORER_DEBUG_TOKEN"` + Pprof bool `ocisConfig:"pprof" env:"GRAPH_EXPLORER_DEBUG_PPROF"` + Zpages bool `ocisConfig:"zpages" env:"GRAPH_EXPLORER_DEBUG_ZPAGES"` +} diff --git a/graph-explorer/pkg/config/defaultconfig.go b/graph-explorer/pkg/config/defaultconfig.go new file mode 100644 index 000000000..7ea490819 --- /dev/null +++ b/graph-explorer/pkg/config/defaultconfig.go @@ -0,0 +1,32 @@ +package config + +func DefaultConfig() *Config { + return &Config{ + Debug: Debug{ + Addr: "127.0.0.1:9136", + Token: "", + Pprof: false, + Zpages: false, + }, + HTTP: HTTP{ + Addr: "127.0.0.1:9135", + Root: "/graph-explorer", + Namespace: "com.owncloud.web", + }, + Service: Service{ + Name: "graph-explorer", + }, + Tracing: Tracing{ + Enabled: false, + Type: "jaeger", + Endpoint: "", + Collector: "", + }, + GraphExplorer: GraphExplorer{ + ClientID: "ocis-explorer.js", + Issuer: "https://localhost:9200", + GraphURLBase: "https://localhost:9200", + GraphURLPath: "/graph", + }, + } +} diff --git a/graph-explorer/pkg/config/http.go b/graph-explorer/pkg/config/http.go new file mode 100644 index 000000000..8990a455e --- /dev/null +++ b/graph-explorer/pkg/config/http.go @@ -0,0 +1,16 @@ +package config + +// HTTP defines the available http configuration. +type HTTP struct { + Addr string `ocisConfig:"addr" env:"GRAPH_EXPLORER_HTTP_ADDR"` + Root string `ocisConfig:"root" env:"GRAPH_EXPLORER_HTTP_ROOT"` + Namespace string +} + +// CORS defines the available cors configuration. +type CORS struct { + AllowedOrigins []string `ocisConfig:"allowed_origins"` + AllowedMethods []string `ocisConfig:"allowed_methods"` + AllowedHeaders []string `ocisConfig:"allowed_headers"` + AllowCredentials bool `ocisConfig:"allowed_credentials"` +} diff --git a/graph-explorer/pkg/config/log.go b/graph-explorer/pkg/config/log.go new file mode 100644 index 000000000..7c9c0f538 --- /dev/null +++ b/graph-explorer/pkg/config/log.go @@ -0,0 +1,9 @@ +package config + +// Log defines the available log configuration. +type Log struct { + Level string `mapstructure:"level" env:"OCIS_LOG_LEVEL;GRAPH_EXPLORER_LOG_LEVEL"` + Pretty bool `mapstructure:"pretty" env:"OCIS_LOG_PRETTY;GRAPH_EXPLORER_LOG_PRETTY"` + Color bool `mapstructure:"color" env:"OCIS_LOG_COLOR;GRAPH_EXPLORER_LOG_COLOR"` + File string `mapstructure:"file" env:"OCIS_LOG_FILE;GRAPH_EXPLORER_LOG_FILE"` +} diff --git a/graph-explorer/pkg/config/mappings.go b/graph-explorer/pkg/config/mappings.go deleted file mode 100644 index 85014e61b..000000000 --- a/graph-explorer/pkg/config/mappings.go +++ /dev/null @@ -1,96 +0,0 @@ -package config - -import "github.com/owncloud/ocis/ocis-pkg/shared" - -// StructMappings binds a set of environment variables to a destination on cfg. Iterating over this set and editing the -// Destination value of a binding will alter the original value, as it is a pointer to its memory address. This lets -// us propagate changes easier. -func StructMappings(cfg *Config) []shared.EnvBinding { - return structMappings(cfg) -} - -// structMappings binds a set of environment variables to a destination on cfg. -func structMappings(cfg *Config) []shared.EnvBinding { - return []shared.EnvBinding{ - { - EnvVars: []string{"OCIS_LOG_LEVEL", "GRAPH_EXPLORER_LOG_LEVEL"}, - Destination: &cfg.Log.Level, - }, - { - EnvVars: []string{"OCIS_LOG_PRETTY", "GRAPH_EXPLORER_LOG_PRETTY"}, - Destination: &cfg.Log.Pretty, - }, - { - EnvVars: []string{"OCIS_LOG_COLOR", "GRAPH_EXPLORER_LOG_COLOR"}, - Destination: &cfg.Log.Color, - }, - { - EnvVars: []string{"OCIS_LOG_FILE", "GRAPH_EXPLORER_LOG_FILE"}, - Destination: &cfg.Log.File, - }, - { - EnvVars: []string{"OCIS_TRACING_ENABLED", "GRAPH_EXPLORER_TRACING_ENABLED"}, - Destination: &cfg.Tracing.Enabled, - }, - { - EnvVars: []string{"OCIS_TRACING_TYPE", "GRAPH_EXPLORER_TRACING_TYPE"}, - Destination: &cfg.Tracing.Type, - }, - { - EnvVars: []string{"OCIS_TRACING_ENDPOINT", "GRAPH_EXPLORER_TRACING_ENDPOINT"}, - Destination: &cfg.Tracing.Endpoint, - }, - { - EnvVars: []string{"OCIS_TRACING_COLLECTOR", "GRAPH_EXPLORER_TRACING_COLLECTOR"}, - Destination: &cfg.Tracing.Collector, - }, - { - EnvVars: []string{"GRAPH_EXPLORER_TRACING_SERVICE"}, - Destination: &cfg.Tracing.Service, - }, - { - EnvVars: []string{"GRAPH_EXPLORER_DEBUG_ADDR"}, - Destination: &cfg.Debug.Addr, - }, - { - EnvVars: []string{"GRAPH_EXPLORER_DEBUG_TOKEN"}, - Destination: &cfg.Debug.Token, - }, - { - EnvVars: []string{"GRAPH_EXPLORER_DEBUG_PPROF"}, - Destination: &cfg.Debug.Pprof, - }, - { - EnvVars: []string{"GRAPH_EXPLORER_DEBUG_ZPAGES"}, - Destination: &cfg.Debug.Zpages, - }, - { - EnvVars: []string{"GRAPH_EXPLORER_HTTP_ADDR"}, - Destination: &cfg.HTTP.Addr, - }, - { - EnvVars: []string{"GRAPH_EXPLORER_HTTP_ROOT"}, - Destination: &cfg.HTTP.Root, - }, - { - EnvVars: []string{"GRAPH_EXPLORER_NAMESPACE"}, - Destination: &cfg.HTTP.Namespace, - }, - { - EnvVars: []string{"OCIS_URL", "GRAPH_EXPLORER_ISSUER"}, - Destination: &cfg.GraphExplorer.Issuer, - }, - { - EnvVars: []string{"GRAPH_EXPLORER_CLIENT_ID"}, - Destination: &cfg.GraphExplorer.ClientID, - }, - { - EnvVars: []string{"OCIS_URL", "GRAPH_EXPLORER_GRAPH_URL_BASE"}, - Destination: &cfg.GraphExplorer.GraphURLBase, - }, - { - EnvVars: []string{"GRAPH_EXPLORER_GRAPH_URL_PATH"}, - Destination: &cfg.GraphExplorer.GraphURLPath, - }, - } -} diff --git a/graph-explorer/pkg/config/parser/parse.go b/graph-explorer/pkg/config/parser/parse.go new file mode 100644 index 000000000..be113e459 --- /dev/null +++ b/graph-explorer/pkg/config/parser/parse.go @@ -0,0 +1,46 @@ +package parser + +import ( + "errors" + "strings" + + "github.com/owncloud/ocis/graph-explorer/pkg/config" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" +) + +// ParseConfig loads accounts configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + // 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.Commons == nil { + cfg.Log = &config.Log{} + } + + // 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 + } + } + + // sanitize config + if cfg.HTTP.Root != "/" { + cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") + } + + return nil +} diff --git a/graph-explorer/pkg/config/service.go b/graph-explorer/pkg/config/service.go new file mode 100644 index 000000000..f98aa3d27 --- /dev/null +++ b/graph-explorer/pkg/config/service.go @@ -0,0 +1,6 @@ +package config + +// Service defines the available service configuration. +type Service struct { + Name string +} diff --git a/graph-explorer/pkg/config/tracing.go b/graph-explorer/pkg/config/tracing.go new file mode 100644 index 000000000..db55ec8c5 --- /dev/null +++ b/graph-explorer/pkg/config/tracing.go @@ -0,0 +1,9 @@ +package config + +// Tracing defines the available tracing configuration. +type Tracing struct { + Enabled bool `ocisConfig:"enabled" env:"OCIS_TRACING_ENABLED;GRAPH_EXPLORER_TRACING_ENABLED"` + Type string `ocisConfig:"type" env:"OCIS_TRACING_TYPE;GRAPH_EXPLORER_TRACING_TYPE"` + Endpoint string `ocisConfig:"endpoint" env:"OCIS_TRACING_ENDPOINT;GRAPH_EXPLORER_TRACING_ENDPOINT"` + Collector string `ocisConfig:"collector" env:"OCIS_TRACING_COLLECTOR;GRAPH_EXPLORER_TRACING_COLLECTOR"` +} diff --git a/graph-explorer/pkg/logging/logging.go b/graph-explorer/pkg/logging/logging.go new file mode 100644 index 000000000..24a2567a1 --- /dev/null +++ b/graph-explorer/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/graph-explorer/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/graph-explorer/pkg/server/debug/server.go b/graph-explorer/pkg/server/debug/server.go index 5717db3c7..116509739 100644 --- a/graph-explorer/pkg/server/debug/server.go +++ b/graph-explorer/pkg/server/debug/server.go @@ -15,7 +15,7 @@ func Server(opts ...Option) (*http.Server, error) { return debug.NewService( debug.Logger(options.Logger), - debug.Name("graph-explorer"), + debug.Name(options.Config.Service.Name), debug.Version(version.String), debug.Address(options.Config.Debug.Addr), debug.Token(options.Config.Debug.Token), @@ -32,9 +32,11 @@ func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) - // TODO(tboerger): check if services are up and running + // TODO: check if services are up and running - if _, err := io.WriteString(w, http.StatusText(http.StatusOK)); err != nil { + _, 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) } } @@ -46,9 +48,11 @@ func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) - // TODO(tboerger): check if services are up and running + // TODO: check if services are up and running - if _, err := io.WriteString(w, http.StatusText(http.StatusOK)); err != nil { + _, 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/graph-explorer/pkg/tracing/tracing.go b/graph-explorer/pkg/tracing/tracing.go index b7fe7a3f6..73b4c863c 100644 --- a/graph-explorer/pkg/tracing/tracing.go +++ b/graph-explorer/pkg/tracing/tracing.go @@ -14,7 +14,7 @@ var ( func Configure(cfg *config.Config) error { var err error if cfg.Tracing.Enabled { - if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, "graph-explorer", cfg.Tracing.Type); err != nil { + if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { return err } } diff --git a/graph/cmd/graph/main.go b/graph/cmd/graph/main.go index c43db0993..ec85f2371 100644 --- a/graph/cmd/graph/main.go +++ b/graph/cmd/graph/main.go @@ -8,7 +8,7 @@ import ( ) func main() { - if err := command.Execute(config.New()); err != nil { + if err := command.Execute(config.DefaultConfig()); err != nil { os.Exit(1) } } diff --git a/graph/pkg/command/health.go b/graph/pkg/command/health.go index 509146e3f..27c8ef9e9 100644 --- a/graph/pkg/command/health.go +++ b/graph/pkg/command/health.go @@ -5,19 +5,22 @@ import ( "net/http" "github.com/owncloud/ocis/graph/pkg/config" + "github.com/owncloud/ocis/graph/pkg/config/parser" + "github.com/owncloud/ocis/graph/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", + Name: "health", + Usage: "check health status", + Category: "info", Before: func(c *cli.Context) error { - return ParseConfig(c, cfg) + return parser.ParseConfig(cfg) }, Action: func(c *cli.Context) error { - logger := NewLogger(cfg) + logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( fmt.Sprintf( @@ -34,7 +37,7 @@ func Health(cfg *config.Config) *cli.Command { defer resp.Body.Close() - if resp.StatusCode != 200 { + if resp.StatusCode != http.StatusOK { logger.Fatal(). Int("code", resp.StatusCode). Msg("Health seems to be in bad state") diff --git a/graph/pkg/command/root.go b/graph/pkg/command/root.go index 20d07c88e..60c389880 100644 --- a/graph/pkg/command/root.go +++ b/graph/pkg/command/root.go @@ -4,86 +4,43 @@ import ( "context" "os" - "github.com/owncloud/ocis/ocis-pkg/shared" - + "github.com/owncloud/ocis/ocis-pkg/clihelper" "github.com/thejerf/suture/v4" "github.com/owncloud/ocis/graph/pkg/config" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/version" "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-graph command. func Execute(cfg *config.Config) error { - app := &cli.App{ + app := clihelper.DefaultApp(&cli.App{ Name: "ocis-graph", - Version: version.String, Usage: "Serve Graph API for oCIS", - Compiled: version.Compiled(), - Authors: []*cli.Author{ - { - Name: "ownCloud GmbH", - Email: "support@owncloud.com", - }, - }, - Before: func(c *cli.Context) error { - cfg.Server.Version = version.String - return ParseConfig(c, cfg) - }, - Commands: []*cli.Command{ - Server(cfg), - Health(cfg), - }, - } + Commands: GetCommands(cfg), + }) cli.HelpFlag = &cli.BoolFlag{ Name: "help,h", Usage: "Show the help", } - cli.VersionFlag = &cli.BoolFlag{ - Name: "version,v", - Usage: "Print the version", - } return app.Run(os.Args) } -// NewLogger initializes a service-specific logger instance. -func NewLogger(cfg *config.Config) log.Logger { - return log.NewLogger( - log.Name("graph"), - log.Level(cfg.Log.Level), - log.Pretty(cfg.Log.Pretty), - log.Color(cfg.Log.Color), - log.File(cfg.Log.File), - ) -} - -// ParseConfig loads graph configuration from known paths. -func ParseConfig(c *cli.Context, cfg *config.Config) error { - conf, err := ociscfg.BindSourcesToStructs("graph", cfg) - if err != nil { - return err - } - - // 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 = &shared.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.Commons == nil { - cfg.Log = &shared.Log{} - } - - conf.LoadOSEnv(config.GetEnv(cfg), false) - bindings := config.StructMappings(cfg) - return ociscfg.BindEnv(conf, bindings) -} - // SutureService allows for the graph command to be embedded and supervised by a suture supervisor tree. type SutureService struct { cfg *config.Config diff --git a/graph/pkg/command/server.go b/graph/pkg/command/server.go index 32cd2a4ab..38c012a20 100644 --- a/graph/pkg/command/server.go +++ b/graph/pkg/command/server.go @@ -2,37 +2,33 @@ package command import ( "context" - "strings" + "fmt" "github.com/oklog/run" "github.com/owncloud/ocis/graph/pkg/config" + "github.com/owncloud/ocis/graph/pkg/config/parser" + "github.com/owncloud/ocis/graph/pkg/logging" "github.com/owncloud/ocis/graph/pkg/metrics" "github.com/owncloud/ocis/graph/pkg/server/debug" "github.com/owncloud/ocis/graph/pkg/server/http" "github.com/owncloud/ocis/graph/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/version" "github.com/urfave/cli/v2" ) // Server is the entrypoint for the server command. func Server(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "server", - Usage: "Start integrated server", - Before: func(ctx *cli.Context) error { - if cfg.HTTP.Root != "/" { - cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") - } - - if err := ParseConfig(ctx, cfg); err != nil { - return err - } - - return nil + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + return parser.ParseConfig(cfg) }, Action: func(c *cli.Context) error { - logger := NewLogger(cfg) - - if err := tracing.Configure(cfg); err != nil { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg) + if err != nil { return err } @@ -47,7 +43,7 @@ func Server(cfg *config.Config) *cli.Command { defer cancel() - mtrcs.BuildInfo.WithLabelValues(cfg.Server.Version).Set(1) + mtrcs.BuildInfo.WithLabelValues(version.String).Set(1) { server, err := http.Server( diff --git a/graph/pkg/command/version.go b/graph/pkg/command/version.go new file mode 100644 index 000000000..8e85a27dd --- /dev/null +++ b/graph/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/graph/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/graph/pkg/config/config.go b/graph/pkg/config/config.go index 0cf4a4735..523bcfdb0 100644 --- a/graph/pkg/config/config.go +++ b/graph/pkg/config/config.go @@ -6,150 +6,54 @@ import ( "github.com/owncloud/ocis/ocis-pkg/shared" ) -// Debug defines the available debug configuration. -type Debug struct { - Addr string `ocisConfig:"addr"` - Token string `ocisConfig:"token"` - Pprof bool `ocisConfig:"pprof"` - Zpages bool `ocisConfig:"zpages"` -} - -// HTTP defines the available http configuration. -type HTTP struct { - Addr string `ocisConfig:"addr"` - Namespace string `ocisConfig:"namespace"` - Root string `ocisConfig:"root"` -} - -// Server configures a server. -type Server struct { - Version string `ocisConfig:"version"` - Name string `ocisConfig:"name"` -} - -// Tracing defines the available tracing configuration. -type Tracing struct { - Enabled bool `ocisConfig:"enabled"` - Type string `ocisConfig:"type"` - Endpoint string `ocisConfig:"endpoint"` - Collector string `ocisConfig:"collector"` - Service string `ocisConfig:"service"` -} - -// Reva defines all available REVA configuration. -type Reva struct { - Address string `ocisConfig:"address"` -} - -// TokenManager is the config for using the reva token manager -type TokenManager struct { - JWTSecret string `ocisConfig:"jwt_secret"` -} - -type Spaces struct { - WebDavBase string `ocisConfig:"webdav_base"` - WebDavPath string `ocisConfig:"webdav_path"` - DefaultQuota string `ocisConfig:"default_quota"` -} - -type LDAP struct { - URI string `ocisConfig:"uri"` - BindDN string `ocisConfig:"bind_dn"` - BindPassword string `ocisConfig:"bind_password"` - - UserBaseDN string `ocisConfig:"user_base_dn"` - UserSearchScope string `ocisConfig:"user_search_scope"` - UserFilter string `ocisConfig:"user_filter"` - UserEmailAttribute string `ocisConfig:"user_mail_attribute"` - UserDisplayNameAttribute string `ocisConfig:"user_displayname_attribute"` - UserNameAttribute string `ocisConfig:"user_name_attribute"` - UserIDAttribute string `ocisConfig:"user_id_attribute"` - - GroupBaseDN string `ocisConfig:"group_base_dn"` - GroupSearchScope string `ocisConfig:"group_search_scope"` - GroupFilter string `ocisConfig:"group_filter"` - GroupNameAttribute string `ocisConfig:"group_name_attribute"` - GroupIDAttribute string `ocisConfig:"group_id_attribute"` -} - -type Identity struct { - Backend string `ocisConfig:"backend"` - LDAP LDAP `ocisConfig:"ldap"` -} - // Config combines all available configuration parts. type Config struct { *shared.Commons - File string `ocisConfig:"file"` - Log *shared.Log `ocisConfig:"log"` - Debug Debug `ocisConfig:"debug"` - HTTP HTTP `ocisConfig:"http"` - Server Server `ocisConfig:"server"` - Tracing Tracing `ocisConfig:"tracing"` + Service Service + + Tracing Tracing `ocisConfig:"tracing"` + Log *Log `ocisConfig:"log"` + Debug Debug `ocisConfig:"debug"` + + HTTP HTTP `ocisConfig:"http"` + Reva Reva `ocisConfig:"reva"` TokenManager TokenManager `ocisConfig:"token_manager"` - Spaces Spaces `ocisConfig:"spaces"` - Identity Identity `ocisConfig:"identity"` - Context context.Context - Supervised bool + Spaces Spaces `ocisConfig:"spaces"` + Identity Identity `ocisConfig:"identity"` + + Context context.Context } -// New initializes a new configuration with or without defaults. -func New() *Config { - return &Config{} +type Spaces struct { + WebDavBase string `ocisConfig:"webdav_base" env:"OCIS_URL;GRAPH_SPACES_WEBDAV_BASE"` + WebDavPath string `ocisConfig:"webdav_path" env:"GRAPH_SPACES_WEBDAV_PATH"` + DefaultQuota string `ocisConfig:"default_quota" env:"GRAPH_SPACES_DEFAULT_QUOTA"` } -func DefaultConfig() *Config { - return &Config{ - Debug: Debug{ - Addr: "127.0.0.1:9124", - Token: "", - }, - HTTP: HTTP{ - Addr: "127.0.0.1:9120", - Namespace: "com.owncloud.web", - Root: "/graph", - }, - Server: Server{}, - Tracing: Tracing{ - Enabled: false, - Type: "jaeger", - Service: "graph", - }, - Reva: Reva{ - Address: "127.0.0.1:9142", - }, - TokenManager: TokenManager{ - JWTSecret: "Pive-Fumkiu4", - }, - Spaces: Spaces{ - WebDavBase: "https://localhost:9200", - WebDavPath: "/dav/spaces/", - DefaultQuota: "1000000000", - }, - Identity: Identity{ - Backend: "cs3", - LDAP: LDAP{ - URI: "ldap://localhost:9125", - BindDN: "", - BindPassword: "", - UserBaseDN: "ou=users,dc=ocis,dc=test", - UserSearchScope: "sub", - UserFilter: "(objectClass=posixaccount)", - UserEmailAttribute: "mail", - UserDisplayNameAttribute: "displayName", - UserNameAttribute: "uid", - // FIXME: switch this to some more widely available attribute by default - // ideally this needs to be constant for the lifetime of a users - UserIDAttribute: "ownclouduuid", - GroupBaseDN: "ou=groups,dc=ocis,dc=test", - GroupSearchScope: "sub", - GroupFilter: "(objectclass=groupOfNames)", - GroupNameAttribute: "cn", - GroupIDAttribute: "cn", - }, - }, - } +type LDAP struct { + URI string `ocisConfig:"uri" env:"GRAPH_LDAP_URI"` + BindDN string `ocisConfig:"bind_dn" env:"GRAPH_LDAP_BIND_DN"` + BindPassword string `ocisConfig:"bind_password" env:"GRAPH_LDAP_BIND_PASSWORD"` + + UserBaseDN string `ocisConfig:"user_base_dn" env:"GRAPH_LDAP_USER_BASE_DN"` + UserSearchScope string `ocisConfig:"user_search_scope" env:"GRAPH_LDAP_USER_SCOPE"` + UserFilter string `ocisConfig:"user_filter" env:"GRAPH_LDAP_USER_FILTER"` + UserEmailAttribute string `ocisConfig:"user_mail_attribute" env:"GRAPH_LDAP_USER_EMAIL_ATTRIBUTE"` + UserDisplayNameAttribute string `ocisConfig:"user_displayname_attribute" env:"GRAPH_LDAP_USER_DISPLAYNAME_ATTRIBUTE"` + UserNameAttribute string `ocisConfig:"user_name_attribute" env:"GRAPH_LDAP_USER_NAME_ATTRIBUTE"` + UserIDAttribute string `ocisConfig:"user_id_attribute" env:"GRAPH_LDAP_USER_UID_ATTRIBUTE"` + + GroupBaseDN string `ocisConfig:"group_base_dn" env:"GRAPH_LDAP_GROUP_BASE_DN"` + GroupSearchScope string `ocisConfig:"group_search_scope" env:"GRAPH_LDAP_GROUP_SEARCH_SCOPE"` + GroupFilter string `ocisConfig:"group_filter" env:"GRAPH_LDAP_GROUP_FILTER"` + GroupNameAttribute string `ocisConfig:"group_name_attribute" env:"GRAPH_LDAP_GROUP_NAME_ATTRIBUTE"` + GroupIDAttribute string `ocisConfig:"group_id_attribute" env:"GRAPH_LDAP_GROUP_ID_ATTRIBUTE"` +} + +type Identity struct { + Backend string `ocisConfig:"backend" env:"GRAPH_IDENTITY_BACKEND"` + LDAP LDAP `ocisConfig:"ldap"` } diff --git a/graph/pkg/config/debug.go b/graph/pkg/config/debug.go new file mode 100644 index 000000000..c1284be91 --- /dev/null +++ b/graph/pkg/config/debug.go @@ -0,0 +1,9 @@ +package config + +// Debug defines the available debug configuration. +type Debug struct { + Addr string `ocisConfig:"addr" env:"GRAPH_DEBUG_ADDR"` + Token string `ocisConfig:"token" env:"GRAPH_DEBUG_TOKEN"` + Pprof bool `ocisConfig:"pprof" env:"GRAPH_DEBUG_PPROF"` + Zpages bool `ocisConfig:"zpages" env:"GRAPH_DEBUG_ZPAGES"` +} diff --git a/graph/pkg/config/defaultconfig.go b/graph/pkg/config/defaultconfig.go new file mode 100644 index 000000000..5879b9487 --- /dev/null +++ b/graph/pkg/config/defaultconfig.go @@ -0,0 +1,57 @@ +package config + +func DefaultConfig() *Config { + return &Config{ + Debug: Debug{ + Addr: "127.0.0.1:9124", + Token: "", + }, + HTTP: HTTP{ + Addr: "127.0.0.1:9120", + Namespace: "com.owncloud.graph", + Root: "/graph", + }, + Service: Service{ + Name: "graph", + }, + Tracing: Tracing{ + Enabled: false, + Type: "jaeger", + Endpoint: "", + Collector: "", + }, + Reva: Reva{ + Address: "127.0.0.1:9142", + }, + TokenManager: TokenManager{ + JWTSecret: "Pive-Fumkiu4", + }, + Spaces: Spaces{ + WebDavBase: "https://localhost:9200", + WebDavPath: "/dav/spaces/", + DefaultQuota: "1000000000", + }, + Identity: Identity{ + Backend: "cs3", + LDAP: LDAP{ + URI: "ldap://localhost:9125", + BindDN: "", + BindPassword: "", + UserBaseDN: "ou=users,dc=ocis,dc=test", + UserSearchScope: "sub", + UserFilter: "(objectClass=posixaccount)", + UserEmailAttribute: "mail", + UserDisplayNameAttribute: "displayName", + UserNameAttribute: "uid", + // FIXME: switch this to some more widely available attribute by default + // ideally this needs to be constant for the lifetime of a users + UserIDAttribute: "ownclouduuid", + GroupBaseDN: "ou=groups,dc=ocis,dc=test", + GroupSearchScope: "sub", + GroupFilter: "(objectclass=groupOfNames)", + GroupNameAttribute: "cn", + GroupIDAttribute: "cn", + }, + }, + } +} diff --git a/graph/pkg/config/http.go b/graph/pkg/config/http.go new file mode 100644 index 000000000..64351105b --- /dev/null +++ b/graph/pkg/config/http.go @@ -0,0 +1,8 @@ +package config + +// HTTP defines the available http configuration. +type HTTP struct { + Addr string `ocisConfig:"addr" env:"GRAPH_HTTP_ADDR"` + Namespace string + Root string `ocisConfig:"root" env:"GRAPH_HTTP_ROOT"` +} diff --git a/graph/pkg/config/log.go b/graph/pkg/config/log.go new file mode 100644 index 000000000..3f1f84603 --- /dev/null +++ b/graph/pkg/config/log.go @@ -0,0 +1,9 @@ +package config + +// Log defines the available log configuration. +type Log struct { + Level string `mapstructure:"level" env:"OCIS_LOG_LEVEL;GRAPH_LOG_LEVEL"` + Pretty bool `mapstructure:"pretty" env:"OCIS_LOG_PRETTY;GRAPH_LOG_PRETTY"` + Color bool `mapstructure:"color" env:"OCIS_LOG_COLOR;GRAPH_LOG_COLOR"` + File string `mapstructure:"file" env:"OCIS_LOG_FILE;GRAPH_LOG_FILE"` +} diff --git a/graph/pkg/config/mappings.go b/graph/pkg/config/mappings.go deleted file mode 100644 index 6798e8068..000000000 --- a/graph/pkg/config/mappings.go +++ /dev/null @@ -1,179 +0,0 @@ -package config - -import "github.com/owncloud/ocis/ocis-pkg/shared" - -// GetEnv fetches a list of known env variables for this extension. It is to be used by gookit, as it provides a list -// with all the environment variables an extension supports. -func GetEnv(cfg *Config) []string { - var r = make([]string, len(structMappings(cfg))) - for i := range structMappings(cfg) { - r = append(r, structMappings(cfg)[i].EnvVars...) - } - - return r -} - -// StructMappings binds a set of environment variables to a destination on cfg. Iterating over this set and editing the -// Destination value of a binding will alter the original value, as it is a pointer to its memory address. This lets -// us propagate changes easier. -func StructMappings(cfg *Config) []shared.EnvBinding { - return structMappings(cfg) -} - -// structMappings binds a set of environment variables to a destination on cfg. -func structMappings(cfg *Config) []shared.EnvBinding { - return []shared.EnvBinding{ - { - EnvVars: []string{"GRAPH_CONFIG_FILE"}, - Destination: &cfg.File, - }, - { - EnvVars: []string{"OCIS_LOG_LEVEL", "GRAPH_LOG_LEVEL"}, - Destination: &cfg.Log.Level, - }, - { - EnvVars: []string{"OCIS_LOG_PRETTY", "GRAPH_LOG_PRETTY"}, - Destination: &cfg.Log.Pretty, - }, - { - EnvVars: []string{"OCIS_LOG_COLOR", "GRAPH_LOG_COLOR"}, - Destination: &cfg.Log.Color, - }, - { - EnvVars: []string{"OCIS_LOG_FILE", "GRAPH_LOG_FILE"}, - Destination: &cfg.Log.File, - }, - { - EnvVars: []string{"OCIS_TRACING_ENABLED", "GRAPH_TRACING_ENABLED"}, - Destination: &cfg.Tracing.Enabled, - }, - { - EnvVars: []string{"OCIS_TRACING_TYPE", "GRAPH_TRACING_TYPE"}, - Destination: &cfg.Tracing.Type, - }, - { - EnvVars: []string{"OCIS_TRACING_ENDPOINT", "GRAPH_TRACING_ENDPOINT"}, - Destination: &cfg.Tracing.Endpoint, - }, - { - EnvVars: []string{"OCIS_TRACING_COLLECTOR", "GRAPH_TRACING_COLLECTOR"}, - Destination: &cfg.Tracing.Collector, - }, - { - EnvVars: []string{"GRAPH_TRACING_SERVICE"}, - Destination: &cfg.Tracing.Service, - }, - { - EnvVars: []string{"GRAPH_DEBUG_ADDR"}, - Destination: &cfg.Debug.Addr, - }, - { - EnvVars: []string{"GRAPH_DEBUG_TOKEN"}, - Destination: &cfg.Debug.Token, - }, - { - EnvVars: []string{"GRAPH_DEBUG_PPROF"}, - Destination: &cfg.Debug.Pprof, - }, - { - EnvVars: []string{"GRAPH_DEBUG_ZPAGES"}, - Destination: &cfg.Debug.Zpages, - }, - { - EnvVars: []string{"GRAPH_HTTP_ADDR"}, - Destination: &cfg.HTTP.Addr, - }, - { - EnvVars: []string{"GRAPH_HTTP_ROOT"}, - Destination: &cfg.HTTP.Root, - }, - { - EnvVars: []string{"GRAPH_HTTP_NAMESPACE"}, - Destination: &cfg.HTTP.Namespace, - }, - { - EnvVars: []string{"OCIS_URL", "GRAPH_SPACES_WEBDAV_BASE"}, - Destination: &cfg.Spaces.WebDavBase, - }, - { - EnvVars: []string{"GRAPH_SPACES_WEBDAV_PATH"}, - Destination: &cfg.Spaces.WebDavPath, - }, - { - EnvVars: []string{"GRAPH_SPACES_DEFAULT_QUOTA"}, - Destination: &cfg.Spaces.DefaultQuota, - }, - { - EnvVars: []string{"OCIS_JWT_SECRET", "GRAPH_JWT_SECRET"}, - Destination: &cfg.TokenManager.JWTSecret, - }, - { - EnvVars: []string{"REVA_GATEWAY"}, - Destination: &cfg.Reva.Address, - }, - { - EnvVars: []string{"GRAPH_IDENTITY_BACKEND"}, - Destination: &cfg.Identity.Backend, - }, - { - EnvVars: []string{"GRAPH_LDAP_URI"}, - Destination: &cfg.Identity.LDAP.URI, - }, - { - EnvVars: []string{"GRAPH_LDAP_BIND_DN"}, - Destination: &cfg.Identity.LDAP.BindDN, - }, - { - EnvVars: []string{"GRAPH_LDAP_BIND_PASSWORD"}, - Destination: &cfg.Identity.LDAP.BindPassword, - }, - { - EnvVars: []string{"GRAPH_LDAP_USER_BASE_DN"}, - Destination: &cfg.Identity.LDAP.UserBaseDN, - }, - { - EnvVars: []string{"GRAPH_LDAP_USER_EMAIL_ATTRIBUTE"}, - Destination: &cfg.Identity.LDAP.UserEmailAttribute, - }, - { - EnvVars: []string{"GRAPH_LDAP_USER_DISPLAYNAME_ATTRIBUTE"}, - Destination: &cfg.Identity.LDAP.UserDisplayNameAttribute, - }, - { - EnvVars: []string{"GRAPH_LDAP_USER_NAME_ATTRIBUTE"}, - Destination: &cfg.Identity.LDAP.UserNameAttribute, - }, - { - EnvVars: []string{"GRAPH_LDAP_USER_UID_ATTRIBUTE"}, - Destination: &cfg.Identity.LDAP.UserIDAttribute, - }, - { - EnvVars: []string{"GRAPH_LDAP_USER_FILTER"}, - Destination: &cfg.Identity.LDAP.UserFilter, - }, - { - EnvVars: []string{"GRAPH_LDAP_USER_SCOPE"}, - Destination: &cfg.Identity.LDAP.UserSearchScope, - }, - { - EnvVars: []string{"GRAPH_LDAP_GROUP_BASE_DN"}, - Destination: &cfg.Identity.LDAP.GroupBaseDN, - }, - { - EnvVars: []string{"GRAPH_LDAP_GROUP_SEARCH_SCOPE"}, - Destination: &cfg.Identity.LDAP.GroupSearchScope, - }, - { - EnvVars: []string{"GRAPH_LDAP_GROUP_FILTER"}, - Destination: &cfg.Identity.LDAP.GroupFilter, - }, - { - EnvVars: []string{"GRAPH_LDAP_GROUP_NAME_ATTRIBUTE"}, - Destination: &cfg.Identity.LDAP.GroupNameAttribute, - }, - { - EnvVars: []string{"GRAPH_LDAP_GROUP_ID_ATTRIBUTE"}, - Destination: &cfg.Identity.LDAP.GroupIDAttribute, - }, - } -} diff --git a/graph/pkg/config/parser/parse.go b/graph/pkg/config/parser/parse.go new file mode 100644 index 000000000..885682154 --- /dev/null +++ b/graph/pkg/config/parser/parse.go @@ -0,0 +1,46 @@ +package parser + +import ( + "errors" + "strings" + + "github.com/owncloud/ocis/graph/pkg/config" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" +) + +// ParseConfig loads accounts configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + // 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.Commons == nil { + cfg.Log = &config.Log{} + } + + // 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 + } + } + + // sanitize config + if cfg.HTTP.Root != "/" { + cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") + } + + return nil +} diff --git a/graph/pkg/config/reva.go b/graph/pkg/config/reva.go new file mode 100644 index 000000000..31f48fbb6 --- /dev/null +++ b/graph/pkg/config/reva.go @@ -0,0 +1,11 @@ +package config + +// Reva defines all available REVA configuration. +type Reva struct { + Address string `ocisConfig:"address" env:"REVA_GATEWAY"` +} + +// TokenManager is the config for using the reva token manager +type TokenManager struct { + JWTSecret string `ocisConfig:"jwt_secret" env:"OCIS_JWT_SECRET;OCS_JWT_SECRET"` +} diff --git a/graph/pkg/config/service.go b/graph/pkg/config/service.go new file mode 100644 index 000000000..f98aa3d27 --- /dev/null +++ b/graph/pkg/config/service.go @@ -0,0 +1,6 @@ +package config + +// Service defines the available service configuration. +type Service struct { + Name string +} diff --git a/graph/pkg/config/tracing.go b/graph/pkg/config/tracing.go new file mode 100644 index 000000000..077a4819a --- /dev/null +++ b/graph/pkg/config/tracing.go @@ -0,0 +1,9 @@ +package config + +// Tracing defines the available tracing configuration. +type Tracing struct { + Enabled bool `ocisConfig:"enabled" env:"OCIS_TRACING_ENABLED;GRAPH_TRACING_ENABLED"` + Type string `ocisConfig:"type" env:"OCIS_TRACING_TYPE;GRAPH_TRACING_TYPE"` + Endpoint string `ocisConfig:"endpoint" env:"OCIS_TRACING_ENDPOINT;GRAPH_TRACING_ENDPOINT"` + Collector string `ocisConfig:"collector" env:"OCIS_TRACING_COLLECTOR;GRAPH_TRACING_COLLECTOR"` +} diff --git a/graph/pkg/logging/logging.go b/graph/pkg/logging/logging.go new file mode 100644 index 000000000..daf29f40b --- /dev/null +++ b/graph/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/graph/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/graph/pkg/server/debug/server.go b/graph/pkg/server/debug/server.go index fe979c8ee..240b1f936 100644 --- a/graph/pkg/server/debug/server.go +++ b/graph/pkg/server/debug/server.go @@ -15,7 +15,7 @@ func Server(opts ...Option) (*http.Server, error) { return debug.NewService( debug.Logger(options.Logger), - debug.Name("graph"), + debug.Name(options.Config.Service.Name), debug.Version(version.String), debug.Address(options.Config.Debug.Addr), debug.Token(options.Config.Debug.Token), @@ -32,9 +32,11 @@ func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) - // TODO(tboerger): check if services are up and running + // TODO: check if services are up and running - if _, err := io.WriteString(w, http.StatusText(http.StatusOK)); err != nil { + _, 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) } } @@ -46,9 +48,11 @@ func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) - // TODO(tboerger): check if services are up and running + // TODO: check if services are up and running - if _, err := io.WriteString(w, http.StatusText(http.StatusOK)); err != nil { + _, 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/graph/pkg/tracing/tracing.go b/graph/pkg/tracing/tracing.go index 5e67b08eb..3f2d775a5 100644 --- a/graph/pkg/tracing/tracing.go +++ b/graph/pkg/tracing/tracing.go @@ -14,7 +14,7 @@ var ( func Configure(cfg *config.Config) error { var err error if cfg.Tracing.Enabled { - if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, "graph", cfg.Tracing.Type); err != nil { + if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { return err } } diff --git a/idp/pkg/command/health.go b/idp/pkg/command/health.go index 813470bd4..d1da86af7 100644 --- a/idp/pkg/command/health.go +++ b/idp/pkg/command/health.go @@ -5,19 +5,22 @@ import ( "net/http" "github.com/owncloud/ocis/idp/pkg/config" + "github.com/owncloud/ocis/idp/pkg/config/parser" + "github.com/owncloud/ocis/idp/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", + Name: "health", + Usage: "check health status", + Category: "info", Before: func(c *cli.Context) error { - return ParseConfig(c, cfg) + return parser.ParseConfig(cfg) }, Action: func(c *cli.Context) error { - logger := NewLogger(cfg) + logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( fmt.Sprintf( @@ -34,7 +37,7 @@ func Health(cfg *config.Config) *cli.Command { defer resp.Body.Close() - if resp.StatusCode != 200 { + if resp.StatusCode != http.StatusOK { logger.Fatal(). Int("code", resp.StatusCode). Msg("Health seems to be in bad state") diff --git a/idp/pkg/command/root.go b/idp/pkg/command/root.go index e0d5ba775..787381c7e 100644 --- a/idp/pkg/command/root.go +++ b/idp/pkg/command/root.go @@ -4,91 +4,43 @@ import ( "context" "os" - "github.com/owncloud/ocis/ocis-pkg/shared" - "github.com/owncloud/ocis/idp/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/version" "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-idp command. func Execute(cfg *config.Config) error { - app := &cli.App{ + app := clihelper.DefaultApp(&cli.App{ Name: "ocis-idp", - Version: version.String, Usage: "Serve IDP API for oCIS", - Compiled: version.Compiled(), - Authors: []*cli.Author{ - { - Name: "ownCloud GmbH", - Email: "support@owncloud.com", - }, - }, - Before: func(c *cli.Context) error { - cfg.Service.Version = version.String - return nil - }, - - Commands: []*cli.Command{ - Server(cfg), - Health(cfg), - PrintVersion(cfg), - }, - } + Commands: GetCommands(cfg), + }) cli.HelpFlag = &cli.BoolFlag{ Name: "help,h", Usage: "Show the help", } - cli.VersionFlag = &cli.BoolFlag{ - Name: "version,v", - Usage: "Print the version", - } - return app.Run(os.Args) } -// NewLogger initializes a service-specific logger instance. -func NewLogger(cfg *config.Config) log.Logger { - return log.NewLogger( - log.Name("idp"), - log.Level(cfg.Log.Level), - log.Pretty(cfg.Log.Pretty), - log.Color(cfg.Log.Color), - log.File(cfg.Log.File), - ) -} - -// ParseConfig loads idp configuration from known paths. -func ParseConfig(c *cli.Context, cfg *config.Config) error { - conf, err := ociscfg.BindSourcesToStructs("idp", cfg) - if err != nil { - return err - } - - // 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 = &shared.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.Commons == nil { - cfg.Log = &shared.Log{} - } - - // load all env variables relevant to the config in the current context. - conf.LoadOSEnv(config.GetEnv(cfg), false) - - bindings := config.StructMappings(cfg) - return ociscfg.BindEnv(conf, bindings) -} - // SutureService allows for the idp command to be embedded and supervised by a suture supervisor tree. type SutureService struct { cfg *config.Config diff --git a/idp/pkg/command/server.go b/idp/pkg/command/server.go index d3b62034f..c3bdfec70 100644 --- a/idp/pkg/command/server.go +++ b/idp/pkg/command/server.go @@ -2,38 +2,35 @@ package command import ( "context" - "strings" + "fmt" "github.com/oklog/run" "github.com/owncloud/ocis/idp/pkg/config" + "github.com/owncloud/ocis/idp/pkg/config/parser" + "github.com/owncloud/ocis/idp/pkg/logging" "github.com/owncloud/ocis/idp/pkg/metrics" "github.com/owncloud/ocis/idp/pkg/server/debug" "github.com/owncloud/ocis/idp/pkg/server/http" "github.com/owncloud/ocis/idp/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/version" "github.com/urfave/cli/v2" ) // Server is the entrypoint for the server command. func Server(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "server", - Usage: "Start integrated server", - Before: func(ctx *cli.Context) error { - if err := ParseConfig(ctx, cfg); err != nil { - return err - } - - if cfg.HTTP.Root != "/" { - cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") - } - - return nil + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + return parser.ParseConfig(cfg) }, Action: func(c *cli.Context) error { - logger := NewLogger(cfg) - - tracing.Configure(cfg) - + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg) + if err != nil { + return err + } var ( gr = run.Group{} ctx, cancel = func() (context.Context, context.CancelFunc) { @@ -47,7 +44,7 @@ func Server(cfg *config.Config) *cli.Command { defer cancel() - metrics.BuildInfo.WithLabelValues(cfg.Service.Version).Set(1) + metrics.BuildInfo.WithLabelValues(version.String).Set(1) { server, err := http.Server( diff --git a/idp/pkg/command/version.go b/idp/pkg/command/version.go index 7408d59f6..22d4f4ce6 100644 --- a/idp/pkg/command/version.go +++ b/idp/pkg/command/version.go @@ -5,30 +5,33 @@ import ( "os" "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" tw "github.com/olekukonko/tablewriter" "github.com/owncloud/ocis/idp/pkg/config" "github.com/urfave/cli/v2" ) -// PrintVersion prints the service versions of all running instances. -func PrintVersion(cfg *config.Config) *cli.Command { +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "version", - Usage: "Print the versions of the running instances", - Before: func(c *cli.Context) error { - return ParseConfig(c, cfg) - }, + 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.Service.Namespace + "." + cfg.Service.Name) + services, err := reg.GetService(cfg.HTTP.Namespace + "." + cfg.Service.Name) if err != nil { - fmt.Println(fmt.Errorf("could not get idp services from the registry: %v", err)) + 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 idp service found.") + fmt.Println("No running " + cfg.Service.Name + " service found.") return nil } diff --git a/idp/pkg/config/config.go b/idp/pkg/config/config.go index ffb7e781e..7c16b9b1b 100644 --- a/idp/pkg/config/config.go +++ b/idp/pkg/config/config.go @@ -2,191 +2,99 @@ package config import ( "context" - "path" "github.com/owncloud/ocis/ocis-pkg/shared" - - "github.com/owncloud/ocis/ocis-pkg/config/defaults" ) -// Debug defines the available debug configuration. -type Debug struct { - Addr string `ocisConfig:"addr"` - Token string `ocisConfig:"token"` - Pprof bool `ocisConfig:"pprof"` - Zpages bool `ocisConfig:"zpages"` -} - -// HTTP defines the available http configuration. -type HTTP struct { - Addr string `ocisConfig:"addr"` - Root string `ocisConfig:"root"` - TLSCert string `ocisConfig:"tls_cert"` - TLSKey string `ocisConfig:"tls_key"` - TLS bool `ocisConfig:"tls"` -} - -// Ldap defines the available LDAP configuration. -type Ldap struct { - URI string `ocisConfig:"uri"` - BindDN string `ocisConfig:"bind_dn"` - BindPassword string `ocisConfig:"bind_password"` - BaseDN string `ocisConfig:"base_dn"` - Scope string `ocisConfig:"scope"` - LoginAttribute string `ocisConfig:"login_attribute"` - EmailAttribute string `ocisConfig:"email_attribute"` - NameAttribute string `ocisConfig:"name_attribute"` - UUIDAttribute string `ocisConfig:"uuid_attribute"` - UUIDAttributeType string `ocisConfig:"uuid_attribute_type"` - Filter string `ocisConfig:"filter"` -} - -// Service defines the available service configuration. -type Service struct { - Name string `ocisConfig:"name"` - Namespace string `ocisConfig:"namespace"` - Version string `ocisConfig:"version"` -} - -// Tracing defines the available tracing configuration. -type Tracing struct { - Enabled bool `ocisConfig:"enabled"` - Type string `ocisConfig:"type"` - Endpoint string `ocisConfig:"endpoint"` - Collector string `ocisConfig:"collector"` - Service string `ocisConfig:"service"` -} - -// Asset defines the available asset configuration. -type Asset struct { - Path string `ocisConfig:"asset"` -} - -type Settings struct { - Iss string `ocisConfig:"iss"` - IdentityManager string `ocisConfig:"identity_manager"` - URIBasePath string `ocisConfig:"uri_base_path"` - SignInURI string `ocisConfig:"sign_in_uri"` - SignedOutURI string `ocisConfig:"signed_out_uri"` - AuthorizationEndpointURI string `ocisConfig:"authorization_endpoint_uri"` - EndsessionEndpointURI string `ocisConfig:"end_session_endpoint_uri"` - Insecure bool `ocisConfig:"insecure"` - TrustedProxy []string `ocisConfig:"trusted_proxy"` - AllowScope []string `ocisConfig:"allow_scope"` - AllowClientGuests bool `ocisConfig:"allow_client_guests"` - AllowDynamicClientRegistration bool `ocisConfig:"allow_dynamic_client_registration"` - EncryptionSecretFile string `ocisConfig:"encrypt_secret_file"` - Listen string `ocisConfig:"listen"` - IdentifierClientDisabled bool `ocisConfig:"identifier_client_disabled"` - IdentifierClientPath string `ocisConfig:"identifier_client_path"` - IdentifierRegistrationConf string `ocisConfig:"identifier_registration_conf"` - IdentifierScopesConf string `ocisConfig:"identifier_scopes_conf"` - IdentifierDefaultBannerLogo string `ocisConfig:"identifier_default_banner_logo"` - IdentifierDefaultSignInPageText string `ocisConfig:"identifier_default_sign_in_page_text"` - IdentifierDefaultUsernameHintText string `ocisConfig:"identifier_default_username_hint_text"` - SigningKid string `ocisConfig:"sign_in_kid"` - SigningMethod string `ocisConfig:"sign_in_method"` - SigningPrivateKeyFiles []string `ocisConfig:"sign_in_private_key_files"` - ValidationKeysPath string `ocisConfig:"validation_keys_path"` - CookieBackendURI string `ocisConfig:"cookie_backend_uri"` - CookieNames []string `ocisConfig:"cookie_names"` - AccessTokenDurationSeconds uint64 `ocisConfig:"access_token_duration_seconds"` - IDTokenDurationSeconds uint64 `ocisConfig:"id_token_duration_seconds"` - RefreshTokenDurationSeconds uint64 `ocisConfig:"refresh_token_duration_seconds"` - DyamicClientSecretDurationSeconds uint64 `ocisConfig:"dynamic_client_secret_duration_seconds"` -} - // Config combines all available configuration parts. type Config struct { *shared.Commons - File string `ocisConfig:"file"` - Log *shared.Log `ocisConfig:"log"` - Debug Debug `ocisConfig:"debug"` - HTTP HTTP `ocisConfig:"http"` - Tracing Tracing `ocisConfig:"tracing"` - Asset Asset `ocisConfig:"asset"` - IDP Settings `ocisConfig:"idp"` - Ldap Ldap `ocisConfig:"ldap"` - Service Service `ocisConfig:"service"` + Service Service - Context context.Context - Supervised bool + Tracing Tracing `ocisConfig:"tracing"` + Log *Log `ocisConfig:"log"` + Debug Debug `ocisConfig:"debug"` + + HTTP HTTP `ocisConfig:"http"` + + Asset Asset `ocisConfig:"asset"` + IDP Settings `ocisConfig:"idp"` + Ldap Ldap `ocisConfig:"ldap"` + + Context context.Context } -// New initializes a new configuration with or without defaults. -func New() *Config { - return &Config{} +// Ldap defines the available LDAP configuration. +type Ldap struct { + URI string `ocisConfig:"uri" env:"IDP_LDAP_URI"` + + BindDN string `ocisConfig:"bind_dn" env:"IDP_LDAP_BIND_DN"` + BindPassword string `ocisConfig:"bind_password" env:"IDP_LDAP_BIND_PASSWORD"` + + BaseDN string `ocisConfig:"base_dn" env:"IDP_LDAP_BASE_DN"` + Scope string `ocisConfig:"scope" env:"IDP_LDAP_SCOPE"` + + LoginAttribute string `ocisConfig:"login_attribute" env:"IDP_LDAP_LOGIN_ATTRIBUTE"` + EmailAttribute string `ocisConfig:"email_attribute" env:"IDP_LDAP_EMAIL_ATTRIBUTE"` + NameAttribute string `ocisConfig:"name_attribute" env:"IDP_LDAP_NAME_ATTRIBUTE"` + UUIDAttribute string `ocisConfig:"uuid_attribute" env:"IDP_LDAP_UUID_ATTRIBUTE"` + UUIDAttributeType string `ocisConfig:"uuid_attribute_type" env:"IDP_LDAP_UUID_ATTRIBUTE_TYPE"` + + Filter string `ocisConfig:"filter" env:"IDP_LDAP_FILTER"` } -func DefaultConfig() *Config { - return &Config{ - Debug: Debug{ - Addr: "127.0.0.1:9134", - }, - HTTP: HTTP{ - Addr: "127.0.0.1:9130", - Root: "/", - TLSCert: path.Join(defaults.BaseDataPath(), "idp", "server.crt"), - TLSKey: path.Join(defaults.BaseDataPath(), "idp", "server.key"), - TLS: false, - }, - Tracing: Tracing{ - Type: "jaeger", - Endpoint: "", - Collector: "", - Service: "idp", - }, - Asset: Asset{}, - IDP: Settings{ - Iss: "https://localhost:9200", - IdentityManager: "ldap", - URIBasePath: "", - SignInURI: "", - SignedOutURI: "", - AuthorizationEndpointURI: "", - EndsessionEndpointURI: "", - Insecure: false, - TrustedProxy: nil, - AllowScope: nil, - AllowClientGuests: false, - AllowDynamicClientRegistration: false, - EncryptionSecretFile: "", - Listen: "", - IdentifierClientDisabled: true, - IdentifierClientPath: path.Join(defaults.BaseDataPath(), "idp"), - IdentifierRegistrationConf: path.Join(defaults.BaseDataPath(), "idp", "identifier-registration.yaml"), - IdentifierScopesConf: "", - IdentifierDefaultBannerLogo: "", - IdentifierDefaultSignInPageText: "", - IdentifierDefaultUsernameHintText: "", - SigningKid: "", - SigningMethod: "PS256", - SigningPrivateKeyFiles: nil, - ValidationKeysPath: "", - CookieBackendURI: "", - CookieNames: nil, - AccessTokenDurationSeconds: 60 * 10, // 10 minutes - IDTokenDurationSeconds: 60 * 60, // 1 hour - RefreshTokenDurationSeconds: 60 * 60 * 24 * 365 * 3, // 1 year - DyamicClientSecretDurationSeconds: 0, - }, - Ldap: Ldap{ - URI: "ldap://localhost:9125", - BindDN: "cn=idp,ou=sysusers,dc=ocis,dc=test", - BindPassword: "idp", - BaseDN: "ou=users,dc=ocis,dc=test", - Scope: "sub", - LoginAttribute: "cn", - EmailAttribute: "mail", - NameAttribute: "sn", - UUIDAttribute: "uid", - UUIDAttributeType: "text", - Filter: "(objectClass=posixaccount)", - }, - Service: Service{ - Name: "idp", - Namespace: "com.owncloud.web", - }, - } +// Asset defines the available asset configuration. +type Asset struct { + Path string `ocisConfig:"asset" env:"IDP_ASSET_PATH"` +} + +type Settings struct { + // don't change the order of elements in this struct + // it needs to match github.com/libregraph/lico/bootstrap.Settings + + Iss string `ocisConfig:"iss" env:"OCIS_URL;IDP_ISS"` + + IdentityManager string `ocisConfig:"identity_manager" env:"IDP_IDENTITY_MANAGER"` + + URIBasePath string `ocisConfig:"uri_base_path" env:"IDP_URI_BASE_PATH"` + + SignInURI string `ocisConfig:"sign_in_uri" env:"IDP_SIGN_IN_URI"` + SignedOutURI string `ocisConfig:"signed_out_uri" env:"IDP_SIGN_OUT_URI"` + + AuthorizationEndpointURI string `ocisConfig:"authorization_endpoint_uri" env:"IDP_ENDPOINT_URI"` + EndsessionEndpointURI string `ocisConfig:"end_session_endpoint_uri" env:"IDP_ENDSESSION_ENDPOINT_URI"` + + Insecure bool `ocisConfig:"insecure" env:"IDP_INSECURE"` + + TrustedProxy []string `ocisConfig:"trusted_proxy"` //TODO: how to configure this via env? + + AllowScope []string `ocisConfig:"allow_scope"` // TODO: is this even needed? + AllowClientGuests bool `ocisConfig:"allow_client_guests" env:"IDP_ALLOW_CLIENT_GUESTS"` + AllowDynamicClientRegistration bool `ocisConfig:"allow_dynamic_client_registration" env:"IDP_ALLOW_DYNAMIC_CLIENT_REGISTRATION"` + + EncryptionSecretFile string `ocisConfig:"encrypt_secret_file" env:"IDP_ENCRYPTION_SECRET"` + + Listen string + + IdentifierClientDisabled bool `ocisConfig:"identifier_client_disabled" env:"IDP_DISABLE_IDENTIFIER_WEBAPP"` + IdentifierClientPath string `ocisConfig:"identifier_client_path" env:"IDP_IDENTIFIER_CLIENT_PATH"` + IdentifierRegistrationConf string `ocisConfig:"identifier_registration_conf" env:"IDP_IDENTIFIER_REGISTRATION_CONF"` + IdentifierScopesConf string `ocisConfig:"identifier_scopes_conf" env:"IDP_IDENTIFIER_SCOPES_CONF"` + IdentifierDefaultBannerLogo string + IdentifierDefaultSignInPageText string + IdentifierDefaultUsernameHintText string + + SigningKid string `ocisConfig:"signing_kid" env:"IDP_SIGNING_KID"` + SigningMethod string `ocisConfig:"signing_method" env:"IDP_SIGNING_METHOD"` + SigningPrivateKeyFiles []string `ocisConfig:"signing_private_key_files"` // TODO: is this even needed? + ValidationKeysPath string `ocisConfig:"validation_keys_path" env:"IDP_VALIDATION_KEYS_PATH"` + + CookieBackendURI string + CookieNames []string + + AccessTokenDurationSeconds uint64 `ocisConfig:"access_token_duration_seconds" env:"IDP_ACCESS_TOKEN_EXPIRATION"` + IDTokenDurationSeconds uint64 `ocisConfig:"id_token_duration_seconds" env:"IDP_ID_TOKEN_EXPIRATION"` + RefreshTokenDurationSeconds uint64 `ocisConfig:"refresh_token_duration_seconds" env:"IDP_REFRESH_TOKEN_EXPIRATION"` + DyamicClientSecretDurationSeconds uint64 `ocisConfig:"dynamic_client_secret_duration_seconds" env:""` } diff --git a/idp/pkg/config/debug.go b/idp/pkg/config/debug.go new file mode 100644 index 000000000..f713bc341 --- /dev/null +++ b/idp/pkg/config/debug.go @@ -0,0 +1,9 @@ +package config + +// Debug defines the available debug configuration. +type Debug struct { + Addr string `ocisConfig:"addr" env:"IDP_DEBUG_ADDR"` + Token string `ocisConfig:"token" env:"IDP_DEBUG_TOKEN"` + Pprof bool `ocisConfig:"pprof" env:"IDP_DEBUG_PPROF"` + Zpages bool `ocisConfig:"zpages" env:"IDP_DEBUG_ZPAGES"` +} diff --git a/idp/pkg/config/defaultconfig.go b/idp/pkg/config/defaultconfig.go new file mode 100644 index 000000000..21e59a339 --- /dev/null +++ b/idp/pkg/config/defaultconfig.go @@ -0,0 +1,79 @@ +package config + +import ( + "path" + + "github.com/owncloud/ocis/ocis-pkg/config/defaults" +) + +func DefaultConfig() *Config { + return &Config{ + Debug: Debug{ + Addr: "127.0.0.1:9134", + }, + HTTP: HTTP{ + Addr: "127.0.0.1:9130", + Root: "/", + Namespace: "com.owncloud.web", + TLSCert: path.Join(defaults.BaseDataPath(), "idp", "server.crt"), + TLSKey: path.Join(defaults.BaseDataPath(), "idp", "server.key"), + TLS: false, + }, + Service: Service{ + Name: "idp", + }, + Tracing: Tracing{ + Enabled: false, + Type: "jaeger", + Endpoint: "", + Collector: "", + }, + Asset: Asset{}, + IDP: Settings{ + Iss: "https://localhost:9200", + IdentityManager: "ldap", + URIBasePath: "", + SignInURI: "", + SignedOutURI: "", + AuthorizationEndpointURI: "", + EndsessionEndpointURI: "", + Insecure: false, + TrustedProxy: nil, + AllowScope: nil, + AllowClientGuests: false, + AllowDynamicClientRegistration: false, + EncryptionSecretFile: "", + Listen: "", + IdentifierClientDisabled: true, + IdentifierClientPath: path.Join(defaults.BaseDataPath(), "idp"), + IdentifierRegistrationConf: path.Join(defaults.BaseDataPath(), "idp", "identifier-registration.yaml"), + IdentifierScopesConf: "", + IdentifierDefaultBannerLogo: "", + IdentifierDefaultSignInPageText: "", + IdentifierDefaultUsernameHintText: "", + SigningKid: "", + SigningMethod: "PS256", + SigningPrivateKeyFiles: nil, + ValidationKeysPath: "", + CookieBackendURI: "", + CookieNames: nil, + AccessTokenDurationSeconds: 60 * 10, // 10 minutes + IDTokenDurationSeconds: 60 * 60, // 1 hour + RefreshTokenDurationSeconds: 60 * 60 * 24 * 365 * 3, // 1 year + DyamicClientSecretDurationSeconds: 0, + }, + Ldap: Ldap{ + URI: "ldap://localhost:9125", + BindDN: "cn=idp,ou=sysusers,dc=ocis,dc=test", + BindPassword: "idp", + BaseDN: "ou=users,dc=ocis,dc=test", + Scope: "sub", + LoginAttribute: "cn", + EmailAttribute: "mail", + NameAttribute: "sn", + UUIDAttribute: "uid", + UUIDAttributeType: "text", + Filter: "(objectClass=posixaccount)", + }, + } +} diff --git a/idp/pkg/config/http.go b/idp/pkg/config/http.go new file mode 100644 index 000000000..4d528e027 --- /dev/null +++ b/idp/pkg/config/http.go @@ -0,0 +1,11 @@ +package config + +// HTTP defines the available http configuration. +type HTTP struct { + Addr string `ocisConfig:"addr" env:"IDP_HTTP_ADDR"` + Root string `ocisConfig:"root" env:"IDP_HTTP_ROOT"` + Namespace string + TLSCert string `ocisConfig:"tls_cert" env:"IDP_TRANSPORT_TLS_CERT"` + TLSKey string `ocisConfig:"tls_key" env:"IDP_TRANSPORT_TLS_KEY"` + TLS bool `ocisConfig:"tls" env:"IDP_TLS"` +} diff --git a/idp/pkg/config/log.go b/idp/pkg/config/log.go new file mode 100644 index 000000000..39ba2d9e5 --- /dev/null +++ b/idp/pkg/config/log.go @@ -0,0 +1,9 @@ +package config + +// Log defines the available log configuration. +type Log struct { + Level string `mapstructure:"level" env:"OCIS_LOG_LEVEL;IDP_LOG_LEVEL"` + Pretty bool `mapstructure:"pretty" env:"OCIS_LOG_PRETTY;IDP_LOG_PRETTY"` + Color bool `mapstructure:"color" env:"OCIS_LOG_COLOR;IDP_LOG_COLOR"` + File string `mapstructure:"file" env:"OCIS_LOG_FILE;IDP_LOG_FILE"` +} diff --git a/idp/pkg/config/mappings.go b/idp/pkg/config/mappings.go deleted file mode 100644 index 68383fe5c..000000000 --- a/idp/pkg/config/mappings.go +++ /dev/null @@ -1,243 +0,0 @@ -package config - -import "github.com/owncloud/ocis/ocis-pkg/shared" - -// GetEnv fetches a list of known env variables for this extension. It is to be used by gookit, as it provides a list -// with all the environment variables an extension supports. -func GetEnv(cfg *Config) []string { - var r = make([]string, len(structMappings(cfg))) - for i := range structMappings(cfg) { - r = append(r, structMappings(cfg)[i].EnvVars...) - } - - return r -} - -// StructMappings binds a set of environment variables to a destination on cfg. Iterating over this set and editing the -// Destination value of a binding will alter the original value, as it is a pointer to its memory address. This lets -// us propagate changes easier. -func StructMappings(cfg *Config) []shared.EnvBinding { - return structMappings(cfg) -} - -// structMappings binds a set of environment variables to a destination on cfg. -func structMappings(cfg *Config) []shared.EnvBinding { - return []shared.EnvBinding{ - { - EnvVars: []string{"OCIS_LOG_LEVEL", "IDP_LOG_LEVEL"}, - Destination: &cfg.Log.Level, - }, - { - EnvVars: []string{"OCIS_LOG_PRETTY", "IDP_LOG_PRETTY"}, - Destination: &cfg.Log.Pretty, - }, - { - EnvVars: []string{"OCIS_LOG_COLOR", "IDP_LOG_COLOR"}, - Destination: &cfg.Log.Color, - }, - { - EnvVars: []string{"OCIS_LOG_FILE", "IDP_LOG_FILE"}, - Destination: &cfg.Log.File, - }, - { - EnvVars: []string{"IDP_CONFIG_FILE"}, - Destination: &cfg.File, - }, - { - EnvVars: []string{"OCIS_TRACING_ENABLED", "IDP_TRACING_ENABLED"}, - Destination: &cfg.Tracing.Enabled, - }, - { - EnvVars: []string{"OCIS_TRACING_TYPE", "IDP_TRACING_TYPE"}, - Destination: &cfg.Tracing.Type, - }, - { - EnvVars: []string{"OCIS_TRACING_ENDPOINT", "IDP_TRACING_ENDPOINT"}, - Destination: &cfg.Tracing.Endpoint, - }, - { - EnvVars: []string{"OCIS_TRACING_COLLECTOR", "IDP_TRACING_COLLECTOR"}, - Destination: &cfg.Tracing.Collector, - }, - { - EnvVars: []string{"IDP_TRACING_SERVICE"}, - Destination: &cfg.Tracing.Service, - }, - { - EnvVars: []string{"IDP_DEBUG_ADDR"}, - Destination: &cfg.Debug.Addr, - }, - { - EnvVars: []string{"IDP_DEBUG_TOKEN"}, - Destination: &cfg.Debug.Token, - }, - { - EnvVars: []string{"IDP_DEBUG_PPROF"}, - Destination: &cfg.Debug.Pprof, - }, - { - EnvVars: []string{"IDP_DEBUG_ZPAGES"}, - Destination: &cfg.Debug.Zpages, - }, - { - EnvVars: []string{"IDP_HTTP_ADDR"}, - Destination: &cfg.HTTP.Addr, - }, - { - EnvVars: []string{"IDP_HTTP_ROOT"}, - Destination: &cfg.HTTP.Root, - }, - { - EnvVars: []string{"IDP_HTTP_NAMESPACE"}, - Destination: &cfg.Service.Namespace, - }, - { - EnvVars: []string{"IDP_NAME"}, - Destination: &cfg.Service.Name, - }, - { - EnvVars: []string{"IDP_IDENTITY_MANAGER"}, - Destination: &cfg.IDP.IdentityManager, - }, - { - EnvVars: []string{"IDP_LDAP_URI"}, - Destination: &cfg.Ldap.URI, - }, - { - EnvVars: []string{"IDP_LDAP_BIND_DN"}, - Destination: &cfg.Ldap.BindDN, - }, - { - EnvVars: []string{"IDP_LDAP_BIND_PASSWORD"}, - Destination: &cfg.Ldap.BindPassword, - }, - { - EnvVars: []string{"IDP_LDAP_BASE_DN"}, - Destination: &cfg.Ldap.BaseDN, - }, - { - EnvVars: []string{"IDP_LDAP_SCOPE"}, - Destination: &cfg.Ldap.Scope, - }, - { - EnvVars: []string{"IDP_LDAP_LOGIN_ATTRIBUTE"}, - Destination: &cfg.Ldap.LoginAttribute, - }, - { - EnvVars: []string{"IDP_LDAP_EMAIL_ATTRIBUTE"}, - Destination: &cfg.Ldap.EmailAttribute, - }, - { - EnvVars: []string{"IDP_LDAP_NAME_ATTRIBUTE"}, - Destination: &cfg.Ldap.NameAttribute, - }, - { - EnvVars: []string{"IDP_LDAP_UUID_ATTRIBUTE"}, - Destination: &cfg.Ldap.UUIDAttribute, - }, - { - EnvVars: []string{"IDP_LDAP_UUID_ATTRIBUTE_TYPE"}, - Destination: &cfg.Ldap.UUIDAttributeType, - }, - { - EnvVars: []string{"IDP_LDAP_FILTER"}, - Destination: &cfg.Ldap.Filter, - }, - { - EnvVars: []string{"IDP_TRANSPORT_TLS_CERT"}, - Destination: &cfg.HTTP.TLSCert, - }, - { - EnvVars: []string{"IDP_TRANSPORT_TLS_KEY"}, - Destination: &cfg.HTTP.TLSKey, - }, - { - EnvVars: []string{"OCIS_URL", "IDP_ISS"}, // IDP_ISS takes precedence over OCIS_URL - Destination: &cfg.IDP.Iss, - }, - { - EnvVars: []string{"IDP_SIGNING_KID"}, - Destination: &cfg.IDP.SigningKid, - }, - { - EnvVars: []string{"IDP_VALIDATION_KEYS_PATH"}, - Destination: &cfg.IDP.ValidationKeysPath, - }, - { - EnvVars: []string{"IDP_ENCRYPTION_SECRET"}, - Destination: &cfg.IDP.EncryptionSecretFile, - }, - { - EnvVars: []string{"IDP_SIGNING_METHOD"}, - Destination: &cfg.IDP.SigningMethod, - }, - { - EnvVars: []string{"IDP_URI_BASE_PATH"}, - Destination: &cfg.IDP.URIBasePath, - }, - { - EnvVars: []string{"IDP_SIGN_IN_URI"}, - Destination: &cfg.IDP.SignInURI, - }, - { - EnvVars: []string{"IDP_SIGN_OUT_URI"}, - Destination: &cfg.IDP.SignedOutURI, - }, - { - EnvVars: []string{"IDP_ENDPOINT_URI"}, - Destination: &cfg.IDP.AuthorizationEndpointURI, - }, - { - EnvVars: []string{"IDP_ENDSESSION_ENDPOINT_URI"}, - Destination: &cfg.IDP.EndsessionEndpointURI, - }, - { - EnvVars: []string{"IDP_ASSET_PATH"}, - Destination: &cfg.Asset.Path, - }, - { - EnvVars: []string{"IDP_IDENTIFIER_CLIENT_PATH"}, - Destination: &cfg.IDP.IdentifierClientPath, - }, - { - EnvVars: []string{"IDP_IDENTIFIER_REGISTRATION_CONF"}, - Destination: &cfg.IDP.IdentifierRegistrationConf, - }, - { - EnvVars: []string{"IDP_IDENTIFIER_SCOPES_CONF"}, - Destination: &cfg.IDP.IdentifierScopesConf, - }, - { - EnvVars: []string{"IDP_INSECURE"}, - Destination: &cfg.IDP.Insecure, - }, - { - EnvVars: []string{"IDP_TLS"}, - Destination: &cfg.HTTP.TLS, - }, - { - EnvVars: []string{"IDP_ALLOW_CLIENT_GUESTS"}, - Destination: &cfg.IDP.AllowClientGuests, - }, - { - EnvVars: []string{"IDP_ALLOW_DYNAMIC_CLIENT_REGISTRATION"}, - Destination: &cfg.IDP.AllowDynamicClientRegistration, - }, - { - EnvVars: []string{"IDP_DISABLE_IDENTIFIER_WEBAPP"}, - Destination: &cfg.IDP.IdentifierClientDisabled, - }, - { - EnvVars: []string{"IDP_ACCESS_TOKEN_EXPIRATION"}, - Destination: &cfg.IDP.AccessTokenDurationSeconds, - }, - { - EnvVars: []string{"IDP_ID_TOKEN_EXPIRATION"}, - Destination: &cfg.IDP.IDTokenDurationSeconds, - }, - { - EnvVars: []string{"IDP_REFRESH_TOKEN_EXPIRATION"}, - Destination: &cfg.IDP.RefreshTokenDurationSeconds, - }, - } -} diff --git a/idp/pkg/config/parser/parse.go b/idp/pkg/config/parser/parse.go new file mode 100644 index 000000000..de98f42d6 --- /dev/null +++ b/idp/pkg/config/parser/parse.go @@ -0,0 +1,46 @@ +package parser + +import ( + "errors" + "strings" + + "github.com/owncloud/ocis/idp/pkg/config" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" +) + +// ParseConfig loads accounts configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + // 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.Commons == nil { + cfg.Log = &config.Log{} + } + + // 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 + } + } + + // sanitize config + if cfg.HTTP.Root != "/" { + cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") + } + + return nil +} diff --git a/idp/pkg/config/service.go b/idp/pkg/config/service.go new file mode 100644 index 000000000..f98aa3d27 --- /dev/null +++ b/idp/pkg/config/service.go @@ -0,0 +1,6 @@ +package config + +// Service defines the available service configuration. +type Service struct { + Name string +} diff --git a/idp/pkg/config/tracing.go b/idp/pkg/config/tracing.go new file mode 100644 index 000000000..c149f9da1 --- /dev/null +++ b/idp/pkg/config/tracing.go @@ -0,0 +1,9 @@ +package config + +// Tracing defines the available tracing configuration. +type Tracing struct { + Enabled bool `ocisConfig:"enabled" env:"OCIS_TRACING_ENABLED;IDP_TRACING_ENABLED"` + Type string `ocisConfig:"type" env:"OCIS_TRACING_TYPE;IDP_TRACING_TYPE"` + Endpoint string `ocisConfig:"endpoint" env:"OCIS_TRACING_ENDPOINT;IDP_TRACING_ENDPOINT"` + Collector string `ocisConfig:"collector" env:"OCIS_TRACING_COLLECTOR;IDP_TRACING_COLLECTOR"` +} diff --git a/idp/pkg/logging/logging.go b/idp/pkg/logging/logging.go new file mode 100644 index 000000000..97eb83fcd --- /dev/null +++ b/idp/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/idp/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/idp/pkg/log/logrus_wrapper.go b/idp/pkg/logging/logrus_wrapper.go similarity index 98% rename from idp/pkg/log/logrus_wrapper.go rename to idp/pkg/logging/logrus_wrapper.go index 71f29be2c..38e302005 100644 --- a/idp/pkg/log/logrus_wrapper.go +++ b/idp/pkg/logging/logrus_wrapper.go @@ -1,9 +1,10 @@ -package log +package logging import ( + "io/ioutil" + "github.com/rs/zerolog" "github.com/sirupsen/logrus" - "io/ioutil" ) type levelMap map[logrus.Level]zerolog.Level diff --git a/idp/pkg/server/debug/server.go b/idp/pkg/server/debug/server.go index f05038d95..9da10444f 100644 --- a/idp/pkg/server/debug/server.go +++ b/idp/pkg/server/debug/server.go @@ -6,6 +6,7 @@ import ( "github.com/owncloud/ocis/idp/pkg/config" "github.com/owncloud/ocis/ocis-pkg/service/debug" + "github.com/owncloud/ocis/ocis-pkg/version" ) // Server initializes the debug service and server. @@ -15,7 +16,7 @@ func Server(opts ...Option) (*http.Server, error) { return debug.NewService( debug.Logger(options.Logger), debug.Name(options.Config.Service.Name), - debug.Version(options.Config.Service.Version), + debug.Version(version.String), debug.Address(options.Config.Debug.Addr), debug.Token(options.Config.Debug.Token), debug.Pprof(options.Config.Debug.Pprof), @@ -31,9 +32,11 @@ func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) - // TODO(tboerger): check if services are up and running + // TODO: check if services are up and running - if _, err := io.WriteString(w, http.StatusText(http.StatusOK)); err != nil { + _, 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) } } @@ -45,9 +48,11 @@ func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) - // TODO(tboerger): check if services are up and running + // TODO: check if services are up and running - if _, err := io.WriteString(w, http.StatusText(http.StatusOK)); err != nil { + _, 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/idp/pkg/server/http/server.go b/idp/pkg/server/http/server.go index cab4554b3..08a1c550c 100644 --- a/idp/pkg/server/http/server.go +++ b/idp/pkg/server/http/server.go @@ -9,6 +9,7 @@ import ( pkgcrypto "github.com/owncloud/ocis/ocis-pkg/crypto" "github.com/owncloud/ocis/ocis-pkg/middleware" "github.com/owncloud/ocis/ocis-pkg/service/http" + "github.com/owncloud/ocis/ocis-pkg/version" "go-micro.dev/v4" ) @@ -40,9 +41,9 @@ func Server(opts ...Option) (http.Service, error) { service := http.NewService( http.Logger(options.Logger), - http.Namespace(options.Config.Service.Namespace), + http.Namespace(options.Config.HTTP.Namespace), http.Name(options.Config.Service.Name), - http.Version(options.Config.Service.Version), + http.Version(version.String), http.Address(options.Config.HTTP.Addr), http.Context(options.Context), http.Flags(options.Flags...), @@ -60,7 +61,7 @@ func Server(opts ...Option) (http.Service, error) { middleware.Secure, middleware.Version( options.Config.Service.Name, - options.Config.Service.Version, + version.String, ), middleware.Logger( options.Logger, @@ -70,7 +71,7 @@ func Server(opts ...Option) (http.Service, error) { { handle = svc.NewInstrument(handle, options.Metrics) - handle = svc.NewLogging(handle, options.Logger) + handle = svc.NewLoggingHandler(handle, options.Logger) } if err := micro.RegisterHandler(service.Server(), handle); err != nil { diff --git a/idp/pkg/service/v0/logging.go b/idp/pkg/service/v0/logging.go index d5098e2fe..f3dfab793 100644 --- a/idp/pkg/service/v0/logging.go +++ b/idp/pkg/service/v0/logging.go @@ -7,19 +7,19 @@ import ( ) // NewLogging returns a service that logs messages. -func NewLogging(next Service, logger log.Logger) Service { - return logging{ +func NewLoggingHandler(next Service, logger log.Logger) Service { + return loggingHandler{ next: next, logger: logger, } } -type logging struct { +type loggingHandler struct { next Service logger log.Logger } // ServeHTTP implements the Service interface. -func (l logging) ServeHTTP(w http.ResponseWriter, r *http.Request) { +func (l loggingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { l.next.ServeHTTP(w, r) } diff --git a/idp/pkg/service/v0/service.go b/idp/pkg/service/v0/service.go index 0f949a91a..24fb3132d 100644 --- a/idp/pkg/service/v0/service.go +++ b/idp/pkg/service/v0/service.go @@ -21,7 +21,7 @@ import ( "github.com/libregraph/lico/server" "github.com/owncloud/ocis/idp/pkg/assets" "github.com/owncloud/ocis/idp/pkg/config" - logw "github.com/owncloud/ocis/idp/pkg/log" + "github.com/owncloud/ocis/idp/pkg/logging" "github.com/owncloud/ocis/idp/pkg/middleware" "github.com/owncloud/ocis/ocis-pkg/log" "stash.kopano.io/kgol/rndm" @@ -59,7 +59,7 @@ func NewService(opts ...Option) Service { idpSettings := bootstrap.Settings(options.Config.IDP) bs, err := bootstrap.Boot(ctx, &idpSettings, &licoconfig.Config{ - Logger: logw.Wrap(logger), + Logger: logging.Wrap(logger), }) if err != nil { diff --git a/idp/pkg/tracing/tracing.go b/idp/pkg/tracing/tracing.go index eb116d6ce..756b6210c 100644 --- a/idp/pkg/tracing/tracing.go +++ b/idp/pkg/tracing/tracing.go @@ -14,7 +14,7 @@ var ( func Configure(cfg *config.Config) error { var err error if cfg.Tracing.Enabled { - if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, "idp", cfg.Tracing.Type); err != nil { + if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { return err } } diff --git a/ocis-pkg/clihelper/app.go b/ocis-pkg/clihelper/app.go new file mode 100644 index 000000000..4ea39ace3 --- /dev/null +++ b/ocis-pkg/clihelper/app.go @@ -0,0 +1,26 @@ +package clihelper + +import ( + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +func DefaultApp(app *cli.App) *cli.App { + // version info + app.Version = version.String + app.Compiled = version.Compiled() + + // author info + app.Authors = []*cli.Author{ + { + Name: "ownCloud GmbH", + Email: "support@owncloud.com", + }, + } + + // disable global version flag + // instead we provide the version command + app.HideVersion = true + + return app +} diff --git a/ocis-pkg/config/config.go b/ocis-pkg/config/config.go index e73c9d07e..1f3e3c0d2 100644 --- a/ocis-pkg/config/config.go +++ b/ocis-pkg/config/config.go @@ -18,18 +18,9 @@ import ( webdav "github.com/owncloud/ocis/webdav/pkg/config" ) -// Tracing defines the available tracing configuration. -type Tracing struct { - Enabled bool `ocisConfig:"enabled"` - Type string `ocisConfig:"type"` - Endpoint string `ocisConfig:"endpoint"` - Collector string `ocisConfig:"collector"` - Service string `ocisConfig:"service"` -} - // TokenManager is the config for using the reva token manager type TokenManager struct { - JWTSecret string `ocisConfig:"jwt_secret"` + JWTSecret string `ocisConfig:"jwt_secret" env:"OCIS_JWT_SECRET"` } const ( @@ -44,22 +35,23 @@ type Mode int // Runtime configures the oCIS runtime when running in supervised mode. type Runtime struct { - Port string `ocisConfig:"port"` - Host string `ocisConfig:"host"` - Extensions string `ocisConfig:"extensions"` + Port string `ocisConfig:"port" env:"OCIS_RUNTIME_PORT"` + Host string `ocisConfig:"host" env:"OCIS_RUNTIME_HOST"` + Extensions string `ocisConfig:"extensions" env:"OCIS_RUN_EXTENSIONS"` } // Config combines all available configuration parts. type Config struct { *shared.Commons `ocisConfig:"shared"` + Tracing shared.Tracing `ocisConfig:"tracing"` + Log *shared.Log `ocisConfig:"log"` + Mode Mode // DEPRECATED File string OcisURL string `ocisConfig:"ocis_url"` Registry string `ocisConfig:"registry"` - Log shared.Log `ocisConfig:"log"` - Tracing Tracing `ocisConfig:"tracing"` TokenManager TokenManager `ocisConfig:"token_manager"` Runtime Runtime `ocisConfig:"runtime"` @@ -77,129 +69,3 @@ type Config struct { Thumbnails *thumbnails.Config `ocisConfig:"thumbnails"` WebDAV *webdav.Config `ocisConfig:"webdav"` } - -// New initializes a new configuration with or without defaults. -func New() *Config { - return &Config{ - Accounts: accounts.DefaultConfig(), - GLAuth: glauth.DefaultConfig(), - Graph: graph.DefaultConfig(), - IDP: idp.DefaultConfig(), - Proxy: proxy.DefaultConfig(), - GraphExplorer: graphExplorer.DefaultConfig(), - OCS: ocs.DefaultConfig(), - Settings: settings.DefaultConfig(), - Web: web.DefaultConfig(), - Store: store.DefaultConfig(), - Thumbnails: thumbnails.DefaultConfig(), - WebDAV: webdav.DefaultConfig(), - Storage: storage.DefaultConfig(), - } -} - -func DefaultConfig() *Config { - return &Config{ - Tracing: Tracing{ - Enabled: false, - Type: "jaeger", - Endpoint: "", - Collector: "", - Service: "ocis", - }, - TokenManager: TokenManager{ - JWTSecret: "Pive-Fumkiu4", - }, - Runtime: Runtime{ - Port: "9250", - Host: "localhost", - }, - Accounts: accounts.DefaultConfig(), - GLAuth: glauth.DefaultConfig(), - Graph: graph.DefaultConfig(), - IDP: idp.DefaultConfig(), - Proxy: proxy.DefaultConfig(), - GraphExplorer: graphExplorer.DefaultConfig(), - OCS: ocs.DefaultConfig(), - Settings: settings.DefaultConfig(), - Web: web.DefaultConfig(), - Store: store.DefaultConfig(), - Thumbnails: thumbnails.DefaultConfig(), - WebDAV: webdav.DefaultConfig(), - Storage: storage.DefaultConfig(), - } -} - -// GetEnv fetches a list of known env variables for this extension. It is to be used by gookit, as it provides a list -// with all the environment variables an extension supports. -func GetEnv() []string { - var r = make([]string, len(structMappings(&Config{}))) - for i := range structMappings(&Config{}) { - r = append(r, structMappings(&Config{})[i].EnvVars...) - } - - return r -} - -// StructMappings binds a set of environment variables to a destination on cfg. Iterating over this set and editing the -// Destination value of a binding will alter the original value, as it is a pointer to its memory address. This lets -// us propagate changes easier. -func StructMappings(cfg *Config) []shared.EnvBinding { - return structMappings(cfg) -} - -func structMappings(cfg *Config) []shared.EnvBinding { - return []shared.EnvBinding{ - { - EnvVars: []string{"OCIS_LOG_LEVEL"}, - Destination: &cfg.Log.Level, - }, - { - EnvVars: []string{"OCIS_LOG_COLOR"}, - Destination: &cfg.Log.Color, - }, - { - EnvVars: []string{"OCIS_LOG_PRETTY"}, - Destination: &cfg.Log.Pretty, - }, - { - EnvVars: []string{"OCIS_LOG_FILE"}, - Destination: &cfg.Log.File, - }, - { - EnvVars: []string{"OCIS_TRACING_ENABLED"}, - Destination: &cfg.Tracing.Enabled, - }, - { - EnvVars: []string{"OCIS_TRACING_TYPE"}, - Destination: &cfg.Tracing.Type, - }, - { - EnvVars: []string{"OCIS_TRACING_ENDPOINT"}, - Destination: &cfg.Tracing.Endpoint, - }, - { - EnvVars: []string{"OCIS_TRACING_COLLECTOR"}, - Destination: &cfg.Tracing.Collector, - }, - { - EnvVars: []string{"OCIS_TRACING_SERVICE"}, - Destination: &cfg.Tracing.Service, - }, - { - EnvVars: []string{"OCIS_JWT_SECRET"}, - Destination: &cfg.TokenManager.JWTSecret, - }, - { - EnvVars: []string{"OCIS_RUNTIME_PORT"}, - Destination: &cfg.Runtime.Port, - }, - { - EnvVars: []string{"OCIS_RUNTIME_HOST"}, - Destination: &cfg.Runtime.Host, - }, - { - EnvVars: []string{"OCIS_RUN_EXTENSIONS"}, - Destination: &cfg.Runtime.Extensions, - }, - } -} diff --git a/ocis-pkg/config/defaultconfig.go b/ocis-pkg/config/defaultconfig.go new file mode 100644 index 000000000..21cf14ba0 --- /dev/null +++ b/ocis-pkg/config/defaultconfig.go @@ -0,0 +1,42 @@ +package config + +import ( + accounts "github.com/owncloud/ocis/accounts/pkg/config" + glauth "github.com/owncloud/ocis/glauth/pkg/config" + graphExplorer "github.com/owncloud/ocis/graph-explorer/pkg/config" + graph "github.com/owncloud/ocis/graph/pkg/config" + idp "github.com/owncloud/ocis/idp/pkg/config" + ocs "github.com/owncloud/ocis/ocs/pkg/config" + proxy "github.com/owncloud/ocis/proxy/pkg/config" + settings "github.com/owncloud/ocis/settings/pkg/config" + storage "github.com/owncloud/ocis/storage/pkg/config" + store "github.com/owncloud/ocis/store/pkg/config" + thumbnails "github.com/owncloud/ocis/thumbnails/pkg/config" + web "github.com/owncloud/ocis/web/pkg/config" + webdav "github.com/owncloud/ocis/webdav/pkg/config" +) + +func DefaultConfig() *Config { + return &Config{ + TokenManager: TokenManager{ + JWTSecret: "Pive-Fumkiu4", + }, + Runtime: Runtime{ + Port: "9250", + Host: "localhost", + }, + Accounts: accounts.DefaultConfig(), + GLAuth: glauth.DefaultConfig(), + Graph: graph.DefaultConfig(), + IDP: idp.DefaultConfig(), + Proxy: proxy.DefaultConfig(), + GraphExplorer: graphExplorer.DefaultConfig(), + OCS: ocs.DefaultConfig(), + Settings: settings.DefaultConfig(), + Web: web.DefaultConfig(), + Store: store.DefaultConfig(), + Thumbnails: thumbnails.DefaultConfig(), + WebDAV: webdav.DefaultConfig(), + Storage: storage.DefaultConfig(), + } +} diff --git a/ocis-pkg/config/envdecode/LICENSE b/ocis-pkg/config/envdecode/LICENSE new file mode 100644 index 000000000..5869b24ec --- /dev/null +++ b/ocis-pkg/config/envdecode/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Joe Shaw + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/ocis-pkg/config/envdecode/README.md b/ocis-pkg/config/envdecode/README.md new file mode 100644 index 000000000..8e7ae860e --- /dev/null +++ b/ocis-pkg/config/envdecode/README.md @@ -0,0 +1,88 @@ +`envdecode` is a Go package for populating structs from environment +variables. It's basically a fork of https://github.com/joeshaw/envdecode, +but changed to support multiple environment variables (precedence). + +`envdecode` uses struct tags to map environment variables to fields, +allowing you you use any names you want for environment variables. +`envdecode` will recurse into nested structs, including pointers to +nested structs, but it will not allocate new pointers to structs. + +## API + +Full API docs are available on +[godoc.org](https://godoc.org/github.com/owncloud/ocis/ocis-pkg/config/envdecode). + +Define a struct with `env` struct tags: + +```go +type Config struct { + Hostname string `env:"SERVER_HOSTNAME,default=localhost"` + Port uint16 `env:"HTTP_PORT;SERVER_PORT,default=8080"` + + AWS struct { + ID string `env:"AWS_ACCESS_KEY_ID"` + Secret string `env:"AWS_SECRET_ACCESS_KEY,required"` + SnsTopics []string `env:"AWS_SNS_TOPICS"` + } + + Timeout time.Duration `env:"TIMEOUT,default=1m,strict"` +} +``` + +Fields _must be exported_ (i.e. begin with a capital letter) in order +for `envdecode` to work with them. An error will be returned if a +struct with no exported fields is decoded (including one that contains +no `env` tags at all). +Default values may be provided by appending ",default=value" to the +struct tag. Required values may be marked by appending ",required" to the +struct tag. Strict values may be marked by appending ",strict" which will +return an error on Decode if there is an error while parsing. + +Then call `envdecode.Decode`: + +```go +var cfg Config +err := envdecode.Decode(&cfg) +``` + +If you want all fields to act `strict`, you may use `envdecode.StrictDecode`: + +```go +var cfg Config +err := envdecode.StrictDecode(&cfg) +``` + +All parse errors will fail fast and return an error in this mode. + +## Supported types + +- Structs (and pointer to structs) +- Slices of below defined types, separated by semicolon +- `bool` +- `float32`, `float64` +- `int`, `int8`, `int16`, `int32`, `int64` +- `uint`, `uint8`, `uint16`, `uint32`, `uint64` +- `string` +- `time.Duration`, using the [`time.ParseDuration()` format](http://golang.org/pkg/time/#ParseDuration) +- `*url.URL`, using [`url.Parse()`](https://godoc.org/net/url#Parse) +- Types those implement a `Decoder` interface + +## Custom `Decoder` + +If you want a field to be decoded with custom behavior, you may implement the interface `Decoder` for the filed type. + +```go +type Config struct { + IPAddr IP `env:"IP_ADDR"` +} + +type IP net.IP + +// Decode implements the interface `envdecode.Decoder` +func (i *IP) Decode(repl string) error { + *i = net.ParseIP(repl) + return nil +} +``` + +`Decoder` is the interface implemented by an object that can decode an environment variable string representation of itself. diff --git a/ocis-pkg/config/envdecode/envdecode.go b/ocis-pkg/config/envdecode/envdecode.go new file mode 100644 index 000000000..0105c1764 --- /dev/null +++ b/ocis-pkg/config/envdecode/envdecode.go @@ -0,0 +1,435 @@ +// Package envdecode is a package for populating structs from environment +// variables, using struct tags. +package envdecode + +import ( + "encoding" + "errors" + "fmt" + "log" + "net/url" + "os" + "reflect" + "sort" + "strconv" + "strings" + "time" +) + +// ErrInvalidTarget indicates that the target value passed to +// Decode is invalid. Target must be a non-nil pointer to a struct. +var ErrInvalidTarget = errors.New("target must be non-nil pointer to struct that has at least one exported field with a valid env tag") +var ErrNoTargetFieldsAreSet = errors.New("none of the target fields were set from environment variables") + +// FailureFunc is called when an error is encountered during a MustDecode +// operation. It prints the error and terminates the process. +// +// This variable can be assigned to another function of the user-programmer's +// design, allowing for graceful recovery of the problem, such as loading +// from a backup configuration file. +var FailureFunc = func(err error) { + log.Fatalf("envdecode: an error was encountered while decoding: %v\n", err) +} + +// Decoder is the interface implemented by an object that can decode an +// environment variable string representation of itself. +type Decoder interface { + Decode(string) error +} + +// Decode environment variables into the provided target. The target +// must be a non-nil pointer to a struct. Fields in the struct must +// be exported, and tagged with an "env" struct tag with a value +// containing the name of the environment variable. An error is +// returned if there are no exported members tagged. +// +// Default values may be provided by appending ",default=value" to the +// struct tag. Required values may be marked by appending ",required" +// to the struct tag. It is an error to provide both "default" and +// "required". Strict values may be marked by appending ",strict" which +// will return an error on Decode if there is an error while parsing. +// If everything must be strict, consider using StrictDecode instead. +// +// All primitive types are supported, including bool, floating point, +// signed and unsigned integers, and string. Boolean and numeric +// types are decoded using the standard strconv Parse functions for +// those types. Structs and pointers to structs are decoded +// recursively. time.Duration is supported via the +// time.ParseDuration() function and *url.URL is supported via the +// url.Parse() function. Slices are supported for all above mentioned +// primitive types. Semicolon is used as delimiter in environment variables. +func Decode(target interface{}) error { + nFields, err := decode(target, false) + if err != nil { + return err + } + + // if we didn't do anything - the user probably did something + // wrong like leave all fields unexported. + if nFields == 0 { + return ErrNoTargetFieldsAreSet + } + + return nil +} + +// StrictDecode is similar to Decode except all fields will have an implicit +// ",strict" on all fields. +func StrictDecode(target interface{}) error { + nFields, err := decode(target, true) + if err != nil { + return err + } + + // if we didn't do anything - the user probably did something + // wrong like leave all fields unexported. + if nFields == 0 { + return ErrInvalidTarget + } + + return nil +} + +func decode(target interface{}, strict bool) (int, error) { + s := reflect.ValueOf(target) + if s.Kind() != reflect.Ptr || s.IsNil() { + return 0, ErrInvalidTarget + } + + s = s.Elem() + if s.Kind() != reflect.Struct { + return 0, ErrInvalidTarget + } + + t := s.Type() + setFieldCount := 0 + for i := 0; i < s.NumField(); i++ { + // Localize the umbrella `strict` value to the specific field. + strict := strict + + f := s.Field(i) + + switch f.Kind() { + case reflect.Ptr: + if f.Elem().Kind() != reflect.Struct { + break + } + + f = f.Elem() + fallthrough + + case reflect.Struct: + if !f.Addr().CanInterface() { + continue + } + + ss := f.Addr().Interface() + _, custom := ss.(Decoder) + if custom { + break + } + + n, err := decode(ss, strict) + if err != nil { + return 0, err + } + setFieldCount += n + } + + if !f.CanSet() { + continue + } + + tag := t.Field(i).Tag.Get("env") + if tag == "" { + continue + } + + parts := strings.Split(tag, ",") + overrides := strings.Split(parts[0], `;`) + + var env string + for _, override := range overrides { + v := os.Getenv(override) + if v != "" { + env = v + } + } + + required := false + hasDefault := false + defaultValue := "" + + for _, o := range parts[1:] { + if !required { + required = strings.HasPrefix(o, "required") + } + if strings.HasPrefix(o, "default=") { + hasDefault = true + defaultValue = o[8:] + } + if !strict { + strict = strings.HasPrefix(o, "strict") + } + } + + if required && hasDefault { + panic(`envdecode: "default" and "required" may not be specified in the same annotation`) + } + if env == "" && required { + return 0, fmt.Errorf("the environment variable \"%s\" is missing", parts[0]) + } + if env == "" { + env = defaultValue + } + if env == "" { + continue + } + + setFieldCount++ + + unmarshaler, implementsUnmarshaler := f.Addr().Interface().(encoding.TextUnmarshaler) + decoder, implmentsDecoder := f.Addr().Interface().(Decoder) + if implmentsDecoder { + if err := decoder.Decode(env); err != nil { + return 0, err + } + } else if implementsUnmarshaler { + if err := unmarshaler.UnmarshalText([]byte(env)); err != nil { + return 0, err + } + } else if f.Kind() == reflect.Slice { + if err := decodeSlice(&f, env); err != nil { + return 0, err + } + } else { + if err := decodePrimitiveType(&f, env); err != nil && strict { + return 0, err + } + } + } + + return setFieldCount, nil +} + +func decodeSlice(f *reflect.Value, env string) error { + parts := strings.Split(env, ";") + + values := parts[:0] + for _, x := range parts { + if x != "" { + values = append(values, strings.TrimSpace(x)) + } + } + + valuesCount := len(values) + slice := reflect.MakeSlice(f.Type(), valuesCount, valuesCount) + if valuesCount > 0 { + for i := 0; i < valuesCount; i++ { + e := slice.Index(i) + err := decodePrimitiveType(&e, values[i]) + if err != nil { + return err + } + } + } + + f.Set(slice) + return nil +} + +func decodePrimitiveType(f *reflect.Value, env string) error { + switch f.Kind() { + case reflect.Bool: + v, err := strconv.ParseBool(env) + if err != nil { + return err + } + f.SetBool(v) + + case reflect.Float32, reflect.Float64: + bits := f.Type().Bits() + v, err := strconv.ParseFloat(env, bits) + if err != nil { + return err + } + f.SetFloat(v) + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if t := f.Type(); t.PkgPath() == "time" && t.Name() == "Duration" { + v, err := time.ParseDuration(env) + if err != nil { + return err + } + f.SetInt(int64(v)) + } else { + bits := f.Type().Bits() + v, err := strconv.ParseInt(env, 0, bits) + if err != nil { + return err + } + f.SetInt(v) + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + bits := f.Type().Bits() + v, err := strconv.ParseUint(env, 0, bits) + if err != nil { + return err + } + f.SetUint(v) + + case reflect.String: + f.SetString(env) + + case reflect.Ptr: + if t := f.Type().Elem(); t.Kind() == reflect.Struct && t.PkgPath() == "net/url" && t.Name() == "URL" { + v, err := url.Parse(env) + if err != nil { + return err + } + f.Set(reflect.ValueOf(v)) + } + } + return nil +} + +// MustDecode calls Decode and terminates the process if any errors +// are encountered. +func MustDecode(target interface{}) { + if err := Decode(target); err != nil { + FailureFunc(err) + } +} + +// MustStrictDecode calls StrictDecode and terminates the process if any errors +// are encountered. +func MustStrictDecode(target interface{}) { + if err := StrictDecode(target); err != nil { + FailureFunc(err) + } +} + +//// Configuration info for Export + +type ConfigInfo struct { + Field string + EnvVar string + Value string + DefaultValue string + HasDefault bool + Required bool + UsesEnv bool +} + +type ConfigInfoSlice []*ConfigInfo + +func (c ConfigInfoSlice) Less(i, j int) bool { + return c[i].EnvVar < c[j].EnvVar +} +func (c ConfigInfoSlice) Len() int { + return len(c) +} +func (c ConfigInfoSlice) Swap(i, j int) { + c[i], c[j] = c[j], c[i] +} + +// Returns a list of final configuration metadata sorted by envvar name +func Export(target interface{}) ([]*ConfigInfo, error) { + s := reflect.ValueOf(target) + if s.Kind() != reflect.Ptr || s.IsNil() { + return nil, ErrInvalidTarget + } + + cfg := []*ConfigInfo{} + + s = s.Elem() + if s.Kind() != reflect.Struct { + return nil, ErrInvalidTarget + } + + t := s.Type() + for i := 0; i < s.NumField(); i++ { + f := s.Field(i) + fName := t.Field(i).Name + + fElem := f + if f.Kind() == reflect.Ptr { + fElem = f.Elem() + } + if fElem.Kind() == reflect.Struct { + ss := fElem.Addr().Interface() + subCfg, err := Export(ss) + if err != ErrInvalidTarget { + f = fElem + for _, v := range subCfg { + v.Field = fmt.Sprintf("%s.%s", fName, v.Field) + cfg = append(cfg, v) + } + } + } + + tag := t.Field(i).Tag.Get("env") + if tag == "" { + continue + } + + parts := strings.Split(tag, ",") + + ci := &ConfigInfo{ + Field: fName, + EnvVar: parts[0], + UsesEnv: os.Getenv(parts[0]) != "", + } + + for _, o := range parts[1:] { + if strings.HasPrefix(o, "default=") { + ci.HasDefault = true + ci.DefaultValue = o[8:] + } else if strings.HasPrefix(o, "required") { + ci.Required = true + } + } + + if f.Kind() == reflect.Ptr && f.IsNil() { + ci.Value = "" + } else if stringer, ok := f.Interface().(fmt.Stringer); ok { + ci.Value = stringer.String() + } else { + switch f.Kind() { + case reflect.Bool: + ci.Value = strconv.FormatBool(f.Bool()) + + case reflect.Float32, reflect.Float64: + bits := f.Type().Bits() + ci.Value = strconv.FormatFloat(f.Float(), 'f', -1, bits) + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + ci.Value = strconv.FormatInt(f.Int(), 10) + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + ci.Value = strconv.FormatUint(f.Uint(), 10) + + case reflect.String: + ci.Value = f.String() + + case reflect.Slice: + ci.Value = fmt.Sprintf("%v", f.Interface()) + + default: + // Unable to determine string format for value + return nil, ErrInvalidTarget + } + } + + cfg = append(cfg, ci) + } + + // No configuration tags found, assume invalid input + if len(cfg) == 0 { + return nil, ErrInvalidTarget + } + + sort.Sort(ConfigInfoSlice(cfg)) + + return cfg, nil +} diff --git a/ocis-pkg/config/envdecode/envdecode_test.go b/ocis-pkg/config/envdecode/envdecode_test.go new file mode 100644 index 000000000..d8cec54c9 --- /dev/null +++ b/ocis-pkg/config/envdecode/envdecode_test.go @@ -0,0 +1,790 @@ +package envdecode + +import ( + "encoding/json" + "fmt" + "math" + "net/url" + "os" + "reflect" + "sort" + "strconv" + "testing" + "time" +) + +type nested struct { + String string `env:"TEST_STRING"` +} + +type testConfig struct { + String string `env:"TEST_STRING"` + Int64 int64 `env:"TEST_INT64"` + Uint16 uint16 `env:"TEST_UINT16"` + Float64 float64 `env:"TEST_FLOAT64"` + Bool bool `env:"TEST_BOOL"` + Duration time.Duration `env:"TEST_DURATION"` + URL *url.URL `env:"TEST_URL"` + + StringSlice []string `env:"TEST_STRING_SLICE"` + Int64Slice []int64 `env:"TEST_INT64_SLICE"` + Uint16Slice []uint16 `env:"TEST_UINT16_SLICE"` + Float64Slice []float64 `env:"TEST_FLOAT64_SLICE"` + BoolSlice []bool `env:"TEST_BOOL_SLICE"` + DurationSlice []time.Duration `env:"TEST_DURATION_SLICE"` + URLSlice []*url.URL `env:"TEST_URL_SLICE"` + + UnsetString string `env:"TEST_UNSET_STRING"` + UnsetInt64 int64 `env:"TEST_UNSET_INT64"` + UnsetDuration time.Duration `env:"TEST_UNSET_DURATION"` + UnsetURL *url.URL `env:"TEST_UNSET_URL"` + UnsetSlice []string `env:"TEST_UNSET_SLICE"` + + InvalidInt64 int64 `env:"TEST_INVALID_INT64"` + + UnusedField string + unexportedField string + + IgnoredPtr *bool `env:"TEST_BOOL"` + + Nested nested + NestedPtr *nested + + DecoderStruct decoderStruct `env:"TEST_DECODER_STRUCT"` + DecoderStructPtr *decoderStruct `env:"TEST_DECODER_STRUCT_PTR"` + + DecoderString decoderString `env:"TEST_DECODER_STRING"` + + UnmarshalerNumber unmarshalerNumber `env:"TEST_UNMARSHALER_NUMBER"` + + DefaultInt int `env:"TEST_UNSET,asdf=asdf,default=1234"` + DefaultSliceInt []int `env:"TEST_UNSET,asdf=asdf,default=1;2;3"` + DefaultDuration time.Duration `env:"TEST_UNSET,asdf=asdf,default=24h"` + DefaultURL *url.URL `env:"TEST_UNSET,default=http://example.com"` +} + +type testConfigNoSet struct { + Some string `env:"TEST_THIS_ENV_WILL_NOT_BE_SET"` +} + +type testConfigRequired struct { + Required string `env:"TEST_REQUIRED,required"` +} + +type testConfigRequiredDefault struct { + RequiredDefault string `env:"TEST_REQUIRED_DEFAULT,required,default=test"` +} + +type testConfigOverride struct { + OverrideString string `env:"TEST_OVERRIDE_A;TEST_OVERRIDE_B,default=override_default"` +} + +type testNoExportedFields struct { + // folowing unexported fields are used for tests + aString string `env:"TEST_STRING"` //nolint:structcheck,unused + anInt64 int64 `env:"TEST_INT64"` //nolint:structcheck,unused + aUint16 uint16 `env:"TEST_UINT16"` //nolint:structcheck,unused + aFloat64 float64 `env:"TEST_FLOAT64"` //nolint:structcheck,unused + aBool bool `env:"TEST_BOOL"` //nolint:structcheck,unused +} + +type testNoTags struct { + String string +} + +type decoderStruct struct { + String string +} + +func (d *decoderStruct) Decode(env string) error { + return json.Unmarshal([]byte(env), &d) +} + +type decoderString string + +func (d *decoderString) Decode(env string) error { + r, l := []rune(env), len(env) + + for i := 0; i < l/2; i++ { + r[i], r[l-1-i] = r[l-1-i], r[i] + } + + *d = decoderString(r) + return nil +} + +type unmarshalerNumber uint8 + +func (o *unmarshalerNumber) UnmarshalText(raw []byte) error { + n, err := strconv.ParseUint(string(raw), 8, 8) // parse text as octal number + if err != nil { + return err + } + *o = unmarshalerNumber(n) + return nil +} + +func TestDecode(t *testing.T) { + int64Val := int64(-(1 << 50)) + int64AsString := fmt.Sprintf("%d", int64Val) + piAsString := fmt.Sprintf("%.48f", math.Pi) + + os.Setenv("TEST_STRING", "foo") + os.Setenv("TEST_INT64", int64AsString) + os.Setenv("TEST_UINT16", "60000") + os.Setenv("TEST_FLOAT64", piAsString) + os.Setenv("TEST_BOOL", "true") + os.Setenv("TEST_DURATION", "10m") + os.Setenv("TEST_URL", "https://example.com") + os.Setenv("TEST_INVALID_INT64", "asdf") + os.Setenv("TEST_STRING_SLICE", "foo;bar") + os.Setenv("TEST_INT64_SLICE", int64AsString+";"+int64AsString) + os.Setenv("TEST_UINT16_SLICE", "60000;50000") + os.Setenv("TEST_FLOAT64_SLICE", piAsString+";"+piAsString) + os.Setenv("TEST_BOOL_SLICE", "true; false; true") + os.Setenv("TEST_DURATION_SLICE", "10m; 20m") + os.Setenv("TEST_URL_SLICE", "https://example.com") + os.Setenv("TEST_DECODER_STRUCT", "{\"string\":\"foo\"}") + os.Setenv("TEST_DECODER_STRUCT_PTR", "{\"string\":\"foo\"}") + os.Setenv("TEST_DECODER_STRING", "oof") + os.Setenv("TEST_UNMARSHALER_NUMBER", "07") + + var tc testConfig + tc.NestedPtr = &nested{} + tc.DecoderStructPtr = &decoderStruct{} + + err := Decode(&tc) + if err != nil { + t.Fatal(err) + } + + if tc.String != "foo" { + t.Fatalf(`Expected "foo", got "%s"`, tc.String) + } + + if tc.Int64 != -(1 << 50) { + t.Fatalf("Expected %d, got %d", -(1 << 50), tc.Int64) + } + + if tc.Uint16 != 60000 { + t.Fatalf("Expected 60000, got %d", tc.Uint16) + } + + if tc.Float64 != math.Pi { + t.Fatalf("Expected %.48f, got %.48f", math.Pi, tc.Float64) + } + + if !tc.Bool { + t.Fatal("Expected true, got false") + } + + duration, _ := time.ParseDuration("10m") + if tc.Duration != duration { + t.Fatalf("Expected %d, got %d", duration, tc.Duration) + } + + if tc.URL == nil { + t.Fatalf("Expected https://example.com, got nil") + } else if tc.URL.String() != "https://example.com" { + t.Fatalf("Expected https://example.com, got %s", tc.URL.String()) + } + + expectedStringSlice := []string{"foo", "bar"} + if !reflect.DeepEqual(tc.StringSlice, expectedStringSlice) { + t.Fatalf("Expected %s, got %s", expectedStringSlice, tc.StringSlice) + } + + expectedInt64Slice := []int64{int64Val, int64Val} + if !reflect.DeepEqual(tc.Int64Slice, expectedInt64Slice) { + t.Fatalf("Expected %#v, got %#v", expectedInt64Slice, tc.Int64Slice) + } + + expectedUint16Slice := []uint16{60000, 50000} + if !reflect.DeepEqual(tc.Uint16Slice, expectedUint16Slice) { + t.Fatalf("Expected %#v, got %#v", expectedUint16Slice, tc.Uint16Slice) + } + + expectedFloat64Slice := []float64{math.Pi, math.Pi} + if !reflect.DeepEqual(tc.Float64Slice, expectedFloat64Slice) { + t.Fatalf("Expected %#v, got %#v", expectedFloat64Slice, tc.Float64Slice) + } + + expectedBoolSlice := []bool{true, false, true} + if !reflect.DeepEqual(tc.BoolSlice, expectedBoolSlice) { + t.Fatalf("Expected %#v, got %#v", expectedBoolSlice, tc.BoolSlice) + } + + duration2, _ := time.ParseDuration("20m") + expectedDurationSlice := []time.Duration{duration, duration2} + if !reflect.DeepEqual(tc.DurationSlice, expectedDurationSlice) { + t.Fatalf("Expected %s, got %s", expectedDurationSlice, tc.DurationSlice) + } + + urlVal, _ := url.Parse("https://example.com") + expectedURLSlice := []*url.URL{urlVal} + if !reflect.DeepEqual(tc.URLSlice, expectedURLSlice) { + t.Fatalf("Expected %s, got %s", expectedURLSlice, tc.URLSlice) + } + + if tc.UnsetString != "" { + t.Fatal("Got non-empty string unexpectedly") + } + + if tc.UnsetInt64 != 0 { + t.Fatal("Got non-zero int unexpectedly") + } + + if tc.UnsetDuration != time.Duration(0) { + t.Fatal("Got non-zero time.Duration unexpectedly") + } + + if tc.UnsetURL != nil { + t.Fatal("Got non-zero *url.URL unexpectedly") + } + + if len(tc.UnsetSlice) > 0 { + t.Fatal("Got not-empty string slice unexpectedly") + } + + if tc.InvalidInt64 != 0 { + t.Fatal("Got non-zero int unexpectedly") + } + + if tc.UnusedField != "" { + t.Fatal("Expected empty field") + } + + if tc.unexportedField != "" { + t.Fatal("Expected empty field") + } + + if tc.IgnoredPtr != nil { + t.Fatal("Expected nil pointer") + } + + if tc.Nested.String != "foo" { + t.Fatalf(`Expected "foo", got "%s"`, tc.Nested.String) + } + + if tc.NestedPtr.String != "foo" { + t.Fatalf(`Expected "foo", got "%s"`, tc.NestedPtr.String) + } + + if tc.DefaultInt != 1234 { + t.Fatalf("Expected 1234, got %d", tc.DefaultInt) + } + + expectedDefaultSlice := []int{1, 2, 3} + if !reflect.DeepEqual(tc.DefaultSliceInt, expectedDefaultSlice) { + t.Fatalf("Expected %d, got %d", expectedDefaultSlice, tc.DefaultSliceInt) + } + + defaultDuration, _ := time.ParseDuration("24h") + if tc.DefaultDuration != defaultDuration { + t.Fatalf("Expected %d, got %d", defaultDuration, tc.DefaultInt) + } + + if tc.DefaultURL.String() != "http://example.com" { + t.Fatalf("Expected http://example.com, got %s", tc.DefaultURL.String()) + } + + if tc.DecoderStruct.String != "foo" { + t.Fatalf("Expected foo, got %s", tc.DecoderStruct.String) + } + + if tc.DecoderStructPtr.String != "foo" { + t.Fatalf("Expected foo, got %s", tc.DecoderStructPtr.String) + } + + if tc.DecoderString != "foo" { + t.Fatalf("Expected foo, got %s", tc.DecoderString) + } + + if tc.UnmarshalerNumber != 07 { + t.Fatalf("Expected 07, got %04o", tc.UnmarshalerNumber) + } + + os.Setenv("TEST_REQUIRED", "required") + var tcr testConfigRequired + + err = Decode(&tcr) + if err != nil { + t.Fatal(err) + } + + if tcr.Required != "required" { + t.Fatalf("Expected \"required\", got %s", tcr.Required) + } + + _, err = Export(&tcr) + if err != nil { + t.Fatal(err) + } + + var tco testConfigOverride + err = Decode(&tco) + if err != nil { + t.Fatal(err) + } + + if tco.OverrideString != "override_default" { + t.Fatalf(`Expected "override_default" but got %s`, tco.OverrideString) + } + + os.Setenv("TEST_OVERRIDE_A", "override_a") + + tco = testConfigOverride{} + err = Decode(&tco) + if err != nil { + t.Fatal(err) + } + + if tco.OverrideString != "override_a" { + t.Fatalf(`Expected "override_a" but got %s`, tco.OverrideString) + } + + os.Setenv("TEST_OVERRIDE_B", "override_b") + + tco = testConfigOverride{} + err = Decode(&tco) + if err != nil { + t.Fatal(err) + } + + if tco.OverrideString != "override_b" { + t.Fatalf(`Expected "override_b" but got %s`, tco.OverrideString) + } +} + +func TestDecodeErrors(t *testing.T) { + var b bool + err := Decode(&b) + if err != ErrInvalidTarget { + t.Fatal("Should have gotten an error decoding into a bool") + } + + var tc testConfig + err = Decode(tc) //nolint:govet + if err != ErrInvalidTarget { + t.Fatal("Should have gotten an error decoding into a non-pointer") + } + + var tcp *testConfig + err = Decode(tcp) + if err != ErrInvalidTarget { + t.Fatal("Should have gotten an error decoding to a nil pointer") + } + + var tnt testNoTags + err = Decode(&tnt) + if err != ErrNoTargetFieldsAreSet { + t.Fatal("Should have gotten an error decoding a struct with no tags") + } + + var tcni testNoExportedFields + err = Decode(&tcni) + if err != ErrNoTargetFieldsAreSet { + t.Fatal("Should have gotten an error decoding a struct with no unexported fields") + } + + var tcr testConfigRequired + os.Clearenv() + err = Decode(&tcr) + if err == nil { + t.Fatal("An error was expected but recieved:", err) + } + + var tcns testConfigNoSet + err = Decode(&tcns) + if err != ErrNoTargetFieldsAreSet { + t.Fatal("Should have gotten an error decoding when no env variables are set") + } + + missing := false + FailureFunc = func(err error) { + missing = true + } + MustDecode(&tcr) + if !missing { + t.Fatal("The FailureFunc should have been called but it was not") + } + + var tcrd testConfigRequiredDefault + defer func() { + _ = recover() + }() + _ = Decode(&tcrd) + t.Fatal("This should not have been reached. A panic should have occured.") +} + +func TestOnlyNested(t *testing.T) { + os.Setenv("TEST_STRING", "foo") + + // No env vars in the outer level are ok, as long as they're + // in the inner struct. + var o struct { + Inner nested + } + if err := Decode(&o); err != nil { + t.Fatalf("Expected no error, got %s", err) + } + + // No env vars in the inner levels are ok, as long as they're + // in the outer struct. + var o2 struct { + Inner noConfig + X string `env:"TEST_STRING"` + } + if err := Decode(&o2); err != nil { + t.Fatalf("Expected no error, got %s", err) + } + + // No env vars in either outer or inner levels should result + // in error + var o3 struct { + Inner noConfig + } + if err := Decode(&o3); err != ErrNoTargetFieldsAreSet { + t.Fatalf("Expected ErrInvalidTarget, got %s", err) + } +} + +func ExampleDecode() { + type Example struct { + // A string field, without any default + String string `env:"EXAMPLE_STRING"` + + // A uint16 field, with a default value of 100 + Uint16 uint16 `env:"EXAMPLE_UINT16,default=100"` + } + + os.Setenv("EXAMPLE_STRING", "an example!") + + var e Example + if err := Decode(&e); err != nil { + panic(err) + } + + // If TEST_STRING is set, e.String will contain its value + fmt.Println(e.String) + + // If TEST_UINT16 is set, e.Uint16 will contain its value. + // Otherwise, it will contain the default value, 100. + fmt.Println(e.Uint16) + + // Output: + // an example! + // 100 +} + +//// Export tests + +type testConfigExport struct { + String string `env:"TEST_STRING"` + Int64 int64 `env:"TEST_INT64"` + Uint16 uint16 `env:"TEST_UINT16"` + Float64 float64 `env:"TEST_FLOAT64"` + Bool bool `env:"TEST_BOOL"` + Duration time.Duration `env:"TEST_DURATION"` + URL *url.URL `env:"TEST_URL"` + + StringSlice []string `env:"TEST_STRING_SLICE"` + + UnsetString string `env:"TEST_UNSET_STRING"` + UnsetInt64 int64 `env:"TEST_UNSET_INT64"` + UnsetDuration time.Duration `env:"TEST_UNSET_DURATION"` + UnsetURL *url.URL `env:"TEST_UNSET_URL"` + + UnusedField string + unexportedField string //nolint:structcheck,unused + + IgnoredPtr *bool `env:"TEST_IGNORED_POINTER"` + + Nested nestedConfigExport + NestedPtr *nestedConfigExportPointer + NestedPtrUnset *nestedConfigExportPointer + + NestedTwice nestedTwiceConfig + + NoConfig noConfig + NoConfigPtr *noConfig + NoConfigPtrSet *noConfig + + RequiredInt int `env:"TEST_REQUIRED_INT,required"` + + DefaultBool bool `env:"TEST_DEFAULT_BOOL,default=true"` + DefaultInt int `env:"TEST_DEFAULT_INT,default=1234"` + DefaultDuration time.Duration `env:"TEST_DEFAULT_DURATION,default=24h"` + DefaultURL *url.URL `env:"TEST_DEFAULT_URL,default=http://example.com"` + DefaultIntSet int `env:"TEST_DEFAULT_INT_SET,default=99"` + DefaultIntSlice []int `env:"TEST_DEFAULT_INT_SLICE,default=99;33"` +} + +type nestedConfigExport struct { + String string `env:"TEST_NESTED_STRING"` +} + +type nestedConfigExportPointer struct { + String string `env:"TEST_NESTED_STRING_POINTER"` +} + +type noConfig struct { + Int int +} + +type nestedTwiceConfig struct { + Nested nestedConfigInner +} + +type nestedConfigInner struct { + String string `env:"TEST_NESTED_TWICE_STRING"` +} + +type testConfigStrict struct { + InvalidInt64Strict int64 `env:"TEST_INVALID_INT64,strict,default=1"` + InvalidInt64Implicit int64 `env:"TEST_INVALID_INT64_IMPLICIT,default=1"` + + Nested struct { + InvalidInt64Strict int64 `env:"TEST_INVALID_INT64_NESTED,strict,required"` + InvalidInt64Implicit int64 `env:"TEST_INVALID_INT64_NESTED_IMPLICIT,required"` + } +} + +func TestInvalidStrict(t *testing.T) { + cases := []struct { + decoder func(interface{}) error + rootValue string + nestedValue string + rootValueImplicit string + nestedValueImplicit string + pass bool + }{ + {Decode, "1", "1", "1", "1", true}, + {Decode, "1", "1", "1", "asdf", true}, + {Decode, "1", "1", "asdf", "1", true}, + {Decode, "1", "1", "asdf", "asdf", true}, + {Decode, "1", "asdf", "1", "1", false}, + {Decode, "asdf", "1", "1", "1", false}, + {Decode, "asdf", "asdf", "1", "1", false}, + {StrictDecode, "1", "1", "1", "1", true}, + {StrictDecode, "asdf", "1", "1", "1", false}, + {StrictDecode, "1", "asdf", "1", "1", false}, + {StrictDecode, "1", "1", "asdf", "1", false}, + {StrictDecode, "1", "1", "1", "asdf", false}, + {StrictDecode, "asdf", "asdf", "1", "1", false}, + {StrictDecode, "1", "asdf", "asdf", "1", false}, + {StrictDecode, "1", "1", "asdf", "asdf", false}, + {StrictDecode, "1", "asdf", "asdf", "asdf", false}, + {StrictDecode, "asdf", "asdf", "asdf", "asdf", false}, + } + + for _, test := range cases { + os.Setenv("TEST_INVALID_INT64", test.rootValue) + os.Setenv("TEST_INVALID_INT64_NESTED", test.nestedValue) + os.Setenv("TEST_INVALID_INT64_IMPLICIT", test.rootValueImplicit) + os.Setenv("TEST_INVALID_INT64_NESTED_IMPLICIT", test.nestedValueImplicit) + + var tc testConfigStrict + if err := test.decoder(&tc); test.pass != (err == nil) { + t.Fatalf("Have err=%s wanted pass=%v", err, test.pass) + } + } +} + +func TestExport(t *testing.T) { + testFloat64 := fmt.Sprintf("%.48f", math.Pi) + testFloat64Output := strconv.FormatFloat(math.Pi, 'f', -1, 64) + testInt64 := fmt.Sprintf("%d", -(1 << 50)) + + os.Setenv("TEST_STRING", "foo") + os.Setenv("TEST_INT64", testInt64) + os.Setenv("TEST_UINT16", "60000") + os.Setenv("TEST_FLOAT64", testFloat64) + os.Setenv("TEST_BOOL", "true") + os.Setenv("TEST_DURATION", "10m") + os.Setenv("TEST_URL", "https://example.com") + os.Setenv("TEST_STRING_SLICE", "foo;bar") + os.Setenv("TEST_NESTED_STRING", "nest_foo") + os.Setenv("TEST_NESTED_STRING_POINTER", "nest_foo_ptr") + os.Setenv("TEST_NESTED_TWICE_STRING", "nest_twice_foo") + os.Setenv("TEST_REQUIRED_INT", "101") + os.Setenv("TEST_DEFAULT_INT_SET", "102") + os.Setenv("TEST_DEFAULT_INT_SLICE", "1;2;3") + + var tc testConfigExport + tc.NestedPtr = &nestedConfigExportPointer{} + tc.NoConfigPtrSet = &noConfig{} + + if err := Decode(&tc); err != nil { + t.Fatal(err) + } + + rc, err := Export(&tc) + if err != nil { + t.Fatal(err) + } + + expected := []*ConfigInfo{ + &ConfigInfo{ + Field: "String", + EnvVar: "TEST_STRING", + Value: "foo", + UsesEnv: true, + }, + &ConfigInfo{ + Field: "Int64", + EnvVar: "TEST_INT64", + Value: testInt64, + UsesEnv: true, + }, + &ConfigInfo{ + Field: "Uint16", + EnvVar: "TEST_UINT16", + Value: "60000", + UsesEnv: true, + }, + &ConfigInfo{ + Field: "Float64", + EnvVar: "TEST_FLOAT64", + Value: testFloat64Output, + UsesEnv: true, + }, + &ConfigInfo{ + Field: "Bool", + EnvVar: "TEST_BOOL", + Value: "true", + UsesEnv: true, + }, + &ConfigInfo{ + Field: "Duration", + EnvVar: "TEST_DURATION", + Value: "10m0s", + UsesEnv: true, + }, + &ConfigInfo{ + Field: "URL", + EnvVar: "TEST_URL", + Value: "https://example.com", + UsesEnv: true, + }, + &ConfigInfo{ + Field: "StringSlice", + EnvVar: "TEST_STRING_SLICE", + Value: "[foo bar]", + UsesEnv: true, + }, + + &ConfigInfo{ + Field: "UnsetString", + EnvVar: "TEST_UNSET_STRING", + Value: "", + }, + &ConfigInfo{ + Field: "UnsetInt64", + EnvVar: "TEST_UNSET_INT64", + Value: "0", + }, + &ConfigInfo{ + Field: "UnsetDuration", + EnvVar: "TEST_UNSET_DURATION", + Value: "0s", + }, + &ConfigInfo{ + Field: "UnsetURL", + EnvVar: "TEST_UNSET_URL", + Value: "", + }, + + &ConfigInfo{ + Field: "IgnoredPtr", + EnvVar: "TEST_IGNORED_POINTER", + Value: "", + }, + + &ConfigInfo{ + Field: "Nested.String", + EnvVar: "TEST_NESTED_STRING", + Value: "nest_foo", + UsesEnv: true, + }, + &ConfigInfo{ + Field: "NestedPtr.String", + EnvVar: "TEST_NESTED_STRING_POINTER", + Value: "nest_foo_ptr", + UsesEnv: true, + }, + + &ConfigInfo{ + Field: "NestedTwice.Nested.String", + EnvVar: "TEST_NESTED_TWICE_STRING", + Value: "nest_twice_foo", + UsesEnv: true, + }, + + &ConfigInfo{ + Field: "RequiredInt", + EnvVar: "TEST_REQUIRED_INT", + Value: "101", + UsesEnv: true, + Required: true, + }, + + &ConfigInfo{ + Field: "DefaultBool", + EnvVar: "TEST_DEFAULT_BOOL", + Value: "true", + DefaultValue: "true", + HasDefault: true, + }, + &ConfigInfo{ + Field: "DefaultInt", + EnvVar: "TEST_DEFAULT_INT", + Value: "1234", + DefaultValue: "1234", + HasDefault: true, + }, + &ConfigInfo{ + Field: "DefaultDuration", + EnvVar: "TEST_DEFAULT_DURATION", + Value: "24h0m0s", + DefaultValue: "24h", + HasDefault: true, + }, + &ConfigInfo{ + Field: "DefaultURL", + EnvVar: "TEST_DEFAULT_URL", + Value: "http://example.com", + DefaultValue: "http://example.com", + HasDefault: true, + }, + &ConfigInfo{ + Field: "DefaultIntSet", + EnvVar: "TEST_DEFAULT_INT_SET", + Value: "102", + DefaultValue: "99", + HasDefault: true, + UsesEnv: true, + }, + &ConfigInfo{ + Field: "DefaultIntSlice", + EnvVar: "TEST_DEFAULT_INT_SLICE", + Value: "[1 2 3]", + DefaultValue: "99;33", + HasDefault: true, + UsesEnv: true, + }, + } + + sort.Sort(ConfigInfoSlice(expected)) + + if len(rc) != len(expected) { + t.Fatalf("Have %d results, expected %d", len(rc), len(expected)) + } + + for n, v := range rc { + ci := expected[n] + if *ci != *v { + t.Fatalf("have %+v, expected %+v", v, ci) + } + } +} diff --git a/ocis-pkg/config/helpers.go b/ocis-pkg/config/helpers.go index f89cdc5dd..7da572a35 100644 --- a/ocis-pkg/config/helpers.go +++ b/ocis-pkg/config/helpers.go @@ -68,7 +68,7 @@ func sanitizeExtensions(set []string, ext []string, f func(a, b string) bool) [] // is to solely modify `dst`, not dealing with the config structs; and do so in a thread safe manner. func BindSourcesToStructs(extension string, dst interface{}) (*gofig.Config, error) { sources := DefaultConfigSources(extension, supportedExtensions) - cnf := gofig.NewWithOptions(extension, gofig.ParseEnv) + cnf := gofig.NewWithOptions(extension) cnf.WithOptions(func(options *gofig.Options) { options.DecoderConfig.TagName = "ocisConfig" }) diff --git a/ocis-pkg/config/parser/parse.go b/ocis-pkg/config/parser/parse.go new file mode 100644 index 000000000..a0870d17c --- /dev/null +++ b/ocis-pkg/config/parser/parse.go @@ -0,0 +1,39 @@ +package parser + +import ( + "errors" + + "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" + "github.com/owncloud/ocis/ocis-pkg/shared" +) + +// ParseConfig loads ocis configuration. +func ParseConfig(cfg *config.Config) error { + _, err := config.BindSourcesToStructs("ocis", cfg) + if err != nil { + return err + } + + // 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 = &shared.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.Commons == nil { + cfg.Log = &shared.Log{} + } + + // 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 + } + } + + return nil +} diff --git a/ocis-pkg/crypto/crypto_test.go b/ocis-pkg/crypto/crypto_test.go index 9bfa55274..328607ba9 100644 --- a/ocis-pkg/crypto/crypto_test.go +++ b/ocis-pkg/crypto/crypto_test.go @@ -16,7 +16,7 @@ var _ = Describe("Crypto", func() { var ( userConfigDir string err error - config = cfg.New() + config = cfg.DefaultConfig() ) BeforeEach(func() { diff --git a/ocis-pkg/indexer/indexer.go b/ocis-pkg/indexer/indexer.go index 2a42a56f7..84189be0a 100644 --- a/ocis-pkg/indexer/indexer.go +++ b/ocis-pkg/indexer/indexer.go @@ -215,12 +215,11 @@ func (i *Indexer) FindByPartial(t interface{}, field string, pattern string) ([] // Update updates all indexes on a value to a value . func (i *Indexer) Update(from, to interface{}) error { typeNameFrom := getTypeFQN(from) - typeNameTo := getTypeFQN(to) i.mu.Lock(typeNameFrom) defer i.mu.Unlock(typeNameFrom) - if typeNameFrom != typeNameTo { + if typeNameTo := getTypeFQN(to); typeNameFrom != typeNameTo { return fmt.Errorf("update types do not match: from %v to %v", typeNameFrom, typeNameTo) } diff --git a/ocis-pkg/log/log.go b/ocis-pkg/log/log.go index 42ae508f8..f2ab74e12 100644 --- a/ocis-pkg/log/log.go +++ b/ocis-pkg/log/log.go @@ -21,7 +21,7 @@ type Logger struct { } // LoggerFromConfig initializes a service-specific logger instance. -func LoggerFromConfig(name string, cfg shared.Log) Logger { +func LoggerFromConfig(name string, cfg *shared.Log) Logger { return NewLogger( Name(name), Level(cfg.Level), diff --git a/ocis-pkg/shared/shared_types.go b/ocis-pkg/shared/shared_types.go index fc655e73d..268c596b0 100644 --- a/ocis-pkg/shared/shared_types.go +++ b/ocis-pkg/shared/shared_types.go @@ -10,15 +10,24 @@ type EnvBinding struct { // Log defines the available logging configuration. type Log struct { - Level string `mapstructure:"level"` - Pretty bool `mapstructure:"pretty"` - Color bool `mapstructure:"color"` - File string `mapstructure:"file"` + Level string `ocisConfig:"level" env:"OCIS_LOG_LEVEL"` + Pretty bool `ocisConfig:"pretty" env:"OCIS_LOG_PRETTY"` + Color bool `ocisConfig:"color" env:"OCIS_LOG_COLOR"` + File string `ocisConfig:"file" env:"OCIS_LOG_FILE"` +} + +// Tracing defines the available tracing configuration. +type Tracing struct { + Enabled bool `ocisConfig:"enabled" env:"OCIS_TRACING_ENABLED"` + Type string `ocisConfig:"type" env:"OCIS_TRACING_TYPE"` + Endpoint string `ocisConfig:"endpoint" env:"OCIS_TRACING_ENDPOINT"` + Collector string `ocisConfig:"collector" env:"OCIS_TRACING_COLLECTOR"` } // Commons holds configuration that are common to all extensions. Each extension can then decide whether // to overwrite its values. type Commons struct { - *Log `mapstructure:"log"` - OcisURL string `mapstructure:"ocis_url"` + Log *Log `ocisConfig:"log"` + Tracing *Tracing `ocisConfig:"tracing"` + OcisURL string `ocisConfig:"ocis_url" env:"OCIS_URL"` } diff --git a/ocis/pkg/command/accounts.go b/ocis/pkg/command/accounts.go index 1cacd70d0..5671fa9b6 100644 --- a/ocis/pkg/command/accounts.go +++ b/ocis/pkg/command/accounts.go @@ -1,11 +1,9 @@ -//go:build !simple -// +build !simple - package command import ( "github.com/owncloud/ocis/accounts/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" ) @@ -13,32 +11,13 @@ import ( // AccountsCommand is the entrypoint for the accounts command. func AccountsCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "accounts", - Usage: "Start accounts server", - Category: "Extensions", - Subcommands: []*cli.Command{ - command.ListAccounts(cfg.Accounts), - command.AddAccount(cfg.Accounts), - command.UpdateAccount(cfg.Accounts), - command.RemoveAccount(cfg.Accounts), - command.InspectAccount(cfg.Accounts), - command.PrintVersion(cfg.Accounts), - }, + Name: cfg.Accounts.Service.Name, + Usage: subcommandDescription(cfg.Accounts.Service.Name), + Category: "extensions", Before: func(ctx *cli.Context) error { - if err := ParseConfig(ctx, cfg); err != nil { - return err - } - - if cfg.Commons != nil { - cfg.Accounts.Commons = cfg.Commons - } - - return nil - }, - Action: func(c *cli.Context) error { - origCmd := command.Server(cfg.Accounts) - return handleOriginalAction(c, origCmd) + return parser.ParseConfig(cfg) }, + Subcommands: command.GetCommands(cfg.Accounts), } } diff --git a/ocis/pkg/command/common.go b/ocis/pkg/command/common.go index 60af3e22a..77bfa381e 100644 --- a/ocis/pkg/command/common.go +++ b/ocis/pkg/command/common.go @@ -1,6 +1,10 @@ package command -import "github.com/urfave/cli/v2" +import ( + "fmt" + + "github.com/urfave/cli/v2" +) func handleOriginalAction(c *cli.Context, cmd *cli.Command) error { @@ -12,3 +16,7 @@ func handleOriginalAction(c *cli.Context, cmd *cli.Command) error { return cli.HandleAction(cmd.Action, c) } + +func subcommandDescription(serviceName string) string { + return fmt.Sprintf("%s extension commands", serviceName) +} diff --git a/ocis/pkg/command/glauth.go b/ocis/pkg/command/glauth.go index c6baacfde..9d7b09b02 100644 --- a/ocis/pkg/command/glauth.go +++ b/ocis/pkg/command/glauth.go @@ -3,6 +3,7 @@ package command import ( "github.com/owncloud/ocis/glauth/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" ) @@ -10,24 +11,13 @@ import ( // GLAuthCommand is the entrypoint for the glauth command. func GLAuthCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "glauth", - Usage: "Start glauth server", - Category: "Extensions", + Name: cfg.GLAuth.Service.Name, + Usage: subcommandDescription(cfg.GLAuth.Service.Name), + Category: "extensions", Before: func(ctx *cli.Context) error { - if err := ParseConfig(ctx, cfg); err != nil { - return err - } - - if cfg.Commons != nil { - cfg.GLAuth.Commons = cfg.Commons - } - - return nil - }, - Action: func(c *cli.Context) error { - origCmd := command.Server(cfg.GLAuth) - return handleOriginalAction(c, origCmd) + return parser.ParseConfig(cfg) }, + Subcommands: command.GetCommands(cfg.GLAuth), } } diff --git a/ocis/pkg/command/graph.go b/ocis/pkg/command/graph.go new file mode 100644 index 000000000..0a663e6db --- /dev/null +++ b/ocis/pkg/command/graph.go @@ -0,0 +1,26 @@ +package command + +import ( + "github.com/owncloud/ocis/graph/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" +) + +// GraphCommand is the entrypoint for the graph command. +func GraphCommand(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: cfg.Graph.Service.Name, + Usage: subcommandDescription(cfg.Graph.Service.Name), + Category: "extensions", + Before: func(ctx *cli.Context) error { + return parser.ParseConfig(cfg) + }, + Subcommands: command.GetCommands(cfg.Graph), + } +} + +func init() { + register.AddCommand(GraphCommand) +} diff --git a/ocis/pkg/command/graphexplorer.go b/ocis/pkg/command/graphexplorer.go new file mode 100644 index 000000000..d88135d0b --- /dev/null +++ b/ocis/pkg/command/graphexplorer.go @@ -0,0 +1,26 @@ +package command + +import ( + "github.com/owncloud/ocis/graph-explorer/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" +) + +// GraphExplorerCommand is the entrypoint for the graph-explorer command. +func GraphExplorerCommand(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: cfg.GraphExplorer.Service.Name, + Usage: subcommandDescription(cfg.GraphExplorer.Service.Name), + Category: "extensions", + Before: func(ctx *cli.Context) error { + return parser.ParseConfig(cfg) + }, + Subcommands: command.GetCommands(cfg.GraphExplorer), + } +} + +func init() { + register.AddCommand(GraphExplorerCommand) +} diff --git a/ocis/pkg/command/idp.go b/ocis/pkg/command/idp.go index 7b35c15ba..8e64daee7 100644 --- a/ocis/pkg/command/idp.go +++ b/ocis/pkg/command/idp.go @@ -3,6 +3,7 @@ package command import ( "github.com/owncloud/ocis/idp/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" ) @@ -10,31 +11,13 @@ import ( // IDPCommand is the entrypoint for the idp command. func IDPCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "idp", - Usage: "Start idp server", - Category: "Extensions", - Subcommands: []*cli.Command{ - command.PrintVersion(cfg.IDP), - }, + Name: cfg.IDP.Service.Name, + Usage: subcommandDescription(cfg.IDP.Service.Name), + Category: "extensions", Before: func(ctx *cli.Context) error { - if err := ParseConfig(ctx, cfg); err != nil { - return err - } - - if cfg.Commons != nil { - cfg.IDP.Commons = cfg.Commons - } - - return nil - }, - Action: func(c *cli.Context) error { - idpCommand := command.Server(cfg.IDP) - if err := idpCommand.Before(c); err != nil { - return err - } - - return cli.HandleAction(idpCommand.Action, c) + return parser.ParseConfig(cfg) }, + Subcommands: command.GetCommands(cfg.IDP), } } diff --git a/ocis/pkg/command/kill.go b/ocis/pkg/command/kill.go index 70530f98a..853acdd71 100644 --- a/ocis/pkg/command/kill.go +++ b/ocis/pkg/command/kill.go @@ -16,8 +16,8 @@ import ( func KillCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "kill", - Usage: "Kill an extension by name", - Category: "Runtime", + Usage: "kill an extension by name in the runtime (supervised mode)", + Category: "runtime", Flags: []cli.Flag{ &cli.StringFlag{ Name: "hostname", diff --git a/ocis/pkg/command/list.go b/ocis/pkg/command/list.go index 7d277e8bf..c7fd15587 100644 --- a/ocis/pkg/command/list.go +++ b/ocis/pkg/command/list.go @@ -15,8 +15,8 @@ import ( func ListCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "list", - Usage: "Lists running ocis extensions", - Category: "Runtime", + Usage: "list oCIS extensions running in the runtime (supervised mode)", + Category: "runtime", Flags: []cli.Flag{ &cli.StringFlag{ Name: "hostname", diff --git a/ocis/pkg/command/ocs.go b/ocis/pkg/command/ocs.go index 17f2d927e..2570f2a38 100644 --- a/ocis/pkg/command/ocs.go +++ b/ocis/pkg/command/ocs.go @@ -1,10 +1,8 @@ -//go:build !simple -// +build !simple - package command import ( "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/parser" "github.com/owncloud/ocis/ocis/pkg/register" "github.com/owncloud/ocis/ocs/pkg/command" "github.com/urfave/cli/v2" @@ -13,27 +11,13 @@ import ( // OCSCommand is the entrypoint for the ocs command. func OCSCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "ocs", - Usage: "Start ocs server", - Category: "Extensions", + Name: cfg.OCS.Service.Name, + Usage: subcommandDescription(cfg.OCS.Service.Name), + Category: "extensions", Before: func(ctx *cli.Context) error { - if err := ParseConfig(ctx, cfg); err != nil { - return err - } - - if cfg.Commons != nil { - cfg.OCS.Commons = cfg.Commons - } - - return nil - }, - Action: func(c *cli.Context) error { - origCmd := command.Server(cfg.OCS) - return handleOriginalAction(c, origCmd) - }, - Subcommands: []*cli.Command{ - command.PrintVersion(cfg.OCS), + return parser.ParseConfig(cfg) }, + Subcommands: command.GetCommands(cfg.OCS), } } diff --git a/ocis/pkg/command/proxy.go b/ocis/pkg/command/proxy.go index 321298689..cd59c6090 100644 --- a/ocis/pkg/command/proxy.go +++ b/ocis/pkg/command/proxy.go @@ -1,10 +1,8 @@ -//go:build !simple -// +build !simple - package command import ( "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/parser" "github.com/owncloud/ocis/ocis/pkg/register" "github.com/owncloud/ocis/proxy/pkg/command" "github.com/urfave/cli/v2" @@ -13,27 +11,13 @@ import ( // ProxyCommand is the entry point for the proxy command. func ProxyCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "proxy", - Usage: "Start proxy server", - Category: "Extensions", - Subcommands: []*cli.Command{ - command.PrintVersion(cfg.Proxy), - }, + Name: cfg.Proxy.Service.Name, + Usage: subcommandDescription(cfg.Proxy.Service.Name), + Category: "extensions", Before: func(ctx *cli.Context) error { - if err := ParseConfig(ctx, cfg); err != nil { - return err - } - - if cfg.Commons != nil { - cfg.Proxy.Commons = cfg.Commons - } - - return nil - }, - Action: func(c *cli.Context) error { - origCmd := command.Server(cfg.Proxy) - return handleOriginalAction(c, origCmd) + return parser.ParseConfig(cfg) }, + Subcommands: command.GetCommands(cfg.Proxy), } } diff --git a/ocis/pkg/command/root.go b/ocis/pkg/command/root.go index 7272c357f..7fb3551af 100644 --- a/ocis/pkg/command/root.go +++ b/ocis/pkg/command/root.go @@ -3,10 +3,8 @@ package command import ( "os" + "github.com/owncloud/ocis/ocis-pkg/clihelper" "github.com/owncloud/ocis/ocis-pkg/config" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/ocis/pkg/register" "github.com/urfave/cli/v2" ) @@ -15,21 +13,10 @@ import ( func Execute() error { cfg := config.DefaultConfig() - app := &cli.App{ - Name: "ocis", - Version: version.String, - Usage: "ownCloud Infinite Scale Stack", - Compiled: version.Compiled(), - Before: func(c *cli.Context) error { - return ParseConfig(c, cfg) - }, - Authors: []*cli.Author{ - { - Name: "ownCloud GmbH", - Email: "support@owncloud.com", - }, - }, - } + app := clihelper.DefaultApp(&cli.App{ + Name: "ocis", + Usage: "ownCloud Infinite Scale Stack", + }) for _, fn := range register.Commands { app.Commands = append( @@ -43,34 +30,5 @@ func Execute() error { Usage: "Show the help", } - cli.VersionFlag = &cli.BoolFlag{ - Name: "version,v", - Usage: "Print the version", - } - return app.Run(os.Args) } - -// NewLogger initializes a service-specific logger instance -func NewLogger(cfg *config.Config) log.Logger { - return log.NewLogger( - log.Name("ocis"), - log.Level(cfg.Log.Level), - log.Pretty(cfg.Log.Pretty), - log.Color(cfg.Log.Color), - log.File(cfg.Log.File), - ) -} - -// ParseConfig loads ocis configuration. -func ParseConfig(c *cli.Context, cfg *config.Config) error { - conf, err := ociscfg.BindSourcesToStructs("ocis", cfg) - if err != nil { - return err - } - - conf.LoadOSEnv(config.GetEnv(), false) - - bindings := config.StructMappings(cfg) - return ociscfg.BindEnv(conf, bindings) -} diff --git a/ocis/pkg/command/run.go b/ocis/pkg/command/run.go index 398bb39e1..1cab47a0d 100644 --- a/ocis/pkg/command/run.go +++ b/ocis/pkg/command/run.go @@ -17,8 +17,8 @@ import ( func RunCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "run", - Usage: "Runs an extension", - Category: "Runtime", + Usage: "run an extension by name in the runtime (supervised mode)", + Category: "runtime", Flags: []cli.Flag{ &cli.StringFlag{ Name: "hostname", diff --git a/ocis/pkg/command/server.go b/ocis/pkg/command/server.go index c750340e8..00b9c89da 100644 --- a/ocis/pkg/command/server.go +++ b/ocis/pkg/command/server.go @@ -1,12 +1,9 @@ -//go:build !simple -// +build !simple - package command import ( - "github.com/owncloud/ocis/ocis-pkg/shared" - "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/parser" + "github.com/owncloud/ocis/ocis-pkg/shared" "github.com/owncloud/ocis/ocis/pkg/register" "github.com/owncloud/ocis/ocis/pkg/runtime" "github.com/urfave/cli/v2" @@ -16,15 +13,15 @@ import ( func Server(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "server", - Usage: "Start fullstack server", - Category: "Fullstack", + Usage: "start a fullstack server (runtime and all extensions in supervised mode)", + Category: "fullstack", Before: func(c *cli.Context) error { - return ParseConfig(c, cfg) + return parser.ParseConfig(cfg) }, Action: func(c *cli.Context) error { cfg.Commons = &shared.Commons{ - Log: &cfg.Log, + Log: cfg.Log, } r := runtime.New(cfg) diff --git a/ocis/pkg/command/settings.go b/ocis/pkg/command/settings.go index adb37be5b..d489ccc80 100644 --- a/ocis/pkg/command/settings.go +++ b/ocis/pkg/command/settings.go @@ -1,10 +1,8 @@ -//go:build !simple -// +build !simple - package command import ( "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/parser" "github.com/owncloud/ocis/ocis/pkg/register" "github.com/owncloud/ocis/settings/pkg/command" "github.com/urfave/cli/v2" @@ -13,27 +11,13 @@ import ( // SettingsCommand is the entry point for the settings command. func SettingsCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "settings", - Usage: "Start settings server", - Category: "Extensions", - Subcommands: []*cli.Command{ - command.PrintVersion(cfg.Settings), - }, + Name: cfg.Settings.Service.Name, + Usage: subcommandDescription(cfg.Settings.Service.Name), + Category: "extensions", Before: func(ctx *cli.Context) error { - if err := ParseConfig(ctx, cfg); err != nil { - return err - } - - if cfg.Commons != nil { - cfg.Settings.Commons = cfg.Commons - } - - return nil - }, - Action: func(c *cli.Context) error { - origCmd := command.Server(cfg.Settings) - return handleOriginalAction(c, origCmd) + return parser.ParseConfig(cfg) }, + Subcommands: command.GetCommands(cfg.Settings), } } diff --git a/ocis/pkg/command/storageappprovider.go b/ocis/pkg/command/storageappprovider.go index 520295965..48744bff5 100644 --- a/ocis/pkg/command/storageappprovider.go +++ b/ocis/pkg/command/storageappprovider.go @@ -1,6 +1,3 @@ -//go:build !simple -// +build !simple - package command import ( @@ -14,8 +11,8 @@ import ( func StorageAppProviderCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "storage-app-provider", - Usage: "Start storage app-provider service", - Category: "Extensions", + Usage: "start storage app-provider service", + Category: "extensions", //Flags: flagset.AppProviderWithConfig(cfg.Storage), Before: func(ctx *cli.Context) error { return ParseStorageCommon(ctx, cfg) diff --git a/ocis/pkg/command/storageauthbasic.go b/ocis/pkg/command/storageauthbasic.go index 62cddc5c8..3523c40b7 100644 --- a/ocis/pkg/command/storageauthbasic.go +++ b/ocis/pkg/command/storageauthbasic.go @@ -1,6 +1,3 @@ -//go:build !simple -// +build !simple - package command import ( @@ -14,8 +11,8 @@ import ( func StorageAuthBasicCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "storage-auth-basic", - Usage: "Start storage auth-basic service", - Category: "Extensions", + Usage: "start storage auth-basic service", + Category: "extensions", //Flags: flagset.AuthBasicWithConfig(cfg.Storage), Before: func(ctx *cli.Context) error { return ParseStorageCommon(ctx, cfg) diff --git a/ocis/pkg/command/storageauthbearer.go b/ocis/pkg/command/storageauthbearer.go index 329e64467..8486373a8 100644 --- a/ocis/pkg/command/storageauthbearer.go +++ b/ocis/pkg/command/storageauthbearer.go @@ -1,6 +1,3 @@ -//go:build !simple -// +build !simple - package command import ( @@ -15,7 +12,7 @@ func StorageAuthBearerCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "storage-auth-bearer", Usage: "Start storage auth-bearer service", - Category: "Extensions", + Category: "extensions", //Flags: flagset.AuthBearerWithConfig(cfg.Storage), Before: func(ctx *cli.Context) error { return ParseStorageCommon(ctx, cfg) diff --git a/ocis/pkg/command/storageauthmachine.go b/ocis/pkg/command/storageauthmachine.go index 573266ca9..2f3392caf 100644 --- a/ocis/pkg/command/storageauthmachine.go +++ b/ocis/pkg/command/storageauthmachine.go @@ -1,6 +1,3 @@ -//go:build !simple -// +build !simple - package command import ( @@ -14,9 +11,9 @@ import ( func StorageAuthMachineCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "storage-auth-machine", - Usage: "Start storage auth-machine service", - Category: "Extensions", - //Flags: flagset.AuthBearerWithConfig(cfg.Storage), + Usage: "start storage auth-machine service", + Category: "extensions", + //Flags: flagset.AuthMachineWithConfig(cfg.Storage), Before: func(ctx *cli.Context) error { return ParseStorageCommon(ctx, cfg) }, diff --git a/ocis/pkg/command/storagefrontend.go b/ocis/pkg/command/storagefrontend.go index 29fb13ee2..9079e268c 100644 --- a/ocis/pkg/command/storagefrontend.go +++ b/ocis/pkg/command/storagefrontend.go @@ -1,6 +1,3 @@ -//go:build !simple -// +build !simple - package command import ( @@ -14,8 +11,8 @@ import ( func StorageFrontendCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "storage-frontend", - Usage: "Start storage frontend", - Category: "Extensions", + Usage: "start storage frontend", + Category: "extensions", //Flags: flagset.FrontendWithConfig(cfg.Storage), Before: func(ctx *cli.Context) error { return ParseStorageCommon(ctx, cfg) diff --git a/ocis/pkg/command/storagegateway.go b/ocis/pkg/command/storagegateway.go index 4f24f3e49..615490fdd 100644 --- a/ocis/pkg/command/storagegateway.go +++ b/ocis/pkg/command/storagegateway.go @@ -1,6 +1,3 @@ -//go:build !simple -// +build !simple - package command import ( @@ -14,8 +11,8 @@ import ( func StorageGatewayCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "storage-gateway", - Usage: "Start storage gateway", - Category: "Extensions", + Usage: "start storage gateway", + Category: "extensions", //Flags: flagset.GatewayWithConfig(cfg.Storage), Before: func(ctx *cli.Context) error { return ParseStorageCommon(ctx, cfg) diff --git a/ocis/pkg/command/storagegroupprovider.go b/ocis/pkg/command/storagegroupprovider.go index 530e8148c..ed7907f4e 100644 --- a/ocis/pkg/command/storagegroupprovider.go +++ b/ocis/pkg/command/storagegroupprovider.go @@ -1,6 +1,3 @@ -//go:build !simple -// +build !simple - package command import ( @@ -14,8 +11,8 @@ import ( func StorageGroupProviderCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "storage-groupprovider", - Usage: "Start storage groupprovider service", - Category: "Extensions", + Usage: "start storage groupprovider service", + Category: "extensions", //Flags: flagset.GroupsWithConfig(cfg.Storage), Before: func(ctx *cli.Context) error { return ParseStorageCommon(ctx, cfg) diff --git a/ocis/pkg/command/storagehome.go b/ocis/pkg/command/storagehome.go index bae5c1c67..14cf7ad91 100644 --- a/ocis/pkg/command/storagehome.go +++ b/ocis/pkg/command/storagehome.go @@ -1,6 +1,3 @@ -//go:build !simple -// +build !simple - package command import ( @@ -14,8 +11,8 @@ import ( func StorageHomeCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "storage-home", - Usage: "Start storage and data provider for /home mount", - Category: "Extensions", + Usage: "start storage and data provider for /home mount", + Category: "extensions", //Flags: flagset.StorageHomeWithConfig(cfg.Storage), Before: func(ctx *cli.Context) error { return ParseStorageCommon(ctx, cfg) diff --git a/ocis/pkg/command/storagemetadata.go b/ocis/pkg/command/storagemetadata.go index 59598a245..cab4a7a08 100644 --- a/ocis/pkg/command/storagemetadata.go +++ b/ocis/pkg/command/storagemetadata.go @@ -11,8 +11,8 @@ import ( func StorageMetadataCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "storage-metadata", - Usage: "Start storage and data service for metadata", - Category: "Extensions", + Usage: "start storage and data service for metadata", + Category: "extensions", Before: func(ctx *cli.Context) error { return ParseStorageCommon(ctx, cfg) }, diff --git a/ocis/pkg/command/storagepubliclink.go b/ocis/pkg/command/storagepubliclink.go index e4d9dc750..653d7baf6 100644 --- a/ocis/pkg/command/storagepubliclink.go +++ b/ocis/pkg/command/storagepubliclink.go @@ -1,6 +1,3 @@ -//go:build !simple -// +build !simple - package command import ( @@ -14,8 +11,8 @@ import ( func StoragePublicLinkCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "storage-public-link", - Usage: "Start storage public link storage", - Category: "Extensions", + Usage: "start storage public link storage", + Category: "extensions", //Flags: flagset.StoragePublicLink(cfg.Storage), Before: func(ctx *cli.Context) error { return ParseStorageCommon(ctx, cfg) diff --git a/ocis/pkg/command/storagesharing.go b/ocis/pkg/command/storagesharing.go index 3a15d90ee..1a2e5f791 100644 --- a/ocis/pkg/command/storagesharing.go +++ b/ocis/pkg/command/storagesharing.go @@ -1,6 +1,3 @@ -//go:build !simple -// +build !simple - package command import ( @@ -14,8 +11,8 @@ import ( func StorageSharingCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "storage-sharing", - Usage: "Start storage sharing service", - Category: "Extensions", + Usage: "start storage sharing service", + Category: "extensions", //Flags: flagset.SharingWithConfig(cfg.Storage), Before: func(ctx *cli.Context) error { return ParseStorageCommon(ctx, cfg) diff --git a/ocis/pkg/command/storageuserprovider.go b/ocis/pkg/command/storageuserprovider.go index fc423737a..611592452 100644 --- a/ocis/pkg/command/storageuserprovider.go +++ b/ocis/pkg/command/storageuserprovider.go @@ -1,6 +1,3 @@ -//go:build !simple -// +build !simple - package command import ( @@ -14,8 +11,8 @@ import ( func StorageUserProviderCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "storage-userprovider", - Usage: "Start storage userprovider service", - Category: "Extensions", + Usage: "start storage userprovider service", + Category: "extensions", //Flags: flagset.UsersWithConfig(cfg.Storage), Before: func(ctx *cli.Context) error { return ParseStorageCommon(ctx, cfg) diff --git a/ocis/pkg/command/storageusers.go b/ocis/pkg/command/storageusers.go index a7978fb69..a6b57ceff 100644 --- a/ocis/pkg/command/storageusers.go +++ b/ocis/pkg/command/storageusers.go @@ -1,6 +1,3 @@ -//go:build !simple -// +build !simple - package command import ( @@ -14,8 +11,8 @@ import ( func StorageUsersCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "storage-users", - Usage: "Start storage and data provider for /users mount", - Category: "Extensions", + Usage: "start storage and data provider for /users mount", + Category: "extensions", //Flags: flagset.StorageUsersWithConfig(cfg.Storage), Before: func(ctx *cli.Context) error { return ParseStorageCommon(ctx, cfg) diff --git a/ocis/pkg/command/store.go b/ocis/pkg/command/store.go index 639462117..7e10d3628 100644 --- a/ocis/pkg/command/store.go +++ b/ocis/pkg/command/store.go @@ -1,11 +1,8 @@ -//go:build !simple -// +build !simple - package command import ( "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/shared" + "github.com/owncloud/ocis/ocis-pkg/config/parser" "github.com/owncloud/ocis/ocis/pkg/register" "github.com/owncloud/ocis/store/pkg/command" "github.com/urfave/cli/v2" @@ -13,34 +10,15 @@ import ( // StoreCommand is the entrypoint for the ocs command. func StoreCommand(cfg *config.Config) *cli.Command { - var globalLog shared.Log return &cli.Command{ - Name: "store", - Usage: "Start a go-micro store", - Category: "Extensions", - Subcommands: []*cli.Command{ - command.PrintVersion(cfg.Store), - }, + Name: cfg.Store.Service.Name, + Usage: subcommandDescription(cfg.Store.Service.Name), + Category: "extensions", Before: func(ctx *cli.Context) error { - if err := ParseConfig(ctx, cfg); err != nil { - return err - } - - globalLog = cfg.Log - - return nil - }, - Action: func(c *cli.Context) error { - // if accounts logging is empty in ocis.yaml - if (cfg.Store.Log == shared.Log{}) && (globalLog != shared.Log{}) { - // we can safely inherit the global logging values. - cfg.Store.Log = globalLog - } - - origCmd := command.Server(cfg.Store) - return handleOriginalAction(c, origCmd) + return parser.ParseConfig(cfg) }, + Subcommands: command.GetCommands(cfg.Store), } } diff --git a/ocis/pkg/command/thumbnails.go b/ocis/pkg/command/thumbnails.go index 0562b5d61..f2c2284b9 100644 --- a/ocis/pkg/command/thumbnails.go +++ b/ocis/pkg/command/thumbnails.go @@ -1,10 +1,8 @@ -//go:build !simple -// +build !simple - package command import ( "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/parser" "github.com/owncloud/ocis/ocis/pkg/register" "github.com/owncloud/ocis/thumbnails/pkg/command" "github.com/urfave/cli/v2" @@ -13,27 +11,13 @@ import ( // ThumbnailsCommand is the entrypoint for the thumbnails command. func ThumbnailsCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "thumbnails", - Usage: "Start thumbnails server", - Category: "Extensions", - Subcommands: []*cli.Command{ - command.PrintVersion(cfg.Thumbnails), - }, + Name: cfg.Thumbnails.Service.Name, + Usage: subcommandDescription(cfg.Thumbnails.Service.Name), + Category: "extensions", Before: func(ctx *cli.Context) error { - if err := ParseConfig(ctx, cfg); err != nil { - return err - } - - if cfg.Commons != nil { - cfg.Thumbnails.Commons = cfg.Commons - } - - return nil - }, - Action: func(c *cli.Context) error { - origCmd := command.Server(cfg.Thumbnails) - return handleOriginalAction(c, origCmd) + return parser.ParseConfig(cfg) }, + Subcommands: command.GetCommands(cfg.Thumbnails), } } diff --git a/ocis/pkg/command/util.go b/ocis/pkg/command/util.go index a2e9b1abc..914c5a37c 100644 --- a/ocis/pkg/command/util.go +++ b/ocis/pkg/command/util.go @@ -2,12 +2,13 @@ package command import ( "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/parser" "github.com/owncloud/ocis/ocis-pkg/shared" "github.com/urfave/cli/v2" ) func ParseStorageCommon(ctx *cli.Context, cfg *config.Config) error { - if err := ParseConfig(ctx, cfg); err != nil { + if err := parser.ParseConfig(cfg); err != nil { return err } diff --git a/ocis/pkg/command/version.go b/ocis/pkg/command/version.go index 21d762b59..4254a46df 100644 --- a/ocis/pkg/command/version.go +++ b/ocis/pkg/command/version.go @@ -7,6 +7,7 @@ import ( tw "github.com/olekukonko/tablewriter" "github.com/owncloud/ocis/ocis-pkg/config" "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/ocis/pkg/register" "github.com/urfave/cli/v2" mreg "go-micro.dev/v4/registry" @@ -16,9 +17,13 @@ import ( func VersionCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "version", - Usage: "Lists running services with version", - Category: "Runtime", + Usage: "print the version of this binary and all 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() serviceList, err := reg.ListServices() if err != nil { diff --git a/ocis/pkg/command/web.go b/ocis/pkg/command/web.go index c3f2df2ea..d5b2a9133 100644 --- a/ocis/pkg/command/web.go +++ b/ocis/pkg/command/web.go @@ -2,6 +2,7 @@ package command import ( "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/parser" "github.com/owncloud/ocis/ocis/pkg/register" "github.com/owncloud/ocis/web/pkg/command" "github.com/urfave/cli/v2" @@ -10,24 +11,13 @@ import ( // WebCommand is the entrypoint for the web command. func WebCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "web", - Usage: "Start web server", - Category: "Extensions", + Name: cfg.Web.Service.Name, + Usage: subcommandDescription(cfg.Web.Service.Name), + Category: "extensions", Before: func(ctx *cli.Context) error { - if err := ParseConfig(ctx, cfg); err != nil { - return err - } - - if cfg.Commons != nil { - cfg.Web.Commons = cfg.Commons - } - - return nil - }, - Action: func(c *cli.Context) error { - origCmd := command.Server(cfg.Web) - return handleOriginalAction(c, origCmd) + return parser.ParseConfig(cfg) }, + Subcommands: command.GetCommands(cfg.Web), } } diff --git a/ocis/pkg/command/webdav.go b/ocis/pkg/command/webdav.go index 0502642bb..f09b59a13 100644 --- a/ocis/pkg/command/webdav.go +++ b/ocis/pkg/command/webdav.go @@ -1,10 +1,8 @@ -//go:build !simple -// +build !simple - package command import ( "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/parser" "github.com/owncloud/ocis/ocis/pkg/register" "github.com/owncloud/ocis/webdav/pkg/command" "github.com/urfave/cli/v2" @@ -14,27 +12,13 @@ import ( func WebDAVCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "webdav", - Usage: "Start webdav server", - Category: "Extensions", - Subcommands: []*cli.Command{ - command.PrintVersion(cfg.WebDAV), - }, + Name: cfg.WebDAV.Service.Name, + Usage: subcommandDescription(cfg.WebDAV.Service.Name), + Category: "extensions", Before: func(ctx *cli.Context) error { - if err := ParseConfig(ctx, cfg); err != nil { - return err - } - - if cfg.Commons != nil { - cfg.WebDAV.Commons = cfg.Commons - } - - return nil - }, - Action: func(c *cli.Context) error { - origCmd := command.Server(cfg.WebDAV) - return handleOriginalAction(c, origCmd) + return parser.ParseConfig(cfg) }, + Subcommands: command.GetCommands(cfg.WebDAV), } } diff --git a/ocis/pkg/runtime/service/service.go b/ocis/pkg/runtime/service/service.go index 2def6c6f5..04f68d24b 100644 --- a/ocis/pkg/runtime/service/service.go +++ b/ocis/pkg/runtime/service/service.go @@ -168,10 +168,10 @@ func Start(o ...Option) error { s.cfg.Storage.Log = &shared.Log{} } - s.cfg.Storage.Log.Color = s.cfg.Commons.Color - s.cfg.Storage.Log.Level = s.cfg.Commons.Level - s.cfg.Storage.Log.Pretty = s.cfg.Commons.Pretty - s.cfg.Storage.Log.File = s.cfg.Commons.File + s.cfg.Storage.Log.Color = s.cfg.Commons.Log.Color + s.cfg.Storage.Log.Level = s.cfg.Commons.Log.Level + s.cfg.Storage.Log.Pretty = s.cfg.Commons.Log.Pretty + s.cfg.Storage.Log.File = s.cfg.Commons.Log.File if err = rpc.Register(s); err != nil { if s != nil { diff --git a/ocs/pkg/command/health.go b/ocs/pkg/command/health.go index 51474a3da..b49d3b41a 100644 --- a/ocs/pkg/command/health.go +++ b/ocs/pkg/command/health.go @@ -5,19 +5,22 @@ import ( "net/http" "github.com/owncloud/ocis/ocs/pkg/config" + "github.com/owncloud/ocis/ocs/pkg/config/parser" + "github.com/owncloud/ocis/ocs/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", + Name: "health", + Usage: "check health status", + Category: "info", Before: func(c *cli.Context) error { - return ParseConfig(c, cfg) + return parser.ParseConfig(cfg) }, Action: func(c *cli.Context) error { - logger := NewLogger(cfg) + logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( fmt.Sprintf( @@ -34,7 +37,7 @@ func Health(cfg *config.Config) *cli.Command { defer resp.Body.Close() - if resp.StatusCode != 200 { + if resp.StatusCode != http.StatusOK { logger.Fatal(). Int("code", resp.StatusCode). Msg("Health seems to be in bad state") diff --git a/ocs/pkg/command/root.go b/ocs/pkg/command/root.go index 19c53801c..cac620e67 100644 --- a/ocs/pkg/command/root.go +++ b/ocs/pkg/command/root.go @@ -4,93 +4,43 @@ import ( "context" "os" - "github.com/owncloud/ocis/ocis-pkg/shared" - + "github.com/owncloud/ocis/ocis-pkg/clihelper" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/ocs/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-ocs command. func Execute(cfg *config.Config) error { - app := &cli.App{ + app := clihelper.DefaultApp(&cli.App{ Name: "ocis-ocs", - Version: version.String, Usage: "Serve OCS API for oCIS", - Compiled: version.Compiled(), - - Authors: []*cli.Author{ - { - Name: "ownCloud GmbH", - Email: "support@owncloud.com", - }, - }, - - Before: func(c *cli.Context) error { - cfg.Service.Version = version.String - return nil - }, - - Commands: []*cli.Command{ - Server(cfg), - Health(cfg), - PrintVersion(cfg), - }, - } + Commands: GetCommands(cfg), + }) cli.HelpFlag = &cli.BoolFlag{ Name: "help,h", Usage: "Show the help", } - cli.VersionFlag = &cli.BoolFlag{ - Name: "version,v", - Usage: "Print the version", - } - return app.Run(os.Args) } -// NewLogger initializes a service-specific logger instance. -func NewLogger(cfg *config.Config) log.Logger { - return log.NewLogger( - log.Name("ocs"), - log.Level(cfg.Log.Level), - log.Pretty(cfg.Log.Pretty), - log.Color(cfg.Log.Color), - log.File(cfg.Log.File), - ) -} - -// ParseConfig loads idp configuration from known paths. -func ParseConfig(c *cli.Context, cfg *config.Config) error { - conf, err := ociscfg.BindSourcesToStructs("ocs", cfg) - if err != nil { - return err - } - - // 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 = &shared.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.Commons == nil { - cfg.Log = &shared.Log{} - } - - // load all env variables relevant to the config in the current context. - conf.LoadOSEnv(config.GetEnv(cfg), false) - - bindings := config.StructMappings(cfg) - return ociscfg.BindEnv(conf, bindings) -} - // SutureService allows for the ocs command to be embedded and supervised by a suture supervisor tree. type SutureService struct { cfg *config.Config diff --git a/ocs/pkg/command/server.go b/ocs/pkg/command/server.go index d72e96216..d8d0acfe4 100644 --- a/ocs/pkg/command/server.go +++ b/ocs/pkg/command/server.go @@ -2,8 +2,11 @@ package command import ( "context" - "strings" + "fmt" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/owncloud/ocis/ocs/pkg/config/parser" + "github.com/owncloud/ocis/ocs/pkg/logging" "github.com/owncloud/ocis/ocs/pkg/tracing" "github.com/oklog/run" @@ -17,23 +20,16 @@ import ( // Server is the entrypoint for the server command. func Server(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "server", - Usage: "Start integrated server", - Before: func(ctx *cli.Context) error { - if cfg.HTTP.Root != "/" { - cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") - } - - if err := ParseConfig(ctx, cfg); err != nil { - return err - } - - return nil + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + return parser.ParseConfig(cfg) }, Action: func(c *cli.Context) error { - logger := NewLogger(cfg) - - if err := tracing.Configure(cfg); err != nil { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg) + if err != nil { return err } @@ -50,7 +46,7 @@ func Server(cfg *config.Config) *cli.Command { defer cancel() - metrics.BuildInfo.WithLabelValues(cfg.Service.Version).Set(1) + metrics.BuildInfo.WithLabelValues(version.String).Set(1) { server, err := http.Server( diff --git a/ocs/pkg/command/version.go b/ocs/pkg/command/version.go index 2c1b64e0e..e00e7b980 100644 --- a/ocs/pkg/command/version.go +++ b/ocs/pkg/command/version.go @@ -5,30 +5,33 @@ import ( "os" "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" tw "github.com/olekukonko/tablewriter" "github.com/owncloud/ocis/ocs/pkg/config" "github.com/urfave/cli/v2" ) -// PrintVersion prints the service versions of all running instances. -func PrintVersion(cfg *config.Config) *cli.Command { +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "version", - Usage: "Print the versions of the running instances", - Before: func(c *cli.Context) error { - return ParseConfig(c, cfg) - }, + 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.Service.Namespace + "." + cfg.Service.Name) + services, err := reg.GetService(cfg.HTTP.Namespace + "." + cfg.Service.Name) if err != nil { - fmt.Println(fmt.Errorf("could not get ocs services from the registry: %v", err)) + 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 ocs service found.") + fmt.Println("No running " + cfg.Service.Name + " service found.") return nil } diff --git a/ocs/pkg/config/config.go b/ocs/pkg/config/config.go index 01f2d65bb..bfad75d86 100644 --- a/ocs/pkg/config/config.go +++ b/ocs/pkg/config/config.go @@ -6,127 +6,33 @@ import ( "github.com/owncloud/ocis/ocis-pkg/shared" ) -// Debug defines the available debug configuration. -type Debug struct { - Addr string `ocisConfig:"addr"` - Token string `ocisConfig:"token"` - Pprof bool `ocisConfig:"pprof"` - Zpages bool `ocisConfig:"zpages"` -} +// Config combines all available configuration parts. +type Config struct { + *shared.Commons -// CORS defines the available cors configuration. -type CORS struct { - AllowedOrigins []string `ocisConfig:"allowed_origins"` - AllowedMethods []string `ocisConfig:"allowed_methods"` - AllowedHeaders []string `ocisConfig:"allowed_headers"` - AllowCredentials bool `ocisConfig:"allow_credentials"` -} + Service Service -// HTTP defines the available http configuration. -type HTTP struct { - Addr string `ocisConfig:"addr"` - Root string `ocisConfig:"root"` - CORS CORS `ocisConfig:"cors"` -} + Tracing Tracing `ocisConfig:"tracing"` + Log *Log `ocisConfig:"log"` + Debug Debug `ocisConfig:"debug"` -// Service defines the available service configuration. -type Service struct { - Name string `ocisConfig:"name"` - Namespace string `ocisConfig:"namespace"` - Version string `ocisConfig:"version"` -} + HTTP HTTP `ocisConfig:"http"` -// Tracing defines the available tracing configuration. -type Tracing struct { - Enabled bool `ocisConfig:"enabled"` - Type string `ocisConfig:"type"` - Endpoint string `ocisConfig:"endpoint"` - Collector string `ocisConfig:"collector"` - Service string `ocisConfig:"service"` -} + TokenManager TokenManager `ocisConfig:"token_manager"` + Reva Reva `ocisConfig:"reva"` -// Reva defines all available REVA configuration. -type Reva struct { - Address string `ocisConfig:"address"` -} + IdentityManagement IdentityManagement `ocisConfig:"identity_management"` -// TokenManager is the config for using the reva token manager -type TokenManager struct { - JWTSecret string `ocisConfig:"jwt_secret"` + AccountBackend string `ocisConfig:"account_backend" env:"OCS_ACCOUNT_BACKEND_TYPE"` + StorageUsersDriver string `ocisConfig:"storage_users_driver" env:"STORAGE_USERS_DRIVER;OCS_STORAGE_USERS_DRIVER"` + MachineAuthAPIKey string `ocisConfig:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY;OCS_MACHINE_AUTH_API_KEY"` + + Context context.Context } // IdentityManagement keeps track of the OIDC address. This is because Reva requisite of uniqueness for users // is based in the combination of IDP hostname + UserID. For more information see: // https://github.com/cs3org/reva/blob/4fd0229f13fae5bc9684556a82dbbd0eced65ef9/pkg/storage/utils/decomposedfs/node/node.go#L856-L865 type IdentityManagement struct { - Address string `ocisConfig:"address"` -} - -// Config combines all available configuration parts. -type Config struct { - *shared.Commons - - File string `ocisConfig:"file"` - Log *shared.Log `ocisConfig:"log"` - Debug Debug `ocisConfig:"debug"` - HTTP HTTP `ocisConfig:"http"` - Tracing Tracing `ocisConfig:"tracing"` - TokenManager TokenManager `ocisConfig:"token_manager"` - Service Service `ocisConfig:"service"` - AccountBackend string `ocisConfig:"account_backend"` - Reva Reva `ocisConfig:"reva"` - StorageUsersDriver string `ocisConfig:"storage_users_driver"` - MachineAuthAPIKey string `ocisConfig:"machine_auth_api_key"` - IdentityManagement IdentityManagement `ocisConfig:"identity_management"` - - Context context.Context - Supervised bool -} - -// New initializes a new configuration with or without defaults. -func New() *Config { - return &Config{} -} - -// DefaultConfig provides default values for a config struct. -func DefaultConfig() *Config { - return &Config{ - Debug: Debug{ - Addr: "127.0.0.1:9114", - Token: "", - Pprof: false, - Zpages: false, - }, - HTTP: HTTP{ - Addr: "127.0.0.1:9110", - Root: "/ocs", - CORS: CORS{ - AllowedOrigins: []string{"*"}, - AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, - AllowedHeaders: []string{"Authorization", "Origin", "Content-Type", "Accept", "X-Requested-With"}, - AllowCredentials: true, - }, - }, - Tracing: Tracing{ - Enabled: false, - Type: "jaeger", - Endpoint: "", - Collector: "", - Service: "ocs", - }, - TokenManager: TokenManager{ - JWTSecret: "Pive-Fumkiu4", - }, - Service: Service{ - Name: "ocs", - Namespace: "com.owncloud.web", - }, - AccountBackend: "accounts", - Reva: Reva{Address: "127.0.0.1:9142"}, - StorageUsersDriver: "ocis", - MachineAuthAPIKey: "change-me-please", - IdentityManagement: IdentityManagement{ - Address: "https://localhost:9200", - }, - } + Address string `ocisConfig:"address" env:"OCIS_URL;OCS_IDM_ADDRESS"` } diff --git a/ocs/pkg/config/debug.go b/ocs/pkg/config/debug.go new file mode 100644 index 000000000..baef37488 --- /dev/null +++ b/ocs/pkg/config/debug.go @@ -0,0 +1,9 @@ +package config + +// Debug defines the available debug configuration. +type Debug struct { + Addr string `ocisConfig:"addr" env:"OCS_DEBUG_ADDR"` + Token string `ocisConfig:"token" env:"OCS_DEBUG_TOKEN"` + Pprof bool `ocisConfig:"pprof" env:"OCS_DEBUG_PPROF"` + Zpages bool `ocisConfig:"zpages" env:"OCS_DEBUG_ZPAGES"` +} diff --git a/ocs/pkg/config/defaultconfig.go b/ocs/pkg/config/defaultconfig.go new file mode 100644 index 000000000..59cbfb8f1 --- /dev/null +++ b/ocs/pkg/config/defaultconfig.go @@ -0,0 +1,42 @@ +package config + +func DefaultConfig() *Config { + return &Config{ + Debug: Debug{ + Addr: "127.0.0.1:9114", + Token: "", + Pprof: false, + Zpages: false, + }, + HTTP: HTTP{ + Addr: "127.0.0.1:9110", + Root: "/ocs", + Namespace: "com.owncloud.web", + CORS: CORS{ + AllowedOrigins: []string{"*"}, + AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, + AllowedHeaders: []string{"Authorization", "Origin", "Content-Type", "Accept", "X-Requested-With"}, + AllowCredentials: true, + }, + }, + Service: Service{ + Name: "ocs", + }, + Tracing: Tracing{ + Enabled: false, + Type: "jaeger", + Endpoint: "", + Collector: "", + }, + TokenManager: TokenManager{ + JWTSecret: "Pive-Fumkiu4", + }, + AccountBackend: "accounts", + Reva: Reva{Address: "127.0.0.1:9142"}, + StorageUsersDriver: "ocis", + MachineAuthAPIKey: "change-me-please", + IdentityManagement: IdentityManagement{ + Address: "https://localhost:9200", + }, + } +} diff --git a/ocs/pkg/config/http.go b/ocs/pkg/config/http.go new file mode 100644 index 000000000..b965e78b3 --- /dev/null +++ b/ocs/pkg/config/http.go @@ -0,0 +1,17 @@ +package config + +// HTTP defines the available http configuration. +type HTTP struct { + Addr string `ocisConfig:"addr" env:"OCS_HTTP_ADDR"` + Root string `ocisConfig:"root" env:"OCS_HTTP_ROOT"` + Namespace string + CORS CORS `ocisConfig:"cors"` +} + +// CORS defines the available cors configuration. +type CORS struct { + AllowedOrigins []string `ocisConfig:"allowed_origins"` + AllowedMethods []string `ocisConfig:"allowed_methods"` + AllowedHeaders []string `ocisConfig:"allowed_headers"` + AllowCredentials bool `ocisConfig:"allowed_credentials"` +} diff --git a/ocs/pkg/config/log.go b/ocs/pkg/config/log.go new file mode 100644 index 000000000..4f235f1ca --- /dev/null +++ b/ocs/pkg/config/log.go @@ -0,0 +1,9 @@ +package config + +// Log defines the available log configuration. +type Log struct { + Level string `mapstructure:"level" env:"OCIS_LOG_LEVEL;OCS_LOG_LEVEL"` + Pretty bool `mapstructure:"pretty" env:"OCIS_LOG_PRETTY;OCS_LOG_PRETTY"` + Color bool `mapstructure:"color" env:"OCIS_LOG_COLOR;OCS_LOG_COLOR"` + File string `mapstructure:"file" env:"OCIS_LOG_FILE;OCS_LOG_FILE"` +} diff --git a/ocs/pkg/config/mappings.go b/ocs/pkg/config/mappings.go deleted file mode 100644 index 2aab7b910..000000000 --- a/ocs/pkg/config/mappings.go +++ /dev/null @@ -1,123 +0,0 @@ -package config - -import "github.com/owncloud/ocis/ocis-pkg/shared" - -// GetEnv fetches a list of known env variables for this extension. It is to be used by gookit, as it provides a list -// with all the environment variables an extension supports. -func GetEnv(cfg *Config) []string { - var r = make([]string, len(structMappings(cfg))) - for i := range structMappings(cfg) { - r = append(r, structMappings(cfg)[i].EnvVars...) - } - - return r -} - -// StructMappings binds a set of environment variables to a destination on cfg. Iterating over this set and editing the -// Destination value of a binding will alter the original value, as it is a pointer to its memory address. This lets -// us propagate changes easier. -func StructMappings(cfg *Config) []shared.EnvBinding { - return structMappings(cfg) -} - -// structMappings binds a set of environment variables to a destination on cfg. -func structMappings(cfg *Config) []shared.EnvBinding { - return []shared.EnvBinding{ - { - EnvVars: []string{"OCIS_LOG_FILE", "OCS_LOG_FILE"}, - Destination: &cfg.Log.File, - }, - { - EnvVars: []string{"OCIS_LOG_LEVEL", "OCS_LOG_LEVEL"}, - Destination: &cfg.Log.Level, - }, - { - EnvVars: []string{"OCIS_LOG_PRETTY", "OCS_LOG_PRETTY"}, - Destination: &cfg.Log.Pretty, - }, - { - EnvVars: []string{"OCIS_LOG_COLOR", "OCS_LOG_COLOR"}, - Destination: &cfg.Log.Color, - }, - { - EnvVars: []string{"OCS_CONFIG_FILE"}, - Destination: &cfg.File, - }, - { - EnvVars: []string{"OCIS_TRACING_ENABLED", "OCS_TRACING_ENABLED"}, - Destination: &cfg.Tracing.Enabled, - }, - { - EnvVars: []string{"OCIS_TRACING_TYPE", "OCS_TRACING_TYPE"}, - Destination: &cfg.Tracing.Type, - }, - { - EnvVars: []string{"OCIS_TRACING_ENDPOINT", "OCS_TRACING_ENDPOINT"}, - Destination: &cfg.Tracing.Endpoint, - }, - { - EnvVars: []string{"OCIS_TRACING_COLLECTOR", "OCS_TRACING_COLLECTOR"}, - Destination: &cfg.Tracing.Collector, - }, - { - EnvVars: []string{"OCS_TRACING_SERVICE"}, - Destination: &cfg.Tracing.Service, - }, - { - EnvVars: []string{"OCS_DEBUG_ADDR"}, - Destination: &cfg.Debug.Addr, - }, - { - EnvVars: []string{"OCS_DEBUG_TOKEN"}, - Destination: &cfg.Debug.Token, - }, - { - EnvVars: []string{"OCS_DEBUG_PPROF"}, - Destination: &cfg.Debug.Pprof, - }, - { - EnvVars: []string{"OCS_DEBUG_ZPAGES"}, - Destination: &cfg.Debug.Zpages, - }, - { - EnvVars: []string{"OCS_HTTP_ADDR"}, - Destination: &cfg.HTTP.Addr, - }, - { - EnvVars: []string{"OCS_NAMESPACE"}, - Destination: &cfg.Service.Namespace, - }, - { - EnvVars: []string{"OCS_NAME"}, - Destination: &cfg.Service.Name, - }, - { - EnvVars: []string{"OCS_HTTP_ROOT"}, - Destination: &cfg.HTTP.Root, - }, - { - EnvVars: []string{"OCIS_JWT_SECRET", "OCS_JWT_SECRET"}, - Destination: &cfg.TokenManager.JWTSecret, - }, - { - EnvVars: []string{"OCS_ACCOUNT_BACKEND_TYPE"}, - Destination: &cfg.AccountBackend, - }, - { - EnvVars: []string{"REVA_GATEWAY"}, - Destination: &cfg.Reva.Address, - }, - { - EnvVars: []string{"OCIS_MACHINE_AUTH_API_KEY", "OCS_MACHINE_AUTH_API_KEY"}, - Destination: &cfg.MachineAuthAPIKey, - }, - { - EnvVars: []string{"OCIS_URL", "OCS_IDM_ADDRESS"}, - Destination: &cfg.IdentityManagement.Address, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER", "OCS_STORAGE_USERS_DRIVER"}, - Destination: &cfg.StorageUsersDriver, - }, - } -} diff --git a/ocs/pkg/config/parser/parse.go b/ocs/pkg/config/parser/parse.go new file mode 100644 index 000000000..9a50e535b --- /dev/null +++ b/ocs/pkg/config/parser/parse.go @@ -0,0 +1,46 @@ +package parser + +import ( + "errors" + "strings" + + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocs/pkg/config" + + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" +) + +// ParseConfig loads accounts configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + // 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.Commons == nil { + cfg.Log = &config.Log{} + } + + // 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 + } + } + + // sanitize config + if cfg.HTTP.Root != "/" { + cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") + } + + return nil +} diff --git a/ocs/pkg/config/reva.go b/ocs/pkg/config/reva.go new file mode 100644 index 000000000..31f48fbb6 --- /dev/null +++ b/ocs/pkg/config/reva.go @@ -0,0 +1,11 @@ +package config + +// Reva defines all available REVA configuration. +type Reva struct { + Address string `ocisConfig:"address" env:"REVA_GATEWAY"` +} + +// TokenManager is the config for using the reva token manager +type TokenManager struct { + JWTSecret string `ocisConfig:"jwt_secret" env:"OCIS_JWT_SECRET;OCS_JWT_SECRET"` +} diff --git a/ocs/pkg/config/service.go b/ocs/pkg/config/service.go new file mode 100644 index 000000000..f98aa3d27 --- /dev/null +++ b/ocs/pkg/config/service.go @@ -0,0 +1,6 @@ +package config + +// Service defines the available service configuration. +type Service struct { + Name string +} diff --git a/ocs/pkg/config/tracing.go b/ocs/pkg/config/tracing.go new file mode 100644 index 000000000..310462e90 --- /dev/null +++ b/ocs/pkg/config/tracing.go @@ -0,0 +1,9 @@ +package config + +// Tracing defines the available tracing configuration. +type Tracing struct { + Enabled bool `ocisConfig:"enabled" env:"OCIS_TRACING_ENABLED;OCS_TRACING_ENABLED"` + Type string `ocisConfig:"type" env:"OCIS_TRACING_TYPE;OCS_TRACING_TYPE"` + Endpoint string `ocisConfig:"endpoint" env:"OCIS_TRACING_ENDPOINT;OCS_TRACING_ENDPOINT"` + Collector string `ocisConfig:"collector" env:"OCIS_TRACING_COLLECTOR;OCS_TRACING_COLLECTOR"` +} diff --git a/ocs/pkg/logging/logging.go b/ocs/pkg/logging/logging.go new file mode 100644 index 000000000..80350b0d0 --- /dev/null +++ b/ocs/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocs/pkg/config" +) + +// 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/ocs/pkg/server/debug/server.go b/ocs/pkg/server/debug/server.go index 7c704840e..7a7b69d80 100644 --- a/ocs/pkg/server/debug/server.go +++ b/ocs/pkg/server/debug/server.go @@ -5,6 +5,7 @@ import ( "net/http" "github.com/owncloud/ocis/ocis-pkg/service/debug" + "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/ocs/pkg/config" ) @@ -15,7 +16,7 @@ func Server(opts ...Option) (*http.Server, error) { return debug.NewService( debug.Logger(options.Logger), debug.Name(options.Config.Service.Name), - debug.Version(options.Config.Service.Version), + debug.Version(version.String), debug.Address(options.Config.Debug.Addr), debug.Token(options.Config.Debug.Token), debug.Pprof(options.Config.Debug.Pprof), @@ -35,9 +36,11 @@ func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) - // TODO(tboerger): check if services are up and running + // TODO: check if services are up and running - if _, err := io.WriteString(w, http.StatusText(http.StatusOK)); err != nil { + _, 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) } } @@ -49,9 +52,11 @@ func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) - // TODO(tboerger): check if services are up and running + // TODO: check if services are up and running - if _, err := io.WriteString(w, http.StatusText(http.StatusOK)); err != nil { + _, 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/ocs/pkg/server/http/server.go b/ocs/pkg/server/http/server.go index 4df5fbbdd..06d380184 100644 --- a/ocs/pkg/server/http/server.go +++ b/ocs/pkg/server/http/server.go @@ -5,6 +5,7 @@ import ( "github.com/owncloud/ocis/ocis-pkg/cors" "github.com/owncloud/ocis/ocis-pkg/middleware" "github.com/owncloud/ocis/ocis-pkg/service/http" + "github.com/owncloud/ocis/ocis-pkg/version" ocsmw "github.com/owncloud/ocis/ocs/pkg/middleware" svc "github.com/owncloud/ocis/ocs/pkg/service/v0" "go-micro.dev/v4" @@ -17,8 +18,8 @@ func Server(opts ...Option) (http.Service, error) { service := http.NewService( http.Logger(options.Logger), http.Name(options.Config.Service.Name), - http.Version(options.Config.Service.Version), - http.Namespace(options.Config.Service.Namespace), + http.Version(version.String), + http.Namespace(options.Config.HTTP.Namespace), http.Address(options.Config.HTTP.Addr), http.Context(options.Context), http.Flags(options.Flags...), @@ -39,7 +40,10 @@ func Server(opts ...Option) (http.Service, error) { cors.AllowCredentials(options.Config.HTTP.CORS.AllowCredentials), ), middleware.Secure, - middleware.Version(options.Config.Service.Name, options.Config.Service.Version), + middleware.Version( + options.Config.Service.Name, + version.String, + ), middleware.Logger(options.Logger), ocsmw.LogTrace, ), diff --git a/ocs/pkg/server/http/svc_test.go b/ocs/pkg/server/http/svc_test.go index 0e432054b..faffe9e46 100644 --- a/ocs/pkg/server/http/svc_test.go +++ b/ocs/pkg/server/http/svc_test.go @@ -14,8 +14,6 @@ import ( "strings" "testing" - "github.com/owncloud/ocis/ocis-pkg/shared" - user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/pkg/auth/scope" @@ -23,10 +21,10 @@ import ( "github.com/cs3org/reva/pkg/token/manager/jwt" "github.com/golang/protobuf/ptypes/empty" accountsCfg "github.com/owncloud/ocis/accounts/pkg/config" + accountsLogging "github.com/owncloud/ocis/accounts/pkg/logging" accountsProto "github.com/owncloud/ocis/accounts/pkg/proto/v0" accountsSvc "github.com/owncloud/ocis/accounts/pkg/service/v0" ocisLog "github.com/owncloud/ocis/ocis-pkg/log" - oclog "github.com/owncloud/ocis/ocis-pkg/log" "github.com/owncloud/ocis/ocis-pkg/service/grpc" "github.com/owncloud/ocis/ocs/pkg/config" svc "github.com/owncloud/ocis/ocs/pkg/service/v0" @@ -542,16 +540,14 @@ func init() { ) c := &accountsCfg.Config{ - Server: accountsCfg.Server{ - DemoUsersAndGroups: true, - }, + DemoUsersAndGroups: true, Repo: accountsCfg.Repo{ Backend: "disk", Disk: accountsCfg.Disk{ Path: dataPath, }, }, - Log: &shared.Log{ + Log: &accountsCfg.Log{ Level: "info", Pretty: true, Color: true, @@ -562,7 +558,7 @@ func init() { var err error if hdlr, err = accountsSvc.New( - accountsSvc.Logger(oclog.LoggerFromConfig("accounts", *c.Log)), + accountsSvc.Logger(accountsLogging.Configure("accounts", c.Log)), accountsSvc.Config(c), accountsSvc.RoleService(buildRoleServiceMock()), ); err != nil { @@ -698,7 +694,7 @@ func getService() svc.Service { TokenManager: config.TokenManager{ JWTSecret: jwtSecret, }, - Log: &shared.Log{ + Log: &config.Log{ Level: "debug", }, } diff --git a/ocs/pkg/tracing/tracing.go b/ocs/pkg/tracing/tracing.go index d79ef622f..bd4873036 100644 --- a/ocs/pkg/tracing/tracing.go +++ b/ocs/pkg/tracing/tracing.go @@ -14,7 +14,7 @@ var ( func Configure(cfg *config.Config) error { var err error if cfg.Tracing.Enabled { - if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, "ocs", cfg.Tracing.Type); err != nil { + if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { return err } } diff --git a/proxy/pkg/command/health.go b/proxy/pkg/command/health.go index adb3f0a77..ff004eae4 100644 --- a/proxy/pkg/command/health.go +++ b/proxy/pkg/command/health.go @@ -5,17 +5,22 @@ import ( "net/http" "github.com/owncloud/ocis/proxy/pkg/config" + "github.com/owncloud/ocis/proxy/pkg/config/parser" + "github.com/owncloud/ocis/proxy/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", - //Flags: flagset.HealthWithConfig(cfg), + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + return parser.ParseConfig(cfg) + }, Action: func(c *cli.Context) error { - logger := NewLogger(cfg) + logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( fmt.Sprintf( @@ -32,7 +37,7 @@ func Health(cfg *config.Config) *cli.Command { defer resp.Body.Close() - if resp.StatusCode != 200 { + if resp.StatusCode != http.StatusOK { logger.Fatal(). Int("code", resp.StatusCode). Msg("Health seems to be in bad state") diff --git a/proxy/pkg/command/root.go b/proxy/pkg/command/root.go index 13ea25e99..cfc2afa62 100644 --- a/proxy/pkg/command/root.go +++ b/proxy/pkg/command/root.go @@ -4,83 +4,43 @@ import ( "context" "os" + "github.com/owncloud/ocis/ocis-pkg/clihelper" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/shared" - "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/proxy/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-proxy command. func Execute(cfg *config.Config) error { - app := &cli.App{ + app := clihelper.DefaultApp(&cli.App{ Name: "ocis-proxy", - Version: version.String, Usage: "proxy for oCIS", - Compiled: version.Compiled(), - Authors: []*cli.Author{ - { - Name: "ownCloud GmbH", - Email: "support@owncloud.com", - }, - }, - Before: func(c *cli.Context) error { - cfg.Service.Version = version.String - return nil - }, - Commands: []*cli.Command{ - Server(cfg), - Health(cfg), - PrintVersion(cfg), - }, - } + Commands: GetCommands(cfg), + }) cli.HelpFlag = &cli.BoolFlag{ Name: "help,h", Usage: "Show the help", } - cli.VersionFlag = &cli.BoolFlag{ - Name: "version,v", - Usage: "Print the version", - } - return app.Run(os.Args) } -// ParseConfig loads proxy configuration. Loading will first attempt to parse config files in the expected locations -// and then parses environment variables. In the context of oCIS env variables will always overwrite values set -// in a config file. -// If this extension is run as a subcommand (i.e: ocis proxy) then there are 2 levels of config parsing: -// 1. ocis.yaml (if any) -// 2. proxy.yaml (if any) -// 3. environment variables. -func ParseConfig(c *cli.Context, cfg *config.Config) error { - conf, err := ociscfg.BindSourcesToStructs("proxy", cfg) - if err != nil { - return err - } - - // 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 = &shared.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.Commons == nil { - cfg.Log = &shared.Log{} - } - - conf.LoadOSEnv(config.GetEnv(cfg), false) - - bindings := config.StructMappings(cfg) - return ociscfg.BindEnv(conf, bindings) -} - // SutureService allows for the proxy command to be embedded and supervised by a suture supervisor tree. type SutureService struct { cfg *config.Config @@ -102,14 +62,3 @@ func (s SutureService) Serve(ctx context.Context) error { return nil } - -// NewLogger initializes a service-specific logger instance. -func NewLogger(cfg *config.Config) log.Logger { - return log.NewLogger( - log.Name("proxy"), - log.Level(cfg.Log.Level), - log.Pretty(cfg.Log.Pretty), - log.Color(cfg.Log.Color), - log.File(cfg.Log.File), - ) -} diff --git a/proxy/pkg/command/server.go b/proxy/pkg/command/server.go index ed8e2d9c0..7f1767ae2 100644 --- a/proxy/pkg/command/server.go +++ b/proxy/pkg/command/server.go @@ -5,7 +5,6 @@ import ( "crypto/tls" "fmt" "net/http" - "strings" "time" "github.com/coreos/go-oidc/v3/oidc" @@ -14,12 +13,14 @@ import ( "github.com/justinas/alice" "github.com/oklog/run" acc "github.com/owncloud/ocis/accounts/pkg/proto/v0" - "github.com/owncloud/ocis/ocis-pkg/conversions" "github.com/owncloud/ocis/ocis-pkg/log" pkgmiddleware "github.com/owncloud/ocis/ocis-pkg/middleware" "github.com/owncloud/ocis/ocis-pkg/service/grpc" + "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/proxy/pkg/config" + "github.com/owncloud/ocis/proxy/pkg/config/parser" "github.com/owncloud/ocis/proxy/pkg/cs3" + "github.com/owncloud/ocis/proxy/pkg/logging" "github.com/owncloud/ocis/proxy/pkg/metrics" "github.com/owncloud/ocis/proxy/pkg/middleware" "github.com/owncloud/ocis/proxy/pkg/proxy" @@ -36,35 +37,16 @@ import ( // Server is the entrypoint for the server command. func Server(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "server", - Usage: "Start integrated server", - Before: func(ctx *cli.Context) error { - if err := ParseConfig(ctx, cfg); err != nil { - return err - } - - if cfg.Policies == nil { - cfg.Policies = config.DefaultPolicies() - } - - if cfg.HTTP.Root != "/" { - cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") - } - - if len(ctx.StringSlice("presignedurl-allow-method")) > 0 { - cfg.PreSignedURL.AllowedHTTPMethods = ctx.StringSlice("presignedurl-allow-method") - } - - if err := loadUserAgent(ctx, cfg); err != nil { - return err - } - - return nil + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + return parser.ParseConfig(cfg) }, Action: func(c *cli.Context) error { - logger := NewLogger(cfg) - - if err := tracing.Configure(cfg); err != nil { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg) + if err != nil { return err } @@ -82,7 +64,7 @@ func Server(cfg *config.Config) *cli.Command { defer cancel() - m.BuildInfo.WithLabelValues(cfg.Service.Version).Set(1) + m.BuildInfo.WithLabelValues(version.String).Set(1) rp := proxy.NewMultiHostReverseProxy( proxy.Logger(logger), @@ -217,7 +199,7 @@ func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config) middleware.OIDCIss(cfg.OIDC.Issuer), middleware.UserOIDCClaim(cfg.UserOIDCClaim), middleware.UserCS3Claim(cfg.UserCS3Claim), - middleware.CredentialsByUserAgent(cfg.Reva.Middleware.Auth.CredentialsByUserAgent), + middleware.CredentialsByUserAgent(cfg.AuthMiddleware.CredentialsByUserAgent), ), middleware.SignedURLAuth( middleware.Logger(logger), @@ -252,28 +234,3 @@ func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config) ), ) } - -// loadUserAgent reads the proxy-user-agent-lock-in, since it is a string flag, and attempts to construct a map of -// "user-agent":"challenge" locks in for Reva. -// Modifies cfg. Spaces don't need to be trimmed as urfavecli takes care of it. User agents with spaces are valid. i.e: -// Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:83.0) Gecko/20100101 Firefox/83.0 -// This function works by relying in our format of specifying [user-agent:challenge] and the fact that the user agent -// might contain ":" (colon), so the original string is reversed, split in two parts, by the time it is split we -// have the indexes reversed and the tuple is in the format of [challenge:user-agent], then the same process is applied -// in reverse for each individual part -func loadUserAgent(c *cli.Context, cfg *config.Config) error { - cfg.Reva.Middleware.Auth.CredentialsByUserAgent = make(map[string]string) - locks := c.StringSlice("proxy-user-agent-lock-in") - - for _, v := range locks { - vv := conversions.Reverse(v) - parts := strings.SplitN(vv, ":", 2) - if len(parts) != 2 { - return fmt.Errorf("unexpected config value for user-agent lock-in: %v, expected format is user-agent:challenge", v) - } - - cfg.Reva.Middleware.Auth.CredentialsByUserAgent[conversions.Reverse(parts[1])] = conversions.Reverse(parts[0]) - } - - return nil -} diff --git a/proxy/pkg/command/version.go b/proxy/pkg/command/version.go index 469abc6ef..e8b907dca 100644 --- a/proxy/pkg/command/version.go +++ b/proxy/pkg/command/version.go @@ -5,30 +5,33 @@ import ( "os" "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" tw "github.com/olekukonko/tablewriter" "github.com/owncloud/ocis/proxy/pkg/config" "github.com/urfave/cli/v2" ) -// PrintVersion prints the service versions of all running instances. -func PrintVersion(cfg *config.Config) *cli.Command { +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "version", - Usage: "Print the versions of the running instances", - Before: func(c *cli.Context) error { - return ParseConfig(c, cfg) - }, + Name: "version", + Usage: "Print the version of this binary and the running extension instances", + Category: "Version", 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.Service.Namespace + "." + cfg.Service.Name) + services, err := reg.GetService(cfg.HTTP.Namespace + "." + cfg.Service.Name) if err != nil { - fmt.Println(fmt.Errorf("could not get proxy services from the registry: %v", err)) + 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 proxy service found.") + fmt.Println("No running " + cfg.Service.Name + " service found.") return nil } diff --git a/proxy/pkg/config/config.go b/proxy/pkg/config/config.go index bd21ee1e3..c525421e8 100644 --- a/proxy/pkg/config/config.go +++ b/proxy/pkg/config/config.go @@ -2,51 +2,39 @@ package config import ( "context" - "path" - "github.com/owncloud/ocis/ocis-pkg/config/defaults" "github.com/owncloud/ocis/ocis-pkg/shared" ) -// Log defines the available logging configuration. -type Log struct { - Level string `ocisConfig:"level"` - Pretty bool `ocisConfig:"pretty"` - Color bool `ocisConfig:"color"` - File string `ocisConfig:"file"` -} +// Config combines all available configuration parts. +type Config struct { + *shared.Commons -// Debug defines the available debug configuration. -type Debug struct { - Addr string `ocisConfig:"addr"` - Token string `ocisConfig:"token"` - Pprof bool `ocisConfig:"pprof"` - Zpages bool `ocisConfig:"zpages"` -} + Service Service -// HTTP defines the available http configuration. -type HTTP struct { - Addr string `ocisConfig:"addr"` - Root string `ocisConfig:"root"` - TLSCert string `ocisConfig:"tls_cert"` - TLSKey string `ocisConfig:"tls_key"` - TLS bool `ocisConfig:"tls"` -} + Tracing Tracing `ocisConfig:"tracing"` + Log *Log `ocisConfig:"log"` + Debug Debug `ocisConfig:"debug"` -// Service defines the available service configuration. -type Service struct { - Name string `ocisConfig:"name"` - Namespace string `ocisConfig:"namespace"` - Version string `ocisConfig:"version"` -} + HTTP HTTP `ocisConfig:"http"` -// Tracing defines the available tracing configuration. -type Tracing struct { - Enabled bool `ocisConfig:"enabled"` - Type string `ocisConfig:"type"` - Endpoint string `ocisConfig:"endpoint"` - Collector string `ocisConfig:"collector"` - Service string `ocisConfig:"service"` + Reva Reva `ocisConfig:"reva"` + + Policies []Policy `ocisConfig:"policies"` + OIDC OIDC `ocisConfig:"oidc"` + TokenManager TokenManager `ocisConfig:"token_manager"` + PolicySelector *PolicySelector `ocisConfig:"policy_selector"` + PreSignedURL PreSignedURL `ocisConfig:"pre_signed_url"` + AccountBackend string `ocisConfig:"account_backend" env:"PROXY_ACCOUNT_BACKEND_TYPE"` + UserOIDCClaim string `ocisConfig:"user_oidc_claim" env:"PROXY_USER_OIDC_CLAIM"` + UserCS3Claim string `ocisConfig:"user_cs3_claim" env:"PROXY_USER_CS3_CLAIM"` + MachineAuthAPIKey string `ocisConfig:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY;PROXY_MACHINE_AUTH_API_KEY"` + AutoprovisionAccounts bool `ocisConfig:"auto_provision_accounts" env:"PROXY_AUTOPROVISION_ACCOUNTS"` + EnableBasicAuth bool `ocisConfig:"enable_basic_auth" env:"PROXY_ENABLE_BASIC_AUTH"` + InsecureBackends bool `ocisConfig:"insecure_backends" env:"PROXY_INSECURE_BACKENDS"` + AuthMiddleware AuthMiddleware `ocisConfig:"auth_middleware"` + + Context context.Context } // Policy enables us to use multiple directors. @@ -82,61 +70,23 @@ var ( RouteTypes = []RouteType{QueryRoute, RegexRoute, PrefixRoute} ) -// Reva defines all available REVA configuration. -type Reva struct { - Address string `ocisConfig:"address"` - Middleware Middleware `ocisConfig:"middleware"` -} - -// Middleware configures proxy middlewares. -type Middleware struct { - Auth Auth `ocisConfig:"middleware"` -} - -// Auth configures proxy http auth middleware. -type Auth struct { - CredentialsByUserAgent map[string]string `ocisConfig:""` -} - -// Cache is a TTL cache configuration. -type Cache struct { - Size int `ocisConfig:"size"` - TTL int `ocisConfig:"ttl"` -} - -// Config combines all available configuration parts. -type Config struct { - *shared.Commons - - Log *shared.Log `ocisConfig:"log"` - Debug Debug `ocisConfig:"debug"` - HTTP HTTP `ocisConfig:"http"` - Service Service `ocisConfig:"service"` - Tracing Tracing `ocisConfig:"tracing"` - Policies []Policy `ocisConfig:"policies"` - OIDC OIDC `ocisConfig:"oidc"` - TokenManager TokenManager `ocisConfig:"token_manager"` - PolicySelector *PolicySelector `ocisConfig:"policy_selector"` - Reva Reva `ocisConfig:"reva"` - PreSignedURL PreSignedURL `ocisConfig:"pre_signed_url"` - AccountBackend string `ocisConfig:"account_backend"` - UserOIDCClaim string `ocisConfig:"user_oidc_claim"` - UserCS3Claim string `ocisConfig:"user_cs3_claim"` - MachineAuthAPIKey string `ocisConfig:"machine_auth_api_key"` - AutoprovisionAccounts bool `ocisConfig:"auto_provision_accounts"` - EnableBasicAuth bool `ocisConfig:"enable_basic_auth"` - InsecureBackends bool `ocisConfig:"insecure_backends"` - - Context context.Context - Supervised bool +// AuthMiddleware configures the proxy http auth middleware. +type AuthMiddleware struct { + CredentialsByUserAgent map[string]string `ocisConfig:"credentials_by_user_agent"` } // OIDC is the config for the OpenID-Connect middleware. If set the proxy will try to authenticate every request // with the configured oidc-provider type OIDC struct { - Issuer string `ocisConfig:"issuer"` - Insecure bool `ocisConfig:"insecure"` - UserinfoCache Cache `ocisConfig:"user_info_cache"` + Issuer string `ocisConfig:"issuer" env:"OCIS_URL;PROXY_OIDC_ISSUER"` + Insecure bool `ocisConfig:"insecure" env:"OCIS_INSECURE;PROXY_OIDC_INSECURE"` + UserinfoCache UserinfoCache `ocisConfig:"user_info_cache"` +} + +// UserinfoCache is a TTL cache configuration. +type UserinfoCache struct { + Size int `ocisConfig:"size" env:"PROXY_OIDC_USERINFO_CACHE_SIZE"` + TTL int `ocisConfig:"ttl" env:"PROXY_OIDC_USERINFO_CACHE_TTL"` } // PolicySelector is the toplevel-configuration for different selectors @@ -154,13 +104,13 @@ type StaticSelectorConf struct { // TokenManager is the config for using the reva token manager type TokenManager struct { - JWTSecret string `ocisConfig:"jwt_secret"` + JWTSecret string `ocisConfig:"jwt_secret" env:"OCIS_JWT_SECRET;PROXY_JWT_SECRET"` } // PreSignedURL is the config for the presigned url middleware type PreSignedURL struct { AllowedHTTPMethods []string `ocisConfig:"allowed_http_methods"` - Enabled bool `ocisConfig:"enabled"` + Enabled bool `ocisConfig:"enabled" env:"PROXY_ENABLE_PRESIGNEDURLS"` } // MigrationSelectorConf is the config for the migration-selector @@ -191,224 +141,3 @@ type RegexRuleConf struct { Match string `ocisConfig:"match"` Policy string `ocisConfig:"policy"` } - -// New initializes a new configuration -func New() *Config { - return &Config{ - HTTP: HTTP{}, - } -} - -// DefaultConfig provides with a working local configuration for a proxy service. -func DefaultConfig() *Config { - return &Config{ - Debug: Debug{ - Addr: "0.0.0.0:9205", - Token: "", - }, - HTTP: HTTP{ - Addr: "0.0.0.0:9200", - Root: "/", - TLSCert: path.Join(defaults.BaseDataPath(), "proxy", "server.crt"), - TLSKey: path.Join(defaults.BaseDataPath(), "proxy", "server.key"), - TLS: true, - }, - Service: Service{ - Name: "proxy", - Namespace: "com.owncloud.web", - }, - Tracing: Tracing{ - Type: "jaeger", - Endpoint: "", - Collector: "", - Service: "proxy", - }, - OIDC: OIDC{ - Issuer: "https://localhost:9200", - Insecure: true, - //Insecure: true, - UserinfoCache: Cache{ - Size: 1024, - TTL: 10, - }, - }, - TokenManager: TokenManager{ - JWTSecret: "Pive-Fumkiu4", - }, - PolicySelector: nil, - Reva: Reva{ - Address: "127.0.0.1:9142", - }, - PreSignedURL: PreSignedURL{ - AllowedHTTPMethods: []string{"GET"}, - Enabled: true, - }, - AccountBackend: "accounts", - UserOIDCClaim: "email", - UserCS3Claim: "mail", - MachineAuthAPIKey: "change-me-please", - //AutoprovisionAccounts: false, - //EnableBasicAuth: false, - //InsecureBackends: false, - Context: nil, - //Policies: defaultPolicies(), - } -} - -func DefaultPolicies() []Policy { - return []Policy{ - { - Name: "ocis", - Routes: []Route{ - { - Endpoint: "/", - Backend: "http://localhost:9100", - }, - { - Endpoint: "/.well-known/", - Backend: "http://localhost:9130", - }, - { - Endpoint: "/konnect/", - Backend: "http://localhost:9130", - }, - { - Endpoint: "/signin/", - Backend: "http://localhost:9130", - }, - { - Endpoint: "/archiver", - Backend: "http://localhost:9140", - }, - { - Type: RegexRoute, - Endpoint: "/ocs/v[12].php/cloud/(users?|groups)", // we have `user`, `users` and `groups` in ocis-ocs - Backend: "http://localhost:9110", - }, - { - Endpoint: "/ocs/", - Backend: "http://localhost:9140", - }, - { - Type: QueryRoute, - Endpoint: "/remote.php/?preview=1", - Backend: "http://localhost:9115", - }, - { - Endpoint: "/remote.php/", - Backend: "http://localhost:9140", - }, - { - Endpoint: "/dav/", - Backend: "http://localhost:9140", - }, - { - Endpoint: "/webdav/", - Backend: "http://localhost:9140", - }, - { - Endpoint: "/status.php", - Backend: "http://localhost:9140", - }, - { - Endpoint: "/index.php/", - Backend: "http://localhost:9140", - }, - { - Endpoint: "/data", - Backend: "http://localhost:9140", - }, - { - Endpoint: "/app/", - Backend: "http://localhost:9140", - }, - { - Endpoint: "/graph/", - Backend: "http://localhost:9120", - }, - { - Endpoint: "/graph-explorer", - Backend: "http://localhost:9135", - }, - // if we were using the go micro api gateway we could look up the endpoint in the registry dynamically - { - Endpoint: "/api/v0/accounts", - Backend: "http://localhost:9181", - }, - // TODO the lookup needs a better mechanism - { - Endpoint: "/accounts.js", - Backend: "http://localhost:9181", - }, - { - Endpoint: "/api/v0/settings", - Backend: "http://localhost:9190", - }, - { - Endpoint: "/settings.js", - Backend: "http://localhost:9190", - }, - }, - }, - { - Name: "oc10", - Routes: []Route{ - { - Endpoint: "/", - Backend: "http://localhost:9100", - }, - { - Endpoint: "/.well-known/", - Backend: "http://localhost:9130", - }, - { - Endpoint: "/konnect/", - Backend: "http://localhost:9130", - }, - { - Endpoint: "/signin/", - Backend: "http://localhost:9130", - }, - { - Endpoint: "/archiver", - Backend: "http://localhost:9140", - }, - { - Endpoint: "/ocs/", - Backend: "https://demo.owncloud.com", - ApacheVHost: true, - }, - { - Endpoint: "/remote.php/", - Backend: "https://demo.owncloud.com", - ApacheVHost: true, - }, - { - Endpoint: "/dav/", - Backend: "https://demo.owncloud.com", - ApacheVHost: true, - }, - { - Endpoint: "/webdav/", - Backend: "https://demo.owncloud.com", - ApacheVHost: true, - }, - { - Endpoint: "/status.php", - Backend: "https://demo.owncloud.com", - ApacheVHost: true, - }, - { - Endpoint: "/index.php/", - Backend: "https://demo.owncloud.com", - ApacheVHost: true, - }, - { - Endpoint: "/data", - Backend: "https://demo.owncloud.com", - ApacheVHost: true, - }, - }, - }, - } -} diff --git a/proxy/pkg/config/debug.go b/proxy/pkg/config/debug.go new file mode 100644 index 000000000..1c450cc4d --- /dev/null +++ b/proxy/pkg/config/debug.go @@ -0,0 +1,9 @@ +package config + +// Debug defines the available debug configuration. +type Debug struct { + Addr string `ocisConfig:"addr" env:"PROXY_DEBUG_ADDR"` + Token string `ocisConfig:"token" env:"PROXY_DEBUG_TOKEN"` + Pprof bool `ocisConfig:"pprof" env:"PROXY_DEBUG_PPROF"` + Zpages bool `ocisConfig:"zpages" env:"PROXY_DEBUG_ZPAGES"` +} diff --git a/proxy/pkg/config/defaultconfig.go b/proxy/pkg/config/defaultconfig.go new file mode 100644 index 000000000..136cf28b9 --- /dev/null +++ b/proxy/pkg/config/defaultconfig.go @@ -0,0 +1,220 @@ +package config + +import ( + "path" + + "github.com/owncloud/ocis/ocis-pkg/config/defaults" +) + +func DefaultConfig() *Config { + return &Config{ + Debug: Debug{ + Addr: "127.0.0.1:9205", + Token: "", + }, + HTTP: HTTP{ + Addr: "0.0.0.0:9200", + Root: "/", + Namespace: "com.owncloud.web", + TLSCert: path.Join(defaults.BaseDataPath(), "proxy", "server.crt"), + TLSKey: path.Join(defaults.BaseDataPath(), "proxy", "server.key"), + TLS: true, + }, + Service: Service{ + Name: "proxy", + }, + Tracing: Tracing{ + Enabled: false, + Type: "jaeger", + Endpoint: "", + Collector: "", + }, + OIDC: OIDC{ + Issuer: "https://localhost:9200", + Insecure: true, + //Insecure: true, + UserinfoCache: UserinfoCache{ + Size: 1024, + TTL: 10, + }, + }, + TokenManager: TokenManager{ + JWTSecret: "Pive-Fumkiu4", + }, + PolicySelector: nil, + Reva: Reva{ + Address: "127.0.0.1:9142", + }, + PreSignedURL: PreSignedURL{ + AllowedHTTPMethods: []string{"GET"}, + Enabled: true, + }, + AccountBackend: "accounts", + UserOIDCClaim: "email", + UserCS3Claim: "mail", + MachineAuthAPIKey: "change-me-please", + AutoprovisionAccounts: false, + EnableBasicAuth: false, + InsecureBackends: false, + // TODO: enable + //Policies: defaultPolicies(), + } +} + +func DefaultPolicies() []Policy { + return []Policy{ + { + Name: "ocis", + Routes: []Route{ + { + Endpoint: "/", + Backend: "http://localhost:9100", + }, + { + Endpoint: "/.well-known/", + Backend: "http://localhost:9130", + }, + { + Endpoint: "/konnect/", + Backend: "http://localhost:9130", + }, + { + Endpoint: "/signin/", + Backend: "http://localhost:9130", + }, + { + Endpoint: "/archiver", + Backend: "http://localhost:9140", + }, + { + Type: RegexRoute, + Endpoint: "/ocs/v[12].php/cloud/(users?|groups)", // we have `user`, `users` and `groups` in ocis-ocs + Backend: "http://localhost:9110", + }, + { + Endpoint: "/ocs/", + Backend: "http://localhost:9140", + }, + { + Type: QueryRoute, + Endpoint: "/remote.php/?preview=1", + Backend: "http://localhost:9115", + }, + { + Endpoint: "/remote.php/", + Backend: "http://localhost:9140", + }, + { + Endpoint: "/dav/", + Backend: "http://localhost:9140", + }, + { + Endpoint: "/webdav/", + Backend: "http://localhost:9140", + }, + { + Endpoint: "/status.php", + Backend: "http://localhost:9140", + }, + { + Endpoint: "/index.php/", + Backend: "http://localhost:9140", + }, + { + Endpoint: "/data", + Backend: "http://localhost:9140", + }, + { + Endpoint: "/app/", + Backend: "http://localhost:9140", + }, + { + Endpoint: "/graph/", + Backend: "http://localhost:9120", + }, + { + Endpoint: "/graph-explorer", + Backend: "http://localhost:9135", + }, + // if we were using the go micro api gateway we could look up the endpoint in the registry dynamically + { + Endpoint: "/api/v0/accounts", + Backend: "http://localhost:9181", + }, + // TODO the lookup needs a better mechanism + { + Endpoint: "/accounts.js", + Backend: "http://localhost:9181", + }, + { + Endpoint: "/api/v0/settings", + Backend: "http://localhost:9190", + }, + { + Endpoint: "/settings.js", + Backend: "http://localhost:9190", + }, + }, + }, + { + Name: "oc10", + Routes: []Route{ + { + Endpoint: "/", + Backend: "http://localhost:9100", + }, + { + Endpoint: "/.well-known/", + Backend: "http://localhost:9130", + }, + { + Endpoint: "/konnect/", + Backend: "http://localhost:9130", + }, + { + Endpoint: "/signin/", + Backend: "http://localhost:9130", + }, + { + Endpoint: "/archiver", + Backend: "http://localhost:9140", + }, + { + Endpoint: "/ocs/", + Backend: "https://demo.owncloud.com", + ApacheVHost: true, + }, + { + Endpoint: "/remote.php/", + Backend: "https://demo.owncloud.com", + ApacheVHost: true, + }, + { + Endpoint: "/dav/", + Backend: "https://demo.owncloud.com", + ApacheVHost: true, + }, + { + Endpoint: "/webdav/", + Backend: "https://demo.owncloud.com", + ApacheVHost: true, + }, + { + Endpoint: "/status.php", + Backend: "https://demo.owncloud.com", + ApacheVHost: true, + }, + { + Endpoint: "/index.php/", + Backend: "https://demo.owncloud.com", + ApacheVHost: true, + }, + { + Endpoint: "/data", + Backend: "https://demo.owncloud.com", + ApacheVHost: true, + }, + }, + }, + } +} diff --git a/proxy/pkg/config/http.go b/proxy/pkg/config/http.go new file mode 100644 index 000000000..8ab553063 --- /dev/null +++ b/proxy/pkg/config/http.go @@ -0,0 +1,11 @@ +package config + +// HTTP defines the available http configuration. +type HTTP struct { + Addr string `ocisConfig:"addr" env:"PROXY_HTTP_ADDR"` + Root string `ocisConfig:"root" env:"PROXY_HTTP_ROOT"` + Namespace string + TLSCert string `ocisConfig:"tls_cert" env:"PROXY_TRANSPORT_TLS_CERT"` + TLSKey string `ocisConfig:"tls_key" env:"PROXY_TRANSPORT_TLS_KEY"` + TLS bool `ocisConfig:"tls" env:"PROXY_TLS"` +} diff --git a/proxy/pkg/config/log.go b/proxy/pkg/config/log.go new file mode 100644 index 000000000..54612fc20 --- /dev/null +++ b/proxy/pkg/config/log.go @@ -0,0 +1,9 @@ +package config + +// Log defines the available log configuration. +type Log struct { + Level string `mapstructure:"level" env:"OCIS_LOG_LEVEL;PROXY_LOG_LEVEL"` + Pretty bool `mapstructure:"pretty" env:"OCIS_LOG_PRETTY;PROXY_LOG_PRETTY"` + Color bool `mapstructure:"color" env:"OCIS_LOG_COLOR;PROXY_LOG_COLOR"` + File string `mapstructure:"file" env:"OCIS_LOG_FILE;PROXY_LOG_FILE"` +} diff --git a/proxy/pkg/config/mappings.go b/proxy/pkg/config/mappings.go deleted file mode 100644 index 582e391b4..000000000 --- a/proxy/pkg/config/mappings.go +++ /dev/null @@ -1,186 +0,0 @@ -package config - -import "github.com/owncloud/ocis/ocis-pkg/shared" - -// GetEnv fetches a list of known env variables for this extension. It is to be used by gookit, as it provides a list -// with all the environment variables an extension supports. -func GetEnv(cfg *Config) []string { - var r = make([]string, len(structMappings(cfg))) - for i := range structMappings(cfg) { - r = append(r, structMappings(cfg)[i].EnvVars...) - } - - return r -} - -// StructMappings binds a set of environment variables to a destination on cfg. Iterating over this set and editing the -// Destination value of a binding will alter the original value, as it is a pointer to its memory address. This lets -// us propagate changes easier. -func StructMappings(cfg *Config) []shared.EnvBinding { - return structMappings(cfg) -} - -func structMappings(cfg *Config) []shared.EnvBinding { - return []shared.EnvBinding{ - // Logging - { - EnvVars: []string{"OCIS_LOG_LEVEL", "PROXY_LOG_LEVEL"}, - Destination: &cfg.Log.Level, - }, - { - EnvVars: []string{"OCIS_LOG_COLOR", "PROXY_LOG_COLOR"}, - Destination: &cfg.Log.Color, - }, - { - EnvVars: []string{"OCIS_LOG_PRETTY", "PROXY_LOG_PRETTY"}, - Destination: &cfg.Log.Pretty, - }, - { - EnvVars: []string{"OCIS_LOG_FILE", "PROXY_LOG_FILE"}, - Destination: &cfg.Log.File, - }, - - // Basic auth - { - EnvVars: []string{"PROXY_ENABLE_BASIC_AUTH"}, - Destination: &cfg.EnableBasicAuth, - }, - - // Debug (health) - { - EnvVars: []string{"PROXY_DEBUG_ADDR"}, - Destination: &cfg.Debug.Addr, - }, - - // Tracing - { - EnvVars: []string{"OCIS_TRACING_ENABLED", "PROXY_TRACING_ENABLED"}, - Destination: &cfg.Tracing.Enabled, - }, - { - EnvVars: []string{"OCIS_TRACING_TYPE", "PROXY_TRACING_TYPE"}, - Destination: &cfg.Tracing.Type, - }, - { - EnvVars: []string{"OCIS_TRACING_ENDPOINT", "PROXY_TRACING_ENDPOINT"}, - Destination: &cfg.Tracing.Endpoint, - }, - { - EnvVars: []string{"OCIS_TRACING_COLLECTOR", "PROXY_TRACING_COLLECTOR"}, - Destination: &cfg.Tracing.Collector, - }, - { - EnvVars: []string{"PROXY_TRACING_SERVICE"}, - Destination: &cfg.Tracing.Service, - }, - - // Debug - { - EnvVars: []string{"PROXY_DEBUG_ADDR"}, - Destination: &cfg.Debug.Addr, - }, - { - EnvVars: []string{"PROXY_DEBUG_TOKEN"}, - Destination: &cfg.Debug.Token, - }, - { - EnvVars: []string{"PROXY_DEBUG_PPROF"}, - Destination: &cfg.Debug.Pprof, - }, - { - EnvVars: []string{"PROXY_DEBUG_ZPAGES"}, - Destination: &cfg.Debug.Zpages, - }, - - // HTTP - { - EnvVars: []string{"PROXY_HTTP_ADDR"}, - Destination: &cfg.HTTP.Addr, - }, - { - EnvVars: []string{"PROXY_HTTP_ROOT"}, - Destination: &cfg.HTTP.Root, - }, - - // Service - { - EnvVars: []string{"PROXY_SERVICE_NAMESPACE"}, - Destination: &cfg.Service.Namespace, - }, - { - EnvVars: []string{"PROXY_SERVICE_NAME"}, - Destination: &cfg.Service.Name, - }, - { - EnvVars: []string{"PROXY_TRANSPORT_TLS_CERT"}, - Destination: &cfg.HTTP.TLSCert, - }, - { - EnvVars: []string{"PROXY_TRANSPORT_TLS_KEY"}, - Destination: &cfg.HTTP.TLSKey, - }, - { - EnvVars: []string{"PROXY_TLS"}, - Destination: &cfg.HTTP.TLS, - }, - { - EnvVars: []string{"OCIS_JWT_SECRET", "PROXY_JWT_SECRET"}, - Destination: &cfg.TokenManager.JWTSecret, - }, - - { - EnvVars: []string{"REVA_GATEWAY"}, - Destination: &cfg.Reva.Address, - }, - { - EnvVars: []string{"PROXY_INSECURE_BACKENDS"}, - Destination: &cfg.InsecureBackends, - }, - { - EnvVars: []string{"OCIS_URL", "PROXY_OIDC_ISSUER"}, - Destination: &cfg.OIDC.Issuer, - }, - { - EnvVars: []string{"OCIS_INSECURE", "PROXY_OIDC_INSECURE"}, - Destination: &cfg.OIDC.Insecure, - }, - { - EnvVars: []string{"PROXY_OIDC_USERINFO_CACHE_TTL"}, - Destination: &cfg.OIDC.UserinfoCache.TTL, - }, - { - EnvVars: []string{"PROXY_OIDC_USERINFO_CACHE_SIZE"}, - Destination: &cfg.OIDC.UserinfoCache.Size, - }, - { - EnvVars: []string{"PROXY_AUTOPROVISION_ACCOUNTS"}, - Destination: &cfg.AutoprovisionAccounts, - }, - { - EnvVars: []string{"PROXY_USER_OIDC_CLAIM"}, - Destination: &cfg.UserOIDCClaim, - }, - { - EnvVars: []string{"PROXY_USER_CS3_CLAIM"}, - Destination: &cfg.UserCS3Claim, - }, - { - EnvVars: []string{"PROXY_ENABLE_PRESIGNEDURLS"}, - Destination: &cfg.PreSignedURL.Enabled, - }, - { - EnvVars: []string{"PROXY_ACCOUNT_BACKEND_TYPE"}, - Destination: &cfg.AccountBackend, - }, - { - EnvVars: []string{"OCIS_MACHINE_AUTH_API_KEY", "PROXY_MACHINE_AUTH_API_KEY"}, - Destination: &cfg.MachineAuthAPIKey, - }, - // there are 2 missing bindings: - // EnvVars: []string{"PROXY_MIDDLEWARE_AUTH_CREDENTIALS_BY_USER_AGENT"}, - // EnvVars: []string{"PRESIGNEDURL_ALLOWED_METHODS"}, - // since they both have no destination - // see https://github.com/owncloud/ocis/blob/52e5effa4fa05a1626d46f7d4cb574dde3a54593/proxy/pkg/flagset/flagset.go#L256-L261 - // and https://github.com/owncloud/ocis/blob/52e5effa4fa05a1626d46f7d4cb574dde3a54593/proxy/pkg/flagset/flagset.go#L295-L300 - } -} diff --git a/proxy/pkg/config/parser/parse.go b/proxy/pkg/config/parser/parse.go new file mode 100644 index 000000000..65921b31c --- /dev/null +++ b/proxy/pkg/config/parser/parse.go @@ -0,0 +1,51 @@ +package parser + +import ( + "errors" + "strings" + + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/proxy/pkg/config" + + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" +) + +// ParseConfig loads accounts configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + // 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.Commons == nil { + cfg.Log = &config.Log{} + } + + // 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 + } + } + + // sanitize config + + if cfg.Policies == nil { + cfg.Policies = config.DefaultPolicies() + } + + if cfg.HTTP.Root != "/" { + cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") + } + + return nil +} diff --git a/proxy/pkg/config/reva.go b/proxy/pkg/config/reva.go new file mode 100644 index 000000000..2b299a0f6 --- /dev/null +++ b/proxy/pkg/config/reva.go @@ -0,0 +1,6 @@ +package config + +// Reva defines all available REVA configuration. +type Reva struct { + Address string `ocisConfig:"address" env:"REVA_GATEWAY"` +} diff --git a/proxy/pkg/config/service.go b/proxy/pkg/config/service.go new file mode 100644 index 000000000..f98aa3d27 --- /dev/null +++ b/proxy/pkg/config/service.go @@ -0,0 +1,6 @@ +package config + +// Service defines the available service configuration. +type Service struct { + Name string +} diff --git a/proxy/pkg/config/tracing.go b/proxy/pkg/config/tracing.go new file mode 100644 index 000000000..79429ee5c --- /dev/null +++ b/proxy/pkg/config/tracing.go @@ -0,0 +1,9 @@ +package config + +// Tracing defines the available tracing configuration. +type Tracing struct { + Enabled bool `ocisConfig:"enabled" env:"OCIS_TRACING_ENABLED;PROXY_TRACING_ENABLED"` + Type string `ocisConfig:"type" env:"OCIS_TRACING_TYPE;PROXY_TRACING_TYPE"` + Endpoint string `ocisConfig:"endpoint" env:"OCIS_TRACING_ENDPOINT;PROXY_TRACING_ENDPOINT"` + Collector string `ocisConfig:"collector" env:"OCIS_TRACING_COLLECTOR;PROXY_TRACING_COLLECTOR"` +} diff --git a/proxy/pkg/cs3/client.go b/proxy/pkg/cs3/client.go index 68f52d2d7..bf8d19871 100644 --- a/proxy/pkg/cs3/client.go +++ b/proxy/pkg/cs3/client.go @@ -5,12 +5,13 @@ import ( proxytracing "github.com/owncloud/ocis/proxy/pkg/tracing" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" ) func newConn(endpoint string) (*grpc.ClientConn, error) { conn, err := grpc.Dial( endpoint, - grpc.WithInsecure(), + grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithUnaryInterceptor( otelgrpc.UnaryClientInterceptor( otelgrpc.WithTracerProvider( @@ -28,6 +29,7 @@ func newConn(endpoint string) (*grpc.ClientConn, error) { // GetGatewayServiceClient returns a new cs3 gateway client func GetGatewayServiceClient(endpoint string) (gateway.GatewayAPIClient, error) { + // TODO: check connection pooling conn, err := newConn(endpoint) if err != nil { return nil, err diff --git a/proxy/pkg/logging/logging.go b/proxy/pkg/logging/logging.go new file mode 100644 index 000000000..dfaeabd24 --- /dev/null +++ b/proxy/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/proxy/pkg/config" +) + +// 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/proxy/pkg/proxy/proxy_integration_test.go b/proxy/pkg/proxy/proxy_integration_test.go index 9809383a6..0137b4413 100644 --- a/proxy/pkg/proxy/proxy_integration_test.go +++ b/proxy/pkg/proxy/proxy_integration_test.go @@ -10,8 +10,6 @@ import ( "net/url" "testing" - "github.com/owncloud/ocis/ocis-pkg/shared" - "github.com/owncloud/ocis/proxy/pkg/config" ) @@ -215,7 +213,7 @@ func (tc *testCase) expectProxyTo(strURL string) testCase { func testConfig(policy []config.Policy) *config.Config { return &config.Config{ - Log: &shared.Log{}, + Log: &config.Log{}, Debug: config.Debug{}, HTTP: config.HTTP{}, Tracing: config.Tracing{}, diff --git a/proxy/pkg/server/debug/server.go b/proxy/pkg/server/debug/server.go index b4106f2a8..9158a697d 100644 --- a/proxy/pkg/server/debug/server.go +++ b/proxy/pkg/server/debug/server.go @@ -6,6 +6,7 @@ import ( "net/http" "github.com/owncloud/ocis/ocis-pkg/service/debug" + "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/proxy/pkg/config" ) @@ -16,7 +17,7 @@ func Server(opts ...Option) (*http.Server, error) { return debug.NewService( debug.Logger(options.Logger), debug.Name(options.Config.Service.Name), - debug.Version(options.Config.Service.Version), + debug.Version(version.String), debug.Address(options.Config.Debug.Addr), debug.Token(options.Config.Debug.Token), debug.Pprof(options.Config.Debug.Pprof), @@ -33,9 +34,11 @@ func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) - // TODO(tboerger): check if services are up and running + // TODO: check if services are up and running - if _, err := io.WriteString(w, http.StatusText(http.StatusOK)); err != nil { + _, 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) } } @@ -47,9 +50,11 @@ func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) - // TODO(tboerger): check if services are up and running + // TODO: check if services are up and running - if _, err := io.WriteString(w, http.StatusText(http.StatusOK)); err != nil { + _, 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/proxy/pkg/server/http/server.go b/proxy/pkg/server/http/server.go index a403f630d..14b377434 100644 --- a/proxy/pkg/server/http/server.go +++ b/proxy/pkg/server/http/server.go @@ -6,6 +6,7 @@ import ( pkgcrypto "github.com/owncloud/ocis/ocis-pkg/crypto" svc "github.com/owncloud/ocis/ocis-pkg/service/http" + "github.com/owncloud/ocis/ocis-pkg/version" "go-micro.dev/v4" ) @@ -43,11 +44,11 @@ func Server(opts ...Option) (svc.Service, error) { service := svc.NewService( svc.Name(options.Config.Service.Name), + svc.Version(version.String), svc.TLSConfig(tlsConfig), svc.Logger(options.Logger), - svc.Namespace(options.Config.Service.Namespace), - svc.Version(options.Config.Service.Version), svc.Address(options.Config.HTTP.Addr), + svc.Namespace(options.Config.HTTP.Namespace), svc.Context(options.Context), svc.Flags(options.Flags...), ) diff --git a/proxy/pkg/tracing/tracing.go b/proxy/pkg/tracing/tracing.go index e3b3aa50c..5ab072223 100644 --- a/proxy/pkg/tracing/tracing.go +++ b/proxy/pkg/tracing/tracing.go @@ -14,7 +14,7 @@ var ( func Configure(cfg *config.Config) error { var err error if cfg.Tracing.Enabled { - if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, "proxy", cfg.Tracing.Type); err != nil { + if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { return err } } diff --git a/settings/pkg/command/health.go b/settings/pkg/command/health.go index 9b475f5e7..3475fceab 100644 --- a/settings/pkg/command/health.go +++ b/settings/pkg/command/health.go @@ -5,6 +5,8 @@ import ( "net/http" "github.com/owncloud/ocis/settings/pkg/config" + "github.com/owncloud/ocis/settings/pkg/config/parser" + "github.com/owncloud/ocis/settings/pkg/logging" "github.com/urfave/cli/v2" ) @@ -14,10 +16,10 @@ func Health(cfg *config.Config) *cli.Command { Name: "health", Usage: "Check health status", Before: func(c *cli.Context) error { - return ParseConfig(c, cfg) + return parser.ParseConfig(cfg) }, Action: func(c *cli.Context) error { - logger := NewLogger(cfg) + logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( fmt.Sprintf( @@ -34,7 +36,7 @@ func Health(cfg *config.Config) *cli.Command { defer resp.Body.Close() - if resp.StatusCode != 200 { + if resp.StatusCode != http.StatusOK { logger.Fatal(). Int("code", resp.StatusCode). Msg("Health seems to be in bad state") diff --git a/settings/pkg/command/root.go b/settings/pkg/command/root.go index 74c09626e..4e65d6391 100644 --- a/settings/pkg/command/root.go +++ b/settings/pkg/command/root.go @@ -4,91 +4,43 @@ import ( "context" "os" - "github.com/owncloud/ocis/ocis-pkg/shared" - + "github.com/owncloud/ocis/ocis-pkg/clihelper" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/settings/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-settings command. func Execute(cfg *config.Config) error { - app := &cli.App{ + app := clihelper.DefaultApp(&cli.App{ Name: "ocis-settings", - Version: version.String, Usage: "Provide settings and permissions for oCIS", - Compiled: version.Compiled(), - - Authors: []*cli.Author{ - { - Name: "ownCloud GmbH", - Email: "support@owncloud.com", - }, - }, - - Before: func(c *cli.Context) error { - cfg.Service.Version = version.String - return nil - }, - - Commands: []*cli.Command{ - Server(cfg), - Health(cfg), - PrintVersion(cfg), - }, - } + Commands: GetCommands(cfg), + }) cli.HelpFlag = &cli.BoolFlag{ Name: "help,h", Usage: "Show the help", } - cli.VersionFlag = &cli.BoolFlag{ - Name: "version,v", - Usage: "Print the version", - } - return app.Run(os.Args) } -// NewLogger initializes a service-specific logger instance. -func NewLogger(cfg *config.Config) log.Logger { - return log.NewLogger( - log.Name("settings"), - log.Level(cfg.Log.Level), - log.Pretty(cfg.Log.Pretty), - log.Color(cfg.Log.Color), - log.File(cfg.Log.File), - ) -} - -// ParseConfig loads idp configuration from known paths. -func ParseConfig(c *cli.Context, cfg *config.Config) error { - conf, err := ociscfg.BindSourcesToStructs("settings", cfg) - if err != nil { - return err - } - - // 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 = &shared.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.Commons == nil { - cfg.Log = &shared.Log{} - } - - conf.LoadOSEnv(config.GetEnv(cfg), false) - bindings := config.StructMappings(cfg) - return ociscfg.BindEnv(conf, bindings) -} - // SutureService allows for the settings command to be embedded and supervised by a suture supervisor tree. type SutureService struct { cfg *config.Config diff --git a/settings/pkg/command/server.go b/settings/pkg/command/server.go index bd430f263..c2c773d0a 100644 --- a/settings/pkg/command/server.go +++ b/settings/pkg/command/server.go @@ -2,10 +2,13 @@ package command import ( "context" - "strings" + "fmt" "github.com/oklog/run" + "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/settings/pkg/config" + "github.com/owncloud/ocis/settings/pkg/config/parser" + "github.com/owncloud/ocis/settings/pkg/logging" "github.com/owncloud/ocis/settings/pkg/metrics" "github.com/owncloud/ocis/settings/pkg/server/debug" "github.com/owncloud/ocis/settings/pkg/server/grpc" @@ -17,22 +20,14 @@ import ( // Server is the entrypoint for the server command. func Server(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "server", - Usage: "Start integrated server", - Before: func(ctx *cli.Context) error { - if cfg.HTTP.Root != "/" { - cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") - } - - if err := ParseConfig(ctx, cfg); err != nil { - return err - } - - return nil + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + return parser.ParseConfig(cfg) }, Action: func(c *cli.Context) error { - logger := NewLogger(cfg) - + logger := logging.Configure(cfg.Service.Name, cfg.Log) err := tracing.Configure(cfg) if err != nil { return err @@ -48,7 +43,7 @@ func Server(cfg *config.Config) *cli.Command { defer cancel() mtrcs := metrics.New() - mtrcs.BuildInfo.WithLabelValues(cfg.Service.Version).Set(1) + mtrcs.BuildInfo.WithLabelValues(version.String).Set(1) // prepare an HTTP server and add it to the group run. httpServer := http.Server( diff --git a/settings/pkg/command/version.go b/settings/pkg/command/version.go index 55091cf81..20df41a1e 100644 --- a/settings/pkg/command/version.go +++ b/settings/pkg/command/version.go @@ -5,30 +5,33 @@ import ( "os" "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" tw "github.com/olekukonko/tablewriter" "github.com/owncloud/ocis/settings/pkg/config" "github.com/urfave/cli/v2" ) -// PrintVersion prints the service versions of all running instances. -func PrintVersion(cfg *config.Config) *cli.Command { +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "version", - Usage: "Print the versions of the running instances", - Before: func(c *cli.Context) error { - return ParseConfig(c, cfg) - }, + 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 settings services from the registry: %v", err)) + 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 settings service found.") + fmt.Println("No running " + cfg.Service.Name + " service found.") return nil } diff --git a/settings/pkg/config/config.go b/settings/pkg/config/config.go index 4d91088b9..fce3b34d5 100644 --- a/settings/pkg/config/config.go +++ b/settings/pkg/config/config.go @@ -2,134 +2,31 @@ package config import ( "context" - "path" "github.com/owncloud/ocis/ocis-pkg/shared" - - "github.com/owncloud/ocis/ocis-pkg/config/defaults" ) -// Debug defines the available debug configuration. -type Debug struct { - Addr string `ocisConfig:"addr"` - Token string `ocisConfig:"token"` - Pprof bool `ocisConfig:"pprof"` - Zpages bool `ocisConfig:"zpages"` -} - -// CORS defines the available cors configuration. -type CORS struct { - AllowedOrigins []string `ocisConfig:"allowed_origins"` - AllowedMethods []string `ocisConfig:"allowed_methods"` - AllowedHeaders []string `ocisConfig:"allowed_headers"` - AllowCredentials bool `ocisConfig:"allow_credentials"` -} - -// HTTP defines the available http configuration. -type HTTP struct { - Addr string `ocisConfig:"addr"` - Namespace string `ocisConfig:"namespace"` - Root string `ocisConfig:"root"` - CacheTTL int `ocisConfig:"cache_ttl"` - CORS CORS `ocisConfig:"cors"` -} - -// GRPC defines the available grpc configuration. -type GRPC struct { - Addr string `ocisConfig:"grpc"` - Namespace string `ocisConfig:"namespace"` -} - -// Service provides configuration options for the service -type Service struct { - Name string `ocisConfig:"name"` - Version string `ocisConfig:"version"` - DataPath string `ocisConfig:"data_path"` -} - -// Tracing defines the available tracing configuration. -type Tracing struct { - Enabled bool `ocisConfig:"enabled"` - Type string `ocisConfig:"type"` - Endpoint string `ocisConfig:"endpoint"` - Collector string `ocisConfig:"collector"` - Service string `ocisConfig:"service"` -} - -// Asset undocumented -type Asset struct { - Path string `ocisConfig:"asset"` -} - -// TokenManager is the config for using the reva token manager -type TokenManager struct { - JWTSecret string `ocisConfig:"jwt_secret"` -} - // Config combines all available configuration parts. type Config struct { *shared.Commons - File string `ocisConfig:"file"` - Service Service `ocisConfig:"service"` - Log *shared.Log `ocisConfig:"log"` - Debug Debug `ocisConfig:"debug"` - HTTP HTTP `ocisConfig:"http"` - GRPC GRPC `ocisConfig:"grpc"` - Tracing Tracing `ocisConfig:"tracing"` + Service Service + + Tracing Tracing `ocisConfig:"tracing"` + Log *Log `ocisConfig:"log"` + Debug Debug `ocisConfig:"debug"` + + HTTP HTTP `ocisConfig:"http"` + GRPC GRPC `ocisConfig:"grpc"` + + DataPath string `ocisConfig:"data_path" env:"SETTINGS_DATA_PATH"` Asset Asset `ocisConfig:"asset"` TokenManager TokenManager `ocisConfig:"token_manager"` - Context context.Context - Supervised bool + Context context.Context } -// New initializes a new configuration with or without defaults. -func New() *Config { - return &Config{} -} - -// DefaultConfig provides sane bootstrapping defaults. -func DefaultConfig() *Config { - return &Config{ - Service: Service{ - Name: "settings", - DataPath: path.Join(defaults.BaseDataPath(), "settings"), - }, - Debug: Debug{ - Addr: "127.0.0.1:9194", - Token: "", - Pprof: false, - Zpages: false, - }, - HTTP: HTTP{ - Addr: "127.0.0.1:9190", - Namespace: "com.owncloud.web", - Root: "/", - CacheTTL: 604800, // 7 days - CORS: CORS{ - AllowedOrigins: []string{"*"}, - AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, - AllowedHeaders: []string{"Authorization", "Origin", "Content-Type", "Accept", "X-Requested-With"}, - AllowCredentials: true, - }, - }, - GRPC: GRPC{ - Addr: "127.0.0.1:9191", - Namespace: "com.owncloud.api", - }, - Tracing: Tracing{ - Enabled: false, - Type: "jaeger", - Endpoint: "", - Collector: "", - Service: "settings", - }, - Asset: Asset{ - Path: "", - }, - TokenManager: TokenManager{ - JWTSecret: "Pive-Fumkiu4", - }, - } +// Asset defines the available asset configuration. +type Asset struct { + Path string `ocisConfig:"path" env:"SETTINGS_ASSET_PATH"` } diff --git a/settings/pkg/config/debug.go b/settings/pkg/config/debug.go new file mode 100644 index 000000000..5cec3b97b --- /dev/null +++ b/settings/pkg/config/debug.go @@ -0,0 +1,9 @@ +package config + +// Debug defines the available debug configuration. +type Debug struct { + Addr string `ocisConfig:"addr" env:"SETTINGS_DEBUG_ADDR"` + Token string `ocisConfig:"token" env:"SETTINGS_DEBUG_TOKEN"` + Pprof bool `ocisConfig:"pprof" env:"SETTINGS_DEBUG_PPROF"` + Zpages bool `ocisConfig:"zpages" env:"SETTINGS_DEBUG_ZPAGES"` +} diff --git a/settings/pkg/config/defaultconfig.go b/settings/pkg/config/defaultconfig.go new file mode 100644 index 000000000..a667c0c25 --- /dev/null +++ b/settings/pkg/config/defaultconfig.go @@ -0,0 +1,50 @@ +package config + +import ( + "path" + + "github.com/owncloud/ocis/ocis-pkg/config/defaults" +) + +func DefaultConfig() *Config { + return &Config{ + Service: Service{ + Name: "settings", + }, + Debug: Debug{ + Addr: "127.0.0.1:9194", + Token: "", + Pprof: false, + Zpages: false, + }, + HTTP: HTTP{ + Addr: "127.0.0.1:9190", + Namespace: "com.owncloud.web", + Root: "/", + CacheTTL: 604800, // 7 days + CORS: CORS{ + AllowedOrigins: []string{"*"}, + AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, + AllowedHeaders: []string{"Authorization", "Origin", "Content-Type", "Accept", "X-Requested-With"}, + AllowCredentials: true, + }, + }, + GRPC: GRPC{ + Addr: "127.0.0.1:9191", + Namespace: "com.owncloud.api", + }, + Tracing: Tracing{ + Enabled: false, + Type: "jaeger", + Endpoint: "", + Collector: "", + }, + DataPath: path.Join(defaults.BaseDataPath(), "settings"), + Asset: Asset{ + Path: "", + }, + TokenManager: TokenManager{ + JWTSecret: "Pive-Fumkiu4", + }, + } +} diff --git a/settings/pkg/config/grpc.go b/settings/pkg/config/grpc.go new file mode 100644 index 000000000..016b61fa9 --- /dev/null +++ b/settings/pkg/config/grpc.go @@ -0,0 +1,7 @@ +package config + +// GRPC defines the available grpc configuration. +type GRPC struct { + Addr string `ocisConfig:"addr" env:"SETTINGS_GRPC_ADDR"` + Namespace string +} diff --git a/settings/pkg/config/http.go b/settings/pkg/config/http.go new file mode 100644 index 000000000..f2099febf --- /dev/null +++ b/settings/pkg/config/http.go @@ -0,0 +1,18 @@ +package config + +// HTTP defines the available http configuration. +type HTTP struct { + Addr string `ocisConfig:"addr" env:"SETTINGS_HTTP_ADDR"` + Namespace string + Root string `ocisConfig:"root" env:"SETTINGS_HTTP_ROOT"` + CacheTTL int `ocisConfig:"cache_ttl" env:"SETTINGS_CACHE_TTL"` + CORS CORS `ocisConfig:"cors"` +} + +// CORS defines the available cors configuration. +type CORS struct { + AllowedOrigins []string `ocisConfig:"allowed_origins"` + AllowedMethods []string `ocisConfig:"allowed_methods"` + AllowedHeaders []string `ocisConfig:"allowed_headers"` + AllowCredentials bool `ocisConfig:"allowed_credentials"` +} diff --git a/settings/pkg/config/log.go b/settings/pkg/config/log.go new file mode 100644 index 000000000..48247bd17 --- /dev/null +++ b/settings/pkg/config/log.go @@ -0,0 +1,9 @@ +package config + +// Log defines the available log configuration. +type Log struct { + Level string `mapstructure:"level" env:"OCIS_LOG_LEVEL;SETTINGS_LOG_LEVEL"` + Pretty bool `mapstructure:"pretty" env:"OCIS_LOG_PRETTY;SETTINGS_LOG_PRETTY"` + Color bool `mapstructure:"color" env:"OCIS_LOG_COLOR;SETTINGS_LOG_COLOR"` + File string `mapstructure:"file" env:"OCIS_LOG_FILE;SETTINGS_LOG_FILE"` +} diff --git a/settings/pkg/config/mappings.go b/settings/pkg/config/mappings.go deleted file mode 100644 index 0fd5a9f2d..000000000 --- a/settings/pkg/config/mappings.go +++ /dev/null @@ -1,119 +0,0 @@ -package config - -import "github.com/owncloud/ocis/ocis-pkg/shared" - -// GetEnv fetches a list of known env variables for this extension. It is to be used by gookit, as it provides a list -// with all the environment variables an extension supports. -func GetEnv(cfg *Config) []string { - var r = make([]string, len(structMappings(cfg))) - for i := range structMappings(cfg) { - r = append(r, structMappings(cfg)[i].EnvVars...) - } - - return r -} - -// StructMappings binds a set of environment variables to a destination on cfg. Iterating over this set and editing the -// Destination value of a binding will alter the original value, as it is a pointer to its memory address. This lets -// us propagate changes easier. -func StructMappings(cfg *Config) []shared.EnvBinding { - return structMappings(cfg) -} - -// structMappings binds a set of environment variables to a destination on cfg. -func structMappings(cfg *Config) []shared.EnvBinding { - return []shared.EnvBinding{ - { - EnvVars: []string{"OCIS_LOG_LEVEL", "SETTINGS_LOG_LEVEL"}, - Destination: &cfg.Log.Level, - }, - { - EnvVars: []string{"OCIS_LOG_PRETTY", "SETTINGS_LOG_PRETTY"}, - Destination: &cfg.Log.Pretty, - }, - { - EnvVars: []string{"OCIS_LOG_COLOR", "SETTINGS_LOG_COLOR"}, - Destination: &cfg.Log.Color, - }, - { - EnvVars: []string{"SETTINGS_CONFIG_FILE"}, - Destination: &cfg.File, - }, - { - EnvVars: []string{"OCIS_TRACING_ENABLED", "SETTINGS_TRACING_ENABLED"}, - Destination: &cfg.Tracing.Enabled, - }, - { - EnvVars: []string{"OCIS_TRACING_TYPE", "SETTINGS_TRACING_TYPE"}, - Destination: &cfg.Tracing.Type, - }, - { - EnvVars: []string{"OCIS_TRACING_ENDPOINT", "SETTINGS_TRACING_ENDPOINT"}, - Destination: &cfg.Tracing.Endpoint, - }, - { - EnvVars: []string{"OCIS_TRACING_COLLECTOR", "SETTINGS_TRACING_COLLECTOR"}, - Destination: &cfg.Tracing.Collector, - }, - { - EnvVars: []string{"SETTINGS_TRACING_SERVICE"}, - Destination: &cfg.Tracing.Service, - }, - { - EnvVars: []string{"SETTINGS_DEBUG_ADDR"}, - Destination: &cfg.Debug.Addr, - }, - { - EnvVars: []string{"SETTINGS_DEBUG_TOKEN"}, - Destination: &cfg.Debug.Token, - }, - { - EnvVars: []string{"SETTINGS_DEBUG_PPROF"}, - Destination: &cfg.Debug.Pprof, - }, - { - EnvVars: []string{"SETTINGS_DEBUG_ZPAGES"}, - Destination: &cfg.Debug.Zpages, - }, - { - EnvVars: []string{"SETTINGS_HTTP_ADDR"}, - Destination: &cfg.HTTP.Addr, - }, - { - EnvVars: []string{"SETTINGS_HTTP_NAMESPACE"}, - Destination: &cfg.HTTP.Namespace, - }, - { - EnvVars: []string{"SETTINGS_HTTP_ROOT"}, - Destination: &cfg.HTTP.Root, - }, - { - EnvVars: []string{"SETTINGS_CACHE_TTL"}, - Destination: &cfg.HTTP.CacheTTL, - }, - { - EnvVars: []string{"SETTINGS_GRPC_ADDR"}, - Destination: &cfg.GRPC.Addr, - }, - { - EnvVars: []string{"SETTINGS_ASSET_PATH"}, - Destination: &cfg.Asset.Path, - }, - { - EnvVars: []string{"SETTINGS_GRPC_NAMESPACE"}, - Destination: &cfg.GRPC.Namespace, - }, - { - EnvVars: []string{"SETTINGS_NAME"}, - Destination: &cfg.Service.Name, - }, - { - EnvVars: []string{"SETTINGS_DATA_PATH"}, - Destination: &cfg.Service.DataPath, - }, - { - EnvVars: []string{"OCIS_JWT_SECRET", "SETTINGS_JWT_SECRET"}, - Destination: &cfg.TokenManager.JWTSecret, - }, - } -} diff --git a/settings/pkg/config/parser/parse.go b/settings/pkg/config/parser/parse.go new file mode 100644 index 000000000..551d44108 --- /dev/null +++ b/settings/pkg/config/parser/parse.go @@ -0,0 +1,46 @@ +package parser + +import ( + "errors" + "strings" + + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/settings/pkg/config" + + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" +) + +// ParseConfig loads accounts configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + // 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.Commons == nil { + cfg.Log = &config.Log{} + } + + // 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 + } + } + + // sanitize config + if cfg.HTTP.Root != "/" { + cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") + } + + return nil +} diff --git a/settings/pkg/config/reva.go b/settings/pkg/config/reva.go new file mode 100644 index 000000000..5427747df --- /dev/null +++ b/settings/pkg/config/reva.go @@ -0,0 +1,6 @@ +package config + +// TokenManager is the config for using the reva token manager +type TokenManager struct { + JWTSecret string `ocisConfig:"jwt_secret" env:"OCIS_JWT_SECRET;SETTINGS_JWT_SECRET"` +} diff --git a/settings/pkg/config/service.go b/settings/pkg/config/service.go new file mode 100644 index 000000000..f98aa3d27 --- /dev/null +++ b/settings/pkg/config/service.go @@ -0,0 +1,6 @@ +package config + +// Service defines the available service configuration. +type Service struct { + Name string +} diff --git a/settings/pkg/config/tracing.go b/settings/pkg/config/tracing.go new file mode 100644 index 000000000..7197a69f3 --- /dev/null +++ b/settings/pkg/config/tracing.go @@ -0,0 +1,9 @@ +package config + +// Tracing defines the available tracing configuration. +type Tracing struct { + Enabled bool `ocisConfig:"enabled" env:"OCIS_TRACING_ENABLED;SETTINGS_TRACING_ENABLED"` + Type string `ocisConfig:"type" env:"OCIS_TRACING_TYPE;SETTINGS_TRACING_TYPE"` + Endpoint string `ocisConfig:"endpoint" env:"OCIS_TRACING_ENDPOINT;SETTINGS_TRACING_ENDPOINT"` + Collector string `ocisConfig:"collector" env:"OCIS_TRACING_COLLECTOR;SETTINGS_TRACING_COLLECTOR"` +} diff --git a/settings/pkg/logging/logging.go b/settings/pkg/logging/logging.go new file mode 100644 index 000000000..aba0cbe82 --- /dev/null +++ b/settings/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/settings/pkg/config" +) + +// 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/settings/pkg/proto/v0/settings.pb.micro_test.go b/settings/pkg/proto/v0/settings.pb.micro_test.go index 786b6ffaf..ec15d60a7 100644 --- a/settings/pkg/proto/v0/settings.pb.micro_test.go +++ b/settings/pkg/proto/v0/settings.pb.micro_test.go @@ -176,8 +176,8 @@ func init() { grpc.Address("localhost:9992"), ) - cfg := config.New() - cfg.Service.DataPath = dataPath + cfg := config.DefaultConfig() + cfg.DataPath = dataPath handler = svc.NewService(cfg, ocislog.NewLogger(ocislog.Color(true), ocislog.Pretty(true))) err := proto.RegisterBundleServiceHandler(service.Server(), handler) if err != nil { diff --git a/settings/pkg/server/debug/server.go b/settings/pkg/server/debug/server.go index 36bf1411e..81f41de18 100644 --- a/settings/pkg/server/debug/server.go +++ b/settings/pkg/server/debug/server.go @@ -15,7 +15,7 @@ func Server(opts ...Option) (*http.Server, error) { return debug.NewService( debug.Logger(options.Logger), - debug.Name("settings"), + debug.Name(options.Config.Service.Name), debug.Version(version.String), debug.Address(options.Config.Debug.Addr), debug.Token(options.Config.Debug.Token), @@ -36,9 +36,13 @@ func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) - // TODO(tboerger): check if services are up and running + // TODO: check if services are up and running - _, _ = io.WriteString(w, http.StatusText(http.StatusOK)) + _, 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) + } } } @@ -48,8 +52,12 @@ func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) - // TODO(tboerger): check if services are up and running + // TODO: check if services are up and running - _, _ = io.WriteString(w, http.StatusText(http.StatusOK)) + _, 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/settings/pkg/server/grpc/server.go b/settings/pkg/server/grpc/server.go index a2b5d1f1d..a3a2bde3b 100644 --- a/settings/pkg/server/grpc/server.go +++ b/settings/pkg/server/grpc/server.go @@ -2,6 +2,7 @@ package grpc import ( "github.com/owncloud/ocis/ocis-pkg/service/grpc" + "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/settings/pkg/proto/v0" svc "github.com/owncloud/ocis/settings/pkg/service/v0" ) @@ -13,7 +14,7 @@ func Server(opts ...Option) grpc.Service { service := grpc.NewService( grpc.Logger(options.Logger), grpc.Name(options.Name), - grpc.Version(options.Config.Service.Version), + grpc.Version(version.String), grpc.Address(options.Config.GRPC.Addr), grpc.Namespace(options.Config.GRPC.Namespace), grpc.Context(options.Context), diff --git a/settings/pkg/server/http/server.go b/settings/pkg/server/http/server.go index cb63ac5ec..29795d78f 100644 --- a/settings/pkg/server/http/server.go +++ b/settings/pkg/server/http/server.go @@ -21,7 +21,7 @@ func Server(opts ...Option) http.Service { service := http.NewService( http.Logger(options.Logger), http.Name(options.Name), - http.Version(options.Config.Service.Version), + http.Version(version.String), http.Address(options.Config.HTTP.Addr), http.Namespace(options.Config.HTTP.Namespace), http.Context(options.Context), diff --git a/settings/pkg/store/filesystem/store.go b/settings/pkg/store/filesystem/store.go index cf81638b9..ccd0fe011 100644 --- a/settings/pkg/store/filesystem/store.go +++ b/settings/pkg/store/filesystem/store.go @@ -32,16 +32,16 @@ func New(cfg *config.Config) settings.Manager { //), } - if _, err := os.Stat(cfg.Service.DataPath); err != nil { - s.Logger.Info().Msgf("creating container on %v", cfg.Service.DataPath) - err = os.MkdirAll(cfg.Service.DataPath, 0700) + if _, err := os.Stat(cfg.DataPath); err != nil { + s.Logger.Info().Msgf("creating container on %v", cfg.DataPath) + err = os.MkdirAll(cfg.DataPath, 0700) if err != nil { - s.Logger.Err(err).Msgf("providing container on %v", cfg.Service.DataPath) + s.Logger.Err(err).Msgf("providing container on %v", cfg.DataPath) } } - s.dataPath = cfg.Service.DataPath + s.dataPath = cfg.DataPath return &s } diff --git a/settings/pkg/tracing/tracing.go b/settings/pkg/tracing/tracing.go index 09d85008b..620d261cc 100644 --- a/settings/pkg/tracing/tracing.go +++ b/settings/pkg/tracing/tracing.go @@ -14,7 +14,7 @@ var ( func Configure(cfg *config.Config) error { var err error if cfg.Tracing.Enabled { - if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, "settings", cfg.Tracing.Type); err != nil { + if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { return err } } diff --git a/storage/pkg/command/appprovider.go b/storage/pkg/command/appprovider.go index 80c8c523b..591fc7813 100644 --- a/storage/pkg/command/appprovider.go +++ b/storage/pkg/command/appprovider.go @@ -22,7 +22,7 @@ import ( func AppProvider(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "app-provider", - Usage: "Start appprovider for providing apps", + Usage: "start appprovider for providing apps", Before: func(c *cli.Context) error { return ParseConfig(c, cfg, "storage-app-provider") }, diff --git a/storage/pkg/command/authbasic.go b/storage/pkg/command/authbasic.go index 10268754a..8528cb6c1 100644 --- a/storage/pkg/command/authbasic.go +++ b/storage/pkg/command/authbasic.go @@ -23,7 +23,7 @@ import ( func AuthBasic(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "auth-basic", - Usage: "Start authprovider for basic auth", + Usage: "start authprovider for basic auth", Before: func(c *cli.Context) error { return ParseConfig(c, cfg, "storage-auth-basic") }, diff --git a/storage/pkg/command/authbearer.go b/storage/pkg/command/authbearer.go index af60ccbda..aff1c2a5f 100644 --- a/storage/pkg/command/authbearer.go +++ b/storage/pkg/command/authbearer.go @@ -22,7 +22,7 @@ import ( func AuthBearer(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "auth-bearer", - Usage: "Start authprovider for bearer auth", + Usage: "start authprovider for bearer auth", Before: func(c *cli.Context) error { return ParseConfig(c, cfg, "storage-auth-bearer") }, diff --git a/storage/pkg/command/authmachine.go b/storage/pkg/command/authmachine.go index eaaa97cbf..46e0fa949 100644 --- a/storage/pkg/command/authmachine.go +++ b/storage/pkg/command/authmachine.go @@ -22,7 +22,7 @@ import ( func AuthMachine(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "auth-machine", - Usage: "Start authprovider for machine auth", + Usage: "start authprovider for machine auth", Before: func(c *cli.Context) error { return ParseConfig(c, cfg, "storage-auth-machine") }, diff --git a/storage/pkg/command/frontend.go b/storage/pkg/command/frontend.go index 23d9f7121..90b482790 100644 --- a/storage/pkg/command/frontend.go +++ b/storage/pkg/command/frontend.go @@ -26,7 +26,7 @@ import ( func Frontend(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "frontend", - Usage: "Start frontend service", + Usage: "start frontend service", Before: func(c *cli.Context) error { if err := loadUserAgent(c, cfg); err != nil { return err diff --git a/storage/pkg/command/gateway.go b/storage/pkg/command/gateway.go index 63f158da1..5570c959b 100644 --- a/storage/pkg/command/gateway.go +++ b/storage/pkg/command/gateway.go @@ -30,7 +30,7 @@ import ( func Gateway(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "gateway", - Usage: "Start gateway", + Usage: "start gateway", Before: func(c *cli.Context) error { if err := ParseConfig(c, cfg, "storage-gateway"); err != nil { return err diff --git a/storage/pkg/command/groups.go b/storage/pkg/command/groups.go index 043c96fdc..b71951431 100644 --- a/storage/pkg/command/groups.go +++ b/storage/pkg/command/groups.go @@ -23,7 +23,7 @@ import ( func Groups(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "groups", - Usage: "Start groups service", + Usage: "start groups service", Before: func(c *cli.Context) error { return ParseConfig(c, cfg, "storage-groups") }, diff --git a/storage/pkg/command/health.go b/storage/pkg/command/health.go index a3c3791a9..7ba232a1c 100644 --- a/storage/pkg/command/health.go +++ b/storage/pkg/command/health.go @@ -11,8 +11,9 @@ import ( // Health is the entrypoint for the health command. func Health(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "health", - Usage: "Check health status", + Name: "health", + Usage: "check health status", + Category: "info", Before: func(c *cli.Context) error { return ParseConfig(c, cfg, "storage") }, @@ -34,7 +35,7 @@ func Health(cfg *config.Config) *cli.Command { defer resp.Body.Close() - if resp.StatusCode != 200 { + if resp.StatusCode != http.StatusOK { logger.Fatal(). Int("code", resp.StatusCode). Msg("Health seems to be in bad state") diff --git a/storage/pkg/command/root.go b/storage/pkg/command/root.go index 4ac235e8c..477ec1df1 100644 --- a/storage/pkg/command/root.go +++ b/storage/pkg/command/root.go @@ -3,57 +3,50 @@ package command import ( "os" + "github.com/owncloud/ocis/ocis-pkg/clihelper" "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/storage/pkg/config" "github.com/urfave/cli/v2" ) +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + Frontend(cfg), + Gateway(cfg), + Users(cfg), + Groups(cfg), + AppProvider(cfg), + AuthBasic(cfg), + AuthBearer(cfg), + AuthMachine(cfg), + Sharing(cfg), + StorageHome(cfg), + StorageUsers(cfg), + StoragePublicLink(cfg), + StorageMetadata(cfg), + Health(cfg), + } +} + // Execute is the entry point for the storage command. func Execute(cfg *config.Config) error { - app := &cli.App{ - Name: "storage", - Version: version.String, - Usage: "Storage service for oCIS", - Compiled: version.Compiled(), + app := clihelper.DefaultApp(&cli.App{ + Name: "storage", + Usage: "Storage service for oCIS", - Authors: []*cli.Author{ - { - Name: "ownCloud GmbH", - Email: "support@owncloud.com", - }, - }, Before: func(c *cli.Context) error { return ParseConfig(c, cfg, "storage") }, - Commands: []*cli.Command{ - Frontend(cfg), - Gateway(cfg), - Users(cfg), - Groups(cfg), - AppProvider(cfg), - AuthBasic(cfg), - AuthBearer(cfg), - Sharing(cfg), - StorageHome(cfg), - StorageUsers(cfg), - StoragePublicLink(cfg), - StorageMetadata(cfg), - Health(cfg), - }, - } + Commands: GetCommands(cfg), + }) cli.HelpFlag = &cli.BoolFlag{ Name: "help,h", Usage: "Show the help", } - cli.VersionFlag = &cli.BoolFlag{ - Name: "version,v", - Usage: "Print the version", - } - return app.Run(os.Args) } diff --git a/storage/pkg/command/sharing.go b/storage/pkg/command/sharing.go index abace4d50..52735f32b 100644 --- a/storage/pkg/command/sharing.go +++ b/storage/pkg/command/sharing.go @@ -25,7 +25,7 @@ import ( func Sharing(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "sharing", - Usage: "Start sharing service", + Usage: "start sharing service", Before: func(c *cli.Context) error { return ParseConfig(c, cfg, "storage-sharing") }, diff --git a/storage/pkg/command/storagehome.go b/storage/pkg/command/storagehome.go index bbeca4d70..8964e0887 100644 --- a/storage/pkg/command/storagehome.go +++ b/storage/pkg/command/storagehome.go @@ -24,7 +24,7 @@ import ( func StorageHome(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "storage-home", - Usage: "Start storage-home service", + Usage: "start storage-home service", Before: func(c *cli.Context) error { return ParseConfig(c, cfg, "storage-home") }, diff --git a/storage/pkg/command/storagemetadata.go b/storage/pkg/command/storagemetadata.go index 1c84313a3..cb84b9ac8 100644 --- a/storage/pkg/command/storagemetadata.go +++ b/storage/pkg/command/storagemetadata.go @@ -28,11 +28,11 @@ import ( func StorageMetadata(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "storage-metadata", - Usage: "Start storage-metadata service", + Usage: "start storage-metadata service", Before: func(c *cli.Context) error { return ParseConfig(c, cfg, "storage-metadata") }, - Category: "Extensions", + Category: "extensions", Action: func(c *cli.Context) error { logger := NewLogger(cfg) tracing.Configure(cfg, logger) diff --git a/storage/pkg/command/storagepubliclink.go b/storage/pkg/command/storagepubliclink.go index edd73c9ad..261539813 100644 --- a/storage/pkg/command/storagepubliclink.go +++ b/storage/pkg/command/storagepubliclink.go @@ -22,11 +22,11 @@ import ( func StoragePublicLink(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "storage-public-link", - Usage: "Start storage-public-link service", + Usage: "start storage-public-link service", Before: func(c *cli.Context) error { return ParseConfig(c, cfg, "storage-public-link") }, - Category: "Extensions", + Category: "extensions", Action: func(c *cli.Context) error { logger := NewLogger(cfg) tracing.Configure(cfg, logger) diff --git a/storage/pkg/command/storageusers.go b/storage/pkg/command/storageusers.go index 7bb7aa6c0..7a2be81e8 100644 --- a/storage/pkg/command/storageusers.go +++ b/storage/pkg/command/storageusers.go @@ -23,7 +23,7 @@ import ( func StorageUsers(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "storage-users", - Usage: "Start storage-users service", + Usage: "start storage-users service", Before: func(c *cli.Context) error { return ParseConfig(c, cfg, "storage-userprovider") }, diff --git a/storage/pkg/command/users.go b/storage/pkg/command/users.go index 34aee1d7c..c71a674b7 100644 --- a/storage/pkg/command/users.go +++ b/storage/pkg/command/users.go @@ -23,7 +23,7 @@ import ( func Users(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "users", - Usage: "Start users service", + Usage: "start users service", Before: func(c *cli.Context) error { return ParseConfig(c, cfg, "storage-users") }, diff --git a/storage/pkg/config/config.go b/storage/pkg/config/config.go index 6691edda4..ac042a47d 100644 --- a/storage/pkg/config/config.go +++ b/storage/pkg/config/config.go @@ -125,7 +125,7 @@ type Port struct { Context context.Context // Supervised is used when running under an oCIS runtime supervision tree - Supervised bool // deprecated + Supervised bool // deprecated // TODO: delete me } // Users defines the available users configuration. diff --git a/storage/pkg/server/debug/server.go b/storage/pkg/server/debug/server.go index 4a3fa24b3..29ea57b0a 100644 --- a/storage/pkg/server/debug/server.go +++ b/storage/pkg/server/debug/server.go @@ -32,9 +32,11 @@ func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) - // TODO(tboerger): check if services are up and running + // TODO: check if services are up and running - if _, err := io.WriteString(w, http.StatusText(http.StatusOK)); err != nil { + _, 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) } } @@ -46,10 +48,12 @@ func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) - // TODO(tboerger): check if services are up and running + // TODO: check if services are up and running - if _, err := io.WriteString(w, http.StatusText(http.StatusOK)); err != nil { + _, 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) } } -} +} \ No newline at end of file diff --git a/store/pkg/command/health.go b/store/pkg/command/health.go index 38461c9ac..fafc60e02 100644 --- a/store/pkg/command/health.go +++ b/store/pkg/command/health.go @@ -5,19 +5,22 @@ import ( "net/http" "github.com/owncloud/ocis/store/pkg/config" + "github.com/owncloud/ocis/store/pkg/config/parser" + "github.com/owncloud/ocis/store/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", + Name: "health", + Usage: "check health status", + Category: "info", Before: func(c *cli.Context) error { - return ParseConfig(c, cfg) + return parser.ParseConfig(cfg) }, Action: func(c *cli.Context) error { - logger := NewLogger(cfg) + logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( fmt.Sprintf( @@ -34,7 +37,7 @@ func Health(cfg *config.Config) *cli.Command { defer resp.Body.Close() - if resp.StatusCode != 200 { + if resp.StatusCode != http.StatusOK { logger.Fatal(). Int("code", resp.StatusCode). Msg("Health seems to be in bad state") diff --git a/store/pkg/command/root.go b/store/pkg/command/root.go index 49584e63c..add49da6f 100644 --- a/store/pkg/command/root.go +++ b/store/pkg/command/root.go @@ -4,79 +4,43 @@ import ( "context" "os" + "github.com/owncloud/ocis/ocis-pkg/clihelper" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/store/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-store command. func Execute(cfg *config.Config) error { - app := &cli.App{ + app := clihelper.DefaultApp(&cli.App{ Name: "ocis-store", - Version: version.String, Usage: "Service to store values for ocis extensions", - Compiled: version.Compiled(), - - Authors: []*cli.Author{ - { - Name: "ownCloud GmbH", - Email: "support@owncloud.com", - }, - }, - - Before: func(c *cli.Context) error { - cfg.Service.Version = version.String - return ParseConfig(c, cfg) - }, - - Commands: []*cli.Command{ - Server(cfg), - Health(cfg), - PrintVersion(cfg), - }, - } + Commands: GetCommands(cfg), + }) cli.HelpFlag = &cli.BoolFlag{ Name: "help,h", Usage: "Show the help", } - cli.VersionFlag = &cli.BoolFlag{ - Name: "version,v", - Usage: "Print the version", - } - return app.Run(os.Args) } -// NewLogger initializes a service-specific logger instance. -func NewLogger(cfg *config.Config) log.Logger { - return log.NewLogger( - log.Name("store"), - log.Level(cfg.Log.Level), - log.Pretty(cfg.Log.Pretty), - log.Color(cfg.Log.Color), - log.File(cfg.Log.File), - ) -} - -// ParseConfig loads idp configuration from known paths. -func ParseConfig(c *cli.Context, cfg *config.Config) error { - conf, err := ociscfg.BindSourcesToStructs("store", cfg) - if err != nil { - return err - } - - // load all env variables relevant to the config in the current context. - conf.LoadOSEnv(config.GetEnv(), false) - - bindings := config.StructMappings(cfg) - return ociscfg.BindEnv(conf, bindings) -} - // SutureService allows for the store command to be embedded and supervised by a suture supervisor tree. type SutureService struct { cfg *config.Config @@ -84,6 +48,7 @@ type SutureService struct { // NewSutureService creates a new store.SutureService func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.Store.Commons = cfg.Commons return SutureService{ cfg: cfg.Store, } diff --git a/store/pkg/command/server.go b/store/pkg/command/server.go index 4bc9b35b9..eb0b7f8d6 100644 --- a/store/pkg/command/server.go +++ b/store/pkg/command/server.go @@ -2,52 +2,34 @@ package command import ( "context" - - gofig "github.com/gookit/config/v2" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/shared" - - "github.com/owncloud/ocis/store/pkg/tracing" + "fmt" "github.com/oklog/run" + + "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/store/pkg/config" + "github.com/owncloud/ocis/store/pkg/config/parser" + "github.com/owncloud/ocis/store/pkg/logging" "github.com/owncloud/ocis/store/pkg/metrics" "github.com/owncloud/ocis/store/pkg/server/debug" "github.com/owncloud/ocis/store/pkg/server/grpc" + "github.com/owncloud/ocis/store/pkg/tracing" "github.com/urfave/cli/v2" ) // Server is the entrypoint for the server command. func Server(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "server", - Usage: "Start integrated server", - Before: func(ctx *cli.Context) error { - // remember shared logging info to prevent empty overwrites - inLog := cfg.Log - if err := ParseConfig(ctx, cfg); err != nil { - return err - } - - if (cfg.Log == shared.Log{}) && (inLog != shared.Log{}) { - // set the default to the parent config - cfg.Log = inLog - - // and parse the environment - conf := &gofig.Config{} - conf.LoadOSEnv(config.GetEnv(), false) - bindings := config.StructMappings(cfg) - if err := ociscfg.BindEnv(conf, bindings); err != nil { - return err - } - } - - return nil + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + return parser.ParseConfig(cfg) }, Action: func(c *cli.Context) error { - logger := NewLogger(cfg) - - if err := tracing.Configure(cfg); err != nil { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg) + if err != nil { return err } @@ -64,7 +46,7 @@ func Server(cfg *config.Config) *cli.Command { defer cancel() - metrics.BuildInfo.WithLabelValues(cfg.Service.Version).Set(1) + metrics.BuildInfo.WithLabelValues(version.String).Set(1) { server := grpc.Server( diff --git a/store/pkg/command/version.go b/store/pkg/command/version.go index b5bfef3b2..e976ef727 100644 --- a/store/pkg/command/version.go +++ b/store/pkg/command/version.go @@ -5,30 +5,33 @@ import ( "os" "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" tw "github.com/olekukonko/tablewriter" "github.com/owncloud/ocis/store/pkg/config" "github.com/urfave/cli/v2" ) -// PrintVersion prints the service versions of all running instances. -func PrintVersion(cfg *config.Config) *cli.Command { +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "version", - Usage: "Print the versions of the running instances", - Before: func(c *cli.Context) error { - return ParseConfig(c, cfg) - }, + 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.Service.Namespace + "." + cfg.Service.Name) + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) if err != nil { - fmt.Println(fmt.Errorf("could not get store services from the registry: %v", err)) + 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 store service found.") + fmt.Println("No running " + cfg.Service.Name + " service found.") return nil } diff --git a/store/pkg/config/config.go b/store/pkg/config/config.go index a39cae29b..46855d55d 100644 --- a/store/pkg/config/config.go +++ b/store/pkg/config/config.go @@ -2,95 +2,23 @@ package config import ( "context" - "path" - "github.com/owncloud/ocis/ocis-pkg/config/defaults" "github.com/owncloud/ocis/ocis-pkg/shared" ) -// Debug defines the available debug configuration. -type Debug struct { - Addr string `ocisConfig:"addr"` - Token string `ocisConfig:"token"` - Pprof bool `ocisConfig:"pprof"` - Zpages bool `ocisConfig:"zpages"` -} - -// GRPC defines the available grpc configuration. -type GRPC struct { - Addr string `ocisConfig:"addr"` - Root string `ocisConfig:"root"` -} - -// Service defines the available service configuration. -type Service struct { - Name string `ocisConfig:"name"` - Namespace string `ocisConfig:"namespace"` - Version string `ocisConfig:"version"` -} - -// Tracing defines the available tracing configuration. -type Tracing struct { - Enabled bool `ocisConfig:"enabled"` - Type string `ocisConfig:"type"` - Endpoint string `ocisConfig:"endpoint"` - Collector string `ocisConfig:"collector"` - Service string `ocisConfig:"service"` -} - // Config combines all available configuration parts. type Config struct { - File string `ocisConfig:"file"` - Log shared.Log `ocisConfig:"log"` - Debug Debug `ocisConfig:"debug"` - GRPC GRPC `ocisConfig:"grpc"` - Tracing Tracing `ocisConfig:"tracing"` - Datapath string `ocisConfig:"data_path"` - Service Service `ocisConfig:"service"` + *shared.Commons - Context context.Context - Supervised bool -} - -// New initializes a new configuration with or without defaults. -func New() *Config { - return &Config{} -} - -func DefaultConfig() *Config { - return &Config{ - Log: shared.Log{}, - Debug: Debug{ - Addr: "127.0.0.1:9464", - Token: "", - Pprof: false, - Zpages: false, - }, - GRPC: GRPC{ - Addr: "127.0.0.1:9460", - }, - Tracing: Tracing{ - Enabled: false, - Type: "jaeger", - Endpoint: "", - Collector: "", - Service: "store", - }, - Datapath: path.Join(defaults.BaseDataPath(), "store"), - Service: Service{ - Name: "store", - Namespace: "com.owncloud.api", - }, - } -} - -// GetEnv fetches a list of known env variables for this extension. It is to be used by gookit, as it provides a list -// with all the environment variables an extension supports. -func GetEnv() []string { - var r = make([]string, len(structMappings(&Config{}))) - for i := range structMappings(&Config{}) { - r = append(r, structMappings(&Config{})[i].EnvVars...) - } - - return r + Service Service + + Tracing Tracing `ocisConfig:"tracing"` + Log *Log `ocisConfig:"log"` + Debug Debug `ocisConfig:"debug"` + + GRPC GRPC `ocisConfig:"grpc"` + + Datapath string `ocisConfig:"data_path" env:"STORE_DATA_PATH"` + + Context context.Context } diff --git a/store/pkg/config/debug.go b/store/pkg/config/debug.go new file mode 100644 index 000000000..a168ce846 --- /dev/null +++ b/store/pkg/config/debug.go @@ -0,0 +1,9 @@ +package config + +// Debug defines the available debug configuration. +type Debug struct { + Addr string `ocisConfig:"addr" env:"STORE_DEBUG_ADDR"` + Token string `ocisConfig:"token" env:"STORE_DEBUG_TOKEN"` + Pprof bool `ocisConfig:"pprof" env:"STORE_DEBUG_PPROF"` + Zpages bool `ocisConfig:"zpages" env:"STORE_DEBUG_ZPAGES"` +} diff --git a/store/pkg/config/defaultconfig.go b/store/pkg/config/defaultconfig.go new file mode 100644 index 000000000..66e44044d --- /dev/null +++ b/store/pkg/config/defaultconfig.go @@ -0,0 +1,32 @@ +package config + +import ( + "path" + + "github.com/owncloud/ocis/ocis-pkg/config/defaults" +) + +func DefaultConfig() *Config { + return &Config{ + Debug: Debug{ + Addr: "127.0.0.1:9464", + Token: "", + Pprof: false, + Zpages: false, + }, + GRPC: GRPC{ + Addr: "127.0.0.1:9460", + Namespace: "com.owncloud.api", + }, + Service: Service{ + Name: "store", + }, + Tracing: Tracing{ + Enabled: false, + Type: "jaeger", + Endpoint: "", + Collector: "", + }, + Datapath: path.Join(defaults.BaseDataPath(), "store"), + } +} diff --git a/store/pkg/config/grpc.go b/store/pkg/config/grpc.go new file mode 100644 index 000000000..ed87112dd --- /dev/null +++ b/store/pkg/config/grpc.go @@ -0,0 +1,7 @@ +package config + +// GRPC defines the available grpc configuration. +type GRPC struct { + Addr string `ocisConfig:"addr" env:"STORE_GRPC_ADDR"` + Namespace string +} diff --git a/store/pkg/config/log.go b/store/pkg/config/log.go new file mode 100644 index 000000000..a00934bb2 --- /dev/null +++ b/store/pkg/config/log.go @@ -0,0 +1,9 @@ +package config + +// Log defines the available log configuration. +type Log struct { + Level string `mapstructure:"level" env:"OCIS_LOG_LEVEL;STORE_LOG_LEVEL"` + Pretty bool `mapstructure:"pretty" env:"OCIS_LOG_PRETTY;STORE_LOG_PRETTY"` + Color bool `mapstructure:"color" env:"OCIS_LOG_COLOR;STORE_LOG_COLOR"` + File string `mapstructure:"file" env:"OCIS_LOG_FILE;STORE_LOG_FILE"` +} diff --git a/store/pkg/config/mappings.go b/store/pkg/config/mappings.go deleted file mode 100644 index 62eea4e71..000000000 --- a/store/pkg/config/mappings.go +++ /dev/null @@ -1,84 +0,0 @@ -package config - -import "github.com/owncloud/ocis/ocis-pkg/shared" - -// StructMappings binds a set of environment variables to a destination on cfg. Iterating over this set and editing the -// Destination value of a binding will alter the original value, as it is a pointer to its memory address. This lets -// us propagate changes easier. -func StructMappings(cfg *Config) []shared.EnvBinding { - return structMappings(cfg) -} - -// structMappings binds a set of environment variables to a destination on cfg. -func structMappings(cfg *Config) []shared.EnvBinding { - return []shared.EnvBinding{ - { - EnvVars: []string{"OCIS_LOG_LEVEL", "STORE_LOG_LEVEL"}, - Destination: &cfg.Log.Level, - }, - { - EnvVars: []string{"OCIS_LOG_PRETTY", "STORE_LOG_PRETTY"}, - Destination: &cfg.Log.Pretty, - }, - { - EnvVars: []string{"OCIS_LOG_COLOR", "STORE_LOG_COLOR"}, - Destination: &cfg.Log.Color, - }, - { - EnvVars: []string{"OCIS_LOG_FILE", "STORE_LOG_FILE"}, - Destination: &cfg.Log.File, - }, - { - EnvVars: []string{"OCIS_TRACING_ENABLED", "STORE_TRACING_ENABLED"}, - Destination: &cfg.Tracing.Enabled, - }, - { - EnvVars: []string{"OCIS_TRACING_TYPE", "STORE_TRACING_TYPE"}, - Destination: &cfg.Tracing.Type, - }, - { - EnvVars: []string{"OCIS_TRACING_ENDPOINT", "STORE_TRACING_ENDPOINT"}, - Destination: &cfg.Tracing.Endpoint, - }, - { - EnvVars: []string{"OCIS_TRACING_COLLECTOR", "STORE_TRACING_COLLECTOR"}, - Destination: &cfg.Tracing.Collector, - }, - { - EnvVars: []string{"STORE_TRACING_SERVICE"}, - Destination: &cfg.Tracing.Service, - }, - { - EnvVars: []string{"STORE_DEBUG_ADDR"}, - Destination: &cfg.Debug.Addr, - }, - { - EnvVars: []string{"STORE_DEBUG_TOKEN"}, - Destination: &cfg.Debug.Token, - }, - { - EnvVars: []string{"STORE_DEBUG_PPROF"}, - Destination: &cfg.Debug.Pprof, - }, - { - EnvVars: []string{"STORE_DEBUG_ZPAGES"}, - Destination: &cfg.Debug.Zpages, - }, - { - EnvVars: []string{"STORE_GRPC_NAMESPACE"}, - Destination: &cfg.Service.Namespace, - }, - { - EnvVars: []string{"STORE_GRPC_ADDR"}, - Destination: &cfg.GRPC.Addr, - }, - { - EnvVars: []string{"STORE_NAME"}, - Destination: &cfg.Service.Name, - }, - { - EnvVars: []string{"STORE_DATA_PATH"}, - Destination: &cfg.Datapath, - }, - } -} diff --git a/store/pkg/config/parser/parse.go b/store/pkg/config/parser/parse.go new file mode 100644 index 000000000..f3789eecb --- /dev/null +++ b/store/pkg/config/parser/parse.go @@ -0,0 +1,42 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/store/pkg/config" + + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" +) + +// ParseConfig loads accounts configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + // 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.Commons == nil { + cfg.Log = &config.Log{} + } + + // 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 + } + } + + // sanitize config + + return nil +} diff --git a/store/pkg/config/service.go b/store/pkg/config/service.go new file mode 100644 index 000000000..f98aa3d27 --- /dev/null +++ b/store/pkg/config/service.go @@ -0,0 +1,6 @@ +package config + +// Service defines the available service configuration. +type Service struct { + Name string +} diff --git a/store/pkg/config/tracing.go b/store/pkg/config/tracing.go new file mode 100644 index 000000000..7e1ba1821 --- /dev/null +++ b/store/pkg/config/tracing.go @@ -0,0 +1,9 @@ +package config + +// Tracing defines the available tracing configuration. +type Tracing struct { + Enabled bool `ocisConfig:"enabled" env:"OCIS_TRACING_ENABLED;STORE_TRACING_ENABLED"` + Type string `ocisConfig:"type" env:"OCIS_TRACING_TYPE;STORE_TRACING_TYPE"` + Endpoint string `ocisConfig:"endpoint" env:"OCIS_TRACING_ENDPOINT;STORE_TRACING_ENDPOINT"` + Collector string `ocisConfig:"collector" env:"OCIS_TRACING_COLLECTOR;STORE_TRACING_COLLECTOR"` +} diff --git a/store/pkg/logging/logging.go b/store/pkg/logging/logging.go new file mode 100644 index 000000000..c5c6d4520 --- /dev/null +++ b/store/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/store/pkg/config" +) + +// 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/store/pkg/server/debug/server.go b/store/pkg/server/debug/server.go index e3812b25d..874071c71 100644 --- a/store/pkg/server/debug/server.go +++ b/store/pkg/server/debug/server.go @@ -5,6 +5,7 @@ import ( "net/http" "github.com/owncloud/ocis/ocis-pkg/service/debug" + "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/store/pkg/config" ) @@ -15,7 +16,7 @@ func Server(opts ...Option) (*http.Server, error) { return debug.NewService( debug.Logger(options.Logger), debug.Name(options.Config.Service.Name), - debug.Version(options.Config.Service.Version), + debug.Version(version.String), debug.Address(options.Config.Debug.Addr), debug.Token(options.Config.Debug.Token), debug.Pprof(options.Config.Debug.Pprof), @@ -31,9 +32,13 @@ func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) - // TODO(tboerger): check if services are up and running + // TODO: check if services are up and running - _, _ = io.WriteString(w, http.StatusText(http.StatusOK)) + _, 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) + } } } @@ -43,8 +48,12 @@ func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) - // TODO(tboerger): check if services are up and running + // TODO: check if services are up and running - _, _ = io.WriteString(w, http.StatusText(http.StatusOK)) + _, 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/store/pkg/server/grpc/server.go b/store/pkg/server/grpc/server.go index 148de47ad..632505486 100644 --- a/store/pkg/server/grpc/server.go +++ b/store/pkg/server/grpc/server.go @@ -2,6 +2,7 @@ package grpc import ( "github.com/owncloud/ocis/ocis-pkg/service/grpc" + "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/store/pkg/proto/v0" svc "github.com/owncloud/ocis/store/pkg/service/v0" ) @@ -11,9 +12,9 @@ func Server(opts ...Option) grpc.Service { options := newOptions(opts...) service := grpc.NewService( - grpc.Namespace(options.Config.Service.Namespace), + grpc.Namespace(options.Config.GRPC.Namespace), grpc.Name(options.Config.Service.Name), - grpc.Version(options.Config.Service.Version), + grpc.Version(version.String), grpc.Context(options.Context), grpc.Address(options.Config.GRPC.Addr), grpc.Logger(options.Logger), diff --git a/store/pkg/service/v0/service.go b/store/pkg/service/v0/service.go index c1a5cc98d..b86033f1d 100644 --- a/store/pkg/service/v0/service.go +++ b/store/pkg/service/v0/service.go @@ -49,7 +49,7 @@ func New(opts ...Option) (s *Service, err error) { indexMapping.DefaultAnalyzer = keyword.Name s = &Service{ - id: cfg.Service.Namespace + "." + cfg.Service.Name, + id: cfg.GRPC.Namespace + "." + cfg.Service.Name, log: logger, Config: cfg, } diff --git a/store/pkg/tracing/tracing.go b/store/pkg/tracing/tracing.go index b6f66479f..54eef8ca3 100644 --- a/store/pkg/tracing/tracing.go +++ b/store/pkg/tracing/tracing.go @@ -14,7 +14,7 @@ var ( func Configure(cfg *config.Config) error { var err error if cfg.Tracing.Enabled { - if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, "store", cfg.Tracing.Type); err != nil { + if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { return err } } diff --git a/thumbnails/pkg/command/health.go b/thumbnails/pkg/command/health.go index 6b5c84939..7eb462475 100644 --- a/thumbnails/pkg/command/health.go +++ b/thumbnails/pkg/command/health.go @@ -5,19 +5,22 @@ import ( "net/http" "github.com/owncloud/ocis/thumbnails/pkg/config" + "github.com/owncloud/ocis/thumbnails/pkg/config/parser" + "github.com/owncloud/ocis/thumbnails/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", + Name: "health", + Usage: "check health status", + Category: "info", Before: func(c *cli.Context) error { - return ParseConfig(c, cfg) + return parser.ParseConfig(cfg) }, Action: func(c *cli.Context) error { - logger := NewLogger(cfg) + logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( fmt.Sprintf( @@ -34,7 +37,7 @@ func Health(cfg *config.Config) *cli.Command { defer resp.Body.Close() - if resp.StatusCode != 200 { + if resp.StatusCode != http.StatusOK { logger.Fatal(). Int("code", resp.StatusCode). Msg("Health seems to be in bad state") diff --git a/thumbnails/pkg/command/root.go b/thumbnails/pkg/command/root.go index 5136f0df0..83b311196 100644 --- a/thumbnails/pkg/command/root.go +++ b/thumbnails/pkg/command/root.go @@ -4,92 +4,43 @@ import ( "context" "os" - "github.com/owncloud/ocis/ocis-pkg/shared" - + "github.com/owncloud/ocis/ocis-pkg/clihelper" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/thumbnails/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-thumbnails command. func Execute(cfg *config.Config) error { - app := &cli.App{ + app := clihelper.DefaultApp(&cli.App{ Name: "ocis-thumbnails", - Version: version.String, Usage: "Example usage", - Compiled: version.Compiled(), - - Authors: []*cli.Author{ - { - Name: "ownCloud GmbH", - Email: "support@owncloud.com", - }, - }, - - Before: func(c *cli.Context) error { - cfg.Server.Version = version.String - return nil - }, - - Commands: []*cli.Command{ - Server(cfg), - Health(cfg), - PrintVersion(cfg), - }, - } + Commands: GetCommands(cfg), + }) cli.HelpFlag = &cli.BoolFlag{ Name: "help,h", Usage: "Show the help", } - cli.VersionFlag = &cli.BoolFlag{ - Name: "version,v", - Usage: "Print the version", - } - return app.Run(os.Args) } -// NewLogger initializes a service-specific logger instance. -func NewLogger(cfg *config.Config) log.Logger { - return log.NewLogger( - log.Name("thumbnails"), - log.Level(cfg.Log.Level), - log.Pretty(cfg.Log.Pretty), - log.Color(cfg.Log.Color), - log.File(cfg.Log.File), - ) -} - -// ParseConfig loads configuration from Viper known paths. -// ParseConfig loads glauth configuration from known paths. -func ParseConfig(c *cli.Context, cfg *config.Config) error { - conf, err := ociscfg.BindSourcesToStructs("thumbnails", cfg) - if err != nil { - return err - } - - // 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 = &shared.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.Commons == nil { - cfg.Log = &shared.Log{} - } - - conf.LoadOSEnv(config.GetEnv(cfg), false) - bindings := config.StructMappings(cfg) - return ociscfg.BindEnv(conf, bindings) -} - // SutureService allows for the thumbnails command to be embedded and supervised by a suture supervisor tree. type SutureService struct { cfg *config.Config diff --git a/thumbnails/pkg/command/server.go b/thumbnails/pkg/command/server.go index fc7ec10a6..f44b230d3 100644 --- a/thumbnails/pkg/command/server.go +++ b/thumbnails/pkg/command/server.go @@ -5,7 +5,10 @@ import ( "fmt" "github.com/oklog/run" + "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/thumbnails/pkg/config" + "github.com/owncloud/ocis/thumbnails/pkg/config/parser" + "github.com/owncloud/ocis/thumbnails/pkg/logging" "github.com/owncloud/ocis/thumbnails/pkg/metrics" "github.com/owncloud/ocis/thumbnails/pkg/server/debug" "github.com/owncloud/ocis/thumbnails/pkg/server/grpc" @@ -16,17 +19,16 @@ import ( // Server is the entrypoint for the server command. func Server(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "server", - Usage: "Start integrated server", - Before: func(ctx *cli.Context) error { - if err := ParseConfig(ctx, cfg); err != nil { - return err - } - return nil + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + return parser.ParseConfig(cfg) }, Action: func(c *cli.Context) error { - logger := NewLogger(cfg) - if err := tracing.Configure(cfg); err != nil { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg) + if err != nil { return err } @@ -43,15 +45,15 @@ func Server(cfg *config.Config) *cli.Command { defer cancel() - metrics.BuildInfo.WithLabelValues(cfg.Server.Version).Set(1) + metrics.BuildInfo.WithLabelValues(version.String).Set(1) service := grpc.NewService( grpc.Logger(logger), grpc.Context(ctx), grpc.Config(cfg), - grpc.Name(cfg.Server.Name), - grpc.Namespace(cfg.Server.Namespace), - grpc.Address(cfg.Server.Address), + grpc.Name(cfg.Service.Name), + grpc.Namespace(cfg.GRPC.Namespace), + grpc.Address(cfg.GRPC.Addr), grpc.Metrics(metrics), ) diff --git a/thumbnails/pkg/command/version.go b/thumbnails/pkg/command/version.go index df3c0230f..b909db37d 100644 --- a/thumbnails/pkg/command/version.go +++ b/thumbnails/pkg/command/version.go @@ -5,30 +5,33 @@ import ( "os" "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" tw "github.com/olekukonko/tablewriter" "github.com/owncloud/ocis/thumbnails/pkg/config" "github.com/urfave/cli/v2" ) -// PrintVersion prints the service versions of all running instances. -func PrintVersion(cfg *config.Config) *cli.Command { +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "version", - Usage: "Print the versions of the running instances", - Before: func(c *cli.Context) error { - return ParseConfig(c, cfg) - }, + 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.Server.Namespace + "." + cfg.Server.Name) + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) if err != nil { - fmt.Println(fmt.Errorf("could not get thumbnails services from the registry: %v", err)) + 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 thumbnails service found.") + fmt.Println("No running " + cfg.Service.Name + " service found.") return nil } diff --git a/thumbnails/pkg/config/config.go b/thumbnails/pkg/config/config.go index 5126a2d60..43071171e 100644 --- a/thumbnails/pkg/config/config.go +++ b/thumbnails/pkg/config/config.go @@ -2,55 +2,30 @@ package config import ( "context" - "path" - "github.com/owncloud/ocis/ocis-pkg/config/defaults" "github.com/owncloud/ocis/ocis-pkg/shared" ) -// Debug defines the available debug configuration. -type Debug struct { - Addr string `ocisConfig:"addr"` - Token string `ocisConfig:"token"` - Pprof bool `ocisConfig:"pprof"` - Zpages bool `ocisConfig:"zpages"` -} - -// Server defines the available server configuration. -type Server struct { - Name string `ocisConfig:"name"` - Namespace string `ocisConfig:"namespace"` - Address string `ocisConfig:"address"` - Version string `ocisConfig:"version"` -} - -// Tracing defines the available tracing configuration. -type Tracing struct { - Enabled bool `ocisConfig:"enabled"` - Type string `ocisConfig:"type"` - Endpoint string `ocisConfig:"endpoint"` - Collector string `ocisConfig:"collector"` - Service string `ocisConfig:"service"` -} - // Config combines all available configuration parts. type Config struct { *shared.Commons - File string `ocisConfig:"file"` - Log *shared.Log `ocisConfig:"log"` - Debug Debug `ocisConfig:"debug"` - Server Server `ocisConfig:"server"` - Tracing Tracing `ocisConfig:"tracing"` - Thumbnail Thumbnail `ocisConfig:"thumbnail"` + Service Service - Context context.Context - Supervised bool + Tracing Tracing `ocisConfig:"tracing"` + Log *Log `ocisConfig:"log"` + Debug Debug `ocisConfig:"debug"` + + GRPC GRPC `ocisConfig:"grpc"` + + Thumbnail Thumbnail `ocisConfig:"thumbnail"` + + Context context.Context } // FileSystemStorage defines the available filesystem storage configuration. type FileSystemStorage struct { - RootDirectory string `ocisConfig:"root_directory"` + RootDirectory string `ocisConfig:"root_directory" env:"THUMBNAILS_FILESYSTEMSTORAGE_ROOT"` } // FileSystemSource defines the available filesystem source configuration. @@ -62,47 +37,9 @@ type FileSystemSource struct { type Thumbnail struct { Resolutions []string `ocisConfig:"resolutions"` FileSystemStorage FileSystemStorage `ocisConfig:"filesystem_storage"` - WebdavAllowInsecure bool `ocisConfig:"webdav_allow_insecure"` - CS3AllowInsecure bool `ocisConfig:"cs3_allow_insecure"` - RevaGateway string `ocisConfig:"reva_gateway"` - WebdavNamespace string `ocisConfig:"webdav_namespace"` - FontMapFile string `ocisConfig:"font_map_file"` -} - -// New initializes a new configuration with or without defaults. -func New() *Config { - return &Config{} -} - -func DefaultConfig() *Config { - return &Config{ - Debug: Debug{ - Addr: "127.0.0.1:9189", - Token: "", - Pprof: false, - Zpages: false, - }, - Server: Server{ - Name: "thumbnails", - Namespace: "com.owncloud.api", - Address: "127.0.0.1:9185", - }, - Tracing: Tracing{ - Enabled: false, - Type: "jaeger", - Endpoint: "", - Collector: "", - Service: "thumbnails", - }, - Thumbnail: Thumbnail{ - Resolutions: []string{"16x16", "32x32", "64x64", "128x128", "1920x1080", "3840x2160", "7680x4320"}, - FileSystemStorage: FileSystemStorage{ - RootDirectory: path.Join(defaults.BaseDataPath(), "thumbnails"), - }, - WebdavAllowInsecure: true, - RevaGateway: "127.0.0.1:9142", - WebdavNamespace: "/home", - CS3AllowInsecure: false, - }, - } + WebdavAllowInsecure bool `ocisConfig:"webdav_allow_insecure" env:"OCIS_INSECURE;THUMBNAILS_WEBDAVSOURCE_INSECURE"` + CS3AllowInsecure bool `ocisConfig:"cs3_allow_insecure" env:"OCIS_INSECURE;THUMBNAILS_CS3SOURCE_INSECURE"` + RevaGateway string `ocisConfig:"reva_gateway" env:"REVA_GATEWAY"` + WebdavNamespace string `ocisConfig:"webdav_namespace" env:"STORAGE_WEBDAV_NAMESPACE"` + FontMapFile string `ocisConfig:"font_map_file" env:"THUMBNAILS_TXT_FONTMAP_FILE"` } diff --git a/thumbnails/pkg/config/debug.go b/thumbnails/pkg/config/debug.go new file mode 100644 index 000000000..f1f3a01e1 --- /dev/null +++ b/thumbnails/pkg/config/debug.go @@ -0,0 +1,9 @@ +package config + +// Debug defines the available debug configuration. +type Debug struct { + Addr string `ocisConfig:"addr" env:"THUMBNAILS_DEBUG_ADDR"` + Token string `ocisConfig:"token" env:"THUMBNAILS_DEBUG_TOKEN"` + Pprof bool `ocisConfig:"pprof" env:"THUMBNAILS_DEBUG_PPROF"` + Zpages bool `ocisConfig:"zpages" env:"THUMBNAILS_DEBUG_ZPAGES"` +} diff --git a/thumbnails/pkg/config/defaultconfig.go b/thumbnails/pkg/config/defaultconfig.go new file mode 100644 index 000000000..7da4bac4f --- /dev/null +++ b/thumbnails/pkg/config/defaultconfig.go @@ -0,0 +1,41 @@ +package config + +import ( + "path" + + "github.com/owncloud/ocis/ocis-pkg/config/defaults" +) + +func DefaultConfig() *Config { + return &Config{ + Debug: Debug{ + Addr: "127.0.0.1:9189", + Token: "", + Pprof: false, + Zpages: false, + }, + GRPC: GRPC{ + Addr: "127.0.0.1:9185", + Namespace: "com.owncloud.api", + }, + Service: Service{ + Name: "thumbnails", + }, + Tracing: Tracing{ + Enabled: false, + Type: "jaeger", + Endpoint: "", + Collector: "", + }, + Thumbnail: Thumbnail{ + Resolutions: []string{"16x16", "32x32", "64x64", "128x128", "1920x1080", "3840x2160", "7680x4320"}, + FileSystemStorage: FileSystemStorage{ + RootDirectory: path.Join(defaults.BaseDataPath(), "thumbnails"), + }, + WebdavAllowInsecure: true, + RevaGateway: "127.0.0.1:9142", + WebdavNamespace: "/home", + CS3AllowInsecure: false, + }, + } +} diff --git a/thumbnails/pkg/config/grpc.go b/thumbnails/pkg/config/grpc.go new file mode 100644 index 000000000..9682ed12c --- /dev/null +++ b/thumbnails/pkg/config/grpc.go @@ -0,0 +1,7 @@ +package config + +// GRPC defines the available grpc configuration. +type GRPC struct { + Addr string `ocisConfig:"addr" env:"THUMBNAILS_GRPC_ADDR"` + Namespace string +} diff --git a/thumbnails/pkg/config/log.go b/thumbnails/pkg/config/log.go new file mode 100644 index 000000000..a9b19f070 --- /dev/null +++ b/thumbnails/pkg/config/log.go @@ -0,0 +1,9 @@ +package config + +// Log defines the available log configuration. +type Log struct { + Level string `mapstructure:"level" env:"OCIS_LOG_LEVEL;THUMBNAILS_LOG_LEVEL"` + Pretty bool `mapstructure:"pretty" env:"OCIS_LOG_PRETTY;THUMBNAILS_LOG_PRETTY"` + Color bool `mapstructure:"color" env:"OCIS_LOG_COLOR;THUMBNAILS_LOG_COLOR"` + File string `mapstructure:"file" env:"OCIS_LOG_FILE;THUMBNAILS_LOG_FILE"` +} diff --git a/thumbnails/pkg/config/mappings.go b/thumbnails/pkg/config/mappings.go deleted file mode 100644 index 6f6ef0d2f..000000000 --- a/thumbnails/pkg/config/mappings.go +++ /dev/null @@ -1,119 +0,0 @@ -package config - -import "github.com/owncloud/ocis/ocis-pkg/shared" - -// GetEnv fetches a list of known env variables for this extension. It is to be used by gookit, as it provides a list -// with all the environment variables an extension supports. -func GetEnv(cfg *Config) []string { - var r = make([]string, len(structMappings(cfg))) - for i := range structMappings(cfg) { - r = append(r, structMappings(cfg)[i].EnvVars...) - } - - return r -} - -// StructMappings binds a set of environment variables to a destination on cfg. Iterating over this set and editing the -// Destination value of a binding will alter the original value, as it is a pointer to its memory address. This lets -// us propagate changes easier. -func StructMappings(cfg *Config) []shared.EnvBinding { - return structMappings(cfg) -} - -// structMappings binds a set of environment variables to a destination on cfg. -func structMappings(cfg *Config) []shared.EnvBinding { - return []shared.EnvBinding{ - { - EnvVars: []string{"OCIS_LOG_FILE", "THUMBNAILS_LOG_FILE"}, - Destination: &cfg.Log.File, - }, - { - EnvVars: []string{"OCIS_LOG_LEVEL", "THUMBNAILS_LOG_LEVEL"}, - Destination: &cfg.Log.Level, - }, - { - EnvVars: []string{"OCIS_LOG_PRETTY", "THUMBNAILS_LOG_PRETTY"}, - Destination: &cfg.Log.Pretty, - }, - { - EnvVars: []string{"OCIS_LOG_COLOR", "THUMBNAILS_LOG_COLOR"}, - Destination: &cfg.Log.Color, - }, - { - EnvVars: []string{"THUMBNAILS_CONFIG_FILE"}, - Destination: &cfg.File, - }, - { - EnvVars: []string{"OCIS_TRACING_ENABLED", "THUMBNAILS_TRACING_ENABLED"}, - Destination: &cfg.Tracing.Enabled, - }, - { - EnvVars: []string{"OCIS_TRACING_TYPE", "THUMBNAILS_TRACING_TYPE"}, - Destination: &cfg.Tracing.Type, - }, - { - EnvVars: []string{"OCIS_TRACING_ENDPOINT", "THUMBNAILS_TRACING_ENDPOINT"}, - Destination: &cfg.Tracing.Endpoint, - }, - { - EnvVars: []string{"OCIS_TRACING_COLLECTOR", "THUMBNAILS_TRACING_COLLECTOR"}, - Destination: &cfg.Tracing.Collector, - }, - { - EnvVars: []string{"THUMBNAILS_TRACING_SERVICE"}, - Destination: &cfg.Tracing.Service, - }, - { - EnvVars: []string{"THUMBNAILS_DEBUG_ADDR"}, - Destination: &cfg.Debug.Addr, - }, - { - EnvVars: []string{"THUMBNAILS_DEBUG_TOKEN"}, - Destination: &cfg.Debug.Token, - }, - { - EnvVars: []string{"THUMBNAILS_DEBUG_PPROF"}, - Destination: &cfg.Debug.Pprof, - }, - { - EnvVars: []string{"THUMBNAILS_DEBUG_ZPAGES"}, - Destination: &cfg.Debug.Zpages, - }, - { - EnvVars: []string{"THUMBNAILS_GRPC_NAME"}, - Destination: &cfg.Server.Name, - }, - { - EnvVars: []string{"THUMBNAILS_GRPC_ADDR"}, - Destination: &cfg.Server.Address, - }, - { - EnvVars: []string{"THUMBNAILS_GRPC_NAMESPACE"}, - Destination: &cfg.Server.Namespace, - }, - { - EnvVars: []string{"THUMBNAILS_TXT_FONTMAP_FILE"}, - Destination: &cfg.Thumbnail.FontMapFile, - }, - { - EnvVars: []string{"THUMBNAILS_FILESYSTEMSTORAGE_ROOT"}, - Destination: &cfg.Thumbnail.FileSystemStorage.RootDirectory, - }, - { - EnvVars: []string{"REVA_GATEWAY"}, - Destination: &cfg.Thumbnail.RevaGateway, - }, - { - EnvVars: []string{"OCIS_INSECURE", "THUMBNAILS_WEBDAVSOURCE_INSECURE"}, - Destination: &cfg.Thumbnail.WebdavAllowInsecure, - }, - { - EnvVars: []string{"OCIS_INSECURE", "THUMBNAILS_CS3SOURCE_INSECURE"}, - Destination: &cfg.Thumbnail.CS3AllowInsecure, - }, - { - EnvVars: []string{"STORAGE_WEBDAV_NAMESPACE"}, - Destination: &cfg.Thumbnail.WebdavNamespace, - }, - } -} diff --git a/thumbnails/pkg/config/parser/parse.go b/thumbnails/pkg/config/parser/parse.go new file mode 100644 index 000000000..3591aea8a --- /dev/null +++ b/thumbnails/pkg/config/parser/parse.go @@ -0,0 +1,42 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/thumbnails/pkg/config" + + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" +) + +// ParseConfig loads accounts configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + // 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.Commons == nil { + cfg.Log = &config.Log{} + } + + // 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 + } + } + + // sanitize config + + return nil +} diff --git a/thumbnails/pkg/config/service.go b/thumbnails/pkg/config/service.go new file mode 100644 index 000000000..f98aa3d27 --- /dev/null +++ b/thumbnails/pkg/config/service.go @@ -0,0 +1,6 @@ +package config + +// Service defines the available service configuration. +type Service struct { + Name string +} diff --git a/thumbnails/pkg/config/tracing.go b/thumbnails/pkg/config/tracing.go new file mode 100644 index 000000000..bbb13435c --- /dev/null +++ b/thumbnails/pkg/config/tracing.go @@ -0,0 +1,9 @@ +package config + +// Tracing defines the available tracing configuration. +type Tracing struct { + Enabled bool `ocisConfig:"enabled" env:"OCIS_TRACING_ENABLED;THUMBNAILS_TRACING_ENABLED"` + Type string `ocisConfig:"type" env:"OCIS_TRACING_TYPE;THUMBNAILS_TRACING_TYPE"` + Endpoint string `ocisConfig:"endpoint" env:"OCIS_TRACING_ENDPOINT;THUMBNAILS_TRACING_ENDPOINT"` + Collector string `ocisConfig:"collector" env:"OCIS_TRACING_COLLECTOR;THUMBNAILS_TRACING_COLLECTOR"` +} diff --git a/thumbnails/pkg/logging/logging.go b/thumbnails/pkg/logging/logging.go new file mode 100644 index 000000000..41d558326 --- /dev/null +++ b/thumbnails/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/thumbnails/pkg/config" +) + +// 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/thumbnails/pkg/proto/v0/thumbnails.pb.micro_test.go b/thumbnails/pkg/proto/v0/thumbnails.pb.micro_test.go index d3298d62c..a435cdf36 100644 --- a/thumbnails/pkg/proto/v0/thumbnails.pb.micro_test.go +++ b/thumbnails/pkg/proto/v0/thumbnails.pb.micro_test.go @@ -26,7 +26,7 @@ func init() { grpc.Address("localhost:9992"), ) - cfg := config.New() + cfg := config.DefaultConfig() cfg.Thumbnail.Resolutions = []string{"16x16", "32x32", "64x64", "128x128"} wd, _ := os.Getwd() @@ -44,17 +44,17 @@ func init() { if err != nil { log.Fatalf("could not register ThumbnailHandler: %v", err) } - if err := service.Server().Start(); err != nil { - log.Fatalf("could not start server: %v", err) - } + if err := service.Server().Start(); err != nil { + log.Fatalf("could not start server: %v", err) + } } func TestGetThumbnailInvalidImage(t *testing.T) { req := proto.GetThumbnailRequest{ - Filepath: "invalid.png", + Filepath: "invalid.png", ThumbnailType: proto.GetThumbnailRequest_PNG, - Height: 32, - Width: 32, + Height: 32, + Width: 32, } client := service.Client() cl := proto.NewThumbnailService("com.owncloud.api.thumbnails", client) diff --git a/thumbnails/pkg/server/debug/server.go b/thumbnails/pkg/server/debug/server.go index 4db1f7236..dd7b40a1f 100644 --- a/thumbnails/pkg/server/debug/server.go +++ b/thumbnails/pkg/server/debug/server.go @@ -5,6 +5,7 @@ import ( "net/http" "github.com/owncloud/ocis/ocis-pkg/service/debug" + "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/thumbnails/pkg/config" ) @@ -14,8 +15,8 @@ func Server(opts ...Option) (*http.Server, error) { return debug.NewService( debug.Logger(options.Logger), - debug.Name(options.Config.Server.Name), - debug.Version(options.Config.Server.Version), + 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), @@ -25,21 +26,33 @@ func Server(opts ...Option) (*http.Server, error) { ), 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) - if _, err := io.WriteString(w, http.StatusText(http.StatusOK)); err != nil { + + // 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) - if _, err := io.WriteString(w, http.StatusText(http.StatusOK)); err != nil { + + // 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/thumbnails/pkg/server/grpc/server.go b/thumbnails/pkg/server/grpc/server.go index 0c905c06e..b90dc699e 100644 --- a/thumbnails/pkg/server/grpc/server.go +++ b/thumbnails/pkg/server/grpc/server.go @@ -22,7 +22,7 @@ func NewService(opts ...Option) grpc.Service { grpc.Address(options.Address), grpc.Context(options.Context), grpc.Flags(options.Flags...), - grpc.Version(options.Config.Server.Version), + grpc.Version(version.String), ) tconf := options.Config.Thumbnail gc, err := pool.GetGatewayServiceClient(tconf.RevaGateway) diff --git a/thumbnails/pkg/service/v0/service.go b/thumbnails/pkg/service/v0/service.go index 8914aebbc..3c1e456db 100644 --- a/thumbnails/pkg/service/v0/service.go +++ b/thumbnails/pkg/service/v0/service.go @@ -30,7 +30,7 @@ func NewService(opts ...Option) v0proto.ThumbnailServiceHandler { logger.Fatal().Err(err).Msg("resolutions not configured correctly") } svc := Thumbnail{ - serviceID: options.Config.Server.Namespace + "." + options.Config.Server.Name, + serviceID: options.Config.GRPC.Namespace + "." + options.Config.Service.Name, webdavNamespace: options.Config.Thumbnail.WebdavNamespace, manager: thumbnail.NewSimpleManager( resolutions, diff --git a/thumbnails/pkg/tracing/tracing.go b/thumbnails/pkg/tracing/tracing.go index 6bbd7afc8..16db3bb6f 100644 --- a/thumbnails/pkg/tracing/tracing.go +++ b/thumbnails/pkg/tracing/tracing.go @@ -14,7 +14,7 @@ var ( func Configure(cfg *config.Config) error { var err error if cfg.Tracing.Enabled { - if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, "thumbnails", cfg.Tracing.Type); err != nil { + if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { return err } } diff --git a/web/pkg/command/health.go b/web/pkg/command/health.go index 97206879b..38713676a 100644 --- a/web/pkg/command/health.go +++ b/web/pkg/command/health.go @@ -5,19 +5,22 @@ import ( "net/http" "github.com/owncloud/ocis/web/pkg/config" + "github.com/owncloud/ocis/web/pkg/config/parser" + "github.com/owncloud/ocis/web/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", + Name: "health", + Usage: "check health status", + Category: "info", Before: func(c *cli.Context) error { - return ParseConfig(c, cfg) + return parser.ParseConfig(cfg) }, Action: func(c *cli.Context) error { - logger := NewLogger(cfg) + logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( fmt.Sprintf( @@ -34,7 +37,7 @@ func Health(cfg *config.Config) *cli.Command { defer resp.Body.Close() - if resp.StatusCode != 200 { + if resp.StatusCode != http.StatusOK { logger.Fatal(). Int("code", resp.StatusCode). Msg("Health seems to be in bad state") diff --git a/web/pkg/command/root.go b/web/pkg/command/root.go index 8c99ecad6..2dbe61c1b 100644 --- a/web/pkg/command/root.go +++ b/web/pkg/command/root.go @@ -4,83 +4,43 @@ import ( "context" "os" - "github.com/owncloud/ocis/ocis-pkg/shared" - + "github.com/owncloud/ocis/ocis-pkg/clihelper" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/web/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 web command. func Execute(cfg *config.Config) error { - app := &cli.App{ + app := clihelper.DefaultApp(&cli.App{ Name: "web", - Version: version.String, Usage: "Serve ownCloud Web for oCIS", - Compiled: version.Compiled(), - Authors: []*cli.Author{ - { - Name: "ownCloud GmbH", - Email: "support@owncloud.com", - }, - }, - Commands: []*cli.Command{ - Server(cfg), - Health(cfg), - }, - } + Commands: GetCommands(cfg), + }) cli.HelpFlag = &cli.BoolFlag{ Name: "help,h", Usage: "Show the help", } - cli.VersionFlag = &cli.BoolFlag{ - Name: "version,v", - Usage: "Print the version", - } - return app.Run(os.Args) } -// NewLogger initializes a service-specific logger instance. -func NewLogger(cfg *config.Config) log.Logger { - return log.NewLogger( - log.Name("web"), - log.Level(cfg.Log.Level), - log.Pretty(cfg.Log.Pretty), - log.Color(cfg.Log.Color), - log.File(cfg.Log.File), - ) -} - -// ParseConfig loads graph configuration from known paths. -func ParseConfig(c *cli.Context, cfg *config.Config) error { - conf, err := ociscfg.BindSourcesToStructs("web", cfg) - if err != nil { - return err - } - - // 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 = &shared.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.Commons == nil { - cfg.Log = &shared.Log{} - } - - conf.LoadOSEnv(config.GetEnv(cfg), false) - bindings := config.StructMappings(cfg) - return ociscfg.BindEnv(conf, bindings) -} - // SutureService allows for the web command to be embedded and supervised by a suture supervisor tree. type SutureService struct { cfg *config.Config diff --git a/web/pkg/command/server.go b/web/pkg/command/server.go index c8b8fd107..f68d16a5d 100644 --- a/web/pkg/command/server.go +++ b/web/pkg/command/server.go @@ -3,11 +3,13 @@ package command import ( "context" "encoding/json" + "fmt" "io/ioutil" - "strings" "github.com/oklog/run" "github.com/owncloud/ocis/web/pkg/config" + "github.com/owncloud/ocis/web/pkg/config/parser" + "github.com/owncloud/ocis/web/pkg/logging" "github.com/owncloud/ocis/web/pkg/metrics" "github.com/owncloud/ocis/web/pkg/server/debug" "github.com/owncloud/ocis/web/pkg/server/http" @@ -18,28 +20,16 @@ import ( // Server is the entrypoint for the server command. func Server(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "server", - Usage: "Start integrated server", - Before: func(ctx *cli.Context) error { - if cfg.HTTP.Root != "/" { - cfg.HTTP.Root = strings.TrimRight(cfg.HTTP.Root, "/") - } - - if err := ParseConfig(ctx, cfg); err != nil { - return err - } - - // build well known openid-configuration endpoint if it is not set - if cfg.Web.Config.OpenIDConnect.MetadataURL == "" { - cfg.Web.Config.OpenIDConnect.MetadataURL = strings.TrimRight(cfg.Web.Config.OpenIDConnect.Authority, "/") + "/.well-known/openid-configuration" - } - - return nil + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + return parser.ParseConfig(cfg) }, Action: func(c *cli.Context) error { - logger := NewLogger(cfg) - - if err := tracing.Configure(cfg); err != nil { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg) + if err != nil { return err } diff --git a/web/pkg/command/version.go b/web/pkg/command/version.go new file mode 100644 index 000000000..783dd284e --- /dev/null +++ b/web/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/web/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/web/pkg/config/config.go b/web/pkg/config/config.go index 0054c9331..13f67adfe 100644 --- a/web/pkg/config/config.go +++ b/web/pkg/config/config.go @@ -6,41 +6,35 @@ import ( "github.com/owncloud/ocis/ocis-pkg/shared" ) -// Debug defines the available debug configuration. -type Debug struct { - Addr string `ocisConfig:"addr"` - Token string `ocisConfig:"token"` - Pprof bool `ocisConfig:"pprof"` - Zpages bool `ocisConfig:"zpages"` -} +// Config combines all available configuration parts. +type Config struct { + *shared.Commons -// HTTP defines the available http configuration. -type HTTP struct { - Addr string `ocisConfig:"addr"` - Root string `ocisConfig:"root"` - Namespace string `ocisConfig:"namespace"` - CacheTTL int `ocisConfig:"cache_ttl"` -} + Service Service -// Tracing defines the available tracing configuration. -type Tracing struct { - Enabled bool `ocisConfig:"enabled"` - Type string `ocisConfig:"type"` - Endpoint string `ocisConfig:"endpoint"` - Collector string `ocisConfig:"collector"` - Service string `ocisConfig:"service"` + Tracing Tracing `ocisConfig:"tracing"` + Log *Log `ocisConfig:"log"` + Debug Debug `ocisConfig:"debug"` + + HTTP HTTP `ocisConfig:"http"` + + Asset Asset `ocisConfig:"asset"` + File string `ocisConfig:"file" env:"WEB_UI_CONFIG"` // TODO: rename this to a more self explaining string + Web Web `ocisConfig:"web"` + + Context context.Context } // Asset defines the available asset configuration. type Asset struct { - Path string `ocisConfig:"path"` + Path string `ocisConfig:"path" env:"WEB_ASSET_PATH"` } // WebConfig defines the available web configuration for a dynamically rendered config.json. type WebConfig struct { - Server string `json:"server,omitempty" ocisConfig:"server"` - Theme string `json:"theme,omitempty" ocisConfig:"theme"` - Version string `json:"version,omitempty" ocisConfig:"version"` + Server string `json:"server,omitempty" ocisConfig:"server" env:"OCIS_URL;WEB_UI_CONFIG_SERVER"` + Theme string `json:"theme,omitempty" ocisConfig:"theme" env:""` + Version string `json:"version,omitempty" ocisConfig:"version" env:"WEB_UI_CONFIG_VERSION"` OpenIDConnect OIDC `json:"openIdConnect,omitempty" ocisConfig:"oids"` Apps []string `json:"apps" ocisConfig:"apps"` ExternalApps []ExternalApp `json:"external_apps,omitempty" ocisConfig:"external_apps"` @@ -49,11 +43,11 @@ type WebConfig struct { // OIDC defines the available oidc configuration type OIDC struct { - MetadataURL string `json:"metadata_url,omitempty" ocisConfig:"metadata_url"` - Authority string `json:"authority,omitempty" ocisConfig:"authority"` - ClientID string `json:"client_id,omitempty" ocisConfig:"client_id"` - ResponseType string `json:"response_type,omitempty" ocisConfig:"response_type"` - Scope string `json:"scope,omitempty" ocisConfig:"scope"` + MetadataURL string `json:"metadata_url,omitempty" ocisConfig:"metadata_url" env:"WEB_OIDC_METADATA_URL"` + Authority string `json:"authority,omitempty" ocisConfig:"authority" env:"OCIS_URL;WEB_OIDC_AUTHORITY"` + ClientID string `json:"client_id,omitempty" ocisConfig:"client_id" env:"WEB_OIDC_CLIENT_ID"` + ResponseType string `json:"response_type,omitempty" ocisConfig:"response_type" env:"WEB_OIDC_RESPONSE_TYPE"` + Scope string `json:"scope,omitempty" ocisConfig:"scope" env:"WEB_OIDC_SCOPE"` } // ExternalApp defines an external web app. @@ -73,79 +67,13 @@ type ExternalApp struct { // ExternalAppConfig defines an external web app configuration. type ExternalAppConfig struct { - URL string `json:"url,omitempty" ocisConfig:"url"` + URL string `json:"url,omitempty" ocisConfig:"url" env:""` } // Web defines the available web configuration. type Web struct { - Path string `ocisConfig:"path"` - ThemeServer string `ocisConfig:"theme_server"` // used to build Theme in WebConfig - ThemePath string `ocisConfig:"theme_path"` // used to build Theme in WebConfig + Path string `ocisConfig:"path" env:"WEB_UI_PATH"` + ThemeServer string `ocisConfig:"theme_server" env:"OCIS_URL;WEB_UI_THEME_SERVER"` // used to build Theme in WebConfig + ThemePath string `ocisConfig:"theme_path" env:"WEB_UI_THEME_PATH"` // used to build Theme in WebConfig Config WebConfig `ocisConfig:"config"` } - -// Config combines all available configuration parts. -type Config struct { - *shared.Commons - - File string `ocisConfig:"file"` - Log *shared.Log `ocisConfig:"log"` - Debug Debug `ocisConfig:"debug"` - HTTP HTTP `ocisConfig:"http"` - Tracing Tracing `ocisConfig:"tracing"` - Asset Asset `ocisConfig:"asset"` - Web Web `ocisConfig:"web"` - - Context context.Context - Supervised bool -} - -// New initializes a new configuration with or without defaults. -func New() *Config { - return &Config{} -} - -func DefaultConfig() *Config { - return &Config{ - Debug: Debug{ - Addr: "127.0.0.1:9104", - Token: "", - Pprof: false, - Zpages: false, - }, - HTTP: HTTP{ - Addr: "127.0.0.1:9100", - Root: "/", - Namespace: "com.owncloud.web", - CacheTTL: 604800, // 7 days - }, - Tracing: Tracing{ - Enabled: false, - Type: "jaeger", - Endpoint: "", - Collector: "", - Service: "web", - }, - Asset: Asset{ - Path: "", - }, - Web: Web{ - Path: "", - ThemeServer: "https://localhost:9200", - ThemePath: "/themes/owncloud/theme.json", - Config: WebConfig{ - Server: "https://localhost:9200", - Theme: "", - Version: "0.1.0", - OpenIDConnect: OIDC{ - MetadataURL: "", - Authority: "https://localhost:9200", - ClientID: "web", - ResponseType: "code", - Scope: "openid profile email", - }, - Apps: []string{"files", "search", "media-viewer", "external"}, - }, - }, - } -} diff --git a/web/pkg/config/debug.go b/web/pkg/config/debug.go new file mode 100644 index 000000000..d4dda707d --- /dev/null +++ b/web/pkg/config/debug.go @@ -0,0 +1,9 @@ +package config + +// Debug defines the available debug configuration. +type Debug struct { + Addr string `ocisConfig:"addr" env:"WEB_DEBUG_ADDR"` + Token string `ocisConfig:"token" env:"WEB_DEBUG_TOKEN"` + Pprof bool `ocisConfig:"pprof" env:"WEB_DEBUG_PPROF"` + Zpages bool `ocisConfig:"zpages" env:"WEB_DEBUG_ZPAGES"` +} diff --git a/web/pkg/config/defaultconfig.go b/web/pkg/config/defaultconfig.go new file mode 100644 index 000000000..42f22beda --- /dev/null +++ b/web/pkg/config/defaultconfig.go @@ -0,0 +1,48 @@ +package config + +func DefaultConfig() *Config { + return &Config{ + Debug: Debug{ + Addr: "127.0.0.1:9104", + Token: "", + Pprof: false, + Zpages: false, + }, + HTTP: HTTP{ + Addr: "127.0.0.1:9100", + Root: "/", + Namespace: "com.owncloud.web", + CacheTTL: 604800, // 7 days + }, + Service: Service{ + Name: "web", + }, + Tracing: Tracing{ + Enabled: false, + Type: "jaeger", + Endpoint: "", + Collector: "", + }, + Asset: Asset{ + Path: "", + }, + Web: Web{ + Path: "", + ThemeServer: "https://localhost:9200", + ThemePath: "/themes/owncloud/theme.json", + Config: WebConfig{ + Server: "https://localhost:9200", + Theme: "", + Version: "0.1.0", + OpenIDConnect: OIDC{ + MetadataURL: "", + Authority: "https://localhost:9200", + ClientID: "web", + ResponseType: "code", + Scope: "openid profile email", + }, + Apps: []string{"files", "search", "media-viewer", "external"}, + }, + }, + } +} diff --git a/web/pkg/config/http.go b/web/pkg/config/http.go new file mode 100644 index 000000000..317b93497 --- /dev/null +++ b/web/pkg/config/http.go @@ -0,0 +1,9 @@ +package config + +// HTTP defines the available http configuration. +type HTTP struct { + Addr string `ocisConfig:"addr" env:"WEB_HTTP_ADDR"` + Namespace string + Root string `ocisConfig:"root" env:"WEB_HTTP_ROOT"` + CacheTTL int `ocisConfig:"cache_ttl" env:"WEB_CACHE_TTL"` +} diff --git a/web/pkg/config/log.go b/web/pkg/config/log.go new file mode 100644 index 000000000..1a3310728 --- /dev/null +++ b/web/pkg/config/log.go @@ -0,0 +1,9 @@ +package config + +// Log defines the available log configuration. +type Log struct { + Level string `mapstructure:"level" env:"OCIS_LOG_LEVEL;WEB_LOG_LEVEL"` + Pretty bool `mapstructure:"pretty" env:"OCIS_LOG_PRETTY;WEB_LOG_PRETTY"` + Color bool `mapstructure:"color" env:"OCIS_LOG_COLOR;WEB_LOG_COLOR"` + File string `mapstructure:"file" env:"OCIS_LOG_FILE;WEB_LOG_FILE"` +} diff --git a/web/pkg/config/mappings.go b/web/pkg/config/mappings.go deleted file mode 100644 index 3957c225e..000000000 --- a/web/pkg/config/mappings.go +++ /dev/null @@ -1,143 +0,0 @@ -package config - -import "github.com/owncloud/ocis/ocis-pkg/shared" - -// GetEnv fetches a list of known env variables for this extension. It is to be used by gookit, as it provides a list -// with all the environment variables an extension supports. -func GetEnv(cfg *Config) []string { - var r = make([]string, len(structMappings(cfg))) - for i := range structMappings(cfg) { - r = append(r, structMappings(cfg)[i].EnvVars...) - } - - return r -} - -// StructMappings binds a set of environment variables to a destination on cfg. Iterating over this set and editing the -// Destination value of a binding will alter the original value, as it is a pointer to its memory address. This lets -// us propagate changes easier. -func StructMappings(cfg *Config) []shared.EnvBinding { - return structMappings(cfg) -} - -// structMappings binds a set of environment variables to a destination on cfg. -func structMappings(cfg *Config) []shared.EnvBinding { - return []shared.EnvBinding{ - { - EnvVars: []string{"OCIS_LOG_LEVEL", "WEB_LOG_LEVEL"}, - Destination: &cfg.Log.Level, - }, - { - EnvVars: []string{"OCIS_LOG_PRETTY", "WEB_LOG_PRETTY"}, - Destination: &cfg.Log.Pretty, - }, - { - EnvVars: []string{"OCIS_LOG_COLOR", "WEB_LOG_COLOR"}, - Destination: &cfg.Log.Color, - }, - { - EnvVars: []string{"OCIS_LOG_FILE", "WEB_LOG_FILE"}, - Destination: &cfg.Log.File, - }, - { - EnvVars: []string{"WEB_CONFIG_FILE"}, - Destination: &cfg.File, - }, - { - EnvVars: []string{"OCIS_TRACING_ENABLED", "WEB_TRACING_ENABLED"}, - Destination: &cfg.Tracing.Enabled, - }, - { - EnvVars: []string{"OCIS_TRACING_TYPE", "WEB_TRACING_TYPE"}, - Destination: &cfg.Tracing.Type, - }, - { - EnvVars: []string{"OCIS_TRACING_ENDPOINT", "WEB_TRACING_ENDPOINT"}, - Destination: &cfg.Tracing.Endpoint, - }, - { - EnvVars: []string{"OCIS_TRACING_COLLECTOR", "WEB_TRACING_COLLECTOR"}, - Destination: &cfg.Tracing.Collector, - }, - { - EnvVars: []string{"WEB_TRACING_SERVICE"}, - Destination: &cfg.Tracing.Service, - }, - { - EnvVars: []string{"WEB_DEBUG_ADDR"}, - Destination: &cfg.Debug.Addr, - }, - { - EnvVars: []string{"WEB_DEBUG_TOKEN"}, - Destination: &cfg.Debug.Token, - }, - { - EnvVars: []string{"WEB_DEBUG_PPROF"}, - Destination: &cfg.Debug.Pprof, - }, - { - EnvVars: []string{"WEB_DEBUG_ZPAGES"}, - Destination: &cfg.Debug.Zpages, - }, - { - EnvVars: []string{"WEB_HTTP_ADDR"}, - Destination: &cfg.HTTP.Addr, - }, - { - EnvVars: []string{"WEB_HTTP_ROOT"}, - Destination: &cfg.HTTP.Root, - }, - { - EnvVars: []string{"WEB_NAMESPACE"}, - Destination: &cfg.HTTP.Namespace, - }, - { - EnvVars: []string{"WEB_CACHE_TTL"}, - Destination: &cfg.HTTP.CacheTTL, - }, - { - EnvVars: []string{"WEB_ASSET_PATH"}, - Destination: &cfg.Asset.Path, - }, - { - EnvVars: []string{"WEB_UI_CONFIG"}, - Destination: &cfg.Web.Path, - }, - { - EnvVars: []string{"OCIS_URL", "WEB_UI_CONFIG_SERVER"}, // WEB_UI_CONFIG_SERVER takes precedence over OCIS_URL - Destination: &cfg.Web.Config.Server, - }, - { - EnvVars: []string{"OCIS_URL", "WEB_UI_THEME_SERVER"}, // WEB_UI_THEME_SERVER takes precedence over OCIS_URL - Destination: &cfg.Web.ThemeServer, - }, - { - EnvVars: []string{"WEB_UI_THEME_PATH"}, - Destination: &cfg.Web.ThemePath, - }, - { - EnvVars: []string{"WEB_UI_CONFIG_VERSION"}, - Destination: &cfg.Web.Config.Version, - }, - { - EnvVars: []string{"WEB_OIDC_METADATA_URL"}, - Destination: &cfg.Web.Config.OpenIDConnect.MetadataURL, - }, - { - EnvVars: []string{"OCIS_URL", "WEB_OIDC_AUTHORITY"}, // WEB_OIDC_AUTHORITY takes precedence over OCIS_URL - Destination: &cfg.Web.Config.OpenIDConnect.Authority, - }, - { - EnvVars: []string{"WEB_OIDC_CLIENT_ID"}, - Destination: &cfg.Web.Config.OpenIDConnect.ClientID, - }, - { - EnvVars: []string{"WEB_OIDC_RESPONSE_TYPE"}, - Destination: &cfg.Web.Config.OpenIDConnect.ResponseType, - }, - { - EnvVars: []string{"WEB_OIDC_SCOPE"}, - Destination: &cfg.Web.Config.OpenIDConnect.Scope, - }, - } -} diff --git a/web/pkg/config/parser/parse.go b/web/pkg/config/parser/parse.go new file mode 100644 index 000000000..71870c8ce --- /dev/null +++ b/web/pkg/config/parser/parse.go @@ -0,0 +1,50 @@ +package parser + +import ( + "errors" + "strings" + + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/web/pkg/config" + + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" +) + +// ParseConfig loads accounts configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + // 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.Commons == nil { + cfg.Log = &config.Log{} + } + + // 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 + } + } + + // sanitize config + if cfg.HTTP.Root != "/" { + cfg.HTTP.Root = strings.TrimRight(cfg.HTTP.Root, "/") + } + // build well known openid-configuration endpoint if it is not set + if cfg.Web.Config.OpenIDConnect.MetadataURL == "" { + cfg.Web.Config.OpenIDConnect.MetadataURL = strings.TrimRight(cfg.Web.Config.OpenIDConnect.Authority, "/") + "/.well-known/openid-configuration" + } + + return nil +} diff --git a/web/pkg/config/service.go b/web/pkg/config/service.go new file mode 100644 index 000000000..f98aa3d27 --- /dev/null +++ b/web/pkg/config/service.go @@ -0,0 +1,6 @@ +package config + +// Service defines the available service configuration. +type Service struct { + Name string +} diff --git a/web/pkg/config/tracing.go b/web/pkg/config/tracing.go new file mode 100644 index 000000000..6c54223d6 --- /dev/null +++ b/web/pkg/config/tracing.go @@ -0,0 +1,9 @@ +package config + +// Tracing defines the available tracing configuration. +type Tracing struct { + Enabled bool `ocisConfig:"enabled" env:"OCIS_TRACING_ENABLED;WEB_TRACING_ENABLED"` + Type string `ocisConfig:"type" env:"OCIS_TRACING_TYPE;WEB_TRACING_TYPE"` + Endpoint string `ocisConfig:"endpoint" env:"OCIS_TRACING_ENDPOINT;WEB_TRACING_ENDPOINT"` + Collector string `ocisConfig:"collector" env:"OCIS_TRACING_COLLECTOR;WEB_TRACING_COLLECTOR"` +} diff --git a/web/pkg/logging/logging.go b/web/pkg/logging/logging.go new file mode 100644 index 000000000..57616a43b --- /dev/null +++ b/web/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/web/pkg/config" +) + +// 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/web/pkg/server/debug/server.go b/web/pkg/server/debug/server.go index 43c45036b..d4d7e302e 100644 --- a/web/pkg/server/debug/server.go +++ b/web/pkg/server/debug/server.go @@ -15,7 +15,7 @@ func Server(opts ...Option) (*http.Server, error) { return debug.NewService( debug.Logger(options.Logger), - debug.Name("web"), + debug.Name(options.Config.Service.Name), debug.Version(version.String), debug.Address(options.Config.Debug.Addr), debug.Token(options.Config.Debug.Token), @@ -32,9 +32,11 @@ func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) - // TODO(tboerger): check if services are up and running + // TODO: check if services are up and running - if _, err := io.WriteString(w, http.StatusText(http.StatusOK)); err != nil { + _, 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) } } @@ -46,9 +48,11 @@ func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) - // TODO(tboerger): check if services are up and running + // TODO: check if services are up and running - if _, err := io.WriteString(w, http.StatusText(http.StatusOK)); err != nil { + _, 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/web/pkg/tracing/tracing.go b/web/pkg/tracing/tracing.go index 1bd7caf66..cebc588bd 100644 --- a/web/pkg/tracing/tracing.go +++ b/web/pkg/tracing/tracing.go @@ -14,7 +14,7 @@ var ( func Configure(cfg *config.Config) error { var err error if cfg.Tracing.Enabled { - if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, "web", cfg.Tracing.Type); err != nil { + if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { return err } } diff --git a/webdav/pkg/command/health.go b/webdav/pkg/command/health.go index 58aca1e1d..94741b29a 100644 --- a/webdav/pkg/command/health.go +++ b/webdav/pkg/command/health.go @@ -5,19 +5,22 @@ import ( "net/http" "github.com/owncloud/ocis/webdav/pkg/config" + "github.com/owncloud/ocis/webdav/pkg/config/parser" + "github.com/owncloud/ocis/webdav/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", + Name: "health", + Usage: "check health status", + Category: "info", Before: func(c *cli.Context) error { - return ParseConfig(c, cfg) + return parser.ParseConfig(cfg) }, Action: func(c *cli.Context) error { - logger := NewLogger(cfg) + logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( fmt.Sprintf( @@ -34,7 +37,7 @@ func Health(cfg *config.Config) *cli.Command { defer resp.Body.Close() - if resp.StatusCode != 200 { + if resp.StatusCode != http.StatusOK { logger.Fatal(). Int("code", resp.StatusCode). Msg("Health seems to be in bad state") diff --git a/webdav/pkg/command/root.go b/webdav/pkg/command/root.go index f221de832..3a3a3432b 100644 --- a/webdav/pkg/command/root.go +++ b/webdav/pkg/command/root.go @@ -4,91 +4,43 @@ import ( "context" "os" - "github.com/owncloud/ocis/ocis-pkg/shared" - + "github.com/owncloud/ocis/ocis-pkg/clihelper" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/webdav/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-webdav command. func Execute(cfg *config.Config) error { - app := &cli.App{ + app := clihelper.DefaultApp(&cli.App{ Name: "webdav", - Version: version.String, Usage: "Serve WebDAV API for oCIS", - Compiled: version.Compiled(), - - Authors: []*cli.Author{ - { - Name: "ownCloud GmbH", - Email: "support@owncloud.com", - }, - }, - Before: func(c *cli.Context) error { - cfg.Service.Version = version.String - return nil - }, - - Commands: []*cli.Command{ - Server(cfg), - Health(cfg), - }, - } + Commands: GetCommands(cfg), + }) cli.HelpFlag = &cli.BoolFlag{ Name: "help,h", Usage: "Show the help", } - cli.VersionFlag = &cli.BoolFlag{ - Name: "version,v", - Usage: "Print the version", - } - return app.Run(os.Args) } -// NewLogger initializes a service-specific logger instance. -func NewLogger(cfg *config.Config) log.Logger { - return log.NewLogger( - log.Name("webdav"), - log.Level(cfg.Log.Level), - log.Pretty(cfg.Log.Pretty), - log.Color(cfg.Log.Color), - log.File(cfg.Log.File), - ) -} - -// ParseConfig loads graph configuration from known paths. -func ParseConfig(c *cli.Context, cfg *config.Config) error { - conf, err := ociscfg.BindSourcesToStructs("webdav", cfg) - if err != nil { - return err - } - - // 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 = &shared.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.Commons == nil { - cfg.Log = &shared.Log{} - } - - // load all env variables relevant to the config in the current context. - conf.LoadOSEnv(config.GetEnv(cfg), false) - - bindings := config.StructMappings(cfg) - return ociscfg.BindEnv(conf, bindings) -} - // SutureService allows for the webdav command to be embedded and supervised by a suture supervisor tree. type SutureService struct { cfg *config.Config diff --git a/webdav/pkg/command/server.go b/webdav/pkg/command/server.go index d3f68efcf..3511d326c 100644 --- a/webdav/pkg/command/server.go +++ b/webdav/pkg/command/server.go @@ -2,10 +2,13 @@ package command import ( "context" - "strings" + "fmt" "github.com/oklog/run" + "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/webdav/pkg/config" + "github.com/owncloud/ocis/webdav/pkg/config/parser" + "github.com/owncloud/ocis/webdav/pkg/logging" "github.com/owncloud/ocis/webdav/pkg/metrics" "github.com/owncloud/ocis/webdav/pkg/server/debug" "github.com/owncloud/ocis/webdav/pkg/server/http" @@ -16,23 +19,16 @@ import ( // Server is the entrypoint for the server command. func Server(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "server", - Usage: "Start integrated server", - Before: func(ctx *cli.Context) error { - if cfg.HTTP.Root != "/" { - cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") - } - - if err := ParseConfig(ctx, cfg); err != nil { - return err - } - - return nil + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + return parser.ParseConfig(cfg) }, Action: func(c *cli.Context) error { - logger := NewLogger(cfg) - - if err := tracing.Configure(cfg); err != nil { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg) + if err != nil { return err } @@ -49,7 +45,7 @@ func Server(cfg *config.Config) *cli.Command { defer cancel() - metrics.BuildInfo.WithLabelValues(cfg.Service.Version).Set(1) + metrics.BuildInfo.WithLabelValues(version.String).Set(1) { server, err := http.Server( diff --git a/webdav/pkg/command/version.go b/webdav/pkg/command/version.go index 813cc1c08..71b5b0d45 100644 --- a/webdav/pkg/command/version.go +++ b/webdav/pkg/command/version.go @@ -5,30 +5,33 @@ import ( "os" "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" tw "github.com/olekukonko/tablewriter" "github.com/owncloud/ocis/webdav/pkg/config" "github.com/urfave/cli/v2" ) -// PrintVersion prints the service versions of all running instances. -func PrintVersion(cfg *config.Config) *cli.Command { +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "version", - Usage: "Print the versions of the running instances", - Before: func(c *cli.Context) error { - return ParseConfig(c, cfg) - }, + 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.Service.Namespace + "." + cfg.Service.Name) + services, err := reg.GetService(cfg.HTTP.Namespace + "." + cfg.Service.Name) if err != nil { - fmt.Println(fmt.Errorf("could not get webdav services from the registry: %v", err)) + 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 webdav service found.") + fmt.Println("No running " + cfg.Service.Name + " service found.") return nil } diff --git a/webdav/pkg/config/config.go b/webdav/pkg/config/config.go index c08c8ea0f..8b817f7f7 100644 --- a/webdav/pkg/config/config.go +++ b/webdav/pkg/config/config.go @@ -6,97 +6,20 @@ import ( "github.com/owncloud/ocis/ocis-pkg/shared" ) -// Debug defines the available debug configuration. -type Debug struct { - Addr string `ocisConfig:"addr"` - Token string `ocisConfig:"token"` - Pprof bool `ocisConfig:"pprof"` - Zpages bool `ocisConfig:"zpages"` -} - -// CORS defines the available cors configuration. -type CORS struct { - AllowedOrigins []string `ocisConfig:"allowed_origins"` - AllowedMethods []string `ocisConfig:"allowed_methods"` - AllowedHeaders []string `ocisConfig:"allowed_headers"` - AllowCredentials bool `ocisConfig:"allow_credentials"` -} - -// HTTP defines the available http configuration. -type HTTP struct { - Addr string `ocisConfig:"addr"` - Root string `ocisConfig:"root"` - CORS CORS `ocisConfig:"cors"` -} - -// Service defines the available service configuration. -type Service struct { - Name string `ocisConfig:"name"` - Namespace string `ocisConfig:"namespace"` - Version string `ocisConfig:"version"` -} - -// Tracing defines the available tracing configuration. -type Tracing struct { - Enabled bool `ocisConfig:"enabled"` - Type string `ocisConfig:"type"` - Endpoint string `ocisConfig:"endpoint"` - Collector string `ocisConfig:"collector"` - Service string `ocisConfig:"service"` -} - // Config combines all available configuration parts. type Config struct { *shared.Commons - File string `ocisConfig:"file"` - Log *shared.Log `ocisConfig:"log"` - Debug Debug `ocisConfig:"debug"` - HTTP HTTP `ocisConfig:"http"` - Tracing Tracing `ocisConfig:"tracing"` - Service Service `ocisConfig:"service"` - OcisPublicURL string `ocisConfig:"ocis_public_url"` - WebdavNamespace string `ocisConfig:"webdav_namespace"` + Service Service - Context context.Context - Supervised bool -} + Tracing Tracing `ocisConfig:"tracing"` + Log *Log `ocisConfig:"log"` + Debug Debug `ocisConfig:"debug"` -// New initializes a new configuration with or without defaults. -func New() *Config { - return &Config{} -} + HTTP HTTP `ocisConfig:"http"` -func DefaultConfig() *Config { - return &Config{ - Debug: Debug{ - Addr: "127.0.0.1:9119", - Token: "", - Pprof: false, - Zpages: false, - }, - HTTP: HTTP{ - Addr: "127.0.0.1:9115", - Root: "/", - CORS: CORS{ - AllowedOrigins: []string{"*"}, - AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, - AllowedHeaders: []string{"Authorization", "Origin", "Content-Type", "Accept", "X-Requested-With"}, - AllowCredentials: true, - }, - }, - Tracing: Tracing{ - Enabled: false, - Type: "jaeger", - Endpoint: "", - Collector: "", - Service: "webdav", - }, - Service: Service{ - Name: "webdav", - Namespace: "com.owncloud.web", - }, - OcisPublicURL: "https://127.0.0.1:9200", - WebdavNamespace: "/home", - } + OcisPublicURL string `ocisConfig:"ocis_public_url" env:"OCIS_URL;OCIS_PUBLIC_URL"` + WebdavNamespace string `ocisConfig:"webdav_namespace" env:"STORAGE_WEBDAV_NAMESPACE"` + + Context context.Context } diff --git a/webdav/pkg/config/debug.go b/webdav/pkg/config/debug.go new file mode 100644 index 000000000..2551ce17a --- /dev/null +++ b/webdav/pkg/config/debug.go @@ -0,0 +1,9 @@ +package config + +// Debug defines the available debug configuration. +type Debug struct { + Addr string `ocisConfig:"addr" env:"WEBDAV_DEBUG_ADDR"` + Token string `ocisConfig:"token" env:"WEBDAV_DEBUG_TOKEN"` + Pprof bool `ocisConfig:"pprof" env:"WEBDAV_DEBUG_PPROF"` + Zpages bool `ocisConfig:"zpages" env:"WEBDAV_DEBUG_ZPAGES"` +} diff --git a/webdav/pkg/config/defaultconfig.go b/webdav/pkg/config/defaultconfig.go new file mode 100644 index 000000000..5edb962c4 --- /dev/null +++ b/webdav/pkg/config/defaultconfig.go @@ -0,0 +1,34 @@ +package config + +func DefaultConfig() *Config { + return &Config{ + Debug: Debug{ + Addr: "127.0.0.1:9119", + Token: "", + Pprof: false, + Zpages: false, + }, + HTTP: HTTP{ + Addr: "127.0.0.1:9115", + Root: "/", + Namespace: "com.owncloud.web", + CORS: CORS{ + AllowedOrigins: []string{"*"}, + AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, + AllowedHeaders: []string{"Authorization", "Origin", "Content-Type", "Accept", "X-Requested-With"}, + AllowCredentials: true, + }, + }, + Service: Service{ + Name: "webdav", + }, + Tracing: Tracing{ + Enabled: false, + Type: "jaeger", + Endpoint: "", + Collector: "", + }, + OcisPublicURL: "https://127.0.0.1:9200", + WebdavNamespace: "/home", + } +} diff --git a/webdav/pkg/config/http.go b/webdav/pkg/config/http.go new file mode 100644 index 000000000..4ce2f2dcd --- /dev/null +++ b/webdav/pkg/config/http.go @@ -0,0 +1,17 @@ +package config + +// CORS defines the available cors configuration. +type CORS struct { + AllowedOrigins []string `ocisConfig:"allowed_origins"` + AllowedMethods []string `ocisConfig:"allowed_methods"` + AllowedHeaders []string `ocisConfig:"allowed_headers"` + AllowCredentials bool `ocisConfig:"allow_credentials"` +} + +// HTTP defines the available http configuration. +type HTTP struct { + Addr string `ocisConfig:"addr" env:"WEBDAV_HTTP_ADDR"` + Namespace string + Root string `ocisConfig:"root" env:"WEBDAV_HTTP_ROOT"` + CORS CORS `ocisConfig:"cors"` +} diff --git a/webdav/pkg/config/log.go b/webdav/pkg/config/log.go new file mode 100644 index 000000000..211aad1a4 --- /dev/null +++ b/webdav/pkg/config/log.go @@ -0,0 +1,9 @@ +package config + +// Log defines the available log configuration. +type Log struct { + Level string `mapstructure:"level" env:"OCIS_LOG_LEVEL;WEBDAV_LOG_LEVEL"` + Pretty bool `mapstructure:"pretty" env:"OCIS_LOG_PRETTY;WEBDAV_LOG_PRETTY"` + Color bool `mapstructure:"color" env:"OCIS_LOG_COLOR;WEBDAV_LOG_COLOR"` + File string `mapstructure:"file" env:"OCIS_LOG_FILE;WEBDAV_LOG_FILE"` +} diff --git a/webdav/pkg/config/mappings.go b/webdav/pkg/config/mappings.go deleted file mode 100644 index 7fa86211e..000000000 --- a/webdav/pkg/config/mappings.go +++ /dev/null @@ -1,107 +0,0 @@ -package config - -import "github.com/owncloud/ocis/ocis-pkg/shared" - -// GetEnv fetches a list of known env variables for this extension. It is to be used by gookit, as it provides a list -// with all the environment variables an extension supports. -func GetEnv(cfg *Config) []string { - var r = make([]string, len(structMappings(cfg))) - for i := range structMappings(cfg) { - r = append(r, structMappings(cfg)[i].EnvVars...) - } - - return r -} - -// StructMappings binds a set of environment variables to a destination on cfg. Iterating over this set and editing the -// Destination value of a binding will alter the original value, as it is a pointer to its memory address. This lets -// us propagate changes easier. -func StructMappings(cfg *Config) []shared.EnvBinding { - return structMappings(cfg) -} - -// structMappings binds a set of environment variables to a destination on cfg. -func structMappings(cfg *Config) []shared.EnvBinding { - return []shared.EnvBinding{ - { - EnvVars: []string{"OCIS_LOG_FILE", "WEBDAV_LOG_FILE"}, - Destination: &cfg.Log.File, - }, - { - EnvVars: []string{"OCIS_LOG_LEVEL", "WEBDAV_LOG_LEVEL"}, - Destination: &cfg.Log.Level, - }, - { - EnvVars: []string{"OCIS_LOG_PRETTY", "WEBDAV_LOG_PRETTY"}, - Destination: &cfg.Log.Pretty, - }, - { - EnvVars: []string{"OCIS_LOG_COLOR", "WEBDAV_LOG_COLOR"}, - Destination: &cfg.Log.Color, - }, - { - EnvVars: []string{"WEBDAV_CONFIG_FILE"}, - Destination: &cfg.File, - }, - { - EnvVars: []string{"OCIS_TRACING_ENABLED", "WEBDAV_TRACING_ENABLED"}, - Destination: &cfg.Tracing.Enabled, - }, - { - EnvVars: []string{"OCIS_TRACING_TYPE", "WEBDAV_TRACING_TYPE"}, - Destination: &cfg.Tracing.Type, - }, - { - EnvVars: []string{"OCIS_TRACING_ENDPOINT", "WEBDAV_TRACING_ENDPOINT"}, - Destination: &cfg.Tracing.Endpoint, - }, - { - EnvVars: []string{"OCIS_TRACING_COLLECTOR", "WEBDAV_TRACING_COLLECTOR"}, - Destination: &cfg.Tracing.Collector, - }, - { - EnvVars: []string{"WEBDAV_TRACING_SERVICE"}, - Destination: &cfg.Tracing.Service, - }, - { - EnvVars: []string{"WEBDAV_DEBUG_ADDR"}, - Destination: &cfg.Debug.Addr, - }, - { - EnvVars: []string{"WEBDAV_DEBUG_TOKEN"}, - Destination: &cfg.Debug.Token, - }, - { - EnvVars: []string{"WEBDAV_DEBUG_PPROF"}, - Destination: &cfg.Debug.Pprof, - }, - { - EnvVars: []string{"WEBDAV_DEBUG_ZPAGES"}, - Destination: &cfg.Debug.Zpages, - }, - { - EnvVars: []string{"WEBDAV_HTTP_ADDR"}, - Destination: &cfg.HTTP.Addr, - }, - { - EnvVars: []string{"WEBDAV_HTTP_NAMESPACE"}, - Destination: &cfg.Service.Namespace, - }, - { - EnvVars: []string{"WEBDAV_SERVICE_NAME"}, - Destination: &cfg.Service.Name, - }, - { - EnvVars: []string{"WEBDAV_HTTP_ROOT"}, - Destination: &cfg.HTTP.Root, - }, - { - EnvVars: []string{"OCIS_URL", "OCIS_PUBLIC_URL"}, - Destination: &cfg.OcisPublicURL, - }, - { - EnvVars: []string{"STORAGE_WEBDAV_NAMESPACE"}, - Destination: &cfg.WebdavNamespace, - }, - } -} diff --git a/webdav/pkg/config/parser/parse.go b/webdav/pkg/config/parser/parse.go new file mode 100644 index 000000000..66341b099 --- /dev/null +++ b/webdav/pkg/config/parser/parse.go @@ -0,0 +1,46 @@ +package parser + +import ( + "errors" + "strings" + + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/webdav/pkg/config" + + "github.com/owncloud/ocis/ocis-pkg/config/envdecode" +) + +// ParseConfig loads accounts configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + // 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.Commons == nil { + cfg.Log = &config.Log{} + } + + // 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 + } + } + + // sanitize config + if cfg.HTTP.Root != "/" { + cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") + } + + return nil +} diff --git a/webdav/pkg/config/service.go b/webdav/pkg/config/service.go new file mode 100644 index 000000000..f98aa3d27 --- /dev/null +++ b/webdav/pkg/config/service.go @@ -0,0 +1,6 @@ +package config + +// Service defines the available service configuration. +type Service struct { + Name string +} diff --git a/webdav/pkg/config/tracing.go b/webdav/pkg/config/tracing.go new file mode 100644 index 000000000..311e5fbc0 --- /dev/null +++ b/webdav/pkg/config/tracing.go @@ -0,0 +1,9 @@ +package config + +// Tracing defines the available tracing configuration. +type Tracing struct { + Enabled bool `ocisConfig:"enabled" env:"OCIS_TRACING_ENABLED;WEBDAV_TRACING_ENABLED"` + Type string `ocisConfig:"type" env:"OCIS_TRACING_TYPE;WEBDAV_TRACING_TYPE"` + Endpoint string `ocisConfig:"endpoint" env:"OCIS_TRACING_ENDPOINT;WEBDAV_TRACING_ENDPOINT"` + Collector string `ocisConfig:"collector" env:"OCIS_TRACING_COLLECTOR;WEBDAV_TRACING_COLLECTOR"` +} diff --git a/webdav/pkg/logging/logging.go b/webdav/pkg/logging/logging.go new file mode 100644 index 000000000..ff82b1f6b --- /dev/null +++ b/webdav/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/webdav/pkg/config" +) + +// 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/webdav/pkg/server/debug/server.go b/webdav/pkg/server/debug/server.go index f79a0538f..ff0ee6b4f 100644 --- a/webdav/pkg/server/debug/server.go +++ b/webdav/pkg/server/debug/server.go @@ -5,6 +5,7 @@ import ( "net/http" "github.com/owncloud/ocis/ocis-pkg/service/debug" + "github.com/owncloud/ocis/ocis-pkg/version" "github.com/owncloud/ocis/webdav/pkg/config" ) @@ -15,7 +16,7 @@ func Server(opts ...Option) (*http.Server, error) { return debug.NewService( debug.Logger(options.Logger), debug.Name(options.Config.Service.Name), - debug.Version(options.Config.Service.Version), + debug.Version(version.String), debug.Address(options.Config.Debug.Addr), debug.Token(options.Config.Debug.Token), debug.Pprof(options.Config.Debug.Pprof), @@ -35,9 +36,11 @@ func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) - // TODO(tboerger): check if services are up and running + // TODO: check if services are up and running - if _, err := io.WriteString(w, http.StatusText(http.StatusOK)); err != nil { + _, 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) } } @@ -49,9 +52,11 @@ func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) - // TODO(tboerger): check if services are up and running + // TODO: check if services are up and running - if _, err := io.WriteString(w, http.StatusText(http.StatusOK)); err != nil { + _, 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/webdav/pkg/server/http/server.go b/webdav/pkg/server/http/server.go index 1b183af5e..bf962961b 100644 --- a/webdav/pkg/server/http/server.go +++ b/webdav/pkg/server/http/server.go @@ -5,6 +5,7 @@ import ( "github.com/owncloud/ocis/ocis-pkg/cors" "github.com/owncloud/ocis/ocis-pkg/middleware" "github.com/owncloud/ocis/ocis-pkg/service/http" + "github.com/owncloud/ocis/ocis-pkg/version" svc "github.com/owncloud/ocis/webdav/pkg/service/v0" "go-micro.dev/v4" ) @@ -15,9 +16,9 @@ func Server(opts ...Option) (http.Service, error) { service := http.NewService( http.Logger(options.Logger), - http.Namespace(options.Config.Service.Namespace), + http.Namespace(options.Config.HTTP.Namespace), http.Name(options.Config.Service.Name), - http.Version(options.Config.Service.Version), + http.Version(version.String), http.Address(options.Config.HTTP.Addr), http.Context(options.Context), http.Flags(options.Flags...), @@ -40,7 +41,7 @@ func Server(opts ...Option) (http.Service, error) { middleware.Secure, middleware.Version( options.Config.Service.Name, - options.Config.Service.Version, + version.String, ), middleware.Logger( options.Logger, diff --git a/webdav/pkg/tracing/tracing.go b/webdav/pkg/tracing/tracing.go index f3efeecd0..8a2c53929 100644 --- a/webdav/pkg/tracing/tracing.go +++ b/webdav/pkg/tracing/tracing.go @@ -14,7 +14,7 @@ var ( func Configure(cfg *config.Config) error { var err error if cfg.Tracing.Enabled { - if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, "webdav", cfg.Tracing.Type); err != nil { + if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { return err } }