modules/core/config/config.go

138 lines
3.5 KiB
Go

package config
import (
"database/sql"
"errors"
"fmt"
"io/ioutil"
"os"
"reflect"
"strings"
"github.com/BurntSushi/toml"
// mysql driver.
_ "github.com/go-sql-driver/mysql"
// psql driver.
_ "github.com/lib/pq"
)
type Interface struct {
str interface{}
}
type Config struct {
ConfigFile string
EnvPrefix string
DSN string
}
// Simple constructor.
func New(str interface{}) Interface {
return Interface{str: str}
}
// Method wraps discrete methods.
func (s Interface) Combine(config Config) error {
if config.ConfigFile != "" {
fmt.Printf("try to apply config from file %s...\n", config.ConfigFile)
if err := s.SetFromFile(config.ConfigFile); err != nil {
return fmt.Errorf("can't apply config from file: %w", err)
}
}
if config.EnvPrefix != "" {
fmt.Printf("try to apply config from environment...\n")
if err := s.SetFromEnv(config.EnvPrefix); err != nil {
return fmt.Errorf("can't apply envvars to config:%w", err)
}
}
if config.DSN != "" {
fmt.Printf("try to apply config from DSN %s...\n", config.DSN)
db, dbname, err := DialDSN(config.DSN)
if err != nil {
return fmt.Errorf("can't dial DB:%w", err)
}
if err := s.SetFromDB(db, dbname); err != nil {
return fmt.Errorf("can't apply db lines to config:%w", err)
}
}
return nil
}
// Method adds and replace config fields from file.
func (s Interface) SetFromFile(fileName string) error {
f, err := os.Open(fileName)
if err != nil {
return fmt.Errorf("can't open config file: %w", err)
}
defer f.Close()
l, err := ioutil.ReadAll(f)
if err != nil {
return fmt.Errorf("can't read content of the config file : %w", err)
}
_, err = toml.Decode(string(l), s.str)
if err != nil {
return fmt.Errorf("can't parce config file : %w", err)
}
return nil
}
// Method adds and replace config fields from env.
func (s Interface) SetFromEnv(prefix string) error {
return getEnvVar(reflect.ValueOf(s.str), reflect.TypeOf(s.str), -1, prefix)
}
func DialDSN(dsn string) (db *sql.DB, dbname string, err error) {
m := strings.FieldsFunc(dsn, func(r rune) bool { return r == ':' || r == '@' || r == '/' })
dbName := m[len(m)-1]
if dbName == "" {
return nil, "", fmt.Errorf("DSN not contains database name: %s", dsn)
}
var driver string
switch {
case strings.HasPrefix(dsn, "postgres://"):
driver = "postgres"
dsn = strings.TrimLeft(dsn, "postgres://")
case strings.HasPrefix(dsn, "mysql://"):
driver = "mysql"
dsn = strings.TrimLeft(dsn, "mysql://")
default:
return nil, "", errors.New("can't use unknown SQL dialect")
}
db, err = sql.Open(driver, dsn)
if err != nil {
return nil, "", fmt.Errorf("can't connect to DB: %w", err)
}
return db, dbName, nil
}
// Method adds and replace config fields from db.
func (s Interface) SetFromDB(database *sql.DB, _ string) error {
defer database.Close()
res := make(map[string]string)
var key, val string
// TODO: Перенести это в параметры.
table := "config"
q := fmt.Sprintf("SELECT %s.key, %s.value FROM %s", table, table, table)
results, err := database.Query(q)
if err != nil || results.Err() != nil {
return fmt.Errorf("can't get key-value pairs from DB: %w", err)
}
defer results.Close()
for results.Next() {
err = results.Scan(&key, &val)
if err != nil {
return fmt.Errorf("can't parse key-value into vars: %w", err)
}
res[strings.ToLower(key)] = val
}
if err = parseToStruct(reflect.ValueOf(s.str), reflect.TypeOf(s.str), -1, "", res); err != nil {
return fmt.Errorf("can't parse into struct: %w", err)
}
return nil
}