1
0
Эх сурвалжийг харах

Load a compose file and pass Project to cobra command

close #2

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De Loof 5 жил өмнө
parent
commit
40bf8c2dae

+ 2 - 0
ecs/go.mod

@@ -35,6 +35,7 @@ require (
 	github.com/miekg/pkcs11 v1.0.3 // indirect
 	github.com/morikuni/aec v1.0.0 // indirect
 	github.com/opencontainers/image-spec v1.0.1 // indirect
+	github.com/sirupsen/logrus v1.5.0
 	github.com/spf13/cobra v0.0.5
 	github.com/spf13/pflag v1.0.3
 	github.com/theupdateframework/notary v0.6.1 // indirect
@@ -44,6 +45,7 @@ require (
 	gopkg.in/dancannon/gorethink.v3 v3.0.5 // indirect
 	gopkg.in/fatih/pool.v2 v2.0.0 // indirect
 	gopkg.in/gorethink/gorethink.v3 v3.0.5 // indirect
+	gotest.tools/v3 v3.0.2
 	vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787 // indirect
 )
 

+ 7 - 0
ecs/go.sum

@@ -140,6 +140,7 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
 github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+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=
@@ -180,6 +181,7 @@ github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/mattn/go-shellwords v1.0.9/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
+github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw=
 github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
 github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
@@ -192,6 +194,7 @@ github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WT
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
 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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -280,9 +283,12 @@ github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPU
 github.com/weppos/publicsuffix-go v0.4.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
 github.com/weppos/publicsuffix-go v0.5.0 h1:rutRtjBJViU/YjcI5d80t4JAVvDltS6bciJg2K1HrLU=
 github.com/weppos/publicsuffix-go v0.5.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
 github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
+github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
 github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 h1:j2hhcujLRHAg872RWAV5yaUrEjHEObwDv3aImCaNLek=
 github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=
@@ -392,6 +398,7 @@ gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
 gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
 gotest.tools/v3 v3.0.0 h1:d+tVGRu6X0ZBQ+kyAR8JKi6AXhTP2gmQaoIYaGFz634=
 gotest.tools/v3 v3.0.0/go.mod h1:TUP+/YtXl/dp++T+SZ5v2zUmLVBHmptSb/ajDLCJ+3c=
+gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E=
 gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

+ 15 - 1
ecs/pkg/compose/opts.go

@@ -1,7 +1,7 @@
 package compose
 
 import (
-	_ "github.com/compose-spec/compose-go/types"
+	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
 )
 
@@ -14,3 +14,17 @@ func (o *ProjectOptions) AddFlags(flags *pflag.FlagSet) {
 	flags.StringArrayVarP(&o.ConfigPaths, "file", "f", nil, "Specify an alternate compose file")
 	flags.StringVarP(&o.name, "project-name", "n", "", "Specify an alternate project name (default: directory name)")
 }
+
+
+type ProjectFunc func(project *Project, args []string) error
+
+// WithProject wrap a ProjectFunc into a cobra command
+func WithProject(options *ProjectOptions, f ProjectFunc) func(cmd *cobra.Command, args []string) error {
+	return func(cmd *cobra.Command, args []string) error {
+		project, err := projectFromOptions(options)
+		if err != nil {
+			return err
+		}
+		return f(project, args)
+	}
+}

+ 165 - 0
ecs/pkg/compose/project.go

@@ -0,0 +1,165 @@
+package compose
+
+import (
+	"fmt"
+	"github.com/compose-spec/compose-go/loader"
+	"github.com/compose-spec/compose-go/types"
+	"github.com/sirupsen/logrus"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"regexp"
+	"strings"
+)
+
+type Project struct {
+	types.Config
+	projectDir string
+	Name       string `yaml:"-" json:"-"`
+}
+
+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
+}
+
+
+// projectFromOptions load a compose project based on command line options
+func projectFromOptions(options *ProjectOptions) (*Project, error) {
+	configPath, err := getConfigPathFromOptions(options)
+	if err != nil {
+		return nil, err
+	}
+
+	name := options.name
+	if name == "" {
+		name = os.Getenv("COMPOSE_PROJECT_NAME")
+	}
+
+	workingDir := filepath.Dir(configPath[0])
+
+	if name == "" {
+		r := regexp.MustCompile(`[^a-z0-9\\-_]+`)
+		name = r.ReplaceAllString(strings.ToLower(filepath.Base(workingDir)), "")
+	}
+
+	configs, err := parseConfigs(configPath)
+	if err != nil {
+		return nil, err
+	}
+
+	return NewProject(types.ConfigDetails{
+		WorkingDir:  workingDir,
+		ConfigFiles: configs,
+		Environment: environment(),
+	}, name)
+}
+
+func getConfigPathFromOptions(options *ProjectOptions) ([]string, error) {
+	paths := []string{}
+	pwd, err := os.Getwd()
+	if err != nil {
+		return nil, err
+	}
+
+	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
+	}
+
+	sep := os.Getenv("COMPOSE_FILE_SEPARATOR")
+	if sep == "" {
+		sep = string(os.PathListSeparator)
+	}
+	f := os.Getenv("COMPOSE_FILE")
+	if f != "" {
+		return strings.Split(f, sep), nil
+	}
+
+	for {
+		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. Are you in the right directory?")
+		}
+		pwd = parent
+	}
+}
+
+var SupportedFilenames = []string{"compose.yaml", "compose.yml", "docker-compose.yml", "docker-compose.yaml"}
+
+func parseConfigs(configPaths []string) ([]types.ConfigFile, error) {
+	files := []types.ConfigFile{}
+	for _, f := range configPaths {
+		var (
+			b   []byte
+			err error
+		)
+		if f == "-" {
+			b, err = ioutil.ReadAll(os.Stdin)
+		} else {
+			if _, err := os.Stat(f); err != nil {
+				return nil, err
+			}
+			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
+}
+
+func environment() map[string]string {
+	return getAsEqualsMap(os.Environ())
+}
+
+// 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
+}

+ 45 - 0
ecs/pkg/compose/project_test.go

@@ -0,0 +1,45 @@
+package compose
+
+import (
+	"gotest.tools/v3/assert"
+	"os"
+	"testing"
+)
+
+func Test_project_name(t *testing.T) {
+	p, err := projectFromOptions(&ProjectOptions{
+		name:        "my_project",
+		ConfigPaths: []string{"testdata/simple/compose.yaml"},
+	})
+	assert.NilError(t, err)
+	assert.Equal(t, p.Name, "my_project")
+
+	p, err = projectFromOptions(&ProjectOptions{
+		name:        "",
+		ConfigPaths: []string{"testdata/simple/compose.yaml"},
+	})
+	assert.NilError(t, err)
+	assert.Equal(t, p.Name, "simple")
+
+	os.Setenv("COMPOSE_PROJECT_NAME", "my_project_from_env")
+	p, err = projectFromOptions(&ProjectOptions{
+		name:        "",
+		ConfigPaths: []string{"testdata/simple/compose.yaml"},
+	})
+	assert.NilError(t, err)
+	assert.Equal(t, p.Name, "my_project_from_env")
+}
+
+func Test_project_from_set_of_files(t *testing.T) {
+	p, err := projectFromOptions(&ProjectOptions{
+		name: "my_project",
+		ConfigPaths: []string{
+			"testdata/simple/compose.yaml",
+			"testdata/simple/compose-with-overrides.yaml",
+		},
+	})
+	assert.NilError(t, err)
+	service, err := p.GetService("simple")
+	assert.NilError(t, err)
+	assert.Equal(t, service.Image, "haproxy")
+}

+ 4 - 0
ecs/pkg/compose/testdata/simple/compose-with-overrides.yaml

@@ -0,0 +1,4 @@
+version: "3"
+services:
+  simple:
+    image: haproxy

+ 4 - 0
ecs/pkg/compose/testdata/simple/compose.yaml

@@ -0,0 +1,4 @@
+version: "3"
+services:
+  simple:
+    image: nginx