| 
									
										
										
										
											2023-03-12 16:00:57 +01:00
										 |  |  | // GoToSocial | 
					
						
							|  |  |  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | 
					
						
							|  |  |  | // SPDX-License-Identifier: AGPL-3.0-or-later | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This program is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  | // it under the terms of the GNU Affero General Public License as published by | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  | // (at your option) any later version. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
					
						
							|  |  |  | // GNU Affero General Public License for more details. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // You should have received a copy of the GNU Affero General Public License | 
					
						
							|  |  |  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | package config | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2025-05-06 15:51:45 +00:00
										 |  |  | 	"os" | 
					
						
							|  |  |  | 	"path" | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 	"strings" | 
					
						
							|  |  |  | 	"sync" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/spf13/cobra" | 
					
						
							|  |  |  | 	"github.com/spf13/viper" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ConfigState manages safe concurrent access to Configuration{} values, | 
					
						
							|  |  |  | // and provides ease of linking them (including reloading) via viper to | 
					
						
							|  |  |  | // environment, CLI and configuration file variables. | 
					
						
							|  |  |  | type ConfigState struct { //nolint | 
					
						
							|  |  |  | 	viper  *viper.Viper | 
					
						
							|  |  |  | 	config Configuration | 
					
						
							| 
									
										
										
										
											2023-07-10 13:56:14 +02:00
										 |  |  | 	mutex  sync.RWMutex | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewState returns a new initialized ConfigState instance. | 
					
						
							|  |  |  | func NewState() *ConfigState { | 
					
						
							| 
									
										
										
										
											2024-05-27 15:46:15 +00:00
										 |  |  | 	st := new(ConfigState) | 
					
						
							|  |  |  | 	st.Reset() | 
					
						
							|  |  |  | 	return st | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Config provides safe access to the ConfigState's contained Configuration, | 
					
						
							|  |  |  | // and will reload the current Configuration back into viper settings. | 
					
						
							|  |  |  | func (st *ConfigState) Config(fn func(*Configuration)) { | 
					
						
							|  |  |  | 	st.mutex.Lock() | 
					
						
							| 
									
										
										
										
											2025-05-06 15:51:45 +00:00
										 |  |  | 	defer st.mutex.Unlock() | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 	fn(&st.config) | 
					
						
							| 
									
										
										
										
											2025-05-06 15:51:45 +00:00
										 |  |  | 	st.reloadToViper() | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Viper provides safe access to the ConfigState's contained viper instance, | 
					
						
							|  |  |  | // and will reload the current viper setting state back into Configuration. | 
					
						
							|  |  |  | func (st *ConfigState) Viper(fn func(*viper.Viper)) { | 
					
						
							|  |  |  | 	st.mutex.Lock() | 
					
						
							| 
									
										
										
										
											2025-05-06 15:51:45 +00:00
										 |  |  | 	defer st.mutex.Unlock() | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 	fn(st.viper) | 
					
						
							| 
									
										
										
										
											2025-05-06 15:51:45 +00:00
										 |  |  | 	st.reloadFromViper() | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-06 15:51:45 +00:00
										 |  |  | // RegisterGlobalFlags ... | 
					
						
							|  |  |  | func (st *ConfigState) RegisterGlobalFlags(root *cobra.Command) { | 
					
						
							|  |  |  | 	st.mutex.RLock() | 
					
						
							|  |  |  | 	st.config.RegisterFlags(root.PersistentFlags()) | 
					
						
							|  |  |  | 	st.mutex.RUnlock() | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // BindFlags will bind given Cobra command's pflags to this ConfigState's viper instance. | 
					
						
							|  |  |  | func (st *ConfigState) BindFlags(cmd *cobra.Command) (err error) { | 
					
						
							|  |  |  | 	st.Viper(func(v *viper.Viper) { | 
					
						
							|  |  |  | 		err = v.BindPFlags(cmd.Flags()) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	return | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-06 15:51:45 +00:00
										 |  |  | // LoadConfigFile loads the currently set configuration file into this ConfigState's viper instance. | 
					
						
							|  |  |  | func (st *ConfigState) LoadConfigFile() (err error) { | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 	st.Viper(func(v *viper.Viper) { | 
					
						
							| 
									
										
										
										
											2025-05-06 15:51:45 +00:00
										 |  |  | 		if path := st.config.ConfigPath; path != "" { | 
					
						
							|  |  |  | 			var cfgmap map[string]any | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-06 15:51:45 +00:00
										 |  |  | 			// Read config map into memory. | 
					
						
							|  |  |  | 			cfgmap, err := readConfigMap(path) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// Merge the parsed config into viper. | 
					
						
							|  |  |  | 			err = st.viper.MergeConfigMap(cfgmap) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	return | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-27 15:46:15 +00:00
										 |  |  | // Reset will totally clear | 
					
						
							|  |  |  | // ConfigState{}, loading defaults. | 
					
						
							|  |  |  | func (st *ConfigState) Reset() { | 
					
						
							|  |  |  | 	// Do within lock. | 
					
						
							|  |  |  | 	st.mutex.Lock() | 
					
						
							|  |  |  | 	defer st.mutex.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Create new viper. | 
					
						
							| 
									
										
										
										
											2025-05-06 15:51:45 +00:00
										 |  |  | 	st.viper = viper.New() | 
					
						
							| 
									
										
										
										
											2024-05-27 15:46:15 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Flag 'some-flag-name' becomes env var 'GTS_SOME_FLAG_NAME' | 
					
						
							| 
									
										
										
										
											2025-05-06 15:51:45 +00:00
										 |  |  | 	st.viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) | 
					
						
							|  |  |  | 	st.viper.SetEnvPrefix("gts") | 
					
						
							| 
									
										
										
										
											2024-05-27 15:46:15 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Load appropriate | 
					
						
							|  |  |  | 	// named vals from env. | 
					
						
							| 
									
										
										
										
											2025-05-06 15:51:45 +00:00
										 |  |  | 	st.viper.AutomaticEnv() | 
					
						
							| 
									
										
										
										
											2024-05-27 15:46:15 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-06 15:51:45 +00:00
										 |  |  | 	// Set default config. | 
					
						
							| 
									
										
										
										
											2024-05-27 15:46:15 +00:00
										 |  |  | 	st.config = Defaults | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Load into viper. | 
					
						
							|  |  |  | 	st.reloadToViper() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | // reloadToViper will reload Configuration{} values into viper. | 
					
						
							|  |  |  | func (st *ConfigState) reloadToViper() { | 
					
						
							| 
									
										
										
										
											2025-05-06 15:51:45 +00:00
										 |  |  | 	if err := st.viper.MergeConfigMap(st.config.MarshalMap()); err != nil { | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 		panic(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // reloadFromViper will reload Configuration{} values from viper. | 
					
						
							|  |  |  | func (st *ConfigState) reloadFromViper() { | 
					
						
							| 
									
										
										
										
											2025-05-06 15:51:45 +00:00
										 |  |  | 	if err := st.config.UnmarshalMap(st.viper.AllSettings()); err != nil { | 
					
						
							|  |  |  | 		panic(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-12-11 13:03:15 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-06 15:51:45 +00:00
										 |  |  | // readConfigMap reads given configuration file into memory, | 
					
						
							|  |  |  | // using viper's codec registry to handle decoding into a map, | 
					
						
							|  |  |  | // flattening the result for standardization, returning this. | 
					
						
							|  |  |  | // this ensures the stored config map in viper always has the | 
					
						
							|  |  |  | // same level of nesting, given we support varying levels. | 
					
						
							|  |  |  | func readConfigMap(file string) (map[string]any, error) { | 
					
						
							|  |  |  | 	ext := path.Ext(file) | 
					
						
							|  |  |  | 	ext = strings.TrimPrefix(ext, ".") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	registry := viper.NewCodecRegistry() | 
					
						
							|  |  |  | 	dec, err := registry.Decoder(ext) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-09-29 21:50:43 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-06 15:51:45 +00:00
										 |  |  | 	data, err := os.ReadFile(file) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-12-11 13:03:15 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-06 15:51:45 +00:00
										 |  |  | 	cfgmap := make(map[string]any) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := dec.Decode(data, cfgmap); err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-05-06 15:51:45 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	flattenConfigMap(cfgmap) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return cfgmap, nil | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | } |