1
0
mirror of https://github.com/rancher/os.git synced 2025-09-04 16:21:07 +00:00

move dependencies to vendor

This commit is contained in:
Ivan Mikushin
2015-11-26 17:37:01 +05:00
parent 63d7de67cd
commit 1d691cd8d6
2232 changed files with 154499 additions and 9037 deletions

136
vendor/github.com/docker/libcompose/project/context.go generated vendored Normal file
View File

@@ -0,0 +1,136 @@
package project
import (
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"regexp"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/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 {
Timeout uint
Log bool
Volume bool
ForceRecreate bool
NoRecreate bool
Signal int
ComposeFile string
ComposeBytes []byte
ProjectName string
isOpen bool
ServiceFactory ServiceFactory
EnvironmentLookup EnvironmentLookup
ConfigLookup ConfigLookup
LoggerFactory logger.Factory
IgnoreMissingConfig bool
Project *Project
}
func (c *Context) readComposeFile() error {
if c.ComposeBytes != nil {
return nil
}
logrus.Debugf("Opening compose file: %s", c.ComposeFile)
if c.ComposeFile == "-" {
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)
return err
} else if err != nil {
logrus.Errorf("Failed to open %s", c.ComposeFile)
return err
} else {
c.ComposeBytes = composeBytes
}
}
return nil
}
func (c *Context) determineProject() error {
name, err := c.lookupProjectName()
if err != nil {
return err
}
c.ProjectName = projectRegexp.ReplaceAllString(strings.ToLower(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
}
func (c *Context) lookupProjectName() (string, error) {
if c.ProjectName != "" {
return c.ProjectName, nil
}
if envProject := os.Getenv("COMPOSE_PROJECT_NAME"); envProject != "" {
return envProject, nil
}
f, err := filepath.Abs(c.ComposeFile)
if err != nil {
logrus.Errorf("Failed to get absolute directory for: %s", c.ComposeFile)
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 toUnixPath(p string) string {
return strings.Replace(p, "\\", "/", -1)
}
func (c *Context) open() error {
if c.isOpen {
return nil
}
if err := c.readComposeFile(); err != nil {
return err
}
if err := c.determineProject(); err != nil {
return err
}
c.isOpen = true
return nil
}

70
vendor/github.com/docker/libcompose/project/empty.go generated vendored Normal file
View File

@@ -0,0 +1,70 @@
package project
// 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 {
return nil
}
// Build implements Service.Build but does nothing.
func (e *EmptyService) Build() error {
return nil
}
// Up implements Service.Up but does nothing.
func (e *EmptyService) Up() error {
return nil
}
// Start implements Service.Start but does nothing.
func (e *EmptyService) Start() error {
return nil
}
// Down implements Service.Down but does nothing.
func (e *EmptyService) Down() error {
return nil
}
// Delete implements Service.Delete but does nothing.
func (e *EmptyService) Delete() error {
return nil
}
// Restart implements Service.Restart but does nothing.
func (e *EmptyService) Restart() error {
return nil
}
// Log implements Service.Log but does nothing.
func (e *EmptyService) Log() 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() 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) error {
return nil
}
// Info implements Service.Info but does nothing.
func (e *EmptyService) Info(qFlag bool) (InfoSet, error) {
return InfoSet{}, nil
}

106
vendor/github.com/docker/libcompose/project/hash.go generated vendored Normal file
View File

@@ -0,0 +1,106 @@
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))
}

42
vendor/github.com/docker/libcompose/project/info.go generated vendored Normal file
View File

@@ -0,0 +1,42 @@
package project
import (
"bytes"
"io"
"text/tabwriter"
)
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,179 @@
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
}

View File

@@ -0,0 +1,226 @@
package project
import (
"fmt"
"os"
"testing"
yaml "github.com/cloudfoundry-incubator/candiedyaml"
"github.com/stretchr/testify/assert"
)
func testInterpolatedLine(t *testing.T, expectedLine, interpolatedLine string, envVariables map[string]string) {
interpolatedLine, _ = parseLine(interpolatedLine, func(s string) string {
return envVariables[s]
})
assert.Equal(t, expectedLine, interpolatedLine)
}
func testInvalidInterpolatedLine(t *testing.T, line string) {
_, success := parseLine(line, func(string) string {
return ""
})
assert.Equal(t, false, success)
}
func TestParseLine(t *testing.T) {
variables := map[string]string{
"A": "ABC",
"X": "XYZ",
"E": "",
"lower": "WORKED",
"MiXeD": "WORKED",
"split_VaLue": "WORKED",
"9aNumber": "WORKED",
"a9Number": "WORKED",
}
testInterpolatedLine(t, "WORKED", "$lower", variables)
testInterpolatedLine(t, "WORKED", "${MiXeD}", variables)
testInterpolatedLine(t, "WORKED", "${split_VaLue}", variables)
// Starting with a number isn't valid
testInterpolatedLine(t, "", "$9aNumber", variables)
testInterpolatedLine(t, "WORKED", "$a9Number", variables)
testInterpolatedLine(t, "ABC", "$A", variables)
testInterpolatedLine(t, "ABC", "${A}", variables)
testInterpolatedLine(t, "ABC DE", "$A DE", variables)
testInterpolatedLine(t, "ABCDE", "${A}DE", variables)
testInterpolatedLine(t, "$A", "$$A", variables)
testInterpolatedLine(t, "${A}", "$${A}", variables)
testInterpolatedLine(t, "$ABC", "$$${A}", variables)
testInterpolatedLine(t, "$ABC", "$$$A", variables)
testInterpolatedLine(t, "ABC XYZ", "$A $X", variables)
testInterpolatedLine(t, "ABCXYZ", "$A$X", variables)
testInterpolatedLine(t, "ABCXYZ", "${A}${X}", variables)
testInterpolatedLine(t, "", "$B", variables)
testInterpolatedLine(t, "", "${B}", variables)
testInterpolatedLine(t, "", "$ADE", variables)
testInterpolatedLine(t, "", "$E", variables)
testInterpolatedLine(t, "", "${E}", variables)
testInvalidInterpolatedLine(t, "${")
testInvalidInterpolatedLine(t, "$}")
testInvalidInterpolatedLine(t, "${}")
testInvalidInterpolatedLine(t, "${ }")
testInvalidInterpolatedLine(t, "${A }")
testInvalidInterpolatedLine(t, "${ A}")
testInvalidInterpolatedLine(t, "${A!}")
testInvalidInterpolatedLine(t, "$!")
}
type MockEnvironmentLookup struct {
Variables map[string]string
}
func (m MockEnvironmentLookup) Lookup(key, serviceName string, config *ServiceConfig) []string {
return []string{fmt.Sprintf("%s=%s", key, m.Variables[key])}
}
func testInterpolatedConfig(t *testing.T, expectedConfig, interpolatedConfig string, envVariables map[string]string) {
for k, v := range envVariables {
os.Setenv(k, v)
}
expectedConfigBytes := []byte(expectedConfig)
interpolatedConfigBytes := []byte(interpolatedConfig)
expectedData := make(rawServiceMap)
interpolatedData := make(rawServiceMap)
yaml.Unmarshal(expectedConfigBytes, &expectedData)
yaml.Unmarshal(interpolatedConfigBytes, &interpolatedData)
_ = interpolate(MockEnvironmentLookup{envVariables}, &interpolatedData)
for k := range envVariables {
os.Unsetenv(k)
}
assert.Equal(t, expectedData, interpolatedData)
}
func testInvalidInterpolatedConfig(t *testing.T, interpolatedConfig string) {
interpolatedConfigBytes := []byte(interpolatedConfig)
interpolatedData := make(rawServiceMap)
yaml.Unmarshal(interpolatedConfigBytes, &interpolatedData)
err := interpolate(new(MockEnvironmentLookup), &interpolatedData)
assert.NotNil(t, err)
}
func TestInterpolate(t *testing.T) {
testInterpolatedConfig(t,
`web:
# unbracketed name
image: busybox
# array element
ports:
- "80:8000"
# dictionary item value
labels:
mylabel: "myvalue"
# unset value
hostname: "host-"
# escaped interpolation
command: "${ESCAPED}"`,
`web:
# unbracketed name
image: $IMAGE
# array element
ports:
- "${HOST_PORT}:8000"
# dictionary item value
labels:
mylabel: "${LABEL_VALUE}"
# unset value
hostname: "host-${UNSET_VALUE}"
# escaped interpolation
command: "$${ESCAPED}"`, map[string]string{
"IMAGE": "busybox",
"HOST_PORT": "80",
"LABEL_VALUE": "myvalue",
})
// Same as above, but testing with equal signs in variables
testInterpolatedConfig(t,
`web:
# unbracketed name
image: =busybox
# array element
ports:
- "=:8000"
# dictionary item value
labels:
mylabel: "myvalue=="
# unset value
hostname: "host-"
# escaped interpolation
command: "${ESCAPED}"`,
`web:
# unbracketed name
image: $IMAGE
# array element
ports:
- "${HOST_PORT}:8000"
# dictionary item value
labels:
mylabel: "${LABEL_VALUE}"
# unset value
hostname: "host-${UNSET_VALUE}"
# escaped interpolation
command: "$${ESCAPED}"`, map[string]string{
"IMAGE": "=busybox",
"HOST_PORT": "=",
"LABEL_VALUE": "myvalue==",
})
testInvalidInterpolatedConfig(t,
`web:
image: "${"`)
testInvalidInterpolatedConfig(t,
`web:
image: busybox
# array element
ports:
- "${}:8000"`)
testInvalidInterpolatedConfig(t,
`web:
image: busybox
# array element
ports:
- "80:8000"
# dictionary item value
labels:
mylabel: "${ LABEL_VALUE}"`)
}

View File

@@ -0,0 +1,76 @@
package project
import (
"bytes"
"github.com/Sirupsen/logrus"
)
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,
}
)
type defaultListener struct {
project *Project
listenChan chan Event
upCount int
}
// NewDefaultListener create a default listener for the specified project.
func NewDefaultListener(p *Project) chan<- Event {
l := defaultListener{
listenChan: make(chan 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 == EventServiceUp {
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, len(d.project.Configs), event.ServiceName, event.EventType, buffer.Bytes())
}
}
}

299
vendor/github.com/docker/libcompose/project/merge.go generated vendored Normal file
View File

@@ -0,0 +1,299 @@
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 ""
}

View File

@@ -0,0 +1,192 @@
package project
import "testing"
type NullLookup struct {
}
func (n *NullLookup) Lookup(file, relativeTo string) ([]byte, string, error) {
return nil, "", nil
}
func TestExtendsInheritImage(t *testing.T) {
p := NewProject(&Context{
ConfigLookup: &NullLookup{},
})
config, err := mergeProject(p, []byte(`
parent:
image: foo
child:
extends:
service: parent
`))
if err != nil {
t.Fatal(err)
}
parent := config["parent"]
child := config["child"]
if parent.Image != "foo" {
t.Fatal("Invalid image", parent.Image)
}
if child.Build != "" {
t.Fatal("Invalid build", child.Build)
}
if child.Image != "foo" {
t.Fatal("Invalid image", child.Image)
}
}
func TestExtendsInheritBuild(t *testing.T) {
p := NewProject(&Context{
ConfigLookup: &NullLookup{},
})
config, err := mergeProject(p, []byte(`
parent:
build: .
child:
extends:
service: parent
`))
if err != nil {
t.Fatal(err)
}
parent := config["parent"]
child := config["child"]
if parent.Build != "." {
t.Fatal("Invalid build", parent.Build)
}
if child.Build != "." {
t.Fatal("Invalid build", child.Build)
}
if child.Image != "" {
t.Fatal("Invalid image", child.Image)
}
}
func TestExtendBuildOverImage(t *testing.T) {
p := NewProject(&Context{
ConfigLookup: &NullLookup{},
})
config, err := mergeProject(p, []byte(`
parent:
image: foo
child:
build: .
extends:
service: parent
`))
if err != nil {
t.Fatal(err)
}
parent := config["parent"]
child := config["child"]
if parent.Image != "foo" {
t.Fatal("Invalid image", parent.Image)
}
if child.Build != "." {
t.Fatal("Invalid build", child.Build)
}
if child.Image != "" {
t.Fatal("Invalid image", child.Image)
}
}
func TestExtendImageOverBuild(t *testing.T) {
p := NewProject(&Context{
ConfigLookup: &NullLookup{},
})
config, err := mergeProject(p, []byte(`
parent:
build: .
child:
image: foo
extends:
service: parent
`))
if err != nil {
t.Fatal(err)
}
parent := config["parent"]
child := config["child"]
if parent.Image != "" {
t.Fatal("Invalid image", parent.Image)
}
if parent.Build != "." {
t.Fatal("Invalid build", parent.Build)
}
if child.Build != "" {
t.Fatal("Invalid build", child.Build)
}
if child.Image != "foo" {
t.Fatal("Invalid image", child.Image)
}
}
func TestRestartNo(t *testing.T) {
p := NewProject(&Context{
ConfigLookup: &NullLookup{},
})
config, err := mergeProject(p, []byte(`
test:
restart: no
image: foo
`))
if err != nil {
t.Fatal(err)
}
test := config["test"]
if test.Restart != "no" {
t.Fatal("Invalid restart policy", test.Restart)
}
}
func TestRestartAlways(t *testing.T) {
p := NewProject(&Context{
ConfigLookup: &NullLookup{},
})
config, err := mergeProject(p, []byte(`
test:
restart: always
image: foo
`))
if err != nil {
t.Fatal(err)
}
test := config["test"]
if test.Restart != "always" {
t.Fatal("Invalid restart policy", test.Restart)
}
}

410
vendor/github.com/docker/libcompose/project/project.go generated vendored Normal file
View File

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

View File

@@ -0,0 +1,148 @@
package project
import (
"fmt"
"reflect"
"strings"
"testing"
)
type TestServiceFactory struct {
Counts map[string]int
}
type TestService struct {
factory *TestServiceFactory
name string
config *ServiceConfig
EmptyService
Count int
}
func (t *TestService) Config() *ServiceConfig {
return t.config
}
func (t *TestService) Name() string {
return t.name
}
func (t *TestService) 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 *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(&Context{
ServiceFactory: factory,
})
p.Configs = map[string]*ServiceConfig{
"foo": {},
}
if err := p.Create("foo"); err != nil {
t.Fatal(err)
}
if err := p.Create("foo"); err != nil {
t.Fatal(err)
}
if factory.Counts["foo.create"] != 2 {
t.Fatal("Failed to create twice")
}
}
func TestEventEquality(t *testing.T) {
if fmt.Sprintf("%s", EventServiceStart) != "Started" ||
fmt.Sprintf("%v", EventServiceStart) != "Started" {
t.Fatalf("EventServiceStart String() doesn't work: %s %v", EventServiceStart, EventServiceStart)
}
if fmt.Sprintf("%s", EventServiceStart) != fmt.Sprintf("%s", EventServiceUp) {
t.Fatal("Event messages do not match")
}
if EventServiceStart == EventServiceUp {
t.Fatal("Events match")
}
}
func TestParseWithBadContent(t *testing.T) {
p := NewProject(&Context{
ComposeBytes: []byte("garbage"),
})
err := p.Parse()
if err == nil {
t.Fatal("Should have failed parse")
}
if !strings.HasPrefix(err.Error(), "Unknown resolution for 'garbage'") {
t.Fatalf("Should have failed parse: %#v", err)
}
}
func TestParseWithGoodContent(t *testing.T) {
p := NewProject(&Context{
ComposeBytes: []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 *ServiceConfig) []string {
return []string{fmt.Sprintf("%s=X", key)}
}
func TestEnvironmentResolve(t *testing.T) {
factory := &TestServiceFactory{
Counts: map[string]int{},
}
p := NewProject(&Context{
ServiceFactory: factory,
EnvironmentLookup: &TestEnvironmentLookup{},
})
p.Configs = map[string]*ServiceConfig{
"foo": {
Environment: NewMaporEqualSlice([]string{
"A",
"A=",
"A=B",
}),
},
}
service, err := p.CreateService("foo")
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(service.Config().Environment.Slice(), []string{"A=X", "A=X", "A=B"}) {
t.Fatal("Invalid environment", service.Config().Environment.Slice())
}
}

View File

@@ -0,0 +1,114 @@
package project
import (
"sync"
log "github.com/Sirupsen/logrus"
)
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(EventServiceUpIgnored, 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(EventProjectReload, 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 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(EventProjectReloadTrigger, 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
}

295
vendor/github.com/docker/libcompose/project/types.go generated vendored Normal file
View File

@@ -0,0 +1,295 @@
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)
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)
)
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
}
// 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,
}
}

View File

@@ -0,0 +1,297 @@
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) {
if s.parts == nil {
return "", []string{}, nil
}
return "", s.parts, nil
}
// UnmarshalYAML implements the Unmarshaller interface.
func (s *Stringorslice) UnmarshalYAML(tag string, value interface{}) error {
switch value := value.(type) {
case []interface{}:
parts := make([]string, len(value))
for k, v := range value {
parts[k] = v.(string)
}
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) {
if s.parts == nil {
return "", []string{}, nil
}
return "", s.parts, nil
}
// UnmarshalYAML implements the Unmarshaller interface.
func (s *Command) UnmarshalYAML(tag string, value interface{}) error {
var err error
switch value := value.(type) {
case []interface{}:
parts := make([]string, len(value))
for k, v := range value {
parts[k] = v.(string)
}
s.parts = parts
case string:
s.parts, err = shlex.Split(value)
default:
return fmt.Errorf("Failed to unmarshal Command: %#v", value)
}
return err
}
// 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) {
if s.parts == nil {
return "", map[string]string{}, nil
}
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 {
parts[k.(string)] = v.(string)
}
s.parts = parts
case []interface{}:
parts := map[string]string{}
for _, str := range value {
str := strings.TrimSpace(str.(string))
keyValueSlice := strings.SplitN(str, "=", 2)
key := keyValueSlice[0]
val := ""
if len(keyValueSlice) == 2 {
val = keyValueSlice[1]
}
parts[key] = val
}
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) {
if s.parts == nil {
return "", []string{}, nil
}
return "", s.parts, nil
}
// UnmarshalYAML implements the Unmarshaller interface.
func (s *MaporEqualSlice) UnmarshalYAML(tag string, value interface{}) error {
switch value := value.(type) {
case []interface{}:
parts := make([]string, len(value))
for k, v := range value {
parts[k] = v.(string)
}
s.parts = parts
case map[interface{}]interface{}:
parts := make([]string, 0, len(value))
for k, v := range value {
parts = append(parts, strings.Join([]string{k.(string), v.(string)}, "="))
}
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) {
if s.parts == nil {
return "", []string{}, nil
}
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 := make([]string, len(value))
for k, v := range value {
parts[k] = v.(string)
}
s.parts = parts
case map[interface{}]interface{}:
parts := make([]string, 0, len(value))
for k, v := range value {
parts = append(parts, strings.Join([]string{k.(string), v.(string)}, ":"))
}
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) {
if s.parts == nil {
return "", []string{}, nil
}
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 := make([]string, len(value))
for k, v := range value {
parts[k] = v.(string)
}
s.parts = parts
case map[interface{}]interface{}:
parts := make([]string, 0, len(value))
for k, v := range value {
parts = append(parts, strings.Join([]string{k.(string), v.(string)}, " "))
}
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}
}

View File

@@ -0,0 +1,194 @@
package project
import (
"testing"
yaml "github.com/cloudfoundry-incubator/candiedyaml"
"github.com/stretchr/testify/assert"
)
type StructStringorslice struct {
Foo Stringorslice
}
type TestConfig struct {
SystemContainers map[string]*ServiceConfig
}
func newTestConfig() TestConfig {
return TestConfig{
SystemContainers: map[string]*ServiceConfig{
"udev": {
Image: "udev",
Entrypoint: Command{[]string{}},
Command: Command{[]string{}},
Restart: "always",
Net: "host",
Privileged: true,
DNS: Stringorslice{[]string{"8.8.8.8", "8.8.4.4"}},
DNSSearch: Stringorslice{[]string{}},
EnvFile: Stringorslice{[]string{}},
Environment: MaporEqualSlice{[]string{
"DAEMON=true",
}},
Labels: SliceorMap{map[string]string{
"io.rancher.os.detach": "true",
"io.rancher.os.scope": "system",
}},
Links: MaporColonSlice{[]string{}},
VolumesFrom: []string{
"system-volumes",
},
},
"system-volumes": {
Image: "state",
Entrypoint: Command{[]string{}},
Command: Command{[]string{}},
Net: "none",
ReadOnly: true,
Privileged: true,
DNS: Stringorslice{[]string{}},
DNSSearch: Stringorslice{[]string{}},
EnvFile: Stringorslice{[]string{}},
Environment: MaporEqualSlice{[]string{}},
Labels: SliceorMap{map[string]string{
"io.rancher.os.createonly": "true",
"io.rancher.os.scope": "system",
}},
Links: MaporColonSlice{[]string{}},
Volumes: []string{
"/dev:/host/dev",
"/var/lib/rancher/conf:/var/lib/rancher/conf",
"/etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt.rancher",
"/lib/modules:/lib/modules",
"/lib/firmware:/lib/firmware",
"/var/run:/var/run",
"/var/log:/var/log",
},
LogDriver: "json-file",
},
},
}
}
func TestMarshalConfig(t *testing.T) {
config := newTestConfig()
bytes, err := yaml.Marshal(config)
assert.Nil(t, err)
config2 := TestConfig{}
err = yaml.Unmarshal(bytes, &config2)
assert.Nil(t, err)
assert.Equal(t, config, config2)
}
func TestMarshalServiceConfig(t *testing.T) {
configPtr := newTestConfig().SystemContainers["udev"]
bytes, err := yaml.Marshal(configPtr)
assert.Nil(t, err)
configPtr2 := &ServiceConfig{}
err = yaml.Unmarshal(bytes, configPtr2)
assert.Nil(t, err)
assert.Equal(t, configPtr, configPtr2)
}
func TestStringorsliceYaml(t *testing.T) {
str := `{foo: [bar, baz]}`
s := StructStringorslice{}
yaml.Unmarshal([]byte(str), &s)
assert.Equal(t, []string{"bar", "baz"}, s.Foo.parts)
d, err := yaml.Marshal(&s)
assert.Nil(t, err)
s2 := StructStringorslice{}
yaml.Unmarshal(d, &s2)
assert.Equal(t, []string{"bar", "baz"}, s2.Foo.parts)
}
type StructSliceorMap struct {
Foos SliceorMap `yaml:"foos,omitempty"`
Bars []string `yaml:"bars"`
}
func TestSliceOrMapYaml(t *testing.T) {
str := `{foos: [bar=baz, far=faz]}`
s := StructSliceorMap{}
yaml.Unmarshal([]byte(str), &s)
assert.Equal(t, map[string]string{"bar": "baz", "far": "faz"}, s.Foos.parts)
d, err := yaml.Marshal(&s)
assert.Nil(t, err)
s2 := StructSliceorMap{}
yaml.Unmarshal(d, &s2)
assert.Equal(t, map[string]string{"bar": "baz", "far": "faz"}, s2.Foos.parts)
}
var sampleStructSliceorMap = `udav:
foos:
io.rancher.os.bar: baz
io.rancher.os.far: faz
bars: []
`
func TestStr2SliceOrMapPtrMap(t *testing.T) {
s := map[string]*StructSliceorMap{"udav": {
Foos: SliceorMap{map[string]string{"io.rancher.os.bar": "baz", "io.rancher.os.far": "faz"}},
Bars: []string{},
}}
d, err := yaml.Marshal(&s)
assert.Nil(t, err)
assert.Equal(t, sampleStructSliceorMap, string(d))
s2 := map[string]*StructSliceorMap{}
yaml.Unmarshal(d, &s2)
assert.Equal(t, s, s2)
}
type StructMaporslice struct {
Foo MaporEqualSlice
}
func contains(list []string, item string) bool {
for _, test := range list {
if test == item {
return true
}
}
return false
}
func TestMaporsliceYaml(t *testing.T) {
str := `{foo: {bar: baz, far: faz}}`
s := StructMaporslice{}
yaml.Unmarshal([]byte(str), &s)
assert.Equal(t, 2, len(s.Foo.parts))
assert.True(t, contains(s.Foo.parts, "bar=baz"))
assert.True(t, contains(s.Foo.parts, "far=faz"))
d, err := yaml.Marshal(&s)
assert.Nil(t, err)
s2 := StructMaporslice{}
yaml.Unmarshal(d, &s2)
assert.Equal(t, 2, len(s2.Foo.parts))
assert.True(t, contains(s2.Foo.parts, "bar=baz"))
assert.True(t, contains(s2.Foo.parts, "far=faz"))
}

68
vendor/github.com/docker/libcompose/project/utils.go generated vendored Normal file
View File

@@ -0,0 +1,68 @@
package project
import (
"strings"
"github.com/docker/docker/runconfig"
)
// 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.Slice() {
result = append(result, NewServiceRelationship(link, RelTypeLink))
}
for _, volumesFrom := range config.VolumesFrom {
result = append(result, NewServiceRelationship(volumesFrom, RelTypeVolumesFrom))
}
result = appendNs(p, result, s.Config().Net, 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 := runconfig.IpcMode(conf)
if !ipc.IsContainer() {
return ""
}
name := ipc.Container()
if name == "" {
return ""
}
if _, ok := p.Configs[name]; ok {
return name
}
return ""
}

View File

@@ -0,0 +1,54 @@
package project
import (
"testing"
yaml "github.com/cloudfoundry-incubator/candiedyaml"
"github.com/stretchr/testify/assert"
)
type structStringorslice struct {
Foo Stringorslice `yaml:"foo,flow,omitempty"`
}
func TestMarshal(t *testing.T) {
s := &structStringorslice{Foo: NewStringorslice("a", "b", "c")}
b, err := yaml.Marshal(s)
assert.Equal(t, "foo: [a, b, c]\n", string(b))
assert.Nil(t, err)
}
func TestMarshalEmpty(t *testing.T) {
s := &structStringorslice{}
b, err := yaml.Marshal(s)
assert.Equal(t, "foo: []\n", string(b))
assert.Nil(t, err)
}
func TestUnmarshalSlice(t *testing.T) {
expected := &structStringorslice{Foo: NewStringorslice("a", "b", "c")}
b := []byte("foo: [a, b, c]\n")
s := &structStringorslice{}
err := yaml.Unmarshal(b, s)
assert.Equal(t, expected, s)
assert.Nil(t, err)
}
func TestUnmarshalString(t *testing.T) {
expected := &structStringorslice{Foo: NewStringorslice("abc")}
b := []byte("foo: abc\n")
s := &structStringorslice{}
err := yaml.Unmarshal(b, s)
assert.Equal(t, expected, s)
assert.Nil(t, err)
}
func TestUnmarshalEmpty(t *testing.T) {
expected := &structStringorslice{Foo: NewStringorslice()}
b := []byte("{}\n")
s := &structStringorslice{}
err := yaml.Unmarshal(b, s)
assert.Equal(t, expected, s)
assert.Nil(t, err)
}