Merge pull request #724 from cloudfoundry-incubator/hookstate-bundlepath

HookState adhears to OCI
This commit is contained in:
Mrunal Patel 2016-04-11 14:59:14 -07:00
commit 4023fe0fb9
10 changed files with 92 additions and 32 deletions

View File

@ -10,6 +10,7 @@ import (
"strings" "strings"
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
"github.com/opencontainers/runc/libcontainer/utils"
"github.com/opencontainers/specs/specs-go" "github.com/opencontainers/specs/specs-go"
) )
@ -103,7 +104,7 @@ func execProcess(context *cli.Context) (int, error) {
if err != nil { if err != nil {
return -1, err return -1, err
} }
bundle := searchLabels(state.Config.Labels, "bundle") bundle := utils.SearchLabels(state.Config.Labels, "bundle")
p, err := getProcess(context, bundle) p, err := getProcess(context, bundle)
if err != nil { if err != nil {
return -1, err return -1, err

View File

@ -249,10 +249,11 @@ func (hooks Hooks) MarshalJSON() ([]byte, error) {
// HookState is the payload provided to a hook on execution. // HookState is the payload provided to a hook on execution.
type HookState struct { type HookState struct {
Version string `json:"version"` Version string `json:"ociVersion"`
ID string `json:"id"` ID string `json:"id"`
Pid int `json:"pid"` Pid int `json:"pid"`
Root string `json:"root"` Root string `json:"root"`
BundlePath string `json:"bundlePath"`
} }
type Hook interface { type Hook interface {

View File

@ -205,10 +205,11 @@ func (c *linuxContainer) Start(process *Process) error {
} }
if c.config.Hooks != nil { if c.config.Hooks != nil {
s := configs.HookState{ s := configs.HookState{
Version: c.config.Version, Version: c.config.Version,
ID: c.id, ID: c.id,
Pid: parent.pid(), Pid: parent.pid(),
Root: c.config.Rootfs, Root: c.config.Rootfs,
BundlePath: utils.SearchLabels(c.config.Labels, "bundle"),
} }
for _, hook := range c.config.Hooks.Poststart { for _, hook := range c.config.Hooks.Poststart {
if err := hook.Run(s); err != nil { if err := hook.Run(s); err != nil {

View File

@ -1068,9 +1068,15 @@ func TestHook(t *testing.T) {
defer remove(rootfs) defer remove(rootfs)
config := newTemplateConfig(rootfs) config := newTemplateConfig(rootfs)
expectedBundlePath := "/path/to/bundle/path"
config.Labels = append(config.Labels, fmt.Sprintf("bundle=%s", expectedBundlePath))
config.Hooks = &configs.Hooks{ config.Hooks = &configs.Hooks{
Prestart: []configs.Hook{ Prestart: []configs.Hook{
configs.NewFunctionHook(func(s configs.HookState) error { 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")) f, err := os.Create(filepath.Join(s.Root, "test"))
if err != nil { if err != nil {
return err return err
@ -1078,8 +1084,21 @@ func TestHook(t *testing.T) {
return f.Close() 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{ Poststop: []configs.Hook{
configs.NewFunctionHook(func(s configs.HookState) error { 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")) 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) 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 { if err := container.Destroy(); err != nil {
t.Fatalf("container destory %s", err) t.Fatalf("container destory %s", err)
} }

View File

@ -318,10 +318,11 @@ loop:
case procHooks: case procHooks:
if p.config.Config.Hooks != nil { if p.config.Config.Hooks != nil {
s := configs.HookState{ s := configs.HookState{
Version: p.container.config.Version, Version: p.container.config.Version,
ID: p.container.id, ID: p.container.id,
Pid: p.pid(), Pid: p.pid(),
Root: p.config.Config.Rootfs, Root: p.config.Config.Rootfs,
BundlePath: utils.SearchLabels(p.config.Config.Labels, "bundle"),
} }
for _, hook := range p.config.Config.Hooks.Prestart { for _, hook := range p.config.Config.Hooks.Prestart {
if err := hook.Run(s); err != nil { if err := hook.Run(s); err != nil {

View File

@ -9,6 +9,7 @@ import (
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/utils"
) )
func newStateTransitionError(from, to containerState) error { func newStateTransitionError(from, to containerState) error {
@ -56,9 +57,10 @@ func destroy(c *linuxContainer) error {
func runPoststopHooks(c *linuxContainer) error { func runPoststopHooks(c *linuxContainer) error {
if c.config.Hooks != nil { if c.config.Hooks != nil {
s := configs.HookState{ s := configs.HookState{
Version: c.config.Version, Version: c.config.Version,
ID: c.id, ID: c.id,
Root: c.config.Rootfs, Root: c.config.Rootfs,
BundlePath: utils.SearchLabels(c.config.Labels, "bundle"),
} }
for _, hook := range c.config.Hooks.Poststop { for _, hook := range c.config.Hooks.Poststop {
if err := hook.Run(s); err != nil { if err := hook.Run(s); err != nil {

View File

@ -7,6 +7,7 @@ import (
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"syscall" "syscall"
) )
@ -84,3 +85,18 @@ func CleanPath(path string) string {
// Clean the path again for good measure. // Clean the path again for good measure.
return filepath.Clean(path) 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 ""
}

View File

@ -23,3 +23,24 @@ func TestGenerateName(t *testing.T) {
t.Fatalf("expected name to be %d chars but received %d", expected, len(name)) 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)
}
}
}

17
list.go
View File

@ -7,13 +7,13 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"text/tabwriter" "text/tabwriter"
"time" "time"
"encoding/json" "encoding/json"
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
"github.com/opencontainers/runc/libcontainer/utils"
) )
const formatOptions = `table or json` const formatOptions = `table or json`
@ -116,22 +116,9 @@ func getContainers(context *cli.Context) ([]containerState, error) {
ID: state.BaseState.ID, ID: state.BaseState.ID,
InitProcessPid: state.BaseState.InitProcessPid, InitProcessPid: state.BaseState.InitProcessPid,
Status: containerStatus.String(), Status: containerStatus.String(),
Bundle: searchLabels(state.Config.Labels, "bundle"), Bundle: utils.SearchLabels(state.Config.Labels, "bundle"),
Created: state.BaseState.Created}) Created: state.BaseState.Created})
} }
} }
return s, nil 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 ""
}

View File

@ -8,6 +8,7 @@ import (
"time" "time"
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
"github.com/opencontainers/runc/libcontainer/utils"
) )
// cState represents the platform agnostic pieces relating to a running // cState represents the platform agnostic pieces relating to a running
@ -57,7 +58,7 @@ instance of a container.`,
ID: state.BaseState.ID, ID: state.BaseState.ID,
InitProcessPid: state.BaseState.InitProcessPid, InitProcessPid: state.BaseState.InitProcessPid,
Status: containerStatus.String(), Status: containerStatus.String(),
Bundle: searchLabels(state.Config.Labels, "bundle"), Bundle: utils.SearchLabels(state.Config.Labels, "bundle"),
Rootfs: state.BaseState.Config.Rootfs, Rootfs: state.BaseState.Config.Rootfs,
Created: state.BaseState.Created} Created: state.BaseState.Created}
data, err := json.MarshalIndent(cs, "", " ") data, err := json.MarshalIndent(cs, "", " ")