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  }