1// Package conf contains configuration structures used to setup the SDK 2package conf 3 4import ( 5 "errors" 6 "fmt" 7 "math" 8 "os/user" 9 "path" 10 "strings" 11 12 impressionlistener "github.com/splitio/go-client/v6/splitio/impressionListener" 13 "github.com/splitio/go-split-commons/v3/conf" 14 "github.com/splitio/go-toolkit/v4/datastructures/set" 15 "github.com/splitio/go-toolkit/v4/logging" 16 "github.com/splitio/go-toolkit/v4/nethelpers" 17) 18 19const ( 20 // RedisConsumer mode 21 RedisConsumer = "redis-consumer" 22 // Localhost mode 23 Localhost = "localhost" 24 // InMemoryStandAlone mode 25 InMemoryStandAlone = "inmemory-standalone" 26) 27 28// SplitSdkConfig struct ... 29// struct used to setup a Split.io SDK client. 30// 31// Parameters: 32// - OperationMode (Required) Must be one of ["inmemory-standalone", "redis-consumer"] 33// - InstanceName (Optional) Name to be used when submitting metrics & impressions to split servers 34// - IPAddress (Optional) Address to be used when submitting metrics & impressions to split servers 35// - BlockUntilReady (Optional) How much to wait until the sdk is ready 36// - SplitFile (Optional) File with splits to use when running in localhost mode 37// - LabelsEnabled (Optional) Can be used to disable labels if the user does not want to send that info to split servers. 38// - Logger: (Optional) Custom logger complying with logging.LoggerInterface 39// - LoggerConfig: (Optional) Options to setup the sdk's own logger 40// - TaskPeriods: (Optional) How often should each task run 41// - Redis: (Required for "redis-consumer". Sets up Redis config 42// - Advanced: (Optional) Sets up various advanced options for the sdk 43// - ImpressionsMode (Optional) Flag for enabling local impressions dedupe - Possible values <'optimized'|'debug'> 44type SplitSdkConfig struct { 45 OperationMode string 46 InstanceName string 47 IPAddress string 48 IPAddressesEnabled bool 49 BlockUntilReady int 50 SplitFile string 51 LabelsEnabled bool 52 SplitSyncProxyURL string 53 Logger logging.LoggerInterface 54 LoggerConfig logging.LoggerOptions 55 TaskPeriods TaskPeriods 56 Advanced AdvancedConfig 57 Redis conf.RedisConfig 58 ImpressionsMode string 59} 60 61// TaskPeriods struct is used to configure the period for each synchronization task 62type TaskPeriods struct { 63 SplitSync int 64 SegmentSync int 65 ImpressionSync int 66 GaugeSync int 67 CounterSync int 68 LatencySync int 69 EventsSync int 70 TelemetrySync int 71} 72 73// AdvancedConfig exposes more configurable parameters that can be used to further tailor the sdk to the user's needs 74// - ImpressionListener - struct that will be notified each time an impression bulk is ready 75// - HTTPTimeout - Timeout for HTTP requests when doing synchronization 76// - SegmentQueueSize - How many segments can be queued for updating (should be >= # segments the user has) 77// - SegmentWorkers - How many workers will be used when performing segments sync. 78type AdvancedConfig struct { 79 ImpressionListener impressionlistener.ImpressionListener 80 HTTPTimeout int 81 SegmentQueueSize int 82 SegmentWorkers int 83 AuthServiceURL string 84 SdkURL string 85 EventsURL string 86 StreamingServiceURL string 87 TelemetryServiceURL string 88 EventsBulkSize int64 89 EventsQueueSize int 90 ImpressionsQueueSize int 91 ImpressionsBulkSize int64 92 StreamingEnabled bool 93} 94 95// Default returns a config struct with all the default values 96func Default() *SplitSdkConfig { 97 instanceName := "unknown" 98 ipAddress, err := nethelpers.ExternalIP() 99 if err != nil { 100 ipAddress = "unknown" 101 } else { 102 instanceName = fmt.Sprintf("ip-%s", strings.Replace(ipAddress, ".", "-", -1)) 103 } 104 105 var splitFile string 106 usr, err := user.Current() 107 if err != nil { 108 splitFile = "splits" 109 } else { 110 splitFile = path.Join(usr.HomeDir, ".splits") 111 } 112 113 return &SplitSdkConfig{ 114 OperationMode: InMemoryStandAlone, 115 LabelsEnabled: true, 116 IPAddress: ipAddress, 117 IPAddressesEnabled: true, 118 InstanceName: instanceName, 119 Logger: nil, 120 LoggerConfig: logging.LoggerOptions{}, 121 SplitFile: splitFile, 122 ImpressionsMode: conf.ImpressionsModeOptimized, 123 Redis: conf.RedisConfig{ 124 Database: 0, 125 Host: "localhost", 126 Password: "", 127 Port: 6379, 128 Prefix: "", 129 }, 130 TaskPeriods: TaskPeriods{ 131 GaugeSync: defaultTelemetrySync, 132 CounterSync: defaultTelemetrySync, 133 LatencySync: defaultTelemetrySync, 134 TelemetrySync: defaultTelemetrySync, 135 ImpressionSync: defaultImpressionSyncOptimized, 136 SegmentSync: defaultTaskPeriod, 137 SplitSync: defaultTaskPeriod, 138 EventsSync: defaultTaskPeriod, 139 }, 140 Advanced: AdvancedConfig{ 141 AuthServiceURL: "", 142 EventsURL: "", 143 SdkURL: "", 144 StreamingServiceURL: "", 145 TelemetryServiceURL: "", 146 HTTPTimeout: 0, 147 ImpressionListener: nil, 148 SegmentQueueSize: 500, 149 SegmentWorkers: 10, 150 EventsBulkSize: 5000, 151 EventsQueueSize: 10000, 152 ImpressionsQueueSize: 10000, 153 ImpressionsBulkSize: 5000, 154 StreamingEnabled: true, 155 }, 156 } 157} 158 159func checkImpressionSync(cfg *SplitSdkConfig) error { 160 if cfg.TaskPeriods.ImpressionSync == 0 { 161 cfg.TaskPeriods.ImpressionSync = defaultImpressionSyncOptimized 162 } else { 163 if cfg.TaskPeriods.ImpressionSync < minImpressionSyncOptimized { 164 return fmt.Errorf("ImpressionSync must be >= %d. Actual is: %d", minImpressionSyncOptimized, cfg.TaskPeriods.ImpressionSync) 165 } 166 cfg.TaskPeriods.ImpressionSync = int(math.Max(float64(minImpressionSyncOptimized), float64(cfg.TaskPeriods.ImpressionSync))) 167 } 168 return nil 169} 170 171func validConfigRates(cfg *SplitSdkConfig) error { 172 if cfg.OperationMode == RedisConsumer { 173 return nil 174 } 175 176 if cfg.TaskPeriods.SplitSync < minSplitSync { 177 return fmt.Errorf("SplitSync must be >= %d. Actual is: %d", minSplitSync, cfg.TaskPeriods.SplitSync) 178 } 179 if cfg.TaskPeriods.SegmentSync < minSegmentSync { 180 return fmt.Errorf("SegmentSync must be >= %d. Actual is: %d", minSegmentSync, cfg.TaskPeriods.SegmentSync) 181 } 182 183 cfg.ImpressionsMode = strings.ToLower(cfg.ImpressionsMode) 184 switch cfg.ImpressionsMode { 185 case conf.ImpressionsModeOptimized: 186 err := checkImpressionSync(cfg) 187 if err != nil { 188 return err 189 } 190 case conf.ImpressionsModeDebug: 191 if cfg.TaskPeriods.ImpressionSync == 0 { 192 cfg.TaskPeriods.ImpressionSync = defaultImpressionSyncDebug 193 } else { 194 if cfg.TaskPeriods.ImpressionSync < minImpressionSync { 195 return fmt.Errorf("ImpressionSync must be >= %d. Actual is: %d", minImpressionSync, cfg.TaskPeriods.ImpressionSync) 196 } 197 } 198 default: 199 fmt.Println(`You passed an invalid impressionsMode, impressionsMode should be one of the following values: 'debug' or 'optimized'. Defaulting to 'optimized' mode.`) 200 cfg.ImpressionsMode = conf.ImpressionsModeOptimized 201 err := checkImpressionSync(cfg) 202 if err != nil { 203 return err 204 } 205 } 206 207 if cfg.TaskPeriods.EventsSync < minEventSync { 208 return fmt.Errorf("EventsSync must be >= %d. Actual is: %d", minEventSync, cfg.TaskPeriods.EventsSync) 209 } 210 if cfg.TaskPeriods.TelemetrySync < minTelemetrySync { 211 return fmt.Errorf("TelemetrySync must be >= %d. Actual is: %d", minTelemetrySync, cfg.TaskPeriods.TelemetrySync) 212 } 213 if cfg.Advanced.SegmentWorkers <= 0 { 214 return errors.New("Number of workers for fetching segments MUST be greater than zero") 215 } 216 return nil 217} 218 219// Normalize checks that the parameters passed by the user are correct and updates parameters if necessary. 220// returns an error if something is wrong 221func Normalize(apikey string, cfg *SplitSdkConfig) error { 222 // Fail if no apikey is provided 223 if apikey == "" && cfg.OperationMode != Localhost { 224 return errors.New("Factory instantiation: you passed an empty apikey, apikey must be a non-empty string") 225 } 226 227 // To keep the interface consistent with other sdks we accept "localhost" as an apikey, 228 // which sets the operation mode to localhost 229 if apikey == Localhost { 230 cfg.OperationMode = Localhost 231 } 232 233 // Fail if an invalid operation-mode is provided 234 operationModes := set.NewSet( 235 Localhost, 236 InMemoryStandAlone, 237 RedisConsumer, 238 ) 239 240 if !operationModes.Has(cfg.OperationMode) { 241 return fmt.Errorf("OperationMode parameter must be one of: %v", operationModes.List()) 242 } 243 244 if cfg.SplitSyncProxyURL != "" { 245 cfg.Advanced.AuthServiceURL = cfg.SplitSyncProxyURL 246 cfg.Advanced.SdkURL = cfg.SplitSyncProxyURL 247 cfg.Advanced.EventsURL = cfg.SplitSyncProxyURL 248 cfg.Advanced.StreamingServiceURL = cfg.SplitSyncProxyURL 249 cfg.Advanced.TelemetryServiceURL = cfg.SplitSyncProxyURL 250 } 251 252 if !cfg.IPAddressesEnabled { 253 cfg.IPAddress = "NA" 254 cfg.InstanceName = "NA" 255 } 256 257 return validConfigRates(cfg) 258} 259