1
0
mirror of https://github.com/rancher/os.git synced 2025-07-06 03:26:12 +00:00
os/vendor/github.com/docker/libcompose/project/project.go
2015-12-04 20:19:31 +05:00

411 lines
11 KiB
Go

package project
import (
"errors"
"fmt"
"strings"
log "github.com/Sirupsen/logrus"
"github.com/docker/libcompose/logger"
"github.com/docker/libcompose/utils"
)
// ServiceState holds the state of a service.
type ServiceState string
// State definitions
var (
StateExecuted = ServiceState("executed")
StateUnknown = ServiceState("unknown")
)
// Error definitions
var (
ErrRestart = errors.New("Restart execution")
ErrUnsupported = errors.New("UnsupportedOperation")
)
// Event holds project-wide event informations.
type Event struct {
EventType EventType
ServiceName string
Data map[string]string
}
type wrapperAction func(*serviceWrapper, map[string]*serviceWrapper)
type serviceAction func(service Service) error
// NewProject create a new project with the specified context.
func NewProject(context *Context) *Project {
p := &Project{
context: context,
Configs: make(map[string]*ServiceConfig),
}
if context.LoggerFactory == nil {
context.LoggerFactory = &logger.NullLogger{}
}
context.Project = p
p.listeners = []chan<- Event{NewDefaultListener(p)}
return p
}
// Parse populates project information based on its context. It sets up the name,
// the composefile and the composebytes (the composefile content).
func (p *Project) Parse() error {
err := p.context.open()
if err != nil {
return err
}
p.Name = p.context.ProjectName
if p.context.ComposeFile == "-" {
p.File = "."
} else {
p.File = p.context.ComposeFile
}
if p.context.ComposeBytes != nil {
return p.Load(p.context.ComposeBytes)
}
return nil
}
// CreateService creates a service with the specified name based. It there
// is no config in the project for this service, it will return an error.
func (p *Project) CreateService(name string) (Service, error) {
existing, ok := p.Configs[name]
if !ok {
return nil, fmt.Errorf("Failed to find service: %s", name)
}
// Copy because we are about to modify the environment
config := *existing
if p.context.EnvironmentLookup != nil {
parsedEnv := make([]string, 0, len(config.Environment.Slice()))
for _, env := range config.Environment.Slice() {
parts := strings.SplitN(env, "=", 2)
if len(parts) > 1 && parts[1] != "" {
parsedEnv = append(parsedEnv, env)
continue
} else {
env = parts[0]
}
for _, value := range p.context.EnvironmentLookup.Lookup(env, name, &config) {
parsedEnv = append(parsedEnv, value)
}
}
config.Environment = NewMaporEqualSlice(parsedEnv)
}
return p.context.ServiceFactory.Create(p, name, &config)
}
// AddConfig adds the specified service config for the specified name.
func (p *Project) AddConfig(name string, config *ServiceConfig) error {
p.Notify(EventServiceAdd, name, nil)
p.Configs[name] = config
p.reload = append(p.reload, name)
return nil
}
// Load loads the specified byte array (the composefile content) and adds the
// service configuration to the project.
func (p *Project) Load(bytes []byte) error {
configs := make(map[string]*ServiceConfig)
configs, err := mergeProject(p, bytes)
if err != nil {
log.Errorf("Could not parse config for project %s : %v", p.Name, err)
return err
}
for name, config := range configs {
err := p.AddConfig(name, config)
if err != nil {
return err
}
}
return nil
}
func (p *Project) loadWrappers(wrappers map[string]*serviceWrapper, servicesToConstruct []string) error {
for _, name := range servicesToConstruct {
wrapper, err := newServiceWrapper(name, p)
if err != nil {
return err
}
wrappers[name] = wrapper
}
return nil
}
// Build builds the specified services (like docker build).
func (p *Project) Build(services ...string) error {
return p.perform(EventProjectBuildStart, EventProjectBuildDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(wrappers, EventServiceBuildStart, EventServiceBuild, func(service Service) error {
return service.Build()
})
}), nil)
}
// Create creates the specified services (like docker create).
func (p *Project) Create(services ...string) error {
return p.perform(EventProjectCreateStart, EventProjectCreateDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(wrappers, EventServiceCreateStart, EventServiceCreate, func(service Service) error {
return service.Create()
})
}), nil)
}
// Down stops the specified services (like docker stop).
func (p *Project) Down(services ...string) error {
return p.perform(EventProjectDownStart, EventProjectDownDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(nil, EventServiceDownStart, EventServiceDown, func(service Service) error {
return service.Down()
})
}), nil)
}
// Restart restarts the specified services (like docker restart).
func (p *Project) Restart(services ...string) error {
return p.perform(EventProjectRestartStart, EventProjectRestartDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(wrappers, EventServiceRestartStart, EventServiceRestart, func(service Service) error {
return service.Restart()
})
}), nil)
}
// Start starts the specified services (like docker start).
func (p *Project) Start(services ...string) error {
return p.perform(EventProjectStartStart, EventProjectStartDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(wrappers, EventServiceStartStart, EventServiceStart, func(service Service) error {
return service.Start()
})
}), nil)
}
// Up create and start the specified services (kinda like docker run).
func (p *Project) Up(services ...string) error {
return p.perform(EventProjectUpStart, EventProjectUpDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(wrappers, EventServiceUpStart, EventServiceUp, func(service Service) error {
return service.Up()
})
}), func(service Service) error {
return service.Create()
})
}
// Log aggregate and prints out the logs for the specified services.
func (p *Project) Log(services ...string) error {
return p.forEach(services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(nil, NoEvent, NoEvent, func(service Service) error {
return service.Log()
})
}), nil)
}
// Pull pulls the specified services (like docker pull).
func (p *Project) Pull(services ...string) error {
return p.forEach(services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(nil, EventServicePullStart, EventServicePull, func(service Service) error {
return service.Pull()
})
}), nil)
}
// Delete removes the specified services (like docker rm).
func (p *Project) Delete(services ...string) error {
return p.perform(EventProjectDeleteStart, EventProjectDeleteDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(nil, EventServiceDeleteStart, EventServiceDelete, func(service Service) error {
return service.Delete()
})
}), nil)
}
// Kill kills the specified services (like docker kill).
func (p *Project) Kill(services ...string) error {
return p.perform(EventProjectKillStart, EventProjectKillDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(nil, EventServiceKillStart, EventServiceKill, func(service Service) error {
return service.Kill()
})
}), nil)
}
func (p *Project) perform(start, done EventType, services []string, action wrapperAction, cycleAction serviceAction) error {
p.Notify(start, "", nil)
err := p.forEach(services, action, cycleAction)
p.Notify(done, "", nil)
return err
}
func isSelected(wrapper *serviceWrapper, selected map[string]bool) bool {
return len(selected) == 0 || selected[wrapper.name]
}
func (p *Project) forEach(services []string, action wrapperAction, cycleAction serviceAction) error {
selected := make(map[string]bool)
wrappers := make(map[string]*serviceWrapper)
for _, s := range services {
selected[s] = true
}
return p.traverse(true, selected, wrappers, action, cycleAction)
}
func (p *Project) startService(wrappers map[string]*serviceWrapper, history []string, selected, launched map[string]bool, wrapper *serviceWrapper, action wrapperAction, cycleAction serviceAction) error {
if launched[wrapper.name] {
return nil
}
launched[wrapper.name] = true
history = append(history, wrapper.name)
for _, dep := range wrapper.service.DependentServices() {
target := wrappers[dep.Target]
if target == nil {
log.Errorf("Failed to find %s", dep.Target)
continue
}
if utils.Contains(history, dep.Target) {
cycle := strings.Join(append(history, dep.Target), "->")
if dep.Optional {
log.Debugf("Ignoring cycle for %s", cycle)
wrapper.IgnoreDep(dep.Target)
if cycleAction != nil {
var err error
log.Debugf("Running cycle action for %s", cycle)
err = cycleAction(target.service)
if err != nil {
return err
}
}
} else {
return fmt.Errorf("Cycle detected in path %s", cycle)
}
continue
}
err := p.startService(wrappers, history, selected, launched, target, action, cycleAction)
if err != nil {
return err
}
}
if isSelected(wrapper, selected) {
log.Debugf("Launching action for %s", wrapper.name)
go action(wrapper, wrappers)
} else {
wrapper.Ignore()
}
return nil
}
func (p *Project) traverse(start bool, selected map[string]bool, wrappers map[string]*serviceWrapper, action wrapperAction, cycleAction serviceAction) error {
restart := false
wrapperList := []string{}
if start {
for name := range p.Configs {
wrapperList = append(wrapperList, name)
}
} else {
for _, wrapper := range wrappers {
if err := wrapper.Reset(); err != nil {
return err
}
}
wrapperList = p.reload
}
p.loadWrappers(wrappers, wrapperList)
p.reload = []string{}
// check service name
for s := range selected {
if wrappers[s] == nil {
return errors.New("No such service: " + s)
}
}
launched := map[string]bool{}
for _, wrapper := range wrappers {
p.startService(wrappers, []string{}, selected, launched, wrapper, action, cycleAction)
}
var firstError error
for _, wrapper := range wrappers {
if !isSelected(wrapper, selected) {
continue
}
if err := wrapper.Wait(); err == ErrRestart {
restart = true
} else if err != nil {
log.Errorf("Failed to start: %s : %v", wrapper.name, err)
if firstError == nil {
firstError = err
}
}
}
if restart {
if p.ReloadCallback != nil {
if err := p.ReloadCallback(); err != nil {
log.Errorf("Failed calling callback: %v", err)
}
}
return p.traverse(false, selected, wrappers, action, cycleAction)
}
return firstError
}
// AddListener adds the specified listener to the project.
func (p *Project) AddListener(c chan<- Event) {
if !p.hasListeners {
for _, l := range p.listeners {
close(l)
}
p.hasListeners = true
p.listeners = []chan<- Event{c}
} else {
p.listeners = append(p.listeners, c)
}
}
// Notify notifies all project listener with the specified eventType, service name and datas.
func (p *Project) Notify(eventType EventType, serviceName string, data map[string]string) {
if eventType == NoEvent {
return
}
event := Event{
EventType: eventType,
ServiceName: serviceName,
Data: data,
}
for _, l := range p.listeners {
l <- event
}
}