From 62934d5d94f418669f2a960480ca143f11703921 Mon Sep 17 00:00:00 2001 From: krhubert Date: Fri, 14 Dec 2018 16:03:49 +0100 Subject: [PATCH] Replace env.Parametr with key=value syntax --- container/service_options.go | 14 ---------- daemon/daemon.go | 2 +- interface/grpc/core/deploy.go | 12 ++++++--- service/dependency.go | 7 ++--- service/importer/assets/schema.go | 4 +-- service/importer/assets/schema.json | 6 ++++- service/importer/definition.go | 4 +-- service/inject_definition.go | 10 ++----- service/service.go | 31 ++++++++++++++++------ service/service_test.go | 4 +-- service/start.go | 10 +++---- service/start_test.go | 11 ++++---- x/xos/env.go | 41 ++++++++++++++++++++++------- x/xos/env_test.go | 36 ++++++++++++++++++++----- 14 files changed, 119 insertions(+), 73 deletions(-) diff --git a/container/service_options.go b/container/service_options.go index 579d85b8a..36741e0b5 100644 --- a/container/service_options.go +++ b/container/service_options.go @@ -1,9 +1,7 @@ package container import ( - "fmt" "os" - "sort" "strconv" "strings" @@ -137,15 +135,3 @@ func mergeLabels(l1 map[string]string, l2 map[string]string) map[string]string { } return l1 } - -// MapToEnv transform a map of key value to a array of env string. -// env vars sorted by names to get an accurate order while testing, otherwise -// comparing a string slice with different orders will fail. -func MapToEnv(data map[string]string) []string { - env := make([]string, 0, len(data)) - for key, value := range data { - env = append(env, fmt.Sprintf("%s=%s", key, value)) - } - sort.Strings(env) - return env -} diff --git a/daemon/daemon.go b/daemon/daemon.go index 09184a1de..3bf5e0bc1 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -62,7 +62,7 @@ func (d *ContainerDaemon) buildServiceOptions(sharedNetworkID string) container. return container.ServiceOptions{ Namespace: []string{}, Image: d.cfg.Core.Image, - Env: xos.MapToEnv(d.cfg.DaemonEnv()), + Env: xos.EnvMapToSlice(d.cfg.DaemonEnv()), Mounts: []container.Mount{ { Source: d.cfg.Docker.Socket, diff --git a/interface/grpc/core/deploy.go b/interface/grpc/core/deploy.go index 270b3c1c0..7e69bed12 100644 --- a/interface/grpc/core/deploy.go +++ b/interface/grpc/core/deploy.go @@ -15,6 +15,7 @@ import ( func (s *Server) DeployService(stream coreapi.Core_DeployServiceServer) error { var ( statuses = make(chan api.DeployStatus) + option = api.DeployServiceStatusOption(statuses) wg sync.WaitGroup service *service.Service @@ -34,18 +35,22 @@ func (s *Server) DeployService(stream coreapi.Core_DeployServiceServer) error { return err } - // env must go always with first package + // env must be set with first package (always) env := in.GetEnv() if url := in.GetUrl(); url != "" { - service, validationError, err = s.api.DeployServiceFromURL(url, env, api.DeployServiceStatusOption(statuses)) + service, validationError, err = s.api.DeployServiceFromURL(url, env, option) } else { + // create tarball reader with first chunk of bytes tarball := &deployChunkReader{ stream: stream, buf: in.GetChunk(), } - service, validationError, err = s.api.DeployService(tarball, env, api.DeployServiceStatusOption(statuses)) + service, validationError, err = s.api.DeployService(tarball, env, option) } + + // wait for statuses to be sent first, otherwise sending multiple messages at the + // same time may cause messages to be sent in different order. wg.Wait() if err != nil { @@ -88,6 +93,7 @@ func sendDeployStatus(statuses chan api.DeployStatus, stream coreapi.Core_Deploy } } +// deployChunkReader implements io.Reader for stream chunks. type deployChunkReader struct { stream coreapi.Core_DeployServiceServer diff --git a/service/dependency.go b/service/dependency.go index 21a2c9bf6..a53bcbd22 100644 --- a/service/dependency.go +++ b/service/dependency.go @@ -31,11 +31,8 @@ type Dependency struct { // Argument holds the args to pass to the Docker container Args []string `hash:"name:7"` - // Env is map of all defiend environments variables. - Env []*Parameter `hash:"name:8"` - - // EnvValue is map of all values for Env - EnvValue map[string]string `hash:"name:9"` + // Env is a slice of all key=value. + Env []string `hash:"name:8"` // service is the dependency's service. service *Service `hash:"-"` diff --git a/service/importer/assets/schema.go b/service/importer/assets/schema.go index 91a3c3313..b822d6de4 100644 --- a/service/importer/assets/schema.go +++ b/service/importer/assets/schema.go @@ -66,7 +66,7 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } -var _serviceImporterAssetsSchemaJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x57\x4f\x4f\xdc\x3e\x10\xbd\xe7\x53\x58\x86\xdb\xef\xb7\x2c\x55\xa5\x4a\xec\xad\xbd\x55\xaa\x0a\x12\xb7\x42\x5a\x99\x64\xb2\x98\x26\xb6\xf1\x1f\xd4\x2d\xda\xef\x5e\xc5\xd9\xcd\x3a\xb1\x9d\x04\xd8\xa2\xaa\xdb\x9c\x12\x8f\x67\xc6\xef\xcd\xf8\xd9\x79\x4c\x10\xc2\xc7\x2a\xbb\x85\x8a\xe0\x05\xc2\xb7\x5a\x8b\xc5\x7c\x7e\xa7\x38\x9b\x35\xa3\x27\x5c\x2e\xe7\xb9\x24\x85\x9e\x9d\xbe\x9b\x37\x63\x47\xf8\xff\xda\x4f\xaf\x04\xd4\x4e\xfc\xe6\x0e\x32\xdd\x8c\x49\xb8\x37\x54\x42\x8e\x17\xe8\x2a\x41\x08\x21\xcc\x48\x05\x38\x41\x28\xb5\x76\x92\xe7\x54\x53\xce\x48\x79\x21\xb9\x00\xa9\x29\x28\xbc\x40\x05\x29\x15\xd8\x09\xc2\x1d\x7e\x74\x42\x6c\xbf\x9c\xc4\x4a\x4b\xca\x96\x36\xb1\x1d\xaf\x28\xfb\x04\x6c\xa9\x6f\xf1\x02\xbd\xb1\x83\xeb\xc6\x86\x15\xcd\x27\x05\x20\x3f\xda\x00\x6f\xcf\xda\x61\x41\xb4\x06\xc9\x6a\x8f\xaf\x57\xa7\xb3\x33\x32\xfb\xf9\x7e\xf6\xe5\xfa\xfa\xe4\xdb\x2c\xfd\xef\x18\x77\x32\xe5\xa0\x32\x49\x45\x8d\x71\x20\x63\xc7\x45\x82\xe0\x8a\x6a\x2e\x57\x53\x3d\xe0\x01\x98\x56\xee\x6c\xce\xe0\xbc\x68\x49\xaf\x9f\xc7\x5d\x08\x66\xca\x12\x6f\x9d\xad\xad\x7d\x0b\x97\xb1\xb5\x8d\x95\xab\x9d\xb8\xa1\xe8\xc2\xaf\x5e\x3b\xc5\xa1\xae\xe1\xad\x3f\xa3\x6e\x45\x09\x35\x0a\x7c\x34\xcf\xa1\xa0\xcc\xe6\x56\x73\x0b\x17\x77\xe6\xae\x93\xd0\xfb\xf6\x2d\xed\xb0\xa5\x89\xfa\x7e\x38\x64\xd5\x68\x9f\xcf\x55\x0e\x02\x58\x0e\x2c\xeb\xae\x2a\x86\x7b\x12\xe6\x11\xbc\x63\x58\x23\x38\xdb\x95\xae\xba\x55\xf0\x14\xa8\xb5\xd0\x8a\x2c\xc1\xa5\x26\xf5\xc8\x58\x77\xc8\xc8\x38\x2b\xe8\xd2\x48\xd2\xdf\xcd\xa3\x2b\x4a\xb6\xc1\x6c\x28\xec\xcc\xda\xa9\x9a\x20\x92\x54\xa0\x41\xfe\x8d\xbd\x39\xb0\x96\x27\xad\x67\xb8\xa4\xbb\x54\xbd\xe1\xd4\x8b\x11\x38\x57\x3a\xf6\xde\x19\x13\x04\xe3\x4a\xb1\xfb\xac\xfb\xd9\xa2\xe7\xc0\x8b\xc3\x72\xd1\xb0\x36\x16\xf3\x86\xf3\x12\x08\x9b\x16\x74\xe3\x34\x65\x91\xbe\x77\x7d\x1c\x31\x53\x05\x6b\x63\xad\x97\x71\x4f\x84\xf0\x67\x53\xdd\x80\x8c\x59\x3f\x6c\x60\x44\xcc\xe7\x4d\x77\x05\x8c\x5e\x03\xa0\xa6\x28\x05\x31\xa5\xae\xf1\x5c\xc6\x48\x4f\x86\xbe\x9f\x7a\xec\xec\x4d\x42\x83\x5b\x00\x73\xa3\x85\xd1\x6a\x8b\x22\xdd\x29\x6e\x44\x6a\x03\x5d\x1e\x6d\x43\xa7\x53\x06\xba\x79\x92\x3b\x65\x76\x99\x93\x74\xdd\x91\xc5\x60\x2c\x05\x99\x84\x7d\x05\xdb\x12\x18\xc1\x74\x60\x4a\x9a\x13\x4d\x0e\x46\x49\x2d\xd8\x48\xbc\xe9\xcd\xd4\x66\x78\x81\x6c\xac\xfd\xbb\xfd\x6f\xd6\x0d\xb7\xd0\x7f\xac\x68\x04\x0a\xf4\x94\x5d\x1e\x24\xd7\xb9\xa2\xed\xed\x72\x1b\x61\xad\xb9\x6d\x0e\xe3\xee\xe8\x85\xff\xf7\xda\x67\x24\xe3\x55\x45\x58\xfe\x1c\x32\x89\x5c\x7a\x2a\xe7\x5f\x35\xd1\xc8\x75\x13\xf5\xae\x9c\x9d\xf4\x44\x4a\xb2\xf2\xf5\x89\x6a\xa8\x22\x4a\x31\xbc\x6d\xe3\x5b\x28\x0d\x42\x7c\xe0\xa5\xa9\x3c\x51\x7a\x25\x94\x86\xd1\x7b\x03\x1f\x37\x58\xb5\x34\xbe\xf6\xbe\x36\x11\x85\xe4\xd5\x3f\x32\x04\x97\xfe\xf9\x7e\x78\x34\x00\x7b\x18\xb9\xe4\xc4\x94\xb3\xfe\x89\x4d\xd6\xc9\xaf\x00\x00\x00\xff\xff\xa6\x34\xa7\xa3\x2a\x14\x00\x00") +var _serviceImporterAssetsSchemaJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x57\x5d\x4f\xdb\x3c\x14\xbe\xcf\xaf\xb0\x0c\x77\xef\x5b\xca\x34\x69\x12\xbd\xdb\xee\x26\x4d\x03\x89\xbb\x41\x36\x99\xe4\xa4\x98\x25\xb6\xf1\x07\x5a\x87\xfa\xdf\xa7\x38\x6d\xea\xc4\x76\x12\xa0\x43\xd3\xba\x5e\xb5\x3e\x3e\x1f\xcf\x73\x8e\x1f\xbb\x8f\x09\x42\xf8\x58\x65\xb7\x50\x11\xbc\x40\xf8\x56\x6b\xb1\x98\xcf\xef\x14\x67\xb3\x66\xf5\x84\xcb\xe5\x3c\x97\xa4\xd0\xb3\xd3\x77\xf3\x66\xed\x08\xff\x5f\xfb\xe9\x95\x80\xda\x89\xdf\xdc\x41\xa6\x9b\x35\x09\xf7\x86\x4a\xc8\xf1\x02\x5d\x25\x08\x21\x84\x19\xa9\x00\x27\x08\xa5\xd6\x4e\xf2\x9c\x6a\xca\x19\x29\x2f\x24\x17\x20\x35\x05\x85\x17\xa8\x20\xa5\x02\xbb\x41\xb8\xcb\x8f\x4e\x88\xed\x2f\x27\xb1\xd2\x92\xb2\xa5\x4d\x6c\xd7\x2b\xca\x3e\x01\x5b\xea\x5b\xbc\x40\x6f\xec\xe2\xba\xb1\x61\x45\xf3\x49\x01\xc8\x8f\x36\xc0\xdb\xb3\x76\x59\x10\xad\x41\xb2\xda\xe3\xeb\xd5\xe9\xec\x8c\xcc\x7e\xbe\x9f\x7d\xb9\xbe\x3e\xf9\x36\x4b\xff\x3b\xc6\x9d\x4c\x39\xa8\x4c\x52\x51\x63\x1c\xc8\xd8\x71\x91\x20\xb8\xa2\x9a\xcb\xd5\x54\x0f\x78\x00\xa6\x95\xbb\x9b\x33\x38\x2f\x5a\xd2\xeb\xcf\xe3\x2e\x04\x33\x65\x89\xb7\xce\xd6\xd6\x7e\x0b\xb7\xb1\xb5\x8d\xb5\xab\xdd\xb8\xa1\xe8\xc2\xef\x5e\xbb\xc5\xa1\xae\xe1\xad\xbf\xa3\x1e\x45\x09\x35\x0a\x7c\x34\xcf\xa1\xa0\xcc\xe6\x56\x73\x0b\x17\x77\xf6\xae\x93\xd0\xf7\xed\xb7\xb4\xc3\x96\x26\xea\xfb\xe1\x90\x55\xa3\x7d\x3e\x57\x39\x08\x60\x39\xb0\xac\x5b\x55\x0c\xf7\x24\xcc\x23\x78\xc7\xb0\x46\x70\xb6\x95\xae\xba\x5d\xf0\x14\xa8\xb5\xd0\x8a\x2c\xc1\xa5\x26\xf5\xc8\x58\x77\xc8\xc8\x38\x2b\xe8\xd2\x48\xd2\x3f\xcd\xa3\x15\x25\xdb\x60\x36\x14\x76\x76\xed\x54\x4d\x10\x49\x2a\xd0\x20\xff\xc6\xd9\x1c\xa8\xe5\x49\xf5\x0c\xb7\x74\x97\xaa\xb7\x9c\x7a\x31\x02\xf7\x4a\xc7\xde\xbb\x63\x82\x60\x5c\x29\x76\x3f\xeb\x7e\xb6\xe8\x3d\xf0\xe2\xb0\x5c\x34\xac\x8d\xc5\xbc\xe1\xbc\x04\xc2\xa6\x05\xdd\x38\x4d\x29\xd2\xf7\xae\xaf\x23\x66\xaa\x60\x6f\xac\xf5\x32\xee\x89\x10\xfe\x6c\xaa\x1b\x90\x31\xeb\x87\x0d\x8c\x88\xf9\xbc\x99\xae\x80\xd1\x1b\x00\xd4\x34\xa5\x20\xa6\xd4\x35\x9e\xcb\x18\xe9\xc9\xd0\xef\xa7\x5e\x3b\x7b\x93\xd0\xe0\x11\xc0\xdc\x68\x61\xb4\xda\xa2\x48\x77\x8a\x1b\x91\xda\xc0\x94\x47\xc7\xd0\x99\x94\x81\x69\x9e\xe4\x4e\x99\x2d\x73\x92\xae\x3b\xb2\x18\x8c\xa5\x20\x93\xb0\xaf\x60\x5b\x02\x23\x98\x0e\x4c\x49\x73\xa2\xc9\xc1\x28\xa9\x05\x1b\x89\x37\x7d\x98\xda\x0c\x2f\x90\x8d\xb5\xff\xb6\xff\xcd\xba\xe1\x36\xfa\x8f\x15\x8d\x40\x83\x9e\x72\xca\x83\xe4\x3a\x4f\xb4\xbd\x3d\x6e\x23\xac\x35\xaf\xcd\x61\xdc\x1d\xbd\xf0\xff\xbd\xf6\x19\xc9\x78\x55\x11\x96\x3f\x87\x4c\x22\x97\x9e\xca\xf9\x4f\x4d\x34\xf2\xdc\x44\xbd\x27\x67\x27\x3d\x91\x92\xac\x7c\x7d\xa2\x1a\xaa\x88\x52\x0c\x1f\xdb\xf8\x11\x4a\x83\x10\x1f\x78\x69\x2a\x4f\x94\x5e\x09\xa5\x61\xf4\xde\xc0\xc7\x0d\x56\x2d\x8d\xaf\xbd\xaf\x4d\x44\x21\x79\xf5\x8f\x0c\xc1\xa5\x7f\xbf\x1f\x1e\x0d\xc0\x1e\x62\xb2\xe1\x57\x3f\x52\x79\xa4\xea\x81\x8a\xa3\x77\x5e\xfd\x0f\x39\x59\x27\xbf\x02\x00\x00\xff\xff\x19\x00\xfb\x06\x87\x14\x00\x00") func serviceImporterAssetsSchemaJsonBytes() ([]byte, error) { return bindataRead( @@ -81,7 +81,7 @@ func serviceImporterAssetsSchemaJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "service/importer/assets/schema.json", size: 5162, mode: os.FileMode(420), modTime: time.Unix(1544626110, 0)} + info := bindataFileInfo{name: "service/importer/assets/schema.json", size: 5255, mode: os.FileMode(420), modTime: time.Unix(1544790675, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/service/importer/assets/schema.json b/service/importer/assets/schema.json index 192cd1dd6..7bb0e2b2f 100644 --- a/service/importer/assets/schema.json +++ b/service/importer/assets/schema.json @@ -227,7 +227,11 @@ ] }, "env": { - "type": "object" + "type": "array", + "uniqueItems": true, + "items": { + "type": "string" + } } } } diff --git a/service/importer/definition.go b/service/importer/definition.go index f4284d620..037af5949 100644 --- a/service/importer/definition.go +++ b/service/importer/definition.go @@ -61,8 +61,8 @@ type Dependency struct { // Args hold the args to pass to the Docker container Args []string `yaml:"args"` - // Env is map of all defiend environments variables. - Env map[string]*Parameter `yaml:"env"` + // Env is a slice of all defiend environments variables in format key=value. + Env []string `yaml:"env"` } // Task describes a service task. diff --git a/service/inject_definition.go b/service/inject_definition.go index d5ed4dcd3..d9f517b2d 100644 --- a/service/inject_definition.go +++ b/service/inject_definition.go @@ -8,7 +8,7 @@ import ( ) // injectDefinition applies service definition to Service type. -func (s *Service) injectDefinition(def *importer.ServiceDefinition, env map[string]string) { +func (s *Service) injectDefinition(def *importer.ServiceDefinition) { s.Name = def.Name s.SID = def.SID s.Description = def.Description @@ -17,11 +17,6 @@ func (s *Service) injectDefinition(def *importer.ServiceDefinition, env map[stri s.Tasks = s.defTasksToService(def.Tasks) s.Dependencies = s.defDependenciesToService(def.Dependencies) - // insert env into dependencies - for _, d := range s.Dependencies { - d.EnvValue = env - } - s.configuration = &Dependency{} if def.Configuration != nil { s.configuration.Command = def.Configuration.Command @@ -29,8 +24,7 @@ func (s *Service) injectDefinition(def *importer.ServiceDefinition, env map[stri s.configuration.Ports = def.Configuration.Ports s.configuration.Volumes = def.Configuration.Volumes s.configuration.VolumesFrom = def.Configuration.VolumesFrom - s.configuration.Env = s.defParametersToService(def.Configuration.Env) - s.configuration.EnvValue = env + s.configuration.Env = def.Configuration.Env } } diff --git a/service/service.go b/service/service.go index e21720136..18f80d100 100644 --- a/service/service.go +++ b/service/service.go @@ -5,11 +5,13 @@ import ( "io" "io/ioutil" "os" + "strings" "time" "github.com/docker/docker/pkg/archive" "github.com/mesg-foundation/core/container" "github.com/mesg-foundation/core/service/importer" + "github.com/mesg-foundation/core/x/xos" "github.com/mesg-foundation/core/x/xstructhash" uuid "github.com/satori/go.uuid" ) @@ -107,12 +109,18 @@ func New(tarball io.Reader, env map[string]string, options ...Option) (*Service, if err != nil { return nil, err } - s.injectDefinition(def, env) - if err := s.validateEnv(); err != nil { + s.injectDefinition(def) + + if err := s.validateConfigurationEnv(env); err != nil { return nil, err } + // if the all keys exists append to configuration env. + // The order of variables allows to safely append new variables + // without removing previous one. The last variable in slice will take precedens. + s.configuration.Env = append(s.configuration.Env, xos.EnvMapToSlice(env)...) + if err := s.deploy(); err != nil { return nil, err } @@ -244,13 +252,20 @@ func (s *Service) getDependency(dependencyKey string) (*Dependency, error) { return nil, fmt.Errorf("dependency %s do not exist", dependencyKey) } -func (s *Service) validateEnv() error { - for _, p := range s.configuration.Env { - if p.Optional { - continue +// validateConfigurationEnv checks if every variable from env map +// has been defiend in mesg.yml in env section. +func (s *Service) validateConfigurationEnv(env map[string]string) error { + for key := range env { + exist := false + // check if "key=" exists in configuration + for _, env := range s.configuration.Env { + if strings.HasPrefix(env, key+"=") { + exist = true + break + } } - if s.configuration.EnvValue[p.Key] == "" { - return fmt.Errorf("service env %s is empty", p.Key) + if !exist { + return fmt.Errorf("service environment variable %q dosen't exist in mesg.yml (under configuration.env key)", key) } } return nil diff --git a/service/service_test.go b/service/service_test.go index b9cb7dfed..eda158847 100644 --- a/service/service_test.go +++ b/service/service_test.go @@ -89,7 +89,7 @@ func TestInjectDefinitionWithConfig(t *testing.T) { Configuration: &importer.Dependency{ Command: command, }, - }, nil) + }) require.Equal(t, command, s.configuration.Command) } @@ -104,6 +104,6 @@ func TestInjectDefinitionWithDependency(t *testing.T) { Image: image, }, }, - }, nil) + }) require.Equal(t, s.Dependencies[0].Image, image) } diff --git a/service/start.go b/service/start.go index 872ccd5a2..86afc0d77 100644 --- a/service/start.go +++ b/service/start.go @@ -69,11 +69,11 @@ func (d *Dependency) Start(networkID string) (containerServiceID string, err err Image: d.Image, Args: d.Args, Command: d.Command, - Env: xos.MapToEnv(xos.MergeMapEnvs(map[string]string{ - "MESG_TOKEN": d.service.Hash, - "MESG_ENDPOINT": endpoint, - "MESG_ENDPOINT_TCP": endpoint, - }, d.EnvValue)), + Env: xos.EnvMergeSlices(d.Env, []string{ + "MESG_TOKEN=" + d.service.Hash, + "MESG_ENDPOINT=" + endpoint, + "MESG_ENDPOINT_TCP=" + endpoint, + }), Mounts: append(volumes, volumesFrom...), Ports: d.extractPorts(), Networks: []container.Network{ diff --git a/service/start_test.go b/service/start_test.go index 6ab87a080..b1c4f1ebb 100644 --- a/service/start_test.go +++ b/service/start_test.go @@ -9,7 +9,6 @@ import ( "github.com/mesg-foundation/core/container" "github.com/mesg-foundation/core/container/mocks" "github.com/mesg-foundation/core/x/xnet" - "github.com/mesg-foundation/core/x/xos" "github.com/stretchr/testify/require" ) @@ -319,11 +318,11 @@ func mockStartService(d *Dependency, mc *mocks.Container, Image: d.Image, Command: d.Command, Args: d.Args, - Env: xos.MapToEnv(map[string]string{ - "MESG_TOKEN": d.service.Hash, - "MESG_ENDPOINT": endpoint, - "MESG_ENDPOINT_TCP": endpoint, - }), + Env: []string{ + "MESG_TOKEN=" + d.service.Hash, + "MESG_ENDPOINT=" + endpoint, + "MESG_ENDPOINT_TCP=" + endpoint, + }, Mounts: append(volumes, volumesFrom...), Ports: d.extractPorts(), Networks: []container.Network{ diff --git a/x/xos/env.go b/x/xos/env.go index cd4386e8e..f74b45721 100644 --- a/x/xos/env.go +++ b/x/xos/env.go @@ -1,9 +1,9 @@ package xos import ( - "fmt" "os" "sort" + "strings" ) // GetenvDefault retrieves the value of the environment variable named by the key. @@ -15,25 +15,48 @@ func GetenvDefault(key, fallback string) string { return fallback } -// MapToEnv transform a map of key value to a slice of env in the form "key=value". +// EnvMapToSlice transform a map of key value to a slice of env in the form "key=value". // Env vars are sorted by names to get an accurate order while testing. -func MapToEnv(data map[string]string) []string { - env := make([]string, 0, len(data)) - for key, value := range data { - env = append(env, fmt.Sprintf("%s=%s", key, value)) +func EnvMapToSlice(values map[string]string) []string { + env := make([]string, 0, len(values)) + for k, v := range values { + env = append(env, k+"="+v) } sort.Strings(env) return env } -// MergeMapEnvs merges multiple maps storing environment varialbes into single one. +// EnvSliceToMap transform a slice of key=value to a map. +func EnvSliceToMap(values []string) map[string]string { + env := make(map[string]string, len(values)) + for _, v := range values { + if e := strings.SplitN(v, "=", 2); len(e) == 1 { + env[e[0]] = "" + } else { + env[e[0]] = e[1] + } + } + return env +} + +// EnvMergeMaps merges multiple maps into single one. // If the same key exist multiple time, it will be overwritten by the latest occurrence. -func MergeMapEnvs(envs ...map[string]string) map[string]string { +func EnvMergeMaps(values ...map[string]string) map[string]string { env := make(map[string]string) - for _, e := range envs { + for _, e := range values { for k, v := range e { env[k] = v } } return env } + +// EnvMergeSlices merges multiple slices into single one. +// If the same key exist multiple time, it will be added in occurrence order. +func EnvMergeSlices(values ...[]string) []string { + env := make([]string, 0, 16) + for _, v := range values { + env = append(env, v...) + } + return env +} diff --git a/x/xos/env_test.go b/x/xos/env_test.go index b0490b379..435ea22d6 100644 --- a/x/xos/env_test.go +++ b/x/xos/env_test.go @@ -23,19 +23,29 @@ func TestGetenvDefault(t *testing.T) { } } -func TestMapToEnv(t *testing.T) { - env := MapToEnv(map[string]string{ +func TestEnvMapToSlice(t *testing.T) { + env := EnvMapToSlice(map[string]string{ "a": "1", "b": "2", }) for _, v := range []string{"a=1", "b=2"} { if !xstrings.SliceContains(env, v) { - t.Errorf("envs dosen't contain %s", v) + t.Errorf("env slice dosen't contain %s", v) } } } -func TestMergeMapEnvs(t *testing.T) { - envs := []map[string]string{ + +func TestEnvSliceToMap(t *testing.T) { + env := EnvSliceToMap([]string{"a=1", "b=2"}) + for k, v := range map[string]string{"a": "1", "b": "2"} { + if env[k] != v { + t.Errorf("env map dosen't contain %s=%v", k, v) + } + } +} + +func TestEnvMergeMaps(t *testing.T) { + values := []map[string]string{ { "a": "1", "b": "2", @@ -45,10 +55,22 @@ func TestMergeMapEnvs(t *testing.T) { "c": "3", }, } - env := MergeMapEnvs(envs...) + env := EnvMergeMaps(values...) for k, v := range map[string]string{"a": "2", "b": "2", "c": "3"} { if env[k] != v { - t.Errorf("envs dosen't contain %s=%s", k, v) + t.Errorf("env map dosen't contain %s=%s", k, v) + } + } +} +func TestEnvMergeSlices(t *testing.T) { + values := [][]string{ + {"a=1", "b=2"}, + {"a=2", "c=3"}, + } + env := EnvMergeSlices(values...) + for i, v := range []string{"a=1", "b=2", "a=2", "c=3"} { + if env[i] != v { + t.Errorf("env slice dosen't contain %s", v) } } }