2016-01-17 18:00:44 +00:00
|
|
|
// © 2013 the CatBase Authors under the WTFPL. See AUTHORS for the list of authors.
|
2013-12-10 23:37:07 +00:00
|
|
|
|
2012-08-17 20:38:15 +00:00
|
|
|
package config
|
|
|
|
|
2017-07-25 17:58:04 +00:00
|
|
|
import (
|
2020-04-28 15:32:52 +00:00
|
|
|
"encoding/json"
|
2017-07-25 17:58:04 +00:00
|
|
|
"fmt"
|
2019-01-20 20:21:26 +00:00
|
|
|
"os"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
2017-07-25 17:58:04 +00:00
|
|
|
|
2021-12-20 17:40:10 +00:00
|
|
|
_ "modernc.org/sqlite"
|
|
|
|
|
|
|
|
bh "github.com/timshannon/bolthold"
|
2019-10-21 00:55:13 +00:00
|
|
|
|
2017-07-25 17:58:04 +00:00
|
|
|
"github.com/jmoiron/sqlx"
|
2019-03-07 16:35:42 +00:00
|
|
|
"github.com/rs/zerolog/log"
|
2017-07-25 17:58:04 +00:00
|
|
|
)
|
2012-08-17 20:38:15 +00:00
|
|
|
|
|
|
|
// Config stores any system-wide startup information that cannot be easily configured via
|
|
|
|
// the database
|
|
|
|
type Config struct {
|
2021-12-20 17:40:10 +00:00
|
|
|
db *sqlx.DB
|
|
|
|
|
|
|
|
store *bh.Store
|
2017-07-25 17:58:04 +00:00
|
|
|
|
2021-05-20 13:59:28 +00:00
|
|
|
DBFile string
|
|
|
|
secrets map[string]Secret
|
|
|
|
}
|
|
|
|
|
2021-12-20 17:40:10 +00:00
|
|
|
// Value is a config value that is loaded permanently and not ever displayed
|
|
|
|
type Value struct {
|
2021-05-20 13:59:28 +00:00
|
|
|
// Key is the key field of the table
|
2021-12-21 04:31:19 +00:00
|
|
|
Key string `db:"key" json:"key"`
|
2021-05-20 13:59:28 +00:00
|
|
|
// Value represents the secret that must not be shared
|
2021-12-21 04:31:19 +00:00
|
|
|
Value string `db:"value" json:"value"`
|
2019-01-20 20:21:26 +00:00
|
|
|
}
|
|
|
|
|
2021-12-20 17:40:10 +00:00
|
|
|
// Secret is a separate type (for storage differentiation)
|
|
|
|
type Secret Value
|
|
|
|
|
|
|
|
// DB returns the SQL database instance
|
|
|
|
func (c *Config) DB() *sqlx.DB {
|
|
|
|
return c.db
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Config) Store() *bh.Store {
|
|
|
|
return c.store
|
|
|
|
}
|
|
|
|
|
2019-01-20 20:21:26 +00:00
|
|
|
// GetFloat64 returns the config value for a string key
|
|
|
|
// It will first look in the env vars for the key
|
2021-07-21 18:52:45 +00:00
|
|
|
// It will check the db for the key if an env DNE
|
2019-01-20 20:21:26 +00:00
|
|
|
// Finally, it will return a zero value if the key does not exist
|
|
|
|
// It will attempt to convert the value to a float64 if it exists
|
2019-01-22 00:16:57 +00:00
|
|
|
func (c *Config) GetFloat64(key string, fallback float64) float64 {
|
|
|
|
f, err := strconv.ParseFloat(c.GetString(key, fmt.Sprintf("%f", fallback)), 64)
|
2019-01-20 20:21:26 +00:00
|
|
|
if err != nil {
|
|
|
|
return 0.0
|
2017-07-25 18:44:36 +00:00
|
|
|
}
|
2019-01-20 20:21:26 +00:00
|
|
|
return f
|
|
|
|
}
|
|
|
|
|
2019-10-21 00:55:13 +00:00
|
|
|
// GetInt64 returns the config value for a string key
|
|
|
|
// It will first look in the env vars for the key
|
2021-07-21 18:52:45 +00:00
|
|
|
// It will check the db for the key if an env DNE
|
2019-10-21 00:55:13 +00:00
|
|
|
// Finally, it will return a zero value if the key does not exist
|
|
|
|
// It will attempt to convert the value to an int if it exists
|
|
|
|
func (c *Config) GetInt64(key string, fallback int64) int64 {
|
|
|
|
i, err := strconv.ParseInt(c.GetString(key, strconv.FormatInt(fallback, 10)), 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
2019-01-20 20:21:26 +00:00
|
|
|
// GetInt returns the config value for a string key
|
|
|
|
// It will first look in the env vars for the key
|
2021-07-21 18:52:45 +00:00
|
|
|
// It will check the db for the key if an env DNE
|
2019-01-20 20:21:26 +00:00
|
|
|
// Finally, it will return a zero value if the key does not exist
|
|
|
|
// It will attempt to convert the value to an int if it exists
|
2019-01-22 00:16:57 +00:00
|
|
|
func (c *Config) GetInt(key string, fallback int) int {
|
|
|
|
i, err := strconv.Atoi(c.GetString(key, strconv.Itoa(fallback)))
|
2019-01-20 20:21:26 +00:00
|
|
|
if err != nil {
|
|
|
|
return 0
|
2017-08-30 17:41:58 +00:00
|
|
|
}
|
2019-01-20 20:21:26 +00:00
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get is a shortcut for GetString
|
2019-01-22 00:16:57 +00:00
|
|
|
func (c *Config) Get(key, fallback string) string {
|
|
|
|
return c.GetString(key, fallback)
|
2019-01-20 20:21:26 +00:00
|
|
|
}
|
|
|
|
|
2019-01-21 19:24:03 +00:00
|
|
|
func envkey(key string) string {
|
|
|
|
key = strings.ToUpper(key)
|
|
|
|
key = strings.Replace(key, ".", "", -1)
|
|
|
|
return key
|
|
|
|
}
|
|
|
|
|
2019-01-20 20:21:26 +00:00
|
|
|
// GetString returns the config value for a string key
|
|
|
|
// It will first look in the env vars for the key
|
2021-07-21 18:52:45 +00:00
|
|
|
// It will check the db for the key if an env DNE
|
2019-01-20 20:21:26 +00:00
|
|
|
// Finally, it will return a zero value if the key does not exist
|
|
|
|
// It will convert the value to a string if it exists
|
2019-01-22 00:16:57 +00:00
|
|
|
func (c *Config) GetString(key, fallback string) string {
|
2019-01-20 20:21:26 +00:00
|
|
|
key = strings.ToLower(key)
|
2019-01-21 19:24:03 +00:00
|
|
|
if v, found := os.LookupEnv(envkey(key)); found {
|
2019-01-20 20:21:26 +00:00
|
|
|
return v
|
|
|
|
}
|
2021-05-20 13:59:28 +00:00
|
|
|
if v, found := c.secrets[key]; found {
|
|
|
|
return v.Value
|
|
|
|
}
|
2021-12-20 17:40:10 +00:00
|
|
|
var configValue Value
|
|
|
|
err := c.store.Get(key, &configValue)
|
2019-01-20 20:21:26 +00:00
|
|
|
if err != nil {
|
2019-03-07 16:35:42 +00:00
|
|
|
log.Debug().Msgf("WARN: Key %s is empty", key)
|
2019-01-22 00:16:57 +00:00
|
|
|
return fallback
|
2017-09-29 04:58:21 +00:00
|
|
|
}
|
2021-12-20 17:40:10 +00:00
|
|
|
return configValue.Value
|
2019-01-20 20:21:26 +00:00
|
|
|
}
|
|
|
|
|
2020-04-28 15:32:52 +00:00
|
|
|
func (c *Config) GetMap(key string, fallback map[string]string) map[string]string {
|
|
|
|
content := c.Get(key, "")
|
|
|
|
if content == "" {
|
|
|
|
return fallback
|
|
|
|
}
|
|
|
|
vals := map[string]string{}
|
|
|
|
err := json.Unmarshal([]byte(content), &vals)
|
|
|
|
if err != nil {
|
2020-10-21 19:55:28 +00:00
|
|
|
log.Error().Err(err).Msgf("Could not decode config for %s", key)
|
2020-04-28 15:32:52 +00:00
|
|
|
return fallback
|
|
|
|
}
|
|
|
|
return vals
|
|
|
|
}
|
|
|
|
|
2019-01-20 20:21:26 +00:00
|
|
|
// GetArray returns the string slice config value for a string key
|
|
|
|
// It will first look in the env vars for the key with ;; separated values
|
|
|
|
// Look, I'm too lazy to do parsing to ensure that a comma is what the user meant
|
2021-07-21 18:52:45 +00:00
|
|
|
// It will check the db for the key if an env DNE
|
2019-01-20 20:21:26 +00:00
|
|
|
// Finally, it will return a zero value if the key does not exist
|
|
|
|
// This will do no conversion.
|
2019-01-22 00:16:57 +00:00
|
|
|
func (c *Config) GetArray(key string, fallback []string) []string {
|
|
|
|
val := c.GetString(key, "")
|
2019-01-21 19:24:03 +00:00
|
|
|
if val == "" {
|
2019-01-22 00:16:57 +00:00
|
|
|
return fallback
|
2019-01-21 19:24:03 +00:00
|
|
|
}
|
2019-01-20 20:21:26 +00:00
|
|
|
return strings.Split(val, ";;")
|
|
|
|
}
|
|
|
|
|
2019-10-26 21:39:01 +00:00
|
|
|
func (c *Config) Unset(key string) error {
|
2021-12-20 17:40:10 +00:00
|
|
|
err := c.store.Delete(key, &Value{})
|
|
|
|
return err
|
2019-10-26 21:39:01 +00:00
|
|
|
}
|
|
|
|
|
2019-01-20 20:21:26 +00:00
|
|
|
// Set changes the value for a configuration in the database
|
|
|
|
// Note, this is always a string. Use the SetArray for an array helper
|
|
|
|
func (c *Config) Set(key, value string) error {
|
|
|
|
key = strings.ToLower(key)
|
2020-12-02 14:53:57 +00:00
|
|
|
value = strings.Trim(value, "`")
|
2021-12-20 17:40:10 +00:00
|
|
|
|
2021-12-21 04:31:19 +00:00
|
|
|
err := c.store.Upsert(key, Value{key, value})
|
2021-12-20 17:40:10 +00:00
|
|
|
return err
|
2019-01-20 20:21:26 +00:00
|
|
|
}
|
|
|
|
|
2021-05-20 13:59:28 +00:00
|
|
|
func (c *Config) RefreshSecrets() error {
|
|
|
|
var secrets []Secret
|
2021-12-20 17:40:10 +00:00
|
|
|
err := c.store.Find(&secrets, &bh.Query{})
|
2021-05-20 13:59:28 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
secretMap := map[string]Secret{}
|
|
|
|
for _, s := range secrets {
|
|
|
|
secretMap[s.Key] = s
|
|
|
|
}
|
|
|
|
c.secrets = secretMap
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Config) GetAllSecrets() map[string]Secret {
|
|
|
|
return c.secrets
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Config) SecretKeys() []string {
|
|
|
|
keys := []string{}
|
|
|
|
for k := range c.secrets {
|
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
return keys
|
|
|
|
}
|
|
|
|
|
2020-04-29 21:36:34 +00:00
|
|
|
func (c *Config) SetMap(key string, values map[string]string) error {
|
|
|
|
b, err := json.Marshal(values)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return c.Set(key, string(b))
|
|
|
|
}
|
|
|
|
|
2019-01-20 20:21:26 +00:00
|
|
|
func (c *Config) SetArray(key string, values []string) error {
|
|
|
|
vals := strings.Join(values, ";;")
|
|
|
|
return c.Set(key, vals)
|
2012-08-17 21:37:49 +00:00
|
|
|
}
|
2012-08-17 20:38:15 +00:00
|
|
|
|
2021-12-20 17:40:10 +00:00
|
|
|
//func init() {
|
|
|
|
// regex := func(re, s string) (bool, error) {
|
|
|
|
// return regexp.MatchString(re, s)
|
|
|
|
// }
|
|
|
|
// sql.Register("sqlite3_custom",
|
|
|
|
// &sqlite3.SQLiteDriver{
|
|
|
|
// ConnectHook: func(conn *sqlite3.SQLiteConn) error {
|
|
|
|
// return conn.RegisterFunc("REGEXP", regex, true)
|
|
|
|
// },
|
|
|
|
// })
|
|
|
|
//}
|
2017-07-25 17:58:04 +00:00
|
|
|
|
2012-08-17 21:37:49 +00:00
|
|
|
// Readconfig loads the config data out of a JSON file located in cfile
|
2021-12-21 04:31:19 +00:00
|
|
|
func ReadConfig(dbpath, storepath string) *Config {
|
2019-01-20 20:21:26 +00:00
|
|
|
if dbpath == "" {
|
|
|
|
dbpath = "catbase.db"
|
2012-08-17 20:38:15 +00:00
|
|
|
}
|
2021-12-20 17:40:10 +00:00
|
|
|
|
2021-12-21 04:31:19 +00:00
|
|
|
store, err := bh.Open(storepath, 0666, nil)
|
2021-12-20 17:40:10 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal().Err(err).Msgf("could not open bolthold")
|
|
|
|
}
|
|
|
|
|
2019-03-07 16:35:42 +00:00
|
|
|
log.Info().Msgf("Using %s as database file.\n", dbpath)
|
2012-08-17 20:38:15 +00:00
|
|
|
|
2021-12-20 17:40:10 +00:00
|
|
|
sqlDB, err := sqlx.Open("sqlite", dbpath)
|
2019-01-20 20:21:26 +00:00
|
|
|
if err != nil {
|
2021-12-20 17:40:10 +00:00
|
|
|
log.Fatal().Err(err).Msgf("could not open sqlite")
|
2012-08-17 20:38:15 +00:00
|
|
|
}
|
2019-01-20 20:21:26 +00:00
|
|
|
c := Config{
|
2021-05-20 13:59:28 +00:00
|
|
|
DBFile: dbpath,
|
|
|
|
secrets: map[string]Secret{},
|
2021-12-20 17:40:10 +00:00
|
|
|
store: store,
|
2016-03-10 18:37:07 +00:00
|
|
|
}
|
2021-12-20 17:40:10 +00:00
|
|
|
c.db = sqlDB
|
2016-03-10 18:37:07 +00:00
|
|
|
|
2021-12-20 17:40:10 +00:00
|
|
|
if _, err := c.db.Exec(`create table if not exists config (
|
2019-01-20 20:21:26 +00:00
|
|
|
key string,
|
|
|
|
value string,
|
|
|
|
primary key (key)
|
|
|
|
);`); err != nil {
|
2021-05-20 13:59:28 +00:00
|
|
|
log.Fatal().Err(err).Msgf("failed to initialize config")
|
|
|
|
}
|
|
|
|
|
2021-12-20 17:40:10 +00:00
|
|
|
if _, err := c.db.Exec(`create table if not exists secrets (
|
2021-05-20 13:59:28 +00:00
|
|
|
key string,
|
|
|
|
value string,
|
|
|
|
primary key (key)
|
|
|
|
);`); err != nil {
|
|
|
|
log.Fatal().Err(err).Msgf("failed to initialize config")
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := c.RefreshSecrets(); err != nil {
|
|
|
|
log.Fatal().Err(err).Msgf("failed to initialize config")
|
2017-07-25 17:58:04 +00:00
|
|
|
}
|
2019-01-20 20:21:26 +00:00
|
|
|
|
2019-03-07 16:35:42 +00:00
|
|
|
log.Info().Msgf("catbase is running.")
|
2017-07-25 17:58:04 +00:00
|
|
|
|
2021-12-20 17:40:10 +00:00
|
|
|
if err := c.migrate(); err != nil {
|
|
|
|
log.Fatal().Err(err).Msgf("could not migrate")
|
|
|
|
}
|
|
|
|
|
2012-08-17 20:38:15 +00:00
|
|
|
return &c
|
|
|
|
}
|
2021-12-20 17:40:10 +00:00
|
|
|
|
|
|
|
func (c *Config) migrate() error {
|
|
|
|
// check countSqliteEntries of config/secrets in sqlite
|
|
|
|
var countSqliteEntries int64
|
|
|
|
err := c.db.Get(&countSqliteEntries, "select count(*) as countSqliteEntries from config")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
countBoltEntries, err := c.store.Count(Value{}, &bh.Query{})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
countBoltSecrets, err := c.store.Count(Secret{}, &bh.Query{})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Debug().Msgf("Found %d sqlite entries and %d bolt entries and %d bolt secrets", countSqliteEntries, countBoltEntries, countBoltSecrets)
|
|
|
|
|
|
|
|
if countSqliteEntries == 0 || countBoltEntries > 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var items []Value
|
|
|
|
c.db.Select(&items, "select key, value from config")
|
|
|
|
|
|
|
|
log.Debug().Msgf("%d entries to migrate", len(items))
|
|
|
|
|
|
|
|
c.RefreshSecrets()
|
|
|
|
secrets := c.GetAllSecrets()
|
|
|
|
|
|
|
|
// add all to the bolthold
|
|
|
|
|
|
|
|
for _, it := range items {
|
|
|
|
err := c.store.Insert(it.Key, it)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, s := range secrets {
|
|
|
|
err := c.store.Insert(s.Key, s)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|