Skip to content

Commit

Permalink
feature: support update env when container is running
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Wan <[email protected]>
  • Loading branch information
HusterWan committed Apr 26, 2018
1 parent 01c4164 commit 7c1f6d1
Show file tree
Hide file tree
Showing 14 changed files with 2,508 additions and 14 deletions.
145 changes: 136 additions & 9 deletions daemon/mgr/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package mgr
import (
"context"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
Expand Down Expand Up @@ -33,6 +34,7 @@ import (
"github.com/docker/libnetwork"
"github.com/go-openapi/strfmt"
"github.com/imdario/mergo"
"github.com/magiconair/properties"
"github.com/opencontainers/image-spec/specs-go/v1"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
Expand Down Expand Up @@ -875,15 +877,6 @@ func (mgr *ContainerManager) Update(ctx context.Context, name string, config *ty
c.Lock()
defer c.Unlock()

// update ContainerConfig of a container.
if c.IsRunning() && len(config.Env) > 0 {
return fmt.Errorf("Only can update the container's Env when it stopped")
}

for _, v := range config.Env {
c.meta.Config.Env = append(c.meta.Config.Env, v)
}

if len(config.Labels) != 0 {
if c.meta.Config.Labels == nil {
c.meta.Config.Labels = map[string]string{}
Expand Down Expand Up @@ -922,6 +915,20 @@ func (mgr *ContainerManager) Update(ctx context.Context, name string, config *ty
c.meta.HostConfig.RestartPolicy = config.RestartPolicy
}

// update env when container is running, default snapshotter driver
// is overlayfs
if (c.IsRunning() || c.IsPaused()) && len(config.Env) > 0 && c.meta.Snapshotter != nil {
if mergedDir, exists := c.meta.Snapshotter.Data["MergedDir"]; exists {
if err := mgr.updateContainerEnv(c.meta.Config.Env, mergedDir); err != nil {
return fmt.Errorf("failed to update env of running container: %v", err)
}
}
}

for _, v := range config.Env {
c.meta.Config.Env = append(c.meta.Config.Env, v)
}

// If container is not running, update container metadata struct is enough,
// resources will be updated when the container is started again,
// If container is running, we need to update configs to the real world.
Expand All @@ -938,6 +945,126 @@ func (mgr *ContainerManager) Update(ctx context.Context, name string, config *ty
return updateErr
}

// updateContainerEnv update the container's envs in /etc/instanceInfo and /etc/profile.d/pouchenv.sh
// Env used by rich container.
func (mgr *ContainerManager) updateContainerEnv(containerEnvs []string, baseFs string) error {
var (
envPropertiesPath = path.Join(baseFs, "/etc/instanceInfo")
envShPath = path.Join(baseFs, "/etc/profile.d/pouchenv.sh")
)

// if dir of pouch.sh is not exist, it's unnecessary to update that files.
if _, ex := os.Stat(path.Join(baseFs, "/etc/profile.d")); ex != nil {
return nil
}

newEnvs := containerEnvs
kv := map[string]string{}
for _, env := range newEnvs {
arr := strings.SplitN(env, "=", 2)
if len(arr) == 2 {
kv[arr[0]] = arr[1]
}
}

// load container's env file if exist
if _, err := os.Stat(envShPath); err != nil {
return fmt.Errorf("failed to state container's env file /etc/profile.d/pouchenv.sh: %v", err)
}
// update /etc/profile.d/pouchnv.sh
b, err := ioutil.ReadFile(envShPath)
if err != nil {
return fmt.Errorf("failed to read container's environment variable file(/etc/profile.d/pouchenv.sh): %v", err)
}
envsh := string(b)
envsh = strings.Trim(envsh, "\n")
envs := strings.Split(envsh, "\n")
envMap := make(map[string]string)
for _, e := range envs {
e = strings.TrimLeft(e, "export ")
arr := strings.SplitN(e, "=", 2)
val := strings.Trim(arr[1], "\"")
envMap[arr[0]] = val
}

var str string
for key, val := range envMap {
if v, ok := kv[key]; ok {
s := strings.Replace(v, "\"", "\\\"", -1)
s = strings.Replace(s, "$", "\\$", -1)
v = s
if key == "PATH" {
v = v + ":$PATH"
}
if v == val {
continue
} else {
envMap[key] = v
logrus.Infof("the env is exist and the value is not same, key=%s, old value=%s, new value=%s", key, val, v)
}
}
}
// append the new envs
for k, v := range kv {
if _, ok := envMap[k]; !ok {
envMap[k] = v
logrus.Infof("the env is not exist, set new key value pair, new key=%s, new value=%s", k, v)
}
}

for k, v := range envMap {
str += fmt.Sprintf("export %s=\"%s\"\n", k, v)
}
ioutil.WriteFile(envShPath, []byte(str), 0755)

// properties load container's env file if exist
if _, err := os.Stat(envPropertiesPath); err != nil {
//if etc/instanceInfo is not exist, it's unnecessary to update that file.
return nil
}

p, err := properties.LoadFile(envPropertiesPath, properties.ISO_8859_1)
if err != nil {
return fmt.Errorf("failed to properties load container's environment variable file(/etc/instanceInfo): %v", err)
}

for key, val := range kv {
if v, ok := p.Get("env_" + key); ok {
if key == "PATH" {
// refer to https://aone.alipay.com/project/532482/task/9745028
val = val + ":$PATH"
}
if v == val {
continue
} else {
_, _, err := p.Set("env_"+key, val)
if err != nil {
return fmt.Errorf("failed to properties set value key=%s, value=%s: %v", "env_"+key, val, err)
}
logrus.Infof("the environment variable exist and the value is not same, key=%s, old value=%s, new value=%s", "env_"+key, v, val)
}
} else {
_, _, err := p.Set("env_"+key, val)
if err != nil {
return fmt.Errorf("failed to properties set value key=%s, value=%s: %v", "env_"+key, val, err)
}
logrus.Infof("the environment variable not exist and set the new key value pair, key=%s, value=%s", "env_"+key, val)
}
}
f, err := os.Create(envPropertiesPath)
if err != nil {
return fmt.Errorf("failed to create container's environment variable file(properties): %v", err)
}
defer f.Close()

_, err = p.Write(f, properties.ISO_8859_1)
if err != nil {
return fmt.Errorf("failed to write to container's environment variable file(properties): %v", err)
}

return nil
}

// Upgrade upgrades a container with new image and args.
func (mgr *ContainerManager) Upgrade(ctx context.Context, name string, config *types.ContainerUpgradeConfig) error {
c, err := mgr.container(name)
Expand Down
19 changes: 14 additions & 5 deletions test/cli_update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,12 +212,21 @@ func (suite *PouchUpdateSuite) TestUpdateRunningContainerEnv(c *check.C) {

command.PouchRun("run", "-d", "-m", "300M", "--name", name, busyboxImage, "top").Assert(c, icmd.Success)

res := command.PouchRun("update", "--env", "foo=bar", name)
c.Assert(res.Error, check.NotNil)
command.PouchRun("update", "--env", "foo=bar", 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' in container env, but got: %v", result[0].Config.Env)
}

expectedStr := "Only can update the container's Env when it stopped"
if out := res.Combined(); !strings.Contains(out, expectedStr) {
c.Fatalf("unexpected output: %s, expected: %s", out, expectedStr)
output = command.PouchRun("exec", name, "env").Stdout()
if !strings.Contains(output, "foo=bar") {
c.Fatalf("Update running container env not worked")
}

command.PouchRun("rm", "-f", name).Assert(c, icmd.Success)
Expand Down
123 changes: 123 additions & 0 deletions vendor/github.com/magiconair/properties/CHANGELOG.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions vendor/github.com/magiconair/properties/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 7c1f6d1

Please sign in to comment.