Update traefik dependencies (docker/docker and related) (#1823)

Update traefik dependencies (docker/docker and related)

- Update dependencies
- Fix compilation problems
- Remove vdemeester/docker-events (in docker api now)
- Remove `integration/vendor`
- Use `testImport`
- update some deps.
- regenerate the lock from scratch (after a `glide cc`)
This commit is contained in:
Vincent Demeester 2017-07-06 16:28:13 +02:00 committed by Ludovic Fernandez
parent 7d178f49b4
commit b7daa2f3a4
1301 changed files with 21476 additions and 150099 deletions

191
vendor/github.com/docker/libcompose/LICENSE generated vendored Normal file
View file

@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2015 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

44
vendor/github.com/docker/libcompose/config/convert.go generated vendored Normal file
View file

@ -0,0 +1,44 @@
package config
import (
"github.com/docker/libcompose/utils"
"github.com/docker/libcompose/yaml"
)
// ConvertServices converts a set of v1 service configs to v2 service configs
func ConvertServices(v1Services map[string]*ServiceConfigV1) (map[string]*ServiceConfig, error) {
v2Services := make(map[string]*ServiceConfig)
replacementFields := make(map[string]*ServiceConfig)
for name, service := range v1Services {
replacementFields[name] = &ServiceConfig{
Build: yaml.Build{
Context: service.Build,
Dockerfile: service.Dockerfile,
},
Logging: Log{
Driver: service.LogDriver,
Options: service.LogOpt,
},
NetworkMode: service.Net,
}
v1Services[name].Build = ""
v1Services[name].Dockerfile = ""
v1Services[name].LogDriver = ""
v1Services[name].LogOpt = nil
v1Services[name].Net = ""
}
if err := utils.Convert(v1Services, &v2Services); err != nil {
return nil, err
}
for name := range v2Services {
v2Services[name].Build = replacementFields[name].Build
v2Services[name].Logging = replacementFields[name].Logging
v2Services[name].NetworkMode = replacementFields[name].NetworkMode
}
return v2Services, nil
}

99
vendor/github.com/docker/libcompose/config/hash.go generated vendored Normal file
View file

@ -0,0 +1,99 @@
package config
import (
"crypto/sha1"
"encoding/hex"
"fmt"
"io"
"reflect"
"sort"
"github.com/docker/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))
}
case *yaml.Networks:
io.WriteString(hash, fmt.Sprintf("%s, ", s.HashString()))
case *yaml.Volumes:
io.WriteString(hash, fmt.Sprintf("%s, ", s.HashString()))
default:
io.WriteString(hash, fmt.Sprintf("%v, ", serviceValue))
}
}
return hex.EncodeToString(hash.Sum(nil))
}

View file

@ -0,0 +1,157 @@
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(key 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 key \"%s\": \"%s\"", key, typedData)
}
case []interface{}:
for k, v := range typedData {
err := parseConfig(key, &v, mapping)
if err != nil {
return err
}
typedData[k] = v
}
case map[interface{}]interface{}:
for k, v := range typedData {
err := parseConfig(key, &v, mapping)
if err != nil {
return err
}
typedData[k] = v
}
}
return nil
}
// Interpolate replaces variables in a map entry
func Interpolate(key string, data *interface{}, environmentLookup EnvironmentLookup) error {
return parseConfig(key, data, func(s string) string {
values := environmentLookup.Lookup(s, 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]
})
}

253
vendor/github.com/docker/libcompose/config/merge.go generated vendored Normal file
View file

@ -0,0 +1,253 @@
package config
import (
"bufio"
"bytes"
"fmt"
"strings"
"github.com/docker/docker/pkg/urlutil"
"github.com/docker/libcompose/utils"
composeYaml "github.com/docker/libcompose/yaml"
"gopkg.in/yaml.v2"
"reflect"
)
var (
noMerge = []string{
"links",
"volumes_from",
}
defaultParseOptions = ParseOptions{
Interpolate: true,
Validate: true,
}
)
// CreateConfig unmarshals bytes to config and creates config based on version
func CreateConfig(bytes []byte) (*Config, error) {
var config Config
if err := yaml.Unmarshal(bytes, &config); err != nil {
return nil, err
}
if config.Version != "2" {
var baseRawServices RawServiceMap
if err := yaml.Unmarshal(bytes, &baseRawServices); err != nil {
return nil, err
}
config.Services = baseRawServices
}
if config.Volumes == nil {
config.Volumes = make(map[string]interface{})
}
if config.Networks == nil {
config.Networks = make(map[string]interface{})
}
return &config, nil
}
// Merge merges a compose file into an existing set of service configs
func Merge(existingServices *ServiceConfigs, environmentLookup EnvironmentLookup, resourceLookup ResourceLookup, file string, bytes []byte, options *ParseOptions) (string, map[string]*ServiceConfig, map[string]*VolumeConfig, map[string]*NetworkConfig, error) {
if options == nil {
options = &defaultParseOptions
}
config, err := CreateConfig(bytes)
if err != nil {
return "", nil, nil, nil, err
}
baseRawServices := config.Services
for service, data := range baseRawServices {
for key, value := range data {
//check for "extends" key and check whether it is string or not
if key == "extends" && reflect.TypeOf(value).Kind() == reflect.String {
//converting string to map
extendMap := make(map[interface{}]interface{})
extendMap["service"] = value
baseRawServices[service][key] = extendMap
}
}
}
if options.Interpolate {
if err := InterpolateRawServiceMap(&baseRawServices, environmentLookup); err != nil {
return "", nil, nil, nil, err
}
for k, v := range config.Volumes {
if err := Interpolate(k, &v, environmentLookup); err != nil {
return "", nil, nil, nil, err
}
config.Volumes[k] = v
}
for k, v := range config.Networks {
if err := Interpolate(k, &v, environmentLookup); err != nil {
return "", nil, nil, nil, err
}
config.Networks[k] = v
}
}
if options.Preprocess != nil {
var err error
baseRawServices, err = options.Preprocess(baseRawServices)
if err != nil {
return "", nil, nil, nil, err
}
}
var serviceConfigs map[string]*ServiceConfig
if config.Version == "2" {
var err error
serviceConfigs, err = MergeServicesV2(existingServices, environmentLookup, resourceLookup, file, baseRawServices, options)
if err != nil {
return "", nil, nil, nil, err
}
} else {
serviceConfigsV1, err := MergeServicesV1(existingServices, environmentLookup, resourceLookup, file, baseRawServices, options)
if err != nil {
return "", nil, nil, nil, err
}
serviceConfigs, err = ConvertServices(serviceConfigsV1)
if err != nil {
return "", nil, nil, nil, err
}
}
adjustValues(serviceConfigs)
if options.Postprocess != nil {
var err error
serviceConfigs, err = options.Postprocess(serviceConfigs)
if err != nil {
return "", nil, nil, nil, err
}
}
var volumes map[string]*VolumeConfig
var networks map[string]*NetworkConfig
if err := utils.Convert(config.Volumes, &volumes); err != nil {
return "", nil, nil, nil, err
}
if err := utils.Convert(config.Networks, &networks); err != nil {
return "", nil, nil, nil, err
}
return config.Version, serviceConfigs, volumes, networks, nil
}
// InterpolateRawServiceMap replaces varialbse in raw service map struct based on environment lookup
func InterpolateRawServiceMap(baseRawServices *RawServiceMap, environmentLookup EnvironmentLookup) error {
for k, v := range *baseRawServices {
for k2, v2 := range v {
if err := Interpolate(k2, &v2, environmentLookup); err != nil {
return err
}
(*baseRawServices)[k][k2] = v2
}
}
return 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
}
var envFiles composeYaml.Stringorslice
if err := utils.Convert(serviceData["env_file"], &envFiles); err != nil {
return nil, err
}
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 composeYaml.MaporEqualSlice
if _, ok := serviceData["environment"]; ok {
if err := utils.Convert(serviceData["environment"], &vars); err != nil {
return nil, err
}
}
for i := len(envFiles) - 1; i >= 0; i-- {
envFile := envFiles[i]
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())
if len(line) > 0 && !strings.HasPrefix(line, "#") {
key := strings.SplitAfter(line, "=")[0]
found := false
for _, v := range vars {
if strings.HasPrefix(v, key) {
found = true
break
}
}
if !found {
vars = append(vars, line)
}
}
}
if scanner.Err() != nil {
return nil, scanner.Err()
}
}
serviceData["environment"] = vars
delete(serviceData, "env_file")
return serviceData, nil
}
func mergeConfig(baseService, serviceData RawService) RawService {
for k, v := range serviceData {
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)
}

198
vendor/github.com/docker/libcompose/config/merge_v1.go generated vendored Normal file
View file

@ -0,0 +1,198 @@
package config
import (
"fmt"
"path"
"github.com/Sirupsen/logrus"
"github.com/docker/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, datas RawServiceMap, options *ParseOptions) (map[string]*ServiceConfigV1, error) {
if options.Validate {
if err := validate(datas); err != nil {
return nil, err
}
}
for name, data := range datas {
data, err := parseV1(resourceLookup, environmentLookup, file, data, datas, options)
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 = mergeConfigV1(rawExistingService, data)
}
datas[name] = data
}
if options.Validate {
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, options *ParseOptions) (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, options)
} 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
}
config, err := CreateConfig(bytes)
if err != nil {
return nil, err
}
baseRawServices := config.Services
if options.Interpolate {
if err = InterpolateRawServiceMap(&baseRawServices, environmentLookup); err != nil {
return nil, err
}
}
if options.Preprocess != nil {
var err error
baseRawServices, err = options.Preprocess(baseRawServices)
if err != nil {
return nil, err
}
}
if options.Validate {
if err := validate(baseRawServices); 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, options)
}
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 = mergeConfigV1(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
}
func mergeConfigV1(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
}

192
vendor/github.com/docker/libcompose/config/merge_v2.go generated vendored Normal file
View file

@ -0,0 +1,192 @@
package config
import (
"fmt"
"path"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/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, datas RawServiceMap, options *ParseOptions) (map[string]*ServiceConfig, error) {
if options.Validate {
if err := validateV2(datas); err != nil {
return nil, err
}
}
for name, data := range datas {
data, err := parseV2(resourceLookup, environmentLookup, file, data, datas, options)
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
}
if options.Validate {
var errs []string
for name, data := range datas {
err := validateServiceConstraintsv2(data, name)
if err != nil {
errs = append(errs, err.Error())
}
}
if len(errs) != 0 {
return nil, fmt.Errorf(strings.Join(errs, "\n"))
}
}
serviceConfigs := make(map[string]*ServiceConfig)
if err := utils.Convert(datas, &serviceConfigs); err != nil {
return nil, err
}
return serviceConfigs, nil
}
func parseV2(resourceLookup ResourceLookup, environmentLookup EnvironmentLookup, inFile string, serviceData RawService, datas RawServiceMap, options *ParseOptions) (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, options)
} 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
}
config, err := CreateConfig(bytes)
if err != nil {
return nil, err
}
baseRawServices := config.Services
if options.Interpolate {
if err = InterpolateRawServiceMap(&baseRawServices, environmentLookup); err != nil {
return nil, err
}
}
if options.Validate {
if err := validateV2(baseRawServices); 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, options)
}
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
}
var build map[interface{}]interface{}
if buildAsString, ok := serviceData["build"].(string); ok {
build = map[interface{}]interface{}{
"context": buildAsString,
}
} else {
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)
}
if _, ok := serviceData["build"].(string); ok {
//build is specified as a string containing a path to the build context
serviceData["build"] = current
} else {
//build is specified as an object with the path specified under context
build["context"] = current
}
return serviceData
}

485
vendor/github.com/docker/libcompose/config/schema.go generated vendored Normal file
View file

@ -0,0 +1,485 @@
package config
var schemaDataV1 = `{
"$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": {
"build": {"type": "string"},
"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"},
"devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"dns": {"$ref": "#/definitions/string_or_list"},
"dns_search": {"$ref": "#/definitions/string_or_list"},
"dockerfile": {"type": "string"},
"domainname": {"type": "string"},
"entrypoint": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"env_file": {"$ref": "#/definitions/string_or_list"},
"environment": {"$ref": "#/definitions/list_or_dict"},
"expose": {
"type": "array",
"items": {
"type": ["string", "number"],
"format": "expose"
},
"uniqueItems": true
},
"extends": {
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"service": {"type": "string"},
"file": {"type": "string"}
},
"required": ["service"],
"additionalProperties": false
}
]
},
"extra_hosts": {"$ref": "#/definitions/list_or_dict"},
"external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"hostname": {"type": "string"},
"image": {"type": "string"},
"ipc": {"type": "string"},
"labels": {"$ref": "#/definitions/list_or_dict"},
"links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"log_driver": {"type": "string"},
"log_opt": {"type": "object"},
"mac_address": {"type": "string"},
"mem_limit": {"type": ["number", "string"]},
"mem_reservation": {"type": ["number", "string"]},
"memswap_limit": {"type": ["number", "string"]},
"mem_swappiness": {"type": "integer"},
"net": {"type": "string"},
"pid": {"type": ["string", "null"]},
"ports": {
"type": "array",
"items": {
"type": ["string", "number"],
"format": "ports"
},
"uniqueItems": true
},
"privileged": {"type": "boolean"},
"read_only": {"type": "boolean"},
"restart": {"type": "string"},
"security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"shm_size": {"type": ["number", "string"]},
"stdin_open": {"type": "boolean"},
"stop_signal": {"type": "string"},
"tty": {"type": "boolean"},
"ulimits": {
"type": "object",
"patternProperties": {
"^[a-z]+$": {
"oneOf": [
{"type": "integer"},
{
"type":"object",
"properties": {
"hard": {"type": "integer"},
"soft": {"type": "integer"}
},
"required": ["soft", "hard"],
"additionalProperties": false
}
]
}
}
},
"user": {"type": "string"},
"volumes": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"volume_driver": {"type": "string"},
"volumes_from": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"working_dir": {"type": "string"}
},
"dependencies": {
"memswap_limit": ["mem_limit"]
},
"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": ["build"],
"not": {"required": ["image"]}
},
{
"required": ["image"],
"not": {"anyOf": [
{"required": ["build"]},
{"required": ["dockerfile"]}
]}
}
]
}
}
}
}
`
var servicesSchemaDataV2 = `{
"$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": {
"build": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"context": {"type": "string"},
"dockerfile": {"type": "string"},
"args": {"$ref": "#/definitions/list_or_dict"}
},
"additionalProperties": false
}
]
},
"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"},
"devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"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": {"$ref": "#/definitions/string_or_list"},
"environment": {"$ref": "#/definitions/list_or_dict"},
"expose": {
"type": "array",
"items": {
"type": ["string", "number"],
"format": "expose"
},
"uniqueItems": true
},
"extends": {
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"service": {"type": "string"},
"file": {"type": "string"}
},
"required": ["service"],
"additionalProperties": false
}
]
},
"external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"extra_hosts": {"$ref": "#/definitions/list_or_dict"},
"group_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"hostname": {"type": "string"},
"image": {"type": "string"},
"ipc": {"type": "string"},
"labels": {"$ref": "#/definitions/list_or_dict"},
"links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"logging": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"options": {"type": "object"}
},
"additionalProperties": false
},
"mac_address": {"type": "string"},
"mem_limit": {"type": ["number", "string"]},
"mem_reservation": {"type": ["number", "string"]},
"memswap_limit": {"type": ["number", "string"]},
"mem_swappiness": {"type": "integer"},
"network_mode": {"type": "string"},
"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
}
]
},
"oom_score_adj": {"type": "integer", "minimum": -1000, "maximum": 1000},
"pid": {"type": ["string", "null"]},
"ports": {
"type": "array",
"items": {
"type": ["string", "number"],
"format": "ports"
},
"uniqueItems": true
},
"privileged": {"type": "boolean"},
"read_only": {"type": "boolean"},
"restart": {"type": "string"},
"security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"shm_size": {"type": ["number", "string"]},
"stdin_open": {"type": "boolean"},
"stop_grace_period": {"type": "string"},
"stop_signal": {"type": "string"},
"tmpfs": {"$ref": "#/definitions/string_or_list"},
"tty": {"type": "boolean"},
"ulimits": {
"type": "object",
"patternProperties": {
"^[a-z]+$": {
"oneOf": [
{"type": "integer"},
{
"type":"object",
"properties": {
"hard": {"type": "integer"},
"soft": {"type": "integer"}
},
"required": ["soft", "hard"],
"additionalProperties": false
}
]
}
}
},
"user": {"type": "string"},
"volumes": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"volume_driver": {"type": "string"},
"volumes_from": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"working_dir": {"type": "string"}
},
"dependencies": {
"memswap_limit": ["mem_limit"]
},
"additionalProperties": false
},
"network": {
"id": "#/definitions/network",
"type": "object",
"properties": {
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"ipam": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"config": {
"type": "array"
}
},
"additionalProperties": false
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false
},
"internal": {"type": "boolean"}
},
"additionalProperties": false
},
"volume": {
"id": "#/definitions/volume",
"type": ["object", "null"],
"properties": {
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
}
}
},
"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": ["build"]},
{"required": ["image"]}
],
"properties": {
"build": {
"required": ["context"]
}
}
}
}
}
}
`

View file

@ -0,0 +1,105 @@
package config
import (
"encoding/json"
"strings"
"github.com/docker/go-connections/nat"
"github.com/xeipuuv/gojsonschema"
)
var (
schemaLoaderV1 gojsonschema.JSONLoader
constraintSchemaLoaderV1 gojsonschema.JSONLoader
schemaLoaderV2 gojsonschema.JSONLoader
constraintSchemaLoaderV2 gojsonschema.JSONLoader
schemaV1 map[string]interface{}
schemaV2 map[string]interface{}
)
func init() {
if err := setupSchemaLoaders(schemaDataV1, &schemaV1, &schemaLoaderV1, &constraintSchemaLoaderV1); err != nil {
panic(err)
}
if err := setupSchemaLoaders(servicesSchemaDataV2, &schemaV2, &schemaLoaderV2, &constraintSchemaLoaderV2); err != nil {
panic(err)
}
}
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(schemaData string, schema *map[string]interface{}, schemaLoader, constraintSchemaLoader *gojsonschema.JSONLoader) error {
if *schema != nil {
return nil
}
var schemaRaw interface{}
err := json.Unmarshal([]byte(schemaData), &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
}

265
vendor/github.com/docker/libcompose/config/types.go generated vendored Normal file
View file

@ -0,0 +1,265 @@
package config
import (
"sync"
"github.com/docker/libcompose/yaml"
)
// EnvironmentLookup defines methods to provides environment variable loading.
type EnvironmentLookup interface {
Lookup(key 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 yaml.StringorInt `yaml:"cpu_quota,omitempty"`
CPUSet string `yaml:"cpuset,omitempty"`
CPUShares yaml.StringorInt `yaml:"cpu_shares,omitempty"`
Command yaml.Command `yaml:"command,flow,omitempty"`
ContainerName string `yaml:"container_name,omitempty"`
Devices []string `yaml:"devices,omitempty"`
DNS yaml.Stringorslice `yaml:"dns,omitempty"`
DNSOpts []string `yaml:"dns_opt,omitempty"`
DNSSearch yaml.Stringorslice `yaml:"dns_search,omitempty"`
Dockerfile string `yaml:"dockerfile,omitempty"`
DomainName string `yaml:"domainname,omitempty"`
Entrypoint yaml.Command `yaml:"entrypoint,flow,omitempty"`
EnvFile yaml.Stringorslice `yaml:"env_file,omitempty"`
Environment yaml.MaporEqualSlice `yaml:"environment,omitempty"`
GroupAdd []string `yaml:"group_add,omitempty"`
Hostname string `yaml:"hostname,omitempty"`
Image string `yaml:"image,omitempty"`
Isolation string `yaml:"isolation,omitempty"`
Labels yaml.SliceorMap `yaml:"labels,omitempty"`
Links yaml.MaporColonSlice `yaml:"links,omitempty"`
LogDriver string `yaml:"log_driver,omitempty"`
MacAddress string `yaml:"mac_address,omitempty"`
MemLimit yaml.MemStringorInt `yaml:"mem_limit,omitempty"`
MemSwapLimit yaml.MemStringorInt `yaml:"memswap_limit,omitempty"`
MemSwappiness yaml.MemStringorInt `yaml:"mem_swappiness,omitempty"`
Name string `yaml:"name,omitempty"`
Net string `yaml:"net,omitempty"`
OomKillDisable bool `yaml:"oom_kill_disable,omitempty"`
OomScoreAdj yaml.StringorInt `yaml:"oom_score_adj,omitempty"`
Pid string `yaml:"pid,omitempty"`
Uts string `yaml:"uts,omitempty"`
Ipc string `yaml:"ipc,omitempty"`
Ports []string `yaml:"ports,omitempty"`
Privileged bool `yaml:"privileged,omitempty"`
Restart string `yaml:"restart,omitempty"`
ReadOnly bool `yaml:"read_only,omitempty"`
ShmSize yaml.MemStringorInt `yaml:"shm_size,omitempty"`
StdinOpen bool `yaml:"stdin_open,omitempty"`
SecurityOpt []string `yaml:"security_opt,omitempty"`
StopSignal string `yaml:"stop_signal,omitempty"`
Tmpfs yaml.Stringorslice `yaml:"tmpfs,omitempty"`
Tty bool `yaml:"tty,omitempty"`
User string `yaml:"user,omitempty"`
VolumeDriver string `yaml:"volume_driver,omitempty"`
Volumes []string `yaml:"volumes,omitempty"`
VolumesFrom []string `yaml:"volumes_from,omitempty"`
WorkingDir string `yaml:"working_dir,omitempty"`
Expose []string `yaml:"expose,omitempty"`
ExternalLinks []string `yaml:"external_links,omitempty"`
LogOpt map[string]string `yaml:"log_opt,omitempty"`
ExtraHosts []string `yaml:"extra_hosts,omitempty"`
Ulimits yaml.Ulimits `yaml:"ulimits,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 yaml.Build `yaml:"build,omitempty"`
CapAdd []string `yaml:"cap_add,omitempty"`
CapDrop []string `yaml:"cap_drop,omitempty"`
CPUSet string `yaml:"cpuset,omitempty"`
CPUShares yaml.StringorInt `yaml:"cpu_shares,omitempty"`
CPUQuota yaml.StringorInt `yaml:"cpu_quota,omitempty"`
Command yaml.Command `yaml:"command,flow,omitempty"`
CgroupParent string `yaml:"cgroup_parent,omitempty"`
ContainerName string `yaml:"container_name,omitempty"`
Devices []string `yaml:"devices,omitempty"`
DependsOn []string `yaml:"depends_on,omitempty"`
DNS yaml.Stringorslice `yaml:"dns,omitempty"`
DNSOpts []string `yaml:"dns_opt,omitempty"`
DNSSearch yaml.Stringorslice `yaml:"dns_search,omitempty"`
DomainName string `yaml:"domainname,omitempty"`
Entrypoint yaml.Command `yaml:"entrypoint,flow,omitempty"`
EnvFile yaml.Stringorslice `yaml:"env_file,omitempty"`
Environment yaml.MaporEqualSlice `yaml:"environment,omitempty"`
Expose []string `yaml:"expose,omitempty"`
Extends yaml.MaporEqualSlice `yaml:"extends,omitempty"`
ExternalLinks []string `yaml:"external_links,omitempty"`
ExtraHosts []string `yaml:"extra_hosts,omitempty"`
GroupAdd []string `yaml:"group_add,omitempty"`
Image string `yaml:"image,omitempty"`
Isolation string `yaml:"isolation,omitempty"`
Hostname string `yaml:"hostname,omitempty"`
Ipc string `yaml:"ipc,omitempty"`
Labels yaml.SliceorMap `yaml:"labels,omitempty"`
Links yaml.MaporColonSlice `yaml:"links,omitempty"`
Logging Log `yaml:"logging,omitempty"`
MacAddress string `yaml:"mac_address,omitempty"`
MemLimit yaml.MemStringorInt `yaml:"mem_limit,omitempty"`
MemReservation yaml.MemStringorInt `yaml:"mem_reservation,omitempty"`
MemSwapLimit yaml.MemStringorInt `yaml:"memswap_limit,omitempty"`
MemSwappiness yaml.MemStringorInt `yaml:"mem_swappiness,omitempty"`
NetworkMode string `yaml:"network_mode,omitempty"`
Networks *yaml.Networks `yaml:"networks,omitempty"`
OomKillDisable bool `yaml:"oom_kill_disable,omitempty"`
OomScoreAdj yaml.StringorInt `yaml:"oom_score_adj,omitempty"`
Pid string `yaml:"pid,omitempty"`
Ports []string `yaml:"ports,omitempty"`
Privileged bool `yaml:"privileged,omitempty"`
SecurityOpt []string `yaml:"security_opt,omitempty"`
ShmSize yaml.MemStringorInt `yaml:"shm_size,omitempty"`
StopGracePeriod string `yaml:"stop_grace_period,omitempty"`
StopSignal string `yaml:"stop_signal,omitempty"`
Tmpfs yaml.Stringorslice `yaml:"tmpfs,omitempty"`
VolumeDriver string `yaml:"volume_driver,omitempty"`
Volumes *yaml.Volumes `yaml:"volumes,omitempty"`
VolumesFrom []string `yaml:"volumes_from,omitempty"`
Uts string `yaml:"uts,omitempty"`
Restart string `yaml:"restart,omitempty"`
ReadOnly bool `yaml:"read_only,omitempty"`
StdinOpen bool `yaml:"stdin_open,omitempty"`
Tty bool `yaml:"tty,omitempty"`
User string `yaml:"user,omitempty"`
WorkingDir string `yaml:"working_dir,omitempty"`
Ulimits yaml.Ulimits `yaml:"ulimits,omitempty"`
}
// VolumeConfig holds v2 volume configuration
type VolumeConfig struct {
Driver string `yaml:"driver,omitempty"`
DriverOpts map[string]string `yaml:"driver_opts,omitempty"`
External yaml.External `yaml:"external,omitempty"`
}
// Ipam holds v2 network IPAM information
type Ipam struct {
Driver string `yaml:"driver,omitempty"`
Config []IpamConfig `yaml:"config,omitempty"`
}
// IpamConfig holds v2 network IPAM configuration information
type IpamConfig struct {
Subnet string `yaml:"subnet,omitempty"`
IPRange string `yaml:"ip_range,omitempty"`
Gateway string `yaml:"gateway,omitempty"`
AuxAddress map[string]string `yaml:"aux_addresses,omitempty"`
}
// NetworkConfig holds v2 network configuration
type NetworkConfig struct {
Driver string `yaml:"driver,omitempty"`
DriverOpts map[string]string `yaml:"driver_opts,omitempty"`
External yaml.External `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]interface{} `yaml:"volumes,omitempty"`
Networks map[string]interface{} `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()
}
// Remove removes the config with the specified name
func (c *ServiceConfigs) Remove(name string) {
c.mu.Lock()
delete(c.m, name)
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
}
// All returns all the config at once
func (c *ServiceConfigs) All() map[string]*ServiceConfig {
c.mu.RLock()
defer c.mu.RUnlock()
return c.m
}
// 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
// ParseOptions are a set of options to customize the parsing process
type ParseOptions struct {
Interpolate bool
Validate bool
Preprocess func(RawServiceMap) (RawServiceMap, error)
Postprocess func(map[string]*ServiceConfig) (map[string]*ServiceConfig, error)
}

42
vendor/github.com/docker/libcompose/config/utils.go generated vendored Normal file
View 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 ""
}

View file

@ -0,0 +1,306 @@
package config
import (
"fmt"
"strconv"
"strings"
"github.com/docker/libcompose/utils"
"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 ""
}
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] = utils.ConvertKeysToStrings(v)
}
return newService
}
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) error {
serviceMap = convertServiceMapKeysToStrings(serviceMap)
dataLoader := gojsonschema.NewGoLoader(serviceMap)
result, err := gojsonschema.Validate(schemaLoaderV1, dataLoader)
if err != nil {
return err
}
return generateErrorMessages(serviceMap, schemaV1, result)
}
func validateV2(serviceMap RawServiceMap) error {
serviceMap = convertServiceMapKeysToStrings(serviceMap)
dataLoader := gojsonschema.NewGoLoader(serviceMap)
result, err := gojsonschema.Validate(schemaLoaderV2, dataLoader)
if err != nil {
return err
}
return generateErrorMessages(serviceMap, schemaV2, result)
}
func generateErrorMessages(serviceMap RawServiceMap, schema map[string]interface{}, result *gojsonschema.Result) error {
var validationErrors []string
// 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":
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()))
}
}
}
return fmt.Errorf(strings.Join(validationErrors, "\n"))
}
return nil
}
func validateServiceConstraints(service RawService, serviceName string) error {
service = convertServiceKeysToStrings(service)
var validationErrors []string
dataLoader := gojsonschema.NewGoLoader(service)
result, err := gojsonschema.Validate(constraintSchemaLoaderV1, 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
}
func validateServiceConstraintsv2(service RawService, serviceName string) error {
service = convertServiceKeysToStrings(service)
var validationErrors []string
dataLoader := gojsonschema.NewGoLoader(service)
result, err := gojsonschema.Validate(constraintSchemaLoaderV2, dataLoader)
if err != nil {
return err
}
if !result.Valid() {
for _, err := range result.Errors() {
if err.Type() == "required" {
_, containsImage := service["image"]
_, containsBuild := service["build"]
if containsBuild || !containsImage && !containsBuild {
validationErrors = append(validationErrors, fmt.Sprintf("Service '%s' has neither an image nor a build context specified. At least one must be provided.", serviceName))
}
}
}
return fmt.Errorf(strings.Join(validationErrors, "\n"))
}
return nil
}

View file

@ -0,0 +1,41 @@
package auth
import (
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/docker/api/types"
"github.com/docker/docker/registry"
)
// Lookup defines a method for looking up authentication information
type Lookup interface {
All() map[string]types.AuthConfig
Lookup(repoInfo *registry.RepositoryInfo) types.AuthConfig
}
// ConfigLookup implements AuthLookup by reading a Docker config file
type ConfigLookup struct {
*configfile.ConfigFile
}
// NewConfigLookup creates a new ConfigLookup for a given context
func NewConfigLookup(configfile *configfile.ConfigFile) *ConfigLookup {
return &ConfigLookup{
ConfigFile: configfile,
}
}
// Lookup uses a Docker config file to lookup authentication information
func (c *ConfigLookup) Lookup(repoInfo *registry.RepositoryInfo) types.AuthConfig {
if c.ConfigFile == nil || repoInfo == nil || repoInfo.Index == nil {
return types.AuthConfig{}
}
return registry.ResolveAuthConfig(c.ConfigFile.AuthConfigs, repoInfo.Index)
}
// All uses a Docker config file to get all authentication information
func (c *ConfigLookup) All() map[string]types.AuthConfig {
if c.ConfigFile == nil {
return map[string]types.AuthConfig{}
}
return c.ConfigFile.AuthConfigs
}

View file

@ -0,0 +1,207 @@
package builder
import (
"fmt"
"io"
"os"
"path"
"path/filepath"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli/command/image/build"
"github.com/docker/docker/api/types"
"github.com/docker/docker/builder/dockerignore"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/term"
"github.com/docker/libcompose/logger"
"golang.org/x/net/context"
)
// DefaultDockerfileName is the default name of a Dockerfile
const DefaultDockerfileName = "Dockerfile"
// Builder defines methods to provide a docker builder. This makes libcompose
// not tied up to the docker daemon builder.
type Builder interface {
Build(imageName string) error
}
// DaemonBuilder is the daemon "docker build" Builder implementation.
type DaemonBuilder struct {
Client client.ImageAPIClient
ContextDirectory string
Dockerfile string
AuthConfigs map[string]types.AuthConfig
NoCache bool
ForceRemove bool
Pull bool
BuildArgs map[string]*string
LoggerFactory logger.Factory
}
// Build implements Builder. It consumes the docker build API endpoint and sends
// a tar of the specified service build context.
func (d *DaemonBuilder) Build(ctx context.Context, imageName string) error {
buildCtx, err := CreateTar(d.ContextDirectory, d.Dockerfile)
if err != nil {
return err
}
defer buildCtx.Close()
if d.LoggerFactory == nil {
d.LoggerFactory = &logger.NullLogger{}
}
l := d.LoggerFactory.CreateBuildLogger(imageName)
progBuff := &logger.Wrapper{
Err: false,
Logger: l,
}
buildBuff := &logger.Wrapper{
Err: false,
Logger: l,
}
errBuff := &logger.Wrapper{
Err: true,
Logger: l,
}
// Setup an upload progress bar
progressOutput := streamformatter.NewProgressOutput(progBuff)
var body io.Reader = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
logrus.Infof("Building %s...", imageName)
outFd, isTerminalOut := term.GetFdInfo(os.Stdout)
w := l.OutWriter()
if w != nil {
outFd, isTerminalOut = term.GetFdInfo(w)
}
response, err := d.Client.ImageBuild(ctx, body, types.ImageBuildOptions{
Tags: []string{imageName},
NoCache: d.NoCache,
Remove: true,
ForceRemove: d.ForceRemove,
PullParent: d.Pull,
Dockerfile: d.Dockerfile,
AuthConfigs: d.AuthConfigs,
BuildArgs: d.BuildArgs,
})
if err != nil {
return err
}
err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, 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
}
errBuff.Write([]byte(jerr.Error()))
return fmt.Errorf("Status: %s, Code: %d", jerr.Message, jerr.Code)
}
}
return err
}
// CreateTar create a build context tar for the specified project and service name.
func CreateTar(contextDirectory, dockerfile string) (io.ReadCloser, error) {
// This code was ripped off from docker/api/client/build.go
dockerfileName := filepath.Join(contextDirectory, dockerfile)
absContextDirectory, err := filepath.Abs(contextDirectory)
if err != nil {
return nil, err
}
filename := dockerfileName
if dockerfile == "" {
// No -f/--file was specified so use the default
dockerfileName = DefaultDockerfileName
filename = filepath.Join(absContextDirectory, dockerfileName)
// Just to be nice ;-) look for 'dockerfile' too but only
// use it if we found it, otherwise ignore this check
if _, err = os.Lstat(filename); os.IsNotExist(err) {
tmpFN := path.Join(absContextDirectory, strings.ToLower(dockerfileName))
if _, err = os.Lstat(tmpFN); err == nil {
dockerfileName = strings.ToLower(dockerfileName)
filename = tmpFN
}
}
}
origDockerfile := dockerfileName // used for error msg
if filename, err = filepath.Abs(filename); err != nil {
return nil, err
}
// Now reset the dockerfileName to be relative to the build context
dockerfileName, err = filepath.Rel(absContextDirectory, filename)
if err != nil {
return nil, err
}
// And canonicalize dockerfile name to a platform-independent one
dockerfileName, err = archive.CanonicalTarNameForPath(dockerfileName)
if err != nil {
return nil, fmt.Errorf("Cannot canonicalize dockerfile path %s: %v", dockerfileName, err)
}
if _, err = os.Lstat(filename); os.IsNotExist(err) {
return nil, fmt.Errorf("Cannot locate Dockerfile: %s", origDockerfile)
}
var includes = []string{"."}
var excludes []string
dockerIgnorePath := path.Join(contextDirectory, ".dockerignore")
dockerIgnore, err := os.Open(dockerIgnorePath)
if err != nil {
if !os.IsNotExist(err) {
return nil, err
}
logrus.Warnf("Error while reading .dockerignore (%s) : %s", dockerIgnorePath, err.Error())
excludes = make([]string, 0)
} else {
excludes, err = dockerignore.ReadAll(dockerIgnore)
if err != nil {
return nil, err
}
}
// If .dockerignore mentions .dockerignore or the Dockerfile
// then make sure we send both files over to the daemon
// because Dockerfile is, obviously, needed no matter what, and
// .dockerignore is needed to know if either one needs to be
// removed. The deamon will remove them for us, if needed, after it
// parses the Dockerfile.
keepThem1, _ := fileutils.Matches(".dockerignore", excludes)
keepThem2, _ := fileutils.Matches(dockerfileName, excludes)
if keepThem1 || keepThem2 {
includes = append(includes, ".dockerignore", dockerfileName)
}
if err := build.ValidateContextDirectory(contextDirectory, excludes); err != nil {
return nil, fmt.Errorf("error checking context is accessible: '%s', please check permissions and try again", err)
}
options := &archive.TarOptions{
Compression: archive.Uncompressed,
ExcludePatterns: excludes,
IncludeFiles: includes,
}
return archive.TarWithOptions(contextDirectory, options)
}

View file

@ -0,0 +1,115 @@
package client
import (
"fmt"
"net/http"
"os"
"path/filepath"
"runtime"
cliconfig "github.com/docker/cli/cli/config"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/homedir"
"github.com/docker/go-connections/sockets"
"github.com/docker/go-connections/tlsconfig"
"github.com/docker/libcompose/version"
)
const (
// DefaultAPIVersion is the default docker API version set by libcompose
DefaultAPIVersion = "v1.20"
defaultTrustKeyFile = "key.json"
defaultCaFile = "ca.pem"
defaultKeyFile = "key.pem"
defaultCertFile = "cert.pem"
)
var (
dockerCertPath = os.Getenv("DOCKER_CERT_PATH")
)
func init() {
if dockerCertPath == "" {
dockerCertPath = cliconfig.Dir()
}
}
// Options holds docker client options (host, tls, ..)
type Options struct {
TLS bool
TLSVerify bool
TLSOptions tlsconfig.Options
TrustKey string
Host string
APIVersion string
}
// Create creates a docker client based on the specified options.
func Create(c Options) (client.APIClient, error) {
if c.Host == "" {
if os.Getenv("DOCKER_API_VERSION") == "" {
os.Setenv("DOCKER_API_VERSION", DefaultAPIVersion)
}
client, err := client.NewEnvClient()
if err != nil {
return nil, err
}
return client, nil
}
apiVersion := c.APIVersion
if apiVersion == "" {
apiVersion = DefaultAPIVersion
}
if c.TLSOptions.CAFile == "" {
c.TLSOptions.CAFile = filepath.Join(dockerCertPath, defaultCaFile)
}
if c.TLSOptions.CertFile == "" {
c.TLSOptions.CertFile = filepath.Join(dockerCertPath, defaultCertFile)
}
if c.TLSOptions.KeyFile == "" {
c.TLSOptions.KeyFile = filepath.Join(dockerCertPath, defaultKeyFile)
}
if c.TrustKey == "" {
c.TrustKey = filepath.Join(homedir.Get(), ".docker", defaultTrustKeyFile)
}
if c.TLSVerify {
c.TLS = true
}
if c.TLS {
c.TLSOptions.InsecureSkipVerify = !c.TLSVerify
}
var httpClient *http.Client
if c.TLS {
config, err := tlsconfig.Client(c.TLSOptions)
if err != nil {
return nil, err
}
tr := &http.Transport{
TLSClientConfig: config,
}
proto, addr, _, err := client.ParseHost(c.Host)
if err != nil {
return nil, err
}
if err := sockets.ConfigureTransport(tr, proto, addr); err != nil {
return nil, err
}
httpClient = &http.Client{
Transport: tr,
}
}
customHeaders := map[string]string{}
customHeaders["User-Agent"] = fmt.Sprintf("Libcompose-Client/%s (%s)", version.VERSION, runtime.GOOS)
client, err := client.NewClient(c.Host, apiVersion, httpClient, customHeaders)
if err != nil {
return nil, err
}
return client, nil
}

View file

@ -0,0 +1,35 @@
package client
import (
"github.com/docker/docker/client"
"github.com/docker/libcompose/project"
)
// Factory is a factory to create docker clients.
type Factory 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 project.Service) client.APIClient
}
type defaultFactory struct {
client client.APIClient
}
// NewDefaultFactory creates and returns the default client factory that uses
// github.com/docker/docker client.
func NewDefaultFactory(opts Options) (Factory, error) {
client, err := Create(opts)
if err != nil {
return nil, err
}
return &defaultFactory{
client: client,
}, nil
}
func (s *defaultFactory) Create(service project.Service) client.APIClient {
return s.client
}

View file

@ -0,0 +1,397 @@
package container
import (
"fmt"
"io"
"math"
"os"
"strconv"
"strings"
"time"
"golang.org/x/net/context"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/promise"
"github.com/docker/docker/pkg/stdcopy"
"github.com/docker/docker/pkg/term"
"github.com/docker/go-connections/nat"
"github.com/docker/libcompose/config"
"github.com/docker/libcompose/labels"
"github.com/docker/libcompose/logger"
"github.com/docker/libcompose/project"
)
// Container holds information about a docker container and the service it is tied on.
type Container struct {
client client.ContainerAPIClient
id string
container *types.ContainerJSON
}
// Create creates a container and return a Container struct (and an error if any)
func Create(ctx context.Context, client client.ContainerAPIClient, name string, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig) (*Container, error) {
container, err := client.ContainerCreate(ctx, config, hostConfig, networkingConfig, name)
if err != nil {
return nil, err
}
return New(ctx, client, container.ID)
}
// New creates a container struct with the specified client, id and name
func New(ctx context.Context, client client.ContainerAPIClient, id string) (*Container, error) {
container, err := Get(ctx, client, id)
if err != nil {
return nil, err
}
return &Container{
client: client,
id: id,
container: container,
}, nil
}
// NewInspected creates a container struct from an inspected container
func NewInspected(client client.ContainerAPIClient, container *types.ContainerJSON) *Container {
return &Container{
client: client,
id: container.ID,
container: container,
}
}
// Info returns info about the container, like name, command, state or ports.
func (c *Container) Info(ctx context.Context) (project.Info, error) {
infos, err := ListByFilter(ctx, c.client, map[string][]string{
"name": {c.container.Name},
})
if err != nil || len(infos) == 0 {
return nil, err
}
info := infos[0]
result := project.Info{}
result["Id"] = c.container.ID
result["Name"] = name(info.Names)
result["Command"] = info.Command
result["State"] = info.Status
result["Ports"] = 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:]
}
// Rename rename the container.
func (c *Container) Rename(ctx context.Context, newName string) error {
return c.client.ContainerRename(ctx, c.container.ID, newName)
}
// Remove removes the container.
func (c *Container) Remove(ctx context.Context, removeVolume bool) error {
return c.client.ContainerRemove(ctx, c.container.ID, types.ContainerRemoveOptions{
Force: true,
RemoveVolumes: removeVolume,
})
}
// Stop stops the container.
func (c *Container) Stop(ctx context.Context, timeout int) error {
timeoutDuration := time.Duration(timeout) * time.Second
return c.client.ContainerStop(ctx, c.container.ID, &timeoutDuration)
}
// Pause pauses the container. If the containers are already paused, don't fail.
func (c *Container) Pause(ctx context.Context) error {
if !c.container.State.Paused {
if err := c.client.ContainerPause(ctx, c.container.ID); err != nil {
return err
}
return c.updateInnerContainer(ctx)
}
return nil
}
// Unpause unpauses the container. If the containers are not paused, don't fail.
func (c *Container) Unpause(ctx context.Context) error {
if c.container.State.Paused {
if err := c.client.ContainerUnpause(ctx, c.container.ID); err != nil {
return err
}
return c.updateInnerContainer(ctx)
}
return nil
}
func (c *Container) updateInnerContainer(ctx context.Context) error {
container, err := Get(ctx, c.client, c.container.ID)
if err != nil {
return err
}
c.container = container
return nil
}
// Kill kill the container.
func (c *Container) Kill(ctx context.Context, signal string) error {
return c.client.ContainerKill(ctx, c.container.ID, signal)
}
// IsRunning returns the running state of the container.
func (c *Container) IsRunning(ctx context.Context) bool {
return c.container.State.Running
}
// 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, configOverride *config.ServiceConfig) (int, error) {
var (
errCh chan error
out, stderr io.Writer
in io.ReadCloser
)
if configOverride.StdinOpen {
in = os.Stdin
}
if configOverride.Tty {
out = os.Stdout
stderr = os.Stderr
}
options := types.ContainerAttachOptions{
Stream: true,
Stdin: configOverride.StdinOpen,
Stdout: configOverride.Tty,
Stderr: configOverride.Tty,
}
resp, err := c.client.ContainerAttach(ctx, c.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, c.container.ID, types.ContainerStartOptions{}); err != nil {
return -1, err
}
if configOverride.Tty {
ws, err := term.GetWinsize(inFd)
if err != nil {
return -1, err
}
resizeOpts := types.ResizeOptions{
Height: uint(ws.Height),
Width: uint(ws.Width),
}
if err := c.client.ContainerResize(ctx, c.container.ID, resizeOpts); err != nil {
return -1, err
}
}
if err := <-errCh; err != nil {
logrus.Debugf("Error hijack: %s", err)
return -1, err
}
exitedContainer, err := c.client.ContainerInspect(ctx, c.container.ID)
if err != nil {
return -1, err
}
return exitedContainer.State.ExitCode, 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
}
// Start the specified container with the specified host config
func (c *Container) Start(ctx context.Context) error {
logrus.WithFields(logrus.Fields{"container.ID": c.container.ID, "container.Name": c.container.Name}).Debug("Starting container")
if err := c.client.ContainerStart(ctx, c.container.ID, types.ContainerStartOptions{}); err != nil {
logrus.WithFields(logrus.Fields{"container.ID": c.container.ID, "container.Name": c.container.Name}).Debug("Failed to start container")
return err
}
return nil
}
// Restart restarts the container if existing, does nothing otherwise.
func (c *Container) Restart(ctx context.Context, timeout int) error {
timeoutDuration := time.Duration(timeout) * time.Second
return c.client.ContainerRestart(ctx, c.container.ID, &timeoutDuration)
}
// Log forwards container logs to the project configured logger.
func (c *Container) Log(ctx context.Context, l logger.Logger, follow bool) error {
info, err := c.client.ContainerInspect(ctx, c.container.ID)
if err != nil {
return err
}
options := types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Follow: follow,
Tail: "all",
}
responseBody, err := c.client.ContainerLogs(ctx, c.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
}
// Port returns the host port the specified port is mapped on.
func (c *Container) Port(ctx context.Context, port string) (string, error) {
if bindings, ok := c.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
}
// Networks returns the containers network
func (c *Container) Networks() (map[string]*network.EndpointSettings, error) {
return c.container.NetworkSettings.Networks, nil
}
// ID returns the container Id.
func (c *Container) ID() string {
return c.container.ID
}
// ShortID return the container Id in its short form
func (c *Container) ShortID() string {
return c.container.ID[:12]
}
// Name returns the container name.
func (c *Container) Name() string {
return c.container.Name
}
// Image returns the container image. Depending on the engine version its either
// the complete id or the digest reference the image.
func (c *Container) Image() string {
return c.container.Image
}
// ImageConfig returns the container image stored in the config. It's the
// human-readable name of the image.
func (c *Container) ImageConfig() string {
return c.container.Config.Image
}
// Hash returns the container hash stored as label.
func (c *Container) Hash() string {
return c.container.Config.Labels[labels.HASH.Str()]
}
// Number returns the container number stored as label.
func (c *Container) Number() (int, error) {
numberStr := c.container.Config.Labels[labels.NUMBER.Str()]
return strconv.Atoi(numberStr)
}

View file

@ -0,0 +1,41 @@
package container
import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"golang.org/x/net/context"
)
// ListByFilter looks up the hosts containers with the specified filters and
// returns a list of container matching it, or an error.
func ListByFilter(ctx context.Context, clientInstance client.ContainerAPIClient, 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(ctx, types.ContainerListOptions{
All: true,
Filters: filterArgs,
})
}
// Get looks up the hosts containers with the specified ID
// or name and returns it, or an error.
func Get(ctx context.Context, clientInstance client.ContainerAPIClient, id string) (*types.ContainerJSON, error) {
container, err := clientInstance.ContainerInspect(ctx, id)
if err != nil {
if client.IsErrContainerNotFound(err) {
return nil, nil
}
return nil, err
}
return &container, nil
}

View file

@ -0,0 +1,35 @@
package ctx
import (
cliconfig "github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/libcompose/docker/auth"
"github.com/docker/libcompose/docker/client"
"github.com/docker/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 client.Factory
ConfigDir string
ConfigFile *configfile.ConfigFile
AuthLookup auth.Lookup
}
// 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
}

View file

@ -0,0 +1,104 @@
package image
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"os"
"github.com/Sirupsen/logrus"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/term"
"github.com/docker/docker/registry"
"github.com/docker/libcompose/docker/auth"
"golang.org/x/net/context"
)
// Exists return whether or not the service image already exists
func Exists(ctx context.Context, clt client.ImageAPIClient, image string) (bool, error) {
_, err := InspectImage(ctx, clt, image)
if err != nil {
if client.IsErrImageNotFound(err) {
return false, nil
}
return false, err
}
return true, nil
}
// InspectImage inspect the specified image (can be a name, an id or a digest)
// with the specified client.
func InspectImage(ctx context.Context, client client.ImageAPIClient, image string) (types.ImageInspect, error) {
imageInspect, _, err := client.ImageInspectWithRaw(ctx, image)
return imageInspect, err
}
// RemoveImage removes the specified image (can be a name, an id or a digest)
// from the daemon store with the specified client.
func RemoveImage(ctx context.Context, client client.ImageAPIClient, image string) error {
_, err := client.ImageRemove(ctx, image, types.ImageRemoveOptions{})
return err
}
// PullImage pulls the specified image (can be a name, an id or a digest)
// to the daemon store with the specified client.
func PullImage(ctx context.Context, client client.ImageAPIClient, serviceName string, authLookup auth.Lookup, image string) error {
fmt.Fprintf(os.Stderr, "Pulling %s (%s)...\n", serviceName, image)
distributionRef, err := reference.ParseNormalizedNamed(image)
if err != nil {
return err
}
repoInfo, err := registry.ParseRepositoryInfo(distributionRef)
if err != nil {
return err
}
authConfig := authLookup.Lookup(repoInfo)
// Use ConfigFile.SaveToWriter to not re-define encodeAuthToBase64
encodedAuth, err := encodeAuthToBase64(authConfig)
if err != nil {
return err
}
options := types.ImagePullOptions{
RegistryAuth: encodedAuth,
}
responseBody, err := client.ImagePull(ctx, 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
}

View file

@ -0,0 +1,19 @@
package network
import (
"github.com/docker/libcompose/config"
composeclient "github.com/docker/libcompose/docker/client"
"github.com/docker/libcompose/project"
)
// DockerFactory implements project.NetworksFactory
type DockerFactory struct {
ClientFactory composeclient.Factory
}
// Create implements project.NetworksFactory Create method.
// It creates a Networks (that implements project.Networks) from specified configurations.
func (f *DockerFactory) Create(projectName string, networkConfigs map[string]*config.NetworkConfig, serviceConfigs *config.ServiceConfigs, networkEnabled bool) (project.Networks, error) {
cli := f.ClientFactory.Create(nil)
return NetworksFromServices(cli, projectName, networkConfigs, serviceConfigs, networkEnabled)
}

View file

@ -0,0 +1,200 @@
package network
import (
"fmt"
"reflect"
"strings"
"golang.org/x/net/context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/docker/libcompose/config"
"github.com/docker/libcompose/yaml"
)
// Network holds attributes and method for a network definition in compose
type Network struct {
client client.NetworkAPIClient
name string
projectName string
driver string
driverOptions map[string]string
ipam config.Ipam
external bool
}
func (n *Network) fullName() string {
name := n.projectName + "_" + n.name
if n.external {
name = n.name
}
return name
}
// Inspect inspect the current network
func (n *Network) Inspect(ctx context.Context) (types.NetworkResource, error) {
return n.client.NetworkInspect(ctx, n.fullName(), false)
}
// Remove removes the current network (from docker engine)
func (n *Network) Remove(ctx context.Context) error {
if n.external {
fmt.Printf("Network %s is external, skipping", n.fullName())
return nil
}
fmt.Printf("Removing network %q\n", n.fullName())
return n.client.NetworkRemove(ctx, n.fullName())
}
// EnsureItExists make sure the network exists and return an error if it does not exists
// and cannot be created.
func (n *Network) EnsureItExists(ctx context.Context) error {
networkResource, err := n.Inspect(ctx)
if n.external {
if client.IsErrNetworkNotFound(err) {
// FIXME(vdemeester) introduce some libcompose error type
return fmt.Errorf("Network %s declared as external, but could not be found. Please create the network manually using docker network create %s and try again", n.fullName(), n.fullName())
}
return err
}
if err != nil && client.IsErrNetworkNotFound(err) {
return n.create(ctx)
}
if n.driver != "" && networkResource.Driver != n.driver {
return fmt.Errorf("Network %q needs to be recreated - driver has changed", n.fullName())
}
if len(n.driverOptions) != 0 && !reflect.DeepEqual(networkResource.Options, n.driverOptions) {
return fmt.Errorf("Network %q needs to be recreated - options have changed", n.fullName())
}
return err
}
func (n *Network) create(ctx context.Context) error {
fmt.Printf("Creating network %q with driver %q\n", n.fullName(), n.driver)
_, err := n.client.NetworkCreate(ctx, n.fullName(), types.NetworkCreate{
Driver: n.driver,
Options: n.driverOptions,
IPAM: convertToAPIIpam(n.ipam),
})
return err
}
func convertToAPIIpam(ipam config.Ipam) *network.IPAM {
ipamConfigs := []network.IPAMConfig{}
for _, config := range ipam.Config {
ipamConfigs = append(ipamConfigs, network.IPAMConfig{
Subnet: config.Subnet,
IPRange: config.IPRange,
Gateway: config.Gateway,
AuxAddress: config.AuxAddress,
})
}
return &network.IPAM{
Driver: ipam.Driver,
Config: ipamConfigs,
}
}
// NewNetwork creates a new network from the specified name and config.
func NewNetwork(projectName, name string, config *config.NetworkConfig, client client.NetworkAPIClient) *Network {
networkName := name
if config.External.External {
networkName = config.External.Name
}
return &Network{
client: client,
name: networkName,
projectName: projectName,
driver: config.Driver,
driverOptions: config.DriverOpts,
external: config.External.External,
ipam: config.Ipam,
}
}
// Networks holds a list of network
type Networks struct {
networks []*Network
networkEnabled bool
}
// Initialize make sure network exists if network is enabled
func (n *Networks) Initialize(ctx context.Context) error {
if !n.networkEnabled {
return nil
}
for _, network := range n.networks {
err := network.EnsureItExists(ctx)
if err != nil {
return err
}
}
return nil
}
// Remove removes networks (clean-up)
func (n *Networks) Remove(ctx context.Context) error {
if !n.networkEnabled {
return nil
}
for _, network := range n.networks {
err := network.Remove(ctx)
if err != nil {
return err
}
}
return nil
}
// NetworksFromServices creates a new Networks struct based on networks configurations and
// services configuration. If a network is defined but not used by any service, it will return
// an error along the Networks.
func NetworksFromServices(cli client.NetworkAPIClient, projectName string, networkConfigs map[string]*config.NetworkConfig, services *config.ServiceConfigs, networkEnabled bool) (*Networks, error) {
var err error
networks := make([]*Network, 0, len(networkConfigs))
networkNames := map[string]*yaml.Network{}
for _, serviceName := range services.Keys() {
serviceConfig, _ := services.Get(serviceName)
if serviceConfig.NetworkMode != "" || serviceConfig.Networks == nil || len(serviceConfig.Networks.Networks) == 0 {
continue
}
for _, network := range serviceConfig.Networks.Networks {
if network.Name != "default" {
if _, ok := networkConfigs[network.Name]; !ok {
return nil, fmt.Errorf(`Service "%s" uses an undefined network "%s"`, serviceName, network.Name)
}
}
networkNames[network.Name] = network
}
}
if len(networkConfigs) == 0 {
network := NewNetwork(projectName, "default", &config.NetworkConfig{
Driver: "bridge",
}, cli)
networks = append(networks, network)
}
for name, config := range networkConfigs {
network := NewNetwork(projectName, name, config, cli)
networks = append(networks, network)
}
if len(networkNames) != len(networks) {
unused := []string{}
for name := range networkConfigs {
if name == "default" {
continue
}
if _, ok := networkNames[name]; !ok {
unused = append(unused, name)
}
}
if len(unused) != 0 {
err = fmt.Errorf("Some networks were defined but are not used by any service: %v", strings.Join(unused, " "))
}
}
return &Networks{
networks: networks,
networkEnabled: networkEnabled,
}, err
}

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

@ -0,0 +1,106 @@
package docker
import (
"golang.org/x/net/context"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/libcompose/config"
"github.com/docker/libcompose/docker/auth"
"github.com/docker/libcompose/docker/client"
"github.com/docker/libcompose/docker/ctx"
"github.com/docker/libcompose/docker/network"
"github.com/docker/libcompose/docker/service"
"github.com/docker/libcompose/docker/volume"
"github.com/docker/libcompose/labels"
"github.com/docker/libcompose/project"
)
// NewProject creates a Project with the specified context.
func NewProject(context *ctx.Context, parseOptions *config.ParseOptions) (project.APIProject, error) {
if err := context.LookupConfig(); err != nil {
logrus.Errorf("Failed to load docker config: %v", err)
}
if context.AuthLookup == nil {
context.AuthLookup = auth.NewConfigLookup(context.ConfigFile)
}
if context.ServiceFactory == nil {
context.ServiceFactory = service.NewFactory(context)
}
if context.ClientFactory == nil {
factory, err := client.NewDefaultFactory(client.Options{})
if err != nil {
return nil, err
}
context.ClientFactory = factory
}
if context.NetworksFactory == nil {
networksFactory := &network.DockerFactory{
ClientFactory: context.ClientFactory,
}
context.NetworksFactory = networksFactory
}
if context.VolumesFactory == nil {
volumesFactory := &volume.DockerFactory{
ClientFactory: context.ClientFactory,
}
context.VolumesFactory = volumesFactory
}
// FIXME(vdemeester) Remove the context duplication ?
runtime := &Project{
clientFactory: context.ClientFactory,
}
p := project.NewProject(&context.Context, runtime, parseOptions)
err := p.Parse()
if err != nil {
return nil, err
}
return p, err
}
// Project implements project.RuntimeProject and define docker runtime specific methods.
type Project struct {
clientFactory client.Factory
}
// RemoveOrphans implements project.RuntimeProject.RemoveOrphans.
// It will remove orphan containers that are part of the project but not to any services.
func (p *Project) RemoveOrphans(ctx context.Context, projectName string, serviceConfigs *config.ServiceConfigs) error {
client := p.clientFactory.Create(nil)
filter := filters.NewArgs()
filter.Add("label", labels.PROJECT.EqString(projectName))
containers, err := client.ContainerList(ctx, types.ContainerListOptions{
Filters: filter,
})
if err != nil {
return err
}
currentServices := map[string]struct{}{}
for _, serviceName := range serviceConfigs.Keys() {
currentServices[serviceName] = struct{}{}
}
for _, container := range containers {
serviceLabel := container.Labels[labels.SERVICE.Str()]
if _, ok := currentServices[serviceLabel]; !ok {
if err := client.ContainerKill(ctx, container.ID, "SIGKILL"); err != nil {
return err
}
if err := client.ContainerRemove(ctx, container.ID, types.ContainerRemoveOptions{
Force: true,
}); err != nil {
return err
}
}
}
return nil
}

View file

@ -0,0 +1,376 @@
package service
import (
"fmt"
"strings"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/docker/runconfig/opts"
"github.com/docker/go-connections/nat"
"github.com/docker/go-units"
"github.com/docker/libcompose/config"
composeclient "github.com/docker/libcompose/docker/client"
composecontainer "github.com/docker/libcompose/docker/container"
"github.com/docker/libcompose/project"
"github.com/docker/libcompose/utils"
"golang.org/x/net/context"
)
// 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 toMap(vs []string) map[string]struct{} {
m := map[string]struct{}{}
for _, v := range vs {
if v != "" {
m[v] = struct{}{}
}
}
return m
}
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(serviceConfig *config.ServiceConfig, ctx project.Context, clientFactory composeclient.Factory) (*ConfigWrapper, error) {
config, hostConfig, err := Convert(serviceConfig, ctx, clientFactory)
if err != nil {
return nil, err
}
result := ConfigWrapper{
Config: config,
HostConfig: hostConfig,
}
return &result, nil
}
func volumes(c *config.ServiceConfig, ctx project.Context) []string {
if c.Volumes == nil {
return []string{}
}
volumes := make([]string, len(c.Volumes.Volumes))
for _, v := range c.Volumes.Volumes {
vol := v
if len(ctx.ComposeFiles) > 0 && !project.IsNamedVolume(v.Source) {
sourceVol := ctx.ResourceLookup.ResolvePath(v.String(), ctx.ComposeFiles[0])
vol.Source = strings.SplitN(sourceVol, ":", 2)[0]
}
volumes = append(volumes, vol.String())
}
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, clientFactory composeclient.Factory) (*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
}
}
vols := volumes(c, ctx)
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: toMap(Filter(vols, isVolume)),
MacAddress: c.MacAddress,
StopSignal: c.StopSignal,
StopTimeout: utils.DurationStrToSecondsInt(c.StopGracePeriod),
}
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,
})
}
}
memorySwappiness := int64(c.MemSwappiness)
resources := container.Resources{
CgroupParent: c.CgroupParent,
Memory: int64(c.MemLimit),
MemoryReservation: int64(c.MemReservation),
MemorySwap: int64(c.MemSwapLimit),
MemorySwappiness: &memorySwappiness,
CPUShares: int64(c.CPUShares),
CPUQuota: int64(c.CPUQuota),
CpusetCpus: c.CPUSet,
Ulimits: ulimits,
Devices: deviceMappings,
OomKillDisable: &c.OomKillDisable,
}
networkMode := c.NetworkMode
if c.NetworkMode == "" {
if c.Networks != nil && len(c.Networks.Networks) > 0 {
networkMode = c.Networks.Networks[0].RealName
}
} else {
switch {
case strings.HasPrefix(c.NetworkMode, "service:"):
serviceName := c.NetworkMode[8:]
if serviceConfig, ok := ctx.Project.ServiceConfigs.Get(serviceName); ok {
// FIXME(vdemeester) this is actually not right, should be fixed but not there
service, err := ctx.ServiceFactory.Create(ctx.Project, serviceName, serviceConfig)
if err != nil {
return nil, nil, err
}
containers, err := service.Containers(context.Background())
if err != nil {
return nil, nil, err
}
if len(containers) != 0 {
container := containers[0]
containerID := container.ID()
networkMode = "container:" + containerID
}
// FIXME(vdemeester) log/warn in case of len(containers) == 0
}
case strings.HasPrefix(c.NetworkMode, "container:"):
containerName := c.NetworkMode[10:]
client := clientFactory.Create(nil)
container, err := composecontainer.Get(context.Background(), client, containerName)
if err != nil {
return nil, nil, err
}
networkMode = "container:" + container.ID
default:
// do nothing :)
}
}
tmpfs := map[string]string{}
for _, path := range c.Tmpfs {
split := strings.SplitN(path, ":", 2)
if len(split) == 1 {
tmpfs[split[0]] = ""
} else if len(split) == 2 {
tmpfs[split[0]] = split[1]
}
}
hostConfig := &container.HostConfig{
VolumesFrom: volumesFrom,
CapAdd: strslice.StrSlice(utils.CopySlice(c.CapAdd)),
CapDrop: strslice.StrSlice(utils.CopySlice(c.CapDrop)),
GroupAdd: c.GroupAdd,
ExtraHosts: utils.CopySlice(c.ExtraHosts),
Privileged: c.Privileged,
Binds: Filter(vols, isBind),
DNS: utils.CopySlice(c.DNS),
DNSOptions: utils.CopySlice(c.DNSOpts),
DNSSearch: utils.CopySlice(c.DNSSearch),
Isolation: container.Isolation(c.Isolation),
LogConfig: container.LogConfig{
Type: c.Logging.Driver,
Config: utils.CopyMap(c.Logging.Options),
},
NetworkMode: container.NetworkMode(networkMode),
ReadonlyRootfs: c.ReadOnly,
OomScoreAdj: int(c.OomScoreAdj),
PidMode: container.PidMode(c.Pid),
UTSMode: container.UTSMode(c.Uts),
IpcMode: container.IpcMode(c.Ipc),
PortBindings: portBindings,
RestartPolicy: *restartPolicy,
ShmSize: int64(c.ShmSize),
SecurityOpt: utils.CopySlice(c.SecurityOpt),
Tmpfs: tmpfs,
VolumeDriver: c.VolumeDriver,
Resources: resources,
}
if config.Labels == nil {
config.Labels = map[string]string{}
}
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 := 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
}
// parseDevice parses a device mapping string to a container.DeviceMapping struct
// FIXME(vdemeester) de-duplicate this by re-exporting it in docker/docker
func parseDevice(device string) (container.DeviceMapping, error) {
src := ""
dst := ""
permissions := "rwm"
arr := strings.Split(device, ":")
switch len(arr) {
case 3:
permissions = arr[2]
fallthrough
case 2:
if validDeviceMode(arr[1]) {
permissions = arr[1]
} else {
dst = arr[1]
}
fallthrough
case 1:
src = arr[0]
default:
return container.DeviceMapping{}, fmt.Errorf("invalid device specification: %s", device)
}
if dst == "" {
dst = src
}
deviceMapping := container.DeviceMapping{
PathOnHost: src,
PathInContainer: dst,
CgroupPermissions: permissions,
}
return deviceMapping, nil
}
// validDeviceMode checks if the mode for device is valid or not.
// Valid mode is a composition of r (read), w (write), and m (mknod).
func validDeviceMode(mode string) bool {
var legalDeviceMode = map[rune]bool{
'r': true,
'w': true,
'm': true,
}
if mode == "" {
return false
}
for _, c := range mode {
if !legalDeviceMode[c] {
return false
}
legalDeviceMode[c] = false
}
return true
}

View file

@ -0,0 +1,92 @@
package service
import (
"fmt"
"strconv"
"golang.org/x/net/context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"github.com/docker/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(ctx context.Context, client client.ContainerAPIClient, 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(ctx, types.ContainerListOptions{
All: true,
Filters: 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
}

View file

@ -0,0 +1,749 @@
package service
import (
"fmt"
"strings"
"time"
"golang.org/x/net/context"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
"github.com/docker/libcompose/config"
"github.com/docker/libcompose/docker/auth"
"github.com/docker/libcompose/docker/builder"
composeclient "github.com/docker/libcompose/docker/client"
"github.com/docker/libcompose/docker/container"
"github.com/docker/libcompose/docker/ctx"
"github.com/docker/libcompose/docker/image"
"github.com/docker/libcompose/labels"
"github.com/docker/libcompose/project"
"github.com/docker/libcompose/project/events"
"github.com/docker/libcompose/project/options"
"github.com/docker/libcompose/utils"
"github.com/docker/libcompose/yaml"
)
// Service is a project.Service implementations.
type Service struct {
name string
project *project.Project
serviceConfig *config.ServiceConfig
clientFactory composeclient.Factory
authLookup auth.Lookup
// FIXME(vdemeester) remove this at some point
context *ctx.Context
}
// NewService creates a service
func NewService(name string, serviceConfig *config.ServiceConfig, context *ctx.Context) *Service {
return &Service{
name: name,
project: context.Project,
serviceConfig: serviceConfig,
clientFactory: context.ClientFactory,
authLookup: context.AuthLookup,
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 DefaultDependentServices(s.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(ctx context.Context, options options.Create) error {
containers, err := s.collectContainers(ctx)
if err != nil {
return err
}
if err := s.ensureImageExists(ctx, options.NoBuild, options.ForceBuild); err != nil {
return err
}
if len(containers) != 0 {
return s.eachContainer(ctx, containers, func(c *container.Container) error {
_, err := s.recreateIfNeeded(ctx, c, options.NoRecreate, options.ForceRecreate)
return err
})
}
namer, err := s.namer(ctx, 1)
if err != nil {
return err
}
_, err = s.createContainer(ctx, namer, "", nil, false)
return err
}
func (s *Service) namer(ctx context.Context, count int) (Namer, error) {
var namer Namer
var err error
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 {
client := s.clientFactory.Create(s)
namer, err = NewNamer(ctx, client, s.project.Name, s.name, false)
if err != nil {
return nil, err
}
}
return namer, nil
}
func (s *Service) collectContainers(ctx context.Context) ([]*container.Container, error) {
client := s.clientFactory.Create(s)
containers, err := container.ListByFilter(ctx, client, labels.SERVICE.Eq(s.name), labels.PROJECT.Eq(s.project.Name))
if err != nil {
return nil, err
}
result := []*container.Container{}
for _, cont := range containers {
c, err := container.New(ctx, client, cont.ID)
if err != nil {
return nil, err
}
result = append(result, c)
}
return result, nil
}
func (s *Service) ensureImageExists(ctx context.Context, noBuild bool, forceBuild bool) error {
if forceBuild {
return s.build(ctx, options.Build{})
}
exists, err := image.Exists(ctx, s.clientFactory.Create(s), s.imageName())
if err != nil {
return err
}
if exists {
return nil
}
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.build(ctx, options.Build{})
}
return s.Pull(ctx)
}
func (s *Service) imageName() string {
if s.Config().Image != "" {
return s.Config().Image
}
return fmt.Sprintf("%s_%s", s.project.Name, s.Name())
}
// Build implements Service.Build. It will try to build the image and returns an error if any.
func (s *Service) Build(ctx context.Context, buildOptions options.Build) error {
return s.build(ctx, buildOptions)
}
func (s *Service) build(ctx context.Context, buildOptions options.Build) error {
if s.Config().Build.Context == "" {
return fmt.Errorf("Specified service does not have a build section")
}
builder := &builder.DaemonBuilder{
Client: s.clientFactory.Create(s),
ContextDirectory: s.Config().Build.Context,
Dockerfile: s.Config().Build.Dockerfile,
BuildArgs: s.Config().Build.Args,
AuthConfigs: s.authLookup.All(),
NoCache: buildOptions.NoCache,
ForceRemove: buildOptions.ForceRemove,
Pull: buildOptions.Pull,
LoggerFactory: s.context.LoggerFactory,
}
return builder.Build(ctx, s.imageName())
}
func (s *Service) constructContainers(ctx context.Context, count int) ([]*container.Container, error) {
result, err := s.collectContainers(ctx)
if err != nil {
return nil, err
}
client := s.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(ctx, client, s.project.Name, s.name, false)
if err != nil {
return nil, err
}
}
for i := len(result); i < count; i++ {
c, err := s.createContainer(ctx, namer, "", nil, false)
if err != nil {
return nil, err
}
id := c.ID()
logrus.Debugf("Created container %s: %v", id, c.Name())
result = append(result, c)
}
return result, nil
}
// Up implements Service.Up. It builds the image if needed, creates a container
// and start it.
func (s *Service) Up(ctx context.Context, options options.Up) error {
containers, err := s.collectContainers(ctx)
if err != nil {
return err
}
var imageName = s.imageName()
if len(containers) == 0 || !options.NoRecreate {
if err = s.ensureImageExists(ctx, options.NoBuild, options.ForceBuild); err != nil {
return err
}
}
return s.up(ctx, imageName, true, options)
}
// Run implements Service.Run. It runs a one of command within the service container.
// It always create a new container.
func (s *Service) Run(ctx context.Context, commandParts []string, options options.Run) (int, error) {
err := s.ensureImageExists(ctx, false, false)
if err != nil {
return -1, err
}
client := s.clientFactory.Create(s)
namer, err := NewNamer(ctx, client, s.project.Name, s.name, true)
if err != nil {
return -1, err
}
configOverride := &config.ServiceConfig{Command: commandParts, Tty: true, StdinOpen: true}
c, err := s.createContainer(ctx, namer, "", configOverride, true)
if err != nil {
return -1, err
}
if err := s.connectContainerToNetworks(ctx, c, true); err != nil {
return -1, err
}
if options.Detached {
logrus.Infof("%s", c.Name())
return 0, c.Start(ctx)
}
return c.Run(ctx, configOverride)
}
// 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(ctx context.Context) (project.InfoSet, error) {
result := project.InfoSet{}
containers, err := s.collectContainers(ctx)
if err != nil {
return nil, err
}
for _, c := range containers {
info, err := c.Info(ctx)
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(ctx context.Context) error {
return s.collectContainersAndDo(ctx, func(c *container.Container) error {
if err := s.connectContainerToNetworks(ctx, c, false); err != nil {
return err
}
return c.Start(ctx)
})
}
func (s *Service) up(ctx context.Context, imageName string, create bool, options options.Up) error {
containers, err := s.collectContainers(ctx)
if err != nil {
return err
}
logrus.Debugf("Found %d existing containers for service %s", len(containers), s.name)
if len(containers) == 0 && create {
namer, err := s.namer(ctx, 1)
if err != nil {
return err
}
c, err := s.createContainer(ctx, namer, "", nil, false)
if err != nil {
return err
}
containers = []*container.Container{c}
}
return s.eachContainer(ctx, containers, func(c *container.Container) error {
var err error
if create {
c, err = s.recreateIfNeeded(ctx, c, options.NoRecreate, options.ForceRecreate)
if err != nil {
return err
}
}
if err := s.connectContainerToNetworks(ctx, c, false); err != nil {
return err
}
err = c.Start(ctx)
if err == nil {
s.project.Notify(events.ContainerStarted, s.name, map[string]string{
"name": c.Name(),
})
}
return err
})
}
func (s *Service) connectContainerToNetworks(ctx context.Context, c *container.Container, oneOff bool) error {
connectedNetworks, err := c.Networks()
if err != nil {
return nil
}
if s.serviceConfig.Networks != nil {
for _, network := range s.serviceConfig.Networks.Networks {
existingNetwork, ok := connectedNetworks[network.Name]
if ok {
// FIXME(vdemeester) implement alias checking (to not disconnect/reconnect for nothing)
aliasPresent := false
for _, alias := range existingNetwork.Aliases {
ID := c.ShortID()
if alias == ID {
aliasPresent = true
}
}
if aliasPresent {
continue
}
if err := s.NetworkDisconnect(ctx, c, network, oneOff); err != nil {
return err
}
}
if err := s.NetworkConnect(ctx, c, network, oneOff); err != nil {
return err
}
}
}
return nil
}
// NetworkDisconnect disconnects the container from the specified network
func (s *Service) NetworkDisconnect(ctx context.Context, c *container.Container, net *yaml.Network, oneOff bool) error {
containerID := c.ID()
client := s.clientFactory.Create(s)
return client.NetworkDisconnect(ctx, net.RealName, containerID, true)
}
// NetworkConnect connects the container to the specified network
// FIXME(vdemeester) will be refactor with Container refactoring
func (s *Service) NetworkConnect(ctx context.Context, c *container.Container, net *yaml.Network, oneOff bool) error {
containerID := c.ID()
client := s.clientFactory.Create(s)
internalLinks, err := s.getLinks()
if err != nil {
return err
}
links := []string{}
// TODO(vdemeester) handle link to self (?)
for k, v := range internalLinks {
links = append(links, strings.Join([]string{v, k}, ":"))
}
for _, v := range s.serviceConfig.ExternalLinks {
links = append(links, v)
}
aliases := []string{}
if !oneOff {
aliases = []string{s.Name()}
}
aliases = append(aliases, net.Aliases...)
return client.NetworkConnect(ctx, net.RealName, containerID, &network.EndpointSettings{
Aliases: aliases,
Links: links,
IPAddress: net.IPv4Address,
IPAMConfig: &network.EndpointIPAMConfig{
IPv4Address: net.IPv4Address,
IPv6Address: net.IPv6Address,
},
})
}
func (s *Service) recreateIfNeeded(ctx context.Context, c *container.Container, noRecreate, forceRecreate bool) (*container.Container, error) {
if noRecreate {
return c, nil
}
outOfSync, err := s.OutOfSync(ctx, c)
if err != nil {
return c, 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)
newContainer, err := s.recreate(ctx, c)
if err != nil {
return c, err
}
return newContainer, nil
}
return c, err
}
func (s *Service) recreate(ctx context.Context, c *container.Container) (*container.Container, error) {
name := c.Name()
id := c.ID()
newName := fmt.Sprintf("%s_%s", name, id[:12])
logrus.Debugf("Renaming %s => %s", name, newName)
if err := c.Rename(ctx, newName); err != nil {
logrus.Errorf("Failed to rename old container %s", c.Name())
return nil, err
}
namer := NewSingleNamer(name)
newContainer, err := s.createContainer(ctx, namer, id, nil, false)
if err != nil {
return nil, err
}
newID := newContainer.ID()
logrus.Debugf("Created replacement container %s", newID)
if err := c.Remove(ctx, 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(), id)
return newContainer, 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 (s *Service) OutOfSync(ctx context.Context, c *container.Container) (bool, error) {
if c.ImageConfig() != s.serviceConfig.Image {
logrus.Debugf("Images for %s do not match %s!=%s", c.Name(), c.ImageConfig(), s.serviceConfig.Image)
return true, nil
}
expectedHash := config.GetServiceHash(s.name, s.Config())
if c.Hash() != expectedHash {
logrus.Debugf("Hashes for %s do not match %s!=%s", c.Name(), c.Hash(), expectedHash)
return true, nil
}
image, err := image.InspectImage(ctx, s.clientFactory.Create(s), c.ImageConfig())
if err != nil {
if client.IsErrImageNotFound(err) {
logrus.Debugf("Image %s do not exist, do not know if it's out of sync", c.Image())
return false, nil
}
return false, err
}
logrus.Debugf("Checking existing image name vs id: %s == %s", image.ID, c.Image())
return image.ID != c.Image(), err
}
func (s *Service) collectContainersAndDo(ctx context.Context, action func(*container.Container) error) error {
containers, err := s.collectContainers(ctx)
if err != nil {
return err
}
return s.eachContainer(ctx, containers, action)
}
func (s *Service) eachContainer(ctx context.Context, containers []*container.Container, action func(*container.Container) error) error {
tasks := utils.InParallel{}
for _, cont := range containers {
task := func(cont *container.Container) func() error {
return func() error {
return action(cont)
}
}(cont)
tasks.Add(task)
}
return tasks.Wait()
}
// Stop implements Service.Stop. It stops any containers related to the service.
func (s *Service) Stop(ctx context.Context, timeout int) error {
timeout = s.stopTimeout(timeout)
return s.collectContainersAndDo(ctx, func(c *container.Container) error {
return c.Stop(ctx, timeout)
})
}
// Restart implements Service.Restart. It restarts any containers related to the service.
func (s *Service) Restart(ctx context.Context, timeout int) error {
timeout = s.stopTimeout(timeout)
return s.collectContainersAndDo(ctx, func(c *container.Container) error {
return c.Restart(ctx, timeout)
})
}
// Kill implements Service.Kill. It kills any containers related to the service.
func (s *Service) Kill(ctx context.Context, signal string) error {
return s.collectContainersAndDo(ctx, func(c *container.Container) error {
return c.Kill(ctx, signal)
})
}
// Delete implements Service.Delete. It removes any containers related to the service.
func (s *Service) Delete(ctx context.Context, options options.Delete) error {
return s.collectContainersAndDo(ctx, func(c *container.Container) error {
running := c.IsRunning(ctx)
if !running || options.RemoveRunning {
return c.Remove(ctx, options.RemoveVolume)
}
return nil
})
}
// Log implements Service.Log. It returns the docker logs for each container related to the service.
func (s *Service) Log(ctx context.Context, follow bool) error {
return s.collectContainersAndDo(ctx, func(c *container.Container) error {
containerNumber, err := c.Number()
if err != nil {
return err
}
name := fmt.Sprintf("%s_%d", s.name, containerNumber)
if s.Config().ContainerName != "" {
name = s.Config().ContainerName
}
l := s.context.LoggerFactory.CreateContainerLogger(name)
return c.Log(ctx, l, 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(ctx context.Context, 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())
}
containers, err := s.collectContainers(ctx)
if err != nil {
return err
}
if len(containers) > scale {
foundCount := 0
for _, c := range containers {
foundCount++
if foundCount > scale {
timeout = s.stopTimeout(timeout)
if err := c.Stop(ctx, timeout); err != nil {
return err
}
// FIXME(vdemeester) remove volume in scale by default ?
if err := c.Remove(ctx, false); err != nil {
return err
}
}
}
}
if err != nil {
return err
}
if len(containers) < scale {
err := s.ensureImageExists(ctx, false, false)
if err != nil {
return err
}
if _, err = s.constructContainers(ctx, scale); err != nil {
return err
}
}
return s.up(ctx, "", 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(ctx context.Context) error {
if s.Config().Image == "" {
return nil
}
return image.PullImage(ctx, s.clientFactory.Create(s), s.name, s.authLookup, s.Config().Image)
}
// Pause implements Service.Pause. It puts into pause the container(s) related
// to the service.
func (s *Service) Pause(ctx context.Context) error {
return s.collectContainersAndDo(ctx, func(c *container.Container) error {
return c.Pause(ctx)
})
}
// Unpause implements Service.Pause. It brings back from pause the container(s)
// related to the service.
func (s *Service) Unpause(ctx context.Context) error {
return s.collectContainersAndDo(ctx, func(c *container.Container) error {
return c.Unpause(ctx)
})
}
// RemoveImage implements Service.RemoveImage. It removes images used for the service
// depending on the specified type.
func (s *Service) RemoveImage(ctx context.Context, imageType options.ImageType) error {
switch imageType {
case "local":
if s.Config().Image != "" {
return nil
}
return image.RemoveImage(ctx, s.clientFactory.Create(s), s.imageName())
case "all":
return image.RemoveImage(ctx, s.clientFactory.Create(s), s.imageName())
default:
// Don't do a thing, should be validated up-front
return nil
}
}
var eventAttributes = []string{"image", "name"}
// Events implements Service.Events. It listen to all real-time events happening
// for the service, and put them into the specified chan.
func (s *Service) Events(ctx context.Context, evts chan events.ContainerEvent) error {
filter := filters.NewArgs()
filter.Add("label", fmt.Sprintf("%s=%s", labels.PROJECT, s.project.Name))
filter.Add("label", fmt.Sprintf("%s=%s", labels.SERVICE, s.name))
client := s.clientFactory.Create(s)
eventq, errq := client.Events(ctx, types.EventsOptions{
Filters: filter,
})
go func() {
for {
select {
case event := <-eventq:
service := event.Actor.Attributes[labels.SERVICE.Str()]
attributes := map[string]string{}
for _, attr := range eventAttributes {
attributes[attr] = event.Actor.Attributes[attr]
}
e := events.ContainerEvent{
Service: service,
Event: event.Action,
Type: event.Type,
ID: event.Actor.ID,
Time: time.Unix(event.Time, 0),
Attributes: attributes,
}
evts <- e
}
}
}()
return <-errq
}
// Containers implements Service.Containers. It returns the list of containers
// that are related to the service.
func (s *Service) Containers(ctx context.Context) ([]project.Container, error) {
result := []project.Container{}
containers, err := s.collectContainers(ctx)
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
}
//take in timeout flag from cli as parameter
//return timeout if it is set,
//else return stop_grace_period if it is set,
//else return default 10s
func (s *Service) stopTimeout(timeout int) int {
DEFAULTTIMEOUT := 10
if timeout != 0 {
return timeout
}
configTimeout := utils.DurationStrToSecondsInt(s.Config().StopGracePeriod)
if configTimeout != nil {
return *configTimeout
}
return DEFAULTTIMEOUT
}

View file

@ -0,0 +1,181 @@
package service
import (
"fmt"
"strconv"
"strings"
"golang.org/x/net/context"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/types"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/libcompose/config"
composecontainer "github.com/docker/libcompose/docker/container"
"github.com/docker/libcompose/labels"
"github.com/docker/libcompose/project"
"github.com/docker/libcompose/project/events"
util "github.com/docker/libcompose/utils"
)
func (s *Service) createContainer(ctx context.Context, namer Namer, oldContainer string, configOverride *config.ServiceConfig, oneOff bool) (*composecontainer.Container, error) {
serviceConfig := s.serviceConfig
if configOverride != nil {
serviceConfig.Command = configOverride.Command
serviceConfig.Tty = configOverride.Tty
serviceConfig.StdinOpen = configOverride.StdinOpen
}
configWrapper, err := ConvertToAPI(serviceConfig, s.context.Context, s.clientFactory)
if err != nil {
return nil, err
}
configWrapper.Config.Image = s.imageName()
containerName, containerNumber := namer.Next()
configWrapper.Config.Labels[labels.SERVICE.Str()] = s.name
configWrapper.Config.Labels[labels.PROJECT.Str()] = s.project.Name
configWrapper.Config.Labels[labels.HASH.Str()] = config.GetServiceHash(s.name, serviceConfig)
configWrapper.Config.Labels[labels.ONEOFF.Str()] = strings.Title(strconv.FormatBool(oneOff))
configWrapper.Config.Labels[labels.NUMBER.Str()] = fmt.Sprintf("%d", containerNumber)
configWrapper.Config.Labels[labels.VERSION.Str()] = project.ComposeVersion
err = s.populateAdditionalHostConfig(configWrapper.HostConfig)
if err != nil {
return nil, err
}
// FIXME(vdemeester): oldContainer should be a Container instead of a string
client := s.clientFactory.Create(s)
if oldContainer != "" {
info, err := client.ContainerInspect(ctx, 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", containerName, configWrapper)
// FIXME(vdemeester): long-term will be container.Create(…)
container, err := composecontainer.Create(ctx, client, containerName, configWrapper.Config, configWrapper.HostConfig, configWrapper.NetworkingConfig)
if err != nil {
return nil, err
}
s.project.Notify(events.ContainerCreated, s.name, map[string]string{
"name": containerName,
})
return container, nil
}
func (s *Service) populateAdditionalHostConfig(hostConfig *containertypes.HostConfig) error {
links, err := s.getLinks()
if err != nil {
return err
}
for _, link := range s.DependentServices() {
if !s.project.ServiceConfigs.Has(link.Target) {
continue
}
service, err := s.project.CreateService(link.Target)
if err != nil {
return err
}
containers, err := service.Containers(context.Background())
if err != nil {
return err
}
if link.Type == project.RelTypeIpcNamespace {
hostConfig, err = addIpc(hostConfig, service, containers, s.serviceConfig.Ipc)
} else if link.Type == project.RelTypeNetNamespace {
hostConfig, err = addNetNs(hostConfig, service, containers, s.serviceConfig.NetworkMode)
}
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 s.serviceConfig.ExternalLinks {
hostConfig.Links = append(hostConfig.Links, v)
}
return nil
}
// FIXME(vdemeester) this is temporary
func (s *Service) getLinks() (map[string]string, error) {
links := map[string]string{}
for _, link := range s.DependentServices() {
if !s.project.ServiceConfigs.Has(link.Target) {
continue
}
service, err := s.project.CreateService(link.Target)
if err != nil {
return nil, err
}
// FIXME(vdemeester) container should not know service
containers, err := service.Containers(context.Background())
if err != nil {
return nil, err
}
if link.Type == project.RelTypeLink {
addLinks(links, service, link, containers)
}
if err != nil {
return nil, err
}
}
return links, nil
}
func 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 addIpc(config *containertypes.HostConfig, service project.Service, containers []project.Container, ipc string) (*containertypes.HostConfig, error) {
if len(containers) == 0 {
return nil, fmt.Errorf("Failed to find container for IPC %v", ipc)
}
id := containers[0].ID()
config.IpcMode = containertypes.IpcMode("container:" + id)
return config, nil
}
func addNetNs(config *containertypes.HostConfig, service project.Service, containers []project.Container, networkMode string) (*containertypes.HostConfig, error) {
if len(containers) == 0 {
return nil, fmt.Errorf("Failed to find container for networks ns %v", networkMode)
}
id := containers[0].ID()
config.NetworkMode = containertypes.NetworkMode("container:" + id)
return config, nil
}
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
}

View file

@ -0,0 +1,24 @@
package service
import (
"github.com/docker/libcompose/config"
"github.com/docker/libcompose/docker/ctx"
"github.com/docker/libcompose/project"
)
// Factory is an implementation of project.ServiceFactory.
type Factory struct {
context *ctx.Context
}
// NewFactory creates a new service factory for the given context
func NewFactory(context *ctx.Context) *Factory {
return &Factory{
context: context,
}
}
// Create creates a Service based on the specified project, name and service configuration.
func (s *Factory) Create(project *project.Project, name string, serviceConfig *config.ServiceConfig) (project.Service, error) {
return NewService(name, serviceConfig, s.context), nil
}

View file

@ -0,0 +1,45 @@
package service
import (
"github.com/docker/docker/api/types/container"
"github.com/docker/libcompose/project"
)
// 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.
// It uses default project implementation and append some docker specific ones.
func DefaultDependentServices(p *project.Project, s project.Service) []project.ServiceRelationship {
result := project.DefaultDependentServices(p, s)
result = appendNs(p, result, s.Config().NetworkMode, project.RelTypeNetNamespace)
result = appendNs(p, result, s.Config().Ipc, project.RelTypeIpcNamespace)
return result
}
func appendNs(p *project.Project, rels []project.ServiceRelationship, conf string, relType project.ServiceRelationshipType) []project.ServiceRelationship {
service := GetContainerFromIpcLikeConfig(p, conf)
if service != "" {
rels = append(rels, project.NewServiceRelationship(service, relType))
}
return rels
}
// GetContainerFromIpcLikeConfig returns name of the service that shares the IPC
// namespace with the specified service.
func GetContainerFromIpcLikeConfig(p *project.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 ""
}

View file

@ -0,0 +1,157 @@
package volume
import (
"fmt"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
"github.com/docker/libcompose/config"
composeclient "github.com/docker/libcompose/docker/client"
"github.com/docker/libcompose/project"
"golang.org/x/net/context"
)
// Volume holds attributes and method for a volume definition in compose
type Volume struct {
client client.VolumeAPIClient
projectName string
name string
driver string
driverOptions map[string]string
external bool
// TODO (shouze) missing labels
}
func (v *Volume) fullName() string {
name := v.projectName + "_" + v.name
if v.external {
name = v.name
}
return name
}
// Inspect inspect the current volume
func (v *Volume) Inspect(ctx context.Context) (types.Volume, error) {
return v.client.VolumeInspect(ctx, v.fullName())
}
// Remove removes the current volume (from docker engine)
func (v *Volume) Remove(ctx context.Context) error {
if v.external {
fmt.Printf("Volume %s is external, skipping", v.fullName())
return nil
}
fmt.Printf("Removing volume %q\n", v.fullName())
return v.client.VolumeRemove(ctx, v.fullName(), true)
}
// EnsureItExists make sure the volume exists and return an error if it does not exists
// and cannot be created.
func (v *Volume) EnsureItExists(ctx context.Context) error {
volumeResource, err := v.Inspect(ctx)
if v.external {
if client.IsErrVolumeNotFound(err) {
// FIXME(shouze) introduce some libcompose error type
return fmt.Errorf("Volume %s declared as external, but could not be found. Please create the volume manually using docker volume create %s and try again", v.name, v.name)
}
return err
}
if err != nil && client.IsErrVolumeNotFound(err) {
return v.create(ctx)
}
if volumeResource.Driver != v.driver {
return fmt.Errorf("Volume %q needs to be recreated - driver has changed", v.name)
}
return err
}
func (v *Volume) create(ctx context.Context) error {
fmt.Printf("Creating volume %q with driver %q\n", v.fullName(), v.driver)
_, err := v.client.VolumeCreate(ctx, volume.VolumesCreateBody{
Name: v.fullName(),
Driver: v.driver,
DriverOpts: v.driverOptions,
// TODO (shouze) missing labels
})
return err
}
// NewVolume creates a new volume from the specified name and config.
func NewVolume(projectName, name string, config *config.VolumeConfig, client client.VolumeAPIClient) *Volume {
vol := &Volume{
client: client,
projectName: projectName,
name: name,
}
if config != nil {
vol.driver = config.Driver
vol.driverOptions = config.DriverOpts
vol.external = config.External.External
}
return vol
}
// Volumes holds a list of volume
type Volumes struct {
volumes []*Volume
volumeEnabled bool
}
// Initialize make sure volume exists if volume is enabled
func (v *Volumes) Initialize(ctx context.Context) error {
if !v.volumeEnabled {
return nil
}
for _, volume := range v.volumes {
err := volume.EnsureItExists(ctx)
if err != nil {
return err
}
}
return nil
}
// Remove removes volumes (clean-up)
func (v *Volumes) Remove(ctx context.Context) error {
if !v.volumeEnabled {
return nil
}
for _, volume := range v.volumes {
err := volume.Remove(ctx)
if err != nil {
return err
}
}
return nil
}
// VolumesFromServices creates a new Volumes struct based on volumes configurations and
// services configuration. If a volume is defined but not used by any service, it will return
// an error along the Volumes.
func VolumesFromServices(cli client.VolumeAPIClient, projectName string, volumeConfigs map[string]*config.VolumeConfig, services *config.ServiceConfigs, volumeEnabled bool) (*Volumes, error) {
var err error
volumes := make([]*Volume, 0, len(volumeConfigs))
for name, config := range volumeConfigs {
volume := NewVolume(projectName, name, config, cli)
volumes = append(volumes, volume)
}
return &Volumes{
volumes: volumes,
volumeEnabled: volumeEnabled,
}, err
}
// DockerFactory implements project.VolumesFactory
type DockerFactory struct {
ClientFactory composeclient.Factory
}
// Create implements project.VolumesFactory Create method.
// It creates a Volumes (that implements project.Volumes) from specified configurations.
func (f *DockerFactory) Create(projectName string, volumeConfigs map[string]*config.VolumeConfig, serviceConfigs *config.ServiceConfigs, volumeEnabled bool) (project.Volumes, error) {
cli := f.ClientFactory.Create(nil)
return VolumesFromServices(cli, projectName, volumeConfigs, serviceConfigs, volumeEnabled)
}

94
vendor/github.com/docker/libcompose/labels/labels.go generated vendored Normal file
View file

@ -0,0 +1,94 @@
package labels
import (
"encoding/json"
"fmt"
"github.com/docker/libcompose/utils"
)
// Label represents a docker label.
type Label string
// Libcompose default labels.
const (
NUMBER = Label("com.docker.compose.container-number")
ONEOFF = Label("com.docker.compose.oneoff")
PROJECT = Label("com.docker.compose.project")
SERVICE = Label("com.docker.compose.service")
HASH = Label("com.docker.compose.config-hash")
VERSION = Label("com.docker.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)},
}
}

42
vendor/github.com/docker/libcompose/logger/null.go generated vendored Normal file
View file

@ -0,0 +1,42 @@
package logger
import (
"io"
)
// 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) {
}
// CreateContainerLogger allows NullLogger to implement logger.Factory.
func (n *NullLogger) CreateContainerLogger(_ string) Logger {
return &NullLogger{}
}
// CreateBuildLogger allows NullLogger to implement logger.Factory.
func (n *NullLogger) CreateBuildLogger(_ string) Logger {
return &NullLogger{}
}
// CreatePullLogger allows NullLogger to implement logger.Factory.
func (n *NullLogger) CreatePullLogger(_ string) Logger {
return &NullLogger{}
}
// OutWriter returns the base writer
func (n *NullLogger) OutWriter() io.Writer {
return nil
}
// ErrWriter returns the base writer
func (n *NullLogger) ErrWriter() io.Writer {
return nil
}

View file

@ -0,0 +1,48 @@
package logger
import (
"fmt"
"io"
"os"
)
// RawLogger is a logger.Logger and logger.Factory implementation that prints raw data with no formatting.
type RawLogger struct {
}
// Out is a no-op function.
func (r *RawLogger) Out(message []byte) {
fmt.Print(string(message))
}
// Err is a no-op function.
func (r *RawLogger) Err(message []byte) {
fmt.Fprint(os.Stderr, string(message))
}
// CreateContainerLogger allows RawLogger to implement logger.Factory.
func (r *RawLogger) CreateContainerLogger(_ string) Logger {
return &RawLogger{}
}
// CreateBuildLogger allows RawLogger to implement logger.Factory.
func (r *RawLogger) CreateBuildLogger(_ string) Logger {
return &RawLogger{}
}
// CreatePullLogger allows RawLogger to implement logger.Factory.
func (r *RawLogger) CreatePullLogger(_ string) Logger {
return &RawLogger{}
}
// OutWriter returns the base writer
func (r *RawLogger) OutWriter() io.Writer {
return os.Stdout
}
// ErrWriter returns the base writer
func (r *RawLogger) ErrWriter() io.Writer {
return os.Stderr
}

37
vendor/github.com/docker/libcompose/logger/types.go generated vendored Normal file
View file

@ -0,0 +1,37 @@
package logger
import (
"io"
)
// Factory defines methods a factory should implement, to create a Logger
// based on the specified container, image or service name.
type Factory interface {
CreateContainerLogger(name string) Logger
CreateBuildLogger(name string) Logger
CreatePullLogger(name string) Logger
}
// Logger defines methods to implement for being a logger.
type Logger interface {
Out(bytes []byte)
Err(bytes []byte)
OutWriter() io.Writer
ErrWriter() io.Writer
}
// 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
}

View file

@ -0,0 +1,25 @@
package lookup
import (
"github.com/docker/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 string, config *config.ServiceConfig) []string {
result := []string{}
for _, lookup := range l.Lookups {
env := lookup.Lookup(key, config)
if len(env) == 1 {
result = env
}
}
return result
}

31
vendor/github.com/docker/libcompose/lookup/envfile.go generated vendored Normal file
View file

@ -0,0 +1,31 @@
package lookup
import (
"strings"
"github.com/docker/docker/runconfig/opts"
"github.com/docker/libcompose/config"
)
// 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 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{}
}

66
vendor/github.com/docker/libcompose/lookup/file.go generated vendored Normal file
View 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 filepath.Join(cwd, file)
}
}
// 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
}
// FileResourceLookup is a "bare" structure that implements the project.ResourceLookup interface
type FileResourceLookup 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 *FileResourceLookup) 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 *FileResourceLookup) ResolvePath(path, relativeTo string) string {
vs := strings.SplitN(path, ":", 2)
if len(vs) != 2 || filepath.IsAbs(vs[0]) {
return path
}
vs[0] = relativePath(vs[0], relativeTo)
return strings.Join(vs, ":")
}

View file

@ -0,0 +1,24 @@
package lookup
import (
"fmt"
"os"
"github.com/docker/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 string, config *config.ServiceConfig) []string {
ret := os.Getenv(key)
if ret == "" {
return []string{}
}
return []string{fmt.Sprintf("%s=%s", key, ret)}
}

1
vendor/github.com/docker/libcompose/package.go generated vendored Normal file
View file

@ -0,0 +1 @@
package libcompose

View file

@ -0,0 +1,13 @@
package project
import (
"golang.org/x/net/context"
)
// Container defines what a libcompose container provides.
type Container interface {
ID() string
Name() string
Port(ctx context.Context, port string) (string, error)
IsRunning(ctx context.Context) bool
}

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

@ -0,0 +1,141 @@
package project
import (
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"regexp"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/libcompose/config"
"github.com/docker/libcompose/logger"
)
var projectRegexp = regexp.MustCompile("[^a-zA-Z0-9_.-]")
// Context holds context meta information about a libcompose project, like
// the project name, the compose file, etc.
type Context struct {
ComposeFiles []string
ComposeBytes [][]byte
ProjectName string
isOpen bool
ServiceFactory ServiceFactory
NetworksFactory NetworksFactory
VolumesFactory VolumesFactory
EnvironmentLookup config.EnvironmentLookup
ResourceLookup config.ResourceLookup
LoggerFactory logger.Factory
IgnoreMissingConfig bool
Project *Project
}
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
}

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

@ -0,0 +1,139 @@
package project
import (
"golang.org/x/net/context"
"github.com/docker/libcompose/config"
"github.com/docker/libcompose/project/events"
"github.com/docker/libcompose/project/options"
)
// this ensures EmptyService implements Service
// useful since it's easy to forget adding new functions to EmptyService
var _ Service = (*EmptyService)(nil)
// EmptyService is a struct that implements Service but does nothing.
type EmptyService struct {
}
// Create implements Service.Create but does nothing.
func (e *EmptyService) Create(ctx context.Context, options options.Create) error {
return nil
}
// Build implements Service.Build but does nothing.
func (e *EmptyService) Build(ctx context.Context, buildOptions options.Build) error {
return nil
}
// Up implements Service.Up but does nothing.
func (e *EmptyService) Up(ctx context.Context, options options.Up) error {
return nil
}
// Start implements Service.Start but does nothing.
func (e *EmptyService) Start(ctx context.Context) error {
return nil
}
// Stop implements Service.Stop() but does nothing.
func (e *EmptyService) Stop(ctx context.Context, timeout int) error {
return nil
}
// Delete implements Service.Delete but does nothing.
func (e *EmptyService) Delete(ctx context.Context, options options.Delete) error {
return nil
}
// Restart implements Service.Restart but does nothing.
func (e *EmptyService) Restart(ctx context.Context, timeout int) error {
return nil
}
// Log implements Service.Log but does nothing.
func (e *EmptyService) Log(ctx context.Context, follow bool) error {
return nil
}
// Pull implements Service.Pull but does nothing.
func (e *EmptyService) Pull(ctx context.Context) error {
return nil
}
// Kill implements Service.Kill but does nothing.
func (e *EmptyService) Kill(ctx context.Context, signal string) error {
return nil
}
// Containers implements Service.Containers but does nothing.
func (e *EmptyService) Containers(ctx context.Context) ([]Container, error) {
return []Container{}, nil
}
// Scale implements Service.Scale but does nothing.
func (e *EmptyService) Scale(ctx context.Context, count int, timeout int) error {
return nil
}
// Info implements Service.Info but does nothing.
func (e *EmptyService) Info(ctx context.Context) (InfoSet, error) {
return InfoSet{}, nil
}
// Pause implements Service.Pause but does nothing.
func (e *EmptyService) Pause(ctx context.Context) error {
return nil
}
// Unpause implements Service.Pause but does nothing.
func (e *EmptyService) Unpause(ctx context.Context) error {
return nil
}
// Run implements Service.Run but does nothing.
func (e *EmptyService) Run(ctx context.Context, commandParts []string, options options.Run) (int, error) {
return 0, nil
}
// RemoveImage implements Service.RemoveImage but does nothing.
func (e *EmptyService) RemoveImage(ctx context.Context, imageType options.ImageType) error {
return nil
}
// Events implements Service.Events but does nothing.
func (e *EmptyService) Events(ctx context.Context, events chan events.ContainerEvent) error {
return nil
}
// DependentServices implements Service.DependentServices with empty slice.
func (e *EmptyService) DependentServices() []ServiceRelationship {
return []ServiceRelationship{}
}
// Config implements Service.Config with empty config.
func (e *EmptyService) Config() *config.ServiceConfig {
return &config.ServiceConfig{}
}
// Name implements Service.Name with empty name.
func (e *EmptyService) Name() string {
return ""
}
// this ensures EmptyNetworks implements Networks
var _ Networks = (*EmptyNetworks)(nil)
// EmptyNetworks is a struct that implements Networks but does nothing.
type EmptyNetworks struct {
}
// Initialize implements Networks.Initialize but does nothing.
func (e *EmptyNetworks) Initialize(ctx context.Context) error {
return nil
}
// Remove implements Networks.Remove but does nothing.
func (e *EmptyNetworks) Remove(ctx context.Context) error {
return nil
}

View file

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

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

@ -0,0 +1,48 @@
package project
import (
"bytes"
"io"
"text/tabwriter"
)
// InfoSet holds a list of Info.
type InfoSet []Info
// Info holds a list of InfoPart.
type Info map[string]string
func (infos InfoSet) String(columns []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, columns, true, info)
}
first = false
writeLine(tabwriter, columns, false, info)
}
tabwriter.Flush()
return buffer.String()
}
func writeLine(writer io.Writer, columns []string, key bool, info Info) {
first := true
for _, column := range columns {
if !first {
writer.Write([]byte{'\t'})
}
first = false
if key {
writer.Write([]byte(column))
} else {
writer.Write([]byte(info[column]))
}
}
writer.Write([]byte{'\n'})
}

View file

@ -0,0 +1,64 @@
package project
import (
"golang.org/x/net/context"
"github.com/docker/libcompose/config"
"github.com/docker/libcompose/project/events"
"github.com/docker/libcompose/project/options"
)
// APIProject defines the methods a libcompose project should implement.
type APIProject interface {
events.Notifier
events.Emitter
Build(ctx context.Context, options options.Build, sevice ...string) error
Config() (string, error)
Create(ctx context.Context, options options.Create, services ...string) error
Delete(ctx context.Context, options options.Delete, services ...string) error
Down(ctx context.Context, options options.Down, services ...string) error
Events(ctx context.Context, services ...string) (chan events.ContainerEvent, error)
Kill(ctx context.Context, signal string, services ...string) error
Log(ctx context.Context, follow bool, services ...string) error
Pause(ctx context.Context, services ...string) error
Ps(ctx context.Context, services ...string) (InfoSet, error)
// FIXME(vdemeester) we could use nat.Port instead ?
Port(ctx context.Context, index int, protocol, serviceName, privatePort string) (string, error)
Pull(ctx context.Context, services ...string) error
Restart(ctx context.Context, timeout int, services ...string) error
Run(ctx context.Context, serviceName string, commandParts []string, options options.Run) (int, error)
Scale(ctx context.Context, timeout int, servicesScale map[string]int) error
Start(ctx context.Context, services ...string) error
Stop(ctx context.Context, timeout int, services ...string) error
Unpause(ctx context.Context, services ...string) error
Up(ctx context.Context, options options.Up, services ...string) error
Parse() error
CreateService(name string) (Service, error)
AddConfig(name string, config *config.ServiceConfig) error
Load(bytes []byte) error
Containers(ctx context.Context, filter Filter, services ...string) ([]string, error)
GetServiceConfig(service string) (*config.ServiceConfig, bool)
}
// Filter holds filter element to filter containers
type Filter struct {
State State
}
// State defines the supported state you can filter on
type State string
// Definitions of filter states
const (
AnyState = State("")
Running = State("running")
Stopped = State("stopped")
)
// RuntimeProject defines runtime-specific methods for a libcompose implementation.
type RuntimeProject interface {
RemoveOrphans(ctx context.Context, projectName string, serviceConfigs *config.ServiceConfigs) error
}

View file

@ -0,0 +1,81 @@
package project
import (
"bytes"
"github.com/Sirupsen/logrus"
"github.com/docker/libcompose/project/events"
)
var (
infoEvents = map[events.EventType]bool{
events.ServiceDeleteStart: true,
events.ServiceDelete: true,
events.ServiceDownStart: true,
events.ServiceDown: true,
events.ServiceStopStart: true,
events.ServiceStop: true,
events.ServiceKillStart: true,
events.ServiceKill: true,
events.ServiceCreateStart: true,
events.ServiceCreate: true,
events.ServiceStartStart: true,
events.ServiceStart: true,
events.ServiceRestartStart: true,
events.ServiceRestart: true,
events.ServiceUpStart: true,
events.ServiceUp: true,
events.ServicePauseStart: true,
events.ServicePause: true,
events.ServiceUnpauseStart: true,
events.ServiceUnpause: true,
}
)
type defaultListener struct {
project *Project
listenChan chan 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())
}
}
}

19
vendor/github.com/docker/libcompose/project/network.go generated vendored Normal file
View file

@ -0,0 +1,19 @@
package project
import (
"golang.org/x/net/context"
"github.com/docker/libcompose/config"
)
// Networks defines the methods a libcompose network aggregate should define.
type Networks interface {
Initialize(ctx context.Context) error
Remove(ctx context.Context) error
}
// NetworksFactory is an interface factory to create Networks object for the specified
// configurations (service, networks, …)
type NetworksFactory interface {
Create(projectName string, networkConfigs map[string]*config.NetworkConfig, serviceConfigs *config.ServiceConfigs, networkEnabled bool) (Networks, error)
}

View file

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

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

@ -0,0 +1,559 @@
package project
import (
"errors"
"fmt"
"os"
"path"
"path/filepath"
"strings"
"golang.org/x/net/context"
log "github.com/Sirupsen/logrus"
"github.com/docker/libcompose/config"
"github.com/docker/libcompose/logger"
"github.com/docker/libcompose/lookup"
"github.com/docker/libcompose/project/events"
"github.com/docker/libcompose/utils"
"github.com/docker/libcompose/yaml"
)
// ComposeVersion is name of docker-compose.yml file syntax supported version
const ComposeVersion = "1.5.0"
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
ParseOptions *config.ParseOptions
runtime RuntimeProject
networks Networks
volumes Volumes
configVersion string
context *Context
reload []string
upCount int
listeners []chan<- events.Event
hasListeners bool
}
// NewProject creates a new project with the specified context.
func NewProject(context *Context, runtime RuntimeProject, parseOptions *config.ParseOptions) *Project {
p := &Project{
context: context,
runtime: runtime,
ParseOptions: parseOptions,
ServiceConfigs: config.NewServiceConfigs(),
VolumeConfigs: make(map[string]*config.VolumeConfig),
NetworkConfigs: make(map[string]*config.NetworkConfig),
}
if context.LoggerFactory == nil {
context.LoggerFactory = &logger.NullLogger{}
}
if context.ResourceLookup == nil {
context.ResourceLookup = &lookup.FileResourceLookup{}
}
if context.EnvironmentLookup == nil {
var envPath, absPath, cwd string
var err error
if len(context.ComposeFiles) > 0 {
absPath, err = filepath.Abs(context.ComposeFiles[0])
dir, _ := path.Split(absPath)
envPath = filepath.Join(dir, ".env")
} else {
cwd, err = os.Getwd()
envPath = filepath.Join(cwd, ".env")
}
if err != nil {
log.Errorf("Could not get the rooted path name to the current directory: %v", err)
return nil
}
context.EnvironmentLookup = &lookup.ComposableEnvLookup{
Lookups: []config.EnvironmentLookup{
&lookup.EnvfileLookup{
Path: envPath,
},
&lookup.OsEnvLookup{},
},
}
}
context.Project = p
p.listeners = []chan<- events.Event{NewDefaultListener(p)}
return p
}
// Parse populates project information based on its context. It sets up the name,
// the composefile and the composebytes (the composefile content).
func (p *Project) Parse() error {
err := p.context.open()
if err != nil {
return err
}
p.Name = p.context.ProjectName
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.GetServiceConfig(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 {
parsedEnv = append(parsedEnv, env)
continue
} else {
env = parts[0]
}
for _, value := range p.context.EnvironmentLookup.Lookup(env, &config) {
parsedEnv = append(parsedEnv, value)
}
}
config.Environment = parsedEnv
// check the environment for extra build Args that are set but not given a value in the compose file
for arg, value := range config.Build.Args {
if *value == "\x00" {
envValue := p.context.EnvironmentLookup.Lookup(arg, &config)
// depending on what we get back we do different things
switch l := len(envValue); l {
case 0:
delete(config.Build.Args, arg)
case 1:
parts := strings.SplitN(envValue[0], "=", 2)
config.Build.Args[parts[0]] = &parts[1]
default:
return nil, fmt.Errorf("tried to set Build Arg %#v to multi-value %#v", arg, envValue)
}
}
}
}
return p.context.ServiceFactory.Create(p, name, &config)
}
// AddConfig adds the specified service config for the specified name.
func (p *Project) AddConfig(name string, config *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 {
version, serviceConfigs, volumeConfigs, networkConfigs, err := config.Merge(p.ServiceConfigs, p.context.EnvironmentLookup, p.context.ResourceLookup, file, bytes, p.ParseOptions)
if err != nil {
log.Errorf("Could not parse config for project %s : %v", p.Name, err)
return err
}
p.configVersion = version
for name, config := range volumeConfigs {
err := p.AddVolumeConfig(name, config)
if err != nil {
return err
}
}
for name, config := range networkConfigs {
err := p.AddNetworkConfig(name, config)
if err != nil {
return err
}
}
for name, config := range serviceConfigs {
err := p.AddConfig(name, config)
if err != nil {
return err
}
}
// Update network configuration a little bit
p.handleNetworkConfig()
p.handleVolumeConfig()
if p.context.NetworksFactory != nil {
networks, err := p.context.NetworksFactory.Create(p.Name, p.NetworkConfigs, p.ServiceConfigs, p.isNetworkEnabled())
if err != nil {
return err
}
p.networks = networks
}
if p.context.VolumesFactory != nil {
volumes, err := p.context.VolumesFactory.Create(p.Name, p.VolumeConfigs, p.ServiceConfigs, p.isVolumeEnabled())
if err != nil {
return err
}
p.volumes = volumes
}
return nil
}
func (p *Project) handleNetworkConfig() {
if p.isNetworkEnabled() {
for _, serviceName := range p.ServiceConfigs.Keys() {
serviceConfig, _ := p.ServiceConfigs.Get(serviceName)
if serviceConfig.NetworkMode != "" {
continue
}
if serviceConfig.Networks == nil || len(serviceConfig.Networks.Networks) == 0 {
// Add default as network
serviceConfig.Networks = &yaml.Networks{
Networks: []*yaml.Network{
{
Name: "default",
RealName: fmt.Sprintf("%s_%s", p.Name, "default"),
},
},
}
p.AddNetworkConfig("default", &config.NetworkConfig{})
}
// Consolidate the name of the network
// FIXME(vdemeester) probably shouldn't be there, maybe move that to interface/factory
for _, network := range serviceConfig.Networks.Networks {
net, ok := p.NetworkConfigs[network.Name]
if ok && net != nil {
if net.External.External {
network.RealName = network.Name
if net.External.Name != "" {
network.RealName = net.External.Name
}
} else {
network.RealName = p.Name + "_" + network.Name
}
} else {
network.RealName = p.Name + "_" + network.Name
p.NetworkConfigs[network.Name] = &config.NetworkConfig{
External: yaml.External{External: false},
}
}
// Ignoring if we don't find the network, it will be catched later
}
}
}
}
func (p *Project) isNetworkEnabled() bool {
return p.configVersion == "2"
}
func (p *Project) handleVolumeConfig() {
if p.isVolumeEnabled() {
for _, serviceName := range p.ServiceConfigs.Keys() {
serviceConfig, _ := p.ServiceConfigs.Get(serviceName)
// Consolidate the name of the volume
// FIXME(vdemeester) probably shouldn't be there, maybe move that to interface/factory
if serviceConfig.Volumes == nil {
continue
}
for _, volume := range serviceConfig.Volumes.Volumes {
if !IsNamedVolume(volume.Source) {
continue
}
vol, ok := p.VolumeConfigs[volume.Source]
if !ok || vol == nil {
continue
}
if vol.External.External {
if vol.External.Name != "" {
volume.Source = vol.External.Name
}
} else {
volume.Source = p.Name + "_" + volume.Source
}
}
}
}
}
func (p *Project) isVolumeEnabled() bool {
return p.configVersion == "2"
}
// initialize sets up required element for project before any action (on project and service).
// This means it's not needed to be called on Config for example.
func (p *Project) initialize(ctx context.Context) error {
if p.networks != nil {
if err := p.networks.Initialize(ctx); err != nil {
return err
}
}
if p.volumes != nil {
if err := p.volumes.Initialize(ctx); 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
}
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.Debugf("Failed to find %s", dep.Target)
return fmt.Errorf("Service '%s' has a link to service '%s' which is undefined", wrapper.name, dep.Target)
}
if utils.Contains(history, dep.Target) {
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
}
}
// GetServiceConfig looks up a service config for a given service name, returning the ServiceConfig
// object and a bool flag indicating whether it was found
func (p *Project) GetServiceConfig(name string) (*config.ServiceConfig, bool) {
return p.ServiceConfigs.Get(name)
}
// IsNamedVolume returns whether the specified volume (string) is a named volume or not.
func IsNamedVolume(volume string) bool {
return !strings.HasPrefix(volume, ".") && !strings.HasPrefix(volume, "/") && !strings.HasPrefix(volume, "~")
}

View file

@ -0,0 +1,17 @@
package project
import (
"golang.org/x/net/context"
"github.com/docker/libcompose/project/events"
"github.com/docker/libcompose/project/options"
)
// Build builds the specified services (like docker build).
func (p *Project) Build(ctx context.Context, buildOptions options.Build, services ...string) error {
return p.perform(events.ProjectBuildStart, events.ProjectBuildDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(wrappers, events.ServiceBuildStart, events.ServiceBuild, func(service Service) error {
return service.Build(ctx, buildOptions)
})
}), nil)
}

View file

@ -0,0 +1,27 @@
package project
import (
"github.com/docker/libcompose/config"
"gopkg.in/yaml.v2"
)
// ExportedConfig holds config attribute that will be exported
type ExportedConfig struct {
Version string `yaml:"version,omitempty"`
Services map[string]*config.ServiceConfig `yaml:"services"`
Volumes map[string]*config.VolumeConfig `yaml:"volumes"`
Networks map[string]*config.NetworkConfig `yaml:"networks"`
}
// Config validates and print the compose file.
func (p *Project) Config() (string, error) {
cfg := ExportedConfig{
Version: "2.0",
Services: p.ServiceConfigs.All(),
Volumes: p.VolumeConfigs,
Networks: p.NetworkConfigs,
}
bytes, err := yaml.Marshal(cfg)
return string(bytes), err
}

View file

@ -0,0 +1,49 @@
package project
import (
"fmt"
"golang.org/x/net/context"
"github.com/docker/libcompose/project/events"
)
// Containers lists the containers for the specified services. Can be filter using
// the Filter struct.
func (p *Project) Containers(ctx context.Context, filter Filter, services ...string) ([]string, error) {
containers := []string{}
err := p.forEach(services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(nil, events.NoEvent, events.NoEvent, func(service Service) error {
serviceContainers, innerErr := service.Containers(ctx)
if innerErr != nil {
return innerErr
}
for _, container := range serviceContainers {
running := container.IsRunning(ctx)
switch filter.State {
case Running:
if !running {
continue
}
case Stopped:
if running {
continue
}
case AnyState:
// Don't do a thing
default:
// Invalid state filter
return fmt.Errorf("Invalid container filter: %s", filter.State)
}
containerID := container.ID()
containers = append(containers, containerID)
}
return nil
})
}), nil)
if err != nil {
return nil, err
}
return containers, nil
}

View file

@ -0,0 +1,25 @@
package project
import (
"fmt"
"golang.org/x/net/context"
"github.com/docker/libcompose/project/events"
"github.com/docker/libcompose/project/options"
)
// Create creates the specified services (like docker create).
func (p *Project) Create(ctx context.Context, options options.Create, services ...string) error {
if options.NoRecreate && options.ForceRecreate {
return fmt.Errorf("no-recreate and force-recreate cannot be combined")
}
if err := p.initialize(ctx); err != nil {
return err
}
return p.perform(events.ProjectCreateStart, events.ProjectCreateDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(wrappers, events.ServiceCreateStart, events.ServiceCreate, func(service Service) error {
return service.Create(ctx, options)
})
}), nil)
}

View file

@ -0,0 +1,17 @@
package project
import (
"golang.org/x/net/context"
"github.com/docker/libcompose/project/events"
"github.com/docker/libcompose/project/options"
)
// Delete removes the specified services (like docker rm).
func (p *Project) Delete(ctx context.Context, options options.Delete, services ...string) error {
return p.perform(events.ProjectDeleteStart, events.ProjectDeleteDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(nil, events.ServiceDeleteStart, events.ServiceDelete, func(service Service) error {
return service.Delete(ctx, options)
})
}), nil)
}

View file

@ -0,0 +1,56 @@
package project
import (
"fmt"
"golang.org/x/net/context"
"github.com/docker/libcompose/project/events"
"github.com/docker/libcompose/project/options"
)
// Down stops the specified services and clean related containers (like docker stop + docker rm).
func (p *Project) Down(ctx context.Context, opts options.Down, services ...string) error {
if !opts.RemoveImages.Valid() {
return fmt.Errorf("--rmi flag must be local, all or empty")
}
if err := p.Stop(ctx, 10, services...); err != nil {
return err
}
if opts.RemoveOrphans {
if err := p.runtime.RemoveOrphans(ctx, p.Name, p.ServiceConfigs); err != nil {
return err
}
}
if err := p.Delete(ctx, options.Delete{
RemoveVolume: opts.RemoveVolume,
}, services...); err != nil {
return err
}
networks, err := p.context.NetworksFactory.Create(p.Name, p.NetworkConfigs, p.ServiceConfigs, p.isNetworkEnabled())
if err != nil {
return err
}
if err := networks.Remove(ctx); err != nil {
return err
}
if opts.RemoveVolume {
volumes, err := p.context.VolumesFactory.Create(p.Name, p.VolumeConfigs, p.ServiceConfigs, p.isVolumeEnabled())
if err != nil {
return err
}
if err := volumes.Remove(ctx); 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(ctx, opts.RemoveImages)
})
}), func(service Service) error {
return service.Create(ctx, options.Create{})
})
}

View file

@ -0,0 +1,24 @@
package project
import (
"golang.org/x/net/context"
"github.com/docker/libcompose/project/events"
)
// Events listen for real time events from containers (of the project).
func (p *Project) Events(ctx context.Context, services ...string) (chan events.ContainerEvent, error) {
events := make(chan events.ContainerEvent)
if len(services) == 0 {
services = p.ServiceConfigs.Keys()
}
// FIXME(vdemeester) handle errors (chan) here
for _, service := range services {
s, err := p.CreateService(service)
if err != nil {
return nil, err
}
go s.Events(ctx, events)
}
return events, nil
}

View file

@ -0,0 +1,16 @@
package project
import (
"golang.org/x/net/context"
"github.com/docker/libcompose/project/events"
)
// Kill kills the specified services (like docker kill).
func (p *Project) Kill(ctx context.Context, signal string, services ...string) error {
return p.perform(events.ProjectKillStart, events.ProjectKillDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(nil, events.ServiceKillStart, events.ServiceKill, func(service Service) error {
return service.Kill(ctx, signal)
})
}), nil)
}

View file

@ -0,0 +1,16 @@
package project
import (
"golang.org/x/net/context"
"github.com/docker/libcompose/project/events"
)
// Log aggregates and prints out the logs for the specified services.
func (p *Project) Log(ctx context.Context, follow bool, services ...string) error {
return p.forEach(services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(nil, events.NoEvent, events.NoEvent, func(service Service) error {
return service.Log(ctx, follow)
})
}), nil)
}

View file

@ -0,0 +1,16 @@
package project
import (
"golang.org/x/net/context"
"github.com/docker/libcompose/project/events"
)
// Pause pauses the specified services containers (like docker pause).
func (p *Project) Pause(ctx context.Context, services ...string) error {
return p.perform(events.ProjectPauseStart, events.ProjectPauseDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(nil, events.ServicePauseStart, events.ServicePause, func(service Service) error {
return service.Pause(ctx)
})
}), nil)
}

View file

@ -0,0 +1,26 @@
package project
import (
"fmt"
"golang.org/x/net/context"
)
// Port returns the public port for a port binding of the specified service.
func (p *Project) Port(ctx context.Context, index int, protocol, serviceName, privatePort string) (string, error) {
service, err := p.CreateService(serviceName)
if err != nil {
return "", err
}
containers, err := service.Containers(ctx)
if err != nil {
return "", err
}
if index < 1 || index > len(containers) {
return "", fmt.Errorf("Invalid index %d", index)
}
return containers[index-1].Port(ctx, fmt.Sprintf("%s/%s", privatePort, protocol))
}

View file

@ -0,0 +1,28 @@
package project
import "golang.org/x/net/context"
// Ps list containers for the specified services.
func (p *Project) Ps(ctx context.Context, services ...string) (InfoSet, error) {
allInfo := InfoSet{}
if len(services) == 0 {
services = p.ServiceConfigs.Keys()
}
for _, name := range services {
service, err := p.CreateService(name)
if err != nil {
return nil, err
}
info, err := service.Info(ctx)
if err != nil {
return nil, err
}
allInfo = append(allInfo, info...)
}
return allInfo, nil
}

View file

@ -0,0 +1,16 @@
package project
import (
"golang.org/x/net/context"
"github.com/docker/libcompose/project/events"
)
// Pull pulls the specified services (like docker pull).
func (p *Project) Pull(ctx context.Context, services ...string) error {
return p.forEach(services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(nil, events.ServicePullStart, events.ServicePull, func(service Service) error {
return service.Pull(ctx)
})
}), nil)
}

View file

@ -0,0 +1,16 @@
package project
import (
"golang.org/x/net/context"
"github.com/docker/libcompose/project/events"
)
// Restart restarts the specified services (like docker restart).
func (p *Project) Restart(ctx context.Context, timeout int, services ...string) error {
return p.perform(events.ProjectRestartStart, events.ProjectRestartDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(wrappers, events.ServiceRestartStart, events.ServiceRestart, func(service Service) error {
return service.Restart(ctx, timeout)
})
}), nil)
}

View file

@ -0,0 +1,35 @@
package project
import (
"fmt"
"golang.org/x/net/context"
"github.com/docker/libcompose/project/events"
"github.com/docker/libcompose/project/options"
)
// Run executes a one off command (like `docker run image command`).
func (p *Project) Run(ctx context.Context, serviceName string, commandParts []string, opts options.Run) (int, error) {
if !p.ServiceConfigs.Has(serviceName) {
return 1, fmt.Errorf("%s is not defined in the template", serviceName)
}
if err := p.initialize(ctx); err != nil {
return 1, err
}
var exitCode int
err := p.forEach([]string{}, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(wrappers, events.ServiceRunStart, events.ServiceRun, func(service Service) error {
if service.Name() == serviceName {
code, err := service.Run(ctx, commandParts, opts)
exitCode = code
return err
}
return nil
})
}), func(service Service) error {
return service.Create(ctx, options.Create{})
})
return exitCode, err
}

View file

@ -0,0 +1,40 @@
package project
import (
"fmt"
"golang.org/x/net/context"
log "github.com/Sirupsen/logrus"
)
// Scale scales the specified services.
func (p *Project) Scale(ctx context.Context, timeout int, servicesScale map[string]int) error {
// This code is a bit verbose but I wanted to parse everything up front
order := make([]string, 0, 0)
services := make(map[string]Service)
for name := range servicesScale {
if !p.ServiceConfigs.Has(name) {
return fmt.Errorf("%s is not defined in the template", name)
}
service, err := p.CreateService(name)
if err != nil {
return fmt.Errorf("Failed to lookup service: %s: %v", service, err)
}
order = append(order, name)
services[name] = service
}
for _, name := range order {
scale := servicesScale[name]
log.Infof("Setting scale %s=%d...", name, scale)
err := services[name].Scale(ctx, scale, timeout)
if err != nil {
return fmt.Errorf("Failed to set the scale %s=%d: %v", name, scale, err)
}
}
return nil
}

View file

@ -0,0 +1,16 @@
package project
import (
"golang.org/x/net/context"
"github.com/docker/libcompose/project/events"
)
// Start starts the specified services (like docker start).
func (p *Project) Start(ctx context.Context, services ...string) error {
return p.perform(events.ProjectStartStart, events.ProjectStartDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(wrappers, events.ServiceStartStart, events.ServiceStart, func(service Service) error {
return service.Start(ctx)
})
}), nil)
}

View file

@ -0,0 +1,16 @@
package project
import (
"golang.org/x/net/context"
"github.com/docker/libcompose/project/events"
)
// Stop stops the specified services (like docker stop).
func (p *Project) Stop(ctx context.Context, timeout int, services ...string) error {
return p.perform(events.ProjectStopStart, events.ProjectStopDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(nil, events.ServiceStopStart, events.ServiceStop, func(service Service) error {
return service.Stop(ctx, timeout)
})
}), nil)
}

View file

@ -0,0 +1,16 @@
package project
import (
"golang.org/x/net/context"
"github.com/docker/libcompose/project/events"
)
// Unpause pauses the specified services containers (like docker pause).
func (p *Project) Unpause(ctx context.Context, services ...string) error {
return p.perform(events.ProjectUnpauseStart, events.ProjectUnpauseDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(nil, events.ServiceUnpauseStart, events.ServiceUnpause, func(service Service) error {
return service.Unpause(ctx)
})
}), nil)
}

View file

@ -0,0 +1,22 @@
package project
import (
"golang.org/x/net/context"
"github.com/docker/libcompose/project/events"
"github.com/docker/libcompose/project/options"
)
// Up creates and starts the specified services (kinda like docker run).
func (p *Project) Up(ctx context.Context, options options.Up, services ...string) error {
if err := p.initialize(ctx); err != nil {
return err
}
return p.perform(events.ProjectUpStart, events.ProjectUpDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(wrappers, events.ServiceUpStart, events.ServiceUp, func(service Service) error {
return service.Up(ctx, options)
})
}), func(service Service) error {
return service.Create(ctx, options.Create)
})
}

View file

@ -0,0 +1,115 @@
package project
import (
"sync"
log "github.com/Sirupsen/logrus"
"github.com/docker/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
}

97
vendor/github.com/docker/libcompose/project/service.go generated vendored Normal file
View file

@ -0,0 +1,97 @@
package project
import (
"errors"
"golang.org/x/net/context"
"github.com/docker/libcompose/config"
"github.com/docker/libcompose/project/events"
"github.com/docker/libcompose/project/options"
)
// Service defines what a libcompose service provides.
type Service interface {
Build(ctx context.Context, buildOptions options.Build) error
Create(ctx context.Context, options options.Create) error
Delete(ctx context.Context, options options.Delete) error
Events(ctx context.Context, messages chan events.ContainerEvent) error
Info(ctx context.Context) (InfoSet, error)
Log(ctx context.Context, follow bool) error
Kill(ctx context.Context, signal string) error
Pause(ctx context.Context) error
Pull(ctx context.Context) error
Restart(ctx context.Context, timeout int) error
Run(ctx context.Context, commandParts []string, options options.Run) (int, error)
Scale(ctx context.Context, count int, timeout int) error
Start(ctx context.Context) error
Stop(ctx context.Context, timeout int) error
Unpause(ctx context.Context) error
Up(ctx context.Context, options options.Up) error
RemoveImage(ctx context.Context, imageType options.ImageType) error
Containers(ctx context.Context) ([]Container, error)
DependentServices() []ServiceRelationship
Config() *config.ServiceConfig
Name() string
}
// ServiceState holds the state of a service.
type ServiceState string
// State definitions
var (
StateExecuted = ServiceState("executed")
StateUnknown = ServiceState("unknown")
)
// Error definitions
var (
ErrRestart = errors.New("Restart execution")
ErrUnsupported = errors.New("UnsupportedOperation")
)
// ServiceFactory is an interface factory to create Service object for the specified
// project, with the specified name and service configuration.
type ServiceFactory interface {
Create(project *Project, name string, serviceConfig *config.ServiceConfig) (Service, error)
}
// ServiceRelationshipType defines the type of service relationship.
type ServiceRelationshipType string
// RelTypeLink means the services are linked (docker links).
const RelTypeLink = ServiceRelationshipType("")
// RelTypeNetNamespace means the services share the same network namespace.
const RelTypeNetNamespace = ServiceRelationshipType("netns")
// RelTypeIpcNamespace means the service share the same ipc namespace.
const RelTypeIpcNamespace = ServiceRelationshipType("ipc")
// RelTypeVolumesFrom means the services share some volumes.
const RelTypeVolumesFrom = ServiceRelationshipType("volumesFrom")
// RelTypeDependsOn means the dependency was explicitly set using 'depends_on'.
const RelTypeDependsOn = ServiceRelationshipType("dependsOn")
// RelTypeNetworkMode means the services depends on another service on networkMode
const RelTypeNetworkMode = ServiceRelationshipType("networkMode")
// ServiceRelationship holds the relationship information between two services.
type ServiceRelationship struct {
Target, Alias string
Type ServiceRelationshipType
Optional bool
}
// NewServiceRelationship creates a new Relationship based on the specified alias
// and relationship type.
func NewServiceRelationship(nameAlias string, relType ServiceRelationshipType) ServiceRelationship {
name, alias := NameAlias(nameAlias)
return ServiceRelationship{
Target: name,
Alias: alias,
Type: relType,
}
}

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

@ -0,0 +1,47 @@
package project
import (
"strings"
)
// 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))
}
for _, dependsOn := range config.DependsOn {
result = append(result, NewServiceRelationship(dependsOn, RelTypeDependsOn))
}
if config.NetworkMode != "" {
if strings.HasPrefix(config.NetworkMode, "service:") {
serviceName := config.NetworkMode[8:]
result = append(result, NewServiceRelationship(serviceName, RelTypeNetworkMode))
}
}
return result
}
// 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]
}

19
vendor/github.com/docker/libcompose/project/volume.go generated vendored Normal file
View file

@ -0,0 +1,19 @@
package project
import (
"golang.org/x/net/context"
"github.com/docker/libcompose/config"
)
// Volumes defines the methods a libcompose volume aggregate should define.
type Volumes interface {
Initialize(ctx context.Context) error
Remove(ctx context.Context) error
}
// VolumesFactory is an interface factory to create Volumes object for the specified
// configurations (service, volumes, …)
type VolumesFactory interface {
Create(projectName string, volumeConfigs map[string]*config.VolumeConfig, serviceConfigs *config.ServiceConfigs, volumeEnabled bool) (Volumes, error)
}

178
vendor/github.com/docker/libcompose/utils/util.go generated vendored Normal file
View file

@ -0,0 +1,178 @@
package utils
import (
"encoding/json"
"sync"
"time"
"github.com/Sirupsen/logrus"
"gopkg.in/yaml.v2"
)
// 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
}
// ConvertKeysToStrings converts map[interface{}] to map[string] recursively
func ConvertKeysToStrings(item interface{}) interface{} {
switch typedDatas := item.(type) {
case map[string]interface{}:
for key, value := range typedDatas {
typedDatas[key] = ConvertKeysToStrings(value)
}
return typedDatas
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{}:
for i, value := range typedDatas {
typedDatas[i] = ConvertKeysToStrings(value)
}
return typedDatas
default:
return item
}
}
// DurationStrToSecondsInt converts duration string to *int in seconds
func DurationStrToSecondsInt(s string) *int {
if s == "" {
return nil
}
duration, err := time.ParseDuration(s)
if err != nil {
logrus.Errorf("Failed to parse duration:%v", s)
return nil
}
r := (int)(duration.Seconds())
return &r
}

20
vendor/github.com/docker/libcompose/version/version.go generated vendored Normal file
View file

@ -0,0 +1,20 @@
package version
var (
// VERSION should be updated by hand at each release
VERSION = "0.4.0"
// GITCOMMIT will be overwritten automatically by the build system
GITCOMMIT = "HEAD"
// BUILDTIME will be overwritten automatically by the build system
BUILDTIME = ""
// SHOWWARNING might be overwritten by the build system to not show the warning
SHOWWARNING = "true"
)
// ShowWarning returns wether the warning should be printed or not
func ShowWarning() bool {
return SHOWWARNING != "false"
}

117
vendor/github.com/docker/libcompose/yaml/build.go generated vendored Normal file
View file

@ -0,0 +1,117 @@
package yaml
import (
"errors"
"fmt"
"strconv"
"strings"
)
// Build represents a build element in compose file.
// It can take multiple form in the compose file, hence this special type
type Build struct {
Context string
Dockerfile string
Args map[string]*string
}
// MarshalYAML implements the Marshaller interface.
func (b Build) MarshalYAML() (interface{}, error) {
m := map[string]interface{}{}
if b.Context != "" {
m["context"] = b.Context
}
if b.Dockerfile != "" {
m["dockerfile"] = b.Dockerfile
}
if len(b.Args) > 0 {
m["args"] = b.Args
}
return m, nil
}
// UnmarshalYAML implements the Unmarshaller interface.
func (b *Build) UnmarshalYAML(unmarshal func(interface{}) error) error {
var stringType string
if err := unmarshal(&stringType); err == nil {
b.Context = stringType
return nil
}
var mapType map[interface{}]interface{}
if err := unmarshal(&mapType); err == nil {
for mapKey, mapValue := range mapType {
switch mapKey {
case "context":
b.Context = mapValue.(string)
case "dockerfile":
b.Dockerfile = mapValue.(string)
case "args":
args, err := handleBuildArgs(mapValue)
if err != nil {
return err
}
b.Args = args
default:
// Ignore unknown keys
continue
}
}
return nil
}
return errors.New("Failed to unmarshal Build")
}
func handleBuildArgs(value interface{}) (map[string]*string, error) {
var args map[string]*string
switch v := value.(type) {
case map[interface{}]interface{}:
return handleBuildArgMap(v)
case []interface{}:
return handleBuildArgSlice(v)
default:
return args, fmt.Errorf("Failed to unmarshal Build args: %#v", value)
}
}
func handleBuildArgSlice(s []interface{}) (map[string]*string, error) {
var args = map[string]*string{}
for _, arg := range s {
// check if a value is provided
switch v := strings.SplitN(arg.(string), "=", 2); len(v) {
case 1:
// if we have not specified a a value for this build arg, we assign it an ascii null value and query the environment
// later when we build the service
str := "\x00"
args[v[0]] = &str
case 2:
// if we do have a value provided, we use it
args[v[0]] = &v[1]
}
}
return args, nil
}
func handleBuildArgMap(m map[interface{}]interface{}) (map[string]*string, error) {
args := map[string]*string{}
for mapKey, mapValue := range m {
var argValue string
name, ok := mapKey.(string)
if !ok {
return args, fmt.Errorf("Cannot unmarshal '%v' to type %T into a string value", name, name)
}
switch a := mapValue.(type) {
case string:
argValue = a
case int:
argValue = strconv.Itoa(a)
case int64:
argValue = strconv.Itoa(int(a))
default:
return args, fmt.Errorf("Cannot unmarshal '%v' to type %T into a string value", mapValue, mapValue)
}
args[name] = &argValue
}
return args, nil
}

42
vendor/github.com/docker/libcompose/yaml/command.go generated vendored Normal file
View file

@ -0,0 +1,42 @@
package yaml
import (
"errors"
"fmt"
"github.com/docker/docker/api/types/strslice"
"github.com/flynn/go-shlex"
)
// 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(unmarshal func(interface{}) error) error {
var stringType string
if err := unmarshal(&stringType); err == nil {
parts, err := shlex.Split(stringType)
if err != nil {
return err
}
*s = parts
return nil
}
var sliceType []interface{}
if err := unmarshal(&sliceType); err == nil {
parts, err := toStrings(sliceType)
if err != nil {
return err
}
*s = parts
return nil
}
var interfaceType interface{}
if err := unmarshal(&interfaceType); err == nil {
fmt.Println(interfaceType)
}
return errors.New("Failed to unmarshal Command")
}

37
vendor/github.com/docker/libcompose/yaml/external.go generated vendored Normal file
View file

@ -0,0 +1,37 @@
package yaml
// External represents an external network entry in compose file.
// It can be a boolean (true|false) or have a name
type External struct {
External bool
Name string
}
// MarshalYAML implements the Marshaller interface.
func (n External) MarshalYAML() (interface{}, error) {
if n.Name == "" {
return n.External, nil
}
return map[string]interface{}{
"name": n.Name,
}, nil
}
// UnmarshalYAML implements the Unmarshaller interface.
func (n *External) UnmarshalYAML(unmarshal func(interface{}) error) error {
if err := unmarshal(&n.External); err == nil {
return nil
}
var dummyExternal struct {
Name string
}
err := unmarshal(&dummyExternal)
if err != nil {
return err
}
n.Name = dummyExternal.Name
n.External = true
return nil
}

139
vendor/github.com/docker/libcompose/yaml/network.go generated vendored Normal file
View file

@ -0,0 +1,139 @@
package yaml
import (
"errors"
"fmt"
"sort"
"strings"
)
// Networks represents a list of service networks in compose file.
// It has several representation, hence this specific struct.
type Networks struct {
Networks []*Network
}
// Network represents a service network in compose file.
type Network struct {
Name string `yaml:"-"`
RealName string `yaml:"-"`
Aliases []string `yaml:"aliases,omitempty"`
IPv4Address string `yaml:"ipv4_address,omitempty"`
IPv6Address string `yaml:"ipv6_address,omitempty"`
}
// Generate a hash string to detect service network config changes
func (n *Networks) HashString() string {
if n == nil {
return ""
}
result := []string{}
for _, net := range n.Networks {
result = append(result, net.HashString())
}
sort.Strings(result)
return strings.Join(result, ",")
}
// Generate a hash string to detect service network config changes
func (n *Network) HashString() string {
if n == nil {
return ""
}
result := []string{}
result = append(result, n.Name)
result = append(result, n.RealName)
sort.Strings(n.Aliases)
result = append(result, strings.Join(n.Aliases, ","))
result = append(result, n.IPv4Address)
result = append(result, n.IPv6Address)
sort.Strings(result)
return strings.Join(result, ",")
}
// MarshalYAML implements the Marshaller interface.
func (n Networks) MarshalYAML() (interface{}, error) {
m := map[string]*Network{}
for _, network := range n.Networks {
m[network.Name] = network
}
return m, nil
}
// UnmarshalYAML implements the Unmarshaller interface.
func (n *Networks) UnmarshalYAML(unmarshal func(interface{}) error) error {
var sliceType []interface{}
if err := unmarshal(&sliceType); err == nil {
n.Networks = []*Network{}
for _, network := range sliceType {
name, ok := network.(string)
if !ok {
return fmt.Errorf("Cannot unmarshal '%v' to type %T into a string value", name, name)
}
n.Networks = append(n.Networks, &Network{
Name: name,
})
}
return nil
}
var mapType map[interface{}]interface{}
if err := unmarshal(&mapType); err == nil {
n.Networks = []*Network{}
for mapKey, mapValue := range mapType {
name, ok := mapKey.(string)
if !ok {
return fmt.Errorf("Cannot unmarshal '%v' to type %T into a string value", name, name)
}
network, err := handleNetwork(name, mapValue)
if err != nil {
return err
}
n.Networks = append(n.Networks, network)
}
return nil
}
return errors.New("Failed to unmarshal Networks")
}
func handleNetwork(name string, value interface{}) (*Network, error) {
if value == nil {
return &Network{
Name: name,
}, nil
}
switch v := value.(type) {
case map[interface{}]interface{}:
network := &Network{
Name: name,
}
for mapKey, mapValue := range v {
name, ok := mapKey.(string)
if !ok {
return &Network{}, fmt.Errorf("Cannot unmarshal '%v' to type %T into a string value", name, name)
}
switch name {
case "aliases":
aliases, ok := mapValue.([]interface{})
if !ok {
return &Network{}, fmt.Errorf("Cannot unmarshal '%v' to type %T into a string value", aliases, aliases)
}
network.Aliases = []string{}
for _, alias := range aliases {
network.Aliases = append(network.Aliases, alias.(string))
}
case "ipv4_address":
network.IPv4Address = mapValue.(string)
case "ipv6_address":
network.IPv6Address = mapValue.(string)
default:
// Ignorer unknown keys ?
continue
}
}
return network, nil
default:
return &Network{}, fmt.Errorf("Failed to unmarshal Network: %#v", value)
}
}

257
vendor/github.com/docker/libcompose/yaml/types_yaml.go generated vendored Normal file
View file

@ -0,0 +1,257 @@
package yaml
import (
"errors"
"fmt"
"strconv"
"strings"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/go-units"
)
// StringorInt represents a string or an integer.
type StringorInt int64
// UnmarshalYAML implements the Unmarshaller interface.
func (s *StringorInt) UnmarshalYAML(unmarshal func(interface{}) error) error {
var intType int64
if err := unmarshal(&intType); err == nil {
*s = StringorInt(intType)
return nil
}
var stringType string
if err := unmarshal(&stringType); err == nil {
intType, err := strconv.ParseInt(stringType, 10, 64)
if err != nil {
return err
}
*s = StringorInt(intType)
return nil
}
return errors.New("Failed to unmarshal StringorInt")
}
// MemStringorInt represents a string or an integer
// the String supports notations like 10m for then Megabyte of memory
type MemStringorInt int64
// UnmarshalYAML implements the Unmarshaller interface.
func (s *MemStringorInt) UnmarshalYAML(unmarshal func(interface{}) error) error {
var intType int64
if err := unmarshal(&intType); err == nil {
*s = MemStringorInt(intType)
return nil
}
var stringType string
if err := unmarshal(&stringType); err == nil {
intType, err := units.RAMInBytes(stringType)
if err != nil {
return err
}
*s = MemStringorInt(intType)
return nil
}
return errors.New("Failed to unmarshal MemStringorInt")
}
// Stringorslice represents
// Using engine-api Strslice and augment it with YAML marshalling stuff. a string or an array of strings.
type Stringorslice strslice.StrSlice
// UnmarshalYAML implements the Unmarshaller interface.
func (s *Stringorslice) UnmarshalYAML(unmarshal func(interface{}) error) error {
var stringType string
if err := unmarshal(&stringType); err == nil {
*s = []string{stringType}
return nil
}
var sliceType []interface{}
if err := unmarshal(&sliceType); err == nil {
parts, err := toStrings(sliceType)
if err != nil {
return err
}
*s = parts
return nil
}
return errors.New("Failed to unmarshal Stringorslice")
}
// SliceorMap represents a slice or a map of strings.
type SliceorMap map[string]string
// UnmarshalYAML implements the Unmarshaller interface.
func (s *SliceorMap) UnmarshalYAML(unmarshal func(interface{}) error) error {
var sliceType []interface{}
if err := unmarshal(&sliceType); err == nil {
parts := map[string]string{}
for _, s := range sliceType {
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
return nil
}
var mapType map[interface{}]interface{}
if err := unmarshal(&mapType); err == nil {
parts := map[string]string{}
for k, v := range mapType {
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
return nil
}
return errors.New("Failed to unmarshal SliceorMap")
}
// 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(unmarshal func(interface{}) error) error {
parts, err := unmarshalToStringOrSepMapParts(unmarshal, "=")
if err != nil {
return err
}
*s = parts
return nil
}
// ToMap returns the list of string as a map splitting using = the key=value
func (s *MaporEqualSlice) ToMap() map[string]string {
return toMap(*s, "=")
}
// 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(unmarshal func(interface{}) error) error {
parts, err := unmarshalToStringOrSepMapParts(unmarshal, ":")
if err != nil {
return err
}
*s = parts
return nil
}
// ToMap returns the list of string as a map splitting using = the key=value
func (s *MaporColonSlice) ToMap() map[string]string {
return toMap(*s, ":")
}
// 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(unmarshal func(interface{}) error) error {
parts, err := unmarshalToStringOrSepMapParts(unmarshal, " ")
if err != nil {
return err
}
*s = parts
return nil
}
// ToMap returns the list of string as a map splitting using = the key=value
func (s *MaporSpaceSlice) ToMap() map[string]string {
return toMap(*s, " ")
}
func unmarshalToStringOrSepMapParts(unmarshal func(interface{}) error, key string) ([]string, error) {
var sliceType []interface{}
if err := unmarshal(&sliceType); err == nil {
return toStrings(sliceType)
}
var mapType map[interface{}]interface{}
if err := unmarshal(&mapType); err == nil {
return toSepMapParts(mapType, key)
}
return nil, errors.New("Failed to unmarshal MaporSlice")
}
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.(int); ok {
parts = append(parts, sk+sep+strconv.Itoa(sv))
} else if sv, ok := v.(int64); ok {
parts = append(parts, sk+sep+strconv.FormatInt(sv, 10))
} else if sv, ok := v.(float64); ok {
parts = append(parts, sk+sep+strconv.FormatFloat(sv, 'f', -1, 64))
} else if v == nil {
parts = append(parts, sk)
} 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
}
func toMap(s []string, sep string) map[string]string {
m := map[string]string{}
for _, v := range s {
// Return everything past first sep
values := strings.Split(v, sep)
m[values[0]] = strings.SplitN(v, sep, 2)[1]
}
return m
}

108
vendor/github.com/docker/libcompose/yaml/ulimit.go generated vendored Normal file
View file

@ -0,0 +1,108 @@
package yaml
import (
"errors"
"fmt"
"sort"
)
// 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() (interface{}, 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(unmarshal func(interface{}) error) error {
ulimits := make(map[string]Ulimit)
var mapType map[interface{}]interface{}
if err := unmarshal(&mapType); err == nil {
for mapKey, mapValue := range mapType {
name, ok := mapKey.(string)
if !ok {
return fmt.Errorf("Cannot unmarshal '%v' to type %T into a string value", name, name)
}
var soft, hard int64
switch mv := mapValue.(type) {
case int:
soft = int64(mv)
hard = int64(mv)
case map[interface{}]interface{}:
if len(mv) != 2 {
return fmt.Errorf("Failed to unmarshal Ulimit: %#v", mapValue)
}
for mkey, mvalue := range mv {
switch mkey {
case "soft":
soft = int64(mvalue.(int))
case "hard":
hard = int64(mvalue.(int))
default:
// FIXME(vdemeester) Should we ignore or fail ?
continue
}
}
default:
return fmt.Errorf("Failed to unmarshal Ulimit: %v, %T", mapValue, mapValue)
}
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])
}
return nil
}
return errors.New("Failed to unmarshal Ulimit")
}
// 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() (interface{}, error) {
if u.Soft == u.Hard {
return u.Soft, nil
}
return u.ulimitValues, nil
}
// 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,
},
}
}

97
vendor/github.com/docker/libcompose/yaml/volume.go generated vendored Normal file
View file

@ -0,0 +1,97 @@
package yaml
import (
"errors"
"fmt"
"sort"
"strings"
)
// Volumes represents a list of service volumes in compose file.
// It has several representation, hence this specific struct.
type Volumes struct {
Volumes []*Volume
}
// Volume represent a service volume
type Volume struct {
Source string `yaml:"-"`
Destination string `yaml:"-"`
AccessMode string `yaml:"-"`
}
// Generate a hash string to detect service volume config changes
func (v *Volumes) HashString() string {
if v == nil {
return ""
}
result := []string{}
for _, vol := range v.Volumes {
result = append(result, vol.String())
}
sort.Strings(result)
return strings.Join(result, ",")
}
// String implements the Stringer interface.
func (v *Volume) String() string {
var paths []string
if v.Source != "" {
paths = []string{v.Source, v.Destination}
} else {
paths = []string{v.Destination}
}
if v.AccessMode != "" {
paths = append(paths, v.AccessMode)
}
return strings.Join(paths, ":")
}
// MarshalYAML implements the Marshaller interface.
func (v Volumes) MarshalYAML() (interface{}, error) {
vs := []string{}
for _, volume := range v.Volumes {
vs = append(vs, volume.String())
}
return vs, nil
}
// UnmarshalYAML implements the Unmarshaller interface.
func (v *Volumes) UnmarshalYAML(unmarshal func(interface{}) error) error {
var sliceType []interface{}
if err := unmarshal(&sliceType); err == nil {
v.Volumes = []*Volume{}
for _, volume := range sliceType {
name, ok := volume.(string)
if !ok {
return fmt.Errorf("Cannot unmarshal '%v' to type %T into a string value", name, name)
}
elts := strings.SplitN(name, ":", 3)
var vol *Volume
switch {
case len(elts) == 1:
vol = &Volume{
Destination: elts[0],
}
case len(elts) == 2:
vol = &Volume{
Source: elts[0],
Destination: elts[1],
}
case len(elts) == 3:
vol = &Volume{
Source: elts[0],
Destination: elts[1],
AccessMode: elts[2],
}
default:
// FIXME
return fmt.Errorf("")
}
v.Volumes = append(v.Volumes, vol)
}
return nil
}
return errors.New("Failed to unmarshal Volumes")
}