浏览代码

Use compose-go's Project and ProjectOptions

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De Loof 5 年之前
父节点
当前提交
622402709a

+ 19 - 20
azure/backend.go

@@ -26,6 +26,7 @@ import (
 
 	"github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2018-10-01/containerinstance"
 	"github.com/Azure/go-autorest/autorest/to"
+	"github.com/compose-spec/compose-go/cli"
 	"github.com/compose-spec/compose-go/types"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
@@ -167,28 +168,26 @@ func (cs *aciContainerService) Run(ctx context.Context, r containers.ContainerCo
 		return err
 	}
 
-	project := compose.Project{
+	project := types.Project{
 		Name: r.ID,
-		Config: types.Config{
-			Services: []types.ServiceConfig{
-				{
-					Name:    singleContainerName,
-					Image:   r.Image,
-					Ports:   ports,
-					Labels:  r.Labels,
-					Volumes: serviceConfigVolumes,
-					Deploy: &types.DeployConfig{
-						Resources: types.Resources{
-							Limits: &types.Resource{
-								NanoCPUs:    fmt.Sprintf("%f", r.CPULimit),
-								MemoryBytes: types.UnitBytes(r.MemLimit.Value()),
-							},
+		Services: []types.ServiceConfig{
+			{
+				Name:    singleContainerName,
+				Image:   r.Image,
+				Ports:   ports,
+				Labels:  r.Labels,
+				Volumes: serviceConfigVolumes,
+				Deploy: &types.DeployConfig{
+					Resources: types.Resources{
+						Limits: &types.Resource{
+							NanoCPUs:    fmt.Sprintf("%f", r.CPULimit),
+							MemoryBytes: types.UnitBytes(r.MemLimit.Value()),
 						},
 					},
 				},
 			},
-			Volumes: projectVolumes,
 		},
+		Volumes: projectVolumes,
 	}
 
 	logrus.Debugf("Running container %q with name %q\n", r.Image, r.ID)
@@ -301,8 +300,8 @@ type aciComposeService struct {
 	ctx store.AciContext
 }
 
-func (cs *aciComposeService) Up(ctx context.Context, opts compose.ProjectOptions) error {
-	project, err := compose.ProjectFromOptions(&opts)
+func (cs *aciComposeService) Up(ctx context.Context, opts cli.ProjectOptions) error {
+	project, err := cli.ProjectFromOptions(&opts)
 	if err != nil {
 		return err
 	}
@@ -315,8 +314,8 @@ func (cs *aciComposeService) Up(ctx context.Context, opts compose.ProjectOptions
 	return createOrUpdateACIContainers(ctx, cs.ctx, groupDefinition)
 }
 
-func (cs *aciComposeService) Down(ctx context.Context, opts compose.ProjectOptions) error {
-	project, err := compose.ProjectFromOptions(&opts)
+func (cs *aciComposeService) Down(ctx context.Context, opts cli.ProjectOptions) error {
+	project, err := cli.ProjectFromOptions(&opts)
 	if err != nil {
 		return err
 	}

+ 2 - 3
azure/convert/convert.go

@@ -29,7 +29,6 @@ import (
 	"github.com/Azure/go-autorest/autorest/to"
 	"github.com/compose-spec/compose-go/types"
 
-	"github.com/docker/api/compose"
 	"github.com/docker/api/containers"
 	"github.com/docker/api/context/store"
 )
@@ -47,7 +46,7 @@ const (
 )
 
 // ToContainerGroup converts a compose project into a ACI container group
-func ToContainerGroup(aciContext store.AciContext, p compose.Project) (containerinstance.ContainerGroup, error) {
+func ToContainerGroup(aciContext store.AciContext, p types.Project) (containerinstance.ContainerGroup, error) {
 	project := projectAciHelper(p)
 	containerGroupName := strings.ToLower(project.Name)
 	volumesCache, volumesSlice, err := project.getAciFileVolumes()
@@ -154,7 +153,7 @@ func getDNSSidecar(containers []containerinstance.Container) containerinstance.C
 	return dnsSideCar
 }
 
-type projectAciHelper compose.Project
+type projectAciHelper types.Project
 
 func (p projectAciHelper) getAciSecretVolumes() ([]containerinstance.Volume, error) {
 	var secretVolumes []containerinstance.Volume

+ 52 - 68
azure/convert/convert_test.go

@@ -23,7 +23,6 @@ import (
 	"github.com/Azure/go-autorest/autorest/to"
 	"github.com/compose-spec/compose-go/types"
 
-	"github.com/docker/api/compose"
 	"github.com/docker/api/containers"
 	"github.com/docker/api/context/store"
 
@@ -46,7 +45,7 @@ func (suite *ConvertTestSuite) BeforeTest(suiteName, testName string) {
 }
 
 func (suite *ConvertTestSuite) TestProjectName() {
-	project := compose.Project{
+	project := types.Project{
 		Name: "TEST",
 	}
 	containerGroup, err := ToContainerGroup(suite.ctx, project)
@@ -110,18 +109,15 @@ func (suite *ConvertTestSuite) TestContainerGroupToContainer() {
 }
 
 func (suite *ConvertTestSuite) TestComposeContainerGroupToContainerWithDnsSideCarSide() {
-	project := compose.Project{
-		Name: "",
-		Config: types.Config{
-			Services: []types.ServiceConfig{
-				{
-					Name:  "service1",
-					Image: "image1",
-				},
-				{
-					Name:  "service2",
-					Image: "image2",
-				},
+	project := types.Project{
+		Services: []types.ServiceConfig{
+			{
+				Name:  "service1",
+				Image: "image1",
+			},
+			{
+				Name:  "service2",
+				Image: "image2",
 			},
 		},
 	}
@@ -142,14 +138,11 @@ func (suite *ConvertTestSuite) TestComposeContainerGroupToContainerWithDnsSideCa
 }
 
 func (suite *ConvertTestSuite) TestComposeSingleContainerGroupToContainerNoDnsSideCarSide() {
-	project := compose.Project{
-		Name: "",
-		Config: types.Config{
-			Services: []types.ServiceConfig{
-				{
-					Name:  "service1",
-					Image: "image1",
-				},
+	project := types.Project{
+		Services: []types.ServiceConfig{
+			{
+				Name:  "service1",
+				Image: "image1",
 			},
 		},
 	}
@@ -163,28 +156,25 @@ func (suite *ConvertTestSuite) TestComposeSingleContainerGroupToContainerNoDnsSi
 }
 
 func (suite *ConvertTestSuite) TestComposeContainerGroupToContainerMultiplePorts() {
-	project := compose.Project{
-		Name: "",
-		Config: types.Config{
-			Services: []types.ServiceConfig{
-				{
-					Name:  "service1",
-					Image: "image1",
-					Ports: []types.ServicePortConfig{
-						{
-							Published: 80,
-							Target:    80,
-						},
+	project := types.Project{
+		Services: []types.ServiceConfig{
+			{
+				Name:  "service1",
+				Image: "image1",
+				Ports: []types.ServicePortConfig{
+					{
+						Published: 80,
+						Target:    80,
 					},
 				},
-				{
-					Name:  "service2",
-					Image: "image2",
-					Ports: []types.ServicePortConfig{
-						{
-							Published: 8080,
-							Target:    8080,
-						},
+			},
+			{
+				Name:  "service2",
+				Image: "image2",
+				Ports: []types.ServicePortConfig{
+					{
+						Published: 8080,
+						Target:    8080,
 					},
 				},
 			},
@@ -215,19 +205,16 @@ func (suite *ConvertTestSuite) TestComposeContainerGroupToContainerMultiplePorts
 
 func (suite *ConvertTestSuite) TestComposeContainerGroupToContainerResourceLimits() {
 	_0_1Gb := 0.1 * 1024 * 1024 * 1024
-	project := compose.Project{
-		Name: "",
-		Config: types.Config{
-			Services: []types.ServiceConfig{
-				{
-					Name:  "service1",
-					Image: "image1",
-					Deploy: &types.DeployConfig{
-						Resources: types.Resources{
-							Limits: &types.Resource{
-								NanoCPUs:    "0.1",
-								MemoryBytes: types.UnitBytes(_0_1Gb),
-							},
+	project := types.Project{
+		Services: []types.ServiceConfig{
+			{
+				Name:  "service1",
+				Image: "image1",
+				Deploy: &types.DeployConfig{
+					Resources: types.Resources{
+						Limits: &types.Resource{
+							NanoCPUs:    "0.1",
+							MemoryBytes: types.UnitBytes(_0_1Gb),
 						},
 					},
 				},
@@ -245,19 +232,16 @@ func (suite *ConvertTestSuite) TestComposeContainerGroupToContainerResourceLimit
 }
 
 func (suite *ConvertTestSuite) TestComposeContainerGroupToContainerResourceLimitsDefaults() {
-	project := compose.Project{
-		Name: "",
-		Config: types.Config{
-			Services: []types.ServiceConfig{
-				{
-					Name:  "service1",
-					Image: "image1",
-					Deploy: &types.DeployConfig{
-						Resources: types.Resources{
-							Limits: &types.Resource{
-								NanoCPUs:    "",
-								MemoryBytes: 0,
-							},
+	project := types.Project{
+		Services: []types.ServiceConfig{
+			{
+				Name:  "service1",
+				Image: "image1",
+				Deploy: &types.DeployConfig{
+					Resources: types.Resources{
+						Limits: &types.Resource{
+							NanoCPUs:    "",
+							MemoryBytes: 0,
 						},
 					},
 				},

+ 1 - 2
azure/convert/registrycredentials.go

@@ -23,11 +23,10 @@ import (
 
 	"github.com/Azure/azure-sdk-for-go/profiles/latest/containerinstance/mgmt/containerinstance"
 	"github.com/Azure/go-autorest/autorest/to"
+	compose "github.com/compose-spec/compose-go/types"
 	"github.com/docker/cli/cli/config"
 	"github.com/docker/cli/cli/config/configfile"
 	"github.com/docker/cli/cli/config/types"
-
-	"github.com/docker/api/compose"
 )
 
 // Specific username from ACR docs : https://github.com/Azure/acr/blob/master/docs/AAD-OAuth.md#getting-credentials-programatically

+ 3 - 7
azure/convert/registrycredentials_test.go

@@ -24,8 +24,6 @@ import (
 	"github.com/compose-spec/compose-go/types"
 	cliconfigtypes "github.com/docker/cli/cli/config/types"
 
-	"github.com/docker/api/compose"
-
 	"github.com/Azure/azure-sdk-for-go/profiles/latest/containerinstance/mgmt/containerinstance"
 
 	. "github.com/onsi/gomega"
@@ -173,7 +171,7 @@ func (suite *RegistryConvertTestSuite) TestHubAndSeveralACRRegistries() {
 	}))
 }
 
-func composeServices(images ...string) compose.Project {
+func composeServices(images ...string) types.Project {
 	var services []types.ServiceConfig
 	for index, name := range images {
 		service := types.ServiceConfig{
@@ -182,10 +180,8 @@ func composeServices(images ...string) compose.Project {
 		}
 		services = append(services, service)
 	}
-	return compose.Project{
-		Config: types.Config{
-			Services: services,
-		},
+	return types.Project{
+		Services: services,
 	}
 }
 

+ 4 - 4
cli/cmd/compose/down.go

@@ -20,14 +20,14 @@ import (
 	"context"
 	"errors"
 
+	"github.com/compose-spec/compose-go/cli"
 	"github.com/spf13/cobra"
 
 	"github.com/docker/api/client"
-	"github.com/docker/api/compose"
 )
 
 func downCommand() *cobra.Command {
-	opts := compose.ProjectOptions{}
+	opts := cli.ProjectOptions{}
 	downCmd := &cobra.Command{
 		Use: "down",
 		RunE: func(cmd *cobra.Command, args []string) error {
@@ -35,13 +35,13 @@ func downCommand() *cobra.Command {
 		},
 	}
 	downCmd.Flags().StringVarP(&opts.Name, "project-name", "p", "", "Project name")
-	downCmd.Flags().StringVar(&opts.WorkDir, "workdir", ".", "Work dir")
+	downCmd.Flags().StringVar(&opts.WorkingDir, "workdir", ".", "Work dir")
 	downCmd.Flags().StringArrayVarP(&opts.ConfigPaths, "file", "f", []string{}, "Compose configuration files")
 
 	return downCmd
 }
 
-func runDown(ctx context.Context, opts compose.ProjectOptions) error {
+func runDown(ctx context.Context, opts cli.ProjectOptions) error {
 	c, err := client.New(ctx)
 	if err != nil {
 		return err

+ 4 - 4
cli/cmd/compose/up.go

@@ -20,15 +20,15 @@ import (
 	"context"
 	"errors"
 
+	"github.com/compose-spec/compose-go/cli"
 	"github.com/spf13/cobra"
 
 	"github.com/docker/api/client"
-	"github.com/docker/api/compose"
 	"github.com/docker/api/progress"
 )
 
 func upCommand() *cobra.Command {
-	opts := compose.ProjectOptions{}
+	opts := cli.ProjectOptions{}
 	upCmd := &cobra.Command{
 		Use: "up",
 		RunE: func(cmd *cobra.Command, args []string) error {
@@ -36,7 +36,7 @@ func upCommand() *cobra.Command {
 		},
 	}
 	upCmd.Flags().StringVarP(&opts.Name, "project-name", "p", "", "Project name")
-	upCmd.Flags().StringVar(&opts.WorkDir, "workdir", ".", "Work dir")
+	upCmd.Flags().StringVar(&opts.WorkingDir, "workdir", ".", "Work dir")
 	upCmd.Flags().StringArrayVarP(&opts.ConfigPaths, "file", "f", []string{}, "Compose configuration files")
 	upCmd.Flags().StringArrayVarP(&opts.Environment, "environment", "e", []string{}, "Environment variables")
 	upCmd.Flags().BoolP("detach", "d", true, " Detached mode: Run containers in the background")
@@ -44,7 +44,7 @@ func upCommand() *cobra.Command {
 	return upCmd
 }
 
-func runUp(ctx context.Context, opts compose.ProjectOptions) error {
+func runUp(ctx context.Context, opts cli.ProjectOptions) error {
 	c, err := client.New(ctx)
 	if err != nil {
 		return err

+ 4 - 2
compose/api.go

@@ -18,12 +18,14 @@ package compose
 
 import (
 	"context"
+
+	"github.com/compose-spec/compose-go/cli"
 )
 
 // Service manages a compose project
 type Service interface {
 	// Up executes the equivalent to a `compose up`
-	Up(ctx context.Context, opts ProjectOptions) error
+	Up(ctx context.Context, opts cli.ProjectOptions) error
 	// Down executes the equivalent to a `compose down`
-	Down(ctx context.Context, opts ProjectOptions) error
+	Down(ctx context.Context, opts cli.ProjectOptions) error
 }

+ 0 - 172
compose/project.go

@@ -1,172 +0,0 @@
-/*
-   Copyright 2020 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.
-*/
-
-package compose
-
-import (
-	"fmt"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"regexp"
-	"strings"
-
-	"github.com/compose-spec/compose-go/loader"
-	"github.com/compose-spec/compose-go/types"
-	"github.com/sirupsen/logrus"
-)
-
-var supportedFilenames = []string{
-	"compose.yml",
-	"compose.yaml",
-	"docker-compose.yml",
-	"docker-compose.yaml",
-}
-
-// ProjectOptions configures a compose project
-type ProjectOptions struct {
-	Name        string
-	WorkDir     string
-	ConfigPaths []string
-	Environment []string
-}
-
-// Project represents a compose project with a name
-type Project struct {
-	types.Config
-	projectDir string
-	Name       string `yaml:"-" json:"-"`
-}
-
-// ProjectFromOptions load a compose project based on given options
-func ProjectFromOptions(options *ProjectOptions) (*Project, error) {
-	configPath, err := getConfigPathFromOptions(options)
-	if err != nil {
-		return nil, err
-	}
-
-	configs, err := parseConfigs(configPath)
-	if err != nil {
-		return nil, err
-	}
-
-	name := options.Name
-	if name == "" {
-		r := regexp.MustCompile(`[^a-z0-9\\-_]+`)
-		absPath, err := filepath.Abs(options.WorkDir)
-		if err != nil {
-			return nil, err
-		}
-		name = r.ReplaceAllString(strings.ToLower(filepath.Base(absPath)), "")
-	}
-
-	return newProject(types.ConfigDetails{
-		WorkingDir:  options.WorkDir,
-		ConfigFiles: configs,
-		Environment: getAsEqualsMap(options.Environment),
-	}, name)
-}
-
-func newProject(config types.ConfigDetails, name string) (*Project, error) {
-	model, err := loader.Load(config)
-	if err != nil {
-		return nil, err
-	}
-
-	p := Project{
-		Config:     *model,
-		projectDir: config.WorkingDir,
-		Name:       name,
-	}
-	return &p, nil
-}
-
-func getConfigPathFromOptions(options *ProjectOptions) ([]string, error) {
-	var paths []string
-	pwd := options.WorkDir
-
-	if len(options.ConfigPaths) != 0 {
-		for _, f := range options.ConfigPaths {
-			if f == "-" {
-				paths = append(paths, f)
-				continue
-			}
-			if !filepath.IsAbs(f) {
-				f = filepath.Join(pwd, f)
-			}
-			if _, err := os.Stat(f); err != nil {
-				return nil, err
-			}
-			paths = append(paths, f)
-		}
-		return paths, nil
-	}
-
-	for {
-		var candidates []string
-		for _, n := range supportedFilenames {
-			f := filepath.Join(pwd, n)
-			if _, err := os.Stat(f); err == nil {
-				candidates = append(candidates, f)
-			}
-		}
-		if len(candidates) > 0 {
-			winner := candidates[0]
-			if len(candidates) > 1 {
-				logrus.Warnf("Found multiple config files with supported names: %s", strings.Join(candidates, ", "))
-				logrus.Warnf("Using %s\n", winner)
-			}
-			return []string{winner}, nil
-		}
-		parent := filepath.Dir(pwd)
-		if parent == pwd {
-			return nil, fmt.Errorf("can't find a suitable configuration file in this directory or any parent. Is %q the right directory?", pwd)
-		}
-		pwd = parent
-	}
-}
-
-func parseConfigs(configPaths []string) ([]types.ConfigFile, error) {
-	var files []types.ConfigFile
-	for _, f := range configPaths {
-		var b []byte
-		var err error
-		if f == "-" {
-			b, err = ioutil.ReadAll(os.Stdin)
-		} else {
-			b, err = ioutil.ReadFile(f)
-		}
-		if err != nil {
-			return nil, err
-		}
-		config, err := loader.ParseYAML(b)
-		if err != nil {
-			return nil, err
-		}
-		files = append(files, types.ConfigFile{Filename: f, Config: config})
-	}
-	return files, nil
-}
-
-// getAsEqualsMap split key=value formatted strings into a key : value map
-func getAsEqualsMap(em []string) map[string]string {
-	m := make(map[string]string)
-	for _, v := range em {
-		kv := strings.SplitN(v, "=", 2)
-		m[kv[0]] = kv[1]
-	}
-	return m
-}

+ 0 - 60
compose/project_test.go

@@ -1,60 +0,0 @@
-/*
-   Copyright 2020 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.
-*/
-
-package compose
-
-import (
-	"os"
-	"testing"
-
-	. "github.com/onsi/gomega"
-	"github.com/stretchr/testify/suite"
-)
-
-type ComposeTest struct {
-	suite.Suite
-}
-
-func (suite *ComposeTest) TestParseComposeFile() {
-	files := []string{"../tests/composefiles/aci-demo/aci_demo_port.yaml"}
-	config, err := parseConfigs(files)
-	Expect(err).To(BeNil())
-	services := config[0].Config["services"].(map[string]interface{})
-	Expect(len(services)).To(Equal(3))
-}
-
-func (suite *ComposeTest) TestParseComposeStdin() {
-	files := []string{"-"}
-	f, err := os.Open("../tests/composefiles/aci-demo/aci_demo_port.yaml")
-	Expect(err).To(BeNil())
-	defer func() {
-		err := f.Close()
-		Expect(err).To(BeNil())
-	}()
-	oldStdin := os.Stdin
-	defer func() { os.Stdin = oldStdin }() // Restore original Stdin
-
-	os.Stdin = f
-	config, err := parseConfigs(files)
-	Expect(err).To(BeNil())
-	services := config[0].Config["services"].(map[string]interface{})
-	Expect(len(services)).To(Equal(3))
-}
-
-func TestComposeProject(t *testing.T) {
-	RegisterTestingT(t)
-	suite.Run(t, new(ComposeTest))
-}

+ 5 - 4
example/backend.go

@@ -22,6 +22,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"github.com/compose-spec/compose-go/cli"
 	"io"
 
 	"github.com/docker/api/context/cloud"
@@ -109,8 +110,8 @@ func (cs *containerService) Delete(ctx context.Context, id string, force bool) e
 
 type composeService struct{}
 
-func (cs *composeService) Up(ctx context.Context, opts compose.ProjectOptions) error {
-	prj, err := compose.ProjectFromOptions(&opts)
+func (cs *composeService) Up(ctx context.Context, opts cli.ProjectOptions) error {
+	prj, err := cli.ProjectFromOptions(&opts)
 	if err != nil {
 		return err
 	}
@@ -118,8 +119,8 @@ func (cs *composeService) Up(ctx context.Context, opts compose.ProjectOptions) e
 	return nil
 }
 
-func (cs *composeService) Down(ctx context.Context, opts compose.ProjectOptions) error {
-	prj, err := compose.ProjectFromOptions(&opts)
+func (cs *composeService) Down(ctx context.Context, opts cli.ProjectOptions) error {
+	prj, err := cli.ProjectFromOptions(&opts)
 	if err != nil {
 		return err
 	}

+ 1 - 1
go.mod

@@ -16,7 +16,7 @@ require (
 	github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5
 	github.com/Microsoft/hcsshim v0.8.9 // indirect
 	github.com/buger/goterm v0.0.0-20200322175922-2f3e71b85129
-	github.com/compose-spec/compose-go v0.0.0-20200423124427-63dcf8c22cae
+	github.com/compose-spec/compose-go v0.0.0-20200629133725-59b25574fd55
 	github.com/containerd/console v1.0.0
 	github.com/containerd/containerd v1.3.5 // indirect
 	github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb // indirect

+ 6 - 5
go.sum

@@ -61,8 +61,8 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
-github.com/compose-spec/compose-go v0.0.0-20200423124427-63dcf8c22cae h1:5zRbbF5Gbkl7ZEJrKwYha2JMWgnfpPjSmv8+jCmkeSA=
-github.com/compose-spec/compose-go v0.0.0-20200423124427-63dcf8c22cae/go.mod h1:1PUpzRF1O/65VOqXZuwpCuYY7pJxbIq1jbAvAf62FGM=
+github.com/compose-spec/compose-go v0.0.0-20200629133725-59b25574fd55 h1:vX1uiHKgVnFBPBLxz6P28n+TKGt3jGThvDr2vkSP8D0=
+github.com/compose-spec/compose-go v0.0.0-20200629133725-59b25574fd55/go.mod h1:ArodJ6gsEB7iWKrbV3fSHZ08LlBvSVB0Oqg04fX86t4=
 github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f h1:tSNMc+rJDfmYntojat8lljbt1mgKNpTxUZJsSzJ9Y1s=
 github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
 github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
@@ -179,6 +179,8 @@ github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
 github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
+github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
@@ -214,8 +216,8 @@ github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyex
 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4=
-github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg=
+github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/moby/term v0.0.0-20200611042045-63b9a826fb74 h1:kvRIeqJNICemq2UFLx8q/Pj+1IRNZS0XPTaMFkuNsvg=
 github.com/moby/term v0.0.0-20200611042045-63b9a826fb74/go.mod h1:pJ0Ot5YGdTcMdxnPMyGCfAr6fKXe0g9cDlz16MuFEBE=
 github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
@@ -272,7 +274,6 @@ github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvH
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
-github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
 github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=