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  }