vendoring dependencies

This commit is contained in:
ngtuna
2016-07-18 23:23:39 +07:00
parent d5414877b5
commit 78feadc695
771 changed files with 129879 additions and 24277 deletions
+13
View File
@@ -0,0 +1,13 @@
package project
import (
"golang.org/x/net/context"
)
// Container defines what a libcompose container provides.
type Container interface {
ID() (string, error)
Name() string
Port(ctx context.Context, port string) (string, error)
IsRunning(ctx context.Context) (bool, error)
}
+37 -34
View File
@@ -10,6 +10,7 @@ import (
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/libcompose/config"
"github.com/docker/libcompose/logger"
)
@@ -18,52 +19,48 @@ 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 {
Timeout uint
Log bool
Volume bool
ForceRecreate bool
NoRecreate bool
NoCache bool
Signal int
ComposeFile string
ComposeBytes []byte
ComposeFiles []string
ComposeBytes [][]byte
ProjectName string
isOpen bool
ServiceFactory ServiceFactory
EnvironmentLookup EnvironmentLookup
ConfigLookup ConfigLookup
NetworksFactory NetworksFactory
EnvironmentLookup config.EnvironmentLookup
ResourceLookup config.ResourceLookup
LoggerFactory logger.Factory
IgnoreMissingConfig bool
Project *Project
}
func (c *Context) readComposeFile() error {
func (c *Context) readComposeFiles() error {
if c.ComposeBytes != nil {
return nil
}
logrus.Debugf("Opening compose file: %s", c.ComposeFile)
logrus.Debugf("Opening compose files: %s", strings.Join(c.ComposeFiles, ","))
if c.ComposeFile == "-" {
// 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 = composeBytes
} else if c.ComposeFile != "" {
if composeBytes, err := ioutil.ReadFile(c.ComposeFile); os.IsNotExist(err) {
if c.IgnoreMissingConfig {
return nil
}
logrus.Errorf("Failed to find %s", c.ComposeFile)
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
} else if err != nil {
logrus.Errorf("Failed to open %s", c.ComposeFile)
return err
} else {
c.ComposeBytes = composeBytes
}
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
@@ -75,16 +72,12 @@ func (c *Context) determineProject() error {
return err
}
c.ProjectName = projectRegexp.ReplaceAllString(strings.ToLower(name), "-")
c.ProjectName = normalizeName(name)
if c.ProjectName == "" {
return fmt.Errorf("Falied to determine project name")
}
if strings.ContainsAny(c.ProjectName[0:1], "_.-") {
c.ProjectName = "x" + c.ProjectName
}
return nil
}
@@ -97,9 +90,14 @@ func (c *Context) lookupProjectName() (string, error) {
return envProject, nil
}
f, err := filepath.Abs(c.ComposeFile)
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", c.ComposeFile)
logrus.Errorf("Failed to get absolute directory for: %s", file)
return "", err
}
@@ -115,6 +113,11 @@ func (c *Context) lookupProjectName() (string, error) {
}
}
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)
}
@@ -124,7 +127,7 @@ func (c *Context) open() error {
return nil
}
if err := c.readComposeFile(); err != nil {
if err := c.readComposeFiles(); err != nil {
return err
}
+75 -16
View File
@@ -1,80 +1,139 @@
package project
import (
"golang.org/x/net/context"
"github.com/docker/libcompose/config"
"github.com/docker/libcompose/project/events"
"github.com/docker/libcompose/project/options"
)
// this ensures EmptyService implements Service
// useful since it's easy to forget adding new functions to EmptyService
var _ Service = (*EmptyService)(nil)
// EmptyService is a struct that implements Service but does nothing.
type EmptyService struct {
}
// Create implements Service.Create but does nothing.
func (e *EmptyService) Create() error {
func (e *EmptyService) Create(ctx context.Context, options options.Create) error {
return nil
}
// Build implements Service.Build but does nothing.
func (e *EmptyService) Build() error {
func (e *EmptyService) Build(ctx context.Context, buildOptions options.Build) error {
return nil
}
// Up implements Service.Up but does nothing.
func (e *EmptyService) Up() error {
func (e *EmptyService) Up(ctx context.Context, options options.Up) error {
return nil
}
// Start implements Service.Start but does nothing.
func (e *EmptyService) Start() error {
func (e *EmptyService) Start(ctx context.Context) error {
return nil
}
// Down implements Service.Down but does nothing.
func (e *EmptyService) Down() error {
// Stop implements Service.Stop() but does nothing.
func (e *EmptyService) Stop(ctx context.Context, timeout int) error {
return nil
}
// Delete implements Service.Delete but does nothing.
func (e *EmptyService) Delete() error {
func (e *EmptyService) Delete(ctx context.Context, options options.Delete) error {
return nil
}
// Restart implements Service.Restart but does nothing.
func (e *EmptyService) Restart() error {
func (e *EmptyService) Restart(ctx context.Context, timeout int) error {
return nil
}
// Log implements Service.Log but does nothing.
func (e *EmptyService) Log() error {
func (e *EmptyService) Log(ctx context.Context, follow bool) error {
return nil
}
// Pull implements Service.Pull but does nothing.
func (e *EmptyService) Pull() error {
func (e *EmptyService) Pull(ctx context.Context) error {
return nil
}
// Kill implements Service.Kill but does nothing.
func (e *EmptyService) Kill() error {
func (e *EmptyService) Kill(ctx context.Context, signal string) error {
return nil
}
// Containers implements Service.Containers but does nothing.
func (e *EmptyService) Containers() ([]Container, error) {
func (e *EmptyService) Containers(ctx context.Context) ([]Container, error) {
return []Container{}, nil
}
// Scale implements Service.Scale but does nothing.
func (e *EmptyService) Scale(count int) error {
func (e *EmptyService) Scale(ctx context.Context, count int, timeout int) error {
return nil
}
// Info implements Service.Info but does nothing.
func (e *EmptyService) Info(qFlag bool) (InfoSet, error) {
func (e *EmptyService) Info(ctx context.Context, qFlag bool) (InfoSet, error) {
return InfoSet{}, nil
}
// Pause implements Service.Pause but does nothing.
func (e *EmptyService) Pause() error {
func (e *EmptyService) Pause(ctx context.Context) error {
return nil
}
// Unpause implements Service.Pause but does nothing.
func (e *EmptyService) Unpause() error {
func (e *EmptyService) Unpause(ctx context.Context) error {
return nil
}
// Run implements Service.Run but does nothing.
func (e *EmptyService) Run(ctx context.Context, commandParts []string, options options.Run) (int, error) {
return 0, nil
}
// RemoveImage implements Service.RemoveImage but does nothing.
func (e *EmptyService) RemoveImage(ctx context.Context, imageType options.ImageType) error {
return nil
}
// Events implements Service.Events but does nothing.
func (e *EmptyService) Events(ctx context.Context, events chan events.ContainerEvent) error {
return nil
}
// DependentServices implements Service.DependentServices with empty slice.
func (e *EmptyService) DependentServices() []ServiceRelationship {
return []ServiceRelationship{}
}
// Config implements Service.Config with empty config.
func (e *EmptyService) Config() *config.ServiceConfig {
return &config.ServiceConfig{}
}
// Name implements Service.Name with empty name.
func (e *EmptyService) Name() string {
return ""
}
// this ensures EmptyNetworks implements Networks
var _ Networks = (*EmptyNetworks)(nil)
// EmptyNetworks is a struct that implements Networks but does nothing.
type EmptyNetworks struct {
}
// Initialize implements Networks.Initialize but does nothing.
func (e *EmptyNetworks) Initialize(ctx context.Context) error {
return nil
}
// Initialize implements Networks.Remove but does nothing.
func (e *EmptyNetworks) Remove(ctx context.Context) error {
return nil
}
+224
View File
@@ -0,0 +1,224 @@
// Package events holds event structures, methods and functions.
package events
import (
"fmt"
"time"
)
// 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
}
// ContainerEvent holds attributes of container events.
type ContainerEvent struct {
Service string `json:"service"`
Event string `json:"event"`
ID string `json:"id"`
Time time.Time `json:"time"`
Attributes map[string]string `json:"attributes"`
Type string `json:"type"`
}
// 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 ServicePauseStart:
m = "Pausing"
case ServicePause:
m = "Paused"
case ServiceUnpauseStart:
m = "Unpausing"
case ServiceUnpause:
m = "Unpaused"
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"
case ProjectPauseStart:
m = "Pausing project"
case ProjectPauseDone:
m = "Project paused"
case ProjectUnpauseStart:
m = "Unpausing project"
case ProjectUnpauseDone:
m = "Project unpaused"
}
if m == "" {
m = fmt.Sprintf("EventType: %d", int(e))
}
return m
}
-106
View File
@@ -1,106 +0,0 @@
package project
import (
"crypto/sha1"
"encoding/hex"
"fmt"
"io"
"reflect"
"sort"
)
// GetServiceHash computes and returns a hash that will identify a service.
// This hash will be then used to detect if the service definition/configuration
// have changed and needs to be recreated.
func GetServiceHash(name string, config *ServiceConfig) string {
hash := sha1.New()
io.WriteString(hash, name)
//Get values of Service through reflection
val := reflect.ValueOf(config).Elem()
//Create slice to sort the keys in Service Config, which allow constant hash ordering
serviceKeys := []string{}
//Create a data structure of map of values keyed by a string
unsortedKeyValue := make(map[string]interface{})
//Get all keys and values in Service Configuration
for i := 0; i < val.NumField(); i++ {
valueField := val.Field(i)
keyField := val.Type().Field(i)
serviceKeys = append(serviceKeys, keyField.Name)
unsortedKeyValue[keyField.Name] = valueField.Interface()
}
//Sort serviceKeys alphabetically
sort.Strings(serviceKeys)
//Go through keys and write hash
for _, serviceKey := range serviceKeys {
serviceValue := unsortedKeyValue[serviceKey]
io.WriteString(hash, fmt.Sprintf("\n %v: ", serviceKey))
switch s := serviceValue.(type) {
case SliceorMap:
sliceKeys := []string{}
for lkey := range s.MapParts() {
sliceKeys = append(sliceKeys, lkey)
}
sort.Strings(sliceKeys)
for _, sliceKey := range sliceKeys {
io.WriteString(hash, fmt.Sprintf("%s=%v, ", sliceKey, s.MapParts()[sliceKey]))
}
case MaporEqualSlice:
sliceKeys := s.Slice()
// do not sort keys as the order matters
for _, sliceKey := range sliceKeys {
io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey))
}
case MaporColonSlice:
sliceKeys := s.Slice()
// do not sort keys as the order matters
for _, sliceKey := range sliceKeys {
io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey))
}
case MaporSpaceSlice:
sliceKeys := s.Slice()
// do not sort keys as the order matters
for _, sliceKey := range sliceKeys {
io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey))
}
case Command:
sliceKeys := s.Slice()
// do not sort keys as the order matters
for _, sliceKey := range sliceKeys {
io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey))
}
case Stringorslice:
sliceKeys := s.Slice()
sort.Strings(sliceKeys)
for _, sliceKey := range sliceKeys {
io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey))
}
case []string:
sliceKeys := s
sort.Strings(sliceKeys)
for _, sliceKey := range sliceKeys {
io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey))
}
default:
io.WriteString(hash, fmt.Sprintf("%v", serviceValue))
}
}
return hex.EncodeToString(hash.Sum(nil))
}
+11
View File
@@ -6,6 +6,17 @@ import (
"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))
+45
View File
@@ -0,0 +1,45 @@
package project
import (
"golang.org/x/net/context"
"github.com/docker/libcompose/config"
"github.com/docker/libcompose/project/events"
"github.com/docker/libcompose/project/options"
)
// APIProject defines the methods a libcompose project should implement.
type APIProject interface {
events.Notifier
events.Emitter
Build(ctx context.Context, options options.Build, sevice ...string) error
Create(ctx context.Context, options options.Create, services ...string) error
Delete(ctx context.Context, options options.Delete, services ...string) error
Down(ctx context.Context, options options.Down, services ...string) error
Events(ctx context.Context, services ...string) (chan events.ContainerEvent, error)
Kill(ctx context.Context, signal string, services ...string) error
Log(ctx context.Context, follow bool, services ...string) error
Pause(ctx context.Context, services ...string) error
Ps(ctx context.Context, onlyID bool, services ...string) (InfoSet, error)
// FIXME(vdemeester) we could use nat.Port instead ?
Port(ctx context.Context, index int, protocol, serviceName, privatePort string) (string, error)
Pull(ctx context.Context, services ...string) error
Restart(ctx context.Context, timeout int, services ...string) error
Run(ctx context.Context, serviceName string, commandParts []string, options options.Run) (int, error)
Scale(ctx context.Context, timeout int, servicesScale map[string]int) error
Start(ctx context.Context, services ...string) error
Stop(ctx context.Context, timeout int, services ...string) error
Unpause(ctx context.Context, services ...string) error
Up(ctx context.Context, options options.Up, services ...string) error
Parse() error
CreateService(name string) (Service, error)
AddConfig(name string, config *config.ServiceConfig) error
Load(bytes []byte) error
}
// RuntimeProject defines runtime-specific methods for a libcompose implementation.
type RuntimeProject interface {
RemoveOrphans(ctx context.Context, projectName string, serviceConfigs *config.ServiceConfigs) error
}
-179
View File
@@ -1,179 +0,0 @@
package project
import (
"bytes"
"fmt"
"strconv"
"strings"
"github.com/Sirupsen/logrus"
)
func isNum(c uint8) bool {
return c >= '0' && c <= '9'
}
func validVariableNameChar(c uint8) bool {
return c == '_' ||
c >= 'A' && c <= 'Z' ||
c >= 'a' && c <= 'z' ||
isNum(c)
}
func parseVariable(line string, pos int, mapping func(string) string) (string, int, bool) {
var buffer bytes.Buffer
for ; pos < len(line); pos++ {
c := line[pos]
switch {
case validVariableNameChar(c):
buffer.WriteByte(c)
default:
return mapping(buffer.String()), pos - 1, true
}
}
return mapping(buffer.String()), pos, true
}
func parseVariableWithBraces(line string, pos int, mapping func(string) string) (string, int, bool) {
var buffer bytes.Buffer
for ; pos < len(line); pos++ {
c := line[pos]
switch {
case c == '}':
bufferString := buffer.String()
if bufferString == "" {
return "", 0, false
}
return mapping(buffer.String()), pos, true
case validVariableNameChar(c):
buffer.WriteByte(c)
default:
return "", 0, false
}
}
return "", 0, false
}
func parseInterpolationExpression(line string, pos int, mapping func(string) string) (string, int, bool) {
c := line[pos]
switch {
case c == '$':
return "$", pos, true
case c == '{':
return parseVariableWithBraces(line, pos+1, mapping)
case !isNum(c) && validVariableNameChar(c):
// Variables can't start with a number
return parseVariable(line, pos, mapping)
default:
return "", 0, false
}
}
func parseLine(line string, mapping func(string) string) (string, bool) {
var buffer bytes.Buffer
for pos := 0; pos < len(line); pos++ {
c := line[pos]
switch {
case c == '$':
var replaced string
var success bool
replaced, pos, success = parseInterpolationExpression(line, pos+1, mapping)
if !success {
return "", false
}
buffer.WriteString(replaced)
default:
buffer.WriteByte(c)
}
}
return buffer.String(), true
}
func parseConfig(option, service string, data *interface{}, mapping func(string) string) error {
switch typedData := (*data).(type) {
case string:
var success bool
interpolatedLine, success := parseLine(typedData, mapping)
if !success {
return fmt.Errorf("Invalid interpolation format for \"%s\" option in service \"%s\": \"%s\"", option, service, typedData)
}
// If possible, convert the value to an integer
// If the type should be a string and not an int, go-yaml will convert it back into a string
lineAsInteger, err := strconv.Atoi(interpolatedLine)
if err == nil {
*data = lineAsInteger
} else {
*data = interpolatedLine
}
case []interface{}:
for k, v := range typedData {
err := parseConfig(option, service, &v, mapping)
if err != nil {
return err
}
typedData[k] = v
}
case map[interface{}]interface{}:
for k, v := range typedData {
err := parseConfig(option, service, &v, mapping)
if err != nil {
return err
}
typedData[k] = v
}
}
return nil
}
func interpolate(environmentLookup EnvironmentLookup, config *rawServiceMap) error {
for k, v := range *config {
for k2, v2 := range v {
err := parseConfig(k2, k, &v2, func(s string) string {
values := environmentLookup.Lookup(s, k, nil)
if len(values) == 0 {
logrus.Warnf("The %s variable is not set. Substituting a blank string.", s)
return ""
}
// Use first result if many are given
value := values[0]
// Environment variables come in key=value format
// Return everything past first '='
return strings.SplitN(value, "=", 2)[1]
})
if err != nil {
return err
}
(*config)[k][k2] = v2
}
}
return nil
}
+27 -22
View File
@@ -4,39 +4,44 @@ import (
"bytes"
"github.com/Sirupsen/logrus"
"github.com/docker/libcompose/project/events"
)
var (
infoEvents = map[EventType]bool{
EventProjectDeleteDone: true,
EventProjectDeleteStart: true,
EventProjectDownDone: true,
EventProjectDownStart: true,
EventProjectRestartDone: true,
EventProjectRestartStart: true,
EventProjectUpDone: true,
EventProjectUpStart: true,
EventServiceDeleteStart: true,
EventServiceDelete: true,
EventServiceDownStart: true,
EventServiceDown: true,
EventServiceRestartStart: true,
EventServiceRestart: true,
EventServiceUpStart: true,
EventServiceUp: true,
infoEvents = map[events.EventType]bool{
events.ServiceDeleteStart: true,
events.ServiceDelete: true,
events.ServiceDownStart: true,
events.ServiceDown: true,
events.ServiceStopStart: true,
events.ServiceStop: true,
events.ServiceKillStart: true,
events.ServiceKill: true,
events.ServiceCreateStart: true,
events.ServiceCreate: true,
events.ServiceStartStart: true,
events.ServiceStart: true,
events.ServiceRestartStart: true,
events.ServiceRestart: true,
events.ServiceUpStart: true,
events.ServiceUp: true,
events.ServicePauseStart: true,
events.ServicePause: true,
events.ServiceUnpauseStart: true,
events.ServiceUnpause: true,
}
)
type defaultListener struct {
project *Project
listenChan chan Event
listenChan chan events.Event
upCount int
}
// NewDefaultListener create a default listener for the specified project.
func NewDefaultListener(p *Project) chan<- Event {
func NewDefaultListener(p *Project) chan<- events.Event {
l := defaultListener{
listenChan: make(chan Event),
listenChan: make(chan events.Event),
project: p,
}
go l.start()
@@ -57,7 +62,7 @@ func (d *defaultListener) start() {
}
}
if event.EventType == EventServiceUp {
if event.EventType == events.ServiceUp {
d.upCount++
}
@@ -70,7 +75,7 @@ func (d *defaultListener) start() {
if event.ServiceName == "" {
logf("Project [%s]: %s %s", d.project.Name, event.EventType, buffer.Bytes())
} else {
logf("[%d/%d] [%s]: %s %s", d.upCount, len(d.project.Configs), event.ServiceName, event.EventType, buffer.Bytes())
logf("[%d/%d] [%s]: %s %s", d.upCount, d.project.ServiceConfigs.Len(), event.ServiceName, event.EventType, buffer.Bytes())
}
}
}
-299
View File
@@ -1,299 +0,0 @@
package project
import (
"bufio"
"bytes"
"fmt"
"path"
"strings"
"github.com/Sirupsen/logrus"
yaml "github.com/cloudfoundry-incubator/candiedyaml"
"github.com/docker/libcompose/utils"
)
var (
// ValidRemotes list the of valid prefixes that can be sent to Docker as a build remote location
// This is public for consumers of libcompose to use
ValidRemotes = []string{
"git://",
"git@github.com:",
"github.com",
"http:",
"https:",
}
noMerge = []string{
"links",
"volumes_from",
}
)
type rawService map[string]interface{}
type rawServiceMap map[string]rawService
func mergeProject(p *Project, bytes []byte) (map[string]*ServiceConfig, error) {
configs := make(map[string]*ServiceConfig)
datas := make(rawServiceMap)
if err := yaml.Unmarshal(bytes, &datas); err != nil {
return nil, err
}
if err := interpolate(p.context.EnvironmentLookup, &datas); err != nil {
return nil, err
}
for name, data := range datas {
data, err := parse(p.context.ConfigLookup, p.context.EnvironmentLookup, p.File, data, datas)
if err != nil {
logrus.Errorf("Failed to parse service %s: %v", name, err)
return nil, err
}
datas[name] = data
}
if err := utils.Convert(datas, &configs); err != nil {
return nil, err
}
adjustValues(configs)
return configs, nil
}
func adjustValues(configs map[string]*ServiceConfig) {
// yaml parser turns "no" into "false" but that is not valid for a restart policy
for _, v := range configs {
if v.Restart == "false" {
v.Restart = "no"
}
}
}
func readEnvFile(configLookup ConfigLookup, inFile string, serviceData rawService) (rawService, error) {
var config ServiceConfig
if err := utils.Convert(serviceData, &config); err != nil {
return nil, err
}
if len(config.EnvFile.Slice()) == 0 {
return serviceData, nil
}
if configLookup == nil {
return nil, fmt.Errorf("Can not use env_file in file %s no mechanism provided to load files", inFile)
}
vars := config.Environment.Slice()
for i := len(config.EnvFile.Slice()) - 1; i >= 0; i-- {
envFile := config.EnvFile.Slice()[i]
content, _, err := configLookup.Lookup(envFile, inFile)
if err != nil {
return nil, err
}
if err != nil {
return nil, err
}
scanner := bufio.NewScanner(bytes.NewBuffer(content))
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
key := strings.SplitAfter(line, "=")[0]
found := false
for _, v := range vars {
if strings.HasPrefix(v, key) {
found = true
break
}
}
if !found {
vars = append(vars, line)
}
}
if scanner.Err() != nil {
return nil, scanner.Err()
}
}
serviceData["environment"] = vars
delete(serviceData, "env_file")
return serviceData, nil
}
func resolveBuild(inFile string, serviceData rawService) (rawService, error) {
build := asString(serviceData["build"])
if build == "" {
return serviceData, nil
}
for _, remote := range ValidRemotes {
if strings.HasPrefix(build, remote) {
return serviceData, nil
}
}
current := path.Dir(inFile)
if build == "." {
build = current
} else {
current = path.Join(current, build)
}
serviceData["build"] = current
return serviceData, nil
}
func parse(configLookup ConfigLookup, environmentLookup EnvironmentLookup, inFile string, serviceData rawService, datas rawServiceMap) (rawService, error) {
serviceData, err := readEnvFile(configLookup, inFile, serviceData)
if err != nil {
return nil, err
}
serviceData, err = resolveBuild(inFile, serviceData)
if err != nil {
return nil, err
}
value, ok := serviceData["extends"]
if !ok {
return serviceData, nil
}
mapValue, ok := value.(map[interface{}]interface{})
if !ok {
return serviceData, nil
}
if configLookup == nil {
return nil, fmt.Errorf("Can not use extends in file %s no mechanism provided to files", inFile)
}
file := asString(mapValue["file"])
service := asString(mapValue["service"])
if service == "" {
return serviceData, nil
}
var baseService rawService
if file == "" {
if serviceData, ok := datas[service]; ok {
baseService, err = parse(configLookup, environmentLookup, inFile, serviceData, datas)
} else {
return nil, fmt.Errorf("Failed to find service %s to extend", service)
}
} else {
bytes, resolved, err := configLookup.Lookup(file, inFile)
if err != nil {
logrus.Errorf("Failed to lookup file %s: %v", file, err)
return nil, err
}
var baseRawServices rawServiceMap
if err := yaml.Unmarshal(bytes, &baseRawServices); err != nil {
return nil, err
}
err = interpolate(environmentLookup, &baseRawServices)
if err != nil {
return nil, err
}
baseService, ok = baseRawServices[service]
if !ok {
return nil, fmt.Errorf("Failed to find service %s in file %s", service, file)
}
baseService, err = parse(configLookup, environmentLookup, resolved, baseService, baseRawServices)
}
if err != nil {
return nil, err
}
baseService = clone(baseService)
logrus.Debugf("Merging %#v, %#v", baseService, serviceData)
for _, k := range noMerge {
if _, ok := baseService[k]; ok {
source := file
if source == "" {
source = inFile
}
return nil, fmt.Errorf("Cannot extend service '%s' in %s: services with '%s' cannot be extended", service, source, k)
}
}
for k, v := range serviceData {
// Image and build are mutually exclusive in merge
if k == "image" {
delete(baseService, "build")
} else if k == "build" {
delete(baseService, "image")
}
existing, ok := baseService[k]
if ok {
baseService[k] = merge(existing, v)
} else {
baseService[k] = v
}
}
logrus.Debugf("Merged result %#v", baseService)
return baseService, nil
}
func merge(existing, value interface{}) interface{} {
// append strings
if left, lok := existing.([]interface{}); lok {
if right, rok := value.([]interface{}); rok {
return append(left, right...)
}
}
//merge maps
if left, lok := existing.(map[interface{}]interface{}); lok {
if right, rok := value.(map[interface{}]interface{}); rok {
newLeft := make(map[interface{}]interface{})
for k, v := range left {
newLeft[k] = v
}
for k, v := range right {
newLeft[k] = v
}
return newLeft
}
}
return value
}
func clone(in rawService) rawService {
result := rawService{}
for k, v := range in {
result[k] = v
}
return result
}
func asString(obj interface{}) string {
if v, ok := obj.(string); ok {
return v
}
return ""
}
+19
View File
@@ -0,0 +1,19 @@
package project
import (
"golang.org/x/net/context"
"github.com/docker/libcompose/config"
)
// Networks defines the methods a libcompose network aggregate should define.
type Networks interface {
Initialize(ctx context.Context) error
Remove(ctx context.Context) error
}
// NetworksFactory is an interface factory to create Networks object for the specified
// configurations (service, networks, …)
type NetworksFactory interface {
Create(projectName string, networkConfigs map[string]*config.NetworkConfig, serviceConfigs *config.ServiceConfigs, networkEnabled bool) (Networks, error)
}
+52
View File
@@ -0,0 +1,52 @@
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
}
// Run holds options of compose run.
type Run struct {
Detached 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
}
}
+431 -112
View File
@@ -5,41 +5,49 @@ import (
"fmt"
"strings"
"golang.org/x/net/context"
log "github.com/Sirupsen/logrus"
"github.com/docker/libcompose/config"
"github.com/docker/libcompose/logger"
"github.com/docker/libcompose/project/events"
"github.com/docker/libcompose/project/options"
"github.com/docker/libcompose/utils"
"github.com/docker/libcompose/yaml"
)
// 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 {
// 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
ParseOptions *config.ParseOptions
runtime RuntimeProject
networks Networks
configVersion string
context *Context
reload []string
upCount int
listeners []chan<- events.Event
hasListeners bool
}
// NewProject creates a new project with the specified context.
func NewProject(context *Context, runtime RuntimeProject, parseOptions *config.ParseOptions) *Project {
p := &Project{
context: context,
Configs: make(map[string]*ServiceConfig),
context: context,
runtime: runtime,
ParseOptions: parseOptions,
ServiceConfigs: config.NewServiceConfigs(),
VolumeConfigs: make(map[string]*config.VolumeConfig),
NetworkConfigs: make(map[string]*config.NetworkConfig),
}
if context.LoggerFactory == nil {
@@ -48,7 +56,7 @@ func NewProject(context *Context) *Project {
context.Project = p
p.listeners = []chan<- Event{NewDefaultListener(p)}
p.listeners = []chan<- events.Event{NewDefaultListener(p)}
return p
}
@@ -63,23 +71,31 @@ func (p *Project) Parse() error {
p.Name = p.context.ProjectName
if p.context.ComposeFile == "-" {
p.File = "."
} else {
p.File = p.context.ComposeFile
p.Files = p.context.ComposeFiles
if len(p.Files) == 1 && p.Files[0] == "-" {
p.Files = []string{"."}
}
if p.context.ComposeBytes != nil {
return p.Load(p.context.ComposeBytes)
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. It there
// 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.Configs[name]
existing, ok := p.ServiceConfigs.Get(name)
if !ok {
return nil, fmt.Errorf("Failed to find service: %s", name)
}
@@ -88,9 +104,9 @@ func (p *Project) CreateService(name string) (Service, error) {
config := *existing
if p.context.EnvironmentLookup != nil {
parsedEnv := make([]string, 0, len(config.Environment.Slice()))
parsedEnv := make([]string, 0, len(config.Environment))
for _, env := range config.Environment.Slice() {
for _, env := range config.Environment {
parts := strings.SplitN(env, "=", 2)
if len(parts) > 1 && parts[1] != "" {
parsedEnv = append(parsedEnv, env)
@@ -104,39 +120,151 @@ func (p *Project) CreateService(name string) (Service, error) {
}
}
config.Environment = NewMaporEqualSlice(parsedEnv)
config.Environment = parsedEnv
// check the environment for extra build Args that are set but not given a value in the compose file
for arg, value := range config.Build.Args {
if value == "\x00" {
envValue := p.context.EnvironmentLookup.Lookup(arg, name, &config)
// depending on what we get back we do different things
switch l := len(envValue); l {
case 0:
delete(config.Build.Args, arg)
case 1:
parts := strings.SplitN(envValue[0], "=", 2)
config.Build.Args[parts[0]] = parts[1]
default:
return nil, fmt.Errorf("Tried to set Build Arg %#v to multi-value %#v.", arg, envValue)
}
}
}
}
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)
func (p *Project) AddConfig(name string, config *config.ServiceConfig) error {
p.Notify(events.ServiceAdd, name, nil)
p.Configs[name] = config
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 {
configs := make(map[string]*ServiceConfig)
configs, err := mergeProject(p, bytes)
return p.load("", bytes)
}
func (p *Project) load(file string, bytes []byte) error {
version, serviceConfigs, volumeConfigs, networkConfigs, err := config.Merge(p.ServiceConfigs, p.context.EnvironmentLookup, p.context.ResourceLookup, file, bytes, p.ParseOptions)
if err != nil {
log.Errorf("Could not parse config for project %s : %v", p.Name, err)
return err
}
for name, config := range configs {
p.configVersion = version
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
}
}
for name, config := range serviceConfigs {
err := p.AddConfig(name, config)
if err != nil {
return err
}
}
// Update network configuration a little bit
if p.isNetworkEnabled() {
for _, serviceName := range p.ServiceConfigs.Keys() {
serviceConfig, _ := p.ServiceConfigs.Get(serviceName)
if serviceConfig.NetworkMode != "" {
continue
}
if serviceConfig.Networks == nil || len(serviceConfig.Networks.Networks) == 0 {
// Add default as network
serviceConfig.Networks = &yaml.Networks{
Networks: []*yaml.Network{
{
Name: "default",
},
},
}
}
// Consolidate the name of the network
// FIXME(vdemeester) probably shouldn't be there, maybe move that to interface/factory
for _, network := range serviceConfig.Networks.Networks {
if net, ok := p.NetworkConfigs[network.Name]; ok {
if net.External.External {
network.RealName = network.Name
if net.External.Name != "" {
network.RealName = net.External.Name
}
} else {
network.RealName = p.Name + "_" + network.Name
}
}
// Ignoring if we don't find the network, it will be catched later
}
}
}
// FIXME(vdemeester) Not sure about this..
if p.context.NetworksFactory != nil {
networks, err := p.context.NetworksFactory.Create(p.Name, p.NetworkConfigs, p.ServiceConfigs, p.isNetworkEnabled())
if err != nil {
return err
}
p.networks = networks
}
return nil
}
func (p *Project) isNetworkEnabled() bool {
return p.configVersion == "2"
}
// initialize sets up required element for project before any action (on project and service).
// This means it's not needed to be called on Config for example.
func (p *Project) initialize(ctx context.Context) error {
if p.networks != nil {
if err := p.networks.Initialize(ctx); err != nil {
return err
}
}
// TODO Initialize volumes
return nil
}
@@ -153,116 +281,303 @@ func (p *Project) loadWrappers(wrappers map[string]*serviceWrapper, servicesToCo
}
// 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()
func (p *Project) Build(ctx context.Context, 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(ctx, buildOptions)
})
}), 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()
func (p *Project) Create(ctx context.Context, 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(ctx, options)
})
}), 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()
// Stop stops the specified services (like docker stop).
func (p *Project) Stop(ctx context.Context, 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(ctx, timeout)
})
}), 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)
}
// Down stops the specified services and clean related containers (like docker stop + docker rm).
func (p *Project) Down(ctx context.Context, 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(ctx, 10, services...); err != nil {
return err
}
if opts.RemoveOrphans {
if err := p.runtime.RemoveOrphans(ctx, p.Name, p.ServiceConfigs); err != nil {
return err
}
}
if err := p.Delete(ctx, options.Delete{
RemoveVolume: opts.RemoveVolume,
}, services...); err != nil {
return err
}
// 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)
}
networks, err := p.context.NetworksFactory.Create(p.Name, p.NetworkConfigs, p.ServiceConfigs, p.isNetworkEnabled())
if err != nil {
return err
}
if err := networks.Remove(ctx); err != nil {
return err
}
// 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()
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(ctx, opts.RemoveImages)
})
}), func(service Service) error {
return service.Create()
return service.Create(ctx, options.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()
// RemoveOrphans implements project.RuntimeProject.RemoveOrphans.
// It does nothing by default as it is supposed to be overriden by specific implementation.
func (p *Project) RemoveOrphans(ctx context.Context) error {
return nil
}
// Restart restarts the specified services (like docker restart).
func (p *Project) Restart(ctx context.Context, 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(ctx, timeout)
})
}), nil)
}
// Port returns the public port for a port binding of the specified service.
func (p *Project) Port(ctx context.Context, index int, protocol, serviceName, privatePort string) (string, error) {
service, err := p.CreateService(serviceName)
if err != nil {
return "", err
}
containers, err := service.Containers(ctx)
if err != nil {
return "", err
}
if index < 1 || index > len(containers) {
return "", fmt.Errorf("Invalid index %d", index)
}
return containers[index-1].Port(ctx, fmt.Sprintf("%s/%s", privatePort, protocol))
}
// Ps list containers for the specified services.
func (p *Project) Ps(ctx context.Context, 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(ctx, 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(ctx context.Context, 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(ctx)
})
}), nil)
}
// Run executes a one off command (like `docker run image command`).
func (p *Project) Run(ctx context.Context, serviceName string, commandParts []string, opts options.Run) (int, error) {
if !p.ServiceConfigs.Has(serviceName) {
return 1, fmt.Errorf("%s is not defined in the template", serviceName)
}
if err := p.initialize(ctx); err != nil {
return 1, err
}
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, opts)
exitCode = code
return err
}
return nil
})
}), func(service Service) error {
return service.Create(ctx, options.Create{})
})
return exitCode, err
}
// Up creates and starts the specified services (kinda like docker run).
func (p *Project) Up(ctx context.Context, options options.Up, services ...string) error {
if err := p.initialize(ctx); err != nil {
return err
}
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(ctx, options)
})
}), func(service Service) error {
return service.Create(ctx, options.Create)
})
}
// Log aggregates and prints out the logs for the specified services.
func (p *Project) Log(ctx context.Context, 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(ctx, follow)
})
}), nil)
}
// Scale scales the specified services.
func (p *Project) Scale(ctx context.Context, 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(ctx, 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 {
func (p *Project) Pull(ctx context.Context, 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()
wrapper.Do(nil, events.ServicePullStart, events.ServicePull, func(service Service) error {
return service.Pull(ctx)
})
}), nil)
}
// listStoppedContainers lists the stopped containers for the specified services.
func (p *Project) listStoppedContainers(ctx context.Context, 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(ctx)
if innerErr != nil {
return innerErr
}
for _, container := range containers {
running, innerErr := container.IsRunning(ctx)
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(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()
func (p *Project) Delete(ctx context.Context, options options.Delete, services ...string) error {
stoppedContainers, err := p.listStoppedContainers(ctx, 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(ctx, options)
})
}), 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()
func (p *Project) Kill(ctx context.Context, 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(ctx, signal)
})
}), nil)
}
// Pause pauses the specified services containers (like docker pause).
func (p *Project) Pause(services ...string) error {
return p.perform(EventProjectPauseStart, EventProjectPauseDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(nil, EventServicePauseStart, EventServicePause, func(service Service) error {
return service.Pause()
func (p *Project) Pause(ctx context.Context, 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(ctx)
})
}), nil)
}
// Unpause pauses the specified services containers (like docker pause).
func (p *Project) Unpause(services ...string) error {
return p.perform(EventProjectUnpauseStart, EventProjectUnpauseDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(nil, EventServiceUnpauseStart, EventServiceUnpause, func(service Service) error {
return service.Unpause()
func (p *Project) Unpause(ctx context.Context, 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(ctx)
})
}), nil)
}
func (p *Project) perform(start, done EventType, services []string, action wrapperAction, cycleAction serviceAction) error {
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)
@@ -297,8 +612,8 @@ func (p *Project) startService(wrappers map[string]*serviceWrapper, history []st
for _, dep := range wrapper.service.DependentServices() {
target := wrappers[dep.Target]
if target == nil {
log.Errorf("Failed to find %s", dep.Target)
continue
log.Debugf("Failed to find %s", dep.Target)
return fmt.Errorf("Service '%s' has a link to service '%s' which is undefined", wrapper.name, dep.Target)
}
if utils.Contains(history, dep.Target) {
@@ -342,7 +657,7 @@ func (p *Project) traverse(start bool, selected map[string]bool, wrappers map[st
wrapperList := []string{}
if start {
for name := range p.Configs {
for _, name := range p.ServiceConfigs.Keys() {
wrapperList = append(wrapperList, name)
}
} else {
@@ -367,7 +682,9 @@ func (p *Project) traverse(start bool, selected map[string]bool, wrappers map[st
launched := map[string]bool{}
for _, wrapper := range wrappers {
p.startService(wrappers, []string{}, selected, launched, wrapper, action, cycleAction)
if err := p.startService(wrappers, []string{}, selected, launched, wrapper, action, cycleAction); err != nil {
return err
}
}
var firstError error
@@ -398,25 +715,27 @@ func (p *Project) traverse(start bool, selected map[string]bool, wrappers map[st
}
// AddListener adds the specified listener to the project.
func (p *Project) AddListener(c chan<- Event) {
// 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<- Event{c}
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.
func (p *Project) Notify(eventType EventType, serviceName string, data map[string]string) {
if eventType == NoEvent {
// 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 := Event{
event := events.Event{
EventType: eventType,
ServiceName: serviceName,
Data: data,
+24
View File
@@ -0,0 +1,24 @@
package project
import (
"golang.org/x/net/context"
"github.com/docker/libcompose/project/events"
)
// Events listen for real time events from containers (of the project).
func (p *Project) Events(ctx context.Context, services ...string) (chan events.ContainerEvent, error) {
events := make(chan events.ContainerEvent)
if len(services) == 0 {
services = p.ServiceConfigs.Keys()
}
// FIXME(vdemeester) handle errors (chan) here
for _, service := range services {
s, err := p.CreateService(service)
if err != nil {
return nil, err
}
go s.Events(ctx, events)
}
return events, nil
}
+5 -4
View File
@@ -4,6 +4,7 @@ import (
"sync"
log "github.com/Sirupsen/logrus"
"github.com/docker/libcompose/project/events"
)
type serviceWrapper struct {
@@ -55,7 +56,7 @@ func (s *serviceWrapper) Ignore() {
defer s.done.Done()
s.state = StateExecuted
s.project.Notify(EventServiceUpIgnored, s.service.Name(), nil)
s.project.Notify(events.ServiceUpIgnored, s.service.Name(), nil)
}
func (s *serviceWrapper) waitForDeps(wrappers map[string]*serviceWrapper) bool {
@@ -70,7 +71,7 @@ func (s *serviceWrapper) waitForDeps(wrappers map[string]*serviceWrapper) bool {
if wrapper, ok := wrappers[dep.Target]; ok {
if wrapper.Wait() == ErrRestart {
s.project.Notify(EventProjectReload, wrapper.service.Name(), nil)
s.project.Notify(events.ProjectReload, wrapper.service.Name(), nil)
s.err = ErrRestart
return false
}
@@ -82,7 +83,7 @@ func (s *serviceWrapper) waitForDeps(wrappers map[string]*serviceWrapper) bool {
return true
}
func (s *serviceWrapper) Do(wrappers map[string]*serviceWrapper, start, done EventType, action func(service Service) error) {
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 {
@@ -100,7 +101,7 @@ func (s *serviceWrapper) Do(wrappers map[string]*serviceWrapper, start, done Eve
s.err = action(s.service)
if s.err == ErrRestart {
s.project.Notify(done, s.service.Name(), nil)
s.project.Notify(EventProjectReloadTrigger, 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 {
+97
View File
@@ -0,0 +1,97 @@
package project
import (
"errors"
"golang.org/x/net/context"
"github.com/docker/libcompose/config"
"github.com/docker/libcompose/project/events"
"github.com/docker/libcompose/project/options"
)
// Service defines what a libcompose service provides.
type Service interface {
Build(ctx context.Context, buildOptions options.Build) error
Create(ctx context.Context, options options.Create) error
Delete(ctx context.Context, options options.Delete) error
Events(ctx context.Context, messages chan events.ContainerEvent) error
Info(ctx context.Context, qFlag bool) (InfoSet, error)
Log(ctx context.Context, follow bool) error
Kill(ctx context.Context, signal string) error
Pause(ctx context.Context) error
Pull(ctx context.Context) error
Restart(ctx context.Context, timeout int) error
Run(ctx context.Context, commandParts []string, options options.Run) (int, error)
Scale(ctx context.Context, count int, timeout int) error
Start(ctx context.Context) error
Stop(ctx context.Context, timeout int) error
Unpause(ctx context.Context) error
Up(ctx context.Context, options options.Up) error
RemoveImage(ctx context.Context, imageType options.ImageType) error
Containers(ctx context.Context) ([]Container, error)
DependentServices() []ServiceRelationship
Config() *config.ServiceConfig
Name() string
}
// 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")
// RelTypeDependsOn means the dependency was explicitly set using 'depends_on'.
const RelTypeDependsOn = ServiceRelationshipType("dependsOn")
// RelTypeNetworkMode means the services depends on another service on networkMode
const RelTypeNetworkMode = ServiceRelationshipType("networkMode")
// 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,
}
}
-305
View File
@@ -1,305 +0,0 @@
package project
import "fmt"
// EventType defines a type of libcompose event.
type EventType int
// Definitions of libcompose events
const (
NoEvent = EventType(iota)
EventContainerCreated = EventType(iota)
EventContainerStarted = EventType(iota)
EventServiceAdd = EventType(iota)
EventServiceUpStart = EventType(iota)
EventServiceUpIgnored = EventType(iota)
EventServiceUp = EventType(iota)
EventServiceCreateStart = EventType(iota)
EventServiceCreate = EventType(iota)
EventServiceDeleteStart = EventType(iota)
EventServiceDelete = EventType(iota)
EventServiceDownStart = EventType(iota)
EventServiceDown = EventType(iota)
EventServiceRestartStart = EventType(iota)
EventServiceRestart = EventType(iota)
EventServicePullStart = EventType(iota)
EventServicePull = EventType(iota)
EventServiceKillStart = EventType(iota)
EventServiceKill = EventType(iota)
EventServiceStartStart = EventType(iota)
EventServiceStart = EventType(iota)
EventServiceBuildStart = EventType(iota)
EventServiceBuild = EventType(iota)
EventServicePauseStart = EventType(iota)
EventServicePause = EventType(iota)
EventServiceUnpauseStart = EventType(iota)
EventServiceUnpause = EventType(iota)
EventProjectDownStart = EventType(iota)
EventProjectDownDone = EventType(iota)
EventProjectCreateStart = EventType(iota)
EventProjectCreateDone = EventType(iota)
EventProjectUpStart = EventType(iota)
EventProjectUpDone = EventType(iota)
EventProjectDeleteStart = EventType(iota)
EventProjectDeleteDone = EventType(iota)
EventProjectRestartStart = EventType(iota)
EventProjectRestartDone = EventType(iota)
EventProjectReload = EventType(iota)
EventProjectReloadTrigger = EventType(iota)
EventProjectKillStart = EventType(iota)
EventProjectKillDone = EventType(iota)
EventProjectStartStart = EventType(iota)
EventProjectStartDone = EventType(iota)
EventProjectBuildStart = EventType(iota)
EventProjectBuildDone = EventType(iota)
EventProjectPauseStart = EventType(iota)
EventProjectPauseDone = EventType(iota)
EventProjectUnpauseStart = EventType(iota)
EventProjectUnpauseDone = EventType(iota)
)
func (e EventType) String() string {
var m string
switch e {
case EventContainerCreated:
m = "Created container"
case EventContainerStarted:
m = "Started container"
case EventServiceAdd:
m = "Adding"
case EventServiceUpStart:
m = "Starting"
case EventServiceUpIgnored:
m = "Ignoring"
case EventServiceUp:
m = "Started"
case EventServiceCreateStart:
m = "Creating"
case EventServiceCreate:
m = "Created"
case EventServiceDeleteStart:
m = "Deleting"
case EventServiceDelete:
m = "Deleted"
case EventServiceDownStart:
m = "Stopping"
case EventServiceDown:
m = "Stopped"
case EventServiceRestartStart:
m = "Restarting"
case EventServiceRestart:
m = "Restarted"
case EventServicePullStart:
m = "Pulling"
case EventServicePull:
m = "Pulled"
case EventServiceKillStart:
m = "Killing"
case EventServiceKill:
m = "Killed"
case EventServiceStartStart:
m = "Starting"
case EventServiceStart:
m = "Started"
case EventServiceBuildStart:
m = "Building"
case EventServiceBuild:
m = "Built"
case EventProjectDownStart:
m = "Stopping project"
case EventProjectDownDone:
m = "Project stopped"
case EventProjectCreateStart:
m = "Creating project"
case EventProjectCreateDone:
m = "Project created"
case EventProjectUpStart:
m = "Starting project"
case EventProjectUpDone:
m = "Project started"
case EventProjectDeleteStart:
m = "Deleting project"
case EventProjectDeleteDone:
m = "Project deleted"
case EventProjectRestartStart:
m = "Restarting project"
case EventProjectRestartDone:
m = "Project restarted"
case EventProjectReload:
m = "Reloading project"
case EventProjectReloadTrigger:
m = "Triggering project reload"
case EventProjectKillStart:
m = "Killing project"
case EventProjectKillDone:
m = "Project killed"
case EventProjectStartStart:
m = "Starting project"
case EventProjectStartDone:
m = "Project started"
case EventProjectBuildStart:
m = "Building project"
case EventProjectBuildDone:
m = "Project built"
}
if m == "" {
m = fmt.Sprintf("EventType: %d", int(e))
}
return m
}
// 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
// ServiceConfig holds libcompose service configuration
type ServiceConfig struct {
Build string `yaml:"build,omitempty"`
CapAdd []string `yaml:"cap_add,omitempty"`
CapDrop []string `yaml:"cap_drop,omitempty"`
CPUSet string `yaml:"cpuset,omitempty"`
CPUShares int64 `yaml:"cpu_shares,omitempty"`
Command Command `yaml:"command,flow,omitempty"`
ContainerName string `yaml:"container_name,omitempty"`
Devices []string `yaml:"devices,omitempty"`
DNS Stringorslice `yaml:"dns,omitempty"`
DNSSearch Stringorslice `yaml:"dns_search,omitempty"`
Dockerfile string `yaml:"dockerfile,omitempty"`
DomainName string `yaml:"domainname,omitempty"`
Entrypoint Command `yaml:"entrypoint,flow,omitempty"`
EnvFile Stringorslice `yaml:"env_file,omitempty"`
Environment MaporEqualSlice `yaml:"environment,omitempty"`
Hostname string `yaml:"hostname,omitempty"`
Image string `yaml:"image,omitempty"`
Labels SliceorMap `yaml:"labels,omitempty"`
Links MaporColonSlice `yaml:"links,omitempty"`
LogDriver string `yaml:"log_driver,omitempty"`
MemLimit int64 `yaml:"mem_limit,omitempty"`
MemSwapLimit int64 `yaml:"memswap_limit,omitempty"`
Name string `yaml:"name,omitempty"`
Net string `yaml:"net,omitempty"`
Pid string `yaml:"pid,omitempty"`
Uts string `yaml:"uts,omitempty"`
Ipc string `yaml:"ipc,omitempty"`
Ports []string `yaml:"ports,omitempty"`
Privileged bool `yaml:"privileged,omitempty"`
Restart string `yaml:"restart,omitempty"`
ReadOnly bool `yaml:"read_only,omitempty"`
StdinOpen bool `yaml:"stdin_open,omitempty"`
SecurityOpt []string `yaml:"security_opt,omitempty"`
Tty bool `yaml:"tty,omitempty"`
User string `yaml:"user,omitempty"`
VolumeDriver string `yaml:"volume_driver,omitempty"`
Volumes []string `yaml:"volumes,omitempty"`
VolumesFrom []string `yaml:"volumes_from,omitempty"`
WorkingDir string `yaml:"working_dir,omitempty"`
Expose []string `yaml:"expose,omitempty"`
ExternalLinks []string `yaml:"external_links,omitempty"`
LogOpt map[string]string `yaml:"log_opt,omitempty"`
ExtraHosts []string `yaml:"extra_hosts,omitempty"`
}
// EnvironmentLookup defines methods to provides environment variable loading.
type EnvironmentLookup interface {
Lookup(key, serviceName string, config *ServiceConfig) []string
}
// ConfigLookup defines methods to provides file loading.
type ConfigLookup interface {
Lookup(file, relativeTo string) ([]byte, string, error)
}
// Project holds libcompose project information.
type Project struct {
Name string
Configs map[string]*ServiceConfig
File string
ReloadCallback func() error
context *Context
reload []string
upCount int
listeners []chan<- Event
hasListeners bool
}
// Service defines what a libcompose service provides.
type Service interface {
Info(qFlag bool) (InfoSet, error)
Name() string
Build() error
Create() error
Up() error
Start() error
Down() error
Delete() error
Restart() error
Log() error
Pull() error
Kill() error
Config() *ServiceConfig
DependentServices() []ServiceRelationship
Containers() ([]Container, error)
Scale(count int) error
Pause() error
Unpause() error
}
// Container defines what a libcompose container provides.
type Container interface {
ID() (string, error)
Name() string
Port(port string) (string, error)
}
// 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 *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,
}
}
-328
View File
@@ -1,328 +0,0 @@
package project
import (
"fmt"
"strings"
"github.com/flynn/go-shlex"
)
// Stringorslice represents a string or an array of strings.
// TODO use docker/docker/pkg/stringutils.StrSlice once 1.9.x is released.
type Stringorslice struct {
parts []string
}
// MarshalYAML implements the Marshaller interface.
func (s Stringorslice) MarshalYAML() (tag string, value interface{}, err error) {
return "", s.parts, nil
}
func toStrings(s []interface{}) ([]string, error) {
if len(s) == 0 {
return nil, nil
}
r := make([]string, len(s))
for k, v := range s {
if sv, ok := v.(string); ok {
r[k] = sv
} else {
return nil, fmt.Errorf("Cannot unmarshal '%v' of type %T into a string value", v, v)
}
}
return r, nil
}
// UnmarshalYAML implements the Unmarshaller interface.
func (s *Stringorslice) UnmarshalYAML(tag string, value interface{}) error {
switch value := value.(type) {
case []interface{}:
parts, err := toStrings(value)
if err != nil {
return err
}
s.parts = parts
case string:
s.parts = []string{value}
default:
return fmt.Errorf("Failed to unmarshal Stringorslice: %#v", value)
}
return nil
}
// Len returns the number of parts of the Stringorslice.
func (s *Stringorslice) Len() int {
if s == nil {
return 0
}
return len(s.parts)
}
// Slice gets the parts of the StrSlice as a Slice of string.
func (s *Stringorslice) Slice() []string {
if s == nil {
return nil
}
return s.parts
}
// NewStringorslice creates an Stringorslice based on the specified parts (as strings).
func NewStringorslice(parts ...string) Stringorslice {
return Stringorslice{parts}
}
// Command represents a docker command, can be a string or an array of strings.
// FIXME why not use Stringorslice (type Command struct { Stringorslice }
type Command struct {
parts []string
}
// MarshalYAML implements the Marshaller interface.
func (s Command) MarshalYAML() (tag string, value interface{}, err error) {
return "", s.parts, nil
}
// UnmarshalYAML implements the Unmarshaller interface.
func (s *Command) UnmarshalYAML(tag string, value interface{}) error {
switch value := value.(type) {
case []interface{}:
parts, err := toStrings(value)
if err != nil {
return err
}
s.parts = parts
case string:
parts, err := shlex.Split(value)
if err != nil {
return err
}
s.parts = parts
default:
return fmt.Errorf("Failed to unmarshal Command: %#v", value)
}
return nil
}
// ToString returns the parts of the command as a string (joined by spaces).
func (s *Command) ToString() string {
return strings.Join(s.parts, " ")
}
// Slice gets the parts of the Command as a Slice of string.
func (s *Command) Slice() []string {
return s.parts
}
// NewCommand create a Command based on the specified parts (as strings).
func NewCommand(parts ...string) Command {
return Command{parts}
}
// SliceorMap represents a slice or a map of strings.
type SliceorMap struct {
parts map[string]string
}
// MarshalYAML implements the Marshaller interface.
func (s SliceorMap) MarshalYAML() (tag string, value interface{}, err error) {
return "", s.parts, nil
}
// UnmarshalYAML implements the Unmarshaller interface.
func (s *SliceorMap) UnmarshalYAML(tag string, value interface{}) error {
switch value := value.(type) {
case map[interface{}]interface{}:
parts := map[string]string{}
for k, v := range value {
if sk, ok := k.(string); ok {
if sv, ok := v.(string); ok {
parts[sk] = sv
} else {
return fmt.Errorf("Cannot unmarshal '%v' of type %T into a string value", v, v)
}
} else {
return fmt.Errorf("Cannot unmarshal '%v' of type %T into a string value", k, k)
}
}
s.parts = parts
case []interface{}:
parts := map[string]string{}
for _, s := range value {
if str, ok := s.(string); ok {
str := strings.TrimSpace(str)
keyValueSlice := strings.SplitN(str, "=", 2)
key := keyValueSlice[0]
val := ""
if len(keyValueSlice) == 2 {
val = keyValueSlice[1]
}
parts[key] = val
} else {
return fmt.Errorf("Cannot unmarshal '%v' of type %T into a string value", s, s)
}
}
s.parts = parts
default:
return fmt.Errorf("Failed to unmarshal SliceorMap: %#v", value)
}
return nil
}
// MapParts get the parts of the SliceorMap as a Map of string.
func (s *SliceorMap) MapParts() map[string]string {
if s == nil {
return nil
}
return s.parts
}
// NewSliceorMap creates a new SliceorMap based on the specified parts (as map of string).
func NewSliceorMap(parts map[string]string) SliceorMap {
return SliceorMap{parts}
}
// MaporEqualSlice represents a slice of strings that gets unmarshal from a
// YAML map into 'key=value' string.
type MaporEqualSlice struct {
parts []string
}
// MarshalYAML implements the Marshaller interface.
func (s MaporEqualSlice) MarshalYAML() (tag string, value interface{}, err error) {
return "", s.parts, nil
}
func toSepMapParts(value map[interface{}]interface{}, sep string) ([]string, error) {
if len(value) == 0 {
return nil, nil
}
parts := make([]string, 0, len(value))
for k, v := range value {
if sk, ok := k.(string); ok {
if sv, ok := v.(string); ok {
parts = append(parts, sk+sep+sv)
} else {
return nil, fmt.Errorf("Cannot unmarshal '%v' of type %T into a string value", v, v)
}
} else {
return nil, fmt.Errorf("Cannot unmarshal '%v' of type %T into a string value", k, k)
}
}
return parts, nil
}
// UnmarshalYAML implements the Unmarshaller interface.
func (s *MaporEqualSlice) UnmarshalYAML(tag string, value interface{}) error {
switch value := value.(type) {
case []interface{}:
parts, err := toStrings(value)
if err != nil {
return err
}
s.parts = parts
case map[interface{}]interface{}:
parts, err := toSepMapParts(value, "=")
if err != nil {
return err
}
s.parts = parts
default:
return fmt.Errorf("Failed to unmarshal MaporEqualSlice: %#v", value)
}
return nil
}
// Slice gets the parts of the MaporEqualSlice as a Slice of string.
func (s *MaporEqualSlice) Slice() []string {
return s.parts
}
// NewMaporEqualSlice creates a new MaporEqualSlice based on the specified parts.
func NewMaporEqualSlice(parts []string) MaporEqualSlice {
return MaporEqualSlice{parts}
}
// MaporColonSlice represents a slice of strings that gets unmarshal from a
// YAML map into 'key:value' string.
type MaporColonSlice struct {
parts []string
}
// MarshalYAML implements the Marshaller interface.
func (s MaporColonSlice) MarshalYAML() (tag string, value interface{}, err error) {
return "", s.parts, nil
}
// UnmarshalYAML implements the Unmarshaller interface.
func (s *MaporColonSlice) UnmarshalYAML(tag string, value interface{}) error {
switch value := value.(type) {
case []interface{}:
parts, err := toStrings(value)
if err != nil {
return err
}
s.parts = parts
case map[interface{}]interface{}:
parts, err := toSepMapParts(value, ":")
if err != nil {
return err
}
s.parts = parts
default:
return fmt.Errorf("Failed to unmarshal MaporColonSlice: %#v", value)
}
return nil
}
// Slice gets the parts of the MaporColonSlice as a Slice of string.
func (s *MaporColonSlice) Slice() []string {
return s.parts
}
// NewMaporColonSlice creates a new MaporColonSlice based on the specified parts.
func NewMaporColonSlice(parts []string) MaporColonSlice {
return MaporColonSlice{parts}
}
// MaporSpaceSlice represents a slice of strings that gets unmarshal from a
// YAML map into 'key value' string.
type MaporSpaceSlice struct {
parts []string
}
// MarshalYAML implements the Marshaller interface.
func (s MaporSpaceSlice) MarshalYAML() (tag string, value interface{}, err error) {
return "", s.parts, nil
}
// UnmarshalYAML implements the Unmarshaller interface.
func (s *MaporSpaceSlice) UnmarshalYAML(tag string, value interface{}) error {
switch value := value.(type) {
case []interface{}:
parts, err := toStrings(value)
if err != nil {
return err
}
s.parts = parts
case map[interface{}]interface{}:
parts, err := toSepMapParts(value, " ")
if err != nil {
return err
}
s.parts = parts
default:
return fmt.Errorf("Failed to unmarshal MaporSpaceSlice: %#v", value)
}
return nil
}
// Slice gets the parts of the MaporSpaceSlice as a Slice of string.
func (s *MaporSpaceSlice) Slice() []string {
return s.parts
}
// NewMaporSpaceSlice creates a new MaporSpaceSlice based on the specified parts.
func NewMaporSpaceSlice(parts []string) MaporSpaceSlice {
return MaporSpaceSlice{parts}
}
+11 -32
View File
@@ -2,8 +2,6 @@ package project
import (
"strings"
"github.com/docker/docker/runconfig"
)
// DefaultDependentServices return the dependent services (as an array of ServiceRelationship)
@@ -15,7 +13,7 @@ func DefaultDependentServices(p *Project, s Service) []ServiceRelationship {
}
result := []ServiceRelationship{}
for _, link := range config.Links.Slice() {
for _, link := range config.Links {
result = append(result, NewServiceRelationship(link, RelTypeLink))
}
@@ -23,20 +21,20 @@ func DefaultDependentServices(p *Project, s Service) []ServiceRelationship {
result = append(result, NewServiceRelationship(volumesFrom, RelTypeVolumesFrom))
}
result = appendNs(p, result, s.Config().Net, RelTypeNetNamespace)
result = appendNs(p, result, s.Config().Ipc, RelTypeIpcNamespace)
for _, dependsOn := range config.DependsOn {
result = append(result, NewServiceRelationship(dependsOn, RelTypeDependsOn))
}
if config.NetworkMode != "" {
if strings.HasPrefix(config.NetworkMode, "service:") {
serviceName := config.NetworkMode[8:]
result = append(result, NewServiceRelationship(serviceName, RelTypeNetworkMode))
}
}
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.
@@ -47,22 +45,3 @@ func NameAlias(name string) (string, string) {
}
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 := runconfig.IpcMode(conf)
if !ipc.IsContainer() {
return ""
}
name := ipc.Container()
if name == "" {
return ""
}
if _, ok := p.Configs[name]; ok {
return name
}
return ""
}