Initial commit
This commit is contained in:
2
vendor/github.com/hyperhq/libcompose/.dockerignore
generated
vendored
Normal file
2
vendor/github.com/hyperhq/libcompose/.dockerignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
bundles/
|
||||
**/*.test
|
||||
8
vendor/github.com/hyperhq/libcompose/.gitignore
generated
vendored
Normal file
8
vendor/github.com/hyperhq/libcompose/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
.DS_Store
|
||||
/main/main
|
||||
/docker-compose*
|
||||
/libcompose-cli*
|
||||
*.log
|
||||
*.swp
|
||||
bundles
|
||||
.gopath
|
||||
43
vendor/github.com/hyperhq/libcompose/config/convert.go
generated
vendored
Normal file
43
vendor/github.com/hyperhq/libcompose/config/convert.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
package config
|
||||
|
||||
import "github.com/hyperhq/libcompose/utils"
|
||||
|
||||
// ConvertV1toV2 converts a v1 service config to a v2 service config
|
||||
func ConvertV1toV2(v1Services map[string]*ServiceConfigV1, environmentLookup EnvironmentLookup, resourceLookup ResourceLookup) (map[string]*ServiceConfig, error) {
|
||||
v2Services := make(map[string]*ServiceConfig)
|
||||
|
||||
/*
|
||||
builds := make(map[string]Build)
|
||||
logs := make(map[string]Log)
|
||||
|
||||
for name, service := range v1Services {
|
||||
builds[name] = Build{
|
||||
Context: service.Build,
|
||||
Dockerfile: service.Dockerfile,
|
||||
}
|
||||
|
||||
v1Services[name].Build = ""
|
||||
v1Services[name].Dockerfile = ""
|
||||
|
||||
logs[name] = Log{
|
||||
Driver: service.LogDriver,
|
||||
Options: service.LogOpt,
|
||||
}
|
||||
|
||||
v1Services[name].LogDriver = ""
|
||||
v1Services[name].LogOpt = nil
|
||||
}
|
||||
*/
|
||||
|
||||
if err := utils.Convert(v1Services, &v2Services); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
/*
|
||||
for name := range v2Services {
|
||||
v2Services[name].Build = builds[name]
|
||||
}
|
||||
*/
|
||||
|
||||
return v2Services, nil
|
||||
}
|
||||
95
vendor/github.com/hyperhq/libcompose/config/hash.go
generated
vendored
Normal file
95
vendor/github.com/hyperhq/libcompose/config/hash.go
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"github.com/hyperhq/libcompose/yaml"
|
||||
)
|
||||
|
||||
// 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 yaml.SliceorMap:
|
||||
sliceKeys := []string{}
|
||||
for lkey := range s {
|
||||
sliceKeys = append(sliceKeys, lkey)
|
||||
}
|
||||
sort.Strings(sliceKeys)
|
||||
|
||||
for _, sliceKey := range sliceKeys {
|
||||
io.WriteString(hash, fmt.Sprintf("%s=%v, ", sliceKey, s[sliceKey]))
|
||||
}
|
||||
case yaml.MaporEqualSlice:
|
||||
for _, sliceKey := range s {
|
||||
io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey))
|
||||
}
|
||||
case yaml.MaporColonSlice:
|
||||
for _, sliceKey := range s {
|
||||
io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey))
|
||||
}
|
||||
case yaml.MaporSpaceSlice:
|
||||
for _, sliceKey := range s {
|
||||
io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey))
|
||||
}
|
||||
case yaml.Command:
|
||||
for _, sliceKey := range s {
|
||||
io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey))
|
||||
}
|
||||
case yaml.Stringorslice:
|
||||
sort.Strings(s)
|
||||
|
||||
for _, sliceKey := range s {
|
||||
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))
|
||||
}
|
||||
169
vendor/github.com/hyperhq/libcompose/config/interpolation.go
generated
vendored
Normal file
169
vendor/github.com/hyperhq/libcompose/config/interpolation.go
generated
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"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
|
||||
|
||||
*data, success = parseLine(typedData, mapping)
|
||||
|
||||
if !success {
|
||||
return fmt.Errorf("Invalid interpolation format for \"%s\" option in service \"%s\": \"%s\"", option, service, typedData)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// Interpolate replaces variables in the raw map representation of the project file
|
||||
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
|
||||
}
|
||||
226
vendor/github.com/hyperhq/libcompose/config/interpolation_test.go
generated
vendored
Normal file
226
vendor/github.com/hyperhq/libcompose/config/interpolation_test.go
generated
vendored
Normal file
@@ -0,0 +1,226 @@
|
||||
package config
|
||||
|
||||
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}"`)
|
||||
}
|
||||
90
vendor/github.com/hyperhq/libcompose/config/marshal_config_test.go
generated
vendored
Normal file
90
vendor/github.com/hyperhq/libcompose/config/marshal_config_test.go
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
yaml "github.com/cloudfoundry-incubator/candiedyaml"
|
||||
yamlTypes "github.com/hyperhq/libcompose/yaml"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type TestConfig struct {
|
||||
SystemContainers map[string]*ServiceConfig
|
||||
}
|
||||
|
||||
func newTestConfig() TestConfig {
|
||||
return TestConfig{
|
||||
SystemContainers: map[string]*ServiceConfig{
|
||||
"udev": {
|
||||
Image: "udev",
|
||||
Restart: "always",
|
||||
// NetworkMode: "host",
|
||||
// Privileged: true,
|
||||
// DNS: []string{"8.8.8.8", "8.8.4.4"},
|
||||
Environment: yamlTypes.MaporEqualSlice([]string{
|
||||
"DAEMON=true",
|
||||
}),
|
||||
Labels: yamlTypes.SliceorMap{
|
||||
"io.rancher.os.detach": "true",
|
||||
"io.rancher.os.scope": "system",
|
||||
},
|
||||
// VolumesFrom: []string{
|
||||
// "system-volumes",
|
||||
// },
|
||||
// Ulimits: yamlTypes.Ulimits{
|
||||
// Elements: []yamlTypes.Ulimit{
|
||||
// yamlTypes.NewUlimit("nproc", 65557, 65557),
|
||||
// },
|
||||
// },
|
||||
},
|
||||
"system-volumes": {
|
||||
Image: "state",
|
||||
// NetworkMode: "none",
|
||||
// ReadOnly: true,
|
||||
// Privileged: true,
|
||||
Labels: yamlTypes.SliceorMap{
|
||||
"io.rancher.os.createonly": "true",
|
||||
"io.rancher.os.scope": "system",
|
||||
},
|
||||
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",
|
||||
},
|
||||
// Logging: Log{
|
||||
// Driver: "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)
|
||||
}
|
||||
150
vendor/github.com/hyperhq/libcompose/config/merge.go
generated
vendored
Normal file
150
vendor/github.com/hyperhq/libcompose/config/merge.go
generated
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
yaml "github.com/cloudfoundry-incubator/candiedyaml"
|
||||
"github.com/hyperhq/hypercli/pkg/urlutil"
|
||||
)
|
||||
|
||||
var (
|
||||
noMerge = []string{
|
||||
"links",
|
||||
"volumes_from",
|
||||
}
|
||||
)
|
||||
|
||||
// Merge merges a compose file into an existing set of service configs
|
||||
func Merge(existingServices *ServiceConfigs, environmentLookup EnvironmentLookup, resourceLookup ResourceLookup, file string, bytes []byte) (map[string]*ServiceConfig, map[string]*VolumeConfig, map[string]*NetworkConfig, error) {
|
||||
var config Config
|
||||
if err := yaml.Unmarshal(bytes, &config); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
var serviceConfigs map[string]*ServiceConfig
|
||||
var volumeConfigs map[string]*VolumeConfig
|
||||
var networkConfigs map[string]*NetworkConfig
|
||||
if config.Version == "2" {
|
||||
var err error
|
||||
serviceConfigs, err = MergeServicesV2(existingServices, environmentLookup, resourceLookup, file, bytes)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
volumeConfigs, err = ParseVolumes(environmentLookup, resourceLookup, file, bytes)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
networkConfigs, err = ParseNetworks(environmentLookup, resourceLookup, file, bytes)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
} else {
|
||||
serviceConfigsV1, err := MergeServicesV1(existingServices, environmentLookup, resourceLookup, file, bytes)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
serviceConfigs, err = ConvertV1toV2(serviceConfigsV1, environmentLookup, resourceLookup)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
adjustValues(serviceConfigs)
|
||||
|
||||
return serviceConfigs, volumeConfigs, networkConfigs, 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(resourceLookup ResourceLookup, inFile string, serviceData RawService) (RawService, error) {
|
||||
if _, ok := serviceData["env_file"]; !ok {
|
||||
return serviceData, nil
|
||||
}
|
||||
envFiles := serviceData["env_file"].([]interface{})
|
||||
if len(envFiles) == 0 {
|
||||
return serviceData, nil
|
||||
}
|
||||
|
||||
if resourceLookup == nil {
|
||||
return nil, fmt.Errorf("Can not use env_file in file %s no mechanism provided to load files", inFile)
|
||||
}
|
||||
|
||||
var vars []interface{}
|
||||
if _, ok := serviceData["environment"]; ok {
|
||||
vars = serviceData["environment"].([]interface{})
|
||||
}
|
||||
|
||||
for i := len(envFiles) - 1; i >= 0; i-- {
|
||||
envFile := envFiles[i].(string)
|
||||
content, _, err := resourceLookup.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.(string), 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 mergeConfig(baseService, serviceData RawService) RawService {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
return baseService
|
||||
}
|
||||
|
||||
// IsValidRemote checks if the specified string is a valid remote (for builds)
|
||||
func IsValidRemote(remote string) bool {
|
||||
return urlutil.IsGitURL(remote) || urlutil.IsURL(remote)
|
||||
}
|
||||
292
vendor/github.com/hyperhq/libcompose/config/merge_test.go
generated
vendored
Normal file
292
vendor/github.com/hyperhq/libcompose/config/merge_test.go
generated
vendored
Normal file
@@ -0,0 +1,292 @@
|
||||
package config
|
||||
|
||||
import "testing"
|
||||
|
||||
type NullLookup struct {
|
||||
}
|
||||
|
||||
func (n *NullLookup) Lookup(file, relativeTo string) ([]byte, string, error) {
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
func (n *NullLookup) ResolvePath(path, inFile string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func TestExtendsInheritImage(t *testing.T) {
|
||||
configV1, _, _, err := Merge(NewServiceConfigs(), nil, &NullLookup{}, "", []byte(`
|
||||
parent:
|
||||
image: foo
|
||||
child:
|
||||
extends:
|
||||
service: parent
|
||||
`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
configV2, _, _, err := Merge(NewServiceConfigs(), nil, &NullLookup{}, "", []byte(`
|
||||
version: '2'
|
||||
services:
|
||||
parent:
|
||||
image: foo
|
||||
child:
|
||||
extends:
|
||||
service: parent
|
||||
`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, config := range []map[string]*ServiceConfig{configV1, configV2} {
|
||||
parent := config["parent"]
|
||||
child := config["child"]
|
||||
|
||||
if parent.Image != "foo" {
|
||||
t.Fatal("Invalid image", parent.Image)
|
||||
}
|
||||
|
||||
// if child.Build.Context != "" {
|
||||
// t.Fatal("Invalid build", child.Build)
|
||||
// }
|
||||
|
||||
if child.Image != "foo" {
|
||||
t.Fatal("Invalid image", child.Image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func TestExtendsInheritBuild(t *testing.T) {
|
||||
configV1, _, _, err := Merge(NewServiceConfigs(), nil, &NullLookup{}, "", []byte(`
|
||||
parent:
|
||||
build: .
|
||||
child:
|
||||
extends:
|
||||
service: parent
|
||||
`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
configV2, _, _, err := Merge(NewServiceConfigs(), nil, &NullLookup{}, "", []byte(`
|
||||
version: '2'
|
||||
services:
|
||||
parent:
|
||||
build:
|
||||
context: .
|
||||
child:
|
||||
extends:
|
||||
service: parent
|
||||
`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, config := range []map[string]*ServiceConfig{configV1, configV2} {
|
||||
parent := config["parent"]
|
||||
child := config["child"]
|
||||
|
||||
if parent.Build.Context != "." {
|
||||
t.Fatal("Invalid build", parent.Build)
|
||||
}
|
||||
|
||||
if child.Build.Context != "." {
|
||||
t.Fatal("Invalid build", child.Build)
|
||||
}
|
||||
|
||||
if child.Image != "" {
|
||||
t.Fatal("Invalid image", child.Image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtendBuildOverImage(t *testing.T) {
|
||||
configV1, _, _, err := Merge(NewServiceConfigs(), nil, &NullLookup{}, "", []byte(`
|
||||
parent:
|
||||
image: foo
|
||||
child:
|
||||
build: .
|
||||
extends:
|
||||
service: parent
|
||||
`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
configV2, _, _, err := Merge(NewServiceConfigs(), nil, &NullLookup{}, "", []byte(`
|
||||
version: '2'
|
||||
services:
|
||||
parent:
|
||||
image: foo
|
||||
child:
|
||||
build:
|
||||
context: .
|
||||
extends:
|
||||
service: parent
|
||||
`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, config := range []map[string]*ServiceConfig{configV1, configV2} {
|
||||
parent := config["parent"]
|
||||
child := config["child"]
|
||||
|
||||
if parent.Image != "foo" {
|
||||
t.Fatal("Invalid image", parent.Image)
|
||||
}
|
||||
|
||||
if child.Build.Context != "." {
|
||||
t.Fatal("Invalid build", child.Build)
|
||||
}
|
||||
|
||||
if child.Image != "" {
|
||||
t.Fatal("Invalid image", child.Image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtendImageOverBuild(t *testing.T) {
|
||||
configV1, _, _, err := Merge(NewServiceConfigs(), nil, &NullLookup{}, "", []byte(`
|
||||
parent:
|
||||
build: .
|
||||
child:
|
||||
image: foo
|
||||
extends:
|
||||
service: parent
|
||||
`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
configV2, _, _, err := Merge(NewServiceConfigs(), nil, &NullLookup{}, "", []byte(`
|
||||
version: '2'
|
||||
services:
|
||||
parent:
|
||||
build:
|
||||
context: .
|
||||
child:
|
||||
image: foo
|
||||
extends:
|
||||
service: parent
|
||||
`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, config := range []map[string]*ServiceConfig{configV1, configV2} {
|
||||
parent := config["parent"]
|
||||
child := config["child"]
|
||||
|
||||
if parent.Image != "" {
|
||||
t.Fatal("Invalid image", parent.Image)
|
||||
}
|
||||
|
||||
if parent.Build.Context != "." {
|
||||
t.Fatal("Invalid build", parent.Build)
|
||||
}
|
||||
|
||||
if child.Build.Context != "" {
|
||||
t.Fatal("Invalid build", child.Build)
|
||||
}
|
||||
|
||||
if child.Image != "foo" {
|
||||
t.Fatal("Invalid image", child.Image)
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
func TestRestartNo(t *testing.T) {
|
||||
configV1, _, _, err := Merge(NewServiceConfigs(), nil, &NullLookup{}, "", []byte(`
|
||||
test:
|
||||
restart: "no"
|
||||
image: foo
|
||||
`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
configV2, _, _, err := Merge(NewServiceConfigs(), nil, &NullLookup{}, "", []byte(`
|
||||
version: '2'
|
||||
services:
|
||||
test:
|
||||
restart: "no"
|
||||
image: foo
|
||||
`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, config := range []map[string]*ServiceConfig{configV1, configV2} {
|
||||
test := config["test"]
|
||||
|
||||
if test.Restart != "no" {
|
||||
t.Fatal("Invalid restart policy", test.Restart)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestartAlways(t *testing.T) {
|
||||
configV1, _, _, err := Merge(NewServiceConfigs(), nil, &NullLookup{}, "", []byte(`
|
||||
test:
|
||||
restart: always
|
||||
image: foo
|
||||
`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
configV2, _, _, err := Merge(NewServiceConfigs(), nil, &NullLookup{}, "", []byte(`
|
||||
version: '2'
|
||||
services:
|
||||
test:
|
||||
restart: always
|
||||
image: foo
|
||||
`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, config := range []map[string]*ServiceConfig{configV1, configV2} {
|
||||
test := config["test"]
|
||||
|
||||
if test.Restart != "always" {
|
||||
t.Fatal("Invalid restart policy", test.Restart)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidRemote(t *testing.T) {
|
||||
gitUrls := []string{
|
||||
"git://github.com/docker/docker",
|
||||
"git@github.com:docker/docker.git",
|
||||
"git@bitbucket.org:atlassianlabs/atlassian-docker.git",
|
||||
"https://github.com/docker/docker.git",
|
||||
"http://github.com/docker/docker.git",
|
||||
"http://github.com/docker/docker.git#branch",
|
||||
"http://github.com/docker/docker.git#:dir",
|
||||
}
|
||||
incompleteGitUrls := []string{
|
||||
"github.com/docker/docker",
|
||||
}
|
||||
invalidGitUrls := []string{
|
||||
"http://github.com/docker/docker.git:#branch",
|
||||
}
|
||||
for _, url := range gitUrls {
|
||||
if !IsValidRemote(url) {
|
||||
t.Fatalf("%q should have been a valid remote", url)
|
||||
}
|
||||
}
|
||||
for _, url := range incompleteGitUrls {
|
||||
if !IsValidRemote(url) {
|
||||
t.Fatalf("%q should have been a valid remote", url)
|
||||
}
|
||||
}
|
||||
for _, url := range invalidGitUrls {
|
||||
if !IsValidRemote(url) {
|
||||
t.Fatalf("%q should have been a valid remote", url)
|
||||
}
|
||||
}
|
||||
}
|
||||
173
vendor/github.com/hyperhq/libcompose/config/merge_v1.go
generated
vendored
Normal file
173
vendor/github.com/hyperhq/libcompose/config/merge_v1.go
generated
vendored
Normal file
@@ -0,0 +1,173 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
yaml "github.com/cloudfoundry-incubator/candiedyaml"
|
||||
"github.com/hyperhq/libcompose/utils"
|
||||
)
|
||||
|
||||
// MergeServicesV1 merges a v1 compose file into an existing set of service configs
|
||||
func MergeServicesV1(existingServices *ServiceConfigs, environmentLookup EnvironmentLookup, resourceLookup ResourceLookup, file string, bytes []byte) (map[string]*ServiceConfigV1, error) {
|
||||
datas := make(RawServiceMap)
|
||||
if err := yaml.Unmarshal(bytes, &datas); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := Interpolate(environmentLookup, &datas); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := validate(datas, "v1"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for name, data := range datas {
|
||||
data, err := parseV1(resourceLookup, environmentLookup, file, data, datas)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to parse service %s: %v", name, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if serviceConfig, ok := existingServices.Get(name); ok {
|
||||
var rawExistingService RawService
|
||||
if err := utils.Convert(serviceConfig, &rawExistingService); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data = mergeConfig(rawExistingService, data)
|
||||
}
|
||||
|
||||
datas[name] = data
|
||||
}
|
||||
|
||||
for name, data := range datas {
|
||||
err := validateServiceConstraints(data, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
serviceConfigs := make(map[string]*ServiceConfigV1)
|
||||
if err := utils.Convert(datas, &serviceConfigs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return serviceConfigs, nil
|
||||
}
|
||||
|
||||
func parseV1(resourceLookup ResourceLookup, environmentLookup EnvironmentLookup, inFile string, serviceData RawService, datas RawServiceMap) (RawService, error) {
|
||||
serviceData, err := readEnvFile(resourceLookup, inFile, serviceData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//serviceData = resolveContextV1(inFile, serviceData)
|
||||
|
||||
value, ok := serviceData["extends"]
|
||||
if !ok {
|
||||
return serviceData, nil
|
||||
}
|
||||
|
||||
mapValue, ok := value.(map[interface{}]interface{})
|
||||
if !ok {
|
||||
return serviceData, nil
|
||||
}
|
||||
|
||||
if resourceLookup == 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 = parseV1(resourceLookup, environmentLookup, inFile, serviceData, datas)
|
||||
} else {
|
||||
return nil, fmt.Errorf("Failed to find service %s to extend", service)
|
||||
}
|
||||
} else {
|
||||
bytes, resolved, err := resourceLookup.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
|
||||
}
|
||||
|
||||
if err := validate(baseRawServices, "v1"); 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 = parseV1(resourceLookup, 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)
|
||||
}
|
||||
}
|
||||
|
||||
baseService = mergeConfig(baseService, serviceData)
|
||||
|
||||
logrus.Debugf("Merged result %#v", baseService)
|
||||
|
||||
return baseService, nil
|
||||
}
|
||||
|
||||
func resolveContextV1(inFile string, serviceData RawService) RawService {
|
||||
context := asString(serviceData["build"])
|
||||
if context == "" {
|
||||
return serviceData
|
||||
}
|
||||
|
||||
if IsValidRemote(context) {
|
||||
return serviceData
|
||||
}
|
||||
|
||||
current := path.Dir(inFile)
|
||||
|
||||
if context == "." {
|
||||
context = current
|
||||
} else {
|
||||
current = path.Join(current, context)
|
||||
}
|
||||
|
||||
serviceData["build"] = current
|
||||
|
||||
return serviceData
|
||||
}
|
||||
211
vendor/github.com/hyperhq/libcompose/config/merge_v2.go
generated
vendored
Normal file
211
vendor/github.com/hyperhq/libcompose/config/merge_v2.go
generated
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
yaml "github.com/cloudfoundry-incubator/candiedyaml"
|
||||
"github.com/hyperhq/libcompose/utils"
|
||||
)
|
||||
|
||||
// MergeServicesV2 merges a v2 compose file into an existing set of service configs
|
||||
func MergeServicesV2(existingServices *ServiceConfigs, environmentLookup EnvironmentLookup, resourceLookup ResourceLookup, file string, bytes []byte) (map[string]*ServiceConfig, error) {
|
||||
var config Config
|
||||
if err := yaml.Unmarshal(bytes, &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
datas := config.Services
|
||||
|
||||
if err := Interpolate(environmentLookup, &datas); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
/*
|
||||
data := RawServiceMap{
|
||||
"services": map[string]interface{}{},
|
||||
}
|
||||
for k, v := range datas {
|
||||
data["services"][k] = v
|
||||
}
|
||||
*/
|
||||
if err := validate(datas, "v2"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for name, data := range datas {
|
||||
data, err := parseV2(resourceLookup, environmentLookup, file, data, datas)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to parse service %s: %v", name, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if serviceConfig, ok := existingServices.Get(name); ok {
|
||||
var rawExistingService RawService
|
||||
if err := utils.Convert(serviceConfig, &rawExistingService); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data = mergeConfig(rawExistingService, data)
|
||||
}
|
||||
|
||||
datas[name] = data
|
||||
}
|
||||
|
||||
serviceConfigs := make(map[string]*ServiceConfig)
|
||||
if err := utils.Convert(datas, &serviceConfigs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return serviceConfigs, nil
|
||||
}
|
||||
|
||||
// ParseVolumes parses volumes in a compose file
|
||||
func ParseVolumes(environmentLookup EnvironmentLookup, resourceLookup ResourceLookup, file string, bytes []byte) (map[string]*VolumeConfig, error) {
|
||||
volumeConfigs := make(map[string]*VolumeConfig)
|
||||
|
||||
var config Config
|
||||
if err := yaml.Unmarshal(bytes, &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := utils.Convert(config.Volumes, &volumeConfigs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return volumeConfigs, nil
|
||||
}
|
||||
|
||||
// ParseNetworks parses networks in a compose file
|
||||
func ParseNetworks(environmentLookup EnvironmentLookup, resourceLookup ResourceLookup, file string, bytes []byte) (map[string]*NetworkConfig, error) {
|
||||
networkConfigs := make(map[string]*NetworkConfig)
|
||||
|
||||
var config Config
|
||||
if err := yaml.Unmarshal(bytes, &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := utils.Convert(config.Networks, &networkConfigs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return networkConfigs, nil
|
||||
}
|
||||
|
||||
func parseV2(resourceLookup ResourceLookup, environmentLookup EnvironmentLookup, inFile string, serviceData RawService, datas RawServiceMap) (RawService, error) {
|
||||
serviceData, err := readEnvFile(resourceLookup, inFile, serviceData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//serviceData = resolveContextV2(inFile, serviceData)
|
||||
|
||||
value, ok := serviceData["extends"]
|
||||
if !ok {
|
||||
return serviceData, nil
|
||||
}
|
||||
|
||||
mapValue, ok := value.(map[interface{}]interface{})
|
||||
if !ok {
|
||||
return serviceData, nil
|
||||
}
|
||||
|
||||
if resourceLookup == 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 = parseV2(resourceLookup, environmentLookup, inFile, serviceData, datas)
|
||||
} else {
|
||||
return nil, fmt.Errorf("Failed to find service %s to extend", service)
|
||||
}
|
||||
} else {
|
||||
bytes, resolved, err := resourceLookup.Lookup(file, inFile)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to lookup file %s: %v", file, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var config Config
|
||||
if err := yaml.Unmarshal(bytes, &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
baseRawServices := config.Services
|
||||
|
||||
err = Interpolate(environmentLookup, &baseRawServices)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = validate(datas, "v2"); 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 = parseV2(resourceLookup, 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)
|
||||
}
|
||||
}
|
||||
|
||||
baseService = mergeConfig(baseService, serviceData)
|
||||
|
||||
logrus.Debugf("Merged result %#v", baseService)
|
||||
|
||||
return baseService, nil
|
||||
}
|
||||
|
||||
func resolveContextV2(inFile string, serviceData RawService) RawService {
|
||||
if _, ok := serviceData["build"]; !ok {
|
||||
return serviceData
|
||||
}
|
||||
build := serviceData["build"].(map[interface{}]interface{})
|
||||
context := asString(build["context"])
|
||||
if context == "" {
|
||||
return serviceData
|
||||
}
|
||||
|
||||
if IsValidRemote(context) {
|
||||
return serviceData
|
||||
}
|
||||
|
||||
current := path.Dir(inFile)
|
||||
|
||||
if context == "." {
|
||||
context = current
|
||||
} else {
|
||||
current = path.Join(current, context)
|
||||
}
|
||||
|
||||
build["context"] = current
|
||||
|
||||
return serviceData
|
||||
}
|
||||
339
vendor/github.com/hyperhq/libcompose/config/schema.go
generated
vendored
Normal file
339
vendor/github.com/hyperhq/libcompose/config/schema.go
generated
vendored
Normal file
@@ -0,0 +1,339 @@
|
||||
package config
|
||||
|
||||
var schemaV1 = `{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"id": "config_schema_v1.json",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"patternProperties": {
|
||||
"^[a-zA-Z0-9._-]+$": {
|
||||
"$ref": "#/definitions/service"
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
|
||||
"definitions": {
|
||||
"service": {
|
||||
"id": "#/definitions/service",
|
||||
"type": "object",
|
||||
|
||||
"properties": {
|
||||
"cap_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||
"cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||
"cgroup_parent": {"type": "string"},
|
||||
"command": {
|
||||
"oneOf": [
|
||||
{"type": "string"},
|
||||
{"type": "array", "items": {"type": "string"}}
|
||||
]
|
||||
},
|
||||
"container_name": {"type": "string"},
|
||||
"cpu_shares": {"type": ["number", "string"]},
|
||||
"cpu_quota": {"type": ["number", "string"]},
|
||||
"cpuset": {"type": "string"},
|
||||
"dns": {"$ref": "#/definitions/string_or_list"},
|
||||
"dns_search": {"$ref": "#/definitions/string_or_list"},
|
||||
"domainname": {"type": "string"},
|
||||
"entrypoint": {
|
||||
"oneOf": [
|
||||
{"type": "string"},
|
||||
{"type": "array", "items": {"type": "string"}}
|
||||
]
|
||||
},
|
||||
"env_file": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||
"environment": {"$ref": "#/definitions/list_or_dict"},
|
||||
|
||||
"extends": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
|
||||
"properties": {
|
||||
"service": {"type": "string"},
|
||||
"file": {"type": "string"}
|
||||
},
|
||||
"required": ["service"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"expose": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": ["string", "number"]
|
||||
},
|
||||
"uniqueItems": true
|
||||
},
|
||||
|
||||
"external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||
"hostname": {"type": "string"},
|
||||
"image": {"type": "string"},
|
||||
"labels": {"$ref": "#/definitions/list_or_dict"},
|
||||
"links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||
"mem_limit": {"type": ["number", "string"]},
|
||||
"memswap_limit": {"type": ["number", "string"]},
|
||||
"noauto_volume": {"type": "boolean"},
|
||||
|
||||
"ports": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": ["string", "number"]
|
||||
},
|
||||
"uniqueItems": true
|
||||
},
|
||||
|
||||
"stop_signal": {"type": "string"},
|
||||
"security_groups": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||
"restart": {"type": "string"},
|
||||
"stdin_open": {"type": "boolean"},
|
||||
"tty": {"type": "boolean"},
|
||||
"volumes": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||
"working_dir": {"type": "string"},
|
||||
"user": {"type": "string"},
|
||||
|
||||
"size": {"type": "string"},
|
||||
"fip": {"type": "string"}
|
||||
},
|
||||
|
||||
"dependencies": {
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
||||
"string_or_list": {
|
||||
"oneOf": [
|
||||
{"type": "string"},
|
||||
{"$ref": "#/definitions/list_of_strings"}
|
||||
]
|
||||
},
|
||||
|
||||
"list_of_strings": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"uniqueItems": true
|
||||
},
|
||||
|
||||
"list_or_dict": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
".+": {
|
||||
"type": ["string", "number", "null"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
|
||||
]
|
||||
},
|
||||
|
||||
"constraints": {
|
||||
"service": {
|
||||
"id": "#/definitions/constraints/service",
|
||||
"anyOf": [
|
||||
{
|
||||
"required": ["image"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var schemaV2 = `{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"id": "config_schema_v2.0.json",
|
||||
"type": "object",
|
||||
|
||||
"patternProperties": {
|
||||
"^[a-zA-Z0-9._-]+$": {
|
||||
"$ref": "#/definitions/service"
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
|
||||
"definitions": {
|
||||
"service": {
|
||||
"id": "#/definitions/service",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cap_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||
"cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||
"cgroup_parent": {"type": "string"},
|
||||
"command": {
|
||||
"oneOf": [
|
||||
{"type": "string"},
|
||||
{"type": "array", "items": {"type": "string"}}
|
||||
]
|
||||
},
|
||||
"container_name": {"type": "string"},
|
||||
"cpu_shares": {"type": ["number", "string"]},
|
||||
"cpu_quota": {"type": ["number", "string"]},
|
||||
"cpuset": {"type": "string"},
|
||||
"depends_on": {"$ref": "#/definitions/list_of_strings"},
|
||||
"dns": {"$ref": "#/definitions/string_or_list"},
|
||||
"dns_search": {"$ref": "#/definitions/string_or_list"},
|
||||
"domainname": {"type": "string"},
|
||||
"entrypoint": {
|
||||
"oneOf": [
|
||||
{"type": "string"},
|
||||
{"type": "array", "items": {"type": "string"}}
|
||||
]
|
||||
},
|
||||
"env_file": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||
"environment": {"$ref": "#/definitions/list_or_dict"},
|
||||
"extends": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
|
||||
"properties": {
|
||||
"service": {"type": "string"},
|
||||
"file": {"type": "string"}
|
||||
},
|
||||
"required": ["service"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"expose": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": ["string", "number"]
|
||||
},
|
||||
"uniqueItems": true
|
||||
},
|
||||
|
||||
"external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||
"hostname": {"type": "string"},
|
||||
"image": {"type": "string"},
|
||||
"labels": {"$ref": "#/definitions/list_or_dict"},
|
||||
"links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||
"mem_limit": {"type": ["number", "string"]},
|
||||
"memswap_limit": {"type": ["number", "string"]},
|
||||
"network_mode": {"type": "string"},
|
||||
"noauto_volume": {"type": "boolean"},
|
||||
|
||||
"networks": {
|
||||
"oneOf": [
|
||||
{"$ref": "#/definitions/list_of_strings"},
|
||||
{
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[a-zA-Z0-9._-]+$": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"aliases": {"$ref": "#/definitions/list_of_strings"},
|
||||
"ipv4_address": {"type": "string"},
|
||||
"ipv6_address": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{"type": "null"}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"ports": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": ["string", "number"]
|
||||
},
|
||||
"uniqueItems": true
|
||||
},
|
||||
|
||||
"restart": {"type": "string"},
|
||||
"stdin_open": {"type": "boolean"},
|
||||
"stop_signal": {"type": "string"},
|
||||
"security_groups": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||
"tty": {"type": "boolean"},
|
||||
"user": {"type": "string"},
|
||||
"volumes": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||
"working_dir": {"type": "string"},
|
||||
|
||||
"size": {"type": "string"},
|
||||
"fip": {"type": "string"}
|
||||
},
|
||||
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
||||
"volume": {
|
||||
"id": "#/definitions/volume",
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"driver_opts": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^.+$": {"type": ["string", "number"]}
|
||||
}
|
||||
},
|
||||
"external": {
|
||||
"type": ["boolean", "object"],
|
||||
"properties": {
|
||||
"name": {"type": "string"}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
||||
"string_or_list": {
|
||||
"oneOf": [
|
||||
{"type": "string"},
|
||||
{"$ref": "#/definitions/list_of_strings"}
|
||||
]
|
||||
},
|
||||
|
||||
"list_of_strings": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"uniqueItems": true
|
||||
},
|
||||
|
||||
"list_or_dict": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
".+": {
|
||||
"type": ["string", "number", "null"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
|
||||
]
|
||||
},
|
||||
|
||||
"constraints": {
|
||||
"service": {
|
||||
"id": "#/definitions/constraints/service",
|
||||
"anyOf": [
|
||||
{"required": ["image"]}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
96
vendor/github.com/hyperhq/libcompose/config/schema_helpers.go
generated
vendored
Normal file
96
vendor/github.com/hyperhq/libcompose/config/schema_helpers.go
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/xeipuuv/gojsonschema"
|
||||
)
|
||||
|
||||
var (
|
||||
schemaLoader gojsonschema.JSONLoader
|
||||
constraintSchemaLoader gojsonschema.JSONLoader
|
||||
schema map[string]interface{}
|
||||
)
|
||||
|
||||
type (
|
||||
environmentFormatChecker struct{}
|
||||
portsFormatChecker struct{}
|
||||
)
|
||||
|
||||
func (checker environmentFormatChecker) IsFormat(input string) bool {
|
||||
// If the value is a boolean, a warning should be given
|
||||
// However, we can't determine type since gojsonschema converts the value to a string
|
||||
// Adding a function with an interface{} parameter to gojsonschema is probably the best way to handle this
|
||||
return true
|
||||
}
|
||||
|
||||
func (checker portsFormatChecker) IsFormat(input string) bool {
|
||||
_, _, err := nat.ParsePortSpecs([]string{input})
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func setupSchemaLoaders(version string) error {
|
||||
if schema != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var schemaRaw interface{}
|
||||
var schemaStr string = schemaV1
|
||||
if version == "v2" {
|
||||
schemaStr = schemaV2
|
||||
}
|
||||
err := json.Unmarshal([]byte(schemaStr), &schemaRaw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
schema = schemaRaw.(map[string]interface{})
|
||||
|
||||
gojsonschema.FormatCheckers.Add("environment", environmentFormatChecker{})
|
||||
//gojsonschema.FormatCheckers.Add("ports", portsFormatChecker{})
|
||||
//gojsonschema.FormatCheckers.Add("expose", portsFormatChecker{})
|
||||
schemaLoader = gojsonschema.NewGoLoader(schemaRaw)
|
||||
|
||||
definitions := schema["definitions"].(map[string]interface{})
|
||||
constraints := definitions["constraints"].(map[string]interface{})
|
||||
service := constraints["service"].(map[string]interface{})
|
||||
constraintSchemaLoader = gojsonschema.NewGoLoader(service)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// gojsonschema doesn't provide a list of valid types for a property
|
||||
// This parses the schema manually to find all valid types
|
||||
func parseValidTypesFromSchema(schema map[string]interface{}, context string) []string {
|
||||
contextSplit := strings.Split(context, ".")
|
||||
key := contextSplit[len(contextSplit)-1]
|
||||
|
||||
definitions := schema["definitions"].(map[string]interface{})
|
||||
service := definitions["service"].(map[string]interface{})
|
||||
properties := service["properties"].(map[string]interface{})
|
||||
property := properties[key].(map[string]interface{})
|
||||
|
||||
var validTypes []string
|
||||
|
||||
if val, ok := property["oneOf"]; ok {
|
||||
validConditions := val.([]interface{})
|
||||
|
||||
for _, validCondition := range validConditions {
|
||||
condition := validCondition.(map[string]interface{})
|
||||
validTypes = append(validTypes, condition["type"].(string))
|
||||
}
|
||||
} else if val, ok := property["$ref"]; ok {
|
||||
reference := val.(string)
|
||||
if reference == "#/definitions/string_or_list" {
|
||||
return []string{"string", "array"}
|
||||
} else if reference == "#/definitions/list_of_strings" {
|
||||
return []string{"array"}
|
||||
} else if reference == "#/definitions/list_or_dict" {
|
||||
return []string{"array", "object"}
|
||||
}
|
||||
}
|
||||
|
||||
return validTypes
|
||||
}
|
||||
238
vendor/github.com/hyperhq/libcompose/config/types.go
generated
vendored
Normal file
238
vendor/github.com/hyperhq/libcompose/config/types.go
generated
vendored
Normal file
@@ -0,0 +1,238 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/hyperhq/libcompose/yaml"
|
||||
)
|
||||
|
||||
// EnvironmentLookup defines methods to provides environment variable loading.
|
||||
type EnvironmentLookup interface {
|
||||
Lookup(key, serviceName string, config *ServiceConfig) []string
|
||||
}
|
||||
|
||||
// ResourceLookup defines methods to provides file loading.
|
||||
type ResourceLookup interface {
|
||||
Lookup(file, relativeTo string) ([]byte, string, error)
|
||||
ResolvePath(path, inFile string) string
|
||||
}
|
||||
|
||||
// ServiceConfigV1 holds version 1 of libcompose service configuration
|
||||
type ServiceConfigV1 struct {
|
||||
/*
|
||||
Build string `yaml:"build,omitempty"`
|
||||
CapAdd []string `yaml:"cap_add,omitempty"`
|
||||
CapDrop []string `yaml:"cap_drop,omitempty"`
|
||||
CgroupParent string `yaml:"cgroup_parent,omitempty"`
|
||||
CPUQuota int64 `yaml:"cpu_quota,omitempty"`
|
||||
CPUSet string `yaml:"cpuset,omitempty"`
|
||||
CPUShares int64 `yaml:"cpu_shares,omitempty"`
|
||||
Devices []string `yaml:"devices,omitempty"`
|
||||
DNS yaml.Stringorslice `yaml:"dns,omitempty"`
|
||||
DNSSearch yaml.Stringorslice `yaml:"dns_search,omitempty"`
|
||||
Dockerfile string `yaml:"dockerfile,omitempty"`
|
||||
LogDriver string `yaml:"log_driver,omitempty"`
|
||||
MacAddress string `yaml:"mac_address,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"`
|
||||
ReadOnly bool `yaml:"read_only,omitempty"`
|
||||
SecurityOpt []string `yaml:"security_opt,omitempty"`
|
||||
User string `yaml:"user,omitempty"`
|
||||
VolumeDriver string `yaml:"volume_driver,omitempty"`
|
||||
VolumesFrom []string `yaml:"volumes_from,omitempty"`
|
||||
Expose []string `yaml:"expose,omitempty"`
|
||||
LogOpt map[string]string `yaml:"log_opt,omitempty"`
|
||||
ExtraHosts []string `yaml:"extra_hosts,omitempty"`
|
||||
Ulimits yaml.Ulimits `yaml:"ulimits,omitempty"`
|
||||
*/
|
||||
Command yaml.Command `yaml:"command,flow,omitempty" json:"command,omitempty"`
|
||||
ContainerName string `yaml:"container_name,omitempty" json:"container_name,omitempty"`
|
||||
DomainName string `yaml:"domainname,omitempty" json:"domainname,omitempty"`
|
||||
Entrypoint yaml.Command `yaml:"entrypoint,flow,omitempty" json:"entrypoint,omitempty"`
|
||||
EnvFile yaml.Stringorslice `yaml:"env_file,omitempty" json:"env_file,omitempty"`
|
||||
Environment yaml.MaporEqualSlice `yaml:"environment,omitempty" json:"environment,omitempty"`
|
||||
Hostname string `yaml:"hostname,omitempty" json:"hostname,omitempty"`
|
||||
Image string `yaml:"image,omitempty" json:"image,omitempty"`
|
||||
Labels yaml.SliceorMap `yaml:"labels,omitempty" json:"labels,omitempty"`
|
||||
Links yaml.MaporColonSlice `yaml:"links,omitempty" json:"links,omitempty"`
|
||||
Restart string `yaml:"restart,omitempty" json:"restart,omitempty"`
|
||||
StdinOpen bool `yaml:"stdin_open,omitempty" json:"stdin_open,omitempty"`
|
||||
Tty bool `yaml:"tty,omitempty" json:"tty,omitempty"`
|
||||
Volumes []string `yaml:"volumes,omitempty" json:"volumes,omitempty"`
|
||||
WorkingDir string `yaml:"working_dir,omitempty" json:"working_dir,omitempty"`
|
||||
ExternalLinks []string `yaml:"external_links,omitempty" json:"external_links,omitempty"`
|
||||
|
||||
Size string `yaml:"size,omitempty" json:"size,omitempty"`
|
||||
Fip string `yaml:"fip,omitempty" json:"fip,omitempty"`
|
||||
SecurityGroups []string `yaml:"security_groups,omitempty" json:"security_groups,omitempty"`
|
||||
NoAutoVolume bool `yaml:"noauto_volume,omitempty" json:"noauto_volume,omitempty"`
|
||||
}
|
||||
|
||||
// Build holds v2 build information
|
||||
type Build struct {
|
||||
Context string `yaml:"context,omitempty"`
|
||||
Dockerfile string `yaml:"dockerfile,omitempty"`
|
||||
Args yaml.MaporEqualSlice `yaml:"args,omitempty"`
|
||||
}
|
||||
|
||||
// Log holds v2 logging information
|
||||
type Log struct {
|
||||
Driver string `yaml:"driver,omitempty"`
|
||||
Options map[string]string `yaml:"options,omitempty"`
|
||||
}
|
||||
|
||||
// ServiceConfig holds version 2 of libcompose service configuration
|
||||
type ServiceConfig struct {
|
||||
/*
|
||||
Build Build `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"`
|
||||
CPUQuota int64 `yaml:"cpu_quota,omitempty"`
|
||||
CgroupParent string `yaml:"cgroup_parrent,omitempty"`
|
||||
Devices []string `yaml:"devices,omitempty"`
|
||||
DNS yaml.Stringorslice `yaml:"dns,omitempty"`
|
||||
DNSSearch yaml.Stringorslice `yaml:"dns_search,omitempty"`
|
||||
Expose []string `yaml:"expose,omitempty"`
|
||||
Ipc string `yaml:"ipc,omitempty"`
|
||||
Logging Log `yaml:"logging,omitempty"`
|
||||
MacAddress string `yaml:"mac_address,omitempty"`
|
||||
MemLimit int64 `yaml:"mem_limit,omitempty"`
|
||||
MemSwapLimit int64 `yaml:"memswap_limit,omitempty"`
|
||||
NetworkMode string `yaml:"network_mode,omitempty"`
|
||||
Networks []string `yaml:"networks,omitempty"`
|
||||
Pid string `yaml:"pid,omitempty"`
|
||||
Ports []string `yaml:"ports,omitempty"`
|
||||
Privileged bool `yaml:"privileged,omitempty"`
|
||||
SecurityOpt []string `yaml:"security_opt,omitempty"`
|
||||
StopSignal string `yaml:"stop_signal,omitempty"`
|
||||
VolumeDriver string `yaml:"volume_driver,omitempty"`
|
||||
VolumesFrom []string `yaml:"volumes_from,omitempty"`
|
||||
Uts string `yaml:"uts,omitempty"`
|
||||
ReadOnly bool `yaml:"read_only,omitempty"`
|
||||
User string `yaml:"user,omitempty"`
|
||||
Ulimits yaml.Ulimits `yaml:"ulimits,omitempty"`
|
||||
*/
|
||||
Expose []string `yaml:"expose,omitempty" json:"expose,omitempty"`
|
||||
Ports []string `yaml:"ports,omitempty" json:"ports,omitempty"`
|
||||
Command yaml.Command `yaml:"command,flow,omitempty" json:"command,omitempty"`
|
||||
ContainerName string `yaml:"container_name,omitempty" json:"container_name,omitempty"`
|
||||
DomainName string `yaml:"domainname,omitempty" json:"domainname,omitempty"`
|
||||
DependsOn []string `yaml:"depends_on,omitempty" json:"depends_on,omitempty"`
|
||||
Entrypoint yaml.Command `yaml:"entrypoint,flow,omitempty" json:"entrypoint,omitempty"`
|
||||
EnvFile yaml.Stringorslice `yaml:"env_file,omitempty" json:"env_file,omitempty"`
|
||||
Environment yaml.MaporEqualSlice `yaml:"environment,omitempty" json:"environment,omitempty"`
|
||||
Extends yaml.MaporEqualSlice `yaml:"extends,omitempty" json:"extends,omitempty"`
|
||||
ExternalLinks []string `yaml:"external_links,omitempty" json:"external_links"`
|
||||
Image string `yaml:"image,omitempty" json:"image,omitempty"`
|
||||
Hostname string `yaml:"hostname,omitempty" json:"hostname,omitempty"`
|
||||
Labels yaml.SliceorMap `yaml:"labels,omitempty" json:"labels,omitempty"`
|
||||
Links yaml.MaporColonSlice `yaml:"links,omitempty" json:"links,omitempty"`
|
||||
Volumes []string `yaml:"volumes,omitempty" json:"volumes,omitempty"`
|
||||
Restart string `yaml:"restart,omitempty" json:"restart,omitempty"`
|
||||
StdinOpen bool `yaml:"stdin_open,omitempty" json:"stdin_open,omitempty"`
|
||||
Tty bool `yaml:"tty,omitempty" json:"tty,omitempty"`
|
||||
WorkingDir string `yaml:"working_dir,omitempty" json:"working_dir,omitempty"`
|
||||
|
||||
Size string `yaml:"size,omitempty" json:"size,omitempty"`
|
||||
Fip string `yaml:"fip,omitempty" json:"fip,omitempty"`
|
||||
SecurityGroups []string `yaml:"security_groups,omitempty" json:"security_groups,omitempty"`
|
||||
NoAutoVolume bool `yaml:"noauto_volume,omitempty" json:"noauto_volume,omitempty"`
|
||||
}
|
||||
|
||||
// VolumeConfig holds v2 volume configuration
|
||||
type VolumeConfig struct {
|
||||
Driver string `yaml:"driver,omitempty"`
|
||||
DriverOpts map[string]string `yaml:"driver_opts,omitempty"`
|
||||
External bool `yaml:"external,omitempty"`
|
||||
}
|
||||
|
||||
// Ipam holds v2 network IPAM information
|
||||
type Ipam struct {
|
||||
Driver string `yaml:"driver,omitempty"`
|
||||
Config []string `yaml:"config,omitempty"`
|
||||
}
|
||||
|
||||
// NetworkConfig holds v2 network configuration
|
||||
type NetworkConfig struct {
|
||||
Driver string `yaml:"driver,omitempty"`
|
||||
DriverOpts map[string]string `yaml:"driver_opts,omitempty"`
|
||||
External bool `yaml:"external,omitempty"`
|
||||
Ipam Ipam `yaml:"ipam,omitempty"`
|
||||
}
|
||||
|
||||
// Config holds libcompose top level configuration
|
||||
type Config struct {
|
||||
Version string `yaml:"version,omitempty"`
|
||||
Services RawServiceMap `yaml:"services,omitempty"`
|
||||
Volumes map[string]*VolumeConfig `yaml:"volumes,omitempty"`
|
||||
Networks map[string]*NetworkConfig `yaml:"networks,omitempty"`
|
||||
}
|
||||
|
||||
// NewServiceConfigs initializes a new Configs struct
|
||||
func NewServiceConfigs() *ServiceConfigs {
|
||||
return &ServiceConfigs{
|
||||
M: make(map[string]*ServiceConfig),
|
||||
}
|
||||
}
|
||||
|
||||
// ServiceConfigs holds a concurrent safe map of ServiceConfig
|
||||
type ServiceConfigs struct {
|
||||
M map[string]*ServiceConfig
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// Has checks if the config map has the specified name
|
||||
func (c *ServiceConfigs) Has(name string) bool {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
_, ok := c.M[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Get returns the config and the presence of the specified name
|
||||
func (c *ServiceConfigs) Get(name string) (*ServiceConfig, bool) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
service, ok := c.M[name]
|
||||
return service, ok
|
||||
}
|
||||
|
||||
// Add add the specifed config with the specified name
|
||||
func (c *ServiceConfigs) Add(name string, service *ServiceConfig) {
|
||||
c.mu.Lock()
|
||||
c.M[name] = service
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// Len returns the len of the configs
|
||||
func (c *ServiceConfigs) Len() int {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
return len(c.M)
|
||||
}
|
||||
|
||||
// Keys returns the names of the config
|
||||
func (c *ServiceConfigs) Keys() []string {
|
||||
keys := []string{}
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
for name := range c.M {
|
||||
keys = append(keys, name)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// RawService is represent a Service in map form unparsed
|
||||
type RawService map[string]interface{}
|
||||
|
||||
// RawServiceMap is a collection of RawServices
|
||||
type RawServiceMap map[string]RawService
|
||||
42
vendor/github.com/hyperhq/libcompose/config/utils.go
generated
vendored
Normal file
42
vendor/github.com/hyperhq/libcompose/config/utils.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
package config
|
||||
|
||||
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 ""
|
||||
}
|
||||
309
vendor/github.com/hyperhq/libcompose/config/validation.go
generated
vendored
Normal file
309
vendor/github.com/hyperhq/libcompose/config/validation.go
generated
vendored
Normal file
@@ -0,0 +1,309 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/xeipuuv/gojsonschema"
|
||||
)
|
||||
|
||||
func serviceNameFromErrorField(field string) string {
|
||||
splitKeys := strings.Split(field, ".")
|
||||
return splitKeys[0]
|
||||
}
|
||||
|
||||
func keyNameFromErrorField(field string) string {
|
||||
splitKeys := strings.Split(field, ".")
|
||||
|
||||
if len(splitKeys) > 0 {
|
||||
return splitKeys[len(splitKeys)-1]
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func containsTypeError(resultError gojsonschema.ResultError) bool {
|
||||
contextSplit := strings.Split(resultError.Context().String(), ".")
|
||||
_, err := strconv.Atoi(contextSplit[len(contextSplit)-1])
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func addArticle(s string) string {
|
||||
switch s[0] {
|
||||
case 'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U':
|
||||
return "an " + s
|
||||
default:
|
||||
return "a " + s
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the value in a service map at a given error context
|
||||
func getValue(val interface{}, context string) string {
|
||||
keys := strings.Split(context, ".")
|
||||
|
||||
if keys[0] == "(root)" {
|
||||
keys = keys[1:]
|
||||
}
|
||||
|
||||
for i, k := range keys {
|
||||
switch typedVal := (val).(type) {
|
||||
case string:
|
||||
return typedVal
|
||||
case []interface{}:
|
||||
if index, err := strconv.Atoi(k); err == nil {
|
||||
val = typedVal[index]
|
||||
}
|
||||
case RawServiceMap:
|
||||
val = typedVal[k]
|
||||
case RawService:
|
||||
val = typedVal[k]
|
||||
case map[interface{}]interface{}:
|
||||
val = typedVal[k]
|
||||
}
|
||||
|
||||
if i == len(keys)-1 {
|
||||
return fmt.Sprint(val)
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// Converts map[interface{}]interface{} to map[string]interface{} recursively
|
||||
// gojsonschema only accepts map[string]interface{}
|
||||
func convertServiceMapKeysToStrings(serviceMap RawServiceMap) RawServiceMap {
|
||||
newServiceMap := make(RawServiceMap)
|
||||
|
||||
for k, v := range serviceMap {
|
||||
newServiceMap[k] = convertServiceKeysToStrings(v)
|
||||
}
|
||||
|
||||
return newServiceMap
|
||||
}
|
||||
|
||||
func convertServiceKeysToStrings(service RawService) RawService {
|
||||
newService := make(RawService)
|
||||
|
||||
for k, v := range service {
|
||||
newService[k] = convertKeysToStrings(v)
|
||||
}
|
||||
|
||||
return newService
|
||||
}
|
||||
|
||||
func convertKeysToStrings(item interface{}) interface{} {
|
||||
switch typedDatas := item.(type) {
|
||||
|
||||
case map[interface{}]interface{}:
|
||||
newMap := make(map[string]interface{})
|
||||
|
||||
for key, value := range typedDatas {
|
||||
stringKey := key.(string)
|
||||
newMap[stringKey] = convertKeysToStrings(value)
|
||||
}
|
||||
return newMap
|
||||
|
||||
case []interface{}:
|
||||
// newArray := make([]interface{}, 0) will cause golint to complain
|
||||
var newArray []interface{}
|
||||
newArray = make([]interface{}, 0)
|
||||
|
||||
for _, value := range typedDatas {
|
||||
newArray = append(newArray, convertKeysToStrings(value))
|
||||
}
|
||||
return newArray
|
||||
|
||||
default:
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
||||
var dockerConfigHints = map[string]string{
|
||||
/*
|
||||
"cpu_share": "cpu_shares",
|
||||
"add_host": "extra_hosts",
|
||||
"hosts": "extra_hosts",
|
||||
"extra_host": "extra_hosts",
|
||||
"device": "devices",
|
||||
*/
|
||||
"link": "links",
|
||||
/*
|
||||
"memory_swap": "memswap_limit",
|
||||
"port": "ports",
|
||||
"privilege": "privileged",
|
||||
"priviliged": "privileged",
|
||||
"privilige": "privileged",
|
||||
*/
|
||||
"volume": "volumes",
|
||||
"workdir": "working_dir",
|
||||
}
|
||||
|
||||
func unsupportedConfigMessage(key string, nextErr gojsonschema.ResultError) string {
|
||||
service := serviceNameFromErrorField(nextErr.Field())
|
||||
|
||||
message := fmt.Sprintf("Unsupported config option for %s service: '%s'", service, key)
|
||||
if val, ok := dockerConfigHints[key]; ok {
|
||||
message += fmt.Sprintf(" (did you mean '%s'?)", val)
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
func oneOfMessage(serviceMap RawServiceMap, schema map[string]interface{}, err, nextErr gojsonschema.ResultError) string {
|
||||
switch nextErr.Type() {
|
||||
case "additional_property_not_allowed":
|
||||
property := nextErr.Details()["property"]
|
||||
|
||||
return fmt.Sprintf("contains unsupported option: '%s'", property)
|
||||
case "invalid_type":
|
||||
if containsTypeError(nextErr) {
|
||||
expectedType := addArticle(nextErr.Details()["expected"].(string))
|
||||
|
||||
return fmt.Sprintf("contains %s, which is an invalid type, it should be %s", getValue(serviceMap, nextErr.Context().String()), expectedType)
|
||||
}
|
||||
|
||||
validTypes := parseValidTypesFromSchema(schema, err.Context().String())
|
||||
|
||||
validTypesMsg := addArticle(strings.Join(validTypes, " or "))
|
||||
|
||||
return fmt.Sprintf("contains an invalid type, it should be %s", validTypesMsg)
|
||||
case "unique":
|
||||
contextWithDuplicates := getValue(serviceMap, nextErr.Context().String())
|
||||
|
||||
return fmt.Sprintf("contains non unique items, please remove duplicates from %s", contextWithDuplicates)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func invalidTypeMessage(service, key string, err gojsonschema.ResultError) string {
|
||||
expectedTypesString := err.Details()["expected"].(string)
|
||||
var expectedTypes []string
|
||||
|
||||
if strings.Contains(expectedTypesString, ",") {
|
||||
expectedTypes = strings.Split(expectedTypesString[1:len(expectedTypesString)-1], ",")
|
||||
} else {
|
||||
expectedTypes = []string{expectedTypesString}
|
||||
}
|
||||
|
||||
validTypesMsg := addArticle(strings.Join(expectedTypes, " or "))
|
||||
|
||||
return fmt.Sprintf("Service '%s' configuration key '%s' contains an invalid type, it should be %s.", service, key, validTypesMsg)
|
||||
}
|
||||
|
||||
func validate(serviceMap RawServiceMap, version string) error {
|
||||
if err := setupSchemaLoaders(version); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
serviceMap = convertServiceMapKeysToStrings(serviceMap)
|
||||
|
||||
var validationErrors []string
|
||||
|
||||
dataLoader := gojsonschema.NewGoLoader(serviceMap)
|
||||
|
||||
result, err := gojsonschema.Validate(schemaLoader, dataLoader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// gojsonschema can create extraneous "additional_property_not_allowed" errors in some cases
|
||||
// If this is set, and the error is at root level, skip over that error
|
||||
skipRootAdditionalPropertyError := false
|
||||
|
||||
if !result.Valid() {
|
||||
for i := 0; i < len(result.Errors()); i++ {
|
||||
err := result.Errors()[i]
|
||||
|
||||
if skipRootAdditionalPropertyError && err.Type() == "additional_property_not_allowed" && err.Context().String() == "(root)" {
|
||||
skipRootAdditionalPropertyError = false
|
||||
continue
|
||||
}
|
||||
|
||||
if err.Context().String() == "(root)" {
|
||||
switch err.Type() {
|
||||
case "additional_property_not_allowed":
|
||||
validationErrors = append(validationErrors, fmt.Sprintf("Invalid service name '%s' - only [a-zA-Z0-9\\._\\-] characters are allowed", err.Field()))
|
||||
default:
|
||||
validationErrors = append(validationErrors, err.Description())
|
||||
}
|
||||
|
||||
} else {
|
||||
skipRootAdditionalPropertyError = true
|
||||
|
||||
serviceName := serviceNameFromErrorField(err.Field())
|
||||
key := keyNameFromErrorField(err.Field())
|
||||
|
||||
switch err.Type() {
|
||||
case "additional_property_not_allowed":
|
||||
logrus.Infof("%s %s", serviceName, key)
|
||||
validationErrors = append(validationErrors, unsupportedConfigMessage(key, result.Errors()[i+1]))
|
||||
case "number_one_of":
|
||||
validationErrors = append(validationErrors, fmt.Sprintf("Service '%s' configuration key '%s' %s", serviceName, key, oneOfMessage(serviceMap, schema, err, result.Errors()[i+1])))
|
||||
|
||||
// Next error handled in oneOfMessage, skip over it
|
||||
i++
|
||||
case "invalid_type":
|
||||
validationErrors = append(validationErrors, invalidTypeMessage(serviceName, key, err))
|
||||
case "required":
|
||||
validationErrors = append(validationErrors, fmt.Sprintf("Service '%s' option '%s' is invalid, %s", serviceName, key, err.Description()))
|
||||
case "missing_dependency":
|
||||
dependency := err.Details()["dependency"].(string)
|
||||
validationErrors = append(validationErrors, fmt.Sprintf("Invalid configuration for '%s' service: dependency '%s' is not satisfied", serviceName, dependency))
|
||||
case "unique":
|
||||
contextWithDuplicates := getValue(serviceMap, err.Context().String())
|
||||
validationErrors = append(validationErrors, fmt.Sprintf("Service '%s' configuration key '%s' value %s has non-unique elements", serviceName, key, contextWithDuplicates))
|
||||
default:
|
||||
validationErrors = append(validationErrors, fmt.Sprintf("Service '%s' configuration key %s value %s", serviceName, key, err.Description()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(validationErrors) > 0 {
|
||||
return fmt.Errorf(strings.Join(validationErrors, "\n"))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateServiceConstraints(service RawService, serviceName string) error {
|
||||
if err := setupSchemaLoaders("v1"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
service = convertServiceKeysToStrings(service)
|
||||
|
||||
var validationErrors []string
|
||||
|
||||
dataLoader := gojsonschema.NewGoLoader(service)
|
||||
|
||||
result, err := gojsonschema.Validate(constraintSchemaLoader, dataLoader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !result.Valid() {
|
||||
for _, err := range result.Errors() {
|
||||
if err.Type() == "number_any_of" {
|
||||
_, containsImage := service["image"]
|
||||
_, containsBuild := service["build"]
|
||||
_, containsDockerfile := service["dockerfile"]
|
||||
|
||||
if containsImage && containsBuild {
|
||||
validationErrors = append(validationErrors, fmt.Sprintf("Service '%s' has both an image and build path specified. A service can either be built to image or use an existing image, not both.", serviceName))
|
||||
} else if !containsImage && !containsBuild {
|
||||
validationErrors = append(validationErrors, fmt.Sprintf("Service '%s' has neither an image nor a build path specified. Exactly one must be provided.", serviceName))
|
||||
} else if containsImage && containsDockerfile {
|
||||
validationErrors = append(validationErrors, fmt.Sprintf("Service '%s' has both an image and alternate Dockerfile. A service can either be built to image or use an existing image, not both.", serviceName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf(strings.Join(validationErrors, "\n"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
360
vendor/github.com/hyperhq/libcompose/config/validation_test.go
generated
vendored
Normal file
360
vendor/github.com/hyperhq/libcompose/config/validation_test.go
generated
vendored
Normal file
@@ -0,0 +1,360 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func testValidSchema(t *testing.T, serviceMap RawServiceMap) {
|
||||
err := validate(serviceMap, "v1")
|
||||
assert.Nil(t, err)
|
||||
|
||||
for name, service := range serviceMap {
|
||||
err := validateServiceConstraints(service, name)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func testInvalidSchema(t *testing.T, serviceMap RawServiceMap, errMsgs []string, errCount int) {
|
||||
var combinedErrMsg bytes.Buffer
|
||||
|
||||
err := validate(serviceMap, "v2")
|
||||
if err != nil {
|
||||
combinedErrMsg.WriteString(err.Error())
|
||||
combinedErrMsg.WriteRune('\n')
|
||||
}
|
||||
|
||||
for name, service := range serviceMap {
|
||||
err := validateServiceConstraints(service, name)
|
||||
if err != nil {
|
||||
combinedErrMsg.WriteString(err.Error())
|
||||
combinedErrMsg.WriteRune('\n')
|
||||
}
|
||||
}
|
||||
|
||||
for _, errMsg := range errMsgs {
|
||||
assert.True(t, strings.Contains(combinedErrMsg.String(), errMsg))
|
||||
}
|
||||
|
||||
// gojsonschema has bugs that can cause extraneous errors
|
||||
// This makes sure we don't have more errors than expected
|
||||
assert.True(t, strings.Count(combinedErrMsg.String(), "\n") == errCount)
|
||||
}
|
||||
|
||||
func TestInvalidServiceNames(t *testing.T) {
|
||||
invalidServiceNames := []string{"?not?allowed", " ", "", "!", "/"}
|
||||
|
||||
for _, invalidServiceName := range invalidServiceNames {
|
||||
testInvalidSchema(t, RawServiceMap{
|
||||
invalidServiceName: map[string]interface{}{
|
||||
"image": "busybox",
|
||||
},
|
||||
}, []string{fmt.Sprintf("Invalid service name '%s' - only [a-zA-Z0-9\\._\\-] characters are allowed", invalidServiceName)}, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidServiceNames(t *testing.T) {
|
||||
validServiceNames := []string{"_", "-", ".__.", "_what-up.", "what_.up----", "whatup"}
|
||||
|
||||
for _, validServiceName := range validServiceNames {
|
||||
testValidSchema(t, RawServiceMap{
|
||||
validServiceName: map[string]interface{}{
|
||||
"image": "busybox",
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func TestConfigInvalidPorts(t *testing.T) {
|
||||
portsValues := []interface{}{
|
||||
map[string]interface{}{
|
||||
"1": "8000",
|
||||
},
|
||||
false,
|
||||
0,
|
||||
"8000",
|
||||
}
|
||||
|
||||
for _, portsValue := range portsValues {
|
||||
testInvalidSchema(t, RawServiceMap{
|
||||
"web": map[string]interface{}{
|
||||
"image": "busybox",
|
||||
"ports": portsValue,
|
||||
},
|
||||
}, []string{"Service 'web' configuration key 'ports' contains an invalid type, it should be an array"}, 1)
|
||||
}
|
||||
|
||||
testInvalidSchema(t, RawServiceMap{
|
||||
"web": map[string]interface{}{
|
||||
"image": "busybox",
|
||||
"ports": []interface{}{
|
||||
"8000",
|
||||
"8000",
|
||||
},
|
||||
},
|
||||
}, []string{"Service 'web' configuration key 'ports' value [8000 8000] has non-unique elements"}, 1)
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
func TestConfigValidPorts(t *testing.T) {
|
||||
portsValues := []interface{}{
|
||||
[]interface{}{"8000", "9000"},
|
||||
[]interface{}{"8000"},
|
||||
[]interface{}{8000},
|
||||
[]interface{}{"127.0.0.1::8000"},
|
||||
[]interface{}{"49153-49154:3002-3003"},
|
||||
}
|
||||
|
||||
for _, portsValue := range portsValues {
|
||||
testValidSchema(t, RawServiceMap{
|
||||
"web": map[string]interface{}{
|
||||
"image": "busybox",
|
||||
"ports": portsValue,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
func TestConfigHint(t *testing.T) {
|
||||
testInvalidSchema(t, RawServiceMap{
|
||||
"foo": map[string]interface{}{
|
||||
"image": "busybox",
|
||||
"privilege": "something",
|
||||
},
|
||||
}, []string{"Unsupported config option for foo service: 'privilege'"}, 1)
|
||||
}
|
||||
|
||||
func TestTypeShouldBeAnArray(t *testing.T) {
|
||||
testInvalidSchema(t, RawServiceMap{
|
||||
"foo": map[string]interface{}{
|
||||
"image": "busybox",
|
||||
"links": "an_link",
|
||||
},
|
||||
}, []string{"Service 'foo' configuration key 'links' contains an invalid type, it should be an array"}, 1)
|
||||
}
|
||||
|
||||
func TestInvalidTypeWithMultipleValidTypes(t *testing.T) {
|
||||
testInvalidSchema(t, RawServiceMap{
|
||||
"web": map[string]interface{}{
|
||||
"image": "busybox",
|
||||
"mem_limit": []interface{}{
|
||||
"array_elem",
|
||||
},
|
||||
},
|
||||
}, []string{"Unsupported config option for web service: 'mem_limit'"}, 1)
|
||||
}
|
||||
|
||||
func TestInvalidNotUniqueItems(t *testing.T) {
|
||||
// Test property with array as only valid type
|
||||
testInvalidSchema(t, RawServiceMap{
|
||||
"foo": map[string]interface{}{
|
||||
"image": "busybox",
|
||||
"devices": []string{
|
||||
"/dev/foo:/dev/foo",
|
||||
"/dev/foo:/dev/foo",
|
||||
},
|
||||
},
|
||||
}, []string{"Unsupported config option for foo service: 'devices'"}, 1)
|
||||
|
||||
// Test property with multiple valid types
|
||||
testInvalidSchema(t, RawServiceMap{
|
||||
"foo": map[string]interface{}{
|
||||
"image": "busybox",
|
||||
"environment": []string{
|
||||
"KEY=VAL",
|
||||
"KEY=VAL",
|
||||
},
|
||||
},
|
||||
}, []string{"Service 'foo' configuration key 'environment' contains non unique items, please remove duplicates from [KEY=VAL KEY=VAL]"}, 1)
|
||||
}
|
||||
|
||||
func TestInvalidListOfStringsFormat(t *testing.T) {
|
||||
testInvalidSchema(t, RawServiceMap{
|
||||
"web": map[string]interface{}{
|
||||
"image": "busybox",
|
||||
"command": []interface{}{
|
||||
1,
|
||||
},
|
||||
},
|
||||
}, []string{"Service 'web' configuration key 'command' contains 1, which is an invalid type, it should be a string"}, 1)
|
||||
}
|
||||
|
||||
func TestInvalidExtraHostsString(t *testing.T) {
|
||||
testInvalidSchema(t, RawServiceMap{
|
||||
"web": map[string]interface{}{
|
||||
"image": "busybox",
|
||||
"extra_hosts": "somehost:162.242.195.82",
|
||||
},
|
||||
}, []string{"Unsupported config option for web service: 'extra_hosts'"}, 1)
|
||||
}
|
||||
|
||||
/*
|
||||
func TestValidConfigWhichAllowsTwoTypeDefinitions(t *testing.T) {
|
||||
for _, exposeValue := range []interface{}{"8000", 9000} {
|
||||
testValidSchema(t, RawServiceMap{
|
||||
"web": map[string]interface{}{
|
||||
"image": "busybox",
|
||||
"expose": []interface{}{
|
||||
exposeValue,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
func TestValidConfigOneOfStringOrList(t *testing.T) {
|
||||
entrypointValues := []interface{}{
|
||||
[]interface{}{
|
||||
"sh",
|
||||
},
|
||||
"sh",
|
||||
}
|
||||
|
||||
for _, entrypointValue := range entrypointValues {
|
||||
testValidSchema(t, RawServiceMap{
|
||||
"web": map[string]interface{}{
|
||||
"image": "busybox",
|
||||
"entrypoint": entrypointValue,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidServiceProperty(t *testing.T) {
|
||||
testInvalidSchema(t, RawServiceMap{
|
||||
"web": map[string]interface{}{
|
||||
"image": "busybox",
|
||||
"invalid_property": "value",
|
||||
},
|
||||
}, []string{"Unsupported config option for web service: 'invalid_property'"}, 1)
|
||||
}
|
||||
|
||||
func TestServiceInvalidMissingImageAndBuild(t *testing.T) {
|
||||
testInvalidSchema(t, RawServiceMap{
|
||||
"web": map[string]interface{}{},
|
||||
}, []string{"Service 'web' has neither an image nor a build path specified. Exactly one must be provided."}, 1)
|
||||
}
|
||||
|
||||
func TestServiceInvalidSpecifiesImageAndBuild(t *testing.T) {
|
||||
testInvalidSchema(t, RawServiceMap{
|
||||
"web": map[string]interface{}{
|
||||
"image": "busybox",
|
||||
"build": ".",
|
||||
},
|
||||
}, []string{"Unsupported config option for web service: 'build'"}, 1)
|
||||
}
|
||||
|
||||
func TestServiceInvalidSpecifiesImageAndDockerfile(t *testing.T) {
|
||||
testInvalidSchema(t, RawServiceMap{
|
||||
"web": map[string]interface{}{
|
||||
"image": "busybox",
|
||||
"dockerfile": "Dockerfile",
|
||||
},
|
||||
}, []string{"Unsupported config option for web service: 'dockerfile'"}, 1)
|
||||
}
|
||||
|
||||
func TestInvalidServiceForMultipleErrors(t *testing.T) {
|
||||
testInvalidSchema(t, RawServiceMap{
|
||||
"foo": map[string]interface{}{
|
||||
"image": "busybox",
|
||||
// "ports": "invalid_type",
|
||||
"links": "an_type",
|
||||
"environment": []string{
|
||||
"KEY=VAL",
|
||||
"KEY=VAL",
|
||||
},
|
||||
},
|
||||
}, []string{
|
||||
// "Service 'foo' configuration key 'ports' contains an invalid type, it should be an array",
|
||||
"Service 'foo' configuration key 'links' contains an invalid type, it should be an array",
|
||||
"Service 'foo' configuration key 'environment' contains non unique items, please remove duplicates from [KEY=VAL KEY=VAL]",
|
||||
}, 2)
|
||||
}
|
||||
|
||||
func TestInvalidServiceWithAdditionalProperties(t *testing.T) {
|
||||
testInvalidSchema(t, RawServiceMap{
|
||||
"foo": map[string]interface{}{
|
||||
"image": "busybox",
|
||||
// "ports": "invalid_type",
|
||||
"---": "nope",
|
||||
"environment": []string{
|
||||
"KEY=VAL",
|
||||
"KEY=VAL",
|
||||
},
|
||||
},
|
||||
}, []string{
|
||||
// "Service 'foo' configuration key 'ports' contains an invalid type, it should be an array",
|
||||
"Unsupported config option for foo service: '---'",
|
||||
"Service 'foo' configuration key 'environment' contains non unique items, please remove duplicates from [KEY=VAL KEY=VAL]",
|
||||
}, 2)
|
||||
}
|
||||
|
||||
func TestMultipleInvalidServices(t *testing.T) {
|
||||
testInvalidSchema(t, RawServiceMap{
|
||||
"foo1": map[string]interface{}{
|
||||
"image": "busybox",
|
||||
// "ports": "invalid_type",
|
||||
},
|
||||
"foo2": map[string]interface{}{
|
||||
"image": "busybox",
|
||||
// "ports": "invalid_type",
|
||||
},
|
||||
}, []string{
|
||||
// "Service 'foo1' configuration key 'ports' contains an invalid type, it should be an array",
|
||||
// "Service 'foo2' configuration key 'ports' contains an invalid type, it should be an array",
|
||||
}, 0)
|
||||
}
|
||||
|
||||
func TestMixedInvalidServicesAndInvalidServiceNames(t *testing.T) {
|
||||
testInvalidSchema(t, RawServiceMap{
|
||||
//"foo1": map[string]interface{}{
|
||||
// "image": "busybox",
|
||||
// "ports": "invalid_type",
|
||||
// },
|
||||
"???": map[string]interface{}{
|
||||
"image": "busybox",
|
||||
},
|
||||
// "foo2": map[string]interface{}{
|
||||
// "image": "busybox",
|
||||
// "ports": "invalid_type",
|
||||
// },
|
||||
}, []string{
|
||||
// "Service 'foo1' configuration key 'ports' contains an invalid type, it should be an array",
|
||||
"Invalid service name '???' - only [a-zA-Z0-9\\._\\-] characters are allowed",
|
||||
// "Service 'foo2' configuration key 'ports' contains an invalid type, it should be an array",
|
||||
}, 1)
|
||||
}
|
||||
|
||||
func TestMultipleInvalidServicesForMultipleErrors(t *testing.T) {
|
||||
testInvalidSchema(t, RawServiceMap{
|
||||
"foo1": map[string]interface{}{
|
||||
"image": "busybox",
|
||||
// "ports": "invalid_type",
|
||||
"environment": []string{
|
||||
"KEY=VAL",
|
||||
"KEY=VAL",
|
||||
},
|
||||
},
|
||||
"foo2": map[string]interface{}{
|
||||
"image": "busybox",
|
||||
// "ports": "invalid_type",
|
||||
"environment": []string{
|
||||
"KEY=VAL",
|
||||
"KEY=VAL",
|
||||
},
|
||||
},
|
||||
}, []string{
|
||||
// "Service 'foo1' configuration key 'ports' contains an invalid type, it should be an array",
|
||||
"Service 'foo1' configuration key 'environment' contains non unique items, please remove duplicates from [KEY=VAL KEY=VAL]",
|
||||
// "Service 'foo2' configuration key 'ports' contains an invalid type, it should be an array",
|
||||
"Service 'foo2' configuration key 'environment' contains non unique items, please remove duplicates from [KEY=VAL KEY=VAL]",
|
||||
}, 2)
|
||||
}
|
||||
33
vendor/github.com/hyperhq/libcompose/docker/auth.go
generated
vendored
Normal file
33
vendor/github.com/hyperhq/libcompose/docker/auth.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/hyperhq/hypercli/registry"
|
||||
)
|
||||
|
||||
// AuthLookup defines a method for looking up authentication information
|
||||
type AuthLookup interface {
|
||||
All() map[string]types.AuthConfig
|
||||
Lookup(repoInfo *registry.RepositoryInfo) types.AuthConfig
|
||||
}
|
||||
|
||||
// ConfigAuthLookup implements AuthLookup by reading a Docker config file
|
||||
type ConfigAuthLookup struct {
|
||||
context *Context
|
||||
}
|
||||
|
||||
// Lookup uses a Docker config file to lookup authentication information
|
||||
func (c *ConfigAuthLookup) Lookup(repoInfo *registry.RepositoryInfo) types.AuthConfig {
|
||||
if c.context.ConfigFile == nil || repoInfo == nil || repoInfo.Index == nil {
|
||||
return types.AuthConfig{}
|
||||
}
|
||||
return registry.ResolveAuthConfig(c.context.ConfigFile.AuthConfigs, repoInfo.Index)
|
||||
}
|
||||
|
||||
// All uses a Docker config file to get all authentication information
|
||||
func (c *ConfigAuthLookup) All() map[string]types.AuthConfig {
|
||||
if c.context.ConfigFile == nil {
|
||||
return map[string]types.AuthConfig{}
|
||||
}
|
||||
return c.context.ConfigFile.AuthConfigs
|
||||
}
|
||||
729
vendor/github.com/hyperhq/libcompose/docker/container.go
generated
vendored
Normal file
729
vendor/github.com/hyperhq/libcompose/docker/container.go
generated
vendored
Normal file
@@ -0,0 +1,729 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/engine-api/client"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/container"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/hyperhq/hypercli/pkg/promise"
|
||||
"github.com/hyperhq/hypercli/pkg/stdcopy"
|
||||
"github.com/hyperhq/hypercli/pkg/stringid"
|
||||
"github.com/hyperhq/hypercli/pkg/term"
|
||||
"github.com/hyperhq/libcompose/config"
|
||||
"github.com/hyperhq/libcompose/labels"
|
||||
"github.com/hyperhq/libcompose/logger"
|
||||
"github.com/hyperhq/libcompose/project"
|
||||
"github.com/hyperhq/libcompose/project/events"
|
||||
util "github.com/hyperhq/libcompose/utils"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Container holds information about a docker container and the service it is tied on.
|
||||
type Container struct {
|
||||
name string
|
||||
serviceName string
|
||||
projectName string
|
||||
containerNumber int
|
||||
oneOff bool
|
||||
eventNotifier events.Notifier
|
||||
loggerFactory logger.Factory
|
||||
client client.APIClient
|
||||
|
||||
// FIXME(vdemeester) Remove this dependency
|
||||
service *Service
|
||||
}
|
||||
|
||||
// NewContainer creates a container struct with the specified docker client, name and service.
|
||||
func NewContainer(client client.APIClient, name string, containerNumber int, service *Service) *Container {
|
||||
return &Container{
|
||||
client: client,
|
||||
name: name,
|
||||
containerNumber: containerNumber,
|
||||
|
||||
// TODO(vdemeester) Move these to arguments
|
||||
serviceName: service.name,
|
||||
projectName: service.context.Project.Name,
|
||||
eventNotifier: service.context.Project,
|
||||
loggerFactory: service.context.LoggerFactory,
|
||||
|
||||
// TODO(vdemeester) Remove this dependency
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
// NewOneOffContainer creates a "oneoff" container struct with the specified docker client, name and service.
|
||||
func NewOneOffContainer(client client.APIClient, name string, containerNumber int, service *Service) *Container {
|
||||
c := NewContainer(client, name, containerNumber, service)
|
||||
c.oneOff = true
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Container) findExisting() (*types.ContainerJSON, error) {
|
||||
return GetContainer(c.client, c.name)
|
||||
}
|
||||
|
||||
// Info returns info about the container, like name, command, state or ports.
|
||||
func (c *Container) Info(qFlag bool) (project.Info, error) {
|
||||
container, err := c.findExisting()
|
||||
if err != nil || container == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
infos, err := GetContainersByFilter(c.client, map[string][]string{
|
||||
"name": {container.Name},
|
||||
})
|
||||
if err != nil || len(infos) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
info := infos[0]
|
||||
|
||||
result := project.Info{}
|
||||
if qFlag {
|
||||
result = append(result, project.InfoPart{Key: "Id", Value: container.ID})
|
||||
} else {
|
||||
result = append(result, project.InfoPart{Key: "Name", Value: name(info.Names)})
|
||||
result = append(result, project.InfoPart{Key: "Command", Value: info.Command})
|
||||
result = append(result, project.InfoPart{Key: "State", Value: info.Status})
|
||||
result = append(result, project.InfoPart{Key: "Ports", Value: portString(info.Ports)})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func portString(ports []types.Port) string {
|
||||
result := []string{}
|
||||
|
||||
for _, port := range ports {
|
||||
if port.PublicPort > 0 {
|
||||
result = append(result, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type))
|
||||
} else {
|
||||
result = append(result, fmt.Sprintf("%d/%s", port.PrivatePort, port.Type))
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(result, ", ")
|
||||
}
|
||||
|
||||
func name(names []string) string {
|
||||
max := math.MaxInt32
|
||||
var current string
|
||||
|
||||
for _, v := range names {
|
||||
if len(v) < max {
|
||||
max = len(v)
|
||||
current = v
|
||||
}
|
||||
}
|
||||
|
||||
return current[1:]
|
||||
}
|
||||
|
||||
// Recreate will not refresh the container by means of relaxation and enjoyment,
|
||||
// just delete it and create a new one with the current configuration
|
||||
func (c *Container) Recreate(imageName string) (*types.ContainerJSON, error) {
|
||||
container, err := c.findExisting()
|
||||
if err != nil || container == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hash := container.Config.Labels[labels.HASH.Str()]
|
||||
if hash == "" {
|
||||
return nil, fmt.Errorf("Failed to find hash on old container: %s", container.Name)
|
||||
}
|
||||
|
||||
name := container.Name[1:]
|
||||
newName := fmt.Sprintf("%s-%s", name, container.ID[:12])
|
||||
logrus.Debugf("Renaming %s => %s", name, newName)
|
||||
if err := c.client.ContainerRename(context.Background(), container.ID, newName); err != nil {
|
||||
logrus.Errorf("Failed to rename old container %s", c.name)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newContainer, err := c.createContainer(imageName, container.ID, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logrus.Debugf("Created replacement container %s", newContainer.ID)
|
||||
|
||||
if _, err := c.client.ContainerRemove(context.Background(), container.ID, types.ContainerRemoveOptions{
|
||||
Force: true,
|
||||
RemoveVolumes: false,
|
||||
}); err != nil {
|
||||
logrus.Errorf("Failed to remove old container %s", c.name)
|
||||
return nil, err
|
||||
}
|
||||
logrus.Debugf("Removed old container %s %s", c.name, container.ID)
|
||||
|
||||
return newContainer, nil
|
||||
}
|
||||
|
||||
// Create creates the container based on the specified image name and send an event
|
||||
// to notify the container has been created. If the container already exists, does
|
||||
// nothing.
|
||||
func (c *Container) Create(imageName string) (*types.ContainerJSON, error) {
|
||||
return c.CreateWithOverride(imageName, nil)
|
||||
}
|
||||
|
||||
// CreateWithOverride create container and override parts of the config to
|
||||
// allow special situations to override the config generated from the compose
|
||||
// file
|
||||
func (c *Container) CreateWithOverride(imageName string, configOverride *config.ServiceConfig) (*types.ContainerJSON, error) {
|
||||
container, err := c.findExisting()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if container == nil {
|
||||
container, err = c.createContainer(imageName, "", configOverride)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.eventNotifier.Notify(events.ContainerCreated, c.serviceName, map[string]string{
|
||||
"name": c.Name(),
|
||||
})
|
||||
}
|
||||
|
||||
return container, err
|
||||
}
|
||||
|
||||
// Stop stops the container.
|
||||
func (c *Container) Stop(timeout int) error {
|
||||
return c.withContainer(func(container *types.ContainerJSON) error {
|
||||
return c.client.ContainerStop(context.Background(), container.ID, timeout)
|
||||
})
|
||||
}
|
||||
|
||||
// Pause pauses the container. If the containers are already paused, don't fail.
|
||||
func (c *Container) Pause() error {
|
||||
return c.withContainer(func(container *types.ContainerJSON) error {
|
||||
if !container.State.Paused {
|
||||
return c.client.ContainerPause(context.Background(), container.ID)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Unpause unpauses the container. If the containers are not paused, don't fail.
|
||||
func (c *Container) Unpause() error {
|
||||
return c.withContainer(func(container *types.ContainerJSON) error {
|
||||
if container.State.Paused {
|
||||
return c.client.ContainerUnpause(context.Background(), container.ID)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Kill kill the container.
|
||||
func (c *Container) Kill(signal string) error {
|
||||
return c.withContainer(func(container *types.ContainerJSON) error {
|
||||
return c.client.ContainerKill(context.Background(), container.ID, signal)
|
||||
})
|
||||
}
|
||||
|
||||
// Delete removes the container if existing. If the container is running, it tries
|
||||
// to stop it first.
|
||||
func (c *Container) Delete(removeVolume bool) error {
|
||||
container, err := c.findExisting()
|
||||
if err != nil || container == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, err := c.client.ContainerInspect(context.Background(), container.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !info.State.Running {
|
||||
_, err := c.client.ContainerRemove(context.Background(), container.ID, types.ContainerRemoveOptions{
|
||||
Force: true,
|
||||
RemoveVolumes: removeVolume,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsRunning returns the running state of the container.
|
||||
func (c *Container) IsRunning() (bool, error) {
|
||||
container, err := c.findExisting()
|
||||
if err != nil || container == nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
info, err := c.client.ContainerInspect(context.Background(), container.ID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return info.State.Running, nil
|
||||
}
|
||||
|
||||
// Run creates, start and attach to the container based on the image name,
|
||||
// the specified configuration.
|
||||
// It will always create a new container.
|
||||
func (c *Container) Run(ctx context.Context, imageName string, configOverride *config.ServiceConfig) (int, error) {
|
||||
var (
|
||||
errCh chan error
|
||||
out, stderr io.Writer
|
||||
in io.ReadCloser
|
||||
)
|
||||
|
||||
container, err := c.createContainer(imageName, "", configOverride)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
if configOverride.StdinOpen {
|
||||
in = os.Stdin
|
||||
}
|
||||
if configOverride.Tty {
|
||||
out = os.Stdout
|
||||
}
|
||||
if configOverride.Tty {
|
||||
stderr = os.Stderr
|
||||
}
|
||||
|
||||
options := types.ContainerAttachOptions{
|
||||
Stream: true,
|
||||
Stdin: configOverride.StdinOpen,
|
||||
Stdout: configOverride.Tty,
|
||||
Stderr: configOverride.Tty,
|
||||
}
|
||||
|
||||
resp, err := c.client.ContainerAttach(ctx, container.ID, options)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// set raw terminal
|
||||
inFd, _ := term.GetFdInfo(in)
|
||||
state, err := term.SetRawTerminal(inFd)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
// restore raw terminal
|
||||
defer term.RestoreTerminal(inFd, state)
|
||||
// holdHijackedConnection (in goroutine)
|
||||
errCh = promise.Go(func() error {
|
||||
return holdHijackedConnection(configOverride.Tty, in, out, stderr, resp)
|
||||
})
|
||||
|
||||
if err := c.client.ContainerStart(ctx, container.ID, ""); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
if err := <-errCh; err != nil {
|
||||
logrus.Debugf("Error hijack: %s", err)
|
||||
return -1, err
|
||||
}
|
||||
|
||||
var status int
|
||||
// Attached mode
|
||||
if c.service.context.Autoremove {
|
||||
// Warn user if they detached us
|
||||
js, err := c.client.ContainerInspect(ctx, container.ID)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
if js.State.Running == true || js.State.Paused == true {
|
||||
logrus.Infof("Detached from %s, awaiting its termination in order to uphold \"--rm\".\n",
|
||||
stringid.TruncateID(container.ID))
|
||||
}
|
||||
|
||||
// Autoremove: wait for the container to finish, retrieve
|
||||
// the exit code and remove the container
|
||||
if status, err = c.client.ContainerWait(ctx, container.ID); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
exitedContainer, err := c.client.ContainerInspect(ctx, container.ID)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
status = exitedContainer.State.ExitCode
|
||||
} else {
|
||||
// No Autoremove: Simply retrieve the exit code
|
||||
if !configOverride.Tty {
|
||||
// In non-TTY mode, we can't detach, so we must wait for container exit
|
||||
if status, err = c.client.ContainerWait(ctx, container.ID); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
} else {
|
||||
// In TTY mode, there is a race: if the process dies too slowly, the state could
|
||||
// be updated after the getExitCode call and result in the wrong exit code being reported
|
||||
exitedContainer, err := c.client.ContainerInspect(ctx, container.ID)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
status = exitedContainer.State.ExitCode
|
||||
}
|
||||
}
|
||||
|
||||
return status, nil
|
||||
}
|
||||
|
||||
func holdHijackedConnection(tty bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error {
|
||||
var err error
|
||||
receiveStdout := make(chan error, 1)
|
||||
if outputStream != nil || errorStream != nil {
|
||||
go func() {
|
||||
// When TTY is ON, use regular copy
|
||||
if tty && outputStream != nil {
|
||||
_, err = io.Copy(outputStream, resp.Reader)
|
||||
} else {
|
||||
_, err = stdcopy.StdCopy(outputStream, errorStream, resp.Reader)
|
||||
}
|
||||
logrus.Debugf("[hijack] End of stdout")
|
||||
receiveStdout <- err
|
||||
}()
|
||||
}
|
||||
|
||||
stdinDone := make(chan struct{})
|
||||
go func() {
|
||||
if inputStream != nil {
|
||||
io.Copy(resp.Conn, inputStream)
|
||||
logrus.Debugf("[hijack] End of stdin")
|
||||
}
|
||||
|
||||
if err := resp.CloseWrite(); err != nil {
|
||||
logrus.Debugf("Couldn't send EOF: %s", err)
|
||||
}
|
||||
close(stdinDone)
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-receiveStdout:
|
||||
if err != nil {
|
||||
logrus.Debugf("Error receiveStdout: %s", err)
|
||||
return err
|
||||
}
|
||||
case <-stdinDone:
|
||||
if outputStream != nil || errorStream != nil {
|
||||
if err := <-receiveStdout; err != nil {
|
||||
logrus.Debugf("Error receiveStdout: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Up creates and start the container based on the image name and send an event
|
||||
// to notify the container has been created. If the container exists but is stopped
|
||||
// it tries to start it.
|
||||
func (c *Container) Up(imageName string) error {
|
||||
var err error
|
||||
|
||||
container, err := c.Create(imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !container.State.Running {
|
||||
c.Start(container)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start the specified container with the specified host config
|
||||
func (c *Container) Start(container *types.ContainerJSON) error {
|
||||
logrus.WithFields(logrus.Fields{"container.ID": container.ID, "c.name": c.name}).Debug("Starting container")
|
||||
if err := c.client.ContainerStart(context.Background(), container.ID, ""); err != nil {
|
||||
logrus.WithFields(logrus.Fields{"container.ID": container.ID, "c.name": c.name}).Debug("Failed to start container")
|
||||
return err
|
||||
}
|
||||
c.eventNotifier.Notify(events.ContainerStarted, c.serviceName, map[string]string{
|
||||
"name": c.Name(),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// OutOfSync checks if the container is out of sync with the service definition.
|
||||
// It looks if the the service hash container label is the same as the computed one.
|
||||
func (c *Container) OutOfSync(imageName string) (bool, error) {
|
||||
container, err := c.findExisting()
|
||||
if err != nil || container == nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if container.Config.Image != imageName {
|
||||
logrus.Debugf("Images for %s do not match %s!=%s", c.name, container.Config.Image, imageName)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if container.Config.Labels[labels.HASH.Str()] != c.getHash() {
|
||||
logrus.Debugf("Hashes for %s do not match %s!=%s", c.name, container.Config.Labels[labels.HASH.Str()], c.getHash())
|
||||
return true, nil
|
||||
}
|
||||
|
||||
image, _, err := c.client.ImageInspectWithRaw(context.Background(), container.Config.Image, false)
|
||||
if err != nil {
|
||||
if client.IsErrImageNotFound(err) {
|
||||
logrus.Debugf("Image %s do not exist, do not know if it's out of sync", container.Config.Image)
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
logrus.Debugf("Checking existing image name vs id: %s == %s", image.ID, container.Image)
|
||||
return image.ID != container.Image, err
|
||||
}
|
||||
|
||||
func (c *Container) getHash() string {
|
||||
return config.GetServiceHash(c.serviceName, c.service.Config())
|
||||
}
|
||||
|
||||
func volumeBinds(volumes map[string]struct{}, container *types.ContainerJSON) []string {
|
||||
result := make([]string, 0, len(container.Mounts))
|
||||
for _, mount := range container.Mounts {
|
||||
if _, ok := volumes[mount.Destination]; ok {
|
||||
result = append(result, fmt.Sprint(mount.Source, ":", mount.Destination))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *Container) createContainer(imageName, oldContainer string, configOverride *config.ServiceConfig) (*types.ContainerJSON, error) {
|
||||
serviceConfig := c.service.serviceConfig
|
||||
if configOverride != nil {
|
||||
serviceConfig.Command = configOverride.Command
|
||||
serviceConfig.Tty = configOverride.Tty
|
||||
serviceConfig.StdinOpen = configOverride.StdinOpen
|
||||
}
|
||||
configWrapper, err := ConvertToAPI(c.service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
configWrapper.Config.Image = imageName
|
||||
|
||||
if configWrapper.Config.Labels == nil {
|
||||
configWrapper.Config.Labels = map[string]string{}
|
||||
}
|
||||
|
||||
oneOffString := "False"
|
||||
if c.oneOff {
|
||||
oneOffString = "True"
|
||||
}
|
||||
|
||||
configWrapper.Config.Labels[labels.SERVICE.Str()] = c.serviceName
|
||||
configWrapper.Config.Labels[labels.PROJECT.Str()] = c.projectName
|
||||
configWrapper.Config.Labels[labels.HASH.Str()] = c.getHash()
|
||||
configWrapper.Config.Labels[labels.ONEOFF.Str()] = oneOffString
|
||||
configWrapper.Config.Labels[labels.NUMBER.Str()] = fmt.Sprint(c.containerNumber)
|
||||
configWrapper.Config.Labels[labels.VERSION.Str()] = ComposeVersion
|
||||
size := "s4"
|
||||
if serviceConfig.Size != "" {
|
||||
size = serviceConfig.Size
|
||||
}
|
||||
configWrapper.Config.Labels["sh_hyper_instancetype"] = size
|
||||
|
||||
err = c.populateAdditionalHostConfig(configWrapper.HostConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if oldContainer != "" {
|
||||
info, err := c.client.ContainerInspect(context.Background(), oldContainer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configWrapper.HostConfig.Binds = util.Merge(configWrapper.HostConfig.Binds, volumeBinds(configWrapper.Config.Volumes, &info))
|
||||
}
|
||||
|
||||
logrus.Debugf("Creating container %s %#v", c.name, configWrapper)
|
||||
|
||||
container, err := c.client.ContainerCreate(context.Background(), configWrapper.Config, configWrapper.HostConfig, configWrapper.NetworkingConfig, c.name)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to create container %s: %v", c.name, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return GetContainer(c.client, container.ID)
|
||||
}
|
||||
|
||||
func (c *Container) populateAdditionalHostConfig(hostConfig *container.HostConfig) error {
|
||||
links := map[string]string{}
|
||||
|
||||
for _, link := range c.service.DependentServices() {
|
||||
if !c.service.context.Project.ServiceConfigs.Has(link.Target) {
|
||||
continue
|
||||
}
|
||||
|
||||
service, err := c.service.context.Project.CreateService(link.Target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
containers, err := service.Containers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if link.Type == project.RelTypeLink {
|
||||
c.addLinks(links, service, link, containers)
|
||||
} else if link.Type == project.RelTypeIpcNamespace {
|
||||
hostConfig, err = c.addIpc(hostConfig, service, containers)
|
||||
} else if link.Type == project.RelTypeNetNamespace {
|
||||
hostConfig, err = c.addNetNs(hostConfig, service, containers)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
hostConfig.Links = []string{}
|
||||
for k, v := range links {
|
||||
hostConfig.Links = append(hostConfig.Links, strings.Join([]string{v, k}, ":"))
|
||||
}
|
||||
for _, v := range c.service.Config().ExternalLinks {
|
||||
hostConfig.Links = append(hostConfig.Links, v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Container) addLinks(links map[string]string, service project.Service, rel project.ServiceRelationship, containers []project.Container) {
|
||||
for _, container := range containers {
|
||||
if _, ok := links[rel.Alias]; !ok {
|
||||
links[rel.Alias] = container.Name()
|
||||
}
|
||||
|
||||
links[container.Name()] = container.Name()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Container) addIpc(config *container.HostConfig, service project.Service, containers []project.Container) (*container.HostConfig, error) {
|
||||
/*
|
||||
if len(containers) == 0 {
|
||||
return nil, fmt.Errorf("Failed to find container for IPC %v", c.service.Config().Ipc)
|
||||
}
|
||||
|
||||
id, err := containers[0].ID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config.IpcMode = container.IpcMode("container:" + id)
|
||||
*/
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (c *Container) addNetNs(config *container.HostConfig, service project.Service, containers []project.Container) (*container.HostConfig, error) {
|
||||
/*
|
||||
if len(containers) == 0 {
|
||||
return nil, fmt.Errorf("Failed to find container for networks ns %v", c.service.Config().NetworkMode)
|
||||
}
|
||||
|
||||
id, err := containers[0].ID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config.NetworkMode = container.NetworkMode("container:" + id)
|
||||
*/
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// ID returns the container Id.
|
||||
func (c *Container) ID() (string, error) {
|
||||
container, err := c.findExisting()
|
||||
if container == nil {
|
||||
return "", err
|
||||
}
|
||||
return container.ID, err
|
||||
}
|
||||
|
||||
// Name returns the container name.
|
||||
func (c *Container) Name() string {
|
||||
return c.name
|
||||
}
|
||||
|
||||
// Restart restarts the container if existing, does nothing otherwise.
|
||||
func (c *Container) Restart(timeout int) error {
|
||||
container, err := c.findExisting()
|
||||
if err != nil || container == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.client.ContainerRestart(context.Background(), container.ID, timeout)
|
||||
}
|
||||
|
||||
// Log forwards container logs to the project configured logger.
|
||||
func (c *Container) Log(follow bool) error {
|
||||
container, err := c.findExisting()
|
||||
if container == nil || err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, err := c.client.ContainerInspect(context.Background(), container.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// FIXME(vdemeester) update container struct to do less API calls
|
||||
name := fmt.Sprintf("%s-%d", c.service.name, c.containerNumber)
|
||||
l := c.loggerFactory.Create(name)
|
||||
|
||||
options := types.ContainerLogsOptions{
|
||||
ShowStdout: true,
|
||||
ShowStderr: true,
|
||||
Follow: follow,
|
||||
Tail: "all",
|
||||
}
|
||||
responseBody, err := c.client.ContainerLogs(context.Background(), container.ID, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer responseBody.Close()
|
||||
|
||||
if info.Config.Tty {
|
||||
_, err = io.Copy(&logger.Wrapper{Logger: l}, responseBody)
|
||||
} else {
|
||||
_, err = stdcopy.StdCopy(&logger.Wrapper{Logger: l}, &logger.Wrapper{Logger: l, Err: true}, responseBody)
|
||||
}
|
||||
logrus.WithFields(logrus.Fields{"Logger": l, "err": err}).Debug("c.client.Logs() returned error")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Container) withContainer(action func(*types.ContainerJSON) error) error {
|
||||
container, err := c.findExisting()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if container != nil {
|
||||
return action(container)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Port returns the host port the specified port is mapped on.
|
||||
func (c *Container) Port(port string) (string, error) {
|
||||
container, err := c.findExisting()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if bindings, ok := container.NetworkSettings.Ports[nat.Port(port)]; ok {
|
||||
result := []string{}
|
||||
for _, binding := range bindings {
|
||||
result = append(result, binding.HostIP+":"+binding.HostPort)
|
||||
}
|
||||
|
||||
return strings.Join(result, "\n"), nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
36
vendor/github.com/hyperhq/libcompose/docker/context.go
generated
vendored
Normal file
36
vendor/github.com/hyperhq/libcompose/docker/context.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/hyperhq/hypercli/cliconfig"
|
||||
"github.com/hyperhq/libcompose/project"
|
||||
)
|
||||
|
||||
// Context holds context meta information about a libcompose project and docker
|
||||
// client information (like configuration file, builder to use, …)
|
||||
type Context struct {
|
||||
project.Context
|
||||
ClientFactory project.ClientFactory
|
||||
ConfigDir string
|
||||
ConfigFile *cliconfig.ConfigFile
|
||||
AuthLookup AuthLookup
|
||||
}
|
||||
|
||||
func (c *Context) open() error {
|
||||
return c.LookupConfig()
|
||||
}
|
||||
|
||||
// LookupConfig tries to load the docker configuration files, if any.
|
||||
func (c *Context) LookupConfig() error {
|
||||
if c.ConfigFile != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
config, err := cliconfig.Load(c.ConfigDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.ConfigFile = config
|
||||
|
||||
return nil
|
||||
}
|
||||
257
vendor/github.com/hyperhq/libcompose/docker/convert.go
generated
vendored
Normal file
257
vendor/github.com/hyperhq/libcompose/docker/convert.go
generated
vendored
Normal file
@@ -0,0 +1,257 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/docker/engine-api/types/container"
|
||||
"github.com/docker/engine-api/types/network"
|
||||
"github.com/docker/engine-api/types/strslice"
|
||||
"github.com/hyperhq/libcompose/config"
|
||||
"github.com/hyperhq/libcompose/project"
|
||||
"github.com/hyperhq/libcompose/utils"
|
||||
"github.com/hyperhq/hypercli/runconfig/opts"
|
||||
)
|
||||
|
||||
// ConfigWrapper wraps Config, HostConfig and NetworkingConfig for a container.
|
||||
type ConfigWrapper struct {
|
||||
Config *container.Config
|
||||
HostConfig *container.HostConfig
|
||||
NetworkingConfig *network.NetworkingConfig
|
||||
}
|
||||
|
||||
// Filter filters the specified string slice with the specified function.
|
||||
func Filter(vs []string, f func(string) bool) []string {
|
||||
r := make([]string, 0, len(vs))
|
||||
for _, v := range vs {
|
||||
if f(v) {
|
||||
r = append(r, v)
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func isBind(s string) bool {
|
||||
return strings.ContainsRune(s, ':')
|
||||
}
|
||||
|
||||
func isVolume(s string) bool {
|
||||
return !isBind(s)
|
||||
}
|
||||
|
||||
// ConvertToAPI converts a service configuration to a docker API container configuration.
|
||||
func ConvertToAPI(s *Service) (*ConfigWrapper, error) {
|
||||
config, hostConfig, err := Convert(s.serviceConfig, s.context.Context)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := ConfigWrapper{
|
||||
Config: config,
|
||||
HostConfig: hostConfig,
|
||||
NetworkingConfig: &network.NetworkingConfig{},
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func isNamedVolume(volume string) bool {
|
||||
return !strings.HasPrefix(volume, ".") && !strings.HasPrefix(volume, "/") && !strings.HasPrefix(volume, "~")
|
||||
}
|
||||
|
||||
func volumes(c *config.ServiceConfig, ctx project.Context) map[string]struct{} {
|
||||
volumes := make(map[string]struct{}, len(c.Volumes))
|
||||
for k, v := range c.Volumes {
|
||||
if len(ctx.ComposeFiles) > 0 && !isNamedVolume(v) {
|
||||
v = ctx.ResourceLookup.ResolvePath(v, ctx.ComposeFiles[0])
|
||||
}
|
||||
|
||||
c.Volumes[k] = v
|
||||
if isVolume(v) {
|
||||
volumes[v] = struct{}{}
|
||||
}
|
||||
}
|
||||
return volumes
|
||||
}
|
||||
|
||||
func restartPolicy(c *config.ServiceConfig) (*container.RestartPolicy, error) {
|
||||
restart, err := opts.ParseRestartPolicy(c.Restart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &container.RestartPolicy{Name: restart.Name, MaximumRetryCount: restart.MaximumRetryCount}, nil
|
||||
}
|
||||
|
||||
/*
|
||||
func ports(c *config.ServiceConfig) (map[nat.Port]struct{}, nat.PortMap, error) {
|
||||
ports, binding, err := nat.ParsePortSpecs(c.Ports)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
exPorts, _, err := nat.ParsePortSpecs(c.Expose)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for k, v := range exPorts {
|
||||
ports[k] = v
|
||||
}
|
||||
|
||||
exposedPorts := map[nat.Port]struct{}{}
|
||||
for k, v := range ports {
|
||||
exposedPorts[nat.Port(k)] = v
|
||||
}
|
||||
|
||||
portBindings := nat.PortMap{}
|
||||
for k, bv := range binding {
|
||||
dcbs := make([]nat.PortBinding, len(bv))
|
||||
for k, v := range bv {
|
||||
dcbs[k] = nat.PortBinding{HostIP: v.HostIP, HostPort: v.HostPort}
|
||||
}
|
||||
portBindings[nat.Port(k)] = dcbs
|
||||
}
|
||||
return exposedPorts, portBindings, nil
|
||||
}
|
||||
*/
|
||||
|
||||
// Convert converts a service configuration to an docker API structures (Config and HostConfig)
|
||||
func Convert(c *config.ServiceConfig, ctx project.Context) (*container.Config, *container.HostConfig, error) {
|
||||
restartPolicy, err := restartPolicy(c)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
/*
|
||||
exposedPorts, portBindings, err := ports(c)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
deviceMappings, err := parseDevices(c.Devices)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var volumesFrom []string
|
||||
if c.VolumesFrom != nil {
|
||||
volumesFrom, err = getVolumesFrom(c.VolumesFrom, ctx.Project.ServiceConfigs, ctx.ProjectName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
*/
|
||||
config := &container.Config{
|
||||
Entrypoint: strslice.StrSlice(utils.CopySlice(c.Entrypoint)),
|
||||
Hostname: c.Hostname,
|
||||
Domainname: c.DomainName,
|
||||
//User: c.User,
|
||||
Env: utils.CopySlice(c.Environment),
|
||||
Cmd: strslice.StrSlice(utils.CopySlice(c.Command)),
|
||||
Image: c.Image,
|
||||
Labels: utils.CopyMap(c.Labels),
|
||||
//ExposedPorts: exposedPorts,
|
||||
Tty: c.Tty,
|
||||
OpenStdin: c.StdinOpen,
|
||||
WorkingDir: c.WorkingDir,
|
||||
Volumes: volumes(c, ctx),
|
||||
//MacAddress: c.MacAddress,
|
||||
}
|
||||
|
||||
/*
|
||||
ulimits := []*units.Ulimit{}
|
||||
if c.Ulimits.Elements != nil {
|
||||
for _, ulimit := range c.Ulimits.Elements {
|
||||
ulimits = append(ulimits, &units.Ulimit{
|
||||
Name: ulimit.Name,
|
||||
Soft: ulimit.Soft,
|
||||
Hard: ulimit.Hard,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
resources := container.Resources{
|
||||
CgroupParent: c.CgroupParent,
|
||||
Memory: c.MemLimit,
|
||||
MemorySwap: c.MemSwapLimit,
|
||||
CPUShares: c.CPUShares,
|
||||
CPUQuota: c.CPUQuota,
|
||||
CpusetCpus: c.CPUSet,
|
||||
Ulimits: ulimits,
|
||||
Devices: deviceMappings,
|
||||
}
|
||||
|
||||
dns := strslice.StrSlice(c.DNS)
|
||||
dnsSearch := strslice.StrSlice(c.DNSSearch)
|
||||
*/
|
||||
hostConfig := &container.HostConfig{
|
||||
/*
|
||||
VolumesFrom: volumesFrom,
|
||||
CapAdd: strslice.New(c.CapAdd...),
|
||||
CapDrop: strslice.New(c.CapDrop...),
|
||||
ExtraHosts: utils.CopySlice(c.ExtraHosts),
|
||||
Privileged: c.Privileged,
|
||||
*/
|
||||
Binds: Filter(c.Volumes, isBind),
|
||||
/*
|
||||
DNS: (&dns).Slice(),
|
||||
DNSSearch: (&dnsSearch).Slice(),
|
||||
LogConfig: container.LogConfig{
|
||||
Type: c.Logging.Driver,
|
||||
Config: utils.CopyMap(c.Logging.Options),
|
||||
},
|
||||
*/
|
||||
NetworkMode: "bridge",
|
||||
/*
|
||||
ReadonlyRootfs: c.ReadOnly,
|
||||
PidMode: container.PidMode(c.Pid),
|
||||
UTSMode: container.UTSMode(c.Uts),
|
||||
IpcMode: container.IpcMode(c.Ipc),
|
||||
PortBindings: portBindings,
|
||||
*/
|
||||
RestartPolicy: *restartPolicy,
|
||||
/*
|
||||
SecurityOpt: utils.CopySlice(c.SecurityOpt),
|
||||
VolumeDriver: c.VolumeDriver,
|
||||
Resources: resources,
|
||||
*/
|
||||
}
|
||||
|
||||
return config, hostConfig, nil
|
||||
}
|
||||
|
||||
/*
|
||||
func getVolumesFrom(volumesFrom []string, serviceConfigs *config.ServiceConfigs, projectName string) ([]string, error) {
|
||||
volumes := []string{}
|
||||
for _, volumeFrom := range volumesFrom {
|
||||
if serviceConfig, ok := serviceConfigs.Get(volumeFrom); ok {
|
||||
// It's a service - Use the first one
|
||||
name := fmt.Sprintf("%s_%s_1", projectName, volumeFrom)
|
||||
// If a container name is specified, use that instead
|
||||
if serviceConfig.ContainerName != "" {
|
||||
name = serviceConfig.ContainerName
|
||||
}
|
||||
volumes = append(volumes, name)
|
||||
} else {
|
||||
volumes = append(volumes, volumeFrom)
|
||||
}
|
||||
}
|
||||
return volumes, nil
|
||||
}
|
||||
|
||||
func parseDevices(devices []string) ([]container.DeviceMapping, error) {
|
||||
// parse device mappings
|
||||
deviceMappings := []container.DeviceMapping{}
|
||||
for _, device := range devices {
|
||||
v, err := opts.ParseDevice(device)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deviceMappings = append(deviceMappings, container.DeviceMapping{
|
||||
PathOnHost: v.PathOnHost,
|
||||
PathInContainer: v.PathInContainer,
|
||||
CgroupPermissions: v.CgroupPermissions,
|
||||
})
|
||||
}
|
||||
|
||||
return deviceMappings, nil
|
||||
}
|
||||
*/
|
||||
58
vendor/github.com/hyperhq/libcompose/docker/convert_test.go
generated
vendored
Normal file
58
vendor/github.com/hyperhq/libcompose/docker/convert_test.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
shlex "github.com/flynn/go-shlex"
|
||||
"github.com/hyperhq/libcompose/config"
|
||||
"github.com/hyperhq/libcompose/lookup"
|
||||
"github.com/hyperhq/libcompose/yaml"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseCommand(t *testing.T) {
|
||||
exp := []string{"sh", "-c", "exec /opt/bin/flanneld -logtostderr=true -iface=${NODE_IP}"}
|
||||
cmd, err := shlex.Split("sh -c 'exec /opt/bin/flanneld -logtostderr=true -iface=${NODE_IP}'")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, exp, cmd)
|
||||
}
|
||||
|
||||
func TestParseBindsAndVolumes(t *testing.T) {
|
||||
ctx := &Context{}
|
||||
ctx.ComposeFiles = []string{"foo/docker-compose.yml"}
|
||||
ctx.ResourceLookup = &lookup.FileConfigLookup{}
|
||||
|
||||
abs, err := filepath.Abs(".")
|
||||
assert.Nil(t, err)
|
||||
cfg, hostCfg, err := Convert(&config.ServiceConfig{
|
||||
Volumes: []string{"/foo", "/home:/home", "/bar/baz", ".:/home", "/usr/lib:/usr/lib:ro"},
|
||||
}, ctx.Context)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, map[string]struct{}{"/foo": {}, "/bar/baz": {}}, cfg.Volumes)
|
||||
assert.Equal(t, []string{"/home:/home", abs + "/foo:/home", "/usr/lib:/usr/lib:ro"}, hostCfg.Binds)
|
||||
}
|
||||
|
||||
func TestParseLabels(t *testing.T) {
|
||||
ctx := &Context{}
|
||||
ctx.ComposeFiles = []string{"foo/docker-compose.yml"}
|
||||
ctx.ResourceLookup = &lookup.FileConfigLookup{}
|
||||
bashCmd := "bash"
|
||||
fooLabel := "foo.label"
|
||||
fooLabelValue := "service.config.value"
|
||||
sc := &config.ServiceConfig{
|
||||
Entrypoint: yaml.Command([]string{bashCmd}),
|
||||
Labels: yaml.SliceorMap{fooLabel: "service.config.value"},
|
||||
}
|
||||
cfg, _, err := Convert(sc, ctx.Context)
|
||||
assert.Nil(t, err)
|
||||
|
||||
cfg.Labels[fooLabel] = "FUN"
|
||||
cfg.Entrypoint[0] = "less"
|
||||
|
||||
assert.Equal(t, fooLabelValue, sc.Labels[fooLabel])
|
||||
assert.Equal(t, "FUN", cfg.Labels[fooLabel])
|
||||
|
||||
assert.Equal(t, yaml.Command{bashCmd}, sc.Entrypoint)
|
||||
assert.Equal(t, []string{"less"}, []string(cfg.Entrypoint))
|
||||
}
|
||||
41
vendor/github.com/hyperhq/libcompose/docker/functions.go
generated
vendored
Normal file
41
vendor/github.com/hyperhq/libcompose/docker/functions.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/docker/engine-api/client"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/filters"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// GetContainersByFilter looks up the hosts containers with the specified filters and
|
||||
// returns a list of container matching it, or an error.
|
||||
func GetContainersByFilter(clientInstance client.APIClient, containerFilters ...map[string][]string) ([]types.Container, error) {
|
||||
filterArgs := filters.NewArgs()
|
||||
|
||||
// FIXME(vdemeester) I don't like 3 for loops >_<
|
||||
for _, filter := range containerFilters {
|
||||
for key, filterValue := range filter {
|
||||
for _, value := range filterValue {
|
||||
filterArgs.Add(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return clientInstance.ContainerList(context.Background(), types.ContainerListOptions{
|
||||
All: true,
|
||||
Filter: filterArgs,
|
||||
})
|
||||
}
|
||||
|
||||
// GetContainer looks up the hosts containers with the specified ID
|
||||
// or name and returns it, or an error.
|
||||
func GetContainer(clientInstance client.APIClient, id string) (*types.ContainerJSON, error) {
|
||||
container, err := clientInstance.ContainerInspect(context.Background(), id)
|
||||
if err != nil {
|
||||
if client.IsErrContainerNotFound(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &container, nil
|
||||
}
|
||||
80
vendor/github.com/hyperhq/libcompose/docker/image.go
generated
vendored
Normal file
80
vendor/github.com/hyperhq/libcompose/docker/image.go
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/engine-api/client"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/hyperhq/hypercli/pkg/jsonmessage"
|
||||
"github.com/hyperhq/hypercli/pkg/term"
|
||||
"github.com/hyperhq/hypercli/reference"
|
||||
"github.com/hyperhq/hypercli/registry"
|
||||
)
|
||||
|
||||
func removeImage(client client.APIClient, image string) error {
|
||||
_, err := client.ImageRemove(context.Background(), image, types.ImageRemoveOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
func pullImage(client client.APIClient, service *Service, image string) error {
|
||||
fmt.Fprintf(os.Stderr, "Pulling %s (%s)...\n", service.name, image)
|
||||
distributionRef, err := reference.ParseNamed(image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repoInfo, err := registry.ParseRepositoryInfo(distributionRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authConfig := service.context.AuthLookup.Lookup(repoInfo)
|
||||
|
||||
encodedAuth, err := encodeAuthToBase64(authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
options := types.ImagePullOptions{
|
||||
RegistryAuth: encodedAuth,
|
||||
}
|
||||
responseBody, err := client.ImagePull(context.Background(), distributionRef.String(), options)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to pull image %s: %v", image, err)
|
||||
return err
|
||||
}
|
||||
defer responseBody.Close()
|
||||
|
||||
var writeBuff io.Writer = os.Stderr
|
||||
|
||||
outFd, isTerminalOut := term.GetFdInfo(os.Stderr)
|
||||
|
||||
err = jsonmessage.DisplayJSONMessagesStream(responseBody, writeBuff, outFd, isTerminalOut, nil)
|
||||
if err != nil {
|
||||
if jerr, ok := err.(*jsonmessage.JSONError); ok {
|
||||
// If no error code is set, default to 1
|
||||
if jerr.Code == 0 {
|
||||
jerr.Code = 1
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "%s", writeBuff)
|
||||
return fmt.Errorf("Status: %s, Code: %d", jerr.Message, jerr.Code)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// encodeAuthToBase64 serializes the auth configuration as JSON base64 payload
|
||||
func encodeAuthToBase64(authConfig types.AuthConfig) (string, error) {
|
||||
buf, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(buf), nil
|
||||
}
|
||||
92
vendor/github.com/hyperhq/libcompose/docker/name.go
generated
vendored
Normal file
92
vendor/github.com/hyperhq/libcompose/docker/name.go
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/engine-api/client"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/filters"
|
||||
"github.com/hyperhq/libcompose/labels"
|
||||
)
|
||||
|
||||
const format = "%s-%s-%d"
|
||||
|
||||
// Namer defines method to provide container name.
|
||||
type Namer interface {
|
||||
Next() (string, int)
|
||||
}
|
||||
|
||||
type defaultNamer struct {
|
||||
project string
|
||||
service string
|
||||
oneOff bool
|
||||
currentNumber int
|
||||
}
|
||||
|
||||
type singleNamer struct {
|
||||
name string
|
||||
}
|
||||
|
||||
// NewSingleNamer returns a namer that only allows a single name.
|
||||
func NewSingleNamer(name string) Namer {
|
||||
return &singleNamer{name}
|
||||
}
|
||||
|
||||
// NewNamer returns a namer that returns names based on the specified project and
|
||||
// service name and an inner counter, e.g. project_service_1, project_service_2…
|
||||
func NewNamer(client client.APIClient, project, service string, oneOff bool) (Namer, error) {
|
||||
namer := &defaultNamer{
|
||||
project: project,
|
||||
service: service,
|
||||
oneOff: oneOff,
|
||||
}
|
||||
|
||||
filter := filters.NewArgs()
|
||||
filter.Add("label", fmt.Sprintf("%s=%s", labels.PROJECT.Str(), project))
|
||||
filter.Add("label", fmt.Sprintf("%s=%s", labels.SERVICE.Str(), service))
|
||||
if oneOff {
|
||||
filter.Add("label", fmt.Sprintf("%s=%s", labels.ONEOFF.Str(), "True"))
|
||||
} else {
|
||||
filter.Add("label", fmt.Sprintf("%s=%s", labels.ONEOFF.Str(), "False"))
|
||||
}
|
||||
|
||||
containers, err := client.ContainerList(context.Background(), types.ContainerListOptions{
|
||||
All: true,
|
||||
Filter: filter,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
maxNumber := 0
|
||||
for _, container := range containers {
|
||||
number, err := strconv.Atoi(container.Labels[labels.NUMBER.Str()])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if number > maxNumber {
|
||||
maxNumber = number
|
||||
}
|
||||
}
|
||||
namer.currentNumber = maxNumber + 1
|
||||
|
||||
return namer, nil
|
||||
}
|
||||
|
||||
func (i *defaultNamer) Next() (string, int) {
|
||||
service := i.service
|
||||
if i.oneOff {
|
||||
service = i.service + "-run"
|
||||
}
|
||||
name := fmt.Sprintf(format, i.project, service, i.currentNumber)
|
||||
number := i.currentNumber
|
||||
i.currentNumber = i.currentNumber + 1
|
||||
return name, number
|
||||
}
|
||||
|
||||
func (s *singleNamer) Next() (string, int) {
|
||||
return s.name, 1
|
||||
}
|
||||
177
vendor/github.com/hyperhq/libcompose/docker/name_test.go
generated
vendored
Normal file
177
vendor/github.com/hyperhq/libcompose/docker/name_test.go
generated
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/hyperhq/libcompose/labels"
|
||||
"github.com/hyperhq/libcompose/test"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestSingleNamer(t *testing.T) {
|
||||
expectedName := "myName"
|
||||
expectedNumber := 1
|
||||
namer := NewSingleNamer("myName")
|
||||
for i := 0; i < 10; i++ {
|
||||
name, number := namer.Next()
|
||||
if name != expectedName {
|
||||
t.Fatalf("expected %s, got %s", expectedName, name)
|
||||
}
|
||||
if number != expectedNumber {
|
||||
t.Fatalf("expected %d, got %d", expectedNumber, number)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type NamerClient struct {
|
||||
test.NopClient
|
||||
expectedLabelFilters []string
|
||||
containers []types.Container
|
||||
}
|
||||
|
||||
func (client *NamerClient) ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) {
|
||||
if len(client.expectedLabelFilters) > 1 {
|
||||
labelFilters := options.Filter.Get("label")
|
||||
if len(labelFilters) != len(client.expectedLabelFilters) {
|
||||
return []types.Container{}, fmt.Errorf("expected filters %v, got %v", client.expectedLabelFilters, labelFilters)
|
||||
}
|
||||
for _, expectedLabelFilter := range client.expectedLabelFilters {
|
||||
found := false
|
||||
for _, labelFilter := range labelFilters {
|
||||
if labelFilter == expectedLabelFilter {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return []types.Container{}, fmt.Errorf("expected to find filter %s, did not in %v", expectedLabelFilter, labelFilters)
|
||||
}
|
||||
}
|
||||
}
|
||||
return client.containers, nil
|
||||
}
|
||||
|
||||
func TestDefaultNamerClientError(t *testing.T) {
|
||||
client := test.NewNopClient()
|
||||
_, err := NewNamer(client, "project", "service", false)
|
||||
if err == nil || err.Error() != "Engine no longer exists" {
|
||||
t.Fatalf("expected an error 'Engine no longer exists', got %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultNamerLabelNotANumber(t *testing.T) {
|
||||
client := &NamerClient{
|
||||
containers: []types.Container{
|
||||
{
|
||||
Labels: map[string]string{
|
||||
labels.ONEOFF.Str(): "IAmAString",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err := NewNamer(client, "project", "service", false)
|
||||
if err == nil {
|
||||
t.Fatal("expected an error, got nothing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultNamer(t *testing.T) {
|
||||
cases := []struct {
|
||||
projectName string
|
||||
serviceName string
|
||||
oneOff bool
|
||||
containers []types.Container
|
||||
expectedLabels []string
|
||||
expectedName string
|
||||
expectedNumber int
|
||||
}{
|
||||
{
|
||||
projectName: "",
|
||||
serviceName: "",
|
||||
oneOff: false,
|
||||
containers: []types.Container{},
|
||||
expectedLabels: []string{
|
||||
fmt.Sprintf("%s=", labels.PROJECT.Str()),
|
||||
fmt.Sprintf("%s=", labels.SERVICE.Str()),
|
||||
fmt.Sprintf("%s=False", labels.ONEOFF.Str()),
|
||||
},
|
||||
expectedName: "--1",
|
||||
expectedNumber: 1,
|
||||
},
|
||||
{
|
||||
projectName: "project",
|
||||
serviceName: "service",
|
||||
oneOff: false,
|
||||
containers: []types.Container{},
|
||||
expectedLabels: []string{
|
||||
fmt.Sprintf("%s=project", labels.PROJECT.Str()),
|
||||
fmt.Sprintf("%s=service", labels.SERVICE.Str()),
|
||||
fmt.Sprintf("%s=False", labels.ONEOFF.Str()),
|
||||
},
|
||||
expectedName: "project-service-1",
|
||||
expectedNumber: 1,
|
||||
},
|
||||
{
|
||||
projectName: "project",
|
||||
serviceName: "service",
|
||||
oneOff: false,
|
||||
containers: []types.Container{
|
||||
{
|
||||
Labels: map[string]string{
|
||||
labels.NUMBER.Str(): "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedLabels: []string{
|
||||
fmt.Sprintf("%s=project", labels.PROJECT.Str()),
|
||||
fmt.Sprintf("%s=service", labels.SERVICE.Str()),
|
||||
fmt.Sprintf("%s=False", labels.ONEOFF.Str()),
|
||||
},
|
||||
expectedName: "project-service-2",
|
||||
expectedNumber: 2,
|
||||
},
|
||||
{
|
||||
projectName: "project",
|
||||
serviceName: "anotherservice",
|
||||
oneOff: false,
|
||||
containers: []types.Container{
|
||||
{
|
||||
Labels: map[string]string{
|
||||
labels.NUMBER.Str(): "10",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedLabels: []string{
|
||||
fmt.Sprintf("%s=project", labels.PROJECT.Str()),
|
||||
fmt.Sprintf("%s=anotherservice", labels.SERVICE.Str()),
|
||||
fmt.Sprintf("%s=False", labels.ONEOFF.Str()),
|
||||
},
|
||||
expectedName: "project-anotherservice-11",
|
||||
expectedNumber: 11,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
client := &NamerClient{
|
||||
expectedLabelFilters: c.expectedLabels,
|
||||
containers: c.containers,
|
||||
}
|
||||
namer, err := NewNamer(client, c.projectName, c.serviceName, c.oneOff)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
name, number := namer.Next()
|
||||
if name != c.expectedName {
|
||||
t.Errorf("Expected %s, got %s for %v", c.expectedName, name, c)
|
||||
}
|
||||
if number != c.expectedNumber {
|
||||
t.Errorf("Expected %d, got %d for %v", c.expectedNumber, number, c)
|
||||
}
|
||||
_, number = namer.Next()
|
||||
if number != c.expectedNumber+1 {
|
||||
t.Errorf("Expected a 2nd call to increment numbre to %d, got %d for %v", c.expectedNumber+1, number, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
66
vendor/github.com/hyperhq/libcompose/docker/project.go
generated
vendored
Normal file
66
vendor/github.com/hyperhq/libcompose/docker/project.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/hyperhq/libcompose/config"
|
||||
"github.com/hyperhq/libcompose/lookup"
|
||||
"github.com/hyperhq/libcompose/project"
|
||||
)
|
||||
|
||||
// ComposeVersion is name of docker-compose.yml file syntax supported version
|
||||
const ComposeVersion = "1.5.0"
|
||||
|
||||
// NewProject creates a Project with the specified context.
|
||||
func NewProject(context *Context) (project.APIProject, error) {
|
||||
if context.ResourceLookup == nil {
|
||||
context.ResourceLookup = &lookup.FileConfigLookup{}
|
||||
}
|
||||
|
||||
if context.EnvironmentLookup == nil {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
context.EnvironmentLookup = &lookup.ComposableEnvLookup{
|
||||
Lookups: []config.EnvironmentLookup{
|
||||
&lookup.EnvfileLookup{
|
||||
Path: filepath.Join(cwd, ".env"),
|
||||
},
|
||||
&lookup.OsEnvLookup{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if context.AuthLookup == nil {
|
||||
context.AuthLookup = &ConfigAuthLookup{context}
|
||||
}
|
||||
|
||||
if context.ServiceFactory == nil {
|
||||
context.ServiceFactory = &ServiceFactory{
|
||||
context: context,
|
||||
}
|
||||
}
|
||||
|
||||
if context.ClientFactory == nil {
|
||||
return nil, fmt.Errorf("please provide the client to operate the Hyper.sh")
|
||||
}
|
||||
|
||||
// FIXME(vdemeester) Remove the context duplication ?
|
||||
p := project.NewProject(context.ClientFactory, &context.Context)
|
||||
|
||||
err := p.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = context.open(); err != nil {
|
||||
logrus.Errorf("Failed to open project %s: %v", p.Name, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p, err
|
||||
}
|
||||
480
vendor/github.com/hyperhq/libcompose/docker/service.go
generated
vendored
Normal file
480
vendor/github.com/hyperhq/libcompose/docker/service.go
generated
vendored
Normal file
@@ -0,0 +1,480 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/engine-api/client"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/hyperhq/libcompose/config"
|
||||
"github.com/hyperhq/libcompose/labels"
|
||||
"github.com/hyperhq/libcompose/project"
|
||||
"github.com/hyperhq/libcompose/project/options"
|
||||
"github.com/hyperhq/libcompose/utils"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Service is a project.Service implementations.
|
||||
type Service struct {
|
||||
name string
|
||||
serviceConfig *config.ServiceConfig
|
||||
context *Context
|
||||
}
|
||||
|
||||
// NewService creates a service
|
||||
func NewService(name string, serviceConfig *config.ServiceConfig, context *Context) *Service {
|
||||
return &Service{
|
||||
name: name,
|
||||
serviceConfig: serviceConfig,
|
||||
context: context,
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns the service name.
|
||||
func (s *Service) Name() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
// Config returns the configuration of the service (config.ServiceConfig).
|
||||
func (s *Service) Config() *config.ServiceConfig {
|
||||
return s.serviceConfig
|
||||
}
|
||||
|
||||
// DependentServices returns the dependent services (as an array of ServiceRelationship) of the service.
|
||||
func (s *Service) DependentServices() []project.ServiceRelationship {
|
||||
return project.DefaultDependentServices(s.context.Project, s)
|
||||
}
|
||||
|
||||
// Create implements Service.Create. It ensures the image exists or build it
|
||||
// if it can and then create a container.
|
||||
func (s *Service) Create(options options.Create) error {
|
||||
containers, err := s.collectContainers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imageName, err := s.ensureImageExists(options.NoBuild)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(containers) != 0 {
|
||||
return s.eachContainer(func(c *Container) error {
|
||||
return s.recreateIfNeeded(imageName, c, options.NoRecreate, options.ForceRecreate)
|
||||
})
|
||||
}
|
||||
|
||||
_, err = s.createOne(imageName)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Service) collectContainers() ([]*Container, error) {
|
||||
client := s.context.ClientFactory.Create(s)
|
||||
containers, err := GetContainersByFilter(client, labels.SERVICE.Eq(s.name), labels.PROJECT.Eq(s.context.Project.Name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := []*Container{}
|
||||
|
||||
for _, container := range containers {
|
||||
containerNumber, err := strconv.Atoi(container.Labels[labels.NUMBER.Str()])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Compose add "/" before name, so Name[1] will store actaul name.
|
||||
name := strings.SplitAfter(container.Names[0], "/")
|
||||
result = append(result, NewContainer(client, name[1], containerNumber, s))
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Service) createOne(imageName string) (*Container, error) {
|
||||
containers, err := s.constructContainers(imageName, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return containers[0], err
|
||||
}
|
||||
|
||||
func (s *Service) ensureImageExists(noBuild bool) (string, error) {
|
||||
err := s.imageExists()
|
||||
|
||||
if err == nil {
|
||||
return s.imageName(), nil
|
||||
}
|
||||
|
||||
if err != nil && !client.IsErrImageNotFound(err) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
/*
|
||||
if s.Config().Build.Context != "" {
|
||||
if noBuild {
|
||||
return "", fmt.Errorf("Service %q needs to be built, but no-build was specified", s.name)
|
||||
}
|
||||
return s.imageName(), s.build(options.Build{})
|
||||
}
|
||||
*/
|
||||
|
||||
return s.imageName(), s.Pull()
|
||||
}
|
||||
|
||||
func (s *Service) imageExists() error {
|
||||
client := s.context.ClientFactory.Create(s)
|
||||
|
||||
_, _, err := client.ImageInspectWithRaw(context.Background(), s.imageName(), false)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Service) imageName() string {
|
||||
if s.Config().Image != "" {
|
||||
return s.Config().Image
|
||||
}
|
||||
return fmt.Sprintf("%s_%s", s.context.ProjectName, s.Name())
|
||||
}
|
||||
|
||||
// Build implements Service.Build. If an imageName is specified or if the context has
|
||||
// no build to work with it will do nothing. Otherwise it will try to build
|
||||
// the image and returns an error if any.
|
||||
func (s *Service) Build(buildOptions options.Build) error {
|
||||
if s.Config().Image != "" {
|
||||
return nil
|
||||
}
|
||||
return s.build(buildOptions)
|
||||
}
|
||||
|
||||
func (s *Service) build(buildOptions options.Build) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) constructContainers(imageName string, count int) ([]*Container, error) {
|
||||
result, err := s.collectContainers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := s.context.ClientFactory.Create(s)
|
||||
|
||||
var namer Namer
|
||||
|
||||
if s.serviceConfig.ContainerName != "" {
|
||||
if count > 1 {
|
||||
logrus.Warnf(`The "%s" service is using the custom container name "%s". Docker requires each container to have a unique name. Remove the custom name to scale the service.`, s.name, s.serviceConfig.ContainerName)
|
||||
}
|
||||
namer = NewSingleNamer(s.serviceConfig.ContainerName)
|
||||
} else {
|
||||
namer, err = NewNamer(client, s.context.Project.Name, s.name, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for i := len(result); i < count; i++ {
|
||||
containerName, containerNumber := namer.Next()
|
||||
|
||||
c := NewContainer(client, containerName, containerNumber, s)
|
||||
|
||||
dockerContainer, err := c.Create(imageName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logrus.Debugf("Created container %s: %v", dockerContainer.ID, dockerContainer.Name)
|
||||
|
||||
result = append(result, NewContainer(client, containerName, containerNumber, s))
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Up implements Service.Up. It builds the image if needed, creates a container
|
||||
// and start it.
|
||||
func (s *Service) Up(options options.Up) error {
|
||||
containers, err := s.collectContainers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var imageName = s.imageName()
|
||||
if len(containers) == 0 || !options.NoRecreate {
|
||||
imageName, err = s.ensureImageExists(options.NoBuild)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return s.up(imageName, true, options)
|
||||
}
|
||||
|
||||
// Run implements Service.Run. It runs a one of command within the service container.
|
||||
func (s *Service) Run(ctx context.Context, commandParts []string) (int, error) {
|
||||
imageName, err := s.ensureImageExists(false)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
client := s.context.ClientFactory.Create(s)
|
||||
|
||||
namer, err := NewNamer(client, s.context.Project.Name, s.name, true)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
containerName, containerNumber := namer.Next()
|
||||
|
||||
c := NewOneOffContainer(client, containerName, containerNumber, s)
|
||||
|
||||
return c.Run(ctx, imageName, &config.ServiceConfig{Command: commandParts, Tty: true, StdinOpen: true})
|
||||
}
|
||||
|
||||
// Info implements Service.Info. It returns an project.InfoSet with the containers
|
||||
// related to this service (can be multiple if using the scale command).
|
||||
func (s *Service) Info(qFlag bool) (project.InfoSet, error) {
|
||||
result := project.InfoSet{}
|
||||
containers, err := s.collectContainers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, c := range containers {
|
||||
info, err := c.Info(qFlag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, info)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Start implements Service.Start. It tries to start a container without creating it.
|
||||
func (s *Service) Start() error {
|
||||
return s.up("", false, options.Up{})
|
||||
}
|
||||
|
||||
func (s *Service) up(imageName string, create bool, options options.Up) error {
|
||||
containers, err := s.collectContainers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Debugf("Found %d existing containers for service %s", len(containers), s.name)
|
||||
|
||||
if len(containers) == 0 && create {
|
||||
c, err := s.createOne(imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
containers = []*Container{c}
|
||||
}
|
||||
|
||||
return s.eachContainer(func(c *Container) error {
|
||||
if create {
|
||||
if err := s.recreateIfNeeded(imageName, c, options.NoRecreate, options.ForceRecreate); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return c.Up(imageName)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) recreateIfNeeded(imageName string, c *Container, noRecreate, forceRecreate bool) error {
|
||||
if noRecreate {
|
||||
return nil
|
||||
}
|
||||
outOfSync, err := c.OutOfSync(imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"outOfSync": outOfSync,
|
||||
"ForceRecreate": forceRecreate,
|
||||
"NoRecreate": noRecreate}).Debug("Going to decide if recreate is needed")
|
||||
|
||||
if forceRecreate || outOfSync {
|
||||
logrus.Infof("Recreating %s", s.name)
|
||||
if _, err := c.Recreate(imageName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) eachContainer(action func(*Container) error) error {
|
||||
containers, err := s.collectContainers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tasks := utils.InParallel{}
|
||||
for _, container := range containers {
|
||||
task := func(container *Container) func() error {
|
||||
return func() error {
|
||||
return action(container)
|
||||
}
|
||||
}(container)
|
||||
|
||||
tasks.Add(task)
|
||||
}
|
||||
|
||||
return tasks.Wait()
|
||||
}
|
||||
|
||||
// Stop implements Service.Stop. It stops any containers related to the service.
|
||||
func (s *Service) Stop(timeout int) error {
|
||||
return s.eachContainer(func(c *Container) error {
|
||||
return c.Stop(timeout)
|
||||
})
|
||||
}
|
||||
|
||||
// Restart implements Service.Restart. It restarts any containers related to the service.
|
||||
func (s *Service) Restart(timeout int) error {
|
||||
return s.eachContainer(func(c *Container) error {
|
||||
return c.Restart(timeout)
|
||||
})
|
||||
}
|
||||
|
||||
// Kill implements Service.Kill. It kills any containers related to the service.
|
||||
func (s *Service) Kill(signal string) error {
|
||||
return s.eachContainer(func(c *Container) error {
|
||||
return c.Kill(signal)
|
||||
})
|
||||
}
|
||||
|
||||
// Delete implements Service.Delete. It removes any containers related to the service.
|
||||
func (s *Service) Delete(options options.Delete) error {
|
||||
return s.eachContainer(func(c *Container) error {
|
||||
return c.Delete(options.RemoveVolume)
|
||||
})
|
||||
}
|
||||
|
||||
// Log implements Service.Log. It returns the docker logs for each container related to the service.
|
||||
func (s *Service) Log(follow bool) error {
|
||||
return s.eachContainer(func(c *Container) error {
|
||||
return c.Log(follow)
|
||||
})
|
||||
}
|
||||
|
||||
// Scale implements Service.Scale. It creates or removes containers to have the specified number
|
||||
// of related container to the service to run.
|
||||
func (s *Service) Scale(scale int, timeout int) error {
|
||||
if s.specificiesHostPort() {
|
||||
logrus.Warnf("The \"%s\" service specifies a port on the host. If multiple containers for this service are created on a single host, the port will clash.", s.Name())
|
||||
}
|
||||
|
||||
foundCount := 0
|
||||
err := s.eachContainer(func(c *Container) error {
|
||||
foundCount++
|
||||
if foundCount > scale {
|
||||
err := c.Stop(timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// FIXME(vdemeester) remove volume in scale by default ?
|
||||
return c.Delete(false)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if foundCount != scale {
|
||||
imageName, err := s.ensureImageExists(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = s.constructContainers(imageName, scale); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return s.up("", false, options.Up{})
|
||||
}
|
||||
|
||||
// Pull implements Service.Pull. It pulls the image of the service and skip the service that
|
||||
// would need to be built.
|
||||
func (s *Service) Pull() error {
|
||||
if s.Config().Image == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return pullImage(s.context.ClientFactory.Create(s), s, s.Config().Image)
|
||||
}
|
||||
|
||||
// Pause implements Service.Pause. It puts into pause the container(s) related
|
||||
// to the service.
|
||||
func (s *Service) Pause() error {
|
||||
return s.eachContainer(func(c *Container) error {
|
||||
return c.Pause()
|
||||
})
|
||||
}
|
||||
|
||||
// Unpause implements Service.Pause. It brings back from pause the container(s)
|
||||
// related to the service.
|
||||
func (s *Service) Unpause() error {
|
||||
return s.eachContainer(func(c *Container) error {
|
||||
return c.Unpause()
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveImage implements Service.RemoveImage. It removes images used for the service
|
||||
// depending on the specified type.
|
||||
func (s *Service) RemoveImage(imageType options.ImageType) error {
|
||||
switch imageType {
|
||||
case "local":
|
||||
if s.Config().Image != "" {
|
||||
return nil
|
||||
}
|
||||
return removeImage(s.context.ClientFactory.Create(s), s.imageName())
|
||||
case "all":
|
||||
return removeImage(s.context.ClientFactory.Create(s), s.imageName())
|
||||
default:
|
||||
// Don't do a thing, should be validated up-front
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Containers implements Service.Containers. It returns the list of containers
|
||||
// that are related to the service.
|
||||
func (s *Service) Containers() ([]project.Container, error) {
|
||||
result := []project.Container{}
|
||||
containers, err := s.collectContainers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, c := range containers {
|
||||
result = append(result, c)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Service) specificiesHostPort() bool {
|
||||
_, bindings, err := nat.ParsePortSpecs(s.Config().Ports)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
for _, portBindings := range bindings {
|
||||
for _, portBinding := range portBindings {
|
||||
if portBinding.HostPort != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
16
vendor/github.com/hyperhq/libcompose/docker/service_factory.go
generated
vendored
Normal file
16
vendor/github.com/hyperhq/libcompose/docker/service_factory.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/hyperhq/libcompose/config"
|
||||
"github.com/hyperhq/libcompose/project"
|
||||
)
|
||||
|
||||
// ServiceFactory is an implementation of project.ServiceFactory.
|
||||
type ServiceFactory struct {
|
||||
context *Context
|
||||
}
|
||||
|
||||
// Create creates a Service based on the specified project, name and service configuration.
|
||||
func (s *ServiceFactory) Create(project *project.Project, name string, serviceConfig *config.ServiceConfig) (project.Service, error) {
|
||||
return NewService(name, serviceConfig, s.context), nil
|
||||
}
|
||||
23
vendor/github.com/hyperhq/libcompose/docker/service_test.go
generated
vendored
Normal file
23
vendor/github.com/hyperhq/libcompose/docker/service_test.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
package docker
|
||||
|
||||
/*
|
||||
func TestSpecifiesHostPort(t *testing.T) {
|
||||
servicesWithHostPort := []Service{
|
||||
{serviceConfig: &config.ServiceConfig{Ports: []string{"8000:8000"}}},
|
||||
{serviceConfig: &config.ServiceConfig{Ports: []string{"127.0.0.1:8000:8000"}}},
|
||||
}
|
||||
|
||||
for _, service := range servicesWithHostPort {
|
||||
assert.True(t, service.specificiesHostPort())
|
||||
}
|
||||
|
||||
servicesWithoutHostPort := []Service{
|
||||
{serviceConfig: &config.ServiceConfig{Ports: []string{"8000"}}},
|
||||
{serviceConfig: &config.ServiceConfig{Ports: []string{"127.0.0.1::8000"}}},
|
||||
}
|
||||
|
||||
for _, service := range servicesWithoutHostPort {
|
||||
assert.False(t, service.specificiesHostPort())
|
||||
}
|
||||
}
|
||||
*/
|
||||
94
vendor/github.com/hyperhq/libcompose/labels/labels.go
generated
vendored
Normal file
94
vendor/github.com/hyperhq/libcompose/labels/labels.go
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
package labels
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/hyperhq/libcompose/utils"
|
||||
)
|
||||
|
||||
// Label represents a docker label.
|
||||
type Label string
|
||||
|
||||
// Libcompose default labels.
|
||||
const (
|
||||
NUMBER = Label("sh.hyper.compose.container-number")
|
||||
ONEOFF = Label("sh.hyper.compose.oneoff")
|
||||
PROJECT = Label("sh.hyper.compose.project")
|
||||
SERVICE = Label("sh.hyper.compose.service")
|
||||
HASH = Label("sh.hyper.compose.config-hash")
|
||||
VERSION = Label("sh.hyper.compose.version")
|
||||
)
|
||||
|
||||
// EqString returns a label json string representation with the specified value.
|
||||
func (f Label) EqString(value string) string {
|
||||
return LabelFilterString(string(f), value)
|
||||
}
|
||||
|
||||
// Eq returns a label map representation with the specified value.
|
||||
func (f Label) Eq(value string) map[string][]string {
|
||||
return LabelFilter(string(f), value)
|
||||
}
|
||||
|
||||
// AndString returns a json list of labels by merging the two specified values (left and right) serialized as string.
|
||||
func AndString(left, right string) string {
|
||||
leftMap := map[string][]string{}
|
||||
rightMap := map[string][]string{}
|
||||
|
||||
// Ignore errors
|
||||
json.Unmarshal([]byte(left), &leftMap)
|
||||
json.Unmarshal([]byte(right), &rightMap)
|
||||
|
||||
for k, v := range rightMap {
|
||||
existing, ok := leftMap[k]
|
||||
if ok {
|
||||
leftMap[k] = append(existing, v...)
|
||||
} else {
|
||||
leftMap[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
result, _ := json.Marshal(leftMap)
|
||||
|
||||
return string(result)
|
||||
}
|
||||
|
||||
// And returns a map of labels by merging the two specified values (left and right).
|
||||
func And(left, right map[string][]string) map[string][]string {
|
||||
result := map[string][]string{}
|
||||
for k, v := range left {
|
||||
result[k] = v
|
||||
}
|
||||
|
||||
for k, v := range right {
|
||||
existing, ok := result[k]
|
||||
if ok {
|
||||
result[k] = append(existing, v...)
|
||||
} else {
|
||||
result[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Str returns the label name.
|
||||
func (f Label) Str() string {
|
||||
return string(f)
|
||||
}
|
||||
|
||||
// LabelFilterString returns a label json string representation of the specifed couple (key,value)
|
||||
// that is used as filter for docker.
|
||||
func LabelFilterString(key, value string) string {
|
||||
return utils.FilterString(map[string][]string{
|
||||
"label": {fmt.Sprintf("%s=%s", key, value)},
|
||||
})
|
||||
}
|
||||
|
||||
// LabelFilter returns a label map representation of the specifed couple (key,value)
|
||||
// that is used as filter for docker.
|
||||
func LabelFilter(key, value string) map[string][]string {
|
||||
return map[string][]string{
|
||||
"label": {fmt.Sprintf("%s=%s", key, value)},
|
||||
}
|
||||
}
|
||||
50
vendor/github.com/hyperhq/libcompose/labels/labels_test.go
generated
vendored
Normal file
50
vendor/github.com/hyperhq/libcompose/labels/labels_test.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
package labels
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLabelEq(t *testing.T) {
|
||||
label := Label("labelName")
|
||||
m := label.Eq("value")
|
||||
values, ok := m["label"]
|
||||
if !ok {
|
||||
t.Fatalf("expected a label key, got %v", m)
|
||||
}
|
||||
if len(values) != 1 {
|
||||
t.Fatalf("expected only one value, got %v", values)
|
||||
}
|
||||
if values[0] != "labelName=value" {
|
||||
t.Fatalf("expected 'labelName=value', got %s", values)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLabelEqString(t *testing.T) {
|
||||
label := Label("labelName")
|
||||
value := label.EqString("value")
|
||||
if value != `{"label":["labelName=value"]}` {
|
||||
t.Fatalf("expected '{labelName=value}', got %s", value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLabelFilter(t *testing.T) {
|
||||
filters := []struct {
|
||||
key string
|
||||
value string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"key", "value", `{"label":["key=value"]}`,
|
||||
}, {
|
||||
"key", "", `{"label":["key="]}`,
|
||||
}, {
|
||||
"", "", `{"label":["="]}`,
|
||||
},
|
||||
}
|
||||
for _, filter := range filters {
|
||||
actual := LabelFilterString(filter.key, filter.value)
|
||||
if actual != filter.expected {
|
||||
t.Fatalf("Expected '%s for key=%s and value=%s, got %s", filter.expected, filter.key, filter.value, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
34
vendor/github.com/hyperhq/libcompose/logger/color.go
generated
vendored
Normal file
34
vendor/github.com/hyperhq/libcompose/logger/color.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
package logger
|
||||
|
||||
import "fmt"
|
||||
|
||||
var (
|
||||
colorPrefix = make(chan string)
|
||||
)
|
||||
|
||||
func generateColors() {
|
||||
i := 0
|
||||
colorOrder := []string{
|
||||
"36", // cyan
|
||||
"33", // yellow
|
||||
"32", // green
|
||||
"35", // magenta
|
||||
"31", // red
|
||||
"34", // blue
|
||||
"36;1", // intense cyan
|
||||
"33;1", // intense yellow
|
||||
"32;1", // intense green
|
||||
"35;1", // intense magenta
|
||||
"31;1", // intense red
|
||||
"34;1", // intense blue
|
||||
}
|
||||
|
||||
for {
|
||||
colorPrefix <- fmt.Sprintf("\033[%sm%%s |\033[0m", colorOrder[i])
|
||||
i = (i + 1) % len(colorOrder)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
go generateColors()
|
||||
}
|
||||
75
vendor/github.com/hyperhq/libcompose/logger/color_logger.go
generated
vendored
Normal file
75
vendor/github.com/hyperhq/libcompose/logger/color_logger.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
// ColorLoggerFactory implements logger.Factory interface using ColorLogger.
|
||||
type ColorLoggerFactory struct {
|
||||
maxLength int
|
||||
tty bool
|
||||
}
|
||||
|
||||
// ColorLogger implements logger.Logger interface with color support.
|
||||
type ColorLogger struct {
|
||||
name string
|
||||
colorPrefix string
|
||||
factory *ColorLoggerFactory
|
||||
}
|
||||
|
||||
// NewColorLoggerFactory creates a new ColorLoggerFactory.
|
||||
func NewColorLoggerFactory() *ColorLoggerFactory {
|
||||
return &ColorLoggerFactory{
|
||||
tty: terminal.IsTerminal(int(os.Stdout.Fd())),
|
||||
}
|
||||
}
|
||||
|
||||
// Create implements logger.Factory.Create.
|
||||
func (c *ColorLoggerFactory) Create(name string) Logger {
|
||||
if c.maxLength < len(name) {
|
||||
c.maxLength = len(name)
|
||||
}
|
||||
|
||||
return &ColorLogger{
|
||||
name: name,
|
||||
factory: c,
|
||||
colorPrefix: <-colorPrefix,
|
||||
}
|
||||
}
|
||||
|
||||
// Out implements logger.Logger.Out.
|
||||
func (c *ColorLogger) Out(bytes []byte) {
|
||||
if len(bytes) == 0 {
|
||||
return
|
||||
}
|
||||
logFmt, name := c.getLogFmt()
|
||||
message := fmt.Sprintf(logFmt, name, string(bytes))
|
||||
fmt.Print(message)
|
||||
}
|
||||
|
||||
// Err implements logger.Logger.Err.
|
||||
func (c *ColorLogger) Err(bytes []byte) {
|
||||
if len(bytes) == 0 {
|
||||
return
|
||||
}
|
||||
logFmt, name := c.getLogFmt()
|
||||
message := fmt.Sprintf(logFmt, name, string(bytes))
|
||||
fmt.Fprint(os.Stderr, message)
|
||||
}
|
||||
|
||||
func (c *ColorLogger) getLogFmt() (string, string) {
|
||||
pad := c.factory.maxLength
|
||||
|
||||
logFmt := "%s | %s"
|
||||
if c.factory.tty {
|
||||
logFmt = c.colorPrefix + " %s"
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("%-"+strconv.Itoa(pad)+"s", c.name)
|
||||
|
||||
return logFmt, name
|
||||
}
|
||||
18
vendor/github.com/hyperhq/libcompose/logger/null.go
generated
vendored
Normal file
18
vendor/github.com/hyperhq/libcompose/logger/null.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
package logger
|
||||
|
||||
// NullLogger is a logger.Logger and logger.Factory implementation that does nothing.
|
||||
type NullLogger struct {
|
||||
}
|
||||
|
||||
// Out is a no-op function.
|
||||
func (n *NullLogger) Out(_ []byte) {
|
||||
}
|
||||
|
||||
// Err is a no-op function.
|
||||
func (n *NullLogger) Err(_ []byte) {
|
||||
}
|
||||
|
||||
// Create implements logger.Factory and returns a NullLogger.
|
||||
func (n *NullLogger) Create(_ string) Logger {
|
||||
return &NullLogger{}
|
||||
}
|
||||
29
vendor/github.com/hyperhq/libcompose/logger/types.go
generated
vendored
Normal file
29
vendor/github.com/hyperhq/libcompose/logger/types.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
package logger
|
||||
|
||||
// Factory defines methods a factory should implement, to create a Logger
|
||||
// based on the specified name.
|
||||
type Factory interface {
|
||||
Create(name string) Logger
|
||||
}
|
||||
|
||||
// Logger defines methods to implement for being a logger.
|
||||
type Logger interface {
|
||||
Out(bytes []byte)
|
||||
Err(bytes []byte)
|
||||
}
|
||||
|
||||
// Wrapper is a wrapper around Logger that implements the Writer interface,
|
||||
// mainly use by docker/pkg/stdcopy functions.
|
||||
type Wrapper struct {
|
||||
Err bool
|
||||
Logger Logger
|
||||
}
|
||||
|
||||
func (l *Wrapper) Write(bytes []byte) (int, error) {
|
||||
if l.Err {
|
||||
l.Logger.Err(bytes)
|
||||
} else {
|
||||
l.Logger.Out(bytes)
|
||||
}
|
||||
return len(bytes), nil
|
||||
}
|
||||
25
vendor/github.com/hyperhq/libcompose/lookup/composable.go
generated
vendored
Normal file
25
vendor/github.com/hyperhq/libcompose/lookup/composable.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
package lookup
|
||||
|
||||
import (
|
||||
"github.com/hyperhq/libcompose/config"
|
||||
)
|
||||
|
||||
// ComposableEnvLookup is a structure that implements the project.EnvironmentLookup interface.
|
||||
// It holds an ordered list of EnvironmentLookup to call to look for the environment value.
|
||||
type ComposableEnvLookup struct {
|
||||
Lookups []config.EnvironmentLookup
|
||||
}
|
||||
|
||||
// Lookup creates a string slice of string containing a "docker-friendly" environment string
|
||||
// in the form of 'key=value'. It loop through the lookups and returns the latest value if
|
||||
// more than one lookup return a result.
|
||||
func (l *ComposableEnvLookup) Lookup(key, serviceName string, config *config.ServiceConfig) []string {
|
||||
result := []string{}
|
||||
for _, lookup := range l.Lookups {
|
||||
env := lookup.Lookup(key, serviceName, config)
|
||||
if len(env) == 1 {
|
||||
result = env
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
47
vendor/github.com/hyperhq/libcompose/lookup/composable_test.go
generated
vendored
Normal file
47
vendor/github.com/hyperhq/libcompose/lookup/composable_test.go
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
package lookup
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hyperhq/libcompose/config"
|
||||
)
|
||||
|
||||
type simpleEnvLookup struct {
|
||||
value []string
|
||||
}
|
||||
|
||||
func (l *simpleEnvLookup) Lookup(key, serviceName string, config *config.ServiceConfig) []string {
|
||||
return l.value
|
||||
}
|
||||
|
||||
func TestComposableLookupWithoutAnyLookup(t *testing.T) {
|
||||
envLookup := &ComposableEnvLookup{}
|
||||
actuals := envLookup.Lookup("any", "", nil)
|
||||
if len(actuals) != 0 {
|
||||
t.Fatalf("expected an empty slice, got %v", actuals)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComposableLookupReturnsTheLastValue(t *testing.T) {
|
||||
envLookup1 := &simpleEnvLookup{
|
||||
value: []string{"value=1"},
|
||||
}
|
||||
envLookup2 := &simpleEnvLookup{
|
||||
value: []string{"value=2"},
|
||||
}
|
||||
envLookup := &ComposableEnvLookup{
|
||||
[]config.EnvironmentLookup{
|
||||
envLookup1,
|
||||
envLookup2,
|
||||
},
|
||||
}
|
||||
validateLookup(t, "value=2", envLookup.Lookup("value", "", nil))
|
||||
|
||||
envLookup = &ComposableEnvLookup{
|
||||
[]config.EnvironmentLookup{
|
||||
envLookup2,
|
||||
envLookup1,
|
||||
},
|
||||
}
|
||||
validateLookup(t, "value=1", envLookup.Lookup("value", "", nil))
|
||||
}
|
||||
31
vendor/github.com/hyperhq/libcompose/lookup/envfile.go
generated
vendored
Normal file
31
vendor/github.com/hyperhq/libcompose/lookup/envfile.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
package lookup
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/hyperhq/libcompose/config"
|
||||
"github.com/hyperhq/hypercli/runconfig/opts"
|
||||
)
|
||||
|
||||
// EnvfileLookup is a structure that implements the project.EnvironmentLookup interface.
|
||||
// It holds the path of the file where to lookup environment values.
|
||||
type EnvfileLookup struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
// Lookup creates a string slice of string containing a "docker-friendly" environment string
|
||||
// in the form of 'key=value'. It gets environment values using a '.env' file in the specified
|
||||
// path.
|
||||
func (l *EnvfileLookup) Lookup(key, serviceName string, config *config.ServiceConfig) []string {
|
||||
envs, err := opts.ParseEnvFile(l.Path)
|
||||
if err != nil {
|
||||
return []string{}
|
||||
}
|
||||
for _, env := range envs {
|
||||
e := strings.Split(env, "=")
|
||||
if e[0] == key {
|
||||
return []string{env}
|
||||
}
|
||||
}
|
||||
return []string{}
|
||||
}
|
||||
57
vendor/github.com/hyperhq/libcompose/lookup/envfile_test.go
generated
vendored
Normal file
57
vendor/github.com/hyperhq/libcompose/lookup/envfile_test.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
package lookup
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEnvfileLookupReturnsEmptyIfError(t *testing.T) {
|
||||
envfileLookup := &EnvfileLookup{
|
||||
Path: "anything/file.env",
|
||||
}
|
||||
actuals := envfileLookup.Lookup("any", "", nil)
|
||||
if len(actuals) != 0 {
|
||||
t.Fatalf("expected an empty slice, got %v", actuals)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvfileLookupWithGoodFile(t *testing.T) {
|
||||
content := `foo=bar
|
||||
baz=quux
|
||||
# comment
|
||||
|
||||
_foobar=foobaz
|
||||
with.dots=working
|
||||
and_underscore=working too
|
||||
`
|
||||
tmpFolder, err := ioutil.TempDir("", "test-envfile")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
envfile := filepath.Join(tmpFolder, ".env")
|
||||
if err := ioutil.WriteFile(envfile, []byte(content), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpFolder)
|
||||
|
||||
envfileLookup := &EnvfileLookup{
|
||||
Path: envfile,
|
||||
}
|
||||
|
||||
validateLookup(t, "baz=quux", envfileLookup.Lookup("baz", "", nil))
|
||||
validateLookup(t, "foo=bar", envfileLookup.Lookup("foo", "", nil))
|
||||
validateLookup(t, "_foobar=foobaz", envfileLookup.Lookup("_foobar", "", nil))
|
||||
validateLookup(t, "with.dots=working", envfileLookup.Lookup("with.dots", "", nil))
|
||||
validateLookup(t, "and_underscore=working too", envfileLookup.Lookup("and_underscore", "", nil))
|
||||
}
|
||||
|
||||
func validateLookup(t *testing.T, expected string, actuals []string) {
|
||||
if len(actuals) != 1 {
|
||||
t.Fatalf("expected 1 result, got %v", actuals)
|
||||
}
|
||||
if actuals[0] != expected {
|
||||
t.Fatalf("expected %s, got %s", expected, actuals[0])
|
||||
}
|
||||
}
|
||||
66
vendor/github.com/hyperhq/libcompose/lookup/file.go
generated
vendored
Normal file
66
vendor/github.com/hyperhq/libcompose/lookup/file.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
package lookup
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// relativePath returns the proper relative path for the given file path. If
|
||||
// the relativeTo string equals "-", then it means that it's from the stdin,
|
||||
// and the returned path will be the current working directory. Otherwise, if
|
||||
// file is really an absolute path, then it will be returned without any
|
||||
// changes. Otherwise, the returned path will be a combination of relativeTo
|
||||
// and file.
|
||||
func relativePath(file, relativeTo string) string {
|
||||
// stdin: return the current working directory if possible.
|
||||
if relativeTo == "-" {
|
||||
if cwd, err := os.Getwd(); err == nil {
|
||||
return cwd
|
||||
}
|
||||
}
|
||||
|
||||
// If the given file is already an absolute path, just return it.
|
||||
// Otherwise, the returned path will be relative to the given relativeTo
|
||||
// path.
|
||||
if filepath.IsAbs(file) {
|
||||
return file
|
||||
}
|
||||
|
||||
abs, err := filepath.Abs(filepath.Join(path.Dir(relativeTo), file))
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to get absolute directory: %s", err)
|
||||
return file
|
||||
}
|
||||
return abs
|
||||
}
|
||||
|
||||
// FileConfigLookup is a "bare" structure that implements the project.ResourceLookup interface
|
||||
type FileConfigLookup struct {
|
||||
}
|
||||
|
||||
// Lookup returns the content and the actual filename of the file that is "built" using the
|
||||
// specified file and relativeTo string. file and relativeTo are supposed to be file path.
|
||||
// If file starts with a slash ('/'), it tries to load it, otherwise it will build a
|
||||
// filename using the folder part of relativeTo joined with file.
|
||||
func (f *FileConfigLookup) Lookup(file, relativeTo string) ([]byte, string, error) {
|
||||
file = relativePath(file, relativeTo)
|
||||
logrus.Debugf("Reading file %s", file)
|
||||
bytes, err := ioutil.ReadFile(file)
|
||||
return bytes, file, err
|
||||
}
|
||||
|
||||
// ResolvePath returns the path to be used for the given path volume. This
|
||||
// function already takes care of relative paths.
|
||||
func (f *FileConfigLookup) ResolvePath(path, inFile string) string {
|
||||
vs := strings.SplitN(path, ":", 2)
|
||||
if len(vs) != 2 || filepath.IsAbs(vs[0]) {
|
||||
return path
|
||||
}
|
||||
vs[0] = relativePath(vs[0], inFile)
|
||||
return strings.Join(vs, ":")
|
||||
}
|
||||
70
vendor/github.com/hyperhq/libcompose/lookup/file_test.go
generated
vendored
Normal file
70
vendor/github.com/hyperhq/libcompose/lookup/file_test.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
package lookup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type input struct {
|
||||
file string
|
||||
relativeTo string
|
||||
}
|
||||
|
||||
func TestLookupError(t *testing.T) {
|
||||
abs, err := filepath.Abs(".")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get absolute directory: %s", err)
|
||||
}
|
||||
invalids := map[input]string{
|
||||
input{"", ""}: fmt.Sprintf("read %s: is a directory", abs),
|
||||
input{"", "/tmp/"}: "read /tmp: is a directory",
|
||||
input{"file", "/does/not/exists/"}: "open /does/not/exists/file: no such file or directory",
|
||||
input{"file", "/does/not/something"}: "open /does/not/file: no such file or directory",
|
||||
input{"file", "/does/not/exists/another"}: "open /does/not/exists/file: no such file or directory",
|
||||
input{"/does/not/exists/file", "/tmp/"}: "open /does/not/exists/file: no such file or directory",
|
||||
input{"does/not/exists/file", "/tmp/"}: "open /tmp/does/not/exists/file: no such file or directory",
|
||||
}
|
||||
|
||||
fileConfigLookup := FileConfigLookup{}
|
||||
|
||||
for invalid, expectedError := range invalids {
|
||||
_, _, err := fileConfigLookup.Lookup(invalid.file, invalid.relativeTo)
|
||||
if err == nil || err.Error() != expectedError {
|
||||
t.Fatalf("Expected error with '%s', got '%v'", expectedError, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLookupOK(t *testing.T) {
|
||||
tmpFolder, err := ioutil.TempDir("", "lookup-tests")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tmpFile1 := filepath.Join(tmpFolder, "file1")
|
||||
tmpFile2 := filepath.Join(tmpFolder, "file2")
|
||||
if err = ioutil.WriteFile(tmpFile1, []byte("content1"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = ioutil.WriteFile(tmpFile2, []byte("content2"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fileConfigLookup := FileConfigLookup{}
|
||||
|
||||
valids := map[input]string{
|
||||
input{"file1", tmpFolder + "/"}: "content1",
|
||||
input{"file2", tmpFolder + "/"}: "content2",
|
||||
input{tmpFile1, tmpFolder}: "content1",
|
||||
input{tmpFile1, "/does/not/exists"}: "content1",
|
||||
input{"file2", tmpFile1}: "content2",
|
||||
}
|
||||
|
||||
for valid, expectedContent := range valids {
|
||||
out, _, err := fileConfigLookup.Lookup(valid.file, valid.relativeTo)
|
||||
if err != nil || string(out) != expectedContent {
|
||||
t.Fatalf("Expected %s to contains '%s', got %s, %v.", valid.file, expectedContent, out, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
24
vendor/github.com/hyperhq/libcompose/lookup/simple_env.go
generated
vendored
Normal file
24
vendor/github.com/hyperhq/libcompose/lookup/simple_env.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
package lookup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/hyperhq/libcompose/config"
|
||||
)
|
||||
|
||||
// OsEnvLookup is a "bare" structure that implements the project.EnvironmentLookup interface
|
||||
type OsEnvLookup struct {
|
||||
}
|
||||
|
||||
// Lookup creates a string slice of string containing a "docker-friendly" environment string
|
||||
// in the form of 'key=value'. It gets environment values using os.Getenv.
|
||||
// If the os environment variable does not exists, the slice is empty. serviceName and config
|
||||
// are not used at all in this implementation.
|
||||
func (o *OsEnvLookup) Lookup(key, serviceName string, config *config.ServiceConfig) []string {
|
||||
ret := os.Getenv(key)
|
||||
if ret == "" {
|
||||
return []string{}
|
||||
}
|
||||
return []string{fmt.Sprintf("%s=%s", key, ret)}
|
||||
}
|
||||
28
vendor/github.com/hyperhq/libcompose/lookup/simple_env_test.go
generated
vendored
Normal file
28
vendor/github.com/hyperhq/libcompose/lookup/simple_env_test.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
package lookup
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOsEnvLookup(t *testing.T) {
|
||||
// Putting bare minimun value for serviceName and config as there are
|
||||
// not important on this test.
|
||||
serviceName := "anything"
|
||||
|
||||
osEnvLookup := &OsEnvLookup{}
|
||||
|
||||
envs := osEnvLookup.Lookup("PATH", serviceName, nil)
|
||||
if len(envs) != 1 {
|
||||
t.Fatalf("Expected envs to contains one element, but was %v", envs)
|
||||
}
|
||||
|
||||
envs = osEnvLookup.Lookup("path", serviceName, nil)
|
||||
if len(envs) != 0 {
|
||||
t.Fatalf("Expected envs to be empty, but was %v", envs)
|
||||
}
|
||||
|
||||
envs = osEnvLookup.Lookup("DOES_NOT_EXIST", serviceName, nil)
|
||||
if len(envs) != 0 {
|
||||
t.Fatalf("Expected envs to be empty, but was %v", envs)
|
||||
}
|
||||
}
|
||||
1
vendor/github.com/hyperhq/libcompose/package.go
generated
vendored
Normal file
1
vendor/github.com/hyperhq/libcompose/package.go
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
package libcompose
|
||||
11
vendor/github.com/hyperhq/libcompose/project/client_factory.go
generated
vendored
Normal file
11
vendor/github.com/hyperhq/libcompose/project/client_factory.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
package project
|
||||
|
||||
import "github.com/docker/engine-api/client"
|
||||
|
||||
// ClientFactory is a factory to create docker clients.
|
||||
type ClientFactory interface {
|
||||
// Create constructs a Docker client for the given service. The passed in
|
||||
// config may be nil in which case a generic client for the project should
|
||||
// be returned.
|
||||
Create(service Service) client.APIClient
|
||||
}
|
||||
9
vendor/github.com/hyperhq/libcompose/project/container.go
generated
vendored
Normal file
9
vendor/github.com/hyperhq/libcompose/project/container.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
package project
|
||||
|
||||
// Container defines what a libcompose container provides.
|
||||
type Container interface {
|
||||
ID() (string, error)
|
||||
Name() string
|
||||
Port(port string) (string, error)
|
||||
IsRunning() (bool, error)
|
||||
}
|
||||
141
vendor/github.com/hyperhq/libcompose/project/context.go
generated
vendored
Normal file
141
vendor/github.com/hyperhq/libcompose/project/context.go
generated
vendored
Normal file
@@ -0,0 +1,141 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/hyperhq/libcompose/config"
|
||||
"github.com/hyperhq/libcompose/logger"
|
||||
)
|
||||
|
||||
var projectRegexp = regexp.MustCompile("[^a-zA-Z0-9_.-]")
|
||||
|
||||
// Context holds context meta information about a libcompose project, like
|
||||
// the project name, the compose file, etc.
|
||||
type Context struct {
|
||||
ComposeFiles []string
|
||||
ComposeBytes [][]byte
|
||||
ProjectName string
|
||||
isOpen bool
|
||||
ServiceFactory ServiceFactory
|
||||
EnvironmentLookup config.EnvironmentLookup
|
||||
ResourceLookup config.ResourceLookup
|
||||
LoggerFactory logger.Factory
|
||||
IgnoreMissingConfig bool
|
||||
Project *Project
|
||||
|
||||
Autoremove bool
|
||||
}
|
||||
|
||||
func (c *Context) readComposeFiles() error {
|
||||
if c.ComposeBytes != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
logrus.Debugf("Opening compose files: %s", strings.Join(c.ComposeFiles, ","))
|
||||
|
||||
// Handle STDIN (`-f -`)
|
||||
if len(c.ComposeFiles) == 1 && c.ComposeFiles[0] == "-" {
|
||||
composeBytes, err := ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to read compose file from stdin: %v", err)
|
||||
return err
|
||||
}
|
||||
c.ComposeBytes = [][]byte{composeBytes}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, composeFile := range c.ComposeFiles {
|
||||
composeBytes, err := ioutil.ReadFile(composeFile)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
logrus.Errorf("Failed to open the compose file: %s", composeFile)
|
||||
return err
|
||||
}
|
||||
if err != nil && !c.IgnoreMissingConfig {
|
||||
logrus.Errorf("Failed to find the compose file: %s", composeFile)
|
||||
return err
|
||||
}
|
||||
c.ComposeBytes = append(c.ComposeBytes, composeBytes)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Context) determineProject() error {
|
||||
name, err := c.lookupProjectName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.ProjectName = normalizeName(name)
|
||||
|
||||
if c.ProjectName == "" {
|
||||
return fmt.Errorf("Falied to determine project name")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Context) lookupProjectName() (string, error) {
|
||||
if c.ProjectName != "" {
|
||||
return c.ProjectName, nil
|
||||
}
|
||||
|
||||
if envProject := os.Getenv("COMPOSE_PROJECT_NAME"); envProject != "" {
|
||||
return envProject, nil
|
||||
}
|
||||
|
||||
file := "."
|
||||
if len(c.ComposeFiles) > 0 {
|
||||
file = c.ComposeFiles[0]
|
||||
}
|
||||
|
||||
f, err := filepath.Abs(file)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to get absolute directory for: %s", file)
|
||||
return "", err
|
||||
}
|
||||
|
||||
f = toUnixPath(f)
|
||||
|
||||
parent := path.Base(path.Dir(f))
|
||||
if parent != "" && parent != "." {
|
||||
return parent, nil
|
||||
} else if wd, err := os.Getwd(); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return path.Base(toUnixPath(wd)), nil
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeName(name string) string {
|
||||
r := regexp.MustCompile("[^a-z0-9]+")
|
||||
return r.ReplaceAllString(strings.ToLower(name), "")
|
||||
}
|
||||
|
||||
func toUnixPath(p string) string {
|
||||
return strings.Replace(p, "\\", "/", -1)
|
||||
}
|
||||
|
||||
func (c *Context) open() error {
|
||||
if c.isOpen {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := c.readComposeFiles(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.determineProject(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.isOpen = true
|
||||
return nil
|
||||
}
|
||||
94
vendor/github.com/hyperhq/libcompose/project/empty.go
generated
vendored
Normal file
94
vendor/github.com/hyperhq/libcompose/project/empty.go
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"github.com/hyperhq/libcompose/project/options"
|
||||
)
|
||||
|
||||
// EmptyService is a struct that implements Service but does nothing.
|
||||
type EmptyService struct {
|
||||
}
|
||||
|
||||
// Create implements Service.Create but does nothing.
|
||||
func (e *EmptyService) Create(options options.Create) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Build implements Service.Build but does nothing.
|
||||
func (e *EmptyService) Build(buildOptions options.Build) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Up implements Service.Up but does nothing.
|
||||
func (e *EmptyService) Up(options options.Up) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start implements Service.Start but does nothing.
|
||||
func (e *EmptyService) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop implements Service.Stop() but does nothing.
|
||||
func (e *EmptyService) Stop(timeout int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete implements Service.Delete but does nothing.
|
||||
func (e *EmptyService) Delete(options options.Delete) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Restart implements Service.Restart but does nothing.
|
||||
func (e *EmptyService) Restart(timeout int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Log implements Service.Log but does nothing.
|
||||
func (e *EmptyService) Log(follow bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pull implements Service.Pull but does nothing.
|
||||
func (e *EmptyService) Pull() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Kill implements Service.Kill but does nothing.
|
||||
func (e *EmptyService) Kill(signal string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Containers implements Service.Containers but does nothing.
|
||||
func (e *EmptyService) Containers() ([]Container, error) {
|
||||
return []Container{}, nil
|
||||
}
|
||||
|
||||
// Scale implements Service.Scale but does nothing.
|
||||
func (e *EmptyService) Scale(count int, timeout int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Info implements Service.Info but does nothing.
|
||||
func (e *EmptyService) Info(qFlag bool) (InfoSet, error) {
|
||||
return InfoSet{}, nil
|
||||
}
|
||||
|
||||
// Pause implements Service.Pause but does nothing.
|
||||
func (e *EmptyService) Pause() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unpause implements Service.Pause but does nothing.
|
||||
func (e *EmptyService) Unpause() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run implements Service.Run but does nothing.
|
||||
func (e *EmptyService) Run(commandParts []string) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// RemoveImage implements Service.RemoveImage but does nothing.
|
||||
func (e *EmptyService) RemoveImage(imageType options.ImageType) error {
|
||||
return nil
|
||||
}
|
||||
197
vendor/github.com/hyperhq/libcompose/project/events/events.go
generated
vendored
Normal file
197
vendor/github.com/hyperhq/libcompose/project/events/events.go
generated
vendored
Normal file
@@ -0,0 +1,197 @@
|
||||
// Package events holds event structures, methods and functions.
|
||||
package events
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Notifier defines the methods an event notifier should have.
|
||||
type Notifier interface {
|
||||
Notify(eventType EventType, serviceName string, data map[string]string)
|
||||
}
|
||||
|
||||
// Emitter defines the methods an event emitter should have.
|
||||
type Emitter interface {
|
||||
AddListener(c chan<- Event)
|
||||
}
|
||||
|
||||
// Event holds project-wide event informations.
|
||||
type Event struct {
|
||||
EventType EventType
|
||||
ServiceName string
|
||||
Data map[string]string
|
||||
}
|
||||
|
||||
// EventType defines a type of libcompose event.
|
||||
type EventType int
|
||||
|
||||
// Definitions of libcompose events
|
||||
const (
|
||||
NoEvent = EventType(iota)
|
||||
|
||||
ContainerCreated = EventType(iota)
|
||||
ContainerStarted = EventType(iota)
|
||||
|
||||
ServiceAdd = EventType(iota)
|
||||
ServiceUpStart = EventType(iota)
|
||||
ServiceUpIgnored = EventType(iota)
|
||||
ServiceUp = EventType(iota)
|
||||
ServiceCreateStart = EventType(iota)
|
||||
ServiceCreate = EventType(iota)
|
||||
ServiceDeleteStart = EventType(iota)
|
||||
ServiceDelete = EventType(iota)
|
||||
ServiceDownStart = EventType(iota)
|
||||
ServiceDown = EventType(iota)
|
||||
ServiceRestartStart = EventType(iota)
|
||||
ServiceRestart = EventType(iota)
|
||||
ServicePullStart = EventType(iota)
|
||||
ServicePull = EventType(iota)
|
||||
ServiceKillStart = EventType(iota)
|
||||
ServiceKill = EventType(iota)
|
||||
ServiceStartStart = EventType(iota)
|
||||
ServiceStart = EventType(iota)
|
||||
ServiceBuildStart = EventType(iota)
|
||||
ServiceBuild = EventType(iota)
|
||||
ServicePauseStart = EventType(iota)
|
||||
ServicePause = EventType(iota)
|
||||
ServiceUnpauseStart = EventType(iota)
|
||||
ServiceUnpause = EventType(iota)
|
||||
ServiceStopStart = EventType(iota)
|
||||
ServiceStop = EventType(iota)
|
||||
ServiceRunStart = EventType(iota)
|
||||
ServiceRun = EventType(iota)
|
||||
|
||||
VolumeAdd = EventType(iota)
|
||||
NetworkAdd = EventType(iota)
|
||||
|
||||
ProjectDownStart = EventType(iota)
|
||||
ProjectDownDone = EventType(iota)
|
||||
ProjectCreateStart = EventType(iota)
|
||||
ProjectCreateDone = EventType(iota)
|
||||
ProjectUpStart = EventType(iota)
|
||||
ProjectUpDone = EventType(iota)
|
||||
ProjectDeleteStart = EventType(iota)
|
||||
ProjectDeleteDone = EventType(iota)
|
||||
ProjectRestartStart = EventType(iota)
|
||||
ProjectRestartDone = EventType(iota)
|
||||
ProjectReload = EventType(iota)
|
||||
ProjectReloadTrigger = EventType(iota)
|
||||
ProjectKillStart = EventType(iota)
|
||||
ProjectKillDone = EventType(iota)
|
||||
ProjectStartStart = EventType(iota)
|
||||
ProjectStartDone = EventType(iota)
|
||||
ProjectBuildStart = EventType(iota)
|
||||
ProjectBuildDone = EventType(iota)
|
||||
ProjectPauseStart = EventType(iota)
|
||||
ProjectPauseDone = EventType(iota)
|
||||
ProjectUnpauseStart = EventType(iota)
|
||||
ProjectUnpauseDone = EventType(iota)
|
||||
ProjectStopStart = EventType(iota)
|
||||
ProjectStopDone = EventType(iota)
|
||||
)
|
||||
|
||||
func (e EventType) String() string {
|
||||
var m string
|
||||
switch e {
|
||||
case ContainerCreated:
|
||||
m = "Created container"
|
||||
case ContainerStarted:
|
||||
m = "Started container"
|
||||
|
||||
case ServiceAdd:
|
||||
m = "Adding"
|
||||
case ServiceUpStart:
|
||||
m = "Starting"
|
||||
case ServiceUpIgnored:
|
||||
m = "Ignoring"
|
||||
case ServiceUp:
|
||||
m = "Started"
|
||||
case ServiceCreateStart:
|
||||
m = "Creating"
|
||||
case ServiceCreate:
|
||||
m = "Created"
|
||||
case ServiceDeleteStart:
|
||||
m = "Deleting"
|
||||
case ServiceDelete:
|
||||
m = "Deleted"
|
||||
case ServiceStopStart:
|
||||
m = "Stopping"
|
||||
case ServiceStop:
|
||||
m = "Stopped"
|
||||
case ServiceDownStart:
|
||||
m = "Stopping"
|
||||
case ServiceDown:
|
||||
m = "Stopped"
|
||||
case ServiceRestartStart:
|
||||
m = "Restarting"
|
||||
case ServiceRestart:
|
||||
m = "Restarted"
|
||||
case ServicePullStart:
|
||||
m = "Pulling"
|
||||
case ServicePull:
|
||||
m = "Pulled"
|
||||
case ServiceKillStart:
|
||||
m = "Killing"
|
||||
case ServiceKill:
|
||||
m = "Killed"
|
||||
case ServiceStartStart:
|
||||
m = "Starting"
|
||||
case ServiceStart:
|
||||
m = "Started"
|
||||
case ServiceBuildStart:
|
||||
m = "Building"
|
||||
case ServiceBuild:
|
||||
m = "Built"
|
||||
case ServiceRunStart:
|
||||
m = "Executing"
|
||||
case ServiceRun:
|
||||
m = "Executed"
|
||||
|
||||
case ProjectDownStart:
|
||||
m = "Stopping project"
|
||||
case ProjectDownDone:
|
||||
m = "Project stopped"
|
||||
case ProjectStopStart:
|
||||
m = "Stopping project"
|
||||
case ProjectStopDone:
|
||||
m = "Project stopped"
|
||||
case ProjectCreateStart:
|
||||
m = "Creating project"
|
||||
case ProjectCreateDone:
|
||||
m = "Project created"
|
||||
case ProjectUpStart:
|
||||
m = "Starting project"
|
||||
case ProjectUpDone:
|
||||
m = "Project started"
|
||||
case ProjectDeleteStart:
|
||||
m = "Deleting project"
|
||||
case ProjectDeleteDone:
|
||||
m = "Project deleted"
|
||||
case ProjectRestartStart:
|
||||
m = "Restarting project"
|
||||
case ProjectRestartDone:
|
||||
m = "Project restarted"
|
||||
case ProjectReload:
|
||||
m = "Reloading project"
|
||||
case ProjectReloadTrigger:
|
||||
m = "Triggering project reload"
|
||||
case ProjectKillStart:
|
||||
m = "Killing project"
|
||||
case ProjectKillDone:
|
||||
m = "Project killed"
|
||||
case ProjectStartStart:
|
||||
m = "Starting project"
|
||||
case ProjectStartDone:
|
||||
m = "Project started"
|
||||
case ProjectBuildStart:
|
||||
m = "Building project"
|
||||
case ProjectBuildDone:
|
||||
m = "Project built"
|
||||
}
|
||||
|
||||
if m == "" {
|
||||
m = fmt.Sprintf("EventType: %d", int(e))
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
21
vendor/github.com/hyperhq/libcompose/project/events/events_test.go
generated
vendored
Normal file
21
vendor/github.com/hyperhq/libcompose/project/events/events_test.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEventEquality(t *testing.T) {
|
||||
if fmt.Sprintf("%s", ServiceStart) != "Started" ||
|
||||
fmt.Sprintf("%v", ServiceStart) != "Started" {
|
||||
t.Fatalf("EventServiceStart String() doesn't work: %s %v", ServiceStart, ServiceStart)
|
||||
}
|
||||
|
||||
if fmt.Sprintf("%s", ServiceStart) != fmt.Sprintf("%s", ServiceUp) {
|
||||
t.Fatal("Event messages do not match")
|
||||
}
|
||||
|
||||
if ServiceStart == ServiceUp {
|
||||
t.Fatal("Events match")
|
||||
}
|
||||
}
|
||||
53
vendor/github.com/hyperhq/libcompose/project/info.go
generated
vendored
Normal file
53
vendor/github.com/hyperhq/libcompose/project/info.go
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
// InfoPart holds key/value strings.
|
||||
type InfoPart struct {
|
||||
Key, Value string
|
||||
}
|
||||
|
||||
// InfoSet holds a list of Info.
|
||||
type InfoSet []Info
|
||||
|
||||
// Info holds a list of InfoPart.
|
||||
type Info []InfoPart
|
||||
|
||||
func (infos InfoSet) String(titleFlag bool) string {
|
||||
//no error checking, none of this should fail
|
||||
buffer := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||
tabwriter := tabwriter.NewWriter(buffer, 4, 4, 2, ' ', 0)
|
||||
|
||||
first := true
|
||||
for _, info := range infos {
|
||||
if first && titleFlag {
|
||||
writeLine(tabwriter, true, info)
|
||||
}
|
||||
first = false
|
||||
writeLine(tabwriter, false, info)
|
||||
}
|
||||
|
||||
tabwriter.Flush()
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func writeLine(writer io.Writer, key bool, info Info) {
|
||||
first := true
|
||||
for _, part := range info {
|
||||
if !first {
|
||||
writer.Write([]byte{'\t'})
|
||||
}
|
||||
first = false
|
||||
if key {
|
||||
writer.Write([]byte(part.Key))
|
||||
} else {
|
||||
writer.Write([]byte(part.Value))
|
||||
}
|
||||
}
|
||||
|
||||
writer.Write([]byte{'\n'})
|
||||
}
|
||||
36
vendor/github.com/hyperhq/libcompose/project/interface.go
generated
vendored
Normal file
36
vendor/github.com/hyperhq/libcompose/project/interface.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"github.com/hyperhq/libcompose/config"
|
||||
"github.com/hyperhq/libcompose/project/events"
|
||||
"github.com/hyperhq/libcompose/project/options"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// APIProject is an interface defining the methods a libcompose project should implement.
|
||||
type APIProject interface {
|
||||
events.Notifier
|
||||
events.Emitter
|
||||
|
||||
Build(options options.Build, sevice ...string) error
|
||||
Create(options options.Create, services ...string) error
|
||||
Delete(options options.Delete, services ...string) error
|
||||
Down(options options.Down, services ...string) error
|
||||
Kill(signal string, services ...string) error
|
||||
Log(follow bool, services ...string) error
|
||||
Pause(services ...string) error
|
||||
Ps(onlyID bool, services ...string) (InfoSet, error)
|
||||
// FIXME(vdemeester) we could use nat.Port instead ?
|
||||
Port(index int, protocol, serviceName, privatePort string) (string, error)
|
||||
Pull(services ...string) error
|
||||
Restart(timeout int, services ...string) error
|
||||
Run(ctx context.Context, serviceName string, commandParts []string) (int, error)
|
||||
Scale(timeout int, servicesScale map[string]int) error
|
||||
Start(services ...string) error
|
||||
Stop(timeout int, services ...string) error
|
||||
Unpause(services ...string) error
|
||||
Up(options options.Up, services ...string) error
|
||||
|
||||
Parse() error
|
||||
GetConfig() (*config.ServiceConfigs, map[string]*config.VolumeConfig, map[string]*config.NetworkConfig)
|
||||
}
|
||||
77
vendor/github.com/hyperhq/libcompose/project/listener.go
generated
vendored
Normal file
77
vendor/github.com/hyperhq/libcompose/project/listener.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/hyperhq/libcompose/project/events"
|
||||
)
|
||||
|
||||
var (
|
||||
infoEvents = map[events.EventType]bool{
|
||||
events.ProjectDeleteDone: true,
|
||||
events.ProjectDeleteStart: true,
|
||||
events.ProjectDownDone: true,
|
||||
events.ProjectDownStart: true,
|
||||
events.ProjectRestartDone: true,
|
||||
events.ProjectRestartStart: true,
|
||||
events.ProjectUpDone: true,
|
||||
events.ProjectUpStart: true,
|
||||
events.ServiceDeleteStart: true,
|
||||
events.ServiceDelete: true,
|
||||
events.ServiceDownStart: true,
|
||||
events.ServiceDown: true,
|
||||
events.ServiceRestartStart: true,
|
||||
events.ServiceRestart: true,
|
||||
events.ServiceUpStart: true,
|
||||
events.ServiceUp: true,
|
||||
}
|
||||
)
|
||||
|
||||
type defaultListener struct {
|
||||
project *Project
|
||||
listenChan chan events.Event
|
||||
upCount int
|
||||
}
|
||||
|
||||
// NewDefaultListener create a default listener for the specified project.
|
||||
func NewDefaultListener(p *Project) chan<- events.Event {
|
||||
l := defaultListener{
|
||||
listenChan: make(chan events.Event),
|
||||
project: p,
|
||||
}
|
||||
go l.start()
|
||||
return l.listenChan
|
||||
}
|
||||
|
||||
func (d *defaultListener) start() {
|
||||
for event := range d.listenChan {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if event.Data != nil {
|
||||
for k, v := range event.Data {
|
||||
if buffer.Len() > 0 {
|
||||
buffer.WriteString(", ")
|
||||
}
|
||||
buffer.WriteString(k)
|
||||
buffer.WriteString("=")
|
||||
buffer.WriteString(v)
|
||||
}
|
||||
}
|
||||
|
||||
if event.EventType == events.ServiceUp {
|
||||
d.upCount++
|
||||
}
|
||||
|
||||
logf := logrus.Debugf
|
||||
|
||||
if infoEvents[event.EventType] {
|
||||
logf = logrus.Infof
|
||||
}
|
||||
|
||||
if event.ServiceName == "" {
|
||||
logf("Project [%s]: %s %s", d.project.Name, event.EventType, buffer.Bytes())
|
||||
} else {
|
||||
logf("[%d/%d] [%s]: %s %s", d.upCount, d.project.ServiceConfigs.Len(), event.ServiceName, event.EventType, buffer.Bytes())
|
||||
}
|
||||
}
|
||||
}
|
||||
47
vendor/github.com/hyperhq/libcompose/project/options/types.go
generated
vendored
Normal file
47
vendor/github.com/hyperhq/libcompose/project/options/types.go
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
package options
|
||||
|
||||
// Build holds options of compose build.
|
||||
type Build struct {
|
||||
NoCache bool
|
||||
ForceRemove bool
|
||||
Pull bool
|
||||
}
|
||||
|
||||
// Delete holds options of compose rm.
|
||||
type Delete struct {
|
||||
RemoveVolume bool
|
||||
BeforeDeleteCallback func([]string) bool
|
||||
}
|
||||
|
||||
// Down holds options of compose down.
|
||||
type Down struct {
|
||||
RemoveVolume bool
|
||||
RemoveImages ImageType
|
||||
RemoveOrphans bool
|
||||
}
|
||||
|
||||
// Create holds options of compose create.
|
||||
type Create struct {
|
||||
NoRecreate bool
|
||||
ForceRecreate bool
|
||||
NoBuild bool
|
||||
// ForceBuild bool
|
||||
}
|
||||
|
||||
// Up holds options of compose up.
|
||||
type Up struct {
|
||||
Create
|
||||
}
|
||||
|
||||
// ImageType defines the type of image (local, all)
|
||||
type ImageType string
|
||||
|
||||
// Valid indicates whether the image type is valid.
|
||||
func (i ImageType) Valid() bool {
|
||||
switch string(i) {
|
||||
case "", "local", "all":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
39
vendor/github.com/hyperhq/libcompose/project/options/types_test.go
generated
vendored
Normal file
39
vendor/github.com/hyperhq/libcompose/project/options/types_test.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestImageType(t *testing.T) {
|
||||
cases := []struct {
|
||||
imageType string
|
||||
valid bool
|
||||
}{
|
||||
{
|
||||
imageType: "",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
imageType: " ",
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
imageType: "hello",
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
imageType: "local",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
imageType: "all",
|
||||
valid: true,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
i := ImageType(c.imageType)
|
||||
if i.Valid() != c.valid {
|
||||
t.Errorf("expected %v, got %v, for %v", c.valid, i.Valid(), c)
|
||||
}
|
||||
}
|
||||
}
|
||||
679
vendor/github.com/hyperhq/libcompose/project/project.go
generated
vendored
Normal file
679
vendor/github.com/hyperhq/libcompose/project/project.go
generated
vendored
Normal file
@@ -0,0 +1,679 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/filters"
|
||||
"github.com/hyperhq/libcompose/config"
|
||||
"github.com/hyperhq/libcompose/labels"
|
||||
"github.com/hyperhq/libcompose/logger"
|
||||
"github.com/hyperhq/libcompose/project/events"
|
||||
"github.com/hyperhq/libcompose/project/options"
|
||||
"github.com/hyperhq/libcompose/utils"
|
||||
)
|
||||
|
||||
type wrapperAction func(*serviceWrapper, map[string]*serviceWrapper)
|
||||
type serviceAction func(service Service) error
|
||||
|
||||
// Project holds libcompose project information.
|
||||
type Project struct {
|
||||
Name string
|
||||
ServiceConfigs *config.ServiceConfigs
|
||||
VolumeConfigs map[string]*config.VolumeConfig
|
||||
NetworkConfigs map[string]*config.NetworkConfig
|
||||
Files []string
|
||||
ReloadCallback func() error
|
||||
|
||||
context *Context
|
||||
clientFactory ClientFactory
|
||||
reload []string
|
||||
upCount int
|
||||
listeners []chan<- events.Event
|
||||
hasListeners bool
|
||||
}
|
||||
|
||||
// NewProject creates a new project with the specified context.
|
||||
func NewProject(clientFactory ClientFactory, context *Context) *Project {
|
||||
p := &Project{
|
||||
context: context,
|
||||
clientFactory: clientFactory,
|
||||
ServiceConfigs: config.NewServiceConfigs(),
|
||||
VolumeConfigs: make(map[string]*config.VolumeConfig),
|
||||
NetworkConfigs: make(map[string]*config.NetworkConfig),
|
||||
}
|
||||
|
||||
if context.LoggerFactory == nil {
|
||||
context.LoggerFactory = &logger.NullLogger{}
|
||||
}
|
||||
|
||||
context.Project = p
|
||||
|
||||
p.listeners = []chan<- events.Event{NewDefaultListener(p)}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Project) GetConfig() (*config.ServiceConfigs, map[string]*config.VolumeConfig, map[string]*config.NetworkConfig) {
|
||||
return p.ServiceConfigs, p.VolumeConfigs, p.NetworkConfigs
|
||||
}
|
||||
|
||||
// Parse populates project information based on its context. It sets up the name,
|
||||
// the composefile and the composebytes (the composefile content).
|
||||
func (p *Project) Parse() error {
|
||||
err := p.context.open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.Name = p.context.ProjectName
|
||||
|
||||
p.Files = p.context.ComposeFiles
|
||||
|
||||
if len(p.Files) == 1 && p.Files[0] == "-" {
|
||||
p.Files = []string{"."}
|
||||
}
|
||||
|
||||
if p.context.ComposeBytes != nil {
|
||||
for i, composeBytes := range p.context.ComposeBytes {
|
||||
file := ""
|
||||
if i < len(p.context.ComposeFiles) {
|
||||
file = p.Files[i]
|
||||
}
|
||||
if err := p.load(file, composeBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateService creates a service with the specified name based. If there
|
||||
// is no config in the project for this service, it will return an error.
|
||||
func (p *Project) CreateService(name string) (Service, error) {
|
||||
existing, ok := p.ServiceConfigs.Get(name)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Failed to find service: %s", name)
|
||||
}
|
||||
|
||||
// Copy because we are about to modify the environment
|
||||
config := *existing
|
||||
|
||||
if p.context.EnvironmentLookup != nil {
|
||||
parsedEnv := make([]string, 0, len(config.Environment))
|
||||
|
||||
for _, env := range config.Environment {
|
||||
parts := strings.SplitN(env, "=", 2)
|
||||
if len(parts) > 1 && parts[1] != "" {
|
||||
parsedEnv = append(parsedEnv, env)
|
||||
continue
|
||||
} else {
|
||||
env = parts[0]
|
||||
}
|
||||
|
||||
for _, value := range p.context.EnvironmentLookup.Lookup(env, name, &config) {
|
||||
parsedEnv = append(parsedEnv, value)
|
||||
}
|
||||
}
|
||||
|
||||
config.Environment = parsedEnv
|
||||
}
|
||||
|
||||
return p.context.ServiceFactory.Create(p, name, &config)
|
||||
}
|
||||
|
||||
// AddConfig adds the specified service config for the specified name.
|
||||
func (p *Project) AddConfig(name string, config *config.ServiceConfig) error {
|
||||
p.Notify(events.ServiceAdd, name, nil)
|
||||
|
||||
p.ServiceConfigs.Add(name, config)
|
||||
p.reload = append(p.reload, name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddVolumeConfig adds the specified volume config for the specified name.
|
||||
func (p *Project) AddVolumeConfig(name string, config *config.VolumeConfig) error {
|
||||
p.Notify(events.VolumeAdd, name, nil)
|
||||
p.VolumeConfigs[name] = config
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddNetworkConfig adds the specified network config for the specified name.
|
||||
func (p *Project) AddNetworkConfig(name string, config *config.NetworkConfig) error {
|
||||
p.Notify(events.NetworkAdd, name, nil)
|
||||
p.NetworkConfigs[name] = config
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load loads the specified byte array (the composefile content) and adds the
|
||||
// service configuration to the project.
|
||||
// FIXME is it needed ?
|
||||
func (p *Project) Load(bytes []byte) error {
|
||||
return p.load("", bytes)
|
||||
}
|
||||
|
||||
func (p *Project) load(file string, bytes []byte) error {
|
||||
serviceConfigs, volumeConfigs, networkConfigs, err := config.Merge(p.ServiceConfigs, p.context.EnvironmentLookup, p.context.ResourceLookup, file, bytes)
|
||||
if err != nil {
|
||||
log.Errorf("Could not parse config for project %s : %v", p.Name, err)
|
||||
return err
|
||||
}
|
||||
|
||||
for name, config := range serviceConfigs {
|
||||
err := p.AddConfig(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for name, config := range volumeConfigs {
|
||||
err := p.AddVolumeConfig(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for name, config := range networkConfigs {
|
||||
err := p.AddNetworkConfig(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Project) loadWrappers(wrappers map[string]*serviceWrapper, servicesToConstruct []string) error {
|
||||
for _, name := range servicesToConstruct {
|
||||
wrapper, err := newServiceWrapper(name, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wrappers[name] = wrapper
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Build builds the specified services (like docker build).
|
||||
func (p *Project) Build(buildOptions options.Build, services ...string) error {
|
||||
return p.perform(events.ProjectBuildStart, events.ProjectBuildDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
|
||||
wrapper.Do(wrappers, events.ServiceBuildStart, events.ServiceBuild, func(service Service) error {
|
||||
return service.Build(buildOptions)
|
||||
})
|
||||
}), nil)
|
||||
}
|
||||
|
||||
// Create creates the specified services (like docker create).
|
||||
func (p *Project) Create(options options.Create, services ...string) error {
|
||||
if options.NoRecreate && options.ForceRecreate {
|
||||
return fmt.Errorf("no-recreate and force-recreate cannot be combined")
|
||||
}
|
||||
return p.perform(events.ProjectCreateStart, events.ProjectCreateDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
|
||||
wrapper.Do(wrappers, events.ServiceCreateStart, events.ServiceCreate, func(service Service) error {
|
||||
return service.Create(options)
|
||||
})
|
||||
}), nil)
|
||||
}
|
||||
|
||||
// Stop stops the specified services (like docker stop).
|
||||
func (p *Project) Stop(timeout int, services ...string) error {
|
||||
return p.perform(events.ProjectStopStart, events.ProjectStopDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
|
||||
wrapper.Do(nil, events.ServiceStopStart, events.ServiceStop, func(service Service) error {
|
||||
return service.Stop(timeout)
|
||||
})
|
||||
}), nil)
|
||||
}
|
||||
|
||||
// Down stops the specified services and clean related containers (like docker stop + docker rm).
|
||||
func (p *Project) Down(opts options.Down, services ...string) error {
|
||||
if !opts.RemoveImages.Valid() {
|
||||
return fmt.Errorf("--rmi flag must be local, all or empty")
|
||||
}
|
||||
if err := p.Stop(10, services...); err != nil {
|
||||
return err
|
||||
}
|
||||
if opts.RemoveOrphans {
|
||||
if err := p.removeOrphanContainers(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := p.Delete(options.Delete{
|
||||
RemoveVolume: opts.RemoveVolume,
|
||||
}, services...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.forEach([]string{}, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
|
||||
wrapper.Do(wrappers, events.NoEvent, events.NoEvent, func(service Service) error {
|
||||
return service.RemoveImage(opts.RemoveImages)
|
||||
})
|
||||
}), func(service Service) error {
|
||||
return service.Create(options.Create{})
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Project) removeOrphanContainers() error {
|
||||
client := p.clientFactory.Create(nil)
|
||||
filter := filters.NewArgs()
|
||||
filter.Add("label", labels.PROJECT.EqString(p.Name))
|
||||
containers, err := client.ContainerList(context.Background(), types.ContainerListOptions{
|
||||
Filter: filter,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentServices := map[string]struct{}{}
|
||||
for _, serviceName := range p.ServiceConfigs.Keys() {
|
||||
currentServices[serviceName] = struct{}{}
|
||||
}
|
||||
for _, container := range containers {
|
||||
serviceLabel := container.Labels[labels.SERVICE.Str()]
|
||||
if _, ok := currentServices[serviceLabel]; !ok {
|
||||
if err := client.ContainerKill(context.Background(), container.ID, "SIGKILL"); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := client.ContainerRemove(context.Background(), container.ID, types.ContainerRemoveOptions{
|
||||
Force: true,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Restart restarts the specified services (like docker restart).
|
||||
func (p *Project) Restart(timeout int, services ...string) error {
|
||||
return p.perform(events.ProjectRestartStart, events.ProjectRestartDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
|
||||
wrapper.Do(wrappers, events.ServiceRestartStart, events.ServiceRestart, func(service Service) error {
|
||||
return service.Restart(timeout)
|
||||
})
|
||||
}), nil)
|
||||
}
|
||||
|
||||
// Port returns the public port for a port binding of the specified service.
|
||||
func (p *Project) Port(index int, protocol, serviceName, privatePort string) (string, error) {
|
||||
service, err := p.CreateService(serviceName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
containers, err := service.Containers()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if index < 1 || index > len(containers) {
|
||||
return "", fmt.Errorf("Invalid index %d", index)
|
||||
}
|
||||
|
||||
return containers[index-1].Port(fmt.Sprintf("%s/%s", privatePort, protocol))
|
||||
}
|
||||
|
||||
// Ps list containers for the specified services.
|
||||
func (p *Project) Ps(onlyID bool, services ...string) (InfoSet, error) {
|
||||
allInfo := InfoSet{}
|
||||
for _, name := range p.ServiceConfigs.Keys() {
|
||||
service, err := p.CreateService(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, err := service.Info(onlyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allInfo = append(allInfo, info...)
|
||||
}
|
||||
return allInfo, nil
|
||||
}
|
||||
|
||||
// Start starts the specified services (like docker start).
|
||||
func (p *Project) Start(services ...string) error {
|
||||
return p.perform(events.ProjectStartStart, events.ProjectStartDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
|
||||
wrapper.Do(wrappers, events.ServiceStartStart, events.ServiceStart, func(service Service) error {
|
||||
return service.Start()
|
||||
})
|
||||
}), nil)
|
||||
}
|
||||
|
||||
// Run executes a one off command (like `docker run image command`).
|
||||
func (p *Project) Run(ctx context.Context, serviceName string, commandParts []string) (int, error) {
|
||||
if !p.ServiceConfigs.Has(serviceName) {
|
||||
return 1, fmt.Errorf("%s is not defined in the template", serviceName)
|
||||
}
|
||||
|
||||
var exitCode int
|
||||
err := p.forEach([]string{}, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
|
||||
wrapper.Do(wrappers, events.ServiceRunStart, events.ServiceRun, func(service Service) error {
|
||||
if service.Name() == serviceName {
|
||||
code, err := service.Run(ctx, commandParts)
|
||||
exitCode = code
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}), func(service Service) error {
|
||||
return service.Create(options.Create{})
|
||||
})
|
||||
return exitCode, err
|
||||
}
|
||||
|
||||
// Up creates and starts the specified services (kinda like docker run).
|
||||
func (p *Project) Up(options options.Up, services ...string) error {
|
||||
return p.perform(events.ProjectUpStart, events.ProjectUpDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
|
||||
wrapper.Do(wrappers, events.ServiceUpStart, events.ServiceUp, func(service Service) error {
|
||||
return service.Up(options)
|
||||
})
|
||||
}), func(service Service) error {
|
||||
return service.Create(options.Create)
|
||||
})
|
||||
}
|
||||
|
||||
// Log aggregates and prints out the logs for the specified services.
|
||||
func (p *Project) Log(follow bool, services ...string) error {
|
||||
return p.forEach(services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
|
||||
wrapper.Do(nil, events.NoEvent, events.NoEvent, func(service Service) error {
|
||||
return service.Log(follow)
|
||||
})
|
||||
}), nil)
|
||||
}
|
||||
|
||||
// Scale scales the specified services.
|
||||
func (p *Project) Scale(timeout int, servicesScale map[string]int) error {
|
||||
// This code is a bit verbose but I wanted to parse everything up front
|
||||
order := make([]string, 0, 0)
|
||||
services := make(map[string]Service)
|
||||
|
||||
for name := range servicesScale {
|
||||
if !p.ServiceConfigs.Has(name) {
|
||||
return fmt.Errorf("%s is not defined in the template", name)
|
||||
}
|
||||
|
||||
service, err := p.CreateService(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to lookup service: %s: %v", service, err)
|
||||
}
|
||||
|
||||
order = append(order, name)
|
||||
services[name] = service
|
||||
}
|
||||
|
||||
for _, name := range order {
|
||||
scale := servicesScale[name]
|
||||
log.Infof("Setting scale %s=%d...", name, scale)
|
||||
err := services[name].Scale(scale, timeout)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to set the scale %s=%d: %v", name, scale, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pull pulls the specified services (like docker pull).
|
||||
func (p *Project) Pull(services ...string) error {
|
||||
return p.forEach(services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
|
||||
wrapper.Do(nil, events.ServicePullStart, events.ServicePull, func(service Service) error {
|
||||
return service.Pull()
|
||||
})
|
||||
}), nil)
|
||||
}
|
||||
|
||||
// listStoppedContainers lists the stopped containers for the specified services.
|
||||
func (p *Project) listStoppedContainers(services ...string) ([]string, error) {
|
||||
stoppedContainers := []string{}
|
||||
err := p.forEach(services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
|
||||
wrapper.Do(nil, events.NoEvent, events.NoEvent, func(service Service) error {
|
||||
containers, innerErr := service.Containers()
|
||||
if innerErr != nil {
|
||||
return innerErr
|
||||
}
|
||||
|
||||
for _, container := range containers {
|
||||
running, innerErr := container.IsRunning()
|
||||
if innerErr != nil {
|
||||
log.Error(innerErr)
|
||||
}
|
||||
if !running {
|
||||
containerID, innerErr := container.ID()
|
||||
if innerErr != nil {
|
||||
log.Error(innerErr)
|
||||
}
|
||||
stoppedContainers = append(stoppedContainers, containerID)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return stoppedContainers, nil
|
||||
}
|
||||
|
||||
// Delete removes the specified services (like docker rm).
|
||||
func (p *Project) Delete(options options.Delete, services ...string) error {
|
||||
stoppedContainers, err := p.listStoppedContainers(services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(stoppedContainers) == 0 {
|
||||
p.Notify(events.ProjectDeleteDone, "", nil)
|
||||
fmt.Println("No stopped containers")
|
||||
return nil
|
||||
}
|
||||
if options.BeforeDeleteCallback != nil && !options.BeforeDeleteCallback(stoppedContainers) {
|
||||
return nil
|
||||
}
|
||||
return p.perform(events.ProjectDeleteStart, events.ProjectDeleteDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
|
||||
wrapper.Do(nil, events.ServiceDeleteStart, events.ServiceDelete, func(service Service) error {
|
||||
return service.Delete(options)
|
||||
})
|
||||
}), nil)
|
||||
}
|
||||
|
||||
// Kill kills the specified services (like docker kill).
|
||||
func (p *Project) Kill(signal string, services ...string) error {
|
||||
return p.perform(events.ProjectKillStart, events.ProjectKillDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
|
||||
wrapper.Do(nil, events.ServiceKillStart, events.ServiceKill, func(service Service) error {
|
||||
return service.Kill(signal)
|
||||
})
|
||||
}), nil)
|
||||
}
|
||||
|
||||
// Pause pauses the specified services containers (like docker pause).
|
||||
func (p *Project) Pause(services ...string) error {
|
||||
return p.perform(events.ProjectPauseStart, events.ProjectPauseDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
|
||||
wrapper.Do(nil, events.ServicePauseStart, events.ServicePause, func(service Service) error {
|
||||
return service.Pause()
|
||||
})
|
||||
}), nil)
|
||||
}
|
||||
|
||||
// Unpause pauses the specified services containers (like docker pause).
|
||||
func (p *Project) Unpause(services ...string) error {
|
||||
return p.perform(events.ProjectUnpauseStart, events.ProjectUnpauseDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
|
||||
wrapper.Do(nil, events.ServiceUnpauseStart, events.ServiceUnpause, func(service Service) error {
|
||||
return service.Unpause()
|
||||
})
|
||||
}), nil)
|
||||
}
|
||||
|
||||
func (p *Project) perform(start, done events.EventType, services []string, action wrapperAction, cycleAction serviceAction) error {
|
||||
p.Notify(start, "", nil)
|
||||
|
||||
err := p.forEach(services, action, cycleAction)
|
||||
|
||||
p.Notify(done, "", nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func isSelected(wrapper *serviceWrapper, selected map[string]bool) bool {
|
||||
return len(selected) == 0 || selected[wrapper.name]
|
||||
}
|
||||
|
||||
func (p *Project) forEach(services []string, action wrapperAction, cycleAction serviceAction) error {
|
||||
selected := make(map[string]bool)
|
||||
wrappers := make(map[string]*serviceWrapper)
|
||||
|
||||
for _, s := range services {
|
||||
selected[s] = true
|
||||
}
|
||||
|
||||
return p.traverse(true, selected, wrappers, action, cycleAction)
|
||||
}
|
||||
|
||||
func (p *Project) startService(wrappers map[string]*serviceWrapper, history []string, selected, launched map[string]bool, wrapper *serviceWrapper, action wrapperAction, cycleAction serviceAction) error {
|
||||
if launched[wrapper.name] {
|
||||
return nil
|
||||
}
|
||||
|
||||
launched[wrapper.name] = true
|
||||
history = append(history, wrapper.name)
|
||||
|
||||
for _, dep := range wrapper.service.DependentServices() {
|
||||
target := wrappers[dep.Target]
|
||||
if target == nil {
|
||||
log.Errorf("Failed to find %s", dep.Target)
|
||||
continue
|
||||
}
|
||||
|
||||
if utils.Contains(history, dep.Target) {
|
||||
cycle := strings.Join(append(history, dep.Target), "->")
|
||||
if dep.Optional {
|
||||
log.Debugf("Ignoring cycle for %s", cycle)
|
||||
wrapper.IgnoreDep(dep.Target)
|
||||
if cycleAction != nil {
|
||||
var err error
|
||||
log.Debugf("Running cycle action for %s", cycle)
|
||||
err = cycleAction(target.service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Cycle detected in path %s", cycle)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
err := p.startService(wrappers, history, selected, launched, target, action, cycleAction)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if isSelected(wrapper, selected) {
|
||||
log.Debugf("Launching action for %s", wrapper.name)
|
||||
go action(wrapper, wrappers)
|
||||
} else {
|
||||
wrapper.Ignore()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Project) traverse(start bool, selected map[string]bool, wrappers map[string]*serviceWrapper, action wrapperAction, cycleAction serviceAction) error {
|
||||
restart := false
|
||||
wrapperList := []string{}
|
||||
|
||||
if start {
|
||||
for _, name := range p.ServiceConfigs.Keys() {
|
||||
wrapperList = append(wrapperList, name)
|
||||
}
|
||||
} else {
|
||||
for _, wrapper := range wrappers {
|
||||
if err := wrapper.Reset(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
wrapperList = p.reload
|
||||
}
|
||||
|
||||
p.loadWrappers(wrappers, wrapperList)
|
||||
p.reload = []string{}
|
||||
|
||||
// check service name
|
||||
for s := range selected {
|
||||
if wrappers[s] == nil {
|
||||
return errors.New("No such service: " + s)
|
||||
}
|
||||
}
|
||||
|
||||
launched := map[string]bool{}
|
||||
|
||||
for _, wrapper := range wrappers {
|
||||
if err := p.startService(wrappers, []string{}, selected, launched, wrapper, action, cycleAction); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var firstError error
|
||||
|
||||
for _, wrapper := range wrappers {
|
||||
if !isSelected(wrapper, selected) {
|
||||
continue
|
||||
}
|
||||
if err := wrapper.Wait(); err == ErrRestart {
|
||||
restart = true
|
||||
} else if err != nil {
|
||||
log.Errorf("Failed to start: %s : %v", wrapper.name, err)
|
||||
if firstError == nil {
|
||||
firstError = err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if restart {
|
||||
if p.ReloadCallback != nil {
|
||||
if err := p.ReloadCallback(); err != nil {
|
||||
log.Errorf("Failed calling callback: %v", err)
|
||||
}
|
||||
}
|
||||
return p.traverse(false, selected, wrappers, action, cycleAction)
|
||||
}
|
||||
return firstError
|
||||
}
|
||||
|
||||
// AddListener adds the specified listener to the project.
|
||||
// This implements implicitly events.Emitter.
|
||||
func (p *Project) AddListener(c chan<- events.Event) {
|
||||
if !p.hasListeners {
|
||||
for _, l := range p.listeners {
|
||||
close(l)
|
||||
}
|
||||
p.hasListeners = true
|
||||
p.listeners = []chan<- events.Event{c}
|
||||
} else {
|
||||
p.listeners = append(p.listeners, c)
|
||||
}
|
||||
}
|
||||
|
||||
// Notify notifies all project listener with the specified eventType, service name and datas.
|
||||
// This implements implicitly events.Notifier interface.
|
||||
func (p *Project) Notify(eventType events.EventType, serviceName string, data map[string]string) {
|
||||
if eventType == events.NoEvent {
|
||||
return
|
||||
}
|
||||
|
||||
event := events.Event{
|
||||
EventType: eventType,
|
||||
ServiceName: serviceName,
|
||||
Data: data,
|
||||
}
|
||||
|
||||
for _, l := range p.listeners {
|
||||
l <- event
|
||||
}
|
||||
}
|
||||
222
vendor/github.com/hyperhq/libcompose/project/project_test.go
generated
vendored
Normal file
222
vendor/github.com/hyperhq/libcompose/project/project_test.go
generated
vendored
Normal file
@@ -0,0 +1,222 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hyperhq/libcompose/config"
|
||||
"github.com/hyperhq/libcompose/project/options"
|
||||
"github.com/hyperhq/libcompose/yaml"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type TestServiceFactory struct {
|
||||
Counts map[string]int
|
||||
}
|
||||
|
||||
type TestService struct {
|
||||
factory *TestServiceFactory
|
||||
name string
|
||||
config *config.ServiceConfig
|
||||
EmptyService
|
||||
Count int
|
||||
}
|
||||
|
||||
func (t *TestService) Config() *config.ServiceConfig {
|
||||
return t.config
|
||||
}
|
||||
|
||||
func (t *TestService) Name() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
func (t *TestService) Run(ctx context.Context, commandParts []string) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (t *TestService) Create(options options.Create) error {
|
||||
key := t.name + ".create"
|
||||
t.factory.Counts[key] = t.factory.Counts[key] + 1
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TestService) DependentServices() []ServiceRelationship {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TestServiceFactory) Create(project *Project, name string, serviceConfig *config.ServiceConfig) (Service, error) {
|
||||
return &TestService{
|
||||
factory: t,
|
||||
config: serviceConfig,
|
||||
name: name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TestTwoCall(t *testing.T) {
|
||||
factory := &TestServiceFactory{
|
||||
Counts: map[string]int{},
|
||||
}
|
||||
|
||||
p := NewProject(nil, &Context{
|
||||
ServiceFactory: factory,
|
||||
})
|
||||
p.ServiceConfigs = config.NewServiceConfigs()
|
||||
p.ServiceConfigs.Add("foo", &config.ServiceConfig{})
|
||||
|
||||
if err := p.Create(options.Create{}, "foo"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := p.Create(options.Create{}, "foo"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if factory.Counts["foo.create"] != 2 {
|
||||
t.Fatal("Failed to create twice")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseWithBadContent(t *testing.T) {
|
||||
p := NewProject(nil, &Context{
|
||||
ComposeBytes: [][]byte{
|
||||
[]byte("garbage"),
|
||||
},
|
||||
})
|
||||
|
||||
err := p.Parse()
|
||||
if err == nil {
|
||||
t.Fatal("Should have failed parse")
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(err.Error(), "Invalid timestamp: 'garbage'") {
|
||||
t.Fatalf("Should have failed parse: %#v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseWithGoodContent(t *testing.T) {
|
||||
p := NewProject(nil, &Context{
|
||||
ComposeBytes: [][]byte{
|
||||
[]byte("not-garbage:\n image: foo"),
|
||||
},
|
||||
})
|
||||
|
||||
err := p.Parse()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
type TestEnvironmentLookup struct {
|
||||
}
|
||||
|
||||
func (t *TestEnvironmentLookup) Lookup(key, serviceName string, config *config.ServiceConfig) []string {
|
||||
return []string{fmt.Sprintf("%s=X", key)}
|
||||
}
|
||||
|
||||
func TestEnvironmentResolve(t *testing.T) {
|
||||
factory := &TestServiceFactory{
|
||||
Counts: map[string]int{},
|
||||
}
|
||||
|
||||
p := NewProject(nil, &Context{
|
||||
ServiceFactory: factory,
|
||||
EnvironmentLookup: &TestEnvironmentLookup{},
|
||||
})
|
||||
p.ServiceConfigs = config.NewServiceConfigs()
|
||||
p.ServiceConfigs.Add("foo", &config.ServiceConfig{
|
||||
Environment: yaml.MaporEqualSlice([]string{
|
||||
"A",
|
||||
"A=",
|
||||
"A=B",
|
||||
}),
|
||||
})
|
||||
|
||||
service, err := p.CreateService("foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(service.Config().Environment, yaml.MaporEqualSlice{"A=X", "A=X", "A=B"}) {
|
||||
t.Fatal("Invalid environment", service.Config().Environment)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseWithMultipleComposeFiles(t *testing.T) {
|
||||
/*
|
||||
configOne := []byte(`
|
||||
multiple:
|
||||
image: tianon/true
|
||||
ports:
|
||||
- 8000`)
|
||||
|
||||
configTwo := []byte(`
|
||||
multiple:
|
||||
image: busybox
|
||||
container_name: multi
|
||||
ports:
|
||||
- 9000`)
|
||||
|
||||
configThree := []byte(`
|
||||
multiple:
|
||||
image: busybox
|
||||
mem_limit: 40000000
|
||||
ports:
|
||||
- 10000`)
|
||||
*/
|
||||
configOne := []byte(`
|
||||
multiple:
|
||||
image: tianon/true`)
|
||||
|
||||
configTwo := []byte(`
|
||||
multiple:
|
||||
image: busybox
|
||||
container_name: multi`)
|
||||
|
||||
configThree := []byte(`
|
||||
multiple:
|
||||
image: busybox
|
||||
size: xxs`)
|
||||
|
||||
p := NewProject(nil, &Context{
|
||||
ComposeBytes: [][]byte{configOne, configTwo},
|
||||
})
|
||||
|
||||
err := p.Parse()
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
multipleConfig, _ := p.ServiceConfigs.Get("multiple")
|
||||
assert.Equal(t, "busybox", multipleConfig.Image)
|
||||
assert.Equal(t, "multi", multipleConfig.ContainerName)
|
||||
//assert.Equal(t, []string{"8000", "9000"}, multipleConfig.Ports)
|
||||
|
||||
p = NewProject(nil, &Context{
|
||||
ComposeBytes: [][]byte{configTwo, configOne},
|
||||
})
|
||||
|
||||
err = p.Parse()
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
multipleConfig, _ = p.ServiceConfigs.Get("multiple")
|
||||
assert.Equal(t, "tianon/true", multipleConfig.Image)
|
||||
assert.Equal(t, "multi", multipleConfig.ContainerName)
|
||||
//assert.Equal(t, []string{"9000", "8000"}, multipleConfig.Ports)
|
||||
|
||||
p = NewProject(nil, &Context{
|
||||
ComposeBytes: [][]byte{configOne, configTwo, configThree},
|
||||
})
|
||||
|
||||
err = p.Parse()
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
multipleConfig, _ = p.ServiceConfigs.Get("multiple")
|
||||
assert.Equal(t, "busybox", multipleConfig.Image)
|
||||
assert.Equal(t, "multi", multipleConfig.ContainerName)
|
||||
//assert.Equal(t, []string{"8000", "9000", "10000"}, multipleConfig.Ports)
|
||||
//assert.Equal(t, int64(40000000), multipleConfig.MemLimit)
|
||||
}
|
||||
115
vendor/github.com/hyperhq/libcompose/project/service-wrapper.go
generated
vendored
Normal file
115
vendor/github.com/hyperhq/libcompose/project/service-wrapper.go
generated
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/hyperhq/libcompose/project/events"
|
||||
)
|
||||
|
||||
type serviceWrapper struct {
|
||||
name string
|
||||
service Service
|
||||
done sync.WaitGroup
|
||||
state ServiceState
|
||||
err error
|
||||
project *Project
|
||||
noWait bool
|
||||
ignored map[string]bool
|
||||
}
|
||||
|
||||
func newServiceWrapper(name string, p *Project) (*serviceWrapper, error) {
|
||||
wrapper := &serviceWrapper{
|
||||
name: name,
|
||||
state: StateUnknown,
|
||||
project: p,
|
||||
ignored: map[string]bool{},
|
||||
}
|
||||
|
||||
return wrapper, wrapper.Reset()
|
||||
}
|
||||
|
||||
func (s *serviceWrapper) IgnoreDep(name string) {
|
||||
s.ignored[name] = true
|
||||
}
|
||||
|
||||
func (s *serviceWrapper) Reset() error {
|
||||
if s.state != StateExecuted {
|
||||
service, err := s.project.CreateService(s.name)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create service for %s : %v", s.name, err)
|
||||
return err
|
||||
}
|
||||
|
||||
s.service = service
|
||||
}
|
||||
|
||||
if s.err == ErrRestart {
|
||||
s.err = nil
|
||||
}
|
||||
s.done.Add(1)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *serviceWrapper) Ignore() {
|
||||
defer s.done.Done()
|
||||
|
||||
s.state = StateExecuted
|
||||
s.project.Notify(events.ServiceUpIgnored, s.service.Name(), nil)
|
||||
}
|
||||
|
||||
func (s *serviceWrapper) waitForDeps(wrappers map[string]*serviceWrapper) bool {
|
||||
if s.noWait {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, dep := range s.service.DependentServices() {
|
||||
if s.ignored[dep.Target] {
|
||||
continue
|
||||
}
|
||||
|
||||
if wrapper, ok := wrappers[dep.Target]; ok {
|
||||
if wrapper.Wait() == ErrRestart {
|
||||
s.project.Notify(events.ProjectReload, wrapper.service.Name(), nil)
|
||||
s.err = ErrRestart
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
log.Errorf("Failed to find %s", dep.Target)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *serviceWrapper) Do(wrappers map[string]*serviceWrapper, start, done events.EventType, action func(service Service) error) {
|
||||
defer s.done.Done()
|
||||
|
||||
if s.state == StateExecuted {
|
||||
return
|
||||
}
|
||||
|
||||
if wrappers != nil && !s.waitForDeps(wrappers) {
|
||||
return
|
||||
}
|
||||
|
||||
s.state = StateExecuted
|
||||
|
||||
s.project.Notify(start, s.service.Name(), nil)
|
||||
|
||||
s.err = action(s.service)
|
||||
if s.err == ErrRestart {
|
||||
s.project.Notify(done, s.service.Name(), nil)
|
||||
s.project.Notify(events.ProjectReloadTrigger, s.service.Name(), nil)
|
||||
} else if s.err != nil {
|
||||
log.Errorf("Failed %s %s : %v", start, s.name, s.err)
|
||||
} else {
|
||||
s.project.Notify(done, s.service.Name(), nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *serviceWrapper) Wait() error {
|
||||
s.done.Wait()
|
||||
return s.err
|
||||
}
|
||||
89
vendor/github.com/hyperhq/libcompose/project/service.go
generated
vendored
Normal file
89
vendor/github.com/hyperhq/libcompose/project/service.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/hyperhq/libcompose/config"
|
||||
"github.com/hyperhq/libcompose/project/options"
|
||||
)
|
||||
|
||||
// Service defines what a libcompose service provides.
|
||||
type Service interface {
|
||||
Info(qFlag bool) (InfoSet, error)
|
||||
Name() string
|
||||
Build(buildOptions options.Build) error
|
||||
Create(options options.Create) error
|
||||
Up(options options.Up) error
|
||||
Start() error
|
||||
Stop(timeout int) error
|
||||
Delete(options options.Delete) error
|
||||
Restart(timeout int) error
|
||||
Log(follow bool) error
|
||||
Pull() error
|
||||
Kill(signal string) error
|
||||
Config() *config.ServiceConfig
|
||||
DependentServices() []ServiceRelationship
|
||||
Containers() ([]Container, error)
|
||||
Scale(count int, timeout int) error
|
||||
Pause() error
|
||||
Unpause() error
|
||||
Run(ctx context.Context, commandParts []string) (int, error)
|
||||
|
||||
RemoveImage(imageType options.ImageType) error
|
||||
}
|
||||
|
||||
// ServiceState holds the state of a service.
|
||||
type ServiceState string
|
||||
|
||||
// State definitions
|
||||
var (
|
||||
StateExecuted = ServiceState("executed")
|
||||
StateUnknown = ServiceState("unknown")
|
||||
)
|
||||
|
||||
// Error definitions
|
||||
var (
|
||||
ErrRestart = errors.New("Restart execution")
|
||||
ErrUnsupported = errors.New("UnsupportedOperation")
|
||||
)
|
||||
|
||||
// ServiceFactory is an interface factory to create Service object for the specified
|
||||
// project, with the specified name and service configuration.
|
||||
type ServiceFactory interface {
|
||||
Create(project *Project, name string, serviceConfig *config.ServiceConfig) (Service, error)
|
||||
}
|
||||
|
||||
// ServiceRelationshipType defines the type of service relationship.
|
||||
type ServiceRelationshipType string
|
||||
|
||||
// RelTypeLink means the services are linked (docker links).
|
||||
const RelTypeLink = ServiceRelationshipType("")
|
||||
|
||||
// RelTypeNetNamespace means the services share the same network namespace.
|
||||
const RelTypeNetNamespace = ServiceRelationshipType("netns")
|
||||
|
||||
// RelTypeIpcNamespace means the service share the same ipc namespace.
|
||||
const RelTypeIpcNamespace = ServiceRelationshipType("ipc")
|
||||
|
||||
// RelTypeVolumesFrom means the services share some volumes.
|
||||
const RelTypeVolumesFrom = ServiceRelationshipType("volumesFrom")
|
||||
|
||||
// ServiceRelationship holds the relationship information between two services.
|
||||
type ServiceRelationship struct {
|
||||
Target, Alias string
|
||||
Type ServiceRelationshipType
|
||||
Optional bool
|
||||
}
|
||||
|
||||
// NewServiceRelationship creates a new Relationship based on the specified alias
|
||||
// and relationship type.
|
||||
func NewServiceRelationship(nameAlias string, relType ServiceRelationshipType) ServiceRelationship {
|
||||
name, alias := NameAlias(nameAlias)
|
||||
return ServiceRelationship{
|
||||
Target: name,
|
||||
Alias: alias,
|
||||
Type: relType,
|
||||
}
|
||||
}
|
||||
70
vendor/github.com/hyperhq/libcompose/project/utils.go
generated
vendored
Normal file
70
vendor/github.com/hyperhq/libcompose/project/utils.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/docker/engine-api/types/container"
|
||||
)
|
||||
|
||||
// DefaultDependentServices return the dependent services (as an array of ServiceRelationship)
|
||||
// for the specified project and service. It looks for : links, volumesFrom, net and ipc configuration.
|
||||
func DefaultDependentServices(p *Project, s Service) []ServiceRelationship {
|
||||
config := s.Config()
|
||||
if config == nil {
|
||||
return []ServiceRelationship{}
|
||||
}
|
||||
|
||||
result := []ServiceRelationship{}
|
||||
for _, link := range config.Links {
|
||||
result = append(result, NewServiceRelationship(link, RelTypeLink))
|
||||
}
|
||||
|
||||
/*
|
||||
for _, volumesFrom := range config.VolumesFrom {
|
||||
result = append(result, NewServiceRelationship(volumesFrom, RelTypeVolumesFrom))
|
||||
}
|
||||
|
||||
result = appendNs(p, result, s.Config().NetworkMode, RelTypeNetNamespace)
|
||||
result = appendNs(p, result, s.Config().Ipc, RelTypeIpcNamespace)
|
||||
*/
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func appendNs(p *Project, rels []ServiceRelationship, conf string, relType ServiceRelationshipType) []ServiceRelationship {
|
||||
service := GetContainerFromIpcLikeConfig(p, conf)
|
||||
if service != "" {
|
||||
rels = append(rels, NewServiceRelationship(service, relType))
|
||||
}
|
||||
return rels
|
||||
}
|
||||
|
||||
// NameAlias returns the name and alias based on the specified string.
|
||||
// If the name contains a colon (like name:alias) it will split it, otherwise
|
||||
// it will return the specified name as name and alias.
|
||||
func NameAlias(name string) (string, string) {
|
||||
parts := strings.SplitN(name, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
return parts[0], parts[1]
|
||||
}
|
||||
return parts[0], parts[0]
|
||||
}
|
||||
|
||||
// GetContainerFromIpcLikeConfig returns name of the service that shares the IPC
|
||||
// namespace with the specified service.
|
||||
func GetContainerFromIpcLikeConfig(p *Project, conf string) string {
|
||||
ipc := container.IpcMode(conf)
|
||||
if !ipc.IsContainer() {
|
||||
return ""
|
||||
}
|
||||
|
||||
name := ipc.Container()
|
||||
if name == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if p.ServiceConfigs.Has(name) {
|
||||
return name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
386
vendor/github.com/hyperhq/libcompose/test/nop.go
generated
vendored
Normal file
386
vendor/github.com/hyperhq/libcompose/test/nop.go
generated
vendored
Normal file
@@ -0,0 +1,386 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/container"
|
||||
"github.com/docker/engine-api/types/filters"
|
||||
"github.com/docker/engine-api/types/network"
|
||||
"github.com/docker/engine-api/types/registry"
|
||||
)
|
||||
|
||||
var (
|
||||
errNoEngine = errors.New("Engine no longer exists")
|
||||
)
|
||||
|
||||
// NopClient is a nop API Client based on engine-api
|
||||
type NopClient struct {
|
||||
}
|
||||
|
||||
// NewNopClient creates a new nop client
|
||||
func NewNopClient() *NopClient {
|
||||
return &NopClient{}
|
||||
}
|
||||
|
||||
// ClientVersion returns the version string associated with this instance of the Client
|
||||
func (client *NopClient) ClientVersion() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ContainerAttach attaches a connection to a container in the server
|
||||
func (client *NopClient) ContainerAttach(ctx context.Context, container string, options types.ContainerAttachOptions) (types.HijackedResponse, error) {
|
||||
return types.HijackedResponse{}, errNoEngine
|
||||
}
|
||||
|
||||
// ContainerCommit applies changes into a container and creates a new tagged image
|
||||
func (client *NopClient) ContainerCommit(ctx context.Context, container string, options types.ContainerCommitOptions) (types.ContainerCommitResponse, error) {
|
||||
return types.ContainerCommitResponse{}, errNoEngine
|
||||
}
|
||||
|
||||
// ContainerCreate creates a new container based in the given configuration
|
||||
func (client *NopClient) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (types.ContainerCreateResponse, error) {
|
||||
return types.ContainerCreateResponse{}, errNoEngine
|
||||
}
|
||||
|
||||
// ContainerDiff shows differences in a container filesystem since it was started
|
||||
func (client *NopClient) ContainerDiff(ctx context.Context, container string) ([]types.ContainerChange, error) {
|
||||
return nil, errNoEngine
|
||||
}
|
||||
|
||||
// ContainerExecAttach attaches a connection to an exec process in the server
|
||||
func (client *NopClient) ContainerExecAttach(ctx context.Context, execID string, config types.ExecConfig) (types.HijackedResponse, error) {
|
||||
return types.HijackedResponse{}, errNoEngine
|
||||
}
|
||||
|
||||
// ContainerExecCreate creates a new exec configuration to run an exec process
|
||||
func (client *NopClient) ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.ContainerExecCreateResponse, error) {
|
||||
return types.ContainerExecCreateResponse{}, errNoEngine
|
||||
}
|
||||
|
||||
// ContainerExecInspect returns information about a specific exec process on the docker host
|
||||
func (client *NopClient) ContainerExecInspect(ctx context.Context, execID string) (types.ContainerExecInspect, error) {
|
||||
return types.ContainerExecInspect{}, errNoEngine
|
||||
}
|
||||
|
||||
// ContainerExecResize changes the size of the tty for an exec process running inside a container
|
||||
func (client *NopClient) ContainerExecResize(ctx context.Context, execID string, options types.ResizeOptions) error {
|
||||
return errNoEngine
|
||||
}
|
||||
|
||||
// ContainerExecStart starts an exec process already create in the docker host
|
||||
func (client *NopClient) ContainerExecStart(ctx context.Context, execID string, config types.ExecStartCheck) error {
|
||||
return errNoEngine
|
||||
}
|
||||
|
||||
// ContainerExport retrieves the raw contents of a container and returns them as an io.ReadCloser
|
||||
func (client *NopClient) ContainerExport(ctx context.Context, container string) (io.ReadCloser, error) {
|
||||
return nil, errNoEngine
|
||||
}
|
||||
|
||||
// ContainerInspect returns the container information
|
||||
func (client *NopClient) ContainerInspect(ctx context.Context, container string) (types.ContainerJSON, error) {
|
||||
return types.ContainerJSON{}, errNoEngine
|
||||
}
|
||||
|
||||
// ContainerInspectWithRaw returns the container information and its raw representation
|
||||
func (client *NopClient) ContainerInspectWithRaw(ctx context.Context, container string, getSize bool) (types.ContainerJSON, []byte, error) {
|
||||
return types.ContainerJSON{}, nil, errNoEngine
|
||||
}
|
||||
|
||||
// ContainerKill terminates the container process but does not remove the container from the docker host
|
||||
func (client *NopClient) ContainerKill(ctx context.Context, container string, signal string) error {
|
||||
return errNoEngine
|
||||
}
|
||||
|
||||
// ContainerList returns the list of containers in the docker host
|
||||
func (client *NopClient) ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) {
|
||||
return nil, errNoEngine
|
||||
}
|
||||
|
||||
// ContainerLogs returns the logs generated by a container in an io.ReadCloser
|
||||
func (client *NopClient) ContainerLogs(ctx context.Context, container string, options types.ContainerLogsOptions) (io.ReadCloser, error) {
|
||||
return nil, errNoEngine
|
||||
}
|
||||
|
||||
// ContainerPause pauses the main process of a given container without terminating it
|
||||
func (client *NopClient) ContainerPause(ctx context.Context, container string) error {
|
||||
return errNoEngine
|
||||
}
|
||||
|
||||
// ContainerRemove kills and removes a container from the docker host
|
||||
func (client *NopClient) ContainerRemove(ctx context.Context, container string, options types.ContainerRemoveOptions) ([]string, error) {
|
||||
return []string{}, errNoEngine
|
||||
}
|
||||
|
||||
// ContainerRename changes the name of a given container
|
||||
func (client *NopClient) ContainerRename(ctx context.Context, container, newContainerName string) error {
|
||||
return errNoEngine
|
||||
}
|
||||
|
||||
// ContainerResize changes the size of the tty for a container
|
||||
func (client *NopClient) ContainerResize(ctx context.Context, container string, options types.ResizeOptions) error {
|
||||
return errNoEngine
|
||||
}
|
||||
|
||||
// ContainerRestart stops and starts a container again
|
||||
func (client *NopClient) ContainerRestart(ctx context.Context, container string, timeout int) error {
|
||||
return errNoEngine
|
||||
}
|
||||
|
||||
// ContainerStatPath returns Stat information about a path inside the container filesystem
|
||||
func (client *NopClient) ContainerStatPath(ctx context.Context, container, path string) (types.ContainerPathStat, error) {
|
||||
return types.ContainerPathStat{}, errNoEngine
|
||||
}
|
||||
|
||||
// ContainerStats returns near realtime stats for a given container
|
||||
func (client *NopClient) ContainerStats(ctx context.Context, container string, stream bool) (io.ReadCloser, error) {
|
||||
return nil, errNoEngine
|
||||
}
|
||||
|
||||
// ContainerStart sends a request to the docker daemon to start a container
|
||||
func (client *NopClient) ContainerStart(ctx context.Context, container, checkpointID string) error {
|
||||
return errNoEngine
|
||||
}
|
||||
|
||||
// ContainerStop stops a container without terminating the process
|
||||
func (client *NopClient) ContainerStop(ctx context.Context, container string, timeout int) error {
|
||||
return errNoEngine
|
||||
}
|
||||
|
||||
// ContainerTop shows process information from within a container
|
||||
func (client *NopClient) ContainerTop(ctx context.Context, container string, arguments []string) (types.ContainerProcessList, error) {
|
||||
return types.ContainerProcessList{}, errNoEngine
|
||||
}
|
||||
|
||||
// ContainerUnpause resumes the process execution within a container
|
||||
func (client *NopClient) ContainerUnpause(ctx context.Context, container string) error {
|
||||
return errNoEngine
|
||||
}
|
||||
|
||||
// ContainerUpdate updates resources of a container
|
||||
func (client *NopClient) ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) error {
|
||||
return errNoEngine
|
||||
}
|
||||
|
||||
// ContainerWait pauses execution until a container exits
|
||||
func (client *NopClient) ContainerWait(ctx context.Context, container string) (int, error) {
|
||||
return 0, errNoEngine
|
||||
}
|
||||
|
||||
// CopyFromContainer gets the content from the container and returns it as a Reader to manipulate it in the host
|
||||
func (client *NopClient) CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
|
||||
return nil, types.ContainerPathStat{}, errNoEngine
|
||||
}
|
||||
|
||||
// CopyToContainer copies content into the container filesystem
|
||||
func (client *NopClient) CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error {
|
||||
return errNoEngine
|
||||
}
|
||||
|
||||
// Events returns a stream of events in the daemon in a ReadCloser
|
||||
func (client *NopClient) Events(ctx context.Context, options types.EventsOptions) (io.ReadCloser, error) {
|
||||
return nil, errNoEngine
|
||||
}
|
||||
|
||||
// ImageBuild sends request to the daemon to build images
|
||||
func (client *NopClient) ImageBuild(ctx context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
|
||||
return types.ImageBuildResponse{}, errNoEngine
|
||||
}
|
||||
|
||||
// ImageCreate creates a new image based in the parent options
|
||||
func (client *NopClient) ImageCreate(ctx context.Context, parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error) {
|
||||
return nil, errNoEngine
|
||||
}
|
||||
|
||||
// ImageHistory returns the changes in an image in history format
|
||||
func (client *NopClient) ImageHistory(ctx context.Context, image string) ([]types.ImageHistory, error) {
|
||||
return nil, errNoEngine
|
||||
}
|
||||
|
||||
// ImageImport creates a new image based in the source options
|
||||
func (client *NopClient) ImageImport(ctx context.Context, source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error) {
|
||||
return nil, errNoEngine
|
||||
}
|
||||
|
||||
// ImageInspectWithRaw returns the image information and it's raw representation
|
||||
func (client *NopClient) ImageInspectWithRaw(ctx context.Context, image string, getSize bool) (types.ImageInspect, []byte, error) {
|
||||
return types.ImageInspect{}, nil, errNoEngine
|
||||
}
|
||||
|
||||
// ImageList returns a list of images in the docker host
|
||||
func (client *NopClient) ImageList(ctx context.Context, options types.ImageListOptions) ([]types.Image, error) {
|
||||
return nil, errNoEngine
|
||||
}
|
||||
|
||||
// ImageLoad loads an image in the docker host from the client host
|
||||
func (client *NopClient) ImageLoad(ctx context.Context, input interface{}) (types.ImageLoadResponse, error) {
|
||||
return types.ImageLoadResponse{}, errNoEngine
|
||||
}
|
||||
|
||||
// ImagePull requests the docker host to pull an image from a remote registry
|
||||
func (client *NopClient) ImagePull(ctx context.Context, ref string, options types.ImagePullOptions) (io.ReadCloser, error) {
|
||||
return nil, errNoEngine
|
||||
}
|
||||
|
||||
// ImagePush requests the docker host to push an image to a remote registry
|
||||
func (client *NopClient) ImagePush(ctx context.Context, ref string, options types.ImagePushOptions) (io.ReadCloser, error) {
|
||||
return nil, errNoEngine
|
||||
}
|
||||
|
||||
// ImageRemove removes an image from the docker host
|
||||
func (client *NopClient) ImageRemove(ctx context.Context, image string, options types.ImageRemoveOptions) ([]types.ImageDelete, error) {
|
||||
return nil, errNoEngine
|
||||
}
|
||||
|
||||
// ImageSearch makes the docker host to search by a term in a remote registry
|
||||
func (client *NopClient) ImageSearch(ctx context.Context, term string, options types.ImageSearchOptions) ([]registry.SearchResult, error) {
|
||||
return nil, errNoEngine
|
||||
}
|
||||
|
||||
// ImageSave retrieves one or more images from the docker host as an io.ReadCloser
|
||||
func (client *NopClient) ImageSave(ctx context.Context, images []string) (io.ReadCloser, error) {
|
||||
return nil, errNoEngine
|
||||
}
|
||||
|
||||
// ImageTag tags an image in the docker host
|
||||
func (client *NopClient) ImageTag(ctx context.Context, image, ref string, options types.ImageTagOptions) error {
|
||||
return errNoEngine
|
||||
}
|
||||
|
||||
// Info returns information about the docker server
|
||||
func (client *NopClient) Info(ctx context.Context) (types.Info, error) {
|
||||
return types.Info{}, errNoEngine
|
||||
}
|
||||
|
||||
// NetworkConnect connects a container to an existent network in the docker host
|
||||
func (client *NopClient) NetworkConnect(ctx context.Context, networkID, container string, config *network.EndpointSettings) error {
|
||||
return errNoEngine
|
||||
}
|
||||
|
||||
// NetworkCreate creates a new network in the docker host
|
||||
func (client *NopClient) NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) {
|
||||
return types.NetworkCreateResponse{}, errNoEngine
|
||||
}
|
||||
|
||||
// NetworkDisconnect disconnects a container from an existent network in the docker host
|
||||
func (client *NopClient) NetworkDisconnect(ctx context.Context, networkID, container string, force bool) error {
|
||||
return errNoEngine
|
||||
}
|
||||
|
||||
// NetworkInspect returns the information for a specific network configured in the docker host
|
||||
func (client *NopClient) NetworkInspect(ctx context.Context, networkID string) (types.NetworkResource, error) {
|
||||
return types.NetworkResource{}, errNoEngine
|
||||
}
|
||||
|
||||
// NetworkInspectWithRaw returns the information for a specific network configured in the docker host and it's raw representation.
|
||||
func (cli *NopClient) NetworkInspectWithRaw(ctx context.Context, networkID string) (types.NetworkResource, []byte, error) {
|
||||
return types.NetworkResource{}, []byte{}, errNoEngine
|
||||
}
|
||||
|
||||
// NetworkList returns the list of networks configured in the docker host
|
||||
func (client *NopClient) NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) {
|
||||
return nil, errNoEngine
|
||||
}
|
||||
|
||||
// NetworkRemove removes an existent network from the docker host
|
||||
func (client *NopClient) NetworkRemove(ctx context.Context, networkID string) error {
|
||||
return errNoEngine
|
||||
}
|
||||
|
||||
// RegistryLogin authenticates the docker server with a given docker registry
|
||||
func (client *NopClient) RegistryLogin(ctx context.Context, auth types.AuthConfig) (types.AuthResponse, error) {
|
||||
return types.AuthResponse{}, errNoEngine
|
||||
}
|
||||
|
||||
// ServerVersion returns information of the docker client and server host
|
||||
func (client *NopClient) ServerVersion(ctx context.Context) (types.Version, error) {
|
||||
return types.Version{}, errNoEngine
|
||||
}
|
||||
|
||||
// UpdateClientVersion updates the version string associated with this instance of the Client
|
||||
func (client *NopClient) UpdateClientVersion(v string) {
|
||||
}
|
||||
|
||||
// VolumeCreate creates a volume in the docker host
|
||||
func (client *NopClient) VolumeCreate(ctx context.Context, options types.VolumeCreateRequest) (types.Volume, error) {
|
||||
return types.Volume{}, errNoEngine
|
||||
}
|
||||
|
||||
// VolumeInspect returns the information about a specific volume in the docker host
|
||||
func (client *NopClient) VolumeInspect(ctx context.Context, volumeID string) (types.Volume, error) {
|
||||
return types.Volume{}, errNoEngine
|
||||
}
|
||||
|
||||
// VolumeInspectWithRaw returns the information about a specific volume in the docker host
|
||||
func (client *NopClient) VolumeInspectWithRaw(ctx context.Context, volumeID string) (types.Volume, []byte, error) {
|
||||
return types.Volume{}, []byte{}, errNoEngine
|
||||
}
|
||||
|
||||
// VolumeList returns the volumes configured in the docker host
|
||||
func (client *NopClient) VolumeList(ctx context.Context, filter filters.Args) (types.VolumesListResponse, error) {
|
||||
return types.VolumesListResponse{}, errNoEngine
|
||||
}
|
||||
|
||||
// VolumeRemove removes a volume from the docker host
|
||||
func (client *NopClient) VolumeRemove(ctx context.Context, volumeID string) error {
|
||||
return errNoEngine
|
||||
}
|
||||
|
||||
// CheckpointCreate creates a checkpoint from the given container with the given name
|
||||
func (client *NopClient) CheckpointCreate(ctx context.Context, container string, options types.CheckpointCreateOptions) error {
|
||||
return errNoEngine
|
||||
}
|
||||
|
||||
// CheckpointDelete deletes the checkpoint with the given name from the given container
|
||||
func (cli *NopClient) CheckpointDelete(ctx context.Context, containerID string, checkpointID string) error {
|
||||
return errNoEngine
|
||||
}
|
||||
|
||||
// CheckpointList returns the volumes configured in the docker host.
|
||||
func (cli *NopClient) CheckpointList(ctx context.Context, container string) ([]types.Checkpoint, error) {
|
||||
return []types.Checkpoint{}, errNoEngine
|
||||
}
|
||||
|
||||
func (cli *NopClient) FipAllocate(ctx context.Context, count string) ([]string, error) {
|
||||
return []string{}, errNoEngine
|
||||
}
|
||||
|
||||
func (cli *NopClient) FipRelease(ctx context.Context, ip string) error {
|
||||
return errNoEngine
|
||||
}
|
||||
|
||||
func (cli *NopClient) FipAssociate(ctx context.Context, ip, container string) error {
|
||||
return errNoEngine
|
||||
}
|
||||
|
||||
func (cli *NopClient) FipDisassociate(ctx context.Context, container string) (string, error) {
|
||||
return "", errNoEngine
|
||||
}
|
||||
|
||||
func (cli *NopClient) FipList(ctx context.Context, options types.NetworkListOptions) ([]map[string]string, error) {
|
||||
return []map[string]string{}, errNoEngine
|
||||
}
|
||||
|
||||
// SnapshotList returns the snapshots configured in the docker host.
|
||||
func (cli *NopClient) SnapshotList(ctx context.Context, filter filters.Args) (types.SnapshotsListResponse, error) {
|
||||
return types.SnapshotsListResponse{}, errNoEngine
|
||||
}
|
||||
|
||||
// SnapshotInspect returns the information about a specific snapshot in the docker host.
|
||||
func (cli *NopClient) SnapshotInspect(ctx context.Context, snapshotID string) (types.Snapshot, error) {
|
||||
return types.Snapshot{}, errNoEngine
|
||||
}
|
||||
|
||||
// SnapshotCreate creates a snapshot in the docker host.
|
||||
func (cli *NopClient) SnapshotCreate(ctx context.Context, options types.SnapshotCreateRequest) (types.Snapshot, error) {
|
||||
return types.Snapshot{}, errNoEngine
|
||||
}
|
||||
|
||||
// SnapshotRemove removes a snapshot from the docker host.
|
||||
func (cli *NopClient) SnapshotRemove(ctx context.Context, snapshotID string) error {
|
||||
return errNoEngine
|
||||
}
|
||||
136
vendor/github.com/hyperhq/libcompose/utils/util.go
generated
vendored
Normal file
136
vendor/github.com/hyperhq/libcompose/utils/util.go
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sync"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
yaml "github.com/cloudfoundry-incubator/candiedyaml"
|
||||
)
|
||||
|
||||
// InParallel holds a pool and a waitgroup to execute tasks in parallel and to be able
|
||||
// to wait for completion of all tasks.
|
||||
type InParallel struct {
|
||||
wg sync.WaitGroup
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
// Add runs the specified task in parallel and adds it to the waitGroup.
|
||||
func (i *InParallel) Add(task func() error) {
|
||||
i.wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer i.wg.Done()
|
||||
err := task()
|
||||
if err != nil {
|
||||
i.pool.Put(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Wait waits for all tasks to complete and returns the latest error encountered if any.
|
||||
func (i *InParallel) Wait() error {
|
||||
i.wg.Wait()
|
||||
obj := i.pool.Get()
|
||||
if err, ok := obj.(error); ok {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConvertByJSON converts a struct (src) to another one (target) using json marshalling/unmarshalling.
|
||||
// If the structure are not compatible, this will throw an error as the unmarshalling will fail.
|
||||
func ConvertByJSON(src, target interface{}) error {
|
||||
newBytes, err := json.Marshal(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(newBytes, target)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to unmarshall: %v\n%s", err, string(newBytes))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Convert converts a struct (src) to another one (target) using yaml marshalling/unmarshalling.
|
||||
// If the structure are not compatible, this will throw an error as the unmarshalling will fail.
|
||||
func Convert(src, target interface{}) error {
|
||||
newBytes, err := yaml.Marshal(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(newBytes, target)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to unmarshall: %v\n%s", err, string(newBytes))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// CopySlice creates an exact copy of the provided string slice
|
||||
func CopySlice(s []string) []string {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
r := make([]string, len(s))
|
||||
copy(r, s)
|
||||
return r
|
||||
}
|
||||
|
||||
// CopyMap creates an exact copy of the provided string-to-string map
|
||||
func CopyMap(m map[string]string) map[string]string {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
r := map[string]string{}
|
||||
for k, v := range m {
|
||||
r[k] = v
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// FilterStringSet accepts a string set `s` (in the form of `map[string]bool`) and a filtering function `f`
|
||||
// and returns a string set containing only the strings `x` for which `f(x) == true`
|
||||
func FilterStringSet(s map[string]bool, f func(x string) bool) map[string]bool {
|
||||
result := map[string]bool{}
|
||||
for k := range s {
|
||||
if f(k) {
|
||||
result[k] = true
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// FilterString returns a json representation of the specified map
|
||||
// that is used as filter for docker.
|
||||
func FilterString(data map[string][]string) string {
|
||||
// I can't imagine this would ever fail
|
||||
bytes, _ := json.Marshal(data)
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
// Contains checks if the specified string (key) is present in the specified collection.
|
||||
func Contains(collection []string, key string) bool {
|
||||
for _, value := range collection {
|
||||
if value == key {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Merge performs a union of two string slices: the result is an unordered slice
|
||||
// that includes every item from either argument exactly once
|
||||
func Merge(coll1, coll2 []string) []string {
|
||||
m := map[string]struct{}{}
|
||||
for _, v := range append(coll1, coll2...) {
|
||||
m[v] = struct{}{}
|
||||
}
|
||||
r := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
r = append(r, k)
|
||||
}
|
||||
return r
|
||||
}
|
||||
85
vendor/github.com/hyperhq/libcompose/utils/util_inparallel_test.go
generated
vendored
Normal file
85
vendor/github.com/hyperhq/libcompose/utils/util_inparallel_test.go
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
// +build !race
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type safeMap struct {
|
||||
mu sync.RWMutex
|
||||
m map[int]bool
|
||||
}
|
||||
|
||||
func (s *safeMap) Add(index int, ok bool) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.m[index] = ok
|
||||
}
|
||||
|
||||
func (s *safeMap) Read() map[int]bool {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.m
|
||||
}
|
||||
|
||||
func TestInParallel(t *testing.T) {
|
||||
size := 5
|
||||
booleanMap := safeMap{
|
||||
m: make(map[int]bool, size+1),
|
||||
}
|
||||
tasks := InParallel{}
|
||||
for i := 0; i < size; i++ {
|
||||
task := func(index int) func() error {
|
||||
return func() error {
|
||||
booleanMap.Add(index, true)
|
||||
return nil
|
||||
}
|
||||
}(i)
|
||||
tasks.Add(task)
|
||||
}
|
||||
err := tasks.Wait()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Make sure every value is true
|
||||
for _, value := range booleanMap.Read() {
|
||||
if !value {
|
||||
t.Fatalf("booleanMap expected to contain only true values, got at least one false")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInParallelError(t *testing.T) {
|
||||
size := 5
|
||||
booleanMap := safeMap{
|
||||
m: make(map[int]bool, size+1),
|
||||
}
|
||||
tasks := InParallel{}
|
||||
for i := 0; i < size; i++ {
|
||||
task := func(index int) func() error {
|
||||
return func() error {
|
||||
booleanMap.Add(index, false)
|
||||
t.Log("index", index)
|
||||
if index%2 == 0 {
|
||||
t.Log("return an error for", index)
|
||||
return fmt.Errorf("Error with %v", index)
|
||||
}
|
||||
booleanMap.Add(index, true)
|
||||
return nil
|
||||
}
|
||||
}(i)
|
||||
tasks.Add(task)
|
||||
}
|
||||
err := tasks.Wait()
|
||||
if err == nil {
|
||||
t.Fatalf("Expected an error on Wait, got nothing.")
|
||||
}
|
||||
for key, value := range booleanMap.Read() {
|
||||
if key%2 != 0 && !value {
|
||||
t.Fatalf("booleanMap expected to contain true values on odd number, got %v", booleanMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
213
vendor/github.com/hyperhq/libcompose/utils/util_test.go
generated
vendored
Normal file
213
vendor/github.com/hyperhq/libcompose/utils/util_test.go
generated
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type jsonfrom struct {
|
||||
Element1 string `json:"element2"`
|
||||
Element2 int `json:"element1"`
|
||||
}
|
||||
type jsonto struct {
|
||||
Elt1 int `json:"element1"`
|
||||
Elt2 string `json:"element2"`
|
||||
Elt3 int
|
||||
}
|
||||
|
||||
func TestConvertByJSON(t *testing.T) {
|
||||
valids := []struct {
|
||||
src jsonfrom
|
||||
expected jsonto
|
||||
}{
|
||||
{
|
||||
jsonfrom{Element2: 1},
|
||||
jsonto{1, "", 0},
|
||||
},
|
||||
{
|
||||
jsonfrom{},
|
||||
jsonto{0, "", 0},
|
||||
},
|
||||
{
|
||||
jsonfrom{"element1", 2},
|
||||
jsonto{2, "element1", 0},
|
||||
},
|
||||
}
|
||||
for _, valid := range valids {
|
||||
var target jsonto
|
||||
err := ConvertByJSON(valid.src, &target)
|
||||
if err != nil || target.Elt1 != valid.expected.Elt1 || target.Elt2 != valid.expected.Elt2 || target.Elt3 != 0 {
|
||||
t.Fatalf("Expected %v from %v got %v, %v", valid.expected, valid.src, target, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertByJSONInvalid(t *testing.T) {
|
||||
invalids := []interface{}{
|
||||
// Incompatible struct
|
||||
struct {
|
||||
Element1 int `json:"element2"`
|
||||
Element2 string `json:"element1"`
|
||||
}{1, "element1"},
|
||||
// Not marshable struct
|
||||
struct {
|
||||
Element1 func(int) int
|
||||
}{
|
||||
func(i int) int { return 0 },
|
||||
},
|
||||
}
|
||||
for _, invalid := range invalids {
|
||||
var target jsonto
|
||||
if err := ConvertByJSON(invalid, &target); err == nil {
|
||||
t.Fatalf("Expected an error converting %v to %v, got nothing", invalid, target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type yamlfrom struct {
|
||||
Element1 string `yaml:"element2"`
|
||||
Element2 int `yaml:"element1"`
|
||||
}
|
||||
type yamlto struct {
|
||||
Elt1 int `yaml:"element1"`
|
||||
Elt2 string `yaml:"element2"`
|
||||
Elt3 int
|
||||
}
|
||||
|
||||
func TestConvert(t *testing.T) {
|
||||
valids := []struct {
|
||||
src yamlfrom
|
||||
expected yamlto
|
||||
}{
|
||||
{
|
||||
yamlfrom{Element2: 1},
|
||||
yamlto{1, "", 0},
|
||||
},
|
||||
{
|
||||
yamlfrom{},
|
||||
yamlto{0, "", 0},
|
||||
},
|
||||
{
|
||||
yamlfrom{"element1", 2},
|
||||
yamlto{2, "element1", 0},
|
||||
},
|
||||
}
|
||||
for _, valid := range valids {
|
||||
var target yamlto
|
||||
err := Convert(valid.src, &target)
|
||||
if err != nil || target.Elt1 != valid.expected.Elt1 || target.Elt2 != valid.expected.Elt2 || target.Elt3 != 0 {
|
||||
t.Fatalf("Expected %v from %v got %v, %v", valid.expected, valid.src, target, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertInvalid(t *testing.T) {
|
||||
invalids := []interface{}{
|
||||
// Incompatible struct
|
||||
struct {
|
||||
Element1 int `yaml:"element2"`
|
||||
Element2 string `yaml:"element1"`
|
||||
}{1, "element1"},
|
||||
// Not marshable struct
|
||||
// This one panics :-|
|
||||
// struct {
|
||||
// Element1 func(int) int
|
||||
// }{
|
||||
// func(i int) int { return 0 },
|
||||
// },
|
||||
}
|
||||
for _, invalid := range invalids {
|
||||
var target yamlto
|
||||
if err := Convert(invalid, &target); err == nil {
|
||||
t.Fatalf("Expected an error converting %v to %v, got nothing", invalid, target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterStringSet(t *testing.T) {
|
||||
s := map[string]bool{"abcd": true, "b": true, "cde": true, "d": true, "ef": true}
|
||||
expected := map[string]bool{"abcd": true, "cde": true}
|
||||
result := FilterStringSet(s, func(x string) bool { return len(x) > 2 })
|
||||
assert.Equal(t, expected, result)
|
||||
}
|
||||
|
||||
func TestFilterString(t *testing.T) {
|
||||
datas := []struct {
|
||||
value map[string][]string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
map[string][]string{},
|
||||
"{}",
|
||||
},
|
||||
{
|
||||
map[string][]string{
|
||||
"key": {},
|
||||
},
|
||||
`{"key":[]}`,
|
||||
},
|
||||
{
|
||||
map[string][]string{
|
||||
"key": {"value1", "value2"},
|
||||
},
|
||||
`{"key":["value1","value2"]}`,
|
||||
},
|
||||
{
|
||||
map[string][]string{
|
||||
"key1": {"value1", "value2"},
|
||||
"key2": {"value3", "value4"},
|
||||
},
|
||||
`{"key1":["value1","value2"],"key2":["value3","value4"]}`,
|
||||
},
|
||||
}
|
||||
for _, data := range datas {
|
||||
actual := FilterString(data.value)
|
||||
if actual != data.expected {
|
||||
t.Fatalf("Expected '%v' for %v, got '%v'", data.expected, data.value, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestContains(t *testing.T) {
|
||||
cases := []struct {
|
||||
collection []string
|
||||
key string
|
||||
contains bool
|
||||
}{
|
||||
{
|
||||
[]string{}, "", false,
|
||||
},
|
||||
{
|
||||
[]string{""}, "", true,
|
||||
},
|
||||
{
|
||||
[]string{"value1", "value2"}, "value3", false,
|
||||
},
|
||||
{
|
||||
[]string{"value1", "value2"}, "value1", true,
|
||||
},
|
||||
{
|
||||
[]string{"value1", "value2"}, "value2", true,
|
||||
},
|
||||
}
|
||||
for _, element := range cases {
|
||||
actual := Contains(element.collection, element.key)
|
||||
if actual != element.contains {
|
||||
t.Fatalf("Expected contains to be %v for %v in %v, but was %v", element.contains, element.key, element.collection, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMerge(t *testing.T) {
|
||||
a := []string{"a", "b", "c"}
|
||||
b := []string{"b", "c", "d", "e"}
|
||||
expected := []string{"a", "b", "c", "d", "e"}
|
||||
r := Merge(a, b)
|
||||
assert.Equal(t, len(expected), len(r))
|
||||
for _, v := range expected {
|
||||
assert.True(t, Contains(r, v))
|
||||
}
|
||||
assert.Equal(t, "a:b", fmt.Sprint("a", ":", "b"))
|
||||
}
|
||||
288
vendor/github.com/hyperhq/libcompose/yaml/types_yaml.go
generated
vendored
Normal file
288
vendor/github.com/hyperhq/libcompose/yaml/types_yaml.go
generated
vendored
Normal file
@@ -0,0 +1,288 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/engine-api/types/strslice"
|
||||
"github.com/flynn/go-shlex"
|
||||
)
|
||||
|
||||
// Stringorslice represents a string or an array of strings.
|
||||
// Using engine-api Strslice and augment it with YAML marshalling stuff.
|
||||
type Stringorslice strslice.StrSlice
|
||||
|
||||
// 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
|
||||
case string:
|
||||
*s = []string{value}
|
||||
default:
|
||||
return fmt.Errorf("Failed to unmarshal Stringorslice: %#v", value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ulimits represents a list of Ulimit.
|
||||
// It is, however, represented in yaml as keys (and thus map in Go)
|
||||
type Ulimits struct {
|
||||
Elements []Ulimit
|
||||
}
|
||||
|
||||
// MarshalYAML implements the Marshaller interface.
|
||||
func (u Ulimits) MarshalYAML() (tag string, value interface{}, err error) {
|
||||
ulimitMap := make(map[string]Ulimit)
|
||||
for _, ulimit := range u.Elements {
|
||||
ulimitMap[ulimit.Name] = ulimit
|
||||
}
|
||||
return "", ulimitMap, nil
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the Unmarshaller interface.
|
||||
func (u *Ulimits) UnmarshalYAML(tag string, value interface{}) error {
|
||||
ulimits := make(map[string]Ulimit)
|
||||
yamlUlimits := reflect.ValueOf(value)
|
||||
switch yamlUlimits.Kind() {
|
||||
case reflect.Map:
|
||||
for _, key := range yamlUlimits.MapKeys() {
|
||||
var name string
|
||||
var soft, hard int64
|
||||
mapValue := yamlUlimits.MapIndex(key).Elem()
|
||||
name = key.Elem().String()
|
||||
switch mapValue.Kind() {
|
||||
case reflect.Int64:
|
||||
soft = mapValue.Int()
|
||||
hard = mapValue.Int()
|
||||
case reflect.Map:
|
||||
if len(mapValue.MapKeys()) != 2 {
|
||||
return fmt.Errorf("Failed to unmarshal Ulimit: %#v", mapValue)
|
||||
}
|
||||
for _, subKey := range mapValue.MapKeys() {
|
||||
subValue := mapValue.MapIndex(subKey).Elem()
|
||||
switch subKey.Elem().String() {
|
||||
case "soft":
|
||||
soft = subValue.Int()
|
||||
case "hard":
|
||||
hard = subValue.Int()
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("Failed to unmarshal Ulimit: %#v, %v", mapValue, mapValue.Kind())
|
||||
}
|
||||
ulimits[name] = Ulimit{
|
||||
Name: name,
|
||||
ulimitValues: ulimitValues{
|
||||
Soft: soft,
|
||||
Hard: hard,
|
||||
},
|
||||
}
|
||||
}
|
||||
keys := make([]string, 0, len(ulimits))
|
||||
for key := range ulimits {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, key := range keys {
|
||||
u.Elements = append(u.Elements, ulimits[key])
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("Failed to unmarshal Ulimit: %#v", value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ulimit represents ulimit information.
|
||||
type Ulimit struct {
|
||||
ulimitValues
|
||||
Name string
|
||||
}
|
||||
|
||||
type ulimitValues struct {
|
||||
Soft int64 `yaml:"soft"`
|
||||
Hard int64 `yaml:"hard"`
|
||||
}
|
||||
|
||||
// MarshalYAML implements the Marshaller interface.
|
||||
func (u Ulimit) MarshalYAML() (tag string, value interface{}, err error) {
|
||||
if u.Soft == u.Hard {
|
||||
return "", u.Soft, nil
|
||||
}
|
||||
return "", u.ulimitValues, err
|
||||
}
|
||||
|
||||
// NewUlimit creates a Ulimit based on the specified parts.
|
||||
func NewUlimit(name string, soft int64, hard int64) Ulimit {
|
||||
return Ulimit{
|
||||
Name: name,
|
||||
ulimitValues: ulimitValues{
|
||||
Soft: soft,
|
||||
Hard: hard,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Command represents a docker command, can be a string or an array of strings.
|
||||
type Command strslice.StrSlice
|
||||
|
||||
// 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
|
||||
case string:
|
||||
parts, err := shlex.Split(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*s = parts
|
||||
default:
|
||||
return fmt.Errorf("Failed to unmarshal Command: %#v", value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SliceorMap represents a slice or a map of strings.
|
||||
type SliceorMap map[string]string
|
||||
|
||||
// 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
|
||||
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
|
||||
default:
|
||||
return fmt.Errorf("Failed to unmarshal SliceorMap: %#v", value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MaporEqualSlice represents a slice of strings that gets unmarshal from a
|
||||
// YAML map into 'key=value' string.
|
||||
type MaporEqualSlice []string
|
||||
|
||||
// UnmarshalYAML implements the Unmarshaller interface.
|
||||
func (s *MaporEqualSlice) UnmarshalYAML(tag string, value interface{}) error {
|
||||
parts, err := unmarshalToStringOrSepMapParts(value, "=")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*s = parts
|
||||
return nil
|
||||
}
|
||||
|
||||
// MaporColonSlice represents a slice of strings that gets unmarshal from a
|
||||
// YAML map into 'key:value' string.
|
||||
type MaporColonSlice []string
|
||||
|
||||
// UnmarshalYAML implements the Unmarshaller interface.
|
||||
func (s *MaporColonSlice) UnmarshalYAML(tag string, value interface{}) error {
|
||||
parts, err := unmarshalToStringOrSepMapParts(value, ":")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*s = parts
|
||||
return nil
|
||||
}
|
||||
|
||||
// MaporSpaceSlice represents a slice of strings that gets unmarshal from a
|
||||
// YAML map into 'key value' string.
|
||||
type MaporSpaceSlice []string
|
||||
|
||||
// UnmarshalYAML implements the Unmarshaller interface.
|
||||
func (s *MaporSpaceSlice) UnmarshalYAML(tag string, value interface{}) error {
|
||||
parts, err := unmarshalToStringOrSepMapParts(value, " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*s = parts
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalToStringOrSepMapParts(value interface{}, key string) ([]string, error) {
|
||||
switch value := value.(type) {
|
||||
case []interface{}:
|
||||
return toStrings(value)
|
||||
case map[interface{}]interface{}:
|
||||
return toSepMapParts(value, key)
|
||||
default:
|
||||
return nil, fmt.Errorf("Failed to unmarshal Map or Slice: %#v", value)
|
||||
}
|
||||
}
|
||||
|
||||
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 if sv, ok := v.(int64); ok {
|
||||
parts = append(parts, sk+sep+strconv.FormatInt(sv, 10))
|
||||
} 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
278
vendor/github.com/hyperhq/libcompose/yaml/types_yaml_test.go
generated
vendored
Normal file
278
vendor/github.com/hyperhq/libcompose/yaml/types_yaml_test.go
generated
vendored
Normal file
@@ -0,0 +1,278 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
yaml "github.com/cloudfoundry-incubator/candiedyaml"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type StructStringorslice struct {
|
||||
Foo Stringorslice
|
||||
}
|
||||
|
||||
func TestStringorsliceYaml(t *testing.T) {
|
||||
str := `{foo: [bar, baz]}`
|
||||
|
||||
s := StructStringorslice{}
|
||||
yaml.Unmarshal([]byte(str), &s)
|
||||
|
||||
assert.Equal(t, Stringorslice{"bar", "baz"}, s.Foo)
|
||||
|
||||
d, err := yaml.Marshal(&s)
|
||||
assert.Nil(t, err)
|
||||
|
||||
s2 := StructStringorslice{}
|
||||
yaml.Unmarshal(d, &s2)
|
||||
|
||||
assert.Equal(t, Stringorslice{"bar", "baz"}, s2.Foo)
|
||||
}
|
||||
|
||||
type StructSliceorMap struct {
|
||||
Foos SliceorMap `yaml:"foos,omitempty"`
|
||||
Bars []string `yaml:"bars"`
|
||||
}
|
||||
|
||||
type StructCommand struct {
|
||||
Entrypoint Command `yaml:"entrypoint,flow,omitempty"`
|
||||
Command Command `yaml:"command,flow,omitempty"`
|
||||
}
|
||||
|
||||
func TestSliceOrMapYaml(t *testing.T) {
|
||||
str := `{foos: [bar=baz, far=faz]}`
|
||||
|
||||
s := StructSliceorMap{}
|
||||
yaml.Unmarshal([]byte(str), &s)
|
||||
|
||||
assert.Equal(t, SliceorMap{"bar": "baz", "far": "faz"}, s.Foos)
|
||||
|
||||
d, err := yaml.Marshal(&s)
|
||||
assert.Nil(t, err)
|
||||
|
||||
s2 := StructSliceorMap{}
|
||||
yaml.Unmarshal(d, &s2)
|
||||
|
||||
assert.Equal(t, SliceorMap{"bar": "baz", "far": "faz"}, s2.Foos)
|
||||
}
|
||||
|
||||
var sampleStructSliceorMap = `
|
||||
foos:
|
||||
io.rancher.os.bar: baz
|
||||
io.rancher.os.far: true
|
||||
bars: []
|
||||
`
|
||||
|
||||
func TestUnmarshalSliceOrMap(t *testing.T) {
|
||||
s := StructSliceorMap{}
|
||||
err := yaml.Unmarshal([]byte(sampleStructSliceorMap), &s)
|
||||
assert.Equal(t, fmt.Errorf("Cannot unmarshal 'true' of type bool into a string value"), err)
|
||||
}
|
||||
|
||||
func TestStr2SliceOrMapPtrMap(t *testing.T) {
|
||||
s := map[string]*StructSliceorMap{"udav": {
|
||||
Foos: SliceorMap{"io.rancher.os.bar": "baz", "io.rancher.os.far": "true"},
|
||||
Bars: []string{},
|
||||
}}
|
||||
d, err := yaml.Marshal(&s)
|
||||
assert.Nil(t, err)
|
||||
|
||||
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))
|
||||
assert.True(t, contains(s.Foo, "bar=baz"))
|
||||
assert.True(t, contains(s.Foo, "far=faz"))
|
||||
|
||||
d, err := yaml.Marshal(&s)
|
||||
assert.Nil(t, err)
|
||||
|
||||
s2 := StructMaporslice{}
|
||||
yaml.Unmarshal(d, &s2)
|
||||
|
||||
assert.Equal(t, 2, len(s2.Foo))
|
||||
assert.True(t, contains(s2.Foo, "bar=baz"))
|
||||
assert.True(t, contains(s2.Foo, "far=faz"))
|
||||
}
|
||||
|
||||
var sampleStructCommand = `command: bash`
|
||||
|
||||
func TestUnmarshalCommand(t *testing.T) {
|
||||
s := &StructCommand{}
|
||||
err := yaml.Unmarshal([]byte(sampleStructCommand), s)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, Command{"bash"}, s.Command)
|
||||
assert.Nil(t, s.Entrypoint)
|
||||
bytes, err := yaml.Marshal(s)
|
||||
assert.Nil(t, err)
|
||||
|
||||
s2 := &StructCommand{}
|
||||
err = yaml.Unmarshal(bytes, s2)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, Command{"bash"}, s2.Command)
|
||||
assert.Nil(t, s2.Entrypoint)
|
||||
}
|
||||
|
||||
var sampleEmptyCommand = `{}`
|
||||
|
||||
func TestUnmarshalEmptyCommand(t *testing.T) {
|
||||
s := &StructCommand{}
|
||||
err := yaml.Unmarshal([]byte(sampleEmptyCommand), s)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, s.Command)
|
||||
|
||||
bytes, err := yaml.Marshal(s)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "{}", strings.TrimSpace(string(bytes)))
|
||||
|
||||
s2 := &StructCommand{}
|
||||
err = yaml.Unmarshal(bytes, s2)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, s2.Command)
|
||||
}
|
||||
|
||||
func TestMarshalUlimit(t *testing.T) {
|
||||
ulimits := []struct {
|
||||
ulimits *Ulimits
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
ulimits: &Ulimits{
|
||||
Elements: []Ulimit{
|
||||
{
|
||||
ulimitValues: ulimitValues{
|
||||
Soft: 65535,
|
||||
Hard: 65535,
|
||||
},
|
||||
Name: "nproc",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: `nproc: 65535
|
||||
`,
|
||||
},
|
||||
{
|
||||
ulimits: &Ulimits{
|
||||
Elements: []Ulimit{
|
||||
{
|
||||
Name: "nofile",
|
||||
ulimitValues: ulimitValues{
|
||||
Soft: 20000,
|
||||
Hard: 40000,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: `nofile:
|
||||
soft: 20000
|
||||
hard: 40000
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, ulimit := range ulimits {
|
||||
|
||||
bytes, err := yaml.Marshal(ulimit.ulimits)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, ulimit.expected, string(bytes), "should be equal")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalUlimits(t *testing.T) {
|
||||
ulimits := []struct {
|
||||
yaml string
|
||||
expected *Ulimits
|
||||
}{
|
||||
{
|
||||
yaml: "nproc: 65535",
|
||||
expected: &Ulimits{
|
||||
Elements: []Ulimit{
|
||||
{
|
||||
Name: "nproc",
|
||||
ulimitValues: ulimitValues{
|
||||
Soft: 65535,
|
||||
Hard: 65535,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
yaml: `nofile:
|
||||
soft: 20000
|
||||
hard: 40000`,
|
||||
expected: &Ulimits{
|
||||
Elements: []Ulimit{
|
||||
{
|
||||
Name: "nofile",
|
||||
ulimitValues: ulimitValues{
|
||||
Soft: 20000,
|
||||
Hard: 40000,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
yaml: `nproc: 65535
|
||||
nofile:
|
||||
soft: 20000
|
||||
hard: 40000`,
|
||||
expected: &Ulimits{
|
||||
Elements: []Ulimit{
|
||||
{
|
||||
Name: "nofile",
|
||||
ulimitValues: ulimitValues{
|
||||
Soft: 20000,
|
||||
Hard: 40000,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "nproc",
|
||||
ulimitValues: ulimitValues{
|
||||
Soft: 65535,
|
||||
Hard: 65535,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, ulimit := range ulimits {
|
||||
actual := &Ulimits{}
|
||||
err := yaml.Unmarshal([]byte(ulimit.yaml), actual)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, ulimit.expected, actual, "should be equal")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user