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 }