github.com/abolfazlbeh/zhycan@v0.0.0-20230819144214-24cf38237387/internal/config/manager.go (about) 1 package config 2 3 // Imports needed list 4 import ( 5 "fmt" 6 "github.com/fsnotify/fsnotify" 7 "github.com/spf13/viper" 8 "log" 9 "os" 10 "sync" 11 "time" 12 ) 13 14 // Mark: manager 15 16 // Manager object 17 type manager struct { 18 modules map[string]*ViperWrapper 19 modulesStatus map[string]bool 20 21 configBasePath string 22 configMode string 23 24 configRemoteAddress string 25 configRemoteInfra string 26 configRemoteDuration int64 27 28 quitCh chan bool 29 } 30 31 // MARK: Module variables 32 var providerInstance *manager = nil 33 var once sync.Once 34 35 // MARK: Module Initializer 36 func init() { 37 log.Println("Initializing Config Provider ...") 38 } 39 40 // MARK: Private Methods 41 42 // constructor - Constructor -> It initializes the config configuration params 43 func constructor(configBasePath string, configInitialMode string, configEnvPrefix string) error { 44 log.Println("Config Manager Initializer ...") 45 46 providerInstance.configMode = configInitialMode 47 providerInstance.configBasePath = configBasePath 48 49 viper.SetEnvPrefix(configEnvPrefix) 50 err := viper.BindEnv("mode") 51 if err != nil { 52 return err 53 } 54 55 err = viper.BindEnv("name") 56 if err != nil { 57 return err 58 } 59 60 err = viper.BindEnv("config_remote_addr") 61 if err != nil { 62 return err 63 } 64 65 err = viper.BindEnv("config_remote_infra") 66 if err != nil { 67 return err 68 } 69 70 err = viper.BindEnv("config_remote_duration") 71 if err != nil { 72 return err 73 } 74 75 mode := viper.Get("mode") 76 if mode != nil { 77 providerInstance.configMode = mode.(string) 78 } 79 80 viper.AddConfigPath(fmt.Sprintf("%s/configs/%s/", providerInstance.configBasePath, providerInstance.configMode)) 81 viper.SetConfigName("base") 82 err = viper.ReadInConfig() 83 if err != nil { 84 return err 85 } 86 87 // Load all modules 88 configRemoteAddr := viper.GetString("config_remote_addr") 89 configRemoteInfra := viper.GetString("config_remote_infra") 90 configRemoteDuration := viper.GetInt64("config_remote_duration") 91 92 providerInstance.configRemoteInfra = configRemoteInfra 93 providerInstance.configRemoteAddress = configRemoteAddr 94 providerInstance.configRemoteDuration = configRemoteDuration 95 96 providerInstance.loadModules() 97 98 log.Printf("Read Base `%s` Configs", viper.GetString("name")) 99 mustWatched := viper.GetBool("config_must_watched") 100 if mustWatched { 101 viper.WatchConfig() 102 viper.OnConfigChange(func(in fsnotify.Event) { 103 log.Println("Configs Changed: ", in.Name) 104 }) 105 } 106 return nil 107 } 108 109 // loadModules - Loads All Modules That is configured in "init" config file 110 func (p *manager) loadModules() { 111 log.Println("Load All Modules Config ...") 112 modules := viper.Get("modules") 113 114 for _, item2 := range modules.([]interface{}) { 115 item := item2.(map[string]interface{}) 116 name := item["name"].(string) 117 118 w := &ViperWrapper{ 119 ConfigPath: []string{fmt.Sprintf("%s/configs/%s/", p.configBasePath, p.configMode)}, 120 ConfigName: item["name"].(string), 121 ConfigResourcePlace: item["type"].(string), 122 } 123 124 err := w.Load() 125 if err == nil { 126 p.modules[name] = w 127 p.modulesStatus[name] = true 128 } else { 129 p.modulesStatus[name] = false 130 } 131 } 132 //else if src == "remote" { 133 // // Start a goroutine 134 // for _, item := range modules { 135 // w := &ViperWrapper{ 136 // ConfigName: item, 137 // } 138 // 139 // p.modules[item] = w 140 // p.modulesStatus[item] = false 141 // } 142 // 143 // // start remote loader as go routines 144 // go p.remoteConfigLoader() 145 //} 146 } 147 148 // MARK: Public Methods 149 150 // CreateManager - Create a new manager instance 151 func CreateManager(configBasePath string, configInitialMode string, configEnvPrefix string) error { 152 // once used for prevent race condition and manage critical section. 153 if providerInstance == nil { 154 var err error 155 once.Do(func() { 156 providerInstance = &manager{ 157 modules: make(map[string]*ViperWrapper), 158 modulesStatus: make(map[string]bool), 159 quitCh: make(chan bool), 160 } 161 162 for item := range providerInstance.modulesStatus { 163 providerInstance.modulesStatus[item] = false 164 } 165 166 err = constructor(configBasePath, configInitialMode, configEnvPrefix) 167 }) 168 return err 169 } 170 return nil 171 } 172 173 func GetManager() *manager { 174 return providerInstance 175 } 176 177 // GetConfigWrapper - returns Config Wrapper based on name 178 func (p *manager) GetConfigWrapper(category string) (*ViperWrapper, error) { 179 if val, ok := p.modules[category]; ok { 180 return val, nil 181 } 182 183 return nil, NewCategoryNotExistErr(category, nil) 184 } 185 186 // GetName - returns service instance name based on config 187 func (p *manager) GetName() string { 188 return viper.GetString("name") 189 } 190 191 // GetOperationType - returns operation type which could be `dev`, `prod` 192 func (p *manager) GetOperationType() string { 193 return p.configMode 194 } 195 196 // GetHostName - returns hostname based on config 197 func (p *manager) GetHostName() string { 198 return os.Getenv(fmt.Sprintf("%s_HOSTNAME", p.GetName())) 199 } 200 201 // Get - get value of the key in specific category 202 func (p *manager) Get(category string, name string) (interface{}, error) { 203 if val, ok := p.modules[category]; ok { 204 result, exist := val.Get(name, false) 205 if exist { 206 return result, nil 207 } 208 209 return nil, NewKeyNotExistErr(name, category, nil) 210 } 211 212 return nil, NewCategoryNotExistErr(name, nil) 213 } 214 215 // Set - set value in category by specified key. 216 func (p *manager) Set(category string, name string, value interface{}) error { 217 if val, ok := p.modules[category]; ok { 218 return val.Set(name, value, false) 219 } 220 221 return NewCategoryNotExistErr(category, nil) 222 } 223 224 // StopLoader - stop remote loader 225 func (p *manager) StopLoader() { 226 //if p.ConfigSrc == "remote" { 227 // p.quitCh <- true 228 //} 229 } 230 231 // IsInitialized - iterate over all config wrappers and see all initialised correctly 232 func (p *manager) IsInitialized() bool { 233 flag := true 234 for _, value := range p.modulesStatus { 235 if value == false { 236 flag = false 237 break 238 } 239 } 240 return flag 241 } 242 243 // GetAllInitializedModuleList - get list of names that initialized truly 244 func (p *manager) GetAllInitializedModuleList() []string { 245 var result []string 246 for key, val := range p.modulesStatus { 247 if val { 248 result = append(result, key) 249 } 250 } 251 252 return result 253 } 254 255 // remoteConfigLoader - get configs from remote 256 func (p *manager) remoteConfigLoader() { 257 for { 258 select { 259 case <-p.quitCh: 260 return 261 default: 262 for key := range p.modulesStatus { 263 data, err := p.remoteConfigLoad(key) 264 if err == nil { 265 err = p.modules[key].LoadFromRemote(data) 266 if err == nil { 267 p.modulesStatus[key] = true 268 } else { 269 log.Println(err.Error()) 270 } 271 } else { 272 log.Println(err.Error()) 273 } 274 } 275 } 276 277 time.Sleep(time.Duration(p.configRemoteDuration) * time.Second) 278 } 279 } 280 281 // remoteConfigLoad 282 func (p *manager) remoteConfigLoad(key string) ([]byte, error) { 283 //if p.ConfigRemoteAddress != "" { 284 // if p.ConfigRemoteInfra == "grpc" { 285 // conn, err := grpc.Dial(p.ConfigRemoteAddress, grpc.WithTransportCredentials(insecure.NewCredentials())) 286 // if err != nil { 287 // return nil, protoerror.GrpcDialError{Addr: p.ConfigRemoteAddress, Err: err} 288 // } 289 // defer conn.Close() 290 // 291 // localContext, cancel := context.WithTimeout(context.Background(), 2*time.Second) 292 // defer cancel() 293 // 294 // c := api.NewHelpClient(conn) 295 // response, err := c.GetServiceConfig(localContext, &api.ServiceConfigRequest{ 296 // Section: key, 297 // Hostname: p.GetHostName(), 298 // ConstructorId: api.ConstructorId_V1901, 299 // ServiceName: p.GetName(), 300 // MsgId: timestamppb.Now()}) 301 // if err != nil { 302 // return nil, RemoteResponseError{Err: err} 303 // } 304 // 305 // return []byte(response.Data), nil 306 // } 307 //} 308 return nil, NewRemoteLoadErr(key, nil) 309 } 310 311 // ManualLoadConfig - load manual config from the path and add to the current dict 312 func (p *manager) ManualLoadConfig(configBasePath string, configName string) error { 313 w := &ViperWrapper{ 314 ConfigPath: []string{configBasePath}, 315 ConfigName: configName, 316 ConfigResourcePlace: "", 317 } 318 319 err := w.Load() 320 if err == nil { 321 p.modules[configName] = w 322 p.modulesStatus[configName] = true 323 } else { 324 p.modulesStatus[configName] = false 325 return err 326 } 327 return nil 328 }