gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

state.go (4217B)


      1 // GoToSocial
      2 // Copyright (C) GoToSocial Authors admin@gotosocial.org
      3 // SPDX-License-Identifier: AGPL-3.0-or-later
      4 //
      5 // This program is free software: you can redistribute it and/or modify
      6 // it under the terms of the GNU Affero General Public License as published by
      7 // the Free Software Foundation, either version 3 of the License, or
      8 // (at your option) any later version.
      9 //
     10 // This program is distributed in the hope that it will be useful,
     11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 // GNU Affero General Public License for more details.
     14 //
     15 // You should have received a copy of the GNU Affero General Public License
     16 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
     17 
     18 package config
     19 
     20 import (
     21 	"strings"
     22 	"sync"
     23 
     24 	"github.com/mitchellh/mapstructure"
     25 	"github.com/spf13/cobra"
     26 	"github.com/spf13/viper"
     27 )
     28 
     29 // ConfigState manages safe concurrent access to Configuration{} values,
     30 // and provides ease of linking them (including reloading) via viper to
     31 // environment, CLI and configuration file variables.
     32 type ConfigState struct { //nolint
     33 	viper  *viper.Viper
     34 	config Configuration
     35 	mutex  sync.Mutex
     36 }
     37 
     38 // NewState returns a new initialized ConfigState instance.
     39 func NewState() *ConfigState {
     40 	viper := viper.New()
     41 
     42 	// Flag 'some-flag-name' becomes env var 'GTS_SOME_FLAG_NAME'
     43 	viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
     44 	viper.SetEnvPrefix("gts")
     45 
     46 	// Load appropriate named vals from env
     47 	viper.AutomaticEnv()
     48 
     49 	// Create new ConfigState with defaults
     50 	state := &ConfigState{
     51 		viper:  viper,
     52 		config: Defaults,
     53 	}
     54 
     55 	// Perform initial load into viper
     56 	state.reloadToViper()
     57 
     58 	return state
     59 }
     60 
     61 // Config provides safe access to the ConfigState's contained Configuration,
     62 // and will reload the current Configuration back into viper settings.
     63 func (st *ConfigState) Config(fn func(*Configuration)) {
     64 	st.mutex.Lock()
     65 	defer func() {
     66 		st.reloadToViper()
     67 		st.mutex.Unlock()
     68 	}()
     69 	fn(&st.config)
     70 }
     71 
     72 // Viper provides safe access to the ConfigState's contained viper instance,
     73 // and will reload the current viper setting state back into Configuration.
     74 func (st *ConfigState) Viper(fn func(*viper.Viper)) {
     75 	st.mutex.Lock()
     76 	defer func() {
     77 		st.reloadFromViper()
     78 		st.mutex.Unlock()
     79 	}()
     80 	fn(st.viper)
     81 }
     82 
     83 // LoadEarlyFlags will bind specific flags from given Cobra command to ConfigState's viper
     84 // instance, and load the current configuration values. This is useful for flags like
     85 // .ConfigPath which have to parsed first in order to perform early configuration load.
     86 func (st *ConfigState) LoadEarlyFlags(cmd *cobra.Command) (err error) {
     87 	name := ConfigPathFlag()
     88 	flag := cmd.Flags().Lookup(name)
     89 	st.Viper(func(v *viper.Viper) {
     90 		err = v.BindPFlag(name, flag)
     91 	})
     92 	return
     93 }
     94 
     95 // BindFlags will bind given Cobra command's pflags to this ConfigState's viper instance.
     96 func (st *ConfigState) BindFlags(cmd *cobra.Command) (err error) {
     97 	st.Viper(func(v *viper.Viper) {
     98 		err = v.BindPFlags(cmd.Flags())
     99 	})
    100 	return
    101 }
    102 
    103 // Reload will reload the Configuration values from ConfigState's viper instance, and from file if set.
    104 func (st *ConfigState) Reload() (err error) {
    105 	st.Viper(func(v *viper.Viper) {
    106 		if st.config.ConfigPath != "" {
    107 			// Ensure configuration path is set
    108 			v.SetConfigFile(st.config.ConfigPath)
    109 
    110 			// Read in configuration from file
    111 			if err = v.ReadInConfig(); err != nil {
    112 				return
    113 			}
    114 		}
    115 	})
    116 	return
    117 }
    118 
    119 // reloadToViper will reload Configuration{} values into viper.
    120 func (st *ConfigState) reloadToViper() {
    121 	raw, err := st.config.MarshalMap()
    122 	if err != nil {
    123 		panic(err)
    124 	}
    125 	if err := st.viper.MergeConfigMap(raw); err != nil {
    126 		panic(err)
    127 	}
    128 }
    129 
    130 // reloadFromViper will reload Configuration{} values from viper.
    131 func (st *ConfigState) reloadFromViper() {
    132 	if err := st.viper.Unmarshal(&st.config, func(c *mapstructure.DecoderConfig) {
    133 		c.TagName = "name"
    134 
    135 		// empty config before marshaling
    136 		c.ZeroFields = true
    137 
    138 		oldhook := c.DecodeHook
    139 
    140 		// Use the TextUnmarshaler interface when decoding.
    141 		c.DecodeHook = mapstructure.ComposeDecodeHookFunc(
    142 			mapstructure.TextUnmarshallerHookFunc(),
    143 			oldhook,
    144 		)
    145 	}); err != nil {
    146 		panic(err)
    147 	}
    148 }