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  }