github.com/xiaobinqt/libcompose@v1.1.0/docker/service/service.go (about) 1 package service 2 3 import ( 4 "fmt" 5 "strings" 6 "time" 7 8 "golang.org/x/net/context" 9 10 "github.com/docker/docker/api/types" 11 "github.com/docker/docker/api/types/filters" 12 "github.com/docker/docker/api/types/network" 13 "github.com/docker/docker/client" 14 "github.com/docker/go-connections/nat" 15 "github.com/sirupsen/logrus" 16 "github.com/xiaobinqt/libcompose/config" 17 "github.com/xiaobinqt/libcompose/docker/auth" 18 "github.com/xiaobinqt/libcompose/docker/builder" 19 composeclient "github.com/xiaobinqt/libcompose/docker/client" 20 "github.com/xiaobinqt/libcompose/docker/container" 21 "github.com/xiaobinqt/libcompose/docker/ctx" 22 "github.com/xiaobinqt/libcompose/docker/image" 23 "github.com/xiaobinqt/libcompose/labels" 24 "github.com/xiaobinqt/libcompose/project" 25 "github.com/xiaobinqt/libcompose/project/events" 26 "github.com/xiaobinqt/libcompose/project/options" 27 "github.com/xiaobinqt/libcompose/utils" 28 "github.com/xiaobinqt/libcompose/yaml" 29 ) 30 31 // Service is a project.Service implementations. 32 type Service struct { 33 name string 34 project *project.Project 35 serviceConfig *config.ServiceConfig 36 clientFactory composeclient.Factory 37 authLookup auth.Lookup 38 39 // FIXME(vdemeester) remove this at some point 40 context *ctx.Context 41 } 42 43 // NewService creates a service 44 func NewService(name string, serviceConfig *config.ServiceConfig, context *ctx.Context) *Service { 45 return &Service{ 46 name: name, 47 project: context.Project, 48 serviceConfig: serviceConfig, 49 clientFactory: context.ClientFactory, 50 authLookup: context.AuthLookup, 51 context: context, 52 } 53 } 54 55 // Name returns the service name. 56 func (s *Service) Name() string { 57 return s.name 58 } 59 60 // Config returns the configuration of the service (config.ServiceConfig). 61 func (s *Service) Config() *config.ServiceConfig { 62 return s.serviceConfig 63 } 64 65 // DependentServices returns the dependent services (as an array of ServiceRelationship) of the service. 66 func (s *Service) DependentServices() []project.ServiceRelationship { 67 return DefaultDependentServices(s.project, s) 68 } 69 70 // Create implements Service.Create. It ensures the image exists or build it 71 // if it can and then create a container. 72 func (s *Service) Create(ctx context.Context, options options.Create) error { 73 containers, err := s.collectContainers(ctx) 74 if err != nil { 75 return err 76 } 77 78 if err := s.ensureImageExists(ctx, options.NoBuild, options.ForceBuild); err != nil { 79 return err 80 } 81 82 if len(containers) != 0 { 83 return s.eachContainer(ctx, containers, func(c *container.Container) error { 84 _, err := s.recreateIfNeeded(ctx, c, options.NoRecreate, options.ForceRecreate) 85 return err 86 }) 87 } 88 89 namer, err := s.namer(ctx, 1) 90 if err != nil { 91 return err 92 } 93 94 _, err = s.createContainer(ctx, namer, "", nil, false) 95 return err 96 } 97 98 func (s *Service) namer(ctx context.Context, count int) (Namer, error) { 99 var namer Namer 100 var err error 101 102 if s.serviceConfig.ContainerName != "" { 103 if count > 1 { 104 logrus.Warnf(`The "%s" service is using the custom container name "%s". Docker requires each container to have a unique name. Remove the custom name to scale the service.`, s.name, s.serviceConfig.ContainerName) 105 } 106 namer = NewSingleNamer(s.serviceConfig.ContainerName) 107 } else { 108 client := s.clientFactory.Create(s) 109 namer, err = NewNamer(ctx, client, s.project.Name, s.name, false) 110 if err != nil { 111 return nil, err 112 } 113 } 114 return namer, nil 115 } 116 117 func (s *Service) collectContainers(ctx context.Context) ([]*container.Container, error) { 118 client := s.clientFactory.Create(s) 119 containers, err := container.ListByFilter(ctx, client, labels.SERVICE.Eq(s.name), labels.PROJECT.Eq(s.project.Name)) 120 if err != nil { 121 return nil, err 122 } 123 124 result := []*container.Container{} 125 126 for _, cont := range containers { 127 c, err := container.New(ctx, client, cont.ID) 128 if err != nil { 129 return nil, err 130 } 131 result = append(result, c) 132 } 133 134 return result, nil 135 } 136 137 func (s *Service) ensureImageExists(ctx context.Context, noBuild bool, forceBuild bool) error { 138 if forceBuild { 139 return s.build(ctx, options.Build{}) 140 } 141 142 exists, err := image.Exists(ctx, s.clientFactory.Create(s), s.imageName()) 143 if err != nil { 144 return err 145 } 146 if exists { 147 return nil 148 } 149 150 if s.Config().Build.Context != "" { 151 if noBuild { 152 return fmt.Errorf("Service %q needs to be built, but no-build was specified", s.name) 153 } 154 return s.build(ctx, options.Build{}) 155 } 156 157 return s.Pull(ctx) 158 } 159 160 func (s *Service) imageName() string { 161 if s.Config().Image != "" { 162 return s.Config().Image 163 } 164 return fmt.Sprintf("%s_%s", s.project.Name, s.Name()) 165 } 166 167 // Build implements Service.Build. It will try to build the image and returns an error if any. 168 func (s *Service) Build(ctx context.Context, buildOptions options.Build) error { 169 return s.build(ctx, buildOptions) 170 } 171 172 func (s *Service) build(ctx context.Context, buildOptions options.Build) error { 173 if s.Config().Build.Context == "" { 174 return fmt.Errorf("Specified service does not have a build section") 175 } 176 builder := &builder.DaemonBuilder{ 177 Client: s.clientFactory.Create(s), 178 ContextDirectory: s.Config().Build.Context, 179 Dockerfile: s.Config().Build.Dockerfile, 180 BuildArgs: s.Config().Build.Args, 181 AuthConfigs: s.authLookup.All(), 182 NoCache: buildOptions.NoCache, 183 ForceRemove: buildOptions.ForceRemove, 184 Pull: buildOptions.Pull, 185 LoggerFactory: s.context.LoggerFactory, 186 } 187 return builder.Build(ctx, s.imageName()) 188 } 189 190 func (s *Service) constructContainers(ctx context.Context, count int) ([]*container.Container, error) { 191 result, err := s.collectContainers(ctx) 192 if err != nil { 193 return nil, err 194 } 195 196 client := s.clientFactory.Create(s) 197 198 var namer Namer 199 200 if s.serviceConfig.ContainerName != "" { 201 if count > 1 { 202 logrus.Warnf(`The "%s" service is using the custom container name "%s". Docker requires each container to have a unique name. Remove the custom name to scale the service.`, s.name, s.serviceConfig.ContainerName) 203 } 204 namer = NewSingleNamer(s.serviceConfig.ContainerName) 205 } else { 206 namer, err = NewNamer(ctx, client, s.project.Name, s.name, false) 207 if err != nil { 208 return nil, err 209 } 210 } 211 212 for i := len(result); i < count; i++ { 213 c, err := s.createContainer(ctx, namer, "", nil, false) 214 if err != nil { 215 return nil, err 216 } 217 218 id := c.ID() 219 logrus.Debugf("Created container %s: %v", id, c.Name()) 220 221 result = append(result, c) 222 } 223 224 return result, nil 225 } 226 227 // Up implements Service.Up. It builds the image if needed, creates a container 228 // and start it. 229 func (s *Service) Up(ctx context.Context, options options.Up) error { 230 containers, err := s.collectContainers(ctx) 231 if err != nil { 232 return err 233 } 234 235 var imageName = s.imageName() 236 if len(containers) == 0 || !options.NoRecreate { 237 if err = s.ensureImageExists(ctx, options.NoBuild, options.ForceBuild); err != nil { 238 return err 239 } 240 } 241 242 return s.up(ctx, imageName, true, options) 243 } 244 245 // Run implements Service.Run. It runs a one of command within the service container. 246 // It always create a new container. 247 func (s *Service) Run(ctx context.Context, commandParts []string, options options.Run) (int, error) { 248 err := s.ensureImageExists(ctx, false, false) 249 if err != nil { 250 return -1, err 251 } 252 253 client := s.clientFactory.Create(s) 254 255 namer, err := NewNamer(ctx, client, s.project.Name, s.name, true) 256 if err != nil { 257 return -1, err 258 } 259 260 configOverride := &config.ServiceConfig{Command: commandParts, Tty: !options.DisableTty, StdinOpen: !options.DisableTty} 261 262 c, err := s.createContainer(ctx, namer, "", configOverride, true) 263 if err != nil { 264 return -1, err 265 } 266 267 if err := s.connectContainerToNetworks(ctx, c, true); err != nil { 268 return -1, err 269 } 270 271 if options.Detached { 272 logrus.Infof("%s", c.Name()) 273 return 0, c.Start(ctx) 274 } 275 return c.Run(ctx, configOverride) 276 } 277 278 // Info implements Service.Info. It returns an project.InfoSet with the containers 279 // related to this service (can be multiple if using the scale command). 280 func (s *Service) Info(ctx context.Context) (project.InfoSet, error) { 281 result := project.InfoSet{} 282 containers, err := s.collectContainers(ctx) 283 if err != nil { 284 return nil, err 285 } 286 287 for _, c := range containers { 288 info, err := c.Info(ctx) 289 if err != nil { 290 return nil, err 291 } 292 result = append(result, info) 293 } 294 295 return result, nil 296 } 297 298 // Start implements Service.Start. It tries to start a container without creating it. 299 func (s *Service) Start(ctx context.Context) error { 300 return s.collectContainersAndDo(ctx, func(c *container.Container) error { 301 if err := s.connectContainerToNetworks(ctx, c, false); err != nil { 302 return err 303 } 304 return c.Start(ctx) 305 }) 306 } 307 308 func (s *Service) up(ctx context.Context, imageName string, create bool, options options.Up) error { 309 containers, err := s.collectContainers(ctx) 310 if err != nil { 311 return err 312 } 313 314 logrus.Debugf("Found %d existing containers for service %s", len(containers), s.name) 315 316 if len(containers) == 0 && create { 317 namer, err := s.namer(ctx, 1) 318 if err != nil { 319 return err 320 } 321 c, err := s.createContainer(ctx, namer, "", nil, false) 322 if err != nil { 323 return err 324 } 325 containers = []*container.Container{c} 326 } 327 328 return s.eachContainer(ctx, containers, func(c *container.Container) error { 329 var err error 330 if create { 331 c, err = s.recreateIfNeeded(ctx, c, options.NoRecreate, options.ForceRecreate) 332 if err != nil { 333 return err 334 } 335 } 336 337 if err := s.connectContainerToNetworks(ctx, c, false); err != nil { 338 return err 339 } 340 341 err = c.Start(ctx) 342 343 if err == nil { 344 s.project.Notify(events.ContainerStarted, s.name, map[string]string{ 345 "name": c.Name(), 346 }) 347 } 348 349 return err 350 }) 351 } 352 353 func (s *Service) connectContainerToNetworks(ctx context.Context, c *container.Container, oneOff bool) error { 354 connectedNetworks, err := c.Networks() 355 if err != nil { 356 return nil 357 } 358 if s.serviceConfig.Networks != nil { 359 for _, network := range s.serviceConfig.Networks.Networks { 360 existingNetwork, ok := connectedNetworks[network.RealName] 361 if ok { 362 // FIXME(vdemeester) implement alias checking (to not disconnect/reconnect for nothing) 363 aliasPresent := false 364 for _, alias := range existingNetwork.Aliases { 365 ID := c.ShortID() 366 if alias == ID { 367 aliasPresent = true 368 } 369 } 370 if aliasPresent { 371 continue 372 } 373 if err := s.NetworkDisconnect(ctx, c, network, oneOff); err != nil { 374 return err 375 } 376 } 377 if err := s.NetworkConnect(ctx, c, network, oneOff); err != nil { 378 return err 379 } 380 } 381 } 382 return nil 383 } 384 385 // NetworkDisconnect disconnects the container from the specified network 386 func (s *Service) NetworkDisconnect(ctx context.Context, c *container.Container, net *yaml.Network, oneOff bool) error { 387 containerID := c.ID() 388 client := s.clientFactory.Create(s) 389 return client.NetworkDisconnect(ctx, net.RealName, containerID, true) 390 } 391 392 // NetworkConnect connects the container to the specified network 393 // FIXME(vdemeester) will be refactor with Container refactoring 394 func (s *Service) NetworkConnect(ctx context.Context, c *container.Container, net *yaml.Network, oneOff bool) error { 395 containerID := c.ID() 396 client := s.clientFactory.Create(s) 397 internalLinks, err := s.getLinks() 398 if err != nil { 399 return err 400 } 401 links := []string{} 402 // TODO(vdemeester) handle link to self (?) 403 for k, v := range internalLinks { 404 links = append(links, strings.Join([]string{v, k}, ":")) 405 } 406 for _, v := range s.serviceConfig.ExternalLinks { 407 links = append(links, v) 408 } 409 aliases := []string{} 410 if !oneOff { 411 aliases = []string{s.Name()} 412 } 413 aliases = append(aliases, net.Aliases...) 414 if len(net.Aliases) >= 1 { 415 logrus.Infof("connect") 416 client.NetworkConnect(ctx, net.RealName, containerID, &network.EndpointSettings{ 417 Aliases: aliases, 418 Links: links, 419 IPAddress: net.IPv4Address, 420 IPAMConfig: &network.EndpointIPAMConfig{ 421 IPv4Address: net.IPv4Address, 422 IPv6Address: net.IPv6Address, 423 }, 424 }) 425 logrus.Infof("disconnect") 426 client.NetworkDisconnect(ctx, net.RealName, containerID, true) 427 } 428 return client.NetworkConnect(ctx, net.RealName, containerID, &network.EndpointSettings{ 429 Aliases: aliases, 430 Links: links, 431 IPAddress: net.IPv4Address, 432 IPAMConfig: &network.EndpointIPAMConfig{ 433 IPv4Address: net.IPv4Address, 434 IPv6Address: net.IPv6Address, 435 }, 436 }) 437 } 438 439 func (s *Service) recreateIfNeeded(ctx context.Context, c *container.Container, noRecreate, forceRecreate bool) (*container.Container, error) { 440 if noRecreate { 441 return c, nil 442 } 443 outOfSync, err := s.OutOfSync(ctx, c) 444 if err != nil { 445 return c, err 446 } 447 448 logrus.WithFields(logrus.Fields{ 449 "outOfSync": outOfSync, 450 "ForceRecreate": forceRecreate, 451 "NoRecreate": noRecreate}).Debug("Going to decide if recreate is needed") 452 453 if forceRecreate || outOfSync { 454 logrus.Infof("Recreating %s", s.name) 455 newContainer, err := s.recreate(ctx, c) 456 if err != nil { 457 return c, err 458 } 459 return newContainer, nil 460 } 461 462 return c, err 463 } 464 465 func (s *Service) recreate(ctx context.Context, c *container.Container) (*container.Container, error) { 466 name := c.Name() 467 id := c.ID() 468 newName := fmt.Sprintf("%s_%s", name, id[:12]) 469 logrus.Debugf("Renaming %s => %s", name, newName) 470 if err := c.Rename(ctx, newName); err != nil { 471 logrus.Errorf("Failed to rename old container %s", c.Name()) 472 return nil, err 473 } 474 namer := NewSingleNamer(name) 475 newContainer, err := s.createContainer(ctx, namer, id, nil, false) 476 if err != nil { 477 return nil, err 478 } 479 newID := newContainer.ID() 480 logrus.Debugf("Created replacement container %s", newID) 481 if err := c.Remove(ctx, false); err != nil { 482 logrus.Errorf("Failed to remove old container %s", c.Name()) 483 return nil, err 484 } 485 logrus.Debugf("Removed old container %s %s", c.Name(), id) 486 return newContainer, nil 487 } 488 489 // OutOfSync checks if the container is out of sync with the service definition. 490 // It looks if the the service hash container label is the same as the computed one. 491 func (s *Service) OutOfSync(ctx context.Context, c *container.Container) (bool, error) { 492 if c.ImageConfig() != s.serviceConfig.Image { 493 logrus.Debugf("Images for %s do not match %s!=%s", c.Name(), c.ImageConfig(), s.serviceConfig.Image) 494 return true, nil 495 } 496 497 expectedHash := config.GetServiceHash(s.name, s.Config()) 498 if c.Hash() != expectedHash { 499 logrus.Debugf("Hashes for %s do not match %s!=%s", c.Name(), c.Hash(), expectedHash) 500 return true, nil 501 } 502 503 image, err := image.InspectImage(ctx, s.clientFactory.Create(s), c.ImageConfig()) 504 if err != nil { 505 if client.IsErrNotFound(err) { 506 logrus.Debugf("Image %s do not exist, do not know if it's out of sync", c.Image()) 507 return false, nil 508 } 509 return false, err 510 } 511 512 logrus.Debugf("Checking existing image name vs id: %s == %s", image.ID, c.Image()) 513 return image.ID != c.Image(), err 514 } 515 516 func (s *Service) collectContainersAndDo(ctx context.Context, action func(*container.Container) error) error { 517 containers, err := s.collectContainers(ctx) 518 if err != nil { 519 return err 520 } 521 return s.eachContainer(ctx, containers, action) 522 } 523 524 func (s *Service) eachContainer(ctx context.Context, containers []*container.Container, action func(*container.Container) error) error { 525 526 tasks := utils.InParallel{} 527 for _, cont := range containers { 528 task := func(cont *container.Container) func() error { 529 return func() error { 530 return action(cont) 531 } 532 }(cont) 533 534 tasks.Add(task) 535 } 536 537 return tasks.Wait() 538 } 539 540 // Stop implements Service.Stop. It stops any containers related to the service. 541 func (s *Service) Stop(ctx context.Context, timeout int) error { 542 timeout = s.stopTimeout(timeout) 543 return s.collectContainersAndDo(ctx, func(c *container.Container) error { 544 return c.Stop(ctx, timeout) 545 }) 546 } 547 548 // Restart implements Service.Restart. It restarts any containers related to the service. 549 func (s *Service) Restart(ctx context.Context, timeout int) error { 550 timeout = s.stopTimeout(timeout) 551 return s.collectContainersAndDo(ctx, func(c *container.Container) error { 552 return c.Restart(ctx, timeout) 553 }) 554 } 555 556 // Kill implements Service.Kill. It kills any containers related to the service. 557 func (s *Service) Kill(ctx context.Context, signal string) error { 558 return s.collectContainersAndDo(ctx, func(c *container.Container) error { 559 return c.Kill(ctx, signal) 560 }) 561 } 562 563 // Delete implements Service.Delete. It removes any containers related to the service. 564 func (s *Service) Delete(ctx context.Context, options options.Delete) error { 565 return s.collectContainersAndDo(ctx, func(c *container.Container) error { 566 running := c.IsRunning(ctx) 567 if !running || options.RemoveRunning { 568 return c.Remove(ctx, options.RemoveVolume) 569 } 570 return nil 571 }) 572 } 573 574 // Log implements Service.Log. It returns the docker logs for each container related to the service. 575 func (s *Service) Log(ctx context.Context, follow bool) error { 576 return s.collectContainersAndDo(ctx, func(c *container.Container) error { 577 containerNumber, err := c.Number() 578 if err != nil { 579 return err 580 } 581 name := fmt.Sprintf("%s_%d", s.name, containerNumber) 582 if s.Config().ContainerName != "" { 583 name = s.Config().ContainerName 584 } 585 l := s.context.LoggerFactory.CreateContainerLogger(name) 586 return c.Log(ctx, l, follow) 587 }) 588 } 589 590 // Scale implements Service.Scale. It creates or removes containers to have the specified number 591 // of related container to the service to run. 592 func (s *Service) Scale(ctx context.Context, scale int, timeout int) error { 593 if s.specificiesHostPort() { 594 logrus.Warnf("The \"%s\" service specifies a port on the host. If multiple containers for this service are created on a single host, the port will clash.", s.Name()) 595 } 596 597 containers, err := s.collectContainers(ctx) 598 if err != nil { 599 return err 600 } 601 if len(containers) > scale { 602 foundCount := 0 603 for _, c := range containers { 604 foundCount++ 605 if foundCount > scale { 606 timeout = s.stopTimeout(timeout) 607 if err := c.Stop(ctx, timeout); err != nil { 608 return err 609 } 610 // FIXME(vdemeester) remove volume in scale by default ? 611 if err := c.Remove(ctx, false); err != nil { 612 return err 613 } 614 } 615 } 616 } 617 618 if err != nil { 619 return err 620 } 621 622 if len(containers) < scale { 623 err := s.ensureImageExists(ctx, false, false) 624 if err != nil { 625 return err 626 } 627 628 if _, err = s.constructContainers(ctx, scale); err != nil { 629 return err 630 } 631 } 632 633 return s.up(ctx, "", false, options.Up{}) 634 } 635 636 // Pull implements Service.Pull. It pulls the image of the service and skip the service that 637 // would need to be built. 638 func (s *Service) Pull(ctx context.Context) error { 639 if s.Config().Image == "" { 640 return nil 641 } 642 643 return image.PullImage(ctx, s.clientFactory.Create(s), s.name, s.authLookup, s.Config().Image) 644 } 645 646 // Pause implements Service.Pause. It puts into pause the container(s) related 647 // to the service. 648 func (s *Service) Pause(ctx context.Context) error { 649 return s.collectContainersAndDo(ctx, func(c *container.Container) error { 650 return c.Pause(ctx) 651 }) 652 } 653 654 // Unpause implements Service.Pause. It brings back from pause the container(s) 655 // related to the service. 656 func (s *Service) Unpause(ctx context.Context) error { 657 return s.collectContainersAndDo(ctx, func(c *container.Container) error { 658 return c.Unpause(ctx) 659 }) 660 } 661 662 // RemoveImage implements Service.RemoveImage. It removes images used for the service 663 // depending on the specified type. 664 func (s *Service) RemoveImage(ctx context.Context, imageType options.ImageType) error { 665 switch imageType { 666 case "local": 667 if s.Config().Image != "" { 668 return nil 669 } 670 return image.RemoveImage(ctx, s.clientFactory.Create(s), s.imageName()) 671 case "all": 672 return image.RemoveImage(ctx, s.clientFactory.Create(s), s.imageName()) 673 default: 674 // Don't do a thing, should be validated up-front 675 return nil 676 } 677 } 678 679 var eventAttributes = []string{"image", "name"} 680 681 // Events implements Service.Events. It listen to all real-time events happening 682 // for the service, and put them into the specified chan. 683 func (s *Service) Events(ctx context.Context, evts chan events.ContainerEvent) error { 684 filter := filters.NewArgs() 685 filter.Add("label", fmt.Sprintf("%s=%s", labels.PROJECT, s.project.Name)) 686 filter.Add("label", fmt.Sprintf("%s=%s", labels.SERVICE, s.name)) 687 client := s.clientFactory.Create(s) 688 eventq, errq := client.Events(ctx, types.EventsOptions{ 689 Filters: filter, 690 }) 691 go func() { 692 for { 693 select { 694 case event := <-eventq: 695 service := event.Actor.Attributes[labels.SERVICE.Str()] 696 attributes := map[string]string{} 697 for _, attr := range eventAttributes { 698 attributes[attr] = event.Actor.Attributes[attr] 699 } 700 e := events.ContainerEvent{ 701 Service: service, 702 Event: event.Action, 703 Type: event.Type, 704 ID: event.Actor.ID, 705 Time: time.Unix(event.Time, 0), 706 Attributes: attributes, 707 } 708 evts <- e 709 } 710 } 711 }() 712 return <-errq 713 } 714 715 // Containers implements Service.Containers. It returns the list of containers 716 // that are related to the service. 717 func (s *Service) Containers(ctx context.Context) ([]project.Container, error) { 718 result := []project.Container{} 719 containers, err := s.collectContainers(ctx) 720 if err != nil { 721 return nil, err 722 } 723 724 for _, c := range containers { 725 result = append(result, c) 726 } 727 728 return result, nil 729 } 730 731 func (s *Service) specificiesHostPort() bool { 732 _, bindings, err := nat.ParsePortSpecs(s.Config().Ports) 733 734 if err != nil { 735 fmt.Println(err) 736 } 737 738 for _, portBindings := range bindings { 739 for _, portBinding := range portBindings { 740 if portBinding.HostPort != "" { 741 return true 742 } 743 } 744 } 745 746 return false 747 } 748 749 // take in timeout flag from cli as parameter 750 // return timeout if it is set, 751 // else return stop_grace_period if it is set, 752 // else return default 10s 753 func (s *Service) stopTimeout(timeout int) int { 754 DEFAULTTIMEOUT := 10 755 if timeout != 0 { 756 return timeout 757 } 758 configTimeout := utils.DurationStrToSecondsInt(s.Config().StopGracePeriod) 759 if configTimeout != nil { 760 return *configTimeout 761 } 762 return DEFAULTTIMEOUT 763 }