Skip to content

Commit

Permalink
Merge pull request #261 from crosbymichael/hooks
Browse files Browse the repository at this point in the history
Implement hooks in libcontainer code base
  • Loading branch information
LK4D4 committed Sep 11, 2015
2 parents cd01b01 + 0f28592 commit 7d122ff
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 7 deletions.
78 changes: 78 additions & 0 deletions libcontainer/configs/config.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package configs

import (
"bytes"
"encoding/json"
"os/exec"
)

type Rlimit struct {
Type int `json:"type"`
Hard uint64 `json:"hard"`
Expand Down Expand Up @@ -159,4 +165,76 @@ type Config struct {
// A number of rules are given, each having an action to be taken if a syscall matches it.
// A default action to be taken if no rules match is also given.
Seccomp *Seccomp `json:"seccomp"`

// Hooks are a collection of actions to perform at various container lifecycle events.
// Hooks are not able to be marshaled to json but they are also not needed to.
Hooks *Hooks `json:"-"`
}

type Hooks struct {
// Prestart commands are executed after the container namespaces are created,
// but before the user supplied command is executed from init.
Prestart []Hook

// Poststop commands are executed after the container init process exits.
Poststop []Hook
}

// HookState is the payload provided to a hook on execution.
type HookState struct {
ID string `json:"id"`
Pid int `json:"pid"`
Root string `json:"root"`
}

type Hook interface {
// Run executes the hook with the provided state.
Run(HookState) error
}

// NewFunctionHooks will call the provided function when the hook is run.
func NewFunctionHook(f func(HookState) error) FuncHook {
return FuncHook{
run: f,
}
}

type FuncHook struct {
run func(HookState) error
}

func (f FuncHook) Run(s HookState) error {
return f.run(s)
}

type Command struct {
Path string `json:"path"`
Args []string `json:"args"`
Env []string `json:"env"`
Dir string `json:"dir"`
}

// NewCommandHooks will execute the provided command when the hook is run.
func NewCommandHook(cmd Command) CommandHook {
return CommandHook{
Command: cmd,
}
}

type CommandHook struct {
Command
}

func (c Command) Run(s HookState) error {
b, err := json.Marshal(s)
if err != nil {
return err
}
cmd := exec.Cmd{
Path: c.Path,
Args: c.Args,
Env: c.Env,
Stdin: bytes.NewReader(b),
}
return cmd.Run()
}
7 changes: 0 additions & 7 deletions libcontainer/configs/mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,3 @@ type Mount struct {
// Optional Command to be run after Source is mounted.
PostmountCmds []Command `json:"postmount_cmds"`
}

type Command struct {
Path string `json:"path"`
Args []string `json:"args"`
Env []string `json:"env"`
Dir string `json:"dir"`
}
12 changes: 12 additions & 0 deletions libcontainer/container_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, c
parentPipe: parentPipe,
manager: c.cgroupManager,
config: c.newInitConfig(p),
container: c,
}, nil
}

Expand Down Expand Up @@ -247,6 +248,17 @@ func (c *linuxContainer) Destroy() error {
err = rerr
}
c.initProcess = nil
if c.config.Hooks != nil {
s := configs.HookState{
ID: c.id,
Root: c.config.Rootfs,
}
for _, hook := range c.config.Hooks.Poststop {
if err := hook.Run(s); err != nil {
return err
}
}
}
return err
}

Expand Down
62 changes: 62 additions & 0 deletions libcontainer/integration/exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -932,3 +932,65 @@ func TestOomScoreAdj(t *testing.T) {
t.Fatalf("Expected oom_score_adj %d; got %q", config.OomScoreAdj, outputOomScoreAdj)
}
}

func TestHook(t *testing.T) {
if testing.Short() {
return
}
root, err := newTestRoot()
ok(t, err)
defer os.RemoveAll(root)

rootfs, err := newRootfs()
ok(t, err)
defer remove(rootfs)

config := newTemplateConfig(rootfs)
config.Hooks = &configs.Hooks{
Prestart: []configs.Hook{
configs.NewFunctionHook(func(s configs.HookState) error {
f, err := os.Create(filepath.Join(s.Root, "test"))
if err != nil {
return err
}
return f.Close()
}),
},
Poststop: []configs.Hook{
configs.NewFunctionHook(func(s configs.HookState) error {
return os.RemoveAll(filepath.Join(s.Root, "test"))
}),
},
}
container, err := factory.Create("test", config)
ok(t, err)

var stdout bytes.Buffer
pconfig := libcontainer.Process{
Args: []string{"sh", "-c", "ls /test"},
Env: standardEnvironment,
Stdin: nil,
Stdout: &stdout,
}
err = container.Start(&pconfig)
ok(t, err)

// Wait for process
waitProcess(&pconfig, t)

outputLs := string(stdout.Bytes())

// Check that the ls output has the expected file touched by the prestart hook
if !strings.Contains(outputLs, "/test") {
container.Destroy()
t.Fatalf("ls output doesn't have the expected file: %s", outputLs)
}

if err := container.Destroy(); err != nil {
t.Fatalf("container destory %s", err)
}
fi, err := os.Stat(filepath.Join(rootfs, "test"))
if err == nil || !os.IsNotExist(err) {
t.Fatalf("expected file to not exist, got %s", fi.Name())
}
}
13 changes: 13 additions & 0 deletions libcontainer/process_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"syscall"

"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/system"
)

Expand Down Expand Up @@ -200,6 +201,18 @@ func (p *initProcess) start() (err error) {
p.manager.Destroy()
}
}()
if p.config.Config.Hooks != nil {
s := configs.HookState{
ID: p.container.id,
Pid: p.pid(),
Root: p.config.Config.Rootfs,
}
for _, hook := range p.config.Config.Hooks.Prestart {
if err := hook.Run(s); err != nil {
return newSystemError(err)
}
}
}
if err := p.createNetworkInterfaces(); err != nil {
return newSystemError(err)
}
Expand Down

0 comments on commit 7d122ff

Please sign in to comment.