github.com/skippbox/kompose-origin@v0.0.0-20160524133224-16a9dca7bac2/project/project.go (about)

     1  package project
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"golang.org/x/net/context"
     9  
    10  	log "github.com/Sirupsen/logrus"
    11  	"github.com/docker/engine-api/types"
    12  	"github.com/docker/engine-api/types/filters"
    13  	"github.com/docker/libcompose/config"
    14  	"github.com/docker/libcompose/labels"
    15  	"github.com/docker/libcompose/logger"
    16  	"github.com/docker/libcompose/project/events"
    17  	"github.com/docker/libcompose/project/options"
    18  	"github.com/docker/libcompose/utils"
    19  )
    20  
    21  type wrapperAction func(*serviceWrapper, map[string]*serviceWrapper)
    22  type serviceAction func(service Service) error
    23  
    24  // Project holds libcompose project information.
    25  type Project struct {
    26  	Name           string
    27  	ServiceConfigs *config.ServiceConfigs
    28  	VolumeConfigs  map[string]*config.VolumeConfig
    29  	NetworkConfigs map[string]*config.NetworkConfig
    30  	Files          []string
    31  	ReloadCallback func() error
    32  	ParseOptions   *config.ParseOptions
    33  
    34  	context       *Context
    35  	clientFactory ClientFactory
    36  	reload        []string
    37  	upCount       int
    38  	listeners     []chan<- events.Event
    39  	hasListeners  bool
    40  }
    41  
    42  // NewProject creates a new project with the specified context.
    43  func NewProject(clientFactory ClientFactory, context *Context, parseOptions *config.ParseOptions) *Project {
    44  	p := &Project{
    45  		context:        context,
    46  		clientFactory:  clientFactory,
    47  		ParseOptions:   parseOptions,
    48  		ServiceConfigs: config.NewServiceConfigs(),
    49  		VolumeConfigs:  make(map[string]*config.VolumeConfig),
    50  		NetworkConfigs: make(map[string]*config.NetworkConfig),
    51  	}
    52  
    53  	if context.LoggerFactory == nil {
    54  		context.LoggerFactory = &logger.NullLogger{}
    55  	}
    56  
    57  	context.Project = p
    58  
    59  	p.listeners = []chan<- events.Event{NewDefaultListener(p)}
    60  
    61  	return p
    62  }
    63  
    64  // Parse populates project information based on its context. It sets up the name,
    65  // the composefile and the composebytes (the composefile content).
    66  func (p *Project) Parse() error {
    67  	err := p.context.open()
    68  	if err != nil {
    69  		return err
    70  	}
    71  
    72  	p.Name = p.context.ProjectName
    73  
    74  	p.Files = p.context.ComposeFiles
    75  
    76  	if len(p.Files) == 1 && p.Files[0] == "-" {
    77  		p.Files = []string{"."}
    78  	}
    79  
    80  	if p.context.ComposeBytes != nil {
    81  		for i, composeBytes := range p.context.ComposeBytes {
    82  			file := ""
    83  			if i < len(p.context.ComposeFiles) {
    84  				file = p.Files[i]
    85  			}
    86  			if err := p.load(file, composeBytes); err != nil {
    87  				return err
    88  			}
    89  		}
    90  	}
    91  
    92  	return nil
    93  }
    94  
    95  // CreateService creates a service with the specified name based. If there
    96  // is no config in the project for this service, it will return an error.
    97  func (p *Project) CreateService(name string) (Service, error) {
    98  	existing, ok := p.ServiceConfigs.Get(name)
    99  	if !ok {
   100  		return nil, fmt.Errorf("Failed to find service: %s", name)
   101  	}
   102  
   103  	// Copy because we are about to modify the environment
   104  	config := *existing
   105  
   106  	if p.context.EnvironmentLookup != nil {
   107  		parsedEnv := make([]string, 0, len(config.Environment))
   108  
   109  		for _, env := range config.Environment {
   110  			parts := strings.SplitN(env, "=", 2)
   111  			if len(parts) > 1 && parts[1] != "" {
   112  				parsedEnv = append(parsedEnv, env)
   113  				continue
   114  			} else {
   115  				env = parts[0]
   116  			}
   117  
   118  			for _, value := range p.context.EnvironmentLookup.Lookup(env, name, &config) {
   119  				parsedEnv = append(parsedEnv, value)
   120  			}
   121  		}
   122  
   123  		config.Environment = parsedEnv
   124  	}
   125  
   126  	return p.context.ServiceFactory.Create(p, name, &config)
   127  }
   128  
   129  // AddConfig adds the specified service config for the specified name.
   130  func (p *Project) AddConfig(name string, config *config.ServiceConfig) error {
   131  	p.Notify(events.ServiceAdd, name, nil)
   132  
   133  	p.ServiceConfigs.Add(name, config)
   134  	p.reload = append(p.reload, name)
   135  
   136  	return nil
   137  }
   138  
   139  // AddVolumeConfig adds the specified volume config for the specified name.
   140  func (p *Project) AddVolumeConfig(name string, config *config.VolumeConfig) error {
   141  	p.Notify(events.VolumeAdd, name, nil)
   142  	p.VolumeConfigs[name] = config
   143  	return nil
   144  }
   145  
   146  // AddNetworkConfig adds the specified network config for the specified name.
   147  func (p *Project) AddNetworkConfig(name string, config *config.NetworkConfig) error {
   148  	p.Notify(events.NetworkAdd, name, nil)
   149  	p.NetworkConfigs[name] = config
   150  	return nil
   151  }
   152  
   153  // Load loads the specified byte array (the composefile content) and adds the
   154  // service configuration to the project.
   155  // FIXME is it needed ?
   156  func (p *Project) Load(bytes []byte) error {
   157  	return p.load("", bytes)
   158  }
   159  
   160  func (p *Project) load(file string, bytes []byte) error {
   161  	serviceConfigs, volumeConfigs, networkConfigs, err := config.Merge(p.ServiceConfigs, p.context.EnvironmentLookup, p.context.ResourceLookup, file, bytes, p.ParseOptions)
   162  	if err != nil {
   163  		log.Errorf("Could not parse config for project %s : %v", p.Name, err)
   164  		return err
   165  	}
   166  
   167  	for name, config := range serviceConfigs {
   168  		err := p.AddConfig(name, config)
   169  		if err != nil {
   170  			return err
   171  		}
   172  	}
   173  
   174  	for name, config := range volumeConfigs {
   175  		err := p.AddVolumeConfig(name, config)
   176  		if err != nil {
   177  			return err
   178  		}
   179  	}
   180  
   181  	for name, config := range networkConfigs {
   182  		err := p.AddNetworkConfig(name, config)
   183  		if err != nil {
   184  			return err
   185  		}
   186  	}
   187  
   188  	return nil
   189  }
   190  
   191  func (p *Project) loadWrappers(wrappers map[string]*serviceWrapper, servicesToConstruct []string) error {
   192  	for _, name := range servicesToConstruct {
   193  		wrapper, err := newServiceWrapper(name, p)
   194  		if err != nil {
   195  			return err
   196  		}
   197  		wrappers[name] = wrapper
   198  	}
   199  
   200  	return nil
   201  }
   202  
   203  // Build builds the specified services (like docker build).
   204  func (p *Project) Build(ctx context.Context, buildOptions options.Build, services ...string) error {
   205  	return p.perform(events.ProjectBuildStart, events.ProjectBuildDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
   206  		wrapper.Do(wrappers, events.ServiceBuildStart, events.ServiceBuild, func(service Service) error {
   207  			return service.Build(ctx, buildOptions)
   208  		})
   209  	}), nil)
   210  }
   211  
   212  // Create creates the specified services (like docker create).
   213  func (p *Project) Create(ctx context.Context, options options.Create, services ...string) error {
   214  	if options.NoRecreate && options.ForceRecreate {
   215  		return fmt.Errorf("no-recreate and force-recreate cannot be combined")
   216  	}
   217  	return p.perform(events.ProjectCreateStart, events.ProjectCreateDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
   218  		wrapper.Do(wrappers, events.ServiceCreateStart, events.ServiceCreate, func(service Service) error {
   219  			return service.Create(ctx, options)
   220  		})
   221  	}), nil)
   222  }
   223  
   224  // Stop stops the specified services (like docker stop).
   225  func (p *Project) Stop(ctx context.Context, timeout int, services ...string) error {
   226  	return p.perform(events.ProjectStopStart, events.ProjectStopDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
   227  		wrapper.Do(nil, events.ServiceStopStart, events.ServiceStop, func(service Service) error {
   228  			return service.Stop(ctx, timeout)
   229  		})
   230  	}), nil)
   231  }
   232  
   233  // Down stops the specified services and clean related containers (like docker stop + docker rm).
   234  func (p *Project) Down(ctx context.Context, opts options.Down, services ...string) error {
   235  	if !opts.RemoveImages.Valid() {
   236  		return fmt.Errorf("--rmi flag must be local, all or empty")
   237  	}
   238  	if err := p.Stop(ctx, 10, services...); err != nil {
   239  		return err
   240  	}
   241  	if opts.RemoveOrphans {
   242  		if err := p.removeOrphanContainers(); err != nil {
   243  			return err
   244  		}
   245  	}
   246  	if err := p.Delete(ctx, options.Delete{
   247  		RemoveVolume: opts.RemoveVolume,
   248  	}, services...); err != nil {
   249  		return err
   250  	}
   251  
   252  	return p.forEach([]string{}, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
   253  		wrapper.Do(wrappers, events.NoEvent, events.NoEvent, func(service Service) error {
   254  			return service.RemoveImage(ctx, opts.RemoveImages)
   255  		})
   256  	}), func(service Service) error {
   257  		return service.Create(ctx, options.Create{})
   258  	})
   259  }
   260  
   261  func (p *Project) removeOrphanContainers() error {
   262  	client := p.clientFactory.Create(nil)
   263  	filter := filters.NewArgs()
   264  	filter.Add("label", labels.PROJECT.EqString(p.Name))
   265  	containers, err := client.ContainerList(context.Background(), types.ContainerListOptions{
   266  		Filter: filter,
   267  	})
   268  	if err != nil {
   269  		return err
   270  	}
   271  	currentServices := map[string]struct{}{}
   272  	for _, serviceName := range p.ServiceConfigs.Keys() {
   273  		currentServices[serviceName] = struct{}{}
   274  	}
   275  	for _, container := range containers {
   276  		serviceLabel := container.Labels[labels.SERVICE.Str()]
   277  		if _, ok := currentServices[serviceLabel]; !ok {
   278  			if err := client.ContainerKill(context.Background(), container.ID, "SIGKILL"); err != nil {
   279  				return err
   280  			}
   281  			if err := client.ContainerRemove(context.Background(), container.ID, types.ContainerRemoveOptions{
   282  				Force: true,
   283  			}); err != nil {
   284  				return err
   285  			}
   286  		}
   287  	}
   288  	return nil
   289  }
   290  
   291  // Restart restarts the specified services (like docker restart).
   292  func (p *Project) Restart(ctx context.Context, timeout int, services ...string) error {
   293  	return p.perform(events.ProjectRestartStart, events.ProjectRestartDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
   294  		wrapper.Do(wrappers, events.ServiceRestartStart, events.ServiceRestart, func(service Service) error {
   295  			return service.Restart(ctx, timeout)
   296  		})
   297  	}), nil)
   298  }
   299  
   300  // Port returns the public port for a port binding of the specified service.
   301  func (p *Project) Port(ctx context.Context, index int, protocol, serviceName, privatePort string) (string, error) {
   302  	service, err := p.CreateService(serviceName)
   303  	if err != nil {
   304  		return "", err
   305  	}
   306  
   307  	containers, err := service.Containers(ctx)
   308  	if err != nil {
   309  		return "", err
   310  	}
   311  
   312  	if index < 1 || index > len(containers) {
   313  		return "", fmt.Errorf("Invalid index %d", index)
   314  	}
   315  
   316  	return containers[index-1].Port(ctx, fmt.Sprintf("%s/%s", privatePort, protocol))
   317  }
   318  
   319  // Ps list containers for the specified services.
   320  func (p *Project) Ps(ctx context.Context, onlyID bool, services ...string) (InfoSet, error) {
   321  	allInfo := InfoSet{}
   322  	for _, name := range p.ServiceConfigs.Keys() {
   323  		service, err := p.CreateService(name)
   324  		if err != nil {
   325  			return nil, err
   326  		}
   327  
   328  		info, err := service.Info(ctx, onlyID)
   329  		if err != nil {
   330  			return nil, err
   331  		}
   332  
   333  		allInfo = append(allInfo, info...)
   334  	}
   335  	return allInfo, nil
   336  }
   337  
   338  // Start starts the specified services (like docker start).
   339  func (p *Project) Start(ctx context.Context, services ...string) error {
   340  	return p.perform(events.ProjectStartStart, events.ProjectStartDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
   341  		wrapper.Do(wrappers, events.ServiceStartStart, events.ServiceStart, func(service Service) error {
   342  			return service.Start(ctx)
   343  		})
   344  	}), nil)
   345  }
   346  
   347  // Run executes a one off command (like `docker run image command`).
   348  func (p *Project) Run(ctx context.Context, serviceName string, commandParts []string) (int, error) {
   349  	if !p.ServiceConfigs.Has(serviceName) {
   350  		return 1, fmt.Errorf("%s is not defined in the template", serviceName)
   351  	}
   352  
   353  	var exitCode int
   354  	err := p.forEach([]string{}, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
   355  		wrapper.Do(wrappers, events.ServiceRunStart, events.ServiceRun, func(service Service) error {
   356  			if service.Name() == serviceName {
   357  				code, err := service.Run(ctx, commandParts)
   358  				exitCode = code
   359  				return err
   360  			}
   361  			return nil
   362  		})
   363  	}), func(service Service) error {
   364  		return service.Create(ctx, options.Create{})
   365  	})
   366  	return exitCode, err
   367  }
   368  
   369  // Up creates and starts the specified services (kinda like docker run).
   370  func (p *Project) Up(ctx context.Context, options options.Up, services ...string) error {
   371  	return p.perform(events.ProjectUpStart, events.ProjectUpDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
   372  		wrapper.Do(wrappers, events.ServiceUpStart, events.ServiceUp, func(service Service) error {
   373  			return service.Up(ctx, options)
   374  		})
   375  	}), func(service Service) error {
   376  		return service.Create(ctx, options.Create)
   377  	})
   378  }
   379  
   380  // Log aggregates and prints out the logs for the specified services.
   381  func (p *Project) Log(ctx context.Context, follow bool, services ...string) error {
   382  	return p.forEach(services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
   383  		wrapper.Do(nil, events.NoEvent, events.NoEvent, func(service Service) error {
   384  			return service.Log(ctx, follow)
   385  		})
   386  	}), nil)
   387  }
   388  
   389  // Scale scales the specified services.
   390  func (p *Project) Scale(ctx context.Context, timeout int, servicesScale map[string]int) error {
   391  	// This code is a bit verbose but I wanted to parse everything up front
   392  	order := make([]string, 0, 0)
   393  	services := make(map[string]Service)
   394  
   395  	for name := range servicesScale {
   396  		if !p.ServiceConfigs.Has(name) {
   397  			return fmt.Errorf("%s is not defined in the template", name)
   398  		}
   399  
   400  		service, err := p.CreateService(name)
   401  		if err != nil {
   402  			return fmt.Errorf("Failed to lookup service: %s: %v", service, err)
   403  		}
   404  
   405  		order = append(order, name)
   406  		services[name] = service
   407  	}
   408  
   409  	for _, name := range order {
   410  		scale := servicesScale[name]
   411  		log.Infof("Setting scale %s=%d...", name, scale)
   412  		err := services[name].Scale(ctx, scale, timeout)
   413  		if err != nil {
   414  			return fmt.Errorf("Failed to set the scale %s=%d: %v", name, scale, err)
   415  		}
   416  	}
   417  	return nil
   418  }
   419  
   420  // Pull pulls the specified services (like docker pull).
   421  func (p *Project) Pull(ctx context.Context, services ...string) error {
   422  	return p.forEach(services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
   423  		wrapper.Do(nil, events.ServicePullStart, events.ServicePull, func(service Service) error {
   424  			return service.Pull(ctx)
   425  		})
   426  	}), nil)
   427  }
   428  
   429  // listStoppedContainers lists the stopped containers for the specified services.
   430  func (p *Project) listStoppedContainers(ctx context.Context, services ...string) ([]string, error) {
   431  	stoppedContainers := []string{}
   432  	err := p.forEach(services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
   433  		wrapper.Do(nil, events.NoEvent, events.NoEvent, func(service Service) error {
   434  			containers, innerErr := service.Containers(ctx)
   435  			if innerErr != nil {
   436  				return innerErr
   437  			}
   438  
   439  			for _, container := range containers {
   440  				running, innerErr := container.IsRunning(ctx)
   441  				if innerErr != nil {
   442  					log.Error(innerErr)
   443  				}
   444  				if !running {
   445  					containerID, innerErr := container.ID()
   446  					if innerErr != nil {
   447  						log.Error(innerErr)
   448  					}
   449  					stoppedContainers = append(stoppedContainers, containerID)
   450  				}
   451  			}
   452  
   453  			return nil
   454  		})
   455  	}), nil)
   456  	if err != nil {
   457  		return nil, err
   458  	}
   459  	return stoppedContainers, nil
   460  }
   461  
   462  // Delete removes the specified services (like docker rm).
   463  func (p *Project) Delete(ctx context.Context, options options.Delete, services ...string) error {
   464  	stoppedContainers, err := p.listStoppedContainers(ctx, services...)
   465  	if err != nil {
   466  		return err
   467  	}
   468  	if len(stoppedContainers) == 0 {
   469  		p.Notify(events.ProjectDeleteDone, "", nil)
   470  		fmt.Println("No stopped containers")
   471  		return nil
   472  	}
   473  	if options.BeforeDeleteCallback != nil && !options.BeforeDeleteCallback(stoppedContainers) {
   474  		return nil
   475  	}
   476  	return p.perform(events.ProjectDeleteStart, events.ProjectDeleteDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
   477  		wrapper.Do(nil, events.ServiceDeleteStart, events.ServiceDelete, func(service Service) error {
   478  			return service.Delete(ctx, options)
   479  		})
   480  	}), nil)
   481  }
   482  
   483  // Kill kills the specified services (like docker kill).
   484  func (p *Project) Kill(ctx context.Context, signal string, services ...string) error {
   485  	return p.perform(events.ProjectKillStart, events.ProjectKillDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
   486  		wrapper.Do(nil, events.ServiceKillStart, events.ServiceKill, func(service Service) error {
   487  			return service.Kill(ctx, signal)
   488  		})
   489  	}), nil)
   490  }
   491  
   492  // Pause pauses the specified services containers (like docker pause).
   493  func (p *Project) Pause(ctx context.Context, services ...string) error {
   494  	return p.perform(events.ProjectPauseStart, events.ProjectPauseDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
   495  		wrapper.Do(nil, events.ServicePauseStart, events.ServicePause, func(service Service) error {
   496  			return service.Pause(ctx)
   497  		})
   498  	}), nil)
   499  }
   500  
   501  // Unpause pauses the specified services containers (like docker pause).
   502  func (p *Project) Unpause(ctx context.Context, services ...string) error {
   503  	return p.perform(events.ProjectUnpauseStart, events.ProjectUnpauseDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
   504  		wrapper.Do(nil, events.ServiceUnpauseStart, events.ServiceUnpause, func(service Service) error {
   505  			return service.Unpause(ctx)
   506  		})
   507  	}), nil)
   508  }
   509  
   510  func (p *Project) perform(start, done events.EventType, services []string, action wrapperAction, cycleAction serviceAction) error {
   511  	p.Notify(start, "", nil)
   512  
   513  	err := p.forEach(services, action, cycleAction)
   514  
   515  	p.Notify(done, "", nil)
   516  	return err
   517  }
   518  
   519  func isSelected(wrapper *serviceWrapper, selected map[string]bool) bool {
   520  	return len(selected) == 0 || selected[wrapper.name]
   521  }
   522  
   523  func (p *Project) forEach(services []string, action wrapperAction, cycleAction serviceAction) error {
   524  	selected := make(map[string]bool)
   525  	wrappers := make(map[string]*serviceWrapper)
   526  
   527  	for _, s := range services {
   528  		selected[s] = true
   529  	}
   530  
   531  	return p.traverse(true, selected, wrappers, action, cycleAction)
   532  }
   533  
   534  func (p *Project) startService(wrappers map[string]*serviceWrapper, history []string, selected, launched map[string]bool, wrapper *serviceWrapper, action wrapperAction, cycleAction serviceAction) error {
   535  	if launched[wrapper.name] {
   536  		return nil
   537  	}
   538  
   539  	launched[wrapper.name] = true
   540  	history = append(history, wrapper.name)
   541  
   542  	for _, dep := range wrapper.service.DependentServices() {
   543  		target := wrappers[dep.Target]
   544  		if target == nil {
   545  			log.Errorf("Failed to find %s", dep.Target)
   546  			continue
   547  		}
   548  
   549  		if utils.Contains(history, dep.Target) {
   550  			cycle := strings.Join(append(history, dep.Target), "->")
   551  			if dep.Optional {
   552  				log.Debugf("Ignoring cycle for %s", cycle)
   553  				wrapper.IgnoreDep(dep.Target)
   554  				if cycleAction != nil {
   555  					var err error
   556  					log.Debugf("Running cycle action for %s", cycle)
   557  					err = cycleAction(target.service)
   558  					if err != nil {
   559  						return err
   560  					}
   561  				}
   562  			} else {
   563  				return fmt.Errorf("Cycle detected in path %s", cycle)
   564  			}
   565  
   566  			continue
   567  		}
   568  
   569  		err := p.startService(wrappers, history, selected, launched, target, action, cycleAction)
   570  		if err != nil {
   571  			return err
   572  		}
   573  	}
   574  
   575  	if isSelected(wrapper, selected) {
   576  		log.Debugf("Launching action for %s", wrapper.name)
   577  		go action(wrapper, wrappers)
   578  	} else {
   579  		wrapper.Ignore()
   580  	}
   581  
   582  	return nil
   583  }
   584  
   585  func (p *Project) traverse(start bool, selected map[string]bool, wrappers map[string]*serviceWrapper, action wrapperAction, cycleAction serviceAction) error {
   586  	restart := false
   587  	wrapperList := []string{}
   588  
   589  	if start {
   590  		for _, name := range p.ServiceConfigs.Keys() {
   591  			wrapperList = append(wrapperList, name)
   592  		}
   593  	} else {
   594  		for _, wrapper := range wrappers {
   595  			if err := wrapper.Reset(); err != nil {
   596  				return err
   597  			}
   598  		}
   599  		wrapperList = p.reload
   600  	}
   601  
   602  	p.loadWrappers(wrappers, wrapperList)
   603  	p.reload = []string{}
   604  
   605  	// check service name
   606  	for s := range selected {
   607  		if wrappers[s] == nil {
   608  			return errors.New("No such service: " + s)
   609  		}
   610  	}
   611  
   612  	launched := map[string]bool{}
   613  
   614  	for _, wrapper := range wrappers {
   615  		if err := p.startService(wrappers, []string{}, selected, launched, wrapper, action, cycleAction); err != nil {
   616  			return err
   617  		}
   618  	}
   619  
   620  	var firstError error
   621  
   622  	for _, wrapper := range wrappers {
   623  		if !isSelected(wrapper, selected) {
   624  			continue
   625  		}
   626  		if err := wrapper.Wait(); err == ErrRestart {
   627  			restart = true
   628  		} else if err != nil {
   629  			log.Errorf("Failed to start: %s : %v", wrapper.name, err)
   630  			if firstError == nil {
   631  				firstError = err
   632  			}
   633  		}
   634  	}
   635  
   636  	if restart {
   637  		if p.ReloadCallback != nil {
   638  			if err := p.ReloadCallback(); err != nil {
   639  				log.Errorf("Failed calling callback: %v", err)
   640  			}
   641  		}
   642  		return p.traverse(false, selected, wrappers, action, cycleAction)
   643  	}
   644  	return firstError
   645  }
   646  
   647  // AddListener adds the specified listener to the project.
   648  // This implements implicitly events.Emitter.
   649  func (p *Project) AddListener(c chan<- events.Event) {
   650  	if !p.hasListeners {
   651  		for _, l := range p.listeners {
   652  			close(l)
   653  		}
   654  		p.hasListeners = true
   655  		p.listeners = []chan<- events.Event{c}
   656  	} else {
   657  		p.listeners = append(p.listeners, c)
   658  	}
   659  }
   660  
   661  // Notify notifies all project listener with the specified eventType, service name and datas.
   662  // This implements implicitly events.Notifier interface.
   663  func (p *Project) Notify(eventType events.EventType, serviceName string, data map[string]string) {
   664  	if eventType == events.NoEvent {
   665  		return
   666  	}
   667  
   668  	event := events.Event{
   669  		EventType:   eventType,
   670  		ServiceName: serviceName,
   671  		Data:        data,
   672  	}
   673  
   674  	for _, l := range p.listeners {
   675  		l <- event
   676  	}
   677  }