ezshare/config/config.go

504 lines
14 KiB
Go
Raw Normal View History

2021-12-04 02:25:09 +00:00
package config
import (
"crypto/tls"
"crypto/x509"
"errors"
2021-12-04 02:25:09 +00:00
"fmt"
"io"
"io/fs"
2021-12-04 02:25:09 +00:00
"io/ioutil"
"os"
"path/filepath"
2021-12-04 08:58:16 +00:00
"strings"
2021-12-04 02:25:09 +00:00
2022-01-13 17:40:15 +00:00
"git.t-juice.club/torjus/ezshare/store"
2021-12-04 02:25:09 +00:00
"github.com/pelletier/go-toml"
2021-12-06 06:55:30 +00:00
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
2021-12-04 02:25:09 +00:00
"google.golang.org/grpc/credentials"
)
2021-12-06 05:08:17 +00:00
var ErrNotFound = fmt.Errorf("config not found")
2021-12-04 02:25:09 +00:00
type Config struct {
Server *ServerConfig `toml:"Server"`
Client *ClientConfig `toml:"Client"`
location string
}
type CertificatePaths struct {
CertificatePath string `toml:"CertificatePath"`
CertificateKeyPath string `toml:"CertificateKeyPath"`
}
type ServerConfig struct {
2021-12-06 05:08:17 +00:00
LogLevel string `toml:"LogLevel"`
Hostname string `toml:"Hostname"`
GRPCEndpoint string `toml:"GRPCEndpoint"`
2021-12-08 08:42:12 +00:00
DataStoreConfig *ServerDataStoreConfig `toml:"DataStore"`
2021-12-06 05:08:17 +00:00
FileStoreConfig *ServerFileStoreConfig `toml:"FileStore"`
GRPC *ServerGRPCConfig `toml:"GRPC"`
HTTP *ServerHTTPConfig `toml:"HTTP"`
2021-12-04 02:25:09 +00:00
}
2021-12-04 08:58:16 +00:00
2021-12-06 05:08:17 +00:00
type ServerFileStoreConfig struct {
2021-12-04 08:58:16 +00:00
Type string `toml:"Type"`
FSStoreConfig *FSStoreConfig `toml:"Filesystem"`
BoltStoreConfig *BoltStoreConfig `toml:"Bolt"`
}
2021-12-08 08:42:12 +00:00
type ServerDataStoreConfig struct {
2021-12-06 05:08:17 +00:00
Type string `toml:"Type"`
BoltStoreConfig *BoltStoreConfig `toml:"Bolt"`
}
2021-12-04 08:58:16 +00:00
type BoltStoreConfig struct {
Path string `toml:"Path"`
2021-12-04 02:25:09 +00:00
}
2021-12-04 08:58:16 +00:00
2021-12-04 02:25:09 +00:00
type FSStoreConfig struct {
Dir string `toml:"Dir"`
}
2021-12-04 08:58:16 +00:00
2021-12-04 02:25:09 +00:00
type ServerGRPCConfig struct {
ListenAddr string `toml:"ListenAddr"`
CACerts *CertificatePaths `toml:"CACerts"`
Certs *CertificatePaths `toml:"Certs"`
}
2021-12-04 08:58:16 +00:00
2021-12-04 02:25:09 +00:00
type ServerHTTPConfig struct {
ListenAddr string `toml:"ListenAddr"`
}
type ClientConfig struct {
DefaultServer string `toml:"DefaultServer"`
ServerCertPath string `toml:"ServerCertPath"`
Certs *CertificatePaths `toml:"Certs"`
}
func FromDefault() *Config {
cfg := &Config{
Server: &ServerConfig{
2021-12-04 03:31:19 +00:00
LogLevel: "INFO",
2021-12-04 02:25:09 +00:00
GRPC: &ServerGRPCConfig{
ListenAddr: ":50051",
CACerts: &CertificatePaths{},
Certs: &CertificatePaths{},
},
HTTP: &ServerHTTPConfig{
ListenAddr: ":8089",
},
DataStoreConfig: &ServerDataStoreConfig{
BoltStoreConfig: &BoltStoreConfig{},
},
FileStoreConfig: &ServerFileStoreConfig{
BoltStoreConfig: &BoltStoreConfig{},
FSStoreConfig: &FSStoreConfig{},
},
2021-12-04 02:25:09 +00:00
},
2021-12-06 05:08:17 +00:00
Client: &ClientConfig{
Certs: &CertificatePaths{},
},
2021-12-04 02:25:09 +00:00
}
return cfg
}
func FromReader(r io.Reader) (*Config, error) {
decoder := toml.NewDecoder(r)
c := FromDefault()
if err := decoder.Decode(c); err != nil {
return nil, fmt.Errorf("unable to read config: %w", err)
}
return c, nil
}
func FromFile(path string) (*Config, error) {
f, err := os.Open(path)
if err != nil {
2021-12-06 05:08:17 +00:00
if errors.Is(err, os.ErrNotExist) {
return nil, ErrNotFound
}
2021-12-04 02:25:09 +00:00
return nil, fmt.Errorf("unable to open config-file: %w", err)
}
defer f.Close()
cfg, err := FromReader(f)
if err == nil {
cfg.location = path
}
return cfg, err
}
func FromDefaultLocations() (*Config, error) {
defaultLocations := []string{
"ezshare.toml",
}
userConfigDir, err := os.UserConfigDir()
2021-12-04 03:13:08 +00:00
if err == nil {
2021-12-04 02:25:09 +00:00
defaultLocations = append(defaultLocations, filepath.Join(userConfigDir, "ezshare", "ezshare.toml"))
}
for _, location := range defaultLocations {
if _, err := os.Stat(location); err == nil {
return FromFile(location)
}
}
return nil, fmt.Errorf("config not found")
}
func (c *Config) UpdateFromEnv() {
2021-12-08 10:43:41 +00:00
// Server stuff
if val, found := os.LookupEnv("EZSHARE_SERVER_LOGLEVEL"); found {
c.Server.LogLevel = val
}
if val, found := os.LookupEnv("EZSHARE_SERVER_HOSTNAME"); found {
c.Server.Hostname = val
}
if val, found := os.LookupEnv("EZSHARE_SERVER_GRPCENDPOINT"); found {
c.Server.GRPCEndpoint = val
}
if val, found := os.LookupEnv("EZSHARE_SERVER_DATASTORE_TYPE"); found {
c.Server.DataStoreConfig.Type = val
}
if val, found := os.LookupEnv("EZSHARE_SERVER_DATASTORE_BOLT_PATH"); found {
c.Server.DataStoreConfig.BoltStoreConfig.Path = val
}
if val, found := os.LookupEnv("EZSHARE_SERVER_FILESTORE_TYPE"); found {
c.Server.FileStoreConfig.Type = val
}
if val, found := os.LookupEnv("EZSHARE_SERVER_FILESTORE_BOLT_PATH"); found {
c.Server.FileStoreConfig.BoltStoreConfig.Path = val
}
if val, found := os.LookupEnv("EZSHARE_SERVER_FILESTORE_FILESYSTEM_DIR"); found {
c.Server.FileStoreConfig.FSStoreConfig.Dir = val
}
if val, found := os.LookupEnv("EZSHARE_SERVER_GRPC_CACERTS_CERTIFICATEKEYPATH"); found {
c.Server.GRPC.CACerts.CertificateKeyPath = val
}
if val, found := os.LookupEnv("EZSHARE_SERVER_GRPC_CACERTS_CERTIFICATEPATH"); found {
c.Server.GRPC.CACerts.CertificatePath = val
}
if val, found := os.LookupEnv("EZSHARE_SERVER_GRPC_CERTS_CERTIFICATEKEYPATH"); found {
c.Server.GRPC.Certs.CertificateKeyPath = val
}
if val, found := os.LookupEnv("EZSHARE_SERVER_GRPC_CERTS_CERTIFICATEPATH"); found {
c.Server.GRPC.Certs.CertificatePath = val
}
if val, found := os.LookupEnv("EZSHARE_SERVER_GRPC_LISTENADDR"); found {
c.Server.GRPC.ListenAddr = val
}
if val, found := os.LookupEnv("EZSHARE_SERVER_HTTP_LISTENADDR"); found {
c.Server.HTTP.ListenAddr = val
}
// Client stuff
if val, found := os.LookupEnv("EZSHARE_CLIENT_DEFAULTSERVER"); found {
c.Client.DefaultServer = val
}
if val, found := os.LookupEnv("EZSHARE_CLIENT_CERTS_CERTIFICATEKEYPATH"); found {
c.Client.Certs.CertificateKeyPath = val
}
if val, found := os.LookupEnv("EZSHARE_CLIENT_CERTS_CERTIFICATEPATH"); found {
c.Client.Certs.CertificatePath = val
}
if val, found := os.LookupEnv("EZSHARE_CLIENT_SERVERCERTPATH"); found {
c.Client.ServerCertPath = val
}
}
2021-12-08 12:12:16 +00:00
func (sc *ServerConfig) Valid() error {
2021-12-08 08:42:12 +00:00
// Verify that grpc-endpoint is set
2021-12-08 12:12:16 +00:00
if sc.GRPCEndpoint == "" {
2021-12-08 08:42:12 +00:00
return fmt.Errorf("missing require config-value Server.GRPCEndpoint")
}
// Verify loglevel
2021-12-08 12:12:16 +00:00
switch strings.ToUpper(sc.LogLevel) {
2021-12-08 08:42:12 +00:00
case "DEBUG", "INFO", "WARN", "ERROR", "FATAL":
break
default:
return fmt.Errorf("config-value Server.LogLevel is invalid")
}
// Verify datastore config
2021-12-08 12:12:16 +00:00
switch strings.ToLower(sc.DataStoreConfig.Type) {
2021-12-08 08:42:12 +00:00
case "memory":
break
case "bolt":
2021-12-08 12:12:16 +00:00
if sc.DataStoreConfig.BoltStoreConfig == nil || sc.DataStoreConfig.BoltStoreConfig.Path == "" {
2021-12-08 08:42:12 +00:00
return fmt.Errorf("server datastore is bolt, missing required config value Server.DataStore.Bolt.Path")
}
default:
return fmt.Errorf("config-value Server.DataStore.Type is invalid")
}
// Verify filestore config
2021-12-08 12:12:16 +00:00
switch strings.ToLower(sc.FileStoreConfig.Type) {
2021-12-08 08:42:12 +00:00
case "memory":
break
case "filesystem":
2021-12-08 12:12:16 +00:00
if sc.FileStoreConfig.FSStoreConfig == nil || sc.FileStoreConfig.FSStoreConfig.Dir == "" {
2021-12-08 08:42:12 +00:00
return fmt.Errorf("server datastore is bolt, missing required config value Server.FileStore.FSStore.Path")
}
case "bolt":
2021-12-08 12:12:16 +00:00
if sc.FileStoreConfig.BoltStoreConfig == nil || sc.FileStoreConfig.BoltStoreConfig.Path == "" {
2021-12-08 08:42:12 +00:00
return fmt.Errorf("server datastore is bolt, missing required config value Server.DataStore.Bolt.Path")
}
}
// Verify grpc-config
2021-12-08 12:12:16 +00:00
if sc.GRPC.ListenAddr == "" {
2021-12-08 08:42:12 +00:00
return fmt.Errorf("missing required config-value Server.GRPC.ListenAddr")
}
2021-12-08 12:12:16 +00:00
if sc.GRPC.CACerts.CertificateKeyPath == "" {
2021-12-08 08:42:12 +00:00
// TODO: Maybe return custom error, so we can create certs if missing
return fmt.Errorf("missing require value Server.GRPC.CACerts.CertificateKeyPath")
}
2021-12-08 12:12:16 +00:00
if sc.GRPC.CACerts.CertificatePath == "" {
2021-12-08 08:42:12 +00:00
// TODO: Maybe return custom error, so we can create certs if missing
return fmt.Errorf("missing require value Server.GRPC.CACerts.CertificatePath")
}
2021-12-08 12:12:16 +00:00
if sc.GRPC.Certs.CertificatePath == "" {
2021-12-08 08:42:12 +00:00
// TODO: Maybe return custom error, so we can create certs if missing
return fmt.Errorf("missing require value Server.GRPC.Certs.CertificatePath")
}
2021-12-08 12:12:16 +00:00
if sc.GRPC.Certs.CertificateKeyPath == "" {
2021-12-08 08:42:12 +00:00
// TODO: Maybe return custom error, so we can create certs if missing
return fmt.Errorf("missing require value Server.GRPC.Certs.CertificateKeyPath")
}
return nil
}
2021-12-08 12:12:16 +00:00
func (cc *ClientConfig) Valid() error {
if cc.Certs.CertificateKeyPath == "" {
2021-12-08 08:42:12 +00:00
return fmt.Errorf("missing required value Client.Certs.CertificateKeyPath")
}
2021-12-08 12:12:16 +00:00
if cc.Certs.CertificatePath == "" {
2021-12-08 08:42:12 +00:00
return fmt.Errorf("missing required value Client.Certs.CertificatePath")
}
2021-12-08 12:12:16 +00:00
if cc.DefaultServer == "" {
2021-12-08 08:42:12 +00:00
// TODO: Should probably have its own custom error
return fmt.Errorf("missing required value Client.DefaultServer")
}
2021-12-08 12:12:16 +00:00
if cc.ServerCertPath == "" {
2021-12-08 08:42:12 +00:00
// TODO: Should probably have its own custom error
return fmt.Errorf("missing required value Client.ServerCertPath")
}
return nil
}
2021-12-04 02:25:09 +00:00
func (c *Config) Location() string {
return c.location
}
func (cp *CertificatePaths) GetCertBytes() ([]byte, error) {
f, err := os.Open(cp.CertificatePath)
if err != nil {
return nil, err
}
return ioutil.ReadAll(f)
}
func (cp *CertificatePaths) GetKeyBytes() ([]byte, error) {
f, err := os.Open(cp.CertificateKeyPath)
if err != nil {
return nil, err
}
return ioutil.ReadAll(f)
}
func (cc *ClientConfig) ServerCertBytes() ([]byte, error) {
f, err := os.Open(cc.ServerCertPath)
if err != nil {
return nil, fmt.Errorf("unable to open server certificate: %w", err)
}
defer f.Close()
data, err := ioutil.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("unable to read client server certificate: %w", err)
}
return data, nil
}
func (cc *ClientConfig) Creds() (credentials.TransportCredentials, error) {
srvCertBytes, err := cc.ServerCertBytes()
if err != nil {
return nil, err
}
clientCertBytes, err := cc.Certs.GetCertBytes()
if err != nil {
return nil, fmt.Errorf("unable to read client cert: %w", err)
}
clientKeyBytes, err := cc.Certs.GetKeyBytes()
if err != nil {
return nil, fmt.Errorf("unable to read client cert: %w", err)
}
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(srvCertBytes) {
return nil, fmt.Errorf("unable to load ca cert")
}
clientCert, err := tls.X509KeyPair(clientCertBytes, clientKeyBytes)
if err != nil {
return nil, fmt.Errorf("unable to load client cert: %s", err)
}
config := &tls.Config{
Certificates: []tls.Certificate{clientCert},
RootCAs: certPool,
}
return credentials.NewTLS(config), nil
}
2021-12-04 02:25:09 +00:00
2021-12-06 05:08:17 +00:00
func CreateDefaultConfigDir() error {
userConfigDir, err := os.UserConfigDir()
if err != nil {
return err
}
configDirPath := filepath.Join(userConfigDir, "ezshare")
info, err := os.Stat(configDirPath)
if err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return err
}
if err := os.Mkdir(configDirPath, 0755); err != nil {
return fmt.Errorf("unable to create config-dir: %w", err)
}
} else {
if !info.IsDir() {
return fmt.Errorf("config-directory is not a directory")
}
}
2021-12-06 05:08:17 +00:00
return nil
}
func DefaultConfigFilePath() (string, error) {
userConfigDir, err := os.UserConfigDir()
if err != nil {
return "", err
}
return filepath.Join(userConfigDir, "ezshare", "ezshare.toml"), nil
2021-12-06 05:08:17 +00:00
}
2021-12-06 14:48:55 +00:00
func (c *Config) ToWriter(w io.Writer) error {
encoder := toml.NewEncoder(w)
return encoder.Encode(c)
}
2021-12-06 05:08:17 +00:00
func (c *Config) ToDefaultFile() error {
if err := CreateDefaultConfigDir(); err != nil {
return err
}
configFilePath, err := DefaultConfigFilePath()
if err != nil {
return err
}
_, err = os.Stat(configFilePath)
if err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("error stating config-file: %w", err)
}
f, err := os.Create(configFilePath)
if err != nil {
return fmt.Errorf("unable to create config-file: %w", err)
}
encoder := toml.NewEncoder(f)
fmt.Printf("Writing config to '%s'", configFilePath)
return encoder.Encode(c)
}
return fmt.Errorf("config-file already exists")
2021-12-04 02:25:09 +00:00
}
2021-12-04 08:58:16 +00:00
2021-12-06 05:08:17 +00:00
func (sc *ServerFileStoreConfig) GetStore() (store.FileStore, func() error, error) {
2021-12-04 08:58:16 +00:00
nopCloseFunc := func() error { return nil }
if strings.EqualFold(sc.Type, "bolt") {
s, err := store.NewBoltStore(sc.BoltStoreConfig.Path)
if err != nil {
return nil, nil, err
}
return s, s.Close, err
}
if strings.EqualFold(sc.Type, "filesystem") {
s := store.NewFileSystemStore(sc.FSStoreConfig.Dir)
return s, nopCloseFunc, nil
}
if strings.EqualFold(sc.Type, "memory") {
2021-12-05 00:00:32 +00:00
return store.NewMemoryStore(), nopCloseFunc, nil
2021-12-04 08:58:16 +00:00
}
2021-12-06 05:08:17 +00:00
if strings.EqualFold(sc.Type, "bolt") {
s, err := store.NewBoltStore(sc.BoltStoreConfig.Path)
if err != nil {
return nil, nil, err
}
closeFunc := func() error { return s.Close() }
return s, closeFunc, nil
}
return nil, nil, fmt.Errorf("invalid store config")
}
2021-12-08 08:42:12 +00:00
func (sc *ServerDataStoreConfig) GetStore() (store.DataStore, func() error, error) {
2021-12-06 05:08:17 +00:00
nopCloseFunc := func() error { return nil }
if strings.EqualFold(sc.Type, "bolt") {
s, err := store.NewBoltStore(sc.BoltStoreConfig.Path)
if err != nil {
return nil, nil, err
}
return s, s.Close, err
}
if strings.EqualFold(sc.Type, "memory") {
return store.NewMemoryStore(), nopCloseFunc, nil
}
if strings.EqualFold(sc.Type, "bolt") {
s, err := store.NewBoltStore(sc.BoltStoreConfig.Path)
if err != nil {
return nil, nil, err
}
closeFunc := func() error { return s.Close() }
return s, closeFunc, nil
}
2021-12-04 08:58:16 +00:00
return nil, nil, fmt.Errorf("invalid store config")
}
2021-12-06 06:55:30 +00:00
func (c *ServerConfig) GetLogger() *zap.SugaredLogger {
logEncoderConfig := zap.NewProductionEncoderConfig()
logEncoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
logEncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
logEncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
logEncoderConfig.EncodeDuration = zapcore.StringDurationEncoder
rootLoggerConfig := &zap.Config{
Level: zap.NewAtomicLevelAt(zap.DebugLevel),
OutputPaths: []string{"stdout"},
ErrorOutputPaths: []string{"stdout"},
Encoding: "console",
EncoderConfig: logEncoderConfig,
DisableCaller: true,
}
switch strings.ToUpper(c.LogLevel) {
case "DEBUG":
rootLoggerConfig.DisableCaller = false
rootLoggerConfig.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
case "INFO":
rootLoggerConfig.Level = zap.NewAtomicLevelAt(zap.InfoLevel)
case "WARN", "WARNING":
rootLoggerConfig.Level = zap.NewAtomicLevelAt(zap.WarnLevel)
case "ERR", "ERROR":
rootLoggerConfig.Level = zap.NewAtomicLevelAt(zap.ErrorLevel)
}
rootLogger, err := rootLoggerConfig.Build()
if err != nil {
panic(err)
}
return rootLogger.Sugar()
}