package config import ( "bytes" "encoding/gob" "encoding/json" "fmt" "sync" "time" "github.com/boltdb/bolt" "github.com/gorilla/mux" "github.com/rs/zerolog/log" "code.chrissexton.org/cws/lameralert/mapping" ) var ( bucket = []byte("config") ) type configValue struct { Key string `json:"key"` Value interface{} `json:"value"` } func (value configValue) getBytes() ([]byte, error) { return json.Marshal(value) } func decodeConfig(in []byte, target *configValue) error { err := json.Unmarshal(in, target) return err } func encodeMapping(in mapping.Mapping) ([]byte, error) { var out []byte buf := bytes.NewBuffer(out) enc := gob.NewEncoder(buf) err := enc.Encode(in) return buf.Bytes(), err } func decodeMapping(in []byte) (mapping.Mapping, error) { var target mapping.Mapping dec := gob.NewDecoder(bytes.NewBuffer(in)) err := dec.Decode(&target) log.Debug(). Interface("err", err). Str("in", string(in)). Interface("target", target). Msg("decodeMapping") return target, err } type Config struct { // I'm not completely sure if this mutex is necessary. // It is left here because I'm opening an closing the db // for every operation. sync.Mutex path string } func (c *Config) db() *bolt.DB { db, err := bolt.Open(c.path, 0666, &bolt.Options{ Timeout: 1 * time.Second, }) if err != nil { log.Fatal().Msgf("Could not open database: %s", err) } err = db.Update(func(tx *bolt.Tx) error { _, err := tx.CreateBucketIfNotExists(bucket) return err }) if err != nil { log.Fatal().Err(err).Msg("Could not open bucket") } return db } func (c *Config) getAll() (map[string]configValue, error) { c.Lock() defer c.Unlock() db := c.db() defer db.Close() vals := map[string]configValue{} err := db.View(func(tx *bolt.Tx) error { b := tx.Bucket(bucket) err := b.ForEach(func(k, v []byte) error { var value configValue err := decodeConfig(v, &value) if err != nil { return fmt.Errorf("could not convert value: %w", err) } vals[string(k)] = value return nil }) if err != nil { return fmt.Errorf("error in forEach: %w", err) } return nil }) return vals, err } func (c *Config) getMapping(key string) (mapping.Mapping, error) { log.Debug().Msgf("getMapping(%s)", key) c.Lock() defer c.Unlock() db := c.db() defer db.Close() var err error var out mapping.Mapping err = db.View(func(tx *bolt.Tx) error { b := tx.Bucket(bucket) out, err = decodeMapping(b.Get([]byte(key))) log.Debug().AnErr("err", err).Interface("out", out).Msgf("decode %s", key) if err != nil { log.Error().Msgf("Error decoding: %w", err) return err } return nil }) if err != nil { log.Error().Msgf("Error getting: %w", err) return mapping.Mapping{}, err } return out, nil } func (c *Config) get(key string) (configValue, error) { c.Lock() defer c.Unlock() db := c.db() defer db.Close() var out configValue err := db.View(func(tx *bolt.Tx) error { b := tx.Bucket(bucket) err := decodeConfig(b.Get([]byte(key)), &out) if err != nil { return err } return nil }) if err != nil { return configValue{}, err } return out, nil } func (c *Config) setMapping(key string, value mapping.Mapping) error { c.Lock() defer c.Unlock() db := c.db() defer db.Close() err := db.Update(func(tx *bolt.Tx) error { b := tx.Bucket(bucket) v, err := encodeMapping(value) if err != nil { return err } err = b.Put([]byte(key), v) return err }) if err != nil { log.Error().Msgf("Error setting key: %s", err) } return err } func (c *Config) set(key string, value configValue) error { c.Lock() defer c.Unlock() db := c.db() defer db.Close() err := db.Update(func(tx *bolt.Tx) error { b := tx.Bucket(bucket) v, err := value.getBytes() if err != nil { return err } err = b.Put([]byte(key), v) return err }) if err != nil { log.Error().Msgf("Error setting key: %s", err) } return err } func (c *Config) delete(key string) error { c.Lock() defer c.Unlock() db := c.db() defer db.Close() err := db.Update(func(tx *bolt.Tx) error { b := tx.Bucket(bucket) return b.Delete([]byte(key)) }) return err } // Public interface func New(r *mux.Router) *Config { c := &Config{ path: "lameralert.db", } c.setup(r) return c } func (c *Config) GetInt64(key string, defaultValue int64) int64 { v, err := c.get(key) if err != nil { return defaultValue } return int64(v.Value.(float64)) } func (c *Config) GetInt(key string, defaultValue int) int { v, err := c.get(key) if err != nil { return defaultValue } return int(v.Value.(float64)) } func (c *Config) GetStringSlice(key string, defaultValue []string) []string { v, err := c.get(key) if err != nil { log.Error().Msgf("GetStringSlice errored looking for %s: %s", key, err) return defaultValue } return interfaceSliceToStringSlice(v.Value.([]interface{})) } func interfaceSliceToStringSlice(in []interface{}) []string { out := []string{} for _, it := range in { out = append(out, it.(string)) } return out } func (c *Config) GetStringMap(key string, defaultValue map[string]string) map[string]string { v, err := c.get(key) if err != nil { return defaultValue } return v.Value.(map[string]string) } func (c *Config) GetString(key, defaultValue string) string { v, err := c.get(key) if err != nil { return defaultValue } return v.Value.(string) } func (c *Config) GetMapping(key string, defaultValue mapping.Mapping) mapping.Mapping { m, err := c.getMapping(key) if err != nil { log.Error().Msgf("Error: %w", err) return defaultValue } return m } func (c *Config) GetGeneric(key string, defaultValue interface{}) interface{} { v, err := c.get(key) if err != nil { return defaultValue } return v.Value } func (c *Config) SetMapping(key string, value mapping.Mapping) error { return c.setMapping(key, value) } func (c *Config) Set(key string, value interface{}) error { return c.set(key, configValue{key, value}) }