github.com/annwntech/go-micro/v2@v2.9.5/runtime/default.go (about)

     1  package runtime
     2  
     3  import (
     4  	"archive/tar"
     5  	"compress/gzip"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"log"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/hpcloud/tail"
    17  	"github.com/annwntech/go-micro/v2/logger"
    18  	"github.com/annwntech/go-micro/v2/runtime/local/git"
    19  )
    20  
    21  // defaultNamespace to use if not provided as an option
    22  const defaultNamespace = "default"
    23  
    24  type runtime struct {
    25  	sync.RWMutex
    26  	// options configure runtime
    27  	options Options
    28  	// used to stop the runtime
    29  	closed chan bool
    30  	// used to start new services
    31  	start chan *service
    32  	// indicates if we're running
    33  	running bool
    34  	// namespaces stores services grouped by namespace, e.g. namespaces["foo"]["go.micro.auth:latest"]
    35  	// would return the latest version of go.micro.auth from the foo namespace
    36  	namespaces map[string]map[string]*service
    37  }
    38  
    39  // NewRuntime creates new local runtime and returns it
    40  func NewRuntime(opts ...Option) Runtime {
    41  	// get default options
    42  	options := Options{}
    43  
    44  	// apply requested options
    45  	for _, o := range opts {
    46  		o(&options)
    47  	}
    48  
    49  	// make the logs directory
    50  	path := filepath.Join(os.TempDir(), "micro", "logs")
    51  	_ = os.MkdirAll(path, 0755)
    52  
    53  	return &runtime{
    54  		options:    options,
    55  		closed:     make(chan bool),
    56  		start:      make(chan *service, 128),
    57  		namespaces: make(map[string]map[string]*service),
    58  	}
    59  }
    60  
    61  // @todo move this to runtime default
    62  func (r *runtime) checkoutSourceIfNeeded(s *Service) error {
    63  	// Runtime service like config have no source.
    64  	// Skip checkout in that case
    65  	if len(s.Source) == 0 {
    66  		return nil
    67  	}
    68  	// @todo make this come from config
    69  	cpath := filepath.Join(os.TempDir(), "micro", "uploads", s.Source)
    70  	path := strings.ReplaceAll(cpath, ".tar.gz", "")
    71  	if ex, _ := exists(cpath); ex {
    72  		err := os.RemoveAll(path)
    73  		if err != nil {
    74  			return err
    75  		}
    76  		err = os.MkdirAll(path, 0777)
    77  		if err != nil {
    78  			return err
    79  		}
    80  		err = uncompress(cpath, path)
    81  		if err != nil {
    82  			return err
    83  		}
    84  		s.Source = path
    85  		return nil
    86  	}
    87  	source, err := git.ParseSourceLocal("", s.Source)
    88  	if err != nil {
    89  		return err
    90  	}
    91  	source.Ref = s.Version
    92  
    93  	err = git.CheckoutSource(os.TempDir(), source)
    94  	if err != nil {
    95  		return err
    96  	}
    97  	s.Source = source.FullPath
    98  	return nil
    99  }
   100  
   101  // modified version of: https://gist.github.com/mimoo/25fc9716e0f1353791f5908f94d6e726
   102  func uncompress(src string, dst string) error {
   103  	file, err := os.OpenFile(src, os.O_RDWR|os.O_CREATE, 0666)
   104  	defer file.Close()
   105  	if err != nil {
   106  		return err
   107  	}
   108  	// ungzip
   109  	zr, err := gzip.NewReader(file)
   110  	if err != nil {
   111  		return err
   112  	}
   113  	// untar
   114  	tr := tar.NewReader(zr)
   115  
   116  	// uncompress each element
   117  	for {
   118  		header, err := tr.Next()
   119  		if err == io.EOF {
   120  			break // End of archive
   121  		}
   122  		if err != nil {
   123  			return err
   124  		}
   125  		target := header.Name
   126  
   127  		// validate name against path traversal
   128  		if !validRelPath(header.Name) {
   129  			return fmt.Errorf("tar contained invalid name error %q\n", target)
   130  		}
   131  
   132  		// add dst + re-format slashes according to system
   133  		target = filepath.Join(dst, header.Name)
   134  		// if no join is needed, replace with ToSlash:
   135  		// target = filepath.ToSlash(header.Name)
   136  
   137  		// check the type
   138  		switch header.Typeflag {
   139  
   140  		// if its a dir and it doesn't exist create it (with 0755 permission)
   141  		case tar.TypeDir:
   142  			if _, err := os.Stat(target); err != nil {
   143  				// @todo think about this:
   144  				// if we don't nuke the folder, we might end up with files from
   145  				// the previous decompress.
   146  				if err := os.MkdirAll(target, 0755); err != nil {
   147  					return err
   148  				}
   149  			}
   150  		// if it's a file create it (with same permission)
   151  		case tar.TypeReg:
   152  			// the truncating is probably unnecessary due to the `RemoveAll` of folders
   153  			// above
   154  			fileToWrite, err := os.OpenFile(target, os.O_TRUNC|os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
   155  			if err != nil {
   156  				return err
   157  			}
   158  			// copy over contents
   159  			if _, err := io.Copy(fileToWrite, tr); err != nil {
   160  				return err
   161  			}
   162  			// manually close here after each file operation; defering would cause each file close
   163  			// to wait until all operations have completed.
   164  			fileToWrite.Close()
   165  		}
   166  	}
   167  	return nil
   168  }
   169  
   170  // check for path traversal and correct forward slashes
   171  func validRelPath(p string) bool {
   172  	if p == "" || strings.Contains(p, `\`) || strings.HasPrefix(p, "/") || strings.Contains(p, "../") {
   173  		return false
   174  	}
   175  	return true
   176  }
   177  
   178  // Init initializes runtime options
   179  func (r *runtime) Init(opts ...Option) error {
   180  	r.Lock()
   181  	defer r.Unlock()
   182  
   183  	for _, o := range opts {
   184  		o(&r.options)
   185  	}
   186  
   187  	return nil
   188  }
   189  
   190  // run runs the runtime management loop
   191  func (r *runtime) run(events <-chan Event) {
   192  	t := time.NewTicker(time.Second * 5)
   193  	defer t.Stop()
   194  
   195  	// process event processes an incoming event
   196  	processEvent := func(event Event, service *service, ns string) error {
   197  		// get current vals
   198  		r.RLock()
   199  		name := service.Name
   200  		updated := service.updated
   201  		r.RUnlock()
   202  
   203  		// only process if the timestamp is newer
   204  		if !event.Timestamp.After(updated) {
   205  			return nil
   206  		}
   207  
   208  		if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   209  			logger.Debugf("Runtime updating service %s in %v namespace", name, ns)
   210  		}
   211  
   212  		// this will cause a delete followed by created
   213  		if err := r.Update(service.Service, UpdateNamespace(ns)); err != nil {
   214  			return err
   215  		}
   216  
   217  		// update the local timestamp
   218  		r.Lock()
   219  		service.updated = updated
   220  		r.Unlock()
   221  
   222  		return nil
   223  	}
   224  
   225  	for {
   226  		select {
   227  		case <-t.C:
   228  			// check running services
   229  			r.RLock()
   230  			for _, sevices := range r.namespaces {
   231  				for _, service := range sevices {
   232  					if !service.ShouldStart() {
   233  						continue
   234  					}
   235  
   236  					// TODO: check service error
   237  					if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   238  						logger.Debugf("Runtime starting %s", service.Name)
   239  					}
   240  					if err := service.Start(); err != nil {
   241  						if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   242  							logger.Debugf("Runtime error starting %s: %v", service.Name, err)
   243  						}
   244  					}
   245  				}
   246  			}
   247  			r.RUnlock()
   248  		case service := <-r.start:
   249  			if !service.ShouldStart() {
   250  				continue
   251  			}
   252  			// TODO: check service error
   253  			if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   254  				logger.Debugf("Runtime starting service %s", service.Name)
   255  			}
   256  			if err := service.Start(); err != nil {
   257  				if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   258  					logger.Debugf("Runtime error starting service %s: %v", service.Name, err)
   259  				}
   260  			}
   261  		case event := <-events:
   262  			if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   263  				logger.Debugf("Runtime received notification event: %v", event)
   264  			}
   265  			// NOTE: we only handle Update events for now
   266  			switch event.Type {
   267  			case Update:
   268  				if event.Service != nil {
   269  					ns := defaultNamespace
   270  					if event.Options != nil && len(event.Options.Namespace) > 0 {
   271  						ns = event.Options.Namespace
   272  					}
   273  
   274  					r.RLock()
   275  					if _, ok := r.namespaces[ns]; !ok {
   276  						if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   277  							logger.Debugf("Runtime unknown namespace: %s", ns)
   278  						}
   279  						r.RUnlock()
   280  						continue
   281  					}
   282  					service, ok := r.namespaces[ns][fmt.Sprintf("%v:%v", event.Service.Name, event.Service.Version)]
   283  					r.RUnlock()
   284  					if !ok {
   285  						logger.Debugf("Runtime unknown service: %s", event.Service)
   286  					}
   287  
   288  					if err := processEvent(event, service, ns); err != nil {
   289  						if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   290  							logger.Debugf("Runtime error updating service %s: %v", event.Service, err)
   291  						}
   292  					}
   293  					continue
   294  				}
   295  
   296  				r.RLock()
   297  				namespaces := r.namespaces
   298  				r.RUnlock()
   299  
   300  				// if blank service was received we update all services
   301  				for ns, services := range namespaces {
   302  					for _, service := range services {
   303  						if err := processEvent(event, service, ns); err != nil {
   304  							if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   305  								logger.Debugf("Runtime error updating service %s: %v", service.Name, err)
   306  							}
   307  						}
   308  					}
   309  				}
   310  			}
   311  		case <-r.closed:
   312  			if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   313  				logger.Debugf("Runtime stopped")
   314  			}
   315  			return
   316  		}
   317  	}
   318  }
   319  
   320  func logFile(serviceName string) string {
   321  	// make the directory
   322  	name := strings.Replace(serviceName, "/", "-", -1)
   323  	path := filepath.Join(os.TempDir(), "micro", "logs")
   324  	return filepath.Join(path, fmt.Sprintf("%v.log", name))
   325  }
   326  
   327  func serviceKey(s *Service) string {
   328  	return fmt.Sprintf("%v:%v", s.Name, s.Version)
   329  }
   330  
   331  // Create creates a new service which is then started by runtime
   332  func (r *runtime) Create(s *Service, opts ...CreateOption) error {
   333  	err := r.checkoutSourceIfNeeded(s)
   334  	if err != nil {
   335  		return err
   336  	}
   337  	r.Lock()
   338  	defer r.Unlock()
   339  
   340  	var options CreateOptions
   341  	for _, o := range opts {
   342  		o(&options)
   343  	}
   344  	if len(options.Namespace) == 0 {
   345  		options.Namespace = defaultNamespace
   346  	}
   347  	if len(options.Command) == 0 {
   348  		options.Command = []string{"go"}
   349  		options.Args = []string{"run", "."}
   350  	}
   351  
   352  	if _, ok := r.namespaces[options.Namespace]; !ok {
   353  		r.namespaces[options.Namespace] = make(map[string]*service)
   354  	}
   355  	if _, ok := r.namespaces[options.Namespace][serviceKey(s)]; ok {
   356  		return errors.New("service already running")
   357  	}
   358  
   359  	// create new service
   360  	service := newService(s, options)
   361  
   362  	f, err := os.OpenFile(logFile(service.Name), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
   363  	if err != nil {
   364  		log.Fatal(err)
   365  	}
   366  
   367  	if service.output != nil {
   368  		service.output = io.MultiWriter(service.output, f)
   369  	} else {
   370  		service.output = f
   371  	}
   372  	// start the service
   373  	if err := service.Start(); err != nil {
   374  		return err
   375  	}
   376  	// save service
   377  	r.namespaces[options.Namespace][serviceKey(s)] = service
   378  
   379  	return nil
   380  }
   381  
   382  // exists returns whether the given file or directory exists
   383  func exists(path string) (bool, error) {
   384  	_, err := os.Stat(path)
   385  	if err == nil {
   386  		return true, nil
   387  	}
   388  	if os.IsNotExist(err) {
   389  		return false, nil
   390  	}
   391  	return true, err
   392  }
   393  
   394  // @todo: Getting existing lines is not supported yet.
   395  // The reason for this is because it's hard to calculate line offset
   396  // as opposed to character offset.
   397  // This logger streams by default and only supports the `StreamCount` option.
   398  func (r *runtime) Logs(s *Service, options ...LogsOption) (LogStream, error) {
   399  	lopts := LogsOptions{}
   400  	for _, o := range options {
   401  		o(&lopts)
   402  	}
   403  	ret := &logStream{
   404  		service: s.Name,
   405  		stream:  make(chan LogRecord),
   406  		stop:    make(chan bool),
   407  	}
   408  
   409  	fpath := logFile(s.Name)
   410  	if ex, err := exists(fpath); err != nil {
   411  		return nil, err
   412  	} else if !ex {
   413  		return nil, fmt.Errorf("Log file %v does not exists", fpath)
   414  	}
   415  
   416  	// have to check file size to avoid too big of a seek
   417  	fi, err := os.Stat(fpath)
   418  	if err != nil {
   419  		return nil, err
   420  	}
   421  	size := fi.Size()
   422  
   423  	whence := 2
   424  	// Multiply by length of an average line of log in bytes
   425  	offset := lopts.Count * 200
   426  
   427  	if offset > size {
   428  		offset = size
   429  	}
   430  	offset *= -1
   431  
   432  	t, err := tail.TailFile(fpath, tail.Config{Follow: lopts.Stream, Location: &tail.SeekInfo{
   433  		Whence: whence,
   434  		Offset: int64(offset),
   435  	}, Logger: tail.DiscardingLogger})
   436  	if err != nil {
   437  		return nil, err
   438  	}
   439  
   440  	ret.tail = t
   441  	go func() {
   442  		for {
   443  			select {
   444  			case line, ok := <-t.Lines:
   445  				if !ok {
   446  					ret.Stop()
   447  					return
   448  				}
   449  				ret.stream <- LogRecord{Message: line.Text}
   450  			case <-ret.stop:
   451  				return
   452  			}
   453  		}
   454  
   455  	}()
   456  	return ret, nil
   457  }
   458  
   459  type logStream struct {
   460  	tail    *tail.Tail
   461  	service string
   462  	stream  chan LogRecord
   463  	sync.Mutex
   464  	stop chan bool
   465  	err  error
   466  }
   467  
   468  func (l *logStream) Chan() chan LogRecord {
   469  	return l.stream
   470  }
   471  
   472  func (l *logStream) Error() error {
   473  	return l.err
   474  }
   475  
   476  func (l *logStream) Stop() error {
   477  	l.Lock()
   478  	defer l.Unlock()
   479  
   480  	select {
   481  	case <-l.stop:
   482  		return nil
   483  	default:
   484  		close(l.stop)
   485  		close(l.stream)
   486  		err := l.tail.Stop()
   487  		if err != nil {
   488  			logger.Errorf("Error stopping tail: %v", err)
   489  			return err
   490  		}
   491  	}
   492  	return nil
   493  }
   494  
   495  // Read returns all instances of requested service
   496  // If no service name is provided we return all the track services.
   497  func (r *runtime) Read(opts ...ReadOption) ([]*Service, error) {
   498  	r.Lock()
   499  	defer r.Unlock()
   500  
   501  	gopts := ReadOptions{}
   502  	for _, o := range opts {
   503  		o(&gopts)
   504  	}
   505  	if len(gopts.Namespace) == 0 {
   506  		gopts.Namespace = defaultNamespace
   507  	}
   508  
   509  	save := func(k, v string) bool {
   510  		if len(k) == 0 {
   511  			return true
   512  		}
   513  		return k == v
   514  	}
   515  
   516  	//nolint:prealloc
   517  	var services []*Service
   518  
   519  	if _, ok := r.namespaces[gopts.Namespace]; !ok {
   520  		return make([]*Service, 0), nil
   521  	}
   522  
   523  	for _, service := range r.namespaces[gopts.Namespace] {
   524  		if !save(gopts.Service, service.Name) {
   525  			continue
   526  		}
   527  		if !save(gopts.Version, service.Version) {
   528  			continue
   529  		}
   530  		// TODO deal with service type
   531  		// no version has sbeen requested, just append the service
   532  		services = append(services, service.Service)
   533  	}
   534  
   535  	return services, nil
   536  }
   537  
   538  // Update attempts to update the service
   539  func (r *runtime) Update(s *Service, opts ...UpdateOption) error {
   540  	var options UpdateOptions
   541  	for _, o := range opts {
   542  		o(&options)
   543  	}
   544  	if len(options.Namespace) == 0 {
   545  		options.Namespace = defaultNamespace
   546  	}
   547  
   548  	err := r.checkoutSourceIfNeeded(s)
   549  	if err != nil {
   550  		return err
   551  	}
   552  
   553  	r.Lock()
   554  	srvs, ok := r.namespaces[options.Namespace]
   555  	r.Unlock()
   556  	if !ok {
   557  		return errors.New("Service not found")
   558  	}
   559  
   560  	r.Lock()
   561  	service, ok := srvs[serviceKey(s)]
   562  	r.Unlock()
   563  	if !ok {
   564  		return errors.New("Service not found")
   565  	}
   566  
   567  	if err := service.Stop(); err != nil && err.Error() != "no such process" {
   568  		logger.Errorf("Error stopping service %s: %s", service.Name, err)
   569  		return err
   570  	}
   571  
   572  	return service.Start()
   573  }
   574  
   575  // Delete removes the service from the runtime and stops it
   576  func (r *runtime) Delete(s *Service, opts ...DeleteOption) error {
   577  	r.Lock()
   578  	defer r.Unlock()
   579  
   580  	var options DeleteOptions
   581  	for _, o := range opts {
   582  		o(&options)
   583  	}
   584  	if len(options.Namespace) == 0 {
   585  		options.Namespace = defaultNamespace
   586  	}
   587  
   588  	srvs, ok := r.namespaces[options.Namespace]
   589  	if !ok {
   590  		return nil
   591  	}
   592  
   593  	if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   594  		logger.Debugf("Runtime deleting service %s", s.Name)
   595  	}
   596  
   597  	service, ok := srvs[serviceKey(s)]
   598  	if !ok {
   599  		return nil
   600  	}
   601  
   602  	// check if running
   603  	if !service.Running() {
   604  		delete(srvs, service.key())
   605  		r.namespaces[options.Namespace] = srvs
   606  		return nil
   607  	}
   608  	// otherwise stop it
   609  	if err := service.Stop(); err != nil {
   610  		return err
   611  	}
   612  	// delete it
   613  	delete(srvs, service.key())
   614  	r.namespaces[options.Namespace] = srvs
   615  	return nil
   616  }
   617  
   618  // Start starts the runtime
   619  func (r *runtime) Start() error {
   620  	r.Lock()
   621  	defer r.Unlock()
   622  
   623  	// already running
   624  	if r.running {
   625  		return nil
   626  	}
   627  
   628  	// set running
   629  	r.running = true
   630  	r.closed = make(chan bool)
   631  
   632  	var events <-chan Event
   633  	if r.options.Scheduler != nil {
   634  		var err error
   635  		events, err = r.options.Scheduler.Notify()
   636  		if err != nil {
   637  			// TODO: should we bail here?
   638  			if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   639  				logger.Debugf("Runtime failed to start update notifier")
   640  			}
   641  		}
   642  	}
   643  
   644  	go r.run(events)
   645  
   646  	return nil
   647  }
   648  
   649  // Stop stops the runtime
   650  func (r *runtime) Stop() error {
   651  	r.Lock()
   652  	defer r.Unlock()
   653  
   654  	if !r.running {
   655  		return nil
   656  	}
   657  
   658  	select {
   659  	case <-r.closed:
   660  		return nil
   661  	default:
   662  		close(r.closed)
   663  
   664  		// set not running
   665  		r.running = false
   666  
   667  		// stop all the services
   668  		for _, services := range r.namespaces {
   669  			for _, service := range services {
   670  				if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   671  					logger.Debugf("Runtime stopping %s", service.Name)
   672  				}
   673  				service.Stop()
   674  			}
   675  		}
   676  
   677  		// stop the scheduler
   678  		if r.options.Scheduler != nil {
   679  			return r.options.Scheduler.Close()
   680  		}
   681  	}
   682  
   683  	return nil
   684  }
   685  
   686  // String implements stringer interface
   687  func (r *runtime) String() string {
   688  	return "local"
   689  }