diff --git a/apis/opts/env.go b/apis/opts/env.go new file mode 100644 index 000000000..6064b1e31 --- /dev/null +++ b/apis/opts/env.go @@ -0,0 +1,27 @@ +package opts + +import ( + "fmt" + "strings" +) + +// ParseEnv parses the env param of container. +func ParseEnv(env []string) (map[string]string, error) { + results := make(map[string]string) + for _, e := range env { + fields := strings.SplitN(e, "=", 2) + if len(fields) != 2 { + return nil, fmt.Errorf("invalid env %s: env must be in format of key=value", e) + } + results[fields[0]] = fields[1] + } + + return results, nil +} + +// ValidateEnv verifies the correct of env +func ValidateEnv(map[string]string) error { + // TODO + + return nil +} diff --git a/apis/opts/env_test.go b/apis/opts/env_test.go new file mode 100644 index 000000000..f721a5790 --- /dev/null +++ b/apis/opts/env_test.go @@ -0,0 +1,34 @@ +package opts + +import ( + "reflect" + "testing" +) + +func TestParseEnv(t *testing.T) { + type args struct { + env []string + } + tests := []struct { + name string + args args + want map[string]string + wantErr bool + }{ + // TODO: Add test cases. + {name: "test1", args: args{env: []string{"foo=bar"}}, want: map[string]string{"foo": "bar"}, wantErr: false}, + {name: "test2", args: args{env: []string{"ErrorInfo"}}, want: nil, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseEnv(tt.args.env) + if (err != nil) != tt.wantErr { + t.Errorf("ParseEnv() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ParseEnv() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/daemon/mgr/container.go b/daemon/mgr/container.go index 53d37ce8b..053da0c39 100644 --- a/daemon/mgr/container.go +++ b/daemon/mgr/container.go @@ -799,8 +799,42 @@ func (mgr *ContainerManager) Update(ctx context.Context, name string, config *ty } } - for _, v := range config.Env { - c.Config.Env = append(c.Config.Env, v) + // Update Env + if len(config.Env) > 0 { + newEnvMap, err := opts.ParseEnv(config.Env) + if err != nil { + return errors.Wrapf(err, "failed to parse new env") + } + + oldEnvMap, err := opts.ParseEnv(c.Config.Env) + if err != nil { + return errors.Wrapf(err, "failed to parse old env") + } + + for k, v := range newEnvMap { + // key should not be empty + if k == "" { + continue + } + + // add or change an env + if v != "" { + oldEnvMap[k] = v + continue + } + + // value is empty, we need delete the env + if _, exists := oldEnvMap[k]; exists { + delete(oldEnvMap, k) + } + } + + newEnvSlice := []string{} + for k, v := range oldEnvMap { + newEnvSlice = append(newEnvSlice, fmt.Sprintf("%s=%s", k, v)) + } + + c.Config.Env = newEnvSlice } // If container is not running, update container metadata struct is enough, diff --git a/test/cli_update_test.go b/test/cli_update_test.go index 7809e4841..01f4e9bcb 100644 --- a/test/cli_update_test.go +++ b/test/cli_update_test.go @@ -292,3 +292,59 @@ func (suite *PouchUpdateSuite) TestUpdateContainerLabel(c *check.C) { c.Errorf("expect 'foo=bar' in Labels, got: %v", result[0].Config.Labels) } } + +// TestUpdateContainerEnvValue is to verify the correctness of updating env's value of container. +func (suite *PouchUpdateSuite) TestUpdateContainerEnvValue(c *check.C) { + name := "update-container-env-value" + + res := command.PouchRun("run", "-d", "--env", "foo=bar", "--name", name, busyboxImage, "top") + defer DelContainerForceMultyTime(c, name) + res.Assert(c, icmd.Success) + + command.PouchRun("update", "--env", "foo=bar1", name).Assert(c, icmd.Success) + + output := command.PouchRun("inspect", name).Stdout() + result := []types.ContainerJSON{} + if err := json.Unmarshal([]byte(output), &result); err != nil { + c.Errorf("failed to decode inspect output: %v", err) + } + + if !utils.StringInSlice(result[0].Config.Env, "foo=bar1") { + c.Errorf("expect 'foo=bar' in container env, but got: %v", result[0].Config.Env) + } + + if utils.StringInSlice(result[0].Config.Env, "foo=bar") { + c.Errorf("expect change 'foo=bar' to 'foo=bar1', but got: %v", result[0].Config.Env) + } + + output = command.PouchRun("exec", name, "env").Stdout() + if !strings.Contains(output, "foo=bar1") { + c.Fatalf("Update running container env not worked") + } +} + +// TestUpdateContainerDeleteEnv is to verify the correctness of delete env by update interface +func (suite *PouchUpdateSuite) TestUpdateContainerDeleteEnv(c *check.C) { + name := "update-container-delete-env" + + res := command.PouchRun("run", "-d", "--env", "foo=bar", "--name", name, busyboxImage, "top") + defer DelContainerForceMultyTime(c, name) + res.Assert(c, icmd.Success) + + command.PouchRun("update", "--env", "foo=", name).Assert(c, icmd.Success) + + output := command.PouchRun("inspect", name).Stdout() + result := []types.ContainerJSON{} + if err := json.Unmarshal([]byte(output), &result); err != nil { + c.Errorf("failed to decode inspect output: %v", err) + } + + if utils.StringInSlice(result[0].Config.Env, "foo=bar") { + c.Errorf("expect 'foo=bar' env being deleted, but not") + } + + output = command.PouchRun("exec", name, "env").Stdout() + if strings.Contains(output, "foo=bar") { + c.Errorf("foo=bar env should be deleted from container's env") + } +}