// New returns a new Service. Config can not be nil. func New(config *Config) (*Service, error) { var err error var keyBytes *[SecretKeyLength]byte var metricsClient metrics.Client // read in key from keypath or if not given, try getting them from key bytes. if config.KeyPath != "" { keyBytes, err = readKeyFromDisk(config.KeyPath) if err != nil { return nil, err } } else { if config.KeyBytes == nil { return nil, fmt.Errorf("No key bytes provided.") } keyBytes = config.KeyBytes } // setup metrics service if config.EmitStats { // get hostname of box hostname, err := os.Hostname() if err != nil { return nil, fmt.Errorf("failed to obtain hostname: %v", err) } // build lemma prefix prefix := "lemma." + strings.Replace(hostname, ".", "_", -1) if config.StatsdPrefix != "" { prefix += "." + config.StatsdPrefix } // build metrics client hostport := fmt.Sprintf("%v:%v", config.StatsdHost, config.StatsdPort) metricsClient, err = metrics.NewWithOptions(hostport, prefix, metrics.Options{UseBuffering: true}) if err != nil { return nil, err } } else { // if you don't want to emit stats, use the nop client metricsClient = metrics.NewNop() } return &Service{ secretKey: keyBytes, metricsClient: metricsClient, }, nil }
// Returns a new Service. Provides control over time and random providers. func NewWithProviders(config *Config, timeProvider timetools.TimeProvider, randomProvider random.RandomProvider) (*Service, error) { // config is required! if config == nil { return nil, fmt.Errorf("config is required.") } // set defaults if not set if config.NonceCacheCapacity < 1 { config.NonceCacheCapacity = CacheCapacity } if config.NonceCacheTimeout < 1 { config.NonceCacheTimeout = CacheTimeout } if config.NonceHeaderName == "" { config.NonceHeaderName = XMailgunNonce } if config.TimestampHeaderName == "" { config.TimestampHeaderName = XMailgunTimestamp } if config.SignatureHeaderName == "" { config.SignatureHeaderName = XMailgunSignature } if config.SignatureVersionHeaderName == "" { config.SignatureVersionHeaderName = XMailgunSignatureVersion } // setup metrics service metricsClient := metrics.NewNop() if config.EmitStats { // get hostname of box hostname, err := os.Hostname() if err != nil { return nil, fmt.Errorf("failed to obtain hostname: %v", err) } // build lemma prefix prefix := "lemma." + strings.Replace(hostname, ".", "_", -1) if config.StatsdPrefix != "" { prefix += "." + config.StatsdPrefix } // build metrics client hostport := fmt.Sprintf("%v:%v", config.StatsdHost, config.StatsdPort) metricsClient, err = metrics.NewWithOptions(hostport, prefix, metrics.Options{UseBuffering: true}) if err != nil { return nil, err } } // read key from disk, if no key is read that's okay it might be passed in keyBytes, err := readKeyFromDisk(config.Keypath) // setup nonce cache ncache, err := NewNonceCache(config.NonceCacheCapacity, config.NonceCacheTimeout, timeProvider) if err != nil { return nil, err } // return service return &Service{ config: config, nonceCache: ncache, secretKey: keyBytes, timeProvider: timeProvider, randomProvider: randomProvider, metricsClient: metricsClient, }, nil }
func (s *Service) Start() error { // if .LogFormatter is set, it'll be used in log.SetFormatter() and .Log will be ignored. if s.options.LogFormatter != nil { log.SetFormatter(s.options.LogFormatter) } else { switch s.options.Log { case "console": { log.SetFormatter(&log.TextFormatter{}) } case "json": { log.SetFormatter(&log.JSONFormatter{}) } case "syslog": { hook, err := logrus_syslog.NewSyslogHook("", "", syslog.LOG_INFO, "") if err == nil { log.SetFormatter(&log.TextFormatter{DisableColors: true}) log.AddHook(hook) } else { setFallbackLogFormatter(s.options) } } case "logstash": { log.SetFormatter(&logrus_logstash.LogstashFormatter{Type: "logs"}) } default: setFallbackLogFormatter(s.options) } } log.SetOutput(os.Stdout) log.SetLevel(s.options.LogSeverity.S) log.Infof("Service starts with options: %#v", s.options) if s.options.PidPath != "" { ioutil.WriteFile(s.options.PidPath, []byte(fmt.Sprint(os.Getpid())), 0644) } if s.options.MetricsClient != nil { s.metricsClient = s.options.MetricsClient } else if s.options.StatsdAddr != "" { var err error s.metricsClient, err = metrics.NewWithOptions(s.options.StatsdAddr, s.options.StatsdPrefix, metrics.Options{UseBuffering: true}) if err != nil { return err } } apiFile, muxFiles, err := s.getFiles() if err != nil { return err } if err := s.newEngine(); err != nil { return err } s.stapler = stapler.New() s.supervisor = supervisor.New( s.newProxy, s.ng, s.errorC, supervisor.Options{Files: muxFiles}) // Tells configurator to perform initial proxy configuration and start watching changes if err := s.supervisor.Start(); err != nil { return err } if err := s.initApi(); err != nil { return err } go func() { s.errorC <- s.startApi(apiFile) }() if s.metricsClient != nil { go s.reportSystemMetrics() } signal.Notify(s.sigC, os.Interrupt, os.Kill, syscall.SIGTERM, syscall.SIGUSR2, syscall.SIGCHLD) // Block until a signal is received or we got an error for { select { case signal := <-s.sigC: switch signal { case syscall.SIGTERM, syscall.SIGINT: log.Infof("Got signal '%s', shutting down gracefully", signal) s.supervisor.Stop(true) log.Infof("All servers stopped") return nil case syscall.SIGKILL: log.Infof("Got signal '%s', exiting now without waiting", signal) s.supervisor.Stop(false) return nil case syscall.SIGUSR2: log.Infof("Got signal '%s', forking a new self", signal) if err := s.startChild(); err != nil { log.Infof("Failed to start self: %s", err) } else { log.Infof("Successfully started self") } case syscall.SIGCHLD: log.Warningf("Child exited, got '%s', collecting status", signal) var wait syscall.WaitStatus syscall.Wait4(-1, &wait, syscall.WNOHANG, nil) log.Warningf("Collected exit status from child") default: log.Infof("Ignoring '%s'", signal) } case err := <-s.errorC: log.Infof("Got request to shutdown with error: %s", err) return err } } }