Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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())
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need test for post stop here :p

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