package micro import ( "fmt" "os" "os/signal" rtime "runtime" "strings" "sync" "time" "github.com/micro/go-micro/v2/auth" "github.com/micro/go-micro/v2/client" "github.com/micro/go-micro/v2/config/cmd" "github.com/micro/go-micro/v2/debug/service/handler" "github.com/micro/go-micro/v2/debug/stats" "github.com/micro/go-micro/v2/debug/trace" "github.com/micro/go-micro/v2/logger" "github.com/micro/go-micro/v2/plugin" "github.com/micro/go-micro/v2/server" "github.com/micro/go-micro/v2/store" signalutil "github.com/micro/go-micro/v2/util/signal" "github.com/micro/go-micro/v2/util/wrapper" ) type service struct { opts Options once sync.Once } func newService(opts ...Option) Service { service := new(service) options := newOptions(opts...) // service name serviceName := options.Server.Options().Name // authFn returns the auth, we pass as a function since auth // has not yet been set at this point. authFn := func() auth.Auth { return options.Server.Options().Auth } // wrap client to inject From-Service header on any calls options.Client = wrapper.FromService(serviceName, options.Client) options.Client = wrapper.TraceCall(serviceName, trace.DefaultTracer, options.Client) options.Client = wrapper.AuthClient(authFn, options.Client) // wrap the server to provide handler stats options.Server.Init( server.WrapHandler(wrapper.HandlerStats(stats.DefaultStats)), server.WrapHandler(wrapper.TraceHandler(trace.DefaultTracer)), server.WrapHandler(wrapper.AuthHandler(authFn)), ) // set opts service.opts = options return service } func (s *service) Name() string { return s.opts.Server.Options().Name } // Init initialises options. Additionally it calls cmd.Init // which parses command line flags. cmd.Init is only called // on first Init. func (s *service) Init(opts ...Option) { // process options for _, o := range opts { o(&s.opts) } s.once.Do(func() { // setup the plugins for _, p := range strings.Split(os.Getenv("MICRO_PLUGIN"), ",") { if len(p) == 0 { continue } // load the plugin c, err := plugin.Load(p) if err != nil { logger.Fatal(err) } // initialise the plugin if err := plugin.Init(c); err != nil { logger.Fatal(err) } } // set cmd name if len(s.opts.Cmd.App().Name) == 0 { s.opts.Cmd.App().Name = s.Server().Options().Name } // Initialise the command flags, overriding new service if err := s.opts.Cmd.Init( cmd.Auth(&s.opts.Auth), cmd.Broker(&s.opts.Broker), cmd.Registry(&s.opts.Registry), cmd.Runtime(&s.opts.Runtime), cmd.Transport(&s.opts.Transport), cmd.Client(&s.opts.Client), cmd.Config(&s.opts.Config), cmd.Server(&s.opts.Server), cmd.Store(&s.opts.Store), cmd.Profile(&s.opts.Profile), ); err != nil { logger.Fatal(err) } // Explicitly set the table name to the service name name := s.opts.Cmd.App().Name s.opts.Store.Init(store.Table(name)) }) } func (s *service) Options() Options { return s.opts } func (s *service) Client() client.Client { return s.opts.Client } func (s *service) Server() server.Server { return s.opts.Server } func (s *service) String() string { return "micro" } func (s *service) Start() error { for _, fn := range s.opts.BeforeStart { if err := fn(); err != nil { return err } } if err := s.opts.Server.Start(); err != nil { return err } for _, fn := range s.opts.AfterStart { if err := fn(); err != nil { return err } } return nil } func (s *service) Stop() error { var gerr error for _, fn := range s.opts.BeforeStop { if err := fn(); err != nil { gerr = err } } if err := s.opts.Server.Stop(); err != nil { return err } for _, fn := range s.opts.AfterStop { if err := fn(); err != nil { gerr = err } } return gerr } func (s *service) Run() error { // generate an auth account if err := s.generateAccount(); err != nil { return err } // register the debug handler s.opts.Server.Handle( s.opts.Server.NewHandler( handler.NewHandler(), server.InternalHandler(true), ), ) // start the profiler if s.opts.Profile != nil { // to view mutex contention rtime.SetMutexProfileFraction(5) // to view blocking profile rtime.SetBlockProfileRate(1) if err := s.opts.Profile.Start(); err != nil { return err } defer s.opts.Profile.Stop() } if logger.V(logger.InfoLevel, logger.DefaultLogger) { logger.Infof("Starting [service] %s", s.Name()) } if err := s.Start(); err != nil { return err } ch := make(chan os.Signal, 1) if s.opts.Signal { signal.Notify(ch, signalutil.Shutdown()...) } select { // wait on kill signal case <-ch: // wait on context cancel case <-s.opts.Context.Done(): } return s.Stop() } func (s *service) generateAccount() error { // extract the account creds from options, these can be set by flags accID := s.Options().Auth.Options().ID accSecret := s.Options().Auth.Options().Secret // if no credentials were provided, generate an account if len(accID) == 0 || len(accSecret) == 0 { name := fmt.Sprintf("%v-%v", s.Name(), s.Server().Options().Id) opts := []auth.GenerateOption{ auth.WithType("service"), auth.WithRoles("service"), auth.WithNamespace(s.Options().Auth.Options().Namespace), } acc, err := s.Options().Auth.Generate(name, opts...) if err != nil { return err } logger.Infof("Auth [%v] Authenticated as %v", s.Options().Auth, name) accID = acc.ID accSecret = acc.Secret } // generate the first token token, err := s.Options().Auth.Token( auth.WithCredentials(accID, accSecret), auth.WithExpiry(time.Minute*10), ) if err != nil { return err } // set the credentials and token in auth options s.Options().Auth.Init( auth.ClientToken(token), auth.Credentials(accID, accSecret), ) // periodically check to see if the token needs refreshing go func() { timer := time.NewTicker(time.Second * 15) for { <-timer.C // don't refresh the token if it's not close to expiring tok := s.Options().Auth.Options().Token if tok.Expiry.Unix() > time.Now().Add(time.Minute).Unix() { continue } // generate the first token tok, err := s.Options().Auth.Token( auth.WithCredentials(accID, accSecret), auth.WithExpiry(time.Minute*10), ) if err != nil { logger.Warnf("[Auth] Error refreshing token: %v", err) continue } // set the token s.Options().Auth.Init(auth.ClientToken(tok)) } }() return nil }