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  }