Initial commit

This commit is contained in:
Ria Bhatia
2017-12-04 13:32:57 -06:00
committed by Erik St. Martin
commit 0075e5b0f3
9056 changed files with 2523100 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
package project
import "github.com/docker/engine-api/client"
// ClientFactory is a factory to create docker clients.
type ClientFactory interface {
// Create constructs a Docker client for the given service. The passed in
// config may be nil in which case a generic client for the project should
// be returned.
Create(service Service) client.APIClient
}

View File

@@ -0,0 +1,9 @@
package project
// Container defines what a libcompose container provides.
type Container interface {
ID() (string, error)
Name() string
Port(port string) (string, error)
IsRunning() (bool, error)
}

141
vendor/github.com/hyperhq/libcompose/project/context.go generated vendored Normal file
View File

@@ -0,0 +1,141 @@
package project
import (
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"regexp"
"strings"
"github.com/Sirupsen/logrus"
"github.com/hyperhq/libcompose/config"
"github.com/hyperhq/libcompose/logger"
)
var projectRegexp = regexp.MustCompile("[^a-zA-Z0-9_.-]")
// Context holds context meta information about a libcompose project, like
// the project name, the compose file, etc.
type Context struct {
ComposeFiles []string
ComposeBytes [][]byte
ProjectName string
isOpen bool
ServiceFactory ServiceFactory
EnvironmentLookup config.EnvironmentLookup
ResourceLookup config.ResourceLookup
LoggerFactory logger.Factory
IgnoreMissingConfig bool
Project *Project
Autoremove bool
}
func (c *Context) readComposeFiles() error {
if c.ComposeBytes != nil {
return nil
}
logrus.Debugf("Opening compose files: %s", strings.Join(c.ComposeFiles, ","))
// Handle STDIN (`-f -`)
if len(c.ComposeFiles) == 1 && c.ComposeFiles[0] == "-" {
composeBytes, err := ioutil.ReadAll(os.Stdin)
if err != nil {
logrus.Errorf("Failed to read compose file from stdin: %v", err)
return err
}
c.ComposeBytes = [][]byte{composeBytes}
return nil
}
for _, composeFile := range c.ComposeFiles {
composeBytes, err := ioutil.ReadFile(composeFile)
if err != nil && !os.IsNotExist(err) {
logrus.Errorf("Failed to open the compose file: %s", composeFile)
return err
}
if err != nil && !c.IgnoreMissingConfig {
logrus.Errorf("Failed to find the compose file: %s", composeFile)
return err
}
c.ComposeBytes = append(c.ComposeBytes, composeBytes)
}
return nil
}
func (c *Context) determineProject() error {
name, err := c.lookupProjectName()
if err != nil {
return err
}
c.ProjectName = normalizeName(name)
if c.ProjectName == "" {
return fmt.Errorf("Falied to determine project name")
}
return nil
}
func (c *Context) lookupProjectName() (string, error) {
if c.ProjectName != "" {
return c.ProjectName, nil
}
if envProject := os.Getenv("COMPOSE_PROJECT_NAME"); envProject != "" {
return envProject, nil
}
file := "."
if len(c.ComposeFiles) > 0 {
file = c.ComposeFiles[0]
}
f, err := filepath.Abs(file)
if err != nil {
logrus.Errorf("Failed to get absolute directory for: %s", file)
return "", err
}
f = toUnixPath(f)
parent := path.Base(path.Dir(f))
if parent != "" && parent != "." {
return parent, nil
} else if wd, err := os.Getwd(); err != nil {
return "", err
} else {
return path.Base(toUnixPath(wd)), nil
}
}
func normalizeName(name string) string {
r := regexp.MustCompile("[^a-z0-9]+")
return r.ReplaceAllString(strings.ToLower(name), "")
}
func toUnixPath(p string) string {
return strings.Replace(p, "\\", "/", -1)
}
func (c *Context) open() error {
if c.isOpen {
return nil
}
if err := c.readComposeFiles(); err != nil {
return err
}
if err := c.determineProject(); err != nil {
return err
}
c.isOpen = true
return nil
}

94
vendor/github.com/hyperhq/libcompose/project/empty.go generated vendored Normal file
View File

@@ -0,0 +1,94 @@
package project
import (
"github.com/hyperhq/libcompose/project/options"
)
// EmptyService is a struct that implements Service but does nothing.
type EmptyService struct {
}
// Create implements Service.Create but does nothing.
func (e *EmptyService) Create(options options.Create) error {
return nil
}
// Build implements Service.Build but does nothing.
func (e *EmptyService) Build(buildOptions options.Build) error {
return nil
}
// Up implements Service.Up but does nothing.
func (e *EmptyService) Up(options options.Up) error {
return nil
}
// Start implements Service.Start but does nothing.
func (e *EmptyService) Start() error {
return nil
}
// Stop implements Service.Stop() but does nothing.
func (e *EmptyService) Stop(timeout int) error {
return nil
}
// Delete implements Service.Delete but does nothing.
func (e *EmptyService) Delete(options options.Delete) error {
return nil
}
// Restart implements Service.Restart but does nothing.
func (e *EmptyService) Restart(timeout int) error {
return nil
}
// Log implements Service.Log but does nothing.
func (e *EmptyService) Log(follow bool) error {
return nil
}
// Pull implements Service.Pull but does nothing.
func (e *EmptyService) Pull() error {
return nil
}
// Kill implements Service.Kill but does nothing.
func (e *EmptyService) Kill(signal string) error {
return nil
}
// Containers implements Service.Containers but does nothing.
func (e *EmptyService) Containers() ([]Container, error) {
return []Container{}, nil
}
// Scale implements Service.Scale but does nothing.
func (e *EmptyService) Scale(count int, timeout int) error {
return nil
}
// Info implements Service.Info but does nothing.
func (e *EmptyService) Info(qFlag bool) (InfoSet, error) {
return InfoSet{}, nil
}
// Pause implements Service.Pause but does nothing.
func (e *EmptyService) Pause() error {
return nil
}
// Unpause implements Service.Pause but does nothing.
func (e *EmptyService) Unpause() error {
return nil
}
// Run implements Service.Run but does nothing.
func (e *EmptyService) Run(commandParts []string) (int, error) {
return 0, nil
}
// RemoveImage implements Service.RemoveImage but does nothing.
func (e *EmptyService) RemoveImage(imageType options.ImageType) error {
return nil
}

View File

@@ -0,0 +1,197 @@
// Package events holds event structures, methods and functions.
package events
import (
"fmt"
)
// Notifier defines the methods an event notifier should have.
type Notifier interface {
Notify(eventType EventType, serviceName string, data map[string]string)
}
// Emitter defines the methods an event emitter should have.
type Emitter interface {
AddListener(c chan<- Event)
}
// Event holds project-wide event informations.
type Event struct {
EventType EventType
ServiceName string
Data map[string]string
}
// EventType defines a type of libcompose event.
type EventType int
// Definitions of libcompose events
const (
NoEvent = EventType(iota)
ContainerCreated = EventType(iota)
ContainerStarted = EventType(iota)
ServiceAdd = EventType(iota)
ServiceUpStart = EventType(iota)
ServiceUpIgnored = EventType(iota)
ServiceUp = EventType(iota)
ServiceCreateStart = EventType(iota)
ServiceCreate = EventType(iota)
ServiceDeleteStart = EventType(iota)
ServiceDelete = EventType(iota)
ServiceDownStart = EventType(iota)
ServiceDown = EventType(iota)
ServiceRestartStart = EventType(iota)
ServiceRestart = EventType(iota)
ServicePullStart = EventType(iota)
ServicePull = EventType(iota)
ServiceKillStart = EventType(iota)
ServiceKill = EventType(iota)
ServiceStartStart = EventType(iota)
ServiceStart = EventType(iota)
ServiceBuildStart = EventType(iota)
ServiceBuild = EventType(iota)
ServicePauseStart = EventType(iota)
ServicePause = EventType(iota)
ServiceUnpauseStart = EventType(iota)
ServiceUnpause = EventType(iota)
ServiceStopStart = EventType(iota)
ServiceStop = EventType(iota)
ServiceRunStart = EventType(iota)
ServiceRun = EventType(iota)
VolumeAdd = EventType(iota)
NetworkAdd = EventType(iota)
ProjectDownStart = EventType(iota)
ProjectDownDone = EventType(iota)
ProjectCreateStart = EventType(iota)
ProjectCreateDone = EventType(iota)
ProjectUpStart = EventType(iota)
ProjectUpDone = EventType(iota)
ProjectDeleteStart = EventType(iota)
ProjectDeleteDone = EventType(iota)
ProjectRestartStart = EventType(iota)
ProjectRestartDone = EventType(iota)
ProjectReload = EventType(iota)
ProjectReloadTrigger = EventType(iota)
ProjectKillStart = EventType(iota)
ProjectKillDone = EventType(iota)
ProjectStartStart = EventType(iota)
ProjectStartDone = EventType(iota)
ProjectBuildStart = EventType(iota)
ProjectBuildDone = EventType(iota)
ProjectPauseStart = EventType(iota)
ProjectPauseDone = EventType(iota)
ProjectUnpauseStart = EventType(iota)
ProjectUnpauseDone = EventType(iota)
ProjectStopStart = EventType(iota)
ProjectStopDone = EventType(iota)
)
func (e EventType) String() string {
var m string
switch e {
case ContainerCreated:
m = "Created container"
case ContainerStarted:
m = "Started container"
case ServiceAdd:
m = "Adding"
case ServiceUpStart:
m = "Starting"
case ServiceUpIgnored:
m = "Ignoring"
case ServiceUp:
m = "Started"
case ServiceCreateStart:
m = "Creating"
case ServiceCreate:
m = "Created"
case ServiceDeleteStart:
m = "Deleting"
case ServiceDelete:
m = "Deleted"
case ServiceStopStart:
m = "Stopping"
case ServiceStop:
m = "Stopped"
case ServiceDownStart:
m = "Stopping"
case ServiceDown:
m = "Stopped"
case ServiceRestartStart:
m = "Restarting"
case ServiceRestart:
m = "Restarted"
case ServicePullStart:
m = "Pulling"
case ServicePull:
m = "Pulled"
case ServiceKillStart:
m = "Killing"
case ServiceKill:
m = "Killed"
case ServiceStartStart:
m = "Starting"
case ServiceStart:
m = "Started"
case ServiceBuildStart:
m = "Building"
case ServiceBuild:
m = "Built"
case ServiceRunStart:
m = "Executing"
case ServiceRun:
m = "Executed"
case ProjectDownStart:
m = "Stopping project"
case ProjectDownDone:
m = "Project stopped"
case ProjectStopStart:
m = "Stopping project"
case ProjectStopDone:
m = "Project stopped"
case ProjectCreateStart:
m = "Creating project"
case ProjectCreateDone:
m = "Project created"
case ProjectUpStart:
m = "Starting project"
case ProjectUpDone:
m = "Project started"
case ProjectDeleteStart:
m = "Deleting project"
case ProjectDeleteDone:
m = "Project deleted"
case ProjectRestartStart:
m = "Restarting project"
case ProjectRestartDone:
m = "Project restarted"
case ProjectReload:
m = "Reloading project"
case ProjectReloadTrigger:
m = "Triggering project reload"
case ProjectKillStart:
m = "Killing project"
case ProjectKillDone:
m = "Project killed"
case ProjectStartStart:
m = "Starting project"
case ProjectStartDone:
m = "Project started"
case ProjectBuildStart:
m = "Building project"
case ProjectBuildDone:
m = "Project built"
}
if m == "" {
m = fmt.Sprintf("EventType: %d", int(e))
}
return m
}

View File

@@ -0,0 +1,21 @@
package events
import (
"fmt"
"testing"
)
func TestEventEquality(t *testing.T) {
if fmt.Sprintf("%s", ServiceStart) != "Started" ||
fmt.Sprintf("%v", ServiceStart) != "Started" {
t.Fatalf("EventServiceStart String() doesn't work: %s %v", ServiceStart, ServiceStart)
}
if fmt.Sprintf("%s", ServiceStart) != fmt.Sprintf("%s", ServiceUp) {
t.Fatal("Event messages do not match")
}
if ServiceStart == ServiceUp {
t.Fatal("Events match")
}
}

53
vendor/github.com/hyperhq/libcompose/project/info.go generated vendored Normal file
View File

@@ -0,0 +1,53 @@
package project
import (
"bytes"
"io"
"text/tabwriter"
)
// InfoPart holds key/value strings.
type InfoPart struct {
Key, Value string
}
// InfoSet holds a list of Info.
type InfoSet []Info
// Info holds a list of InfoPart.
type Info []InfoPart
func (infos InfoSet) String(titleFlag bool) string {
//no error checking, none of this should fail
buffer := bytes.NewBuffer(make([]byte, 0, 1024))
tabwriter := tabwriter.NewWriter(buffer, 4, 4, 2, ' ', 0)
first := true
for _, info := range infos {
if first && titleFlag {
writeLine(tabwriter, true, info)
}
first = false
writeLine(tabwriter, false, info)
}
tabwriter.Flush()
return buffer.String()
}
func writeLine(writer io.Writer, key bool, info Info) {
first := true
for _, part := range info {
if !first {
writer.Write([]byte{'\t'})
}
first = false
if key {
writer.Write([]byte(part.Key))
} else {
writer.Write([]byte(part.Value))
}
}
writer.Write([]byte{'\n'})
}

View File

@@ -0,0 +1,36 @@
package project
import (
"github.com/hyperhq/libcompose/config"
"github.com/hyperhq/libcompose/project/events"
"github.com/hyperhq/libcompose/project/options"
"golang.org/x/net/context"
)
// APIProject is an interface defining the methods a libcompose project should implement.
type APIProject interface {
events.Notifier
events.Emitter
Build(options options.Build, sevice ...string) error
Create(options options.Create, services ...string) error
Delete(options options.Delete, services ...string) error
Down(options options.Down, services ...string) error
Kill(signal string, services ...string) error
Log(follow bool, services ...string) error
Pause(services ...string) error
Ps(onlyID bool, services ...string) (InfoSet, error)
// FIXME(vdemeester) we could use nat.Port instead ?
Port(index int, protocol, serviceName, privatePort string) (string, error)
Pull(services ...string) error
Restart(timeout int, services ...string) error
Run(ctx context.Context, serviceName string, commandParts []string) (int, error)
Scale(timeout int, servicesScale map[string]int) error
Start(services ...string) error
Stop(timeout int, services ...string) error
Unpause(services ...string) error
Up(options options.Up, services ...string) error
Parse() error
GetConfig() (*config.ServiceConfigs, map[string]*config.VolumeConfig, map[string]*config.NetworkConfig)
}

View File

@@ -0,0 +1,77 @@
package project
import (
"bytes"
"github.com/Sirupsen/logrus"
"github.com/hyperhq/libcompose/project/events"
)
var (
infoEvents = map[events.EventType]bool{
events.ProjectDeleteDone: true,
events.ProjectDeleteStart: true,
events.ProjectDownDone: true,
events.ProjectDownStart: true,
events.ProjectRestartDone: true,
events.ProjectRestartStart: true,
events.ProjectUpDone: true,
events.ProjectUpStart: true,
events.ServiceDeleteStart: true,
events.ServiceDelete: true,
events.ServiceDownStart: true,
events.ServiceDown: true,
events.ServiceRestartStart: true,
events.ServiceRestart: true,
events.ServiceUpStart: true,
events.ServiceUp: true,
}
)
type defaultListener struct {
project *Project
listenChan chan events.Event
upCount int
}
// NewDefaultListener create a default listener for the specified project.
func NewDefaultListener(p *Project) chan<- events.Event {
l := defaultListener{
listenChan: make(chan events.Event),
project: p,
}
go l.start()
return l.listenChan
}
func (d *defaultListener) start() {
for event := range d.listenChan {
buffer := bytes.NewBuffer(nil)
if event.Data != nil {
for k, v := range event.Data {
if buffer.Len() > 0 {
buffer.WriteString(", ")
}
buffer.WriteString(k)
buffer.WriteString("=")
buffer.WriteString(v)
}
}
if event.EventType == events.ServiceUp {
d.upCount++
}
logf := logrus.Debugf
if infoEvents[event.EventType] {
logf = logrus.Infof
}
if event.ServiceName == "" {
logf("Project [%s]: %s %s", d.project.Name, event.EventType, buffer.Bytes())
} else {
logf("[%d/%d] [%s]: %s %s", d.upCount, d.project.ServiceConfigs.Len(), event.ServiceName, event.EventType, buffer.Bytes())
}
}
}

View File

@@ -0,0 +1,47 @@
package options
// Build holds options of compose build.
type Build struct {
NoCache bool
ForceRemove bool
Pull bool
}
// Delete holds options of compose rm.
type Delete struct {
RemoveVolume bool
BeforeDeleteCallback func([]string) bool
}
// Down holds options of compose down.
type Down struct {
RemoveVolume bool
RemoveImages ImageType
RemoveOrphans bool
}
// Create holds options of compose create.
type Create struct {
NoRecreate bool
ForceRecreate bool
NoBuild bool
// ForceBuild bool
}
// Up holds options of compose up.
type Up struct {
Create
}
// ImageType defines the type of image (local, all)
type ImageType string
// Valid indicates whether the image type is valid.
func (i ImageType) Valid() bool {
switch string(i) {
case "", "local", "all":
return true
default:
return false
}
}

View File

@@ -0,0 +1,39 @@
package options
import (
"testing"
)
func TestImageType(t *testing.T) {
cases := []struct {
imageType string
valid bool
}{
{
imageType: "",
valid: true,
},
{
imageType: " ",
valid: false,
},
{
imageType: "hello",
valid: false,
},
{
imageType: "local",
valid: true,
},
{
imageType: "all",
valid: true,
},
}
for _, c := range cases {
i := ImageType(c.imageType)
if i.Valid() != c.valid {
t.Errorf("expected %v, got %v, for %v", c.valid, i.Valid(), c)
}
}
}

679
vendor/github.com/hyperhq/libcompose/project/project.go generated vendored Normal file
View File

@@ -0,0 +1,679 @@
package project
import (
"errors"
"fmt"
"strings"
"golang.org/x/net/context"
log "github.com/Sirupsen/logrus"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/filters"
"github.com/hyperhq/libcompose/config"
"github.com/hyperhq/libcompose/labels"
"github.com/hyperhq/libcompose/logger"
"github.com/hyperhq/libcompose/project/events"
"github.com/hyperhq/libcompose/project/options"
"github.com/hyperhq/libcompose/utils"
)
type wrapperAction func(*serviceWrapper, map[string]*serviceWrapper)
type serviceAction func(service Service) error
// Project holds libcompose project information.
type Project struct {
Name string
ServiceConfigs *config.ServiceConfigs
VolumeConfigs map[string]*config.VolumeConfig
NetworkConfigs map[string]*config.NetworkConfig
Files []string
ReloadCallback func() error
context *Context
clientFactory ClientFactory
reload []string
upCount int
listeners []chan<- events.Event
hasListeners bool
}
// NewProject creates a new project with the specified context.
func NewProject(clientFactory ClientFactory, context *Context) *Project {
p := &Project{
context: context,
clientFactory: clientFactory,
ServiceConfigs: config.NewServiceConfigs(),
VolumeConfigs: make(map[string]*config.VolumeConfig),
NetworkConfigs: make(map[string]*config.NetworkConfig),
}
if context.LoggerFactory == nil {
context.LoggerFactory = &logger.NullLogger{}
}
context.Project = p
p.listeners = []chan<- events.Event{NewDefaultListener(p)}
return p
}
func (p *Project) GetConfig() (*config.ServiceConfigs, map[string]*config.VolumeConfig, map[string]*config.NetworkConfig) {
return p.ServiceConfigs, p.VolumeConfigs, p.NetworkConfigs
}
// 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
p.Files = p.context.ComposeFiles
if len(p.Files) == 1 && p.Files[0] == "-" {
p.Files = []string{"."}
}
if p.context.ComposeBytes != nil {
for i, composeBytes := range p.context.ComposeBytes {
file := ""
if i < len(p.context.ComposeFiles) {
file = p.Files[i]
}
if err := p.load(file, composeBytes); err != nil {
return err
}
}
}
return nil
}
// CreateService creates a service with the specified name based. If 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.ServiceConfigs.Get(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))
for _, env := range config.Environment {
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 = 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 *config.ServiceConfig) error {
p.Notify(events.ServiceAdd, name, nil)
p.ServiceConfigs.Add(name, config)
p.reload = append(p.reload, name)
return nil
}
// AddVolumeConfig adds the specified volume config for the specified name.
func (p *Project) AddVolumeConfig(name string, config *config.VolumeConfig) error {
p.Notify(events.VolumeAdd, name, nil)
p.VolumeConfigs[name] = config
return nil
}
// AddNetworkConfig adds the specified network config for the specified name.
func (p *Project) AddNetworkConfig(name string, config *config.NetworkConfig) error {
p.Notify(events.NetworkAdd, name, nil)
p.NetworkConfigs[name] = config
return nil
}
// Load loads the specified byte array (the composefile content) and adds the
// service configuration to the project.
// FIXME is it needed ?
func (p *Project) Load(bytes []byte) error {
return p.load("", bytes)
}
func (p *Project) load(file string, bytes []byte) error {
serviceConfigs, volumeConfigs, networkConfigs, err := config.Merge(p.ServiceConfigs, p.context.EnvironmentLookup, p.context.ResourceLookup, file, bytes)
if err != nil {
log.Errorf("Could not parse config for project %s : %v", p.Name, err)
return err
}
for name, config := range serviceConfigs {
err := p.AddConfig(name, config)
if err != nil {
return err
}
}
for name, config := range volumeConfigs {
err := p.AddVolumeConfig(name, config)
if err != nil {
return err
}
}
for name, config := range networkConfigs {
err := p.AddNetworkConfig(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(buildOptions options.Build, services ...string) error {
return p.perform(events.ProjectBuildStart, events.ProjectBuildDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(wrappers, events.ServiceBuildStart, events.ServiceBuild, func(service Service) error {
return service.Build(buildOptions)
})
}), nil)
}
// Create creates the specified services (like docker create).
func (p *Project) Create(options options.Create, services ...string) error {
if options.NoRecreate && options.ForceRecreate {
return fmt.Errorf("no-recreate and force-recreate cannot be combined")
}
return p.perform(events.ProjectCreateStart, events.ProjectCreateDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(wrappers, events.ServiceCreateStart, events.ServiceCreate, func(service Service) error {
return service.Create(options)
})
}), nil)
}
// Stop stops the specified services (like docker stop).
func (p *Project) Stop(timeout int, services ...string) error {
return p.perform(events.ProjectStopStart, events.ProjectStopDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(nil, events.ServiceStopStart, events.ServiceStop, func(service Service) error {
return service.Stop(timeout)
})
}), nil)
}
// Down stops the specified services and clean related containers (like docker stop + docker rm).
func (p *Project) Down(opts options.Down, services ...string) error {
if !opts.RemoveImages.Valid() {
return fmt.Errorf("--rmi flag must be local, all or empty")
}
if err := p.Stop(10, services...); err != nil {
return err
}
if opts.RemoveOrphans {
if err := p.removeOrphanContainers(); err != nil {
return err
}
}
if err := p.Delete(options.Delete{
RemoveVolume: opts.RemoveVolume,
}, services...); err != nil {
return err
}
return p.forEach([]string{}, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(wrappers, events.NoEvent, events.NoEvent, func(service Service) error {
return service.RemoveImage(opts.RemoveImages)
})
}), func(service Service) error {
return service.Create(options.Create{})
})
}
func (p *Project) removeOrphanContainers() error {
client := p.clientFactory.Create(nil)
filter := filters.NewArgs()
filter.Add("label", labels.PROJECT.EqString(p.Name))
containers, err := client.ContainerList(context.Background(), types.ContainerListOptions{
Filter: filter,
})
if err != nil {
return err
}
currentServices := map[string]struct{}{}
for _, serviceName := range p.ServiceConfigs.Keys() {
currentServices[serviceName] = struct{}{}
}
for _, container := range containers {
serviceLabel := container.Labels[labels.SERVICE.Str()]
if _, ok := currentServices[serviceLabel]; !ok {
if err := client.ContainerKill(context.Background(), container.ID, "SIGKILL"); err != nil {
return err
}
if _, err := client.ContainerRemove(context.Background(), container.ID, types.ContainerRemoveOptions{
Force: true,
}); err != nil {
return err
}
}
}
return nil
}
// Restart restarts the specified services (like docker restart).
func (p *Project) Restart(timeout int, services ...string) error {
return p.perform(events.ProjectRestartStart, events.ProjectRestartDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(wrappers, events.ServiceRestartStart, events.ServiceRestart, func(service Service) error {
return service.Restart(timeout)
})
}), nil)
}
// Port returns the public port for a port binding of the specified service.
func (p *Project) Port(index int, protocol, serviceName, privatePort string) (string, error) {
service, err := p.CreateService(serviceName)
if err != nil {
return "", err
}
containers, err := service.Containers()
if err != nil {
return "", err
}
if index < 1 || index > len(containers) {
return "", fmt.Errorf("Invalid index %d", index)
}
return containers[index-1].Port(fmt.Sprintf("%s/%s", privatePort, protocol))
}
// Ps list containers for the specified services.
func (p *Project) Ps(onlyID bool, services ...string) (InfoSet, error) {
allInfo := InfoSet{}
for _, name := range p.ServiceConfigs.Keys() {
service, err := p.CreateService(name)
if err != nil {
return nil, err
}
info, err := service.Info(onlyID)
if err != nil {
return nil, err
}
allInfo = append(allInfo, info...)
}
return allInfo, nil
}
// Start starts the specified services (like docker start).
func (p *Project) Start(services ...string) error {
return p.perform(events.ProjectStartStart, events.ProjectStartDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(wrappers, events.ServiceStartStart, events.ServiceStart, func(service Service) error {
return service.Start()
})
}), nil)
}
// Run executes a one off command (like `docker run image command`).
func (p *Project) Run(ctx context.Context, serviceName string, commandParts []string) (int, error) {
if !p.ServiceConfigs.Has(serviceName) {
return 1, fmt.Errorf("%s is not defined in the template", serviceName)
}
var exitCode int
err := p.forEach([]string{}, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(wrappers, events.ServiceRunStart, events.ServiceRun, func(service Service) error {
if service.Name() == serviceName {
code, err := service.Run(ctx, commandParts)
exitCode = code
return err
}
return nil
})
}), func(service Service) error {
return service.Create(options.Create{})
})
return exitCode, err
}
// Up creates and starts the specified services (kinda like docker run).
func (p *Project) Up(options options.Up, services ...string) error {
return p.perform(events.ProjectUpStart, events.ProjectUpDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(wrappers, events.ServiceUpStart, events.ServiceUp, func(service Service) error {
return service.Up(options)
})
}), func(service Service) error {
return service.Create(options.Create)
})
}
// Log aggregates and prints out the logs for the specified services.
func (p *Project) Log(follow bool, services ...string) error {
return p.forEach(services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(nil, events.NoEvent, events.NoEvent, func(service Service) error {
return service.Log(follow)
})
}), nil)
}
// Scale scales the specified services.
func (p *Project) Scale(timeout int, servicesScale map[string]int) error {
// This code is a bit verbose but I wanted to parse everything up front
order := make([]string, 0, 0)
services := make(map[string]Service)
for name := range servicesScale {
if !p.ServiceConfigs.Has(name) {
return fmt.Errorf("%s is not defined in the template", name)
}
service, err := p.CreateService(name)
if err != nil {
return fmt.Errorf("Failed to lookup service: %s: %v", service, err)
}
order = append(order, name)
services[name] = service
}
for _, name := range order {
scale := servicesScale[name]
log.Infof("Setting scale %s=%d...", name, scale)
err := services[name].Scale(scale, timeout)
if err != nil {
return fmt.Errorf("Failed to set the scale %s=%d: %v", name, scale, err)
}
}
return 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, events.ServicePullStart, events.ServicePull, func(service Service) error {
return service.Pull()
})
}), nil)
}
// listStoppedContainers lists the stopped containers for the specified services.
func (p *Project) listStoppedContainers(services ...string) ([]string, error) {
stoppedContainers := []string{}
err := p.forEach(services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(nil, events.NoEvent, events.NoEvent, func(service Service) error {
containers, innerErr := service.Containers()
if innerErr != nil {
return innerErr
}
for _, container := range containers {
running, innerErr := container.IsRunning()
if innerErr != nil {
log.Error(innerErr)
}
if !running {
containerID, innerErr := container.ID()
if innerErr != nil {
log.Error(innerErr)
}
stoppedContainers = append(stoppedContainers, containerID)
}
}
return nil
})
}), nil)
if err != nil {
return nil, err
}
return stoppedContainers, nil
}
// Delete removes the specified services (like docker rm).
func (p *Project) Delete(options options.Delete, services ...string) error {
stoppedContainers, err := p.listStoppedContainers(services...)
if err != nil {
return err
}
if len(stoppedContainers) == 0 {
p.Notify(events.ProjectDeleteDone, "", nil)
fmt.Println("No stopped containers")
return nil
}
if options.BeforeDeleteCallback != nil && !options.BeforeDeleteCallback(stoppedContainers) {
return nil
}
return p.perform(events.ProjectDeleteStart, events.ProjectDeleteDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(nil, events.ServiceDeleteStart, events.ServiceDelete, func(service Service) error {
return service.Delete(options)
})
}), nil)
}
// Kill kills the specified services (like docker kill).
func (p *Project) Kill(signal string, services ...string) error {
return p.perform(events.ProjectKillStart, events.ProjectKillDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(nil, events.ServiceKillStart, events.ServiceKill, func(service Service) error {
return service.Kill(signal)
})
}), nil)
}
// Pause pauses the specified services containers (like docker pause).
func (p *Project) Pause(services ...string) error {
return p.perform(events.ProjectPauseStart, events.ProjectPauseDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(nil, events.ServicePauseStart, events.ServicePause, func(service Service) error {
return service.Pause()
})
}), nil)
}
// Unpause pauses the specified services containers (like docker pause).
func (p *Project) Unpause(services ...string) error {
return p.perform(events.ProjectUnpauseStart, events.ProjectUnpauseDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(nil, events.ServiceUnpauseStart, events.ServiceUnpause, func(service Service) error {
return service.Unpause()
})
}), nil)
}
func (p *Project) perform(start, done events.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.ServiceConfigs.Keys() {
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 {
if err := p.startService(wrappers, []string{}, selected, launched, wrapper, action, cycleAction); err != nil {
return err
}
}
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.
// This implements implicitly events.Emitter.
func (p *Project) AddListener(c chan<- events.Event) {
if !p.hasListeners {
for _, l := range p.listeners {
close(l)
}
p.hasListeners = true
p.listeners = []chan<- events.Event{c}
} else {
p.listeners = append(p.listeners, c)
}
}
// Notify notifies all project listener with the specified eventType, service name and datas.
// This implements implicitly events.Notifier interface.
func (p *Project) Notify(eventType events.EventType, serviceName string, data map[string]string) {
if eventType == events.NoEvent {
return
}
event := events.Event{
EventType: eventType,
ServiceName: serviceName,
Data: data,
}
for _, l := range p.listeners {
l <- event
}
}

View File

@@ -0,0 +1,222 @@
package project
import (
"fmt"
"reflect"
"strings"
"testing"
"github.com/hyperhq/libcompose/config"
"github.com/hyperhq/libcompose/project/options"
"github.com/hyperhq/libcompose/yaml"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
type TestServiceFactory struct {
Counts map[string]int
}
type TestService struct {
factory *TestServiceFactory
name string
config *config.ServiceConfig
EmptyService
Count int
}
func (t *TestService) Config() *config.ServiceConfig {
return t.config
}
func (t *TestService) Name() string {
return t.name
}
func (t *TestService) Run(ctx context.Context, commandParts []string) (int, error) {
return 0, nil
}
func (t *TestService) Create(options options.Create) error {
key := t.name + ".create"
t.factory.Counts[key] = t.factory.Counts[key] + 1
return nil
}
func (t *TestService) DependentServices() []ServiceRelationship {
return nil
}
func (t *TestServiceFactory) Create(project *Project, name string, serviceConfig *config.ServiceConfig) (Service, error) {
return &TestService{
factory: t,
config: serviceConfig,
name: name,
}, nil
}
func TestTwoCall(t *testing.T) {
factory := &TestServiceFactory{
Counts: map[string]int{},
}
p := NewProject(nil, &Context{
ServiceFactory: factory,
})
p.ServiceConfigs = config.NewServiceConfigs()
p.ServiceConfigs.Add("foo", &config.ServiceConfig{})
if err := p.Create(options.Create{}, "foo"); err != nil {
t.Fatal(err)
}
if err := p.Create(options.Create{}, "foo"); err != nil {
t.Fatal(err)
}
if factory.Counts["foo.create"] != 2 {
t.Fatal("Failed to create twice")
}
}
func TestParseWithBadContent(t *testing.T) {
p := NewProject(nil, &Context{
ComposeBytes: [][]byte{
[]byte("garbage"),
},
})
err := p.Parse()
if err == nil {
t.Fatal("Should have failed parse")
}
if !strings.HasPrefix(err.Error(), "Invalid timestamp: 'garbage'") {
t.Fatalf("Should have failed parse: %#v", err)
}
}
func TestParseWithGoodContent(t *testing.T) {
p := NewProject(nil, &Context{
ComposeBytes: [][]byte{
[]byte("not-garbage:\n image: foo"),
},
})
err := p.Parse()
if err != nil {
t.Fatal(err)
}
}
type TestEnvironmentLookup struct {
}
func (t *TestEnvironmentLookup) Lookup(key, serviceName string, config *config.ServiceConfig) []string {
return []string{fmt.Sprintf("%s=X", key)}
}
func TestEnvironmentResolve(t *testing.T) {
factory := &TestServiceFactory{
Counts: map[string]int{},
}
p := NewProject(nil, &Context{
ServiceFactory: factory,
EnvironmentLookup: &TestEnvironmentLookup{},
})
p.ServiceConfigs = config.NewServiceConfigs()
p.ServiceConfigs.Add("foo", &config.ServiceConfig{
Environment: yaml.MaporEqualSlice([]string{
"A",
"A=",
"A=B",
}),
})
service, err := p.CreateService("foo")
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(service.Config().Environment, yaml.MaporEqualSlice{"A=X", "A=X", "A=B"}) {
t.Fatal("Invalid environment", service.Config().Environment)
}
}
func TestParseWithMultipleComposeFiles(t *testing.T) {
/*
configOne := []byte(`
multiple:
image: tianon/true
ports:
- 8000`)
configTwo := []byte(`
multiple:
image: busybox
container_name: multi
ports:
- 9000`)
configThree := []byte(`
multiple:
image: busybox
mem_limit: 40000000
ports:
- 10000`)
*/
configOne := []byte(`
multiple:
image: tianon/true`)
configTwo := []byte(`
multiple:
image: busybox
container_name: multi`)
configThree := []byte(`
multiple:
image: busybox
size: xxs`)
p := NewProject(nil, &Context{
ComposeBytes: [][]byte{configOne, configTwo},
})
err := p.Parse()
assert.Nil(t, err)
multipleConfig, _ := p.ServiceConfigs.Get("multiple")
assert.Equal(t, "busybox", multipleConfig.Image)
assert.Equal(t, "multi", multipleConfig.ContainerName)
//assert.Equal(t, []string{"8000", "9000"}, multipleConfig.Ports)
p = NewProject(nil, &Context{
ComposeBytes: [][]byte{configTwo, configOne},
})
err = p.Parse()
assert.Nil(t, err)
multipleConfig, _ = p.ServiceConfigs.Get("multiple")
assert.Equal(t, "tianon/true", multipleConfig.Image)
assert.Equal(t, "multi", multipleConfig.ContainerName)
//assert.Equal(t, []string{"9000", "8000"}, multipleConfig.Ports)
p = NewProject(nil, &Context{
ComposeBytes: [][]byte{configOne, configTwo, configThree},
})
err = p.Parse()
assert.Nil(t, err)
multipleConfig, _ = p.ServiceConfigs.Get("multiple")
assert.Equal(t, "busybox", multipleConfig.Image)
assert.Equal(t, "multi", multipleConfig.ContainerName)
//assert.Equal(t, []string{"8000", "9000", "10000"}, multipleConfig.Ports)
//assert.Equal(t, int64(40000000), multipleConfig.MemLimit)
}

View File

@@ -0,0 +1,115 @@
package project
import (
"sync"
log "github.com/Sirupsen/logrus"
"github.com/hyperhq/libcompose/project/events"
)
type serviceWrapper struct {
name string
service Service
done sync.WaitGroup
state ServiceState
err error
project *Project
noWait bool
ignored map[string]bool
}
func newServiceWrapper(name string, p *Project) (*serviceWrapper, error) {
wrapper := &serviceWrapper{
name: name,
state: StateUnknown,
project: p,
ignored: map[string]bool{},
}
return wrapper, wrapper.Reset()
}
func (s *serviceWrapper) IgnoreDep(name string) {
s.ignored[name] = true
}
func (s *serviceWrapper) Reset() error {
if s.state != StateExecuted {
service, err := s.project.CreateService(s.name)
if err != nil {
log.Errorf("Failed to create service for %s : %v", s.name, err)
return err
}
s.service = service
}
if s.err == ErrRestart {
s.err = nil
}
s.done.Add(1)
return nil
}
func (s *serviceWrapper) Ignore() {
defer s.done.Done()
s.state = StateExecuted
s.project.Notify(events.ServiceUpIgnored, s.service.Name(), nil)
}
func (s *serviceWrapper) waitForDeps(wrappers map[string]*serviceWrapper) bool {
if s.noWait {
return true
}
for _, dep := range s.service.DependentServices() {
if s.ignored[dep.Target] {
continue
}
if wrapper, ok := wrappers[dep.Target]; ok {
if wrapper.Wait() == ErrRestart {
s.project.Notify(events.ProjectReload, wrapper.service.Name(), nil)
s.err = ErrRestart
return false
}
} else {
log.Errorf("Failed to find %s", dep.Target)
}
}
return true
}
func (s *serviceWrapper) Do(wrappers map[string]*serviceWrapper, start, done events.EventType, action func(service Service) error) {
defer s.done.Done()
if s.state == StateExecuted {
return
}
if wrappers != nil && !s.waitForDeps(wrappers) {
return
}
s.state = StateExecuted
s.project.Notify(start, s.service.Name(), nil)
s.err = action(s.service)
if s.err == ErrRestart {
s.project.Notify(done, s.service.Name(), nil)
s.project.Notify(events.ProjectReloadTrigger, s.service.Name(), nil)
} else if s.err != nil {
log.Errorf("Failed %s %s : %v", start, s.name, s.err)
} else {
s.project.Notify(done, s.service.Name(), nil)
}
}
func (s *serviceWrapper) Wait() error {
s.done.Wait()
return s.err
}

View File

@@ -0,0 +1,89 @@
package project
import (
"errors"
"golang.org/x/net/context"
"github.com/hyperhq/libcompose/config"
"github.com/hyperhq/libcompose/project/options"
)
// Service defines what a libcompose service provides.
type Service interface {
Info(qFlag bool) (InfoSet, error)
Name() string
Build(buildOptions options.Build) error
Create(options options.Create) error
Up(options options.Up) error
Start() error
Stop(timeout int) error
Delete(options options.Delete) error
Restart(timeout int) error
Log(follow bool) error
Pull() error
Kill(signal string) error
Config() *config.ServiceConfig
DependentServices() []ServiceRelationship
Containers() ([]Container, error)
Scale(count int, timeout int) error
Pause() error
Unpause() error
Run(ctx context.Context, commandParts []string) (int, error)
RemoveImage(imageType options.ImageType) error
}
// 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")
)
// ServiceFactory is an interface factory to create Service object for the specified
// project, with the specified name and service configuration.
type ServiceFactory interface {
Create(project *Project, name string, serviceConfig *config.ServiceConfig) (Service, error)
}
// ServiceRelationshipType defines the type of service relationship.
type ServiceRelationshipType string
// RelTypeLink means the services are linked (docker links).
const RelTypeLink = ServiceRelationshipType("")
// RelTypeNetNamespace means the services share the same network namespace.
const RelTypeNetNamespace = ServiceRelationshipType("netns")
// RelTypeIpcNamespace means the service share the same ipc namespace.
const RelTypeIpcNamespace = ServiceRelationshipType("ipc")
// RelTypeVolumesFrom means the services share some volumes.
const RelTypeVolumesFrom = ServiceRelationshipType("volumesFrom")
// ServiceRelationship holds the relationship information between two services.
type ServiceRelationship struct {
Target, Alias string
Type ServiceRelationshipType
Optional bool
}
// NewServiceRelationship creates a new Relationship based on the specified alias
// and relationship type.
func NewServiceRelationship(nameAlias string, relType ServiceRelationshipType) ServiceRelationship {
name, alias := NameAlias(nameAlias)
return ServiceRelationship{
Target: name,
Alias: alias,
Type: relType,
}
}

70
vendor/github.com/hyperhq/libcompose/project/utils.go generated vendored Normal file
View File

@@ -0,0 +1,70 @@
package project
import (
"strings"
"github.com/docker/engine-api/types/container"
)
// DefaultDependentServices return the dependent services (as an array of ServiceRelationship)
// for the specified project and service. It looks for : links, volumesFrom, net and ipc configuration.
func DefaultDependentServices(p *Project, s Service) []ServiceRelationship {
config := s.Config()
if config == nil {
return []ServiceRelationship{}
}
result := []ServiceRelationship{}
for _, link := range config.Links {
result = append(result, NewServiceRelationship(link, RelTypeLink))
}
/*
for _, volumesFrom := range config.VolumesFrom {
result = append(result, NewServiceRelationship(volumesFrom, RelTypeVolumesFrom))
}
result = appendNs(p, result, s.Config().NetworkMode, RelTypeNetNamespace)
result = appendNs(p, result, s.Config().Ipc, RelTypeIpcNamespace)
*/
return result
}
func appendNs(p *Project, rels []ServiceRelationship, conf string, relType ServiceRelationshipType) []ServiceRelationship {
service := GetContainerFromIpcLikeConfig(p, conf)
if service != "" {
rels = append(rels, NewServiceRelationship(service, relType))
}
return rels
}
// NameAlias returns the name and alias based on the specified string.
// If the name contains a colon (like name:alias) it will split it, otherwise
// it will return the specified name as name and alias.
func NameAlias(name string) (string, string) {
parts := strings.SplitN(name, ":", 2)
if len(parts) == 2 {
return parts[0], parts[1]
}
return parts[0], parts[0]
}
// GetContainerFromIpcLikeConfig returns name of the service that shares the IPC
// namespace with the specified service.
func GetContainerFromIpcLikeConfig(p *Project, conf string) string {
ipc := container.IpcMode(conf)
if !ipc.IsContainer() {
return ""
}
name := ipc.Container()
if name == "" {
return ""
}
if p.ServiceConfigs.Has(name) {
return name
}
return ""
}