diff --git a/exec.go b/exec.go index ac46f6e8..1ee6cd81 100644 --- a/exec.go +++ b/exec.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/codegangsta/cli" + "github.com/opencontainers/runc/libcontainer/utils" "github.com/opencontainers/specs/specs-go" ) @@ -103,7 +104,7 @@ func execProcess(context *cli.Context) (int, error) { if err != nil { return -1, err } - bundle := searchLabels(state.Config.Labels, "bundle") + bundle := utils.SearchLabels(state.Config.Labels, "bundle") p, err := getProcess(context, bundle) if err != nil { return -1, err diff --git a/libcontainer/configs/config.go b/libcontainer/configs/config.go index 0354dbd5..1221ce27 100644 --- a/libcontainer/configs/config.go +++ b/libcontainer/configs/config.go @@ -249,10 +249,11 @@ func (hooks Hooks) MarshalJSON() ([]byte, error) { // HookState is the payload provided to a hook on execution. type HookState struct { - Version string `json:"version"` - ID string `json:"id"` - Pid int `json:"pid"` - Root string `json:"root"` + Version string `json:"ociVersion"` + ID string `json:"id"` + Pid int `json:"pid"` + Root string `json:"root"` + BundlePath string `json:"bundlePath"` } type Hook interface { diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go index 46e8766a..2ae50c46 100644 --- a/libcontainer/container_linux.go +++ b/libcontainer/container_linux.go @@ -205,10 +205,11 @@ func (c *linuxContainer) Start(process *Process) error { } if c.config.Hooks != nil { s := configs.HookState{ - Version: c.config.Version, - ID: c.id, - Pid: parent.pid(), - Root: c.config.Rootfs, + Version: c.config.Version, + ID: c.id, + Pid: parent.pid(), + Root: c.config.Rootfs, + BundlePath: utils.SearchLabels(c.config.Labels, "bundle"), } for _, hook := range c.config.Hooks.Poststart { if err := hook.Run(s); err != nil { diff --git a/libcontainer/integration/exec_test.go b/libcontainer/integration/exec_test.go index 33241d3e..45c64128 100644 --- a/libcontainer/integration/exec_test.go +++ b/libcontainer/integration/exec_test.go @@ -1068,9 +1068,15 @@ func TestHook(t *testing.T) { defer remove(rootfs) config := newTemplateConfig(rootfs) + expectedBundlePath := "/path/to/bundle/path" + config.Labels = append(config.Labels, fmt.Sprintf("bundle=%s", expectedBundlePath)) config.Hooks = &configs.Hooks{ Prestart: []configs.Hook{ configs.NewFunctionHook(func(s configs.HookState) error { + if s.BundlePath != expectedBundlePath { + t.Fatalf("Expected prestart hook bundlePath '%s'; got '%s'", expectedBundlePath, s.BundlePath) + } + f, err := os.Create(filepath.Join(s.Root, "test")) if err != nil { return err @@ -1078,8 +1084,21 @@ func TestHook(t *testing.T) { return f.Close() }), }, + Poststart: []configs.Hook{ + configs.NewFunctionHook(func(s configs.HookState) error { + if s.BundlePath != expectedBundlePath { + t.Fatalf("Expected poststart hook bundlePath '%s'; got '%s'", expectedBundlePath, s.BundlePath) + } + + return ioutil.WriteFile(filepath.Join(s.Root, "test"), []byte("hello world"), 0755) + }), + }, Poststop: []configs.Hook{ configs.NewFunctionHook(func(s configs.HookState) error { + if s.BundlePath != expectedBundlePath { + t.Fatalf("Expected poststop hook bundlePath '%s'; got '%s'", expectedBundlePath, s.BundlePath) + } + return os.RemoveAll(filepath.Join(s.Root, "test")) }), }, @@ -1109,6 +1128,16 @@ func TestHook(t *testing.T) { t.Fatalf("ls output doesn't have the expected file: %s", outputLs) } + // Check that the file is written by the poststart hook + testFilePath := filepath.Join(rootfs, "test") + contents, err := ioutil.ReadFile(testFilePath) + if err != nil { + t.Fatalf("cannot read file '%s': %s", testFilePath, err) + } + if string(contents) != "hello world" { + t.Fatalf("Expected test file to contain 'hello world'; got '%s'", string(contents)) + } + if err := container.Destroy(); err != nil { t.Fatalf("container destory %s", err) } diff --git a/libcontainer/process_linux.go b/libcontainer/process_linux.go index e2ae1b1a..1a2ee0bc 100644 --- a/libcontainer/process_linux.go +++ b/libcontainer/process_linux.go @@ -318,10 +318,11 @@ loop: case procHooks: if p.config.Config.Hooks != nil { s := configs.HookState{ - Version: p.container.config.Version, - ID: p.container.id, - Pid: p.pid(), - Root: p.config.Config.Rootfs, + Version: p.container.config.Version, + ID: p.container.id, + Pid: p.pid(), + Root: p.config.Config.Rootfs, + BundlePath: utils.SearchLabels(p.config.Config.Labels, "bundle"), } for _, hook := range p.config.Config.Hooks.Prestart { if err := hook.Run(s); err != nil { diff --git a/libcontainer/state_linux.go b/libcontainer/state_linux.go index 9ffe15a4..d2618f69 100644 --- a/libcontainer/state_linux.go +++ b/libcontainer/state_linux.go @@ -9,6 +9,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/opencontainers/runc/libcontainer/configs" + "github.com/opencontainers/runc/libcontainer/utils" ) func newStateTransitionError(from, to containerState) error { @@ -56,9 +57,10 @@ func destroy(c *linuxContainer) error { func runPoststopHooks(c *linuxContainer) error { if c.config.Hooks != nil { s := configs.HookState{ - Version: c.config.Version, - ID: c.id, - Root: c.config.Rootfs, + Version: c.config.Version, + ID: c.id, + Root: c.config.Rootfs, + BundlePath: utils.SearchLabels(c.config.Labels, "bundle"), } for _, hook := range c.config.Hooks.Poststop { if err := hook.Run(s); err != nil { diff --git a/libcontainer/utils/utils.go b/libcontainer/utils/utils.go index 68ae3c47..9e748a6d 100644 --- a/libcontainer/utils/utils.go +++ b/libcontainer/utils/utils.go @@ -7,6 +7,7 @@ import ( "io" "os" "path/filepath" + "strings" "syscall" ) @@ -84,3 +85,18 @@ func CleanPath(path string) string { // Clean the path again for good measure. return filepath.Clean(path) } + +// SearchLabels searches a list of key-value pairs for the provided key and +// returns the corresponding value. The pairs must be separated with '='. +func SearchLabels(labels []string, query string) string { + for _, l := range labels { + parts := strings.SplitN(l, "=", 2) + if len(parts) < 2 { + continue + } + if parts[0] == query { + return parts[1] + } + } + return "" +} diff --git a/libcontainer/utils/utils_test.go b/libcontainer/utils/utils_test.go index 813180a8..51f0efa1 100644 --- a/libcontainer/utils/utils_test.go +++ b/libcontainer/utils/utils_test.go @@ -23,3 +23,24 @@ func TestGenerateName(t *testing.T) { t.Fatalf("expected name to be %d chars but received %d", expected, len(name)) } } + +var labelTest = []struct { + labels []string + query string + expectedValue string +}{ + {[]string{"bundle=/path/to/bundle"}, "bundle", "/path/to/bundle"}, + {[]string{"test=a", "test=b"}, "bundle", ""}, + {[]string{"bundle=a", "test=b", "bundle=c"}, "bundle", "a"}, + {[]string{"", "test=a", "bundle=b"}, "bundle", "b"}, + {[]string{"test", "bundle=a"}, "bundle", "a"}, + {[]string{"test=a", "bundle="}, "bundle", ""}, +} + +func TestSearchLabels(t *testing.T) { + for _, tt := range labelTest { + if v := SearchLabels(tt.labels, tt.query); v != tt.expectedValue { + t.Errorf("expected value '%s' for query '%s'; got '%s'", tt.expectedValue, tt.query, v) + } + } +} diff --git a/list.go b/list.go index 588e37ee..1750ef81 100644 --- a/list.go +++ b/list.go @@ -7,13 +7,13 @@ import ( "io/ioutil" "os" "path/filepath" - "strings" "text/tabwriter" "time" "encoding/json" "github.com/codegangsta/cli" + "github.com/opencontainers/runc/libcontainer/utils" ) const formatOptions = `table or json` @@ -116,22 +116,9 @@ func getContainers(context *cli.Context) ([]containerState, error) { ID: state.BaseState.ID, InitProcessPid: state.BaseState.InitProcessPid, Status: containerStatus.String(), - Bundle: searchLabels(state.Config.Labels, "bundle"), + Bundle: utils.SearchLabels(state.Config.Labels, "bundle"), Created: state.BaseState.Created}) } } return s, nil } - -func searchLabels(labels []string, query string) string { - for _, l := range labels { - parts := strings.SplitN(l, "=", 2) - if len(parts) < 2 { - continue - } - if parts[0] == query { - return parts[1] - } - } - return "" -} diff --git a/state.go b/state.go index 0b1fbc9b..89517046 100644 --- a/state.go +++ b/state.go @@ -8,6 +8,7 @@ import ( "time" "github.com/codegangsta/cli" + "github.com/opencontainers/runc/libcontainer/utils" ) // cState represents the platform agnostic pieces relating to a running @@ -57,7 +58,7 @@ instance of a container.`, ID: state.BaseState.ID, InitProcessPid: state.BaseState.InitProcessPid, Status: containerStatus.String(), - Bundle: searchLabels(state.Config.Labels, "bundle"), + Bundle: utils.SearchLabels(state.Config.Labels, "bundle"), Rootfs: state.BaseState.Config.Rootfs, Created: state.BaseState.Created} data, err := json.MarshalIndent(cs, "", " ")