diff --git a/.circleci/config.yml b/.circleci/config.yml index 53db76302..ac0159e74 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,7 +41,7 @@ jobs: - run: docker swarm init - run: docker build -t sleep docker-images/sleep/ - run: docker build -t http-server docker-images/http-server/ - - run: env MESG_CORE_IMAGE=mesg/core:$CIRCLE_SHA1 go test -ldflags="-X 'github.com/mesg-foundation/core/config.Path=./'" -timeout 180s -p 1 -coverprofile=coverage.txt ./... # TODO: should remove ldflags when package database is mocked + - run: env MESG_CORE_IMAGE=mesg/core:$CIRCLE_SHA1 go test -timeout 180s -p 1 -coverprofile=coverage.txt ./... - run: bash <(curl -s https://codecov.io/bash) "publish_docker_dev": diff --git a/api/deploy.go b/api/deploy.go index 4c42a22c9..58434bbaa 100644 --- a/api/deploy.go +++ b/api/deploy.go @@ -5,6 +5,7 @@ import ( "io" "io/ioutil" "os" + "path/filepath" "github.com/logrusorgru/aurora" "github.com/mesg-foundation/core/database/services" @@ -87,6 +88,13 @@ func (d *serviceDeployer) FromGitURL(url string) (*service.Service, *importer.Va if err := xgit.Clone(url, path); err != nil { return nil, nil, err } + + // XXX: remove .git folder from repo. + // It makes docker build iamge id same between repo clones. + if err := os.RemoveAll(filepath.Join(path, ".git")); err != nil { + return nil, nil, err + } + d.sendStatus(fmt.Sprintf("%s Service downloaded with success.", aurora.Green("✔")), DONE) r, err := xarchive.GzippedTar(path) if err != nil { diff --git a/commands/service_dev.go b/commands/service_dev.go index bf868cc93..da3263dea 100644 --- a/commands/service_dev.go +++ b/commands/service_dev.go @@ -95,7 +95,7 @@ loop: case e := <-listenEventsC: fmt.Printf("Receive event %s: %s\n", pretty.Success(e.EventKey), - pretty.ColorizeJSON(pretty.FgCyan, nil, []byte(e.EventData)), + pretty.ColorizeJSON(pretty.FgCyan, nil, false, []byte(e.EventData)), ) case err := <-eventsErrC: fmt.Fprintf(os.Stderr, "%s Listening events error: %s", pretty.FailSign, err) @@ -103,7 +103,7 @@ loop: fmt.Printf("Receive result %s %s: %s\n", pretty.Success(r.TaskKey), pretty.Colorize(color.New(color.FgCyan), r.OutputKey), - pretty.ColorizeJSON(pretty.FgCyan, nil, []byte(r.OutputData)), + pretty.ColorizeJSON(pretty.FgCyan, nil, false, []byte(r.OutputData)), ) case err := <-resultsErrC: fmt.Fprintf(os.Stderr, "%s Listening results error: %s", pretty.FailSign, err) diff --git a/config/config.go b/config/config.go index 0769d2e68..170296357 100644 --- a/config/config.go +++ b/config/config.go @@ -2,12 +2,15 @@ package config import ( "fmt" + "os" + "path/filepath" "strings" "sync" "github.com/kelseyhightower/envconfig" "github.com/mesg-foundation/core/version" "github.com/mesg-foundation/core/x/xstrings" + homedir "github.com/mitchellh/go-homedir" "github.com/sirupsen/logrus" ) @@ -18,10 +21,6 @@ var ( once sync.Once ) -// Path to a dedicated directory for Core -// TODO: Path should be reverted to a const when the package database is renovated -var Path = "/mesg" - // Config contains all the configuration needed. type Config struct { Server struct { @@ -39,26 +38,54 @@ type Config struct { Core struct { Image string + Path string + } + + Docker struct { + Socket string + Core struct { + Path string + } } } // New creates a new config with default values. -func New() *Config { +func New() (*Config, error) { + home, err := homedir.Dir() + if err != nil { + return nil, err + } + var c Config c.Server.Address = ":50052" c.Client.Address = "localhost:50052" c.Log.Format = "text" c.Log.Level = "info" c.Core.Image = "mesg/core:" + strings.Split(version.Version, " ")[0] - return &c + c.Core.Path = filepath.Join(home, ".mesg") + c.Docker.Core.Path = "/mesg" + c.Docker.Socket = "/var/run/docker.sock" + return &c, nil } // Global returns a singleton of a Config after loaded ENV and validate the values. func Global() (*Config, error) { + var err error once.Do(func() { - instance = New() - instance.Load() + instance, err = New() + if err != nil { + return + } + if err = instance.Load(); err != nil { + return + } + if err = instance.Prepare(); err != nil { + return + } }) + if err != nil { + return nil, err + } if err := instance.Validate(); err != nil { return nil, err } @@ -66,8 +93,14 @@ func Global() (*Config, error) { } // Load reads config from environmental variables. -func (c *Config) Load() { +func (c *Config) Load() error { envconfig.MustProcess(envPrefix, c) + return nil +} + +// Prepare setups local directories or any other required thing based on config +func (c *Config) Prepare() error { + return os.MkdirAll(c.Core.Path, os.FileMode(0755)) } // Validate checks values and return an error if any validation failed. @@ -86,5 +119,6 @@ func (c *Config) DaemonEnv() map[string]string { return map[string]string{ "MESG_LOG_FORMAT": c.Log.Format, "MESG_LOG_LEVEL": c.Log.Level, + "MESG_CORE_PATH": c.Docker.Core.Path, } } diff --git a/config/config_test.go b/config/config_test.go index ac36b1098..47dc6b0cf 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -2,18 +2,25 @@ package config import ( "os" + "path/filepath" "strings" "testing" + homedir "github.com/mitchellh/go-homedir" "github.com/stretchr/testify/require" ) func TestDefaultValue(t *testing.T) { - c := New() + home, _ := homedir.Dir() + c, err := New() + require.NoError(t, err) require.Equal(t, ":50052", c.Server.Address) require.Equal(t, "localhost:50052", c.Client.Address) require.Equal(t, "text", c.Log.Format) require.Equal(t, "info", c.Log.Level) + require.Equal(t, filepath.Join(home, ".mesg"), c.Core.Path) + require.Equal(t, "/mesg", c.Docker.Core.Path) + require.Equal(t, "/var/run/docker.sock", c.Docker.Socket) require.True(t, strings.HasPrefix(c.Core.Image, "mesg/core:")) } @@ -45,7 +52,7 @@ func TestLoad(t *testing.T) { os.Setenv("MESG_LOG_FORMAT", "test_log_format") os.Setenv("MESG_LOG_LEVEL", "test_log_level") os.Setenv("MESG_CORE_IMAGE", "test_core_image") - c := New() + c, _ := New() c.Load() require.Equal(t, "test_server_address", c.Server.Address) require.Equal(t, "test_client_address", c.Client.Address) @@ -55,20 +62,20 @@ func TestLoad(t *testing.T) { } func TestValidate(t *testing.T) { - c := New() + c, _ := New() require.NoError(t, c.Validate()) - c = New() + c, _ = New() c.Log.Format = "wrongValue" require.Error(t, c.Validate()) - c = New() + c, _ = New() c.Log.Level = "wrongValue" require.Error(t, c.Validate()) } func TestDaemonEnv(t *testing.T) { - c := New() + c, _ := New() env := c.DaemonEnv() require.Equal(t, c.Log.Format, env["MESG_LOG_FORMAT"]) require.Equal(t, c.Log.Level, env["MESG_LOG_LEVEL"]) diff --git a/daemon/const.go b/daemon/const.go index aa0dd50b9..f0c88651c 100644 --- a/daemon/const.go +++ b/daemon/const.go @@ -1,9 +1,7 @@ package daemon const ( - name = "core" - dockerSocket = "/var/run/docker.sock" - volume = "mesg-core" + name = "core" ) // Namespace returns the namespace of the MESG Core. diff --git a/daemon/start.go b/daemon/start.go index 629fa5e00..ceebf6fc4 100644 --- a/daemon/start.go +++ b/daemon/start.go @@ -38,13 +38,14 @@ func serviceSpec() (spec container.ServiceOptions, err error) { Env: container.MapToEnv(c.DaemonEnv()), Mounts: []container.Mount{ { - Source: dockerSocket, - Target: dockerSocket, + Source: c.Docker.Socket, + Target: c.Docker.Socket, Bind: true, }, { - Source: volume, - Target: config.Path, + Source: c.Core.Path, + Target: c.Docker.Core.Path, + Bind: true, }, }, Ports: []container.Port{ diff --git a/daemon/start_test.go b/daemon/start_test.go index 541327933..64a412f0d 100644 --- a/daemon/start_test.go +++ b/daemon/start_test.go @@ -33,37 +33,24 @@ func startForTest() { return } -// func TestStart(t *testing.T) { -// <-testForceAndWaitForFullStop() -// service, err := Start() -// require.Nil(t, err) -// require.NotNil(t, service) -// } - -func contains(list []string, item string) bool { - for _, itemInList := range list { - if itemInList == item { - return true - } - } - return false -} - func TestStartConfig(t *testing.T) { + c, _ := config.Global() spec, err := serviceSpec() - require.Nil(t, err) + require.NoError(t, err) // Make sure that the config directory is passed in parameter to write on the same folder - require.True(t, contains(spec.Env, "MESG_LOG_LEVEL=info")) - require.True(t, contains(spec.Env, "MESG_LOG_FORMAT=text")) + require.Contains(t, spec.Env, "MESG_LOG_LEVEL=info") + require.Contains(t, spec.Env, "MESG_LOG_FORMAT=text") + require.Contains(t, spec.Env, "MESG_CORE_PATH="+c.Docker.Core.Path) // Ensure that the port is shared - c, err := config.Global() - _, port, err := xnet.SplitHostPort(c.Server.Address) + _, port, _ := xnet.SplitHostPort(c.Server.Address) require.Equal(t, spec.Ports[0].Published, uint32(port)) require.Equal(t, spec.Ports[0].Target, uint32(port)) // Ensure that the docker socket is shared in the core - require.Equal(t, spec.Mounts[0].Source, dockerSocket) - require.Equal(t, spec.Mounts[0].Target, dockerSocket) + require.Equal(t, spec.Mounts[0].Source, c.Docker.Socket) + require.Equal(t, spec.Mounts[0].Target, c.Docker.Socket) + require.True(t, spec.Mounts[0].Bind) // Ensure that the host users folder is sync with the core - require.Equal(t, spec.Mounts[1].Source, volume) - require.Equal(t, spec.Mounts[1].Target, config.Path) + require.Equal(t, spec.Mounts[1].Source, c.Core.Path) + require.Equal(t, spec.Mounts[1].Target, c.Docker.Core.Path) + require.True(t, spec.Mounts[1].Bind) } diff --git a/database/services/db.go b/database/services/db.go index c40f7c6e1..b26bc2bf4 100644 --- a/database/services/db.go +++ b/database/services/db.go @@ -16,10 +16,14 @@ func open() (db *leveldb.DB, err error) { instanceMutex.Lock() defer instanceMutex.Unlock() if _instance == nil { - storagePath := filepath.Join(config.Path, "database", "services") + c, err := config.Global() + if err != nil { + return nil, err + } + storagePath := filepath.Join(c.Core.Path, "database", "services") _instance, err = leveldb.OpenFile(storagePath, nil) if err != nil { - panic(err) // TODO: this should just be returned? + return nil, err } } instances++ diff --git a/utils/pretty/pretty.go b/utils/pretty/pretty.go index 26362114f..1fe48ea1f 100644 --- a/utils/pretty/pretty.go +++ b/utils/pretty/pretty.go @@ -209,14 +209,16 @@ func (p *Pretty) Colorize(c *color.Color, msg string) string { // ColorizeJSON colors keys and values of stringified JSON. On errors the original string is returned. // If color is nil then key/value won't be colorize. -func (p *Pretty) ColorizeJSON(keyColor *color.Color, valueColor *color.Color, data []byte) []byte { +func (p *Pretty) ColorizeJSON(keyColor *color.Color, valueColor *color.Color, multiline bool, data []byte) []byte { if p.noColor { return data } f := prettyjson.NewFormatter() - f.Indent = 0 - f.Newline = "" + if !multiline { + f.Indent = 0 + f.Newline = "" + } f.KeyColor = keyColor f.StringColor = valueColor @@ -358,8 +360,8 @@ func Progress(message string, fn func()) { pg.Progress(message, fn) } // ColorizeJSON colors keys and values of stringified JSON. On errors the original string is returned. // If color is nil then key/value won't be colorize. -func ColorizeJSON(keyColor *color.Color, valueColor *color.Color, data []byte) []byte { - return pg.ColorizeJSON(keyColor, valueColor, data) +func ColorizeJSON(keyColor *color.Color, valueColor *color.Color, multiline bool, data []byte) []byte { + return pg.ColorizeJSON(keyColor, valueColor, multiline, data) } // FgColors returns a slice with predefiend foreground color.