github.com/criteo/command-launcher@v0.0.0-20230407142452-fb616f546e98/internal/config/load.go (about) 1 package config 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "path/filepath" 8 "time" 9 10 "github.com/criteo/command-launcher/internal/context" 11 "github.com/criteo/command-launcher/internal/helper" 12 log "github.com/sirupsen/logrus" 13 14 "github.com/spf13/viper" 15 ) 16 17 const REMOTE_CONFIG_CHECK_TIME_KEY = "REMOTE_CONFIG_CHECK_TIME" 18 const REMOTE_CONFIG_CHECK_CYCLE_KEY = "REMOTE_CONFIG_CHECK_CYCLE" 19 20 // store some metadata about the configuration settings 21 type ConfigMetadata struct { 22 File string 23 Reason string 24 } 25 26 var ( 27 configMetadata = ConfigMetadata{} 28 ) 29 30 func LoadConfig(appCtx context.LauncherContext) { 31 // NOTE: we don't put default value for the DEBUG_FLAGS configuration, it will not show in a newly created config file 32 // Please keep it as a hidden config, better not to let developer directly see this option 33 setDefaultConfig() 34 wd, _ := os.Getwd() 35 cfgFile := os.Getenv(appCtx.ConfigurationFileEnvVar()) 36 localCftFileName := fmt.Sprintf("%s.json", appCtx.AppName()) 37 appDir := AppDir() 38 if cfgFile != "" { 39 configMetadata.Reason = fmt.Sprintf("from environment variable: %s", appCtx.ConfigurationFileEnvVar()) 40 configMetadata.File = cfgFile 41 42 viper.SetConfigFile(cfgFile) 43 } else if localCfgFile, found := findLocalConfig(wd, localCftFileName); found { 44 configMetadata.Reason = fmt.Sprintf("found config file from working dir or its parents: %s", localCfgFile) 45 configMetadata.File = localCfgFile 46 47 viper.SetConfigFile(localCfgFile) 48 } else { 49 configMetadata.Reason = fmt.Sprintf("use default config file from app home %s", appDir) 50 configMetadata.File = fmt.Sprintf("%s/config.json", appDir) 51 52 viper.AddConfigPath(appDir) 53 viper.SetConfigType("json") 54 viper.SetConfigName("config") 55 } 56 57 viper.AutomaticEnv() 58 59 if err := viper.ReadInConfig(); err != nil { 60 if _, ok := err.(viper.ConfigFileNotFoundError); ok { 61 initDefaultConfigFile() 62 } else { 63 log.Fatal("Cannot read configuration file: ", err) 64 } 65 } 66 // load remote config first 67 remoteConfigLoaded := loadRemoteConfig(appCtx) 68 69 if remoteConfigLoaded { 70 log.Info("Remote Configuration loaded...") 71 viper.WriteConfig() 72 } 73 74 } 75 76 func setDefaultConfig() { 77 appDir := AppDir() 78 viper.SetDefault(LOG_ENABLED_KEY, false) 79 viper.SetDefault(LOG_LEVEL_KEY, "fatal") // trace, debug, info, warn, error, fatal, panic 80 81 viper.SetDefault(SELF_UPDATE_ENABLED_KEY, false) 82 viper.SetDefault(SELF_UPDATE_TIMEOUT_KEY, 2*time.Second) // In seconds 83 viper.SetDefault(SELF_UPDATE_LATEST_VERSION_URL_KEY, "") 84 viper.SetDefault(SELF_UPDATE_BASE_URL_KEY, "") 85 86 viper.SetDefault(COMMAND_UPDATE_ENABLED_KEY, false) 87 viper.SetDefault(COMMAND_REPOSITORY_BASE_URL_KEY, "") 88 89 viper.SetDefault(DROPIN_FOLDER_KEY, filepath.Join(appDir, "dropins")) 90 viper.SetDefault(LOCAL_COMMAND_REPOSITORY_DIRNAME_KEY, filepath.Join(appDir, "current")) 91 92 viper.SetDefault(USAGE_METRICS_ENABLED_KEY, false) 93 viper.SetDefault(METRIC_GRAPHITE_HOST_KEY, "dummy") 94 95 viper.SetDefault(INTERNAL_COMMAND_ENABLED_KEY, false) 96 viper.SetDefault(EXPERIMENTAL_COMMAND_ENABLED_KEY, false) 97 // set default remote config time to now, so that the first run it will always check 98 viper.SetDefault(REMOTE_CONFIG_CHECK_TIME_KEY, time.Now()) 99 // set default remote config check cycle to 24 hours 100 viper.SetDefault(REMOTE_CONFIG_CHECK_CYCLE_KEY, 24) 101 102 viper.SetDefault(CI_ENABLED_KEY, false) 103 viper.SetDefault(PACKAGE_LOCK_FILE_KEY, filepath.Join(appDir, "lock.json")) 104 105 viper.SetDefault(ENABLE_USER_CONSENT_KEY, false) 106 viper.SetDefault(USER_CONSENT_LIFE_KEY, 7*24*time.Hour) 107 108 viper.SetDefault(SYSTEM_PACKAGE_KEY, "") 109 viper.SetDefault(SYSTEM_PACKAGE_PUBLIC_KEY_KEY, "") 110 viper.SetDefault(SYSTEM_PACKAGE_PUBLIC_KEY_FILE_KEY, "") 111 112 viper.SetDefault(VERIFY_PACKAGE_CHECKSUM_KEY, false) 113 viper.SetDefault(VERIFY_PACKAGE_SIGNATURE_KEY, false) 114 115 viper.SetDefault(EXTRA_REMOTES_KEY, []map[string]string{}) 116 viper.SetDefault(ENABLE_PACKAGE_SETUP_HOOK_KEY, false) 117 } 118 119 func initDefaultConfigFile() { 120 log.Info("Create default config file") 121 createAppDir() 122 if err := viper.SafeWriteConfig(); err != nil { 123 log.Error("cannot write the default configuration: ", err) 124 } 125 } 126 127 func findLocalConfig(startPath string, configFileName string) (string, bool) { 128 wd := startPath 129 checked := "" 130 found := hasConfigFile(wd, configFileName) 131 for !found && wd != checked { 132 checked = wd 133 wd = filepath.Dir(wd) 134 found = hasConfigFile(wd, configFileName) 135 } 136 137 if found { 138 return filepath.Join(wd, configFileName), true 139 } else { 140 return "", false 141 } 142 } 143 144 func hasConfigFile(configRootPath string, configFileName string) bool { 145 _, err := os.Stat(filepath.Join(configRootPath, configFileName)) 146 if err == nil { 147 return true 148 } 149 return false 150 } 151 152 func loadRemoteConfig(appCtx context.LauncherContext) bool { 153 if urlCfg := os.Getenv(appCtx.RemoteConfigurationUrlEnvVar()); urlCfg != "" { 154 remoteCheckTime := viper.GetTime(REMOTE_CONFIG_CHECK_TIME_KEY) 155 checkCycle := viper.GetInt(REMOTE_CONFIG_CHECK_CYCLE_KEY) 156 if checkCycle == 0 { 157 checkCycle = 24 158 } 159 160 // check if we have passed the remote check time 161 if time.Now().Before(remoteCheckTime) { 162 return false 163 } 164 165 viper.Set(REMOTE_CONFIG_CHECK_TIME_KEY, time.Now().Add(time.Duration(checkCycle)*time.Hour)) 166 viper.Set(REMOTE_CONFIG_CHECK_CYCLE_KEY, checkCycle) 167 data, err := helper.LoadFile(urlCfg) 168 if err != nil { 169 return false 170 } 171 172 err = viper.MergeConfig(bytes.NewReader(data)) 173 return err == nil 174 } 175 176 return false 177 }