From 205cb1ca2501ef819e18777133588affdc15376d Mon Sep 17 00:00:00 2001 From: schnie Date: Mon, 26 Feb 2018 17:19:50 -0500 Subject: [PATCH] Remove libcompose submodule --- vendor/github.com/docker/libcompose | 1 - vendor/github.com/docker/libcompose/LICENSE | 191 +++++ .../docker/libcompose/config/convert.go | 44 + .../docker/libcompose/config/hash.go | 99 +++ .../docker/libcompose/config/interpolation.go | 199 +++++ .../docker/libcompose/config/merge.go | 252 ++++++ .../docker/libcompose/config/merge_v1.go | 198 +++++ .../docker/libcompose/config/merge_v2.go | 192 +++++ .../docker/libcompose/config/schema.go | 485 ++++++++++++ .../libcompose/config/schema_helpers.go | 105 +++ .../docker/libcompose/config/types.go | 265 +++++++ .../docker/libcompose/config/utils.go | 42 + .../docker/libcompose/config/validation.go | 306 +++++++ .../docker/libcompose/docker/auth/auth.go | 41 + .../libcompose/docker/builder/builder.go | 209 +++++ .../docker/libcompose/docker/client/client.go | 115 +++ .../docker/client/client_factory.go | 35 + .../libcompose/docker/container/container.go | 401 ++++++++++ .../libcompose/docker/container/functions.go | 41 + .../docker/libcompose/docker/ctx/context.go | 35 + .../docker/libcompose/docker/image/image.go | 104 +++ .../libcompose/docker/network/factory.go | 19 + .../libcompose/docker/network/network.go | 202 +++++ .../docker/libcompose/docker/project.go | 106 +++ .../libcompose/docker/service/convert.go | 376 +++++++++ .../docker/libcompose/docker/service/name.go | 92 +++ .../libcompose/docker/service/service.go | 749 ++++++++++++++++++ .../docker/service/service_create.go | 201 +++++ .../docker/service/service_factory.go | 24 + .../docker/libcompose/docker/service/utils.go | 45 ++ .../docker/libcompose/docker/volume/volume.go | 157 ++++ .../docker/libcompose/labels/labels.go | 94 +++ .../docker/libcompose/logger/null.go | 42 + .../docker/libcompose/logger/raw_logger.go | 48 ++ .../docker/libcompose/logger/types.go | 37 + .../docker/libcompose/lookup/composable.go | 25 + .../docker/libcompose/lookup/envfile.go | 31 + .../docker/libcompose/lookup/file.go | 66 ++ .../docker/libcompose/lookup/simple_env.go | 24 + .../docker/libcompose/project/container.go | 13 + .../docker/libcompose/project/context.go | 141 ++++ .../docker/libcompose/project/empty.go | 139 ++++ .../libcompose/project/events/events.go | 224 ++++++ .../docker/libcompose/project/info.go | 48 ++ .../docker/libcompose/project/interface.go | 64 ++ .../docker/libcompose/project/listener.go | 81 ++ .../docker/libcompose/project/network.go | 19 + .../libcompose/project/options/types.go | 53 ++ .../docker/libcompose/project/project.go | 559 +++++++++++++ .../libcompose/project/project_build.go | 17 + .../libcompose/project/project_config.go | 27 + .../libcompose/project/project_containers.go | 54 ++ .../libcompose/project/project_create.go | 25 + .../libcompose/project/project_delete.go | 17 + .../docker/libcompose/project/project_down.go | 56 ++ .../libcompose/project/project_events.go | 24 + .../docker/libcompose/project/project_kill.go | 16 + .../docker/libcompose/project/project_log.go | 16 + .../libcompose/project/project_pause.go | 16 + .../docker/libcompose/project/project_port.go | 26 + .../docker/libcompose/project/project_ps.go | 28 + .../docker/libcompose/project/project_pull.go | 16 + .../libcompose/project/project_restart.go | 16 + .../docker/libcompose/project/project_run.go | 35 + .../libcompose/project/project_scale.go | 40 + .../libcompose/project/project_start.go | 16 + .../docker/libcompose/project/project_stop.go | 16 + .../libcompose/project/project_unpause.go | 16 + .../docker/libcompose/project/project_up.go | 22 + .../libcompose/project/service-wrapper.go | 115 +++ .../docker/libcompose/project/service.go | 97 +++ .../docker/libcompose/project/utils.go | 47 ++ .../docker/libcompose/project/volume.go | 19 + .../docker/libcompose/utils/util.go | 178 +++++ .../docker/libcompose/version/version.go | 20 + .../docker/libcompose/yaml/build.go | 117 +++ .../docker/libcompose/yaml/command.go | 42 + .../docker/libcompose/yaml/external.go | 37 + .../docker/libcompose/yaml/network.go | 139 ++++ .../docker/libcompose/yaml/types_yaml.go | 257 ++++++ .../docker/libcompose/yaml/ulimit.go | 108 +++ .../docker/libcompose/yaml/volume.go | 97 +++ 82 files changed, 8810 insertions(+), 1 deletion(-) delete mode 160000 vendor/github.com/docker/libcompose create mode 100644 vendor/github.com/docker/libcompose/LICENSE create mode 100644 vendor/github.com/docker/libcompose/config/convert.go create mode 100644 vendor/github.com/docker/libcompose/config/hash.go create mode 100644 vendor/github.com/docker/libcompose/config/interpolation.go create mode 100644 vendor/github.com/docker/libcompose/config/merge.go create mode 100644 vendor/github.com/docker/libcompose/config/merge_v1.go create mode 100644 vendor/github.com/docker/libcompose/config/merge_v2.go create mode 100644 vendor/github.com/docker/libcompose/config/schema.go create mode 100644 vendor/github.com/docker/libcompose/config/schema_helpers.go create mode 100644 vendor/github.com/docker/libcompose/config/types.go create mode 100644 vendor/github.com/docker/libcompose/config/utils.go create mode 100644 vendor/github.com/docker/libcompose/config/validation.go create mode 100644 vendor/github.com/docker/libcompose/docker/auth/auth.go create mode 100644 vendor/github.com/docker/libcompose/docker/builder/builder.go create mode 100644 vendor/github.com/docker/libcompose/docker/client/client.go create mode 100644 vendor/github.com/docker/libcompose/docker/client/client_factory.go create mode 100644 vendor/github.com/docker/libcompose/docker/container/container.go create mode 100644 vendor/github.com/docker/libcompose/docker/container/functions.go create mode 100644 vendor/github.com/docker/libcompose/docker/ctx/context.go create mode 100644 vendor/github.com/docker/libcompose/docker/image/image.go create mode 100644 vendor/github.com/docker/libcompose/docker/network/factory.go create mode 100644 vendor/github.com/docker/libcompose/docker/network/network.go create mode 100644 vendor/github.com/docker/libcompose/docker/project.go create mode 100644 vendor/github.com/docker/libcompose/docker/service/convert.go create mode 100644 vendor/github.com/docker/libcompose/docker/service/name.go create mode 100644 vendor/github.com/docker/libcompose/docker/service/service.go create mode 100644 vendor/github.com/docker/libcompose/docker/service/service_create.go create mode 100644 vendor/github.com/docker/libcompose/docker/service/service_factory.go create mode 100644 vendor/github.com/docker/libcompose/docker/service/utils.go create mode 100644 vendor/github.com/docker/libcompose/docker/volume/volume.go create mode 100644 vendor/github.com/docker/libcompose/labels/labels.go create mode 100644 vendor/github.com/docker/libcompose/logger/null.go create mode 100644 vendor/github.com/docker/libcompose/logger/raw_logger.go create mode 100644 vendor/github.com/docker/libcompose/logger/types.go create mode 100644 vendor/github.com/docker/libcompose/lookup/composable.go create mode 100644 vendor/github.com/docker/libcompose/lookup/envfile.go create mode 100644 vendor/github.com/docker/libcompose/lookup/file.go create mode 100644 vendor/github.com/docker/libcompose/lookup/simple_env.go create mode 100644 vendor/github.com/docker/libcompose/project/container.go create mode 100644 vendor/github.com/docker/libcompose/project/context.go create mode 100644 vendor/github.com/docker/libcompose/project/empty.go create mode 100644 vendor/github.com/docker/libcompose/project/events/events.go create mode 100644 vendor/github.com/docker/libcompose/project/info.go create mode 100644 vendor/github.com/docker/libcompose/project/interface.go create mode 100644 vendor/github.com/docker/libcompose/project/listener.go create mode 100644 vendor/github.com/docker/libcompose/project/network.go create mode 100644 vendor/github.com/docker/libcompose/project/options/types.go create mode 100644 vendor/github.com/docker/libcompose/project/project.go create mode 100644 vendor/github.com/docker/libcompose/project/project_build.go create mode 100644 vendor/github.com/docker/libcompose/project/project_config.go create mode 100644 vendor/github.com/docker/libcompose/project/project_containers.go create mode 100644 vendor/github.com/docker/libcompose/project/project_create.go create mode 100644 vendor/github.com/docker/libcompose/project/project_delete.go create mode 100644 vendor/github.com/docker/libcompose/project/project_down.go create mode 100644 vendor/github.com/docker/libcompose/project/project_events.go create mode 100644 vendor/github.com/docker/libcompose/project/project_kill.go create mode 100644 vendor/github.com/docker/libcompose/project/project_log.go create mode 100644 vendor/github.com/docker/libcompose/project/project_pause.go create mode 100644 vendor/github.com/docker/libcompose/project/project_port.go create mode 100644 vendor/github.com/docker/libcompose/project/project_ps.go create mode 100644 vendor/github.com/docker/libcompose/project/project_pull.go create mode 100644 vendor/github.com/docker/libcompose/project/project_restart.go create mode 100644 vendor/github.com/docker/libcompose/project/project_run.go create mode 100644 vendor/github.com/docker/libcompose/project/project_scale.go create mode 100644 vendor/github.com/docker/libcompose/project/project_start.go create mode 100644 vendor/github.com/docker/libcompose/project/project_stop.go create mode 100644 vendor/github.com/docker/libcompose/project/project_unpause.go create mode 100644 vendor/github.com/docker/libcompose/project/project_up.go create mode 100644 vendor/github.com/docker/libcompose/project/service-wrapper.go create mode 100644 vendor/github.com/docker/libcompose/project/service.go create mode 100644 vendor/github.com/docker/libcompose/project/utils.go create mode 100644 vendor/github.com/docker/libcompose/project/volume.go create mode 100644 vendor/github.com/docker/libcompose/utils/util.go create mode 100644 vendor/github.com/docker/libcompose/version/version.go create mode 100644 vendor/github.com/docker/libcompose/yaml/build.go create mode 100644 vendor/github.com/docker/libcompose/yaml/command.go create mode 100644 vendor/github.com/docker/libcompose/yaml/external.go create mode 100644 vendor/github.com/docker/libcompose/yaml/network.go create mode 100644 vendor/github.com/docker/libcompose/yaml/types_yaml.go create mode 100644 vendor/github.com/docker/libcompose/yaml/ulimit.go create mode 100644 vendor/github.com/docker/libcompose/yaml/volume.go diff --git a/vendor/github.com/docker/libcompose b/vendor/github.com/docker/libcompose deleted file mode 160000 index 57bd71650..000000000 --- a/vendor/github.com/docker/libcompose +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 57bd716502dcbe1799f026148016022b0f3b989c diff --git a/vendor/github.com/docker/libcompose/LICENSE b/vendor/github.com/docker/libcompose/LICENSE new file mode 100644 index 000000000..9023c749e --- /dev/null +++ b/vendor/github.com/docker/libcompose/LICENSE @@ -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. diff --git a/vendor/github.com/docker/libcompose/config/convert.go b/vendor/github.com/docker/libcompose/config/convert.go new file mode 100644 index 000000000..dacee03b5 --- /dev/null +++ b/vendor/github.com/docker/libcompose/config/convert.go @@ -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 +} diff --git a/vendor/github.com/docker/libcompose/config/hash.go b/vendor/github.com/docker/libcompose/config/hash.go new file mode 100644 index 000000000..c3c16d8b1 --- /dev/null +++ b/vendor/github.com/docker/libcompose/config/hash.go @@ -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)) +} diff --git a/vendor/github.com/docker/libcompose/config/interpolation.go b/vendor/github.com/docker/libcompose/config/interpolation.go new file mode 100644 index 000000000..1d7406ed4 --- /dev/null +++ b/vendor/github.com/docker/libcompose/config/interpolation.go @@ -0,0 +1,199 @@ +package config + +import ( + "bytes" + "fmt" + "strings" + + "github.com/sirupsen/logrus" +) + +var defaultValues = make(map[string]string) + +func isNum(c uint8) bool { + return c >= '0' && c <= '9' +} + +func validVariableDefault(c uint8, line string, pos int) bool { + return (c == ':' && line[pos+1] == '-') || (c == '-') +} + +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 parseDefaultValue(line string, pos int) (string, int, bool) { + var buffer bytes.Buffer + + // only skip :, :- and - at the beginning + for ; pos < len(line); pos++ { + c := line[pos] + if c == ':' || c == '-' { + continue + } + break + } + for ; pos < len(line); pos++ { + c := line[pos] + if c == '}' { + return buffer.String(), pos - 1, true + } + err := buffer.WriteByte(c) + if err != nil { + return "", pos, false + } + } + return "", 0, false +} + +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) + case validVariableDefault(c, line, pos): + defaultValue := "" + defaultValue, pos, _ = parseDefaultValue(line, pos) + defaultValues[buffer.String()] = defaultValue + 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 { + if val, ok := defaultValues[s]; ok { + return val + } + logrus.Warnf("The %s variable is not set. Substituting a blank string.", s) + return "" + } + + if strings.SplitN(values[0], "=", 2)[1] == "" { + if val, ok := defaultValues[s]; ok { + return val + } + } + + // 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] + }) +} diff --git a/vendor/github.com/docker/libcompose/config/merge.go b/vendor/github.com/docker/libcompose/config/merge.go new file mode 100644 index 000000000..a375e546f --- /dev/null +++ b/vendor/github.com/docker/libcompose/config/merge.go @@ -0,0 +1,252 @@ +package config + +import ( + "bufio" + "bytes" + "fmt" + "strings" + + "reflect" + + "github.com/docker/docker/pkg/urlutil" + "github.com/docker/libcompose/utils" + composeYaml "github.com/docker/libcompose/yaml" + "gopkg.in/yaml.v2" +) + +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 + + 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) +} diff --git a/vendor/github.com/docker/libcompose/config/merge_v1.go b/vendor/github.com/docker/libcompose/config/merge_v1.go new file mode 100644 index 000000000..78c1249e5 --- /dev/null +++ b/vendor/github.com/docker/libcompose/config/merge_v1.go @@ -0,0 +1,198 @@ +package config + +import ( + "fmt" + "path" + + "github.com/docker/libcompose/utils" + "github.com/sirupsen/logrus" +) + +// 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 +} diff --git a/vendor/github.com/docker/libcompose/config/merge_v2.go b/vendor/github.com/docker/libcompose/config/merge_v2.go new file mode 100644 index 000000000..de653696c --- /dev/null +++ b/vendor/github.com/docker/libcompose/config/merge_v2.go @@ -0,0 +1,192 @@ +package config + +import ( + "fmt" + "path" + "strings" + + "github.com/docker/libcompose/utils" + "github.com/sirupsen/logrus" +) + +// 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 +} diff --git a/vendor/github.com/docker/libcompose/config/schema.go b/vendor/github.com/docker/libcompose/config/schema.go new file mode 100644 index 000000000..672a77f55 --- /dev/null +++ b/vendor/github.com/docker/libcompose/config/schema.go @@ -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"] + } + } + } + } + } +} +` diff --git a/vendor/github.com/docker/libcompose/config/schema_helpers.go b/vendor/github.com/docker/libcompose/config/schema_helpers.go new file mode 100644 index 000000000..8a766fb32 --- /dev/null +++ b/vendor/github.com/docker/libcompose/config/schema_helpers.go @@ -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 +} diff --git a/vendor/github.com/docker/libcompose/config/types.go b/vendor/github.com/docker/libcompose/config/types.go new file mode 100644 index 000000000..9e4c9dfca --- /dev/null +++ b/vendor/github.com/docker/libcompose/config/types.go @@ -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) +} diff --git a/vendor/github.com/docker/libcompose/config/utils.go b/vendor/github.com/docker/libcompose/config/utils.go new file mode 100644 index 000000000..ae9b86cf9 --- /dev/null +++ b/vendor/github.com/docker/libcompose/config/utils.go @@ -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 "" +} diff --git a/vendor/github.com/docker/libcompose/config/validation.go b/vendor/github.com/docker/libcompose/config/validation.go new file mode 100644 index 000000000..2d277d4b7 --- /dev/null +++ b/vendor/github.com/docker/libcompose/config/validation.go @@ -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 +} diff --git a/vendor/github.com/docker/libcompose/docker/auth/auth.go b/vendor/github.com/docker/libcompose/docker/auth/auth.go new file mode 100644 index 000000000..9930970cf --- /dev/null +++ b/vendor/github.com/docker/libcompose/docker/auth/auth.go @@ -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 +} diff --git a/vendor/github.com/docker/libcompose/docker/builder/builder.go b/vendor/github.com/docker/libcompose/docker/builder/builder.go new file mode 100644 index 000000000..98d24354f --- /dev/null +++ b/vendor/github.com/docker/libcompose/docker/builder/builder.go @@ -0,0 +1,209 @@ +package builder + +import ( + "fmt" + "io" + "os" + "path" + "path/filepath" + "strings" + + "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" + "github.com/sirupsen/logrus" + "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 + CacheFrom []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, + CacheFrom: d.CacheFrom, + }) + 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) +} diff --git a/vendor/github.com/docker/libcompose/docker/client/client.go b/vendor/github.com/docker/libcompose/docker/client/client.go new file mode 100644 index 000000000..a88be92f4 --- /dev/null +++ b/vendor/github.com/docker/libcompose/docker/client/client.go @@ -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 +} diff --git a/vendor/github.com/docker/libcompose/docker/client/client_factory.go b/vendor/github.com/docker/libcompose/docker/client/client_factory.go new file mode 100644 index 000000000..26d88d70b --- /dev/null +++ b/vendor/github.com/docker/libcompose/docker/client/client_factory.go @@ -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 +} diff --git a/vendor/github.com/docker/libcompose/docker/container/container.go b/vendor/github.com/docker/libcompose/docker/container/container.go new file mode 100644 index 000000000..d26458537 --- /dev/null +++ b/vendor/github.com/docker/libcompose/docker/container/container.go @@ -0,0 +1,401 @@ +package container + +import ( + "fmt" + "io" + "math" + "os" + "strconv" + "strings" + "time" + + "golang.org/x/net/context" + + "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/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" + "github.com/sirupsen/logrus" +) + +// 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 + inFd uintptr + ) + + 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 + } + + if configOverride.StdinOpen { + // 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 = make(chan error, 1) + go func() { + errCh <- 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) +} diff --git a/vendor/github.com/docker/libcompose/docker/container/functions.go b/vendor/github.com/docker/libcompose/docker/container/functions.go new file mode 100644 index 000000000..d97d66692 --- /dev/null +++ b/vendor/github.com/docker/libcompose/docker/container/functions.go @@ -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.IsErrNotFound(err) { + return nil, nil + } + return nil, err + } + return &container, nil +} diff --git a/vendor/github.com/docker/libcompose/docker/ctx/context.go b/vendor/github.com/docker/libcompose/docker/ctx/context.go new file mode 100644 index 000000000..35ee42a8d --- /dev/null +++ b/vendor/github.com/docker/libcompose/docker/ctx/context.go @@ -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 +} diff --git a/vendor/github.com/docker/libcompose/docker/image/image.go b/vendor/github.com/docker/libcompose/docker/image/image.go new file mode 100644 index 000000000..f4bc25145 --- /dev/null +++ b/vendor/github.com/docker/libcompose/docker/image/image.go @@ -0,0 +1,104 @@ +package image + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "io" + "os" + + "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" + "github.com/sirupsen/logrus" + "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.IsErrNotFound(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 +} diff --git a/vendor/github.com/docker/libcompose/docker/network/factory.go b/vendor/github.com/docker/libcompose/docker/network/factory.go new file mode 100644 index 000000000..158131f07 --- /dev/null +++ b/vendor/github.com/docker/libcompose/docker/network/factory.go @@ -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) +} diff --git a/vendor/github.com/docker/libcompose/docker/network/network.go b/vendor/github.com/docker/libcompose/docker/network/network.go new file mode 100644 index 000000000..632a18fd0 --- /dev/null +++ b/vendor/github.com/docker/libcompose/docker/network/network.go @@ -0,0 +1,202 @@ +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(), types.NetworkInspectOptions{ + Verbose: true, + }) +} + +// 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.IsErrNotFound(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.IsErrNotFound(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 +} diff --git a/vendor/github.com/docker/libcompose/docker/project.go b/vendor/github.com/docker/libcompose/docker/project.go new file mode 100644 index 000000000..4713d0f2d --- /dev/null +++ b/vendor/github.com/docker/libcompose/docker/project.go @@ -0,0 +1,106 @@ +package docker + +import ( + "golang.org/x/net/context" + + "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" + "github.com/sirupsen/logrus" +) + +// 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 +} diff --git a/vendor/github.com/docker/libcompose/docker/service/convert.go b/vendor/github.com/docker/libcompose/docker/service/convert.go new file mode 100644 index 000000000..fbce036f2 --- /dev/null +++ b/vendor/github.com/docker/libcompose/docker/service/convert.go @@ -0,0 +1,376 @@ +package service + +import ( + "fmt" + "strings" + + "github.com/docker/cli/opts" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/network" + "github.com/docker/docker/api/types/strslice" + "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 +} diff --git a/vendor/github.com/docker/libcompose/docker/service/name.go b/vendor/github.com/docker/libcompose/docker/service/name.go new file mode 100644 index 000000000..faa9e3d44 --- /dev/null +++ b/vendor/github.com/docker/libcompose/docker/service/name.go @@ -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 +} diff --git a/vendor/github.com/docker/libcompose/docker/service/service.go b/vendor/github.com/docker/libcompose/docker/service/service.go new file mode 100644 index 000000000..faea399a6 --- /dev/null +++ b/vendor/github.com/docker/libcompose/docker/service/service.go @@ -0,0 +1,749 @@ +package service + +import ( + "fmt" + "strings" + "time" + + "golang.org/x/net/context" + + "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" + "github.com/sirupsen/logrus" +) + +// 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: !options.DisableTty, StdinOpen: !options.DisableTty} + + 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.RealName] + 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.IsErrNotFound(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 +} diff --git a/vendor/github.com/docker/libcompose/docker/service/service_create.go b/vendor/github.com/docker/libcompose/docker/service/service_create.go new file mode 100644 index 000000000..97a15ef92 --- /dev/null +++ b/vendor/github.com/docker/libcompose/docker/service/service_create.go @@ -0,0 +1,201 @@ +package service + +import ( + "fmt" + "strconv" + "strings" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/types" + containertypes "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/network" + "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" + "github.com/sirupsen/logrus" +) + +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)) + } + + networkConfig := configWrapper.NetworkingConfig + if configWrapper.HostConfig.NetworkMode != "" && configWrapper.HostConfig.NetworkMode.IsUserDefined() { + if networkConfig == nil { + networkConfig = &network.NetworkingConfig{ + EndpointsConfig: map[string]*network.EndpointSettings{ + string(configWrapper.HostConfig.NetworkMode): {}, + }, + } + } + for key, value := range networkConfig.EndpointsConfig { + + conf := value + if value.Aliases == nil { + value.Aliases = []string{} + } + value.Aliases = append(value.Aliases, s.name) + networkConfig.EndpointsConfig[key] = conf + } + } + 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, networkConfig) + 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 +} diff --git a/vendor/github.com/docker/libcompose/docker/service/service_factory.go b/vendor/github.com/docker/libcompose/docker/service/service_factory.go new file mode 100644 index 000000000..2fcaafab1 --- /dev/null +++ b/vendor/github.com/docker/libcompose/docker/service/service_factory.go @@ -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 +} diff --git a/vendor/github.com/docker/libcompose/docker/service/utils.go b/vendor/github.com/docker/libcompose/docker/service/utils.go new file mode 100644 index 000000000..5b28c887f --- /dev/null +++ b/vendor/github.com/docker/libcompose/docker/service/utils.go @@ -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 "" +} diff --git a/vendor/github.com/docker/libcompose/docker/volume/volume.go b/vendor/github.com/docker/libcompose/docker/volume/volume.go new file mode 100644 index 000000000..ac5cb479c --- /dev/null +++ b/vendor/github.com/docker/libcompose/docker/volume/volume.go @@ -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.IsErrNotFound(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.IsErrNotFound(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) +} diff --git a/vendor/github.com/docker/libcompose/labels/labels.go b/vendor/github.com/docker/libcompose/labels/labels.go new file mode 100644 index 000000000..c8eefb093 --- /dev/null +++ b/vendor/github.com/docker/libcompose/labels/labels.go @@ -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)}, + } +} diff --git a/vendor/github.com/docker/libcompose/logger/null.go b/vendor/github.com/docker/libcompose/logger/null.go new file mode 100644 index 000000000..d791bfb0d --- /dev/null +++ b/vendor/github.com/docker/libcompose/logger/null.go @@ -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 +} diff --git a/vendor/github.com/docker/libcompose/logger/raw_logger.go b/vendor/github.com/docker/libcompose/logger/raw_logger.go new file mode 100644 index 000000000..9aa0a7bc8 --- /dev/null +++ b/vendor/github.com/docker/libcompose/logger/raw_logger.go @@ -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 +} diff --git a/vendor/github.com/docker/libcompose/logger/types.go b/vendor/github.com/docker/libcompose/logger/types.go new file mode 100644 index 000000000..7300095aa --- /dev/null +++ b/vendor/github.com/docker/libcompose/logger/types.go @@ -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 +} diff --git a/vendor/github.com/docker/libcompose/lookup/composable.go b/vendor/github.com/docker/libcompose/lookup/composable.go new file mode 100644 index 000000000..ff76480b3 --- /dev/null +++ b/vendor/github.com/docker/libcompose/lookup/composable.go @@ -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 +} diff --git a/vendor/github.com/docker/libcompose/lookup/envfile.go b/vendor/github.com/docker/libcompose/lookup/envfile.go new file mode 100644 index 000000000..d606d97a4 --- /dev/null +++ b/vendor/github.com/docker/libcompose/lookup/envfile.go @@ -0,0 +1,31 @@ +package lookup + +import ( + "strings" + + "github.com/docker/cli/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{} +} diff --git a/vendor/github.com/docker/libcompose/lookup/file.go b/vendor/github.com/docker/libcompose/lookup/file.go new file mode 100644 index 000000000..b2ec2f586 --- /dev/null +++ b/vendor/github.com/docker/libcompose/lookup/file.go @@ -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, ":") +} diff --git a/vendor/github.com/docker/libcompose/lookup/simple_env.go b/vendor/github.com/docker/libcompose/lookup/simple_env.go new file mode 100644 index 000000000..40ce12843 --- /dev/null +++ b/vendor/github.com/docker/libcompose/lookup/simple_env.go @@ -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)} +} diff --git a/vendor/github.com/docker/libcompose/project/container.go b/vendor/github.com/docker/libcompose/project/container.go new file mode 100644 index 000000000..def4dd877 --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/container.go @@ -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 +} diff --git a/vendor/github.com/docker/libcompose/project/context.go b/vendor/github.com/docker/libcompose/project/context.go new file mode 100644 index 000000000..dbf6be68d --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/context.go @@ -0,0 +1,141 @@ +package project + +import ( + "fmt" + "io/ioutil" + "os" + "path" + "path/filepath" + "regexp" + "strings" + + "github.com/docker/libcompose/config" + "github.com/docker/libcompose/logger" + "github.com/sirupsen/logrus" +) + +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 +} diff --git a/vendor/github.com/docker/libcompose/project/empty.go b/vendor/github.com/docker/libcompose/project/empty.go new file mode 100644 index 000000000..4b31b1dfe --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/empty.go @@ -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 +} diff --git a/vendor/github.com/docker/libcompose/project/events/events.go b/vendor/github.com/docker/libcompose/project/events/events.go new file mode 100644 index 000000000..816a1b04f --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/events/events.go @@ -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 +} diff --git a/vendor/github.com/docker/libcompose/project/info.go b/vendor/github.com/docker/libcompose/project/info.go new file mode 100644 index 000000000..c12b04c7d --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/info.go @@ -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'}) +} diff --git a/vendor/github.com/docker/libcompose/project/interface.go b/vendor/github.com/docker/libcompose/project/interface.go new file mode 100644 index 000000000..3356407c2 --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/interface.go @@ -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 +} diff --git a/vendor/github.com/docker/libcompose/project/listener.go b/vendor/github.com/docker/libcompose/project/listener.go new file mode 100644 index 000000000..446144c47 --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/listener.go @@ -0,0 +1,81 @@ +package project + +import ( + "bytes" + + "github.com/docker/libcompose/project/events" + "github.com/sirupsen/logrus" +) + +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()) + } + } +} diff --git a/vendor/github.com/docker/libcompose/project/network.go b/vendor/github.com/docker/libcompose/project/network.go new file mode 100644 index 000000000..af0aa5671 --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/network.go @@ -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) +} diff --git a/vendor/github.com/docker/libcompose/project/options/types.go b/vendor/github.com/docker/libcompose/project/options/types.go new file mode 100644 index 000000000..3d8d54a0b --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/options/types.go @@ -0,0 +1,53 @@ +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 + DisableTty 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 + } +} diff --git a/vendor/github.com/docker/libcompose/project/project.go b/vendor/github.com/docker/libcompose/project/project.go new file mode 100644 index 000000000..5bf2c17e8 --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/project.go @@ -0,0 +1,559 @@ +package project + +import ( + "errors" + "fmt" + "os" + "path" + "path/filepath" + "strings" + + "golang.org/x/net/context" + + "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" + log "github.com/sirupsen/logrus" +) + +// 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, "~") +} diff --git a/vendor/github.com/docker/libcompose/project/project_build.go b/vendor/github.com/docker/libcompose/project/project_build.go new file mode 100644 index 000000000..506a5c8fe --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/project_build.go @@ -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) +} diff --git a/vendor/github.com/docker/libcompose/project/project_config.go b/vendor/github.com/docker/libcompose/project/project_config.go new file mode 100644 index 000000000..c4ca1a31d --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/project_config.go @@ -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 +} diff --git a/vendor/github.com/docker/libcompose/project/project_containers.go b/vendor/github.com/docker/libcompose/project/project_containers.go new file mode 100644 index 000000000..a8fed9942 --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/project_containers.go @@ -0,0 +1,54 @@ +package project + +import ( + "fmt" + "sync" + + "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{} + var lock sync.Mutex + + 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() + lock.Lock() + containers = append(containers, containerID) + lock.Unlock() + } + return nil + }) + }), nil) + if err != nil { + return nil, err + } + return containers, nil +} diff --git a/vendor/github.com/docker/libcompose/project/project_create.go b/vendor/github.com/docker/libcompose/project/project_create.go new file mode 100644 index 000000000..9141b719c --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/project_create.go @@ -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) +} diff --git a/vendor/github.com/docker/libcompose/project/project_delete.go b/vendor/github.com/docker/libcompose/project/project_delete.go new file mode 100644 index 000000000..ab1d35017 --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/project_delete.go @@ -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) +} diff --git a/vendor/github.com/docker/libcompose/project/project_down.go b/vendor/github.com/docker/libcompose/project/project_down.go new file mode 100644 index 000000000..288ce53dd --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/project_down.go @@ -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{}) + }) +} diff --git a/vendor/github.com/docker/libcompose/project/project_events.go b/vendor/github.com/docker/libcompose/project/project_events.go new file mode 100644 index 000000000..0b296f30e --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/project_events.go @@ -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 +} diff --git a/vendor/github.com/docker/libcompose/project/project_kill.go b/vendor/github.com/docker/libcompose/project/project_kill.go new file mode 100644 index 000000000..ac3c87d71 --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/project_kill.go @@ -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) +} diff --git a/vendor/github.com/docker/libcompose/project/project_log.go b/vendor/github.com/docker/libcompose/project/project_log.go new file mode 100644 index 000000000..7e576d586 --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/project_log.go @@ -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) +} diff --git a/vendor/github.com/docker/libcompose/project/project_pause.go b/vendor/github.com/docker/libcompose/project/project_pause.go new file mode 100644 index 000000000..c5c4c39b3 --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/project_pause.go @@ -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) +} diff --git a/vendor/github.com/docker/libcompose/project/project_port.go b/vendor/github.com/docker/libcompose/project/project_port.go new file mode 100644 index 000000000..85fa6d8a5 --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/project_port.go @@ -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)) +} diff --git a/vendor/github.com/docker/libcompose/project/project_ps.go b/vendor/github.com/docker/libcompose/project/project_ps.go new file mode 100644 index 000000000..2c69018bc --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/project_ps.go @@ -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 +} diff --git a/vendor/github.com/docker/libcompose/project/project_pull.go b/vendor/github.com/docker/libcompose/project/project_pull.go new file mode 100644 index 000000000..5a7b0eedd --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/project_pull.go @@ -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) +} diff --git a/vendor/github.com/docker/libcompose/project/project_restart.go b/vendor/github.com/docker/libcompose/project/project_restart.go new file mode 100644 index 000000000..0cb326523 --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/project_restart.go @@ -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) +} diff --git a/vendor/github.com/docker/libcompose/project/project_run.go b/vendor/github.com/docker/libcompose/project/project_run.go new file mode 100644 index 000000000..c5f223fcc --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/project_run.go @@ -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 +} diff --git a/vendor/github.com/docker/libcompose/project/project_scale.go b/vendor/github.com/docker/libcompose/project/project_scale.go new file mode 100644 index 000000000..4ce26402a --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/project_scale.go @@ -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 +} diff --git a/vendor/github.com/docker/libcompose/project/project_start.go b/vendor/github.com/docker/libcompose/project/project_start.go new file mode 100644 index 000000000..8ffebdd3c --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/project_start.go @@ -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) +} diff --git a/vendor/github.com/docker/libcompose/project/project_stop.go b/vendor/github.com/docker/libcompose/project/project_stop.go new file mode 100644 index 000000000..16887f5b7 --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/project_stop.go @@ -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) +} diff --git a/vendor/github.com/docker/libcompose/project/project_unpause.go b/vendor/github.com/docker/libcompose/project/project_unpause.go new file mode 100644 index 000000000..625129d8b --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/project_unpause.go @@ -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) +} diff --git a/vendor/github.com/docker/libcompose/project/project_up.go b/vendor/github.com/docker/libcompose/project/project_up.go new file mode 100644 index 000000000..f5954cd7b --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/project_up.go @@ -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) + }) +} diff --git a/vendor/github.com/docker/libcompose/project/service-wrapper.go b/vendor/github.com/docker/libcompose/project/service-wrapper.go new file mode 100644 index 000000000..75dc1e868 --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/service-wrapper.go @@ -0,0 +1,115 @@ +package project + +import ( + "sync" + + "github.com/docker/libcompose/project/events" + log "github.com/sirupsen/logrus" +) + +type serviceWrapper struct { + name string + service Service + done sync.WaitGroup + state ServiceState + err error + project *Project + noWait bool + ignored map[string]bool +} + +func newServiceWrapper(name string, p *Project) (*serviceWrapper, error) { + wrapper := &serviceWrapper{ + name: name, + state: StateUnknown, + project: p, + ignored: map[string]bool{}, + } + + return wrapper, wrapper.Reset() +} + +func (s *serviceWrapper) IgnoreDep(name string) { + s.ignored[name] = true +} + +func (s *serviceWrapper) Reset() error { + if s.state != StateExecuted { + service, err := s.project.CreateService(s.name) + if err != nil { + log.Errorf("Failed to create service for %s : %v", s.name, err) + return err + } + + s.service = service + } + + if s.err == ErrRestart { + s.err = nil + } + s.done.Add(1) + + return nil +} + +func (s *serviceWrapper) Ignore() { + defer s.done.Done() + + s.state = StateExecuted + s.project.Notify(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 +} diff --git a/vendor/github.com/docker/libcompose/project/service.go b/vendor/github.com/docker/libcompose/project/service.go new file mode 100644 index 000000000..581df6740 --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/service.go @@ -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, + } +} diff --git a/vendor/github.com/docker/libcompose/project/utils.go b/vendor/github.com/docker/libcompose/project/utils.go new file mode 100644 index 000000000..418f245fc --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/utils.go @@ -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] +} diff --git a/vendor/github.com/docker/libcompose/project/volume.go b/vendor/github.com/docker/libcompose/project/volume.go new file mode 100644 index 000000000..08f926e30 --- /dev/null +++ b/vendor/github.com/docker/libcompose/project/volume.go @@ -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) +} diff --git a/vendor/github.com/docker/libcompose/utils/util.go b/vendor/github.com/docker/libcompose/utils/util.go new file mode 100644 index 000000000..6f42c1f05 --- /dev/null +++ b/vendor/github.com/docker/libcompose/utils/util.go @@ -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 + +} diff --git a/vendor/github.com/docker/libcompose/version/version.go b/vendor/github.com/docker/libcompose/version/version.go new file mode 100644 index 000000000..2d4593e92 --- /dev/null +++ b/vendor/github.com/docker/libcompose/version/version.go @@ -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" +} diff --git a/vendor/github.com/docker/libcompose/yaml/build.go b/vendor/github.com/docker/libcompose/yaml/build.go new file mode 100644 index 000000000..b6a8a9251 --- /dev/null +++ b/vendor/github.com/docker/libcompose/yaml/build.go @@ -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 +} diff --git a/vendor/github.com/docker/libcompose/yaml/command.go b/vendor/github.com/docker/libcompose/yaml/command.go new file mode 100644 index 000000000..ace69b5d3 --- /dev/null +++ b/vendor/github.com/docker/libcompose/yaml/command.go @@ -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") +} diff --git a/vendor/github.com/docker/libcompose/yaml/external.go b/vendor/github.com/docker/libcompose/yaml/external.go new file mode 100644 index 000000000..be7efca9f --- /dev/null +++ b/vendor/github.com/docker/libcompose/yaml/external.go @@ -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 +} diff --git a/vendor/github.com/docker/libcompose/yaml/network.go b/vendor/github.com/docker/libcompose/yaml/network.go new file mode 100644 index 000000000..d56ca4e08 --- /dev/null +++ b/vendor/github.com/docker/libcompose/yaml/network.go @@ -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) + } +} diff --git a/vendor/github.com/docker/libcompose/yaml/types_yaml.go b/vendor/github.com/docker/libcompose/yaml/types_yaml.go new file mode 100644 index 000000000..a3bed1d95 --- /dev/null +++ b/vendor/github.com/docker/libcompose/yaml/types_yaml.go @@ -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 +} diff --git a/vendor/github.com/docker/libcompose/yaml/ulimit.go b/vendor/github.com/docker/libcompose/yaml/ulimit.go new file mode 100644 index 000000000..c25c49364 --- /dev/null +++ b/vendor/github.com/docker/libcompose/yaml/ulimit.go @@ -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, + }, + } +} diff --git a/vendor/github.com/docker/libcompose/yaml/volume.go b/vendor/github.com/docker/libcompose/yaml/volume.go new file mode 100644 index 000000000..8eabe572a --- /dev/null +++ b/vendor/github.com/docker/libcompose/yaml/volume.go @@ -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") +}