github.com/Rookout/GoSDK@v0.1.48/pkg/singleton.go (about) 1 package pkg 2 3 import ( 4 "os" 5 "regexp" 6 "runtime/debug" 7 "strconv" 8 "strings" 9 "sync" 10 11 "github.com/Rookout/GoSDK/pkg/aug_manager" 12 "github.com/Rookout/GoSDK/pkg/com_ws" 13 "github.com/Rookout/GoSDK/pkg/config" 14 "github.com/Rookout/GoSDK/pkg/information" 15 "github.com/Rookout/GoSDK/pkg/logger" 16 "github.com/Rookout/GoSDK/pkg/rookoutErrors" 17 "github.com/Rookout/GoSDK/pkg/services/assembler/common" 18 "github.com/Rookout/GoSDK/pkg/services/instrumentation" 19 "github.com/Rookout/GoSDK/pkg/utils" 20 ) 21 22 type singleton struct { 23 output com_ws.Output 24 agentCom com_ws.AgentCom 25 commandHandler *aug_manager.CommandHandler 26 augManager aug_manager.AugManager 27 triggerServices *instrumentation.TriggerServices 28 29 opts *config.RookOptions 30 31 started bool 32 servicesStarted bool 33 } 34 35 var initOnce sync.Once 36 var rookSingleton *singleton 37 38 func GetSingleton() *singleton { 39 if rookSingleton == nil { 40 InitSingleton() 41 } 42 43 return rookSingleton 44 } 45 46 47 func InitSingleton() { 48 initOnce.Do(func() { 49 initializedSingleton := createSingleton() 50 rookSingleton = initializedSingleton 51 }) 52 } 53 54 func createSingleton() *singleton { 55 return &singleton{ 56 servicesStarted: false, 57 } 58 } 59 60 func initOptsFromEnv(opts *config.RookOptions) error { 61 if !opts.Debug { 62 rookoutDebug, _ := os.LookupEnv("ROOKOUT_DEBUG") 63 opts.Debug = utils.Contains(utils.TrueValues, rookoutDebug) 64 } 65 66 if !opts.LogToStderr { 67 logToStderr, _ := os.LookupEnv("ROOKOUT_LOG_TO_STDERR") 68 opts.LogToStderr = utils.Contains(utils.TrueValues, logToStderr) 69 } 70 71 if !opts.LogToFile { 72 logToFile, _ := os.LookupEnv("ROOKOUT_LOG_TO_FILE") 73 opts.LogToFile = utils.Contains(utils.TrueValues, logToFile) 74 } 75 76 if opts.LogFile == "" { 77 opts.LogFile, _ = os.LookupEnv("ROOKOUT_LOG_FILE") 78 } 79 80 if opts.LogLevel == "" { 81 opts.LogLevel, _ = os.LookupEnv("ROOKOUT_LOG_LEVEL") 82 } 83 84 if opts.Token == "" { 85 opts.Token, _ = os.LookupEnv("ROOKOUT_TOKEN") 86 } 87 88 if opts.Host == "" { 89 opts.Host, _ = os.LookupEnv("ROOKOUT_CONTROLLER_HOST") 90 } 91 92 if opts.GitOrigin == "" { 93 opts.GitOrigin, _ = os.LookupEnv("ROOKOUT_REMOTE_ORIGIN") 94 } 95 information.GitConfig.RemoteOrigin = opts.GitOrigin 96 97 if opts.GitCommit == "" { 98 opts.GitCommit, _ = os.LookupEnv("ROOKOUT_COMMIT") 99 } 100 information.GitConfig.Commit = opts.GitCommit 101 102 if opts.GitSources == nil { 103 rawGitSources, exists := os.LookupEnv("ROOKOUT_SOURCES") 104 if exists { 105 opts.GitSources = make(map[string]string) 106 gitSourceList := strings.Split(rawGitSources, ";") 107 for _, s := range gitSourceList { 108 if strings.Contains(s, "#") { 109 source := strings.Split(s, "#") 110 if len(source) != 2 { 111 continue 112 } 113 114 opts.GitSources[source[0]] = source[1] 115 } 116 } 117 } 118 } 119 120 information.GitConfig.Sources = opts.GitSources 121 122 if !opts.LiveTail { 123 liveTail, _ := os.LookupEnv("ROOKOUT_LIVE_LOGGER") 124 opts.LiveTail = utils.Contains(utils.TrueValues, liveTail) 125 } 126 127 if opts.Proxy == "" { 128 opts.Proxy, _ = os.LookupEnv("ROOKOUT_PROXY") 129 } 130 131 if !opts.Quiet { 132 quiet, _ := os.LookupEnv("ROOKOUT_QUIET") 133 opts.Quiet = utils.Contains(utils.TrueValues, quiet) 134 } 135 136 if opts.Port == 0 { 137 if port, ok := os.LookupEnv("ROOKOUT_CONTROLLER_PORT"); ok { 138 if p, ok := strconv.Atoi(port); ok == nil { 139 opts.Port = p 140 } 141 } 142 } 143 144 if len(opts.Labels) == 0 { 145 var err error 146 if opts.Labels, err = getLabelsFromEnv(opts.Labels); err != nil { 147 return err 148 } 149 } 150 151 return nil 152 } 153 154 155 func normalizeOpts(opts *config.RookOptions) error { 156 Sanitize(opts) 157 if opts.Token == "" && opts.Host == "" { 158 return rookoutErrors.NewRookMissingToken() 159 } else if opts.Token != "" { 160 if err := validateToken(opts.Token); err != nil { 161 return err 162 } 163 } 164 165 if opts.Host == "" { 166 opts.Host = ControllerAddressHost 167 } 168 169 if opts.Host == "staging.cloud.agent.rookout.com" || opts.Host == "cloud.agent.rookout.com" { 170 opts.Host = "https://" + opts.Host 171 } 172 173 if opts.Host == "staging.control.rookout.com" || opts.Host == "control.rookout.com" { 174 opts.Host = "wss://" + opts.Host 175 } 176 177 if opts.Port == 0 { 178 opts.Port = ControllerAddressPort 179 } 180 181 if opts.LogLevel == "" { 182 opts.LogLevel = "info" 183 } 184 185 for key := range opts.Labels { 186 if err := validateLabel(key); err != nil { 187 return err 188 } 189 } 190 191 if opts.Debug { 192 opts.LogToFile = true 193 opts.LogToStderr = true 194 } 195 196 return nil 197 } 198 199 func (s *singleton) Start(opts *config.RookOptions) (err error) { 200 if s.started { 201 return nil 202 } 203 204 s.opts = opts 205 206 s.started = true 207 208 if err = initOptsFromEnv(s.opts); err != nil { 209 return err 210 } 211 if err = normalizeOpts(s.opts); err != nil { 212 return err 213 } 214 215 config.UpdateFromOpts(*s.opts) 216 217 logger.Init(s.opts.Debug, s.opts.LogLevel) 218 logger.InitHandlers(s.opts.LogToStderr, s.opts.LogToFile, s.opts.LogFile) 219 utils.SetOnPanicFunc(func(err error) { 220 logger.Logger().WithError(err).Fatalf("Caught panic in goroutine, stack trace: %s\n", string(debug.Stack())) 221 }) 222 223 s.triggerServices, err = instrumentation.NewTriggerServices() 224 if err != nil { 225 return err 226 } 227 228 output := com_ws.NewOutputWs() 229 s.output = output 230 logger.SetLoggerOutput(output) 231 232 err = s.connect() 233 if err != nil { 234 return err 235 } 236 237 if common.InitError != nil { 238 logger.Logger().WithError(common.InitError).Error("Unable to start rook") 239 return common.InitError 240 } 241 242 buildOpts, buildInfo, verifyBuildOptsErr := utils.GetBuildOpts() 243 if verifyBuildOptsErr != nil { 244 logger.Logger().WithError(verifyBuildOptsErr).Warning("Failed to read the build flags") 245 return err 246 } 247 logger.Logger().Infof("Got build info:%v", buildInfo) 248 verifyBuildOptsErr = utils.ValidateBuildOpts(buildOpts) 249 if verifyBuildOptsErr != nil { 250 logger.Logger().WithError(verifyBuildOptsErr).Warning("Validation of build flags failed.") 251 return err 252 } 253 return err 254 } 255 256 func (s *singleton) Stop() { 257 if !s.started { 258 return 259 } 260 261 s.triggerServices.Close() 262 } 263 264 func (s *singleton) Flush() { 265 if !s.started || s.agentCom == nil { 266 return 267 } 268 269 s.agentCom.Flush() 270 } 271 272 func (s *singleton) connect() (err error) { 273 agentCom, err := com_ws.NewAgentComWs( 274 com_ws.NewWebSocketClient, 275 s.output, 276 com_ws.NewBackoff(), 277 s.opts.Host, 278 s.opts.Port, 279 s.opts.Proxy, 280 s.opts.Token, 281 s.opts.Labels, 282 true, 283 ) 284 if err != nil { 285 return err 286 } 287 288 s.output.SetAgentCom(agentCom) 289 s.agentCom = agentCom 290 s.augManager = aug_manager.NewAugManager(s.triggerServices, s.output) 291 s.commandHandler = aug_manager.NewCommandHandler(s.agentCom, s.augManager) 292 return agentCom.ConnectToAgent() 293 } 294 295 func (s *singleton) startServices() (err error) { 296 s.triggerServices, err = instrumentation.NewTriggerServices() 297 return err 298 } 299 300 func getLabelsFromEnv(labels map[string]string) (map[string]string, error) { 301 if len(labels) == 0 { 302 if labelsEnvVar, ok := os.LookupEnv("ROOKOUT_LABELS"); ok { 303 labels = make(map[string]string) 304 305 labelsPairs := strings.Split(labelsEnvVar, ",") 306 for _, pair := range labelsPairs { 307 k := strings.Split(pair, ":") 308 if len(k) == 2 { 309 if err := validateLabel(k[0]); err != nil { 310 return nil, rookoutErrors.NewInvalidLabelError(k[0]) 311 } 312 labels[k[0]] = k[1] 313 } 314 } 315 } 316 } 317 318 return labels, nil 319 } 320 321 func validateToken(token string) error { 322 if len(token) != 64 { 323 return rookoutErrors.NewRookInvalidOptions("Rookout token should be 64 characters") 324 } 325 326 res, e := regexp.MatchString("^[0-9a-zA-Z]+$", token) 327 if e != nil { 328 return rookoutErrors.NewRuntimeError(e.Error()) 329 } 330 331 if !res { 332 return rookoutErrors.NewRookInvalidOptions("Rookout token must consist of only hexadecimal characters") 333 } 334 335 return nil 336 } 337 338 func validateLabel(label string) error { 339 if strings.HasPrefix(label, "$") { 340 return rookoutErrors.NewInvalidLabelError(label) 341 } 342 return nil 343 }