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 }