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