Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CreateRuntime, CreateContainer and StartContainer Hooks #2229

Merged
merged 3 commits into from
Jun 19, 2020
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
18 changes: 18 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,19 @@ RUN mkdir -p /usr/src/criu \
&& cd - \
&& rm -rf /usr/src/criu

# install skopeo
RUN echo 'deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/Debian_Unstable/ /' > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list \
&& wget -nv https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/Debian_Unstable/Release.key -O- | sudo apt-key add - \
&& apt-get update \
&& apt-get install -y --no-install-recommends skopeo \
&& rm -rf /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list \
&& apt-get clean \
&& rm -rf /var/cache/apt /var/lib/apt/lists/*;

# install umoci
RUN curl -o /usr/local/bin/umoci -fsSL https://github.com/opencontainers/umoci/releases/download/v0.4.5/umoci.amd64 \
&& chmod +x /usr/local/bin/umoci

COPY script/tmpmount /
WORKDIR /go/src/github.com/opencontainers/runc
ENTRYPOINT ["/tmpmount"]
Expand All @@ -78,4 +91,9 @@ RUN mkdir -p "${ROOTFS}"
RUN . tests/integration/multi-arch.bash \
&& curl -fsSL `get_busybox` | tar xfJC - "${ROOTFS}"

ENV DEBIAN_ROOTFS /debian
RUN mkdir -p "${DEBIAN_ROOTFS}"
RUN . tests/integration/multi-arch.bash \
&& get_and_extract_debian "$DEBIAN_ROOTFS"

COPY . .
6 changes: 5 additions & 1 deletion Vagrantfile.centos7
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,16 @@ Vagrant.configure("2") do |config|

# install yum packages
yum install -y -q epel-release
yum install -y -q gcc git iptables jq libseccomp-devel make
yum install -y -q gcc git iptables jq libseccomp-devel make skopeo
yum clean all

# install Go
curl -fsSL "https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz" | tar Cxz /usr/local

# Install umoci
curl -o /usr/local/bin/umoci -fsSL https://github.com/opencontainers/umoci/releases/download/v0.4.5/umoci.amd64
chmod +x /usr/local/bin/umoci

# install bats
git clone https://github.com/bats-core/bats-core
cd bats-core
Expand Down
11 changes: 8 additions & 3 deletions Vagrantfile.fedora32
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Vagrant.configure("2") do |config|
config exclude kernel,kernel-core
config install_weak_deps false
update
install iptables gcc make golang-go libseccomp-devel bats jq git-core criu
install iptables gcc make golang-go libseccomp-devel bats jq git-core criu skopeo
ts run
EOF
dnf clean all
Expand All @@ -31,10 +31,15 @@ EOF
cat /root/rootless.key.pub >> /home/rootless/.ssh/authorized_keys
chown -R rootless.rootless /home/rootless

# Install umoci
curl -o /usr/local/bin/umoci -fsSL https://github.com/opencontainers/umoci/releases/download/v0.4.5/umoci.amd64
chmod +x /usr/local/bin/umoci

# Add busybox for libcontainer/integration tests
. /vagrant/tests/integration/multi-arch.bash \
&& mkdir /busybox \
&& curl -fsSL $(get_busybox) | tar xfJC - /busybox
&& mkdir /busybox /debian \
&& curl -fsSL $(get_busybox) | tar xfJC - /busybox \
&& get_and_extract_debian /debian

# Delegate cgroup v2 controllers to rootless user via --systemd-cgroup
mkdir -p /etc/systemd/system/user@.service.d
Expand Down
89 changes: 66 additions & 23 deletions libcontainer/configs/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"time"

"github.com/opencontainers/runtime-spec/specs-go"

"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)

Expand Down Expand Up @@ -176,7 +176,7 @@ type Config struct {

// Hooks are a collection of actions to perform at various container lifecycle events.
// CommandHooks are serialized to JSON, but other hooks are not.
Hooks *Hooks
Hooks Hooks

// Version is the version of opencontainer specification that is supported.
Version string `json:"version"`
Expand All @@ -203,17 +203,50 @@ type Config struct {
RootlessCgroups bool `json:"rootless_cgroups,omitempty"`
}

type Hooks struct {
type HookName string
type HookList []Hook
type Hooks map[HookName]HookList

const (
// Prestart commands are executed after the container namespaces are created,
// but before the user supplied command is executed from init.
Prestart []Hook
// Note: This hook is now deprecated
// Prestart commands are called in the Runtime namespace.
Prestart HookName = "prestart"

// CreateRuntime commands MUST be called as part of the create operation after
// the runtime environment has been created but before the pivot_root has been executed.
// CreateRuntime is called immediately after the deprecated Prestart hook.
RenaudWasTaken marked this conversation as resolved.
Show resolved Hide resolved
// CreateRuntime commands are called in the Runtime Namespace.
CreateRuntime = "createRuntime"

RenaudWasTaken marked this conversation as resolved.
Show resolved Hide resolved
// CreateContainer commands MUST be called as part of the create operation after
// the runtime environment has been created but before the pivot_root has been executed.
// CreateContainer commands are called in the Container namespace.
CreateContainer = "createContainer"

// StartContainer commands MUST be called as part of the start operation and before
// the container process is started.
// StartContainer commands are called in the Container namespace.
StartContainer = "startContainer"

// Poststart commands are executed after the container init process starts.
Poststart []Hook
// Poststart commands are called in the Runtime Namespace.
Poststart = "poststart"

// Poststop commands are executed after the container init process exits.
Poststop []Hook
}
// Poststop commands are called in the Runtime Namespace.
Poststop = "poststop"
)

// TODO move this to runtime-spec
// See: https://github.com/opencontainers/runtime-spec/pull/1046
const (
Creating = "creating"
Created = "created"
Running = "running"
Stopped = "stopped"
)

type Capabilities struct {
// Bounding is the set of capabilities checked by the kernel.
Expand All @@ -228,32 +261,39 @@ type Capabilities struct {
Ambient []string
}

func (hooks *Hooks) UnmarshalJSON(b []byte) error {
var state struct {
Prestart []CommandHook
Poststart []CommandHook
Poststop []CommandHook
func (hooks HookList) RunHooks(state *specs.State) error {
for i, h := range hooks {
if err := h.Run(state); err != nil {
return errors.Wrapf(err, "Running hook #%d:", i)
}
}

return nil
}

func (hooks *Hooks) UnmarshalJSON(b []byte) error {
var state map[HookName][]CommandHook

if err := json.Unmarshal(b, &state); err != nil {
return err
}

deserialize := func(shooks []CommandHook) (hooks []Hook) {
for _, shook := range shooks {
hooks = append(hooks, shook)
*hooks = Hooks{}
for n, commandHooks := range state {
if len(commandHooks) == 0 {
cyphar marked this conversation as resolved.
Show resolved Hide resolved
continue
}

return hooks
(*hooks)[n] = HookList{}
for _, h := range commandHooks {
(*hooks)[n] = append((*hooks)[n], h)
}
}

hooks.Prestart = deserialize(state.Prestart)
hooks.Poststart = deserialize(state.Poststart)
hooks.Poststop = deserialize(state.Poststop)
return nil
}

func (hooks Hooks) MarshalJSON() ([]byte, error) {
func (hooks *Hooks) MarshalJSON() ([]byte, error) {
serialize := func(hooks []Hook) (serializableHooks []CommandHook) {
for _, hook := range hooks {
switch chook := hook.(type) {
Expand All @@ -268,9 +308,12 @@ func (hooks Hooks) MarshalJSON() ([]byte, error) {
}

return json.Marshal(map[string]interface{}{
"prestart": serialize(hooks.Prestart),
"poststart": serialize(hooks.Poststart),
"poststop": serialize(hooks.Poststop),
"prestart": serialize((*hooks)[Prestart]),
"createRuntime": serialize((*hooks)[CreateRuntime]),
"createContainer": serialize((*hooks)[CreateContainer]),
"startContainer": serialize((*hooks)[StartContainer]),
"poststart": serialize((*hooks)[Poststart]),
"poststop": serialize((*hooks)[Poststop]),
})
}

Expand Down
4 changes: 4 additions & 0 deletions libcontainer/configs/config_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import (
"testing"
)

var (
HookNameList = []HookName{Prestart, CreateRuntime, CreateContainer, StartContainer, Poststart, Poststop}
)

func loadConfig(name string) (*Config, error) {
f, err := os.Open(filepath.Join("../sample_configs", name))
if err != nil {
Expand Down
58 changes: 36 additions & 22 deletions libcontainer/configs/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,29 @@ import (
func TestUnmarshalHooks(t *testing.T) {
timeout := time.Second

prestartCmd := configs.NewCommandHook(configs.Command{
Path: "/var/vcap/hooks/prestart",
hookCmd := configs.NewCommandHook(configs.Command{
Path: "/var/vcap/hooks/hook",
Args: []string{"--pid=123"},
Env: []string{"FOO=BAR"},
Dir: "/var/vcap",
Timeout: &timeout,
})
prestart, err := json.Marshal(prestartCmd.Command)
if err != nil {
t.Fatal(err)
}

hook := configs.Hooks{}
err = hook.UnmarshalJSON([]byte(fmt.Sprintf(`{"Prestart" :[%s]}`, prestart)))
hookJson, err := json.Marshal(hookCmd)
if err != nil {
t.Fatal(err)
}

if !reflect.DeepEqual(hook.Prestart[0], prestartCmd) {
t.Errorf("Expected prestart to equal %+v but it was %+v",
prestartCmd, hook.Prestart[0])
for _, hookName := range configs.HookNameList {
hooks := configs.Hooks{}
err = hooks.UnmarshalJSON([]byte(fmt.Sprintf(`{"%s" :[%s]}`, hookName, hookJson)))
if err != nil {
t.Fatal(err)
}

if !reflect.DeepEqual(hooks[hookName], configs.HookList{hookCmd}) {
t.Errorf("Expected %s to equal %+v but it was %+v", hookName, hookCmd, hooks[hookName])
}
}
}

Expand All @@ -50,23 +52,30 @@ func TestUnmarshalHooksWithInvalidData(t *testing.T) {
func TestMarshalHooks(t *testing.T) {
timeout := time.Second

prestartCmd := configs.NewCommandHook(configs.Command{
Path: "/var/vcap/hooks/prestart",
hookCmd := configs.NewCommandHook(configs.Command{
Path: "/var/vcap/hooks/hook",
Args: []string{"--pid=123"},
Env: []string{"FOO=BAR"},
Dir: "/var/vcap",
Timeout: &timeout,
})

hook := configs.Hooks{
Prestart: []configs.Hook{prestartCmd},
configs.Prestart: configs.HookList{hookCmd},
configs.CreateRuntime: configs.HookList{hookCmd},
configs.CreateContainer: configs.HookList{hookCmd},
configs.StartContainer: configs.HookList{hookCmd},
configs.Poststart: configs.HookList{hookCmd},
configs.Poststop: configs.HookList{hookCmd},
}
hooks, err := hook.MarshalJSON()
if err != nil {
t.Fatal(err)
}

h := `{"poststart":null,"poststop":null,"prestart":[{"path":"/var/vcap/hooks/prestart","args":["--pid=123"],"env":["FOO=BAR"],"dir":"/var/vcap","timeout":1000000000}]}`
// Note Marshal seems to output fields in alphabetical order
hookCmdJson := `[{"path":"/var/vcap/hooks/hook","args":["--pid=123"],"env":["FOO=BAR"],"dir":"/var/vcap","timeout":1000000000}]`
h := fmt.Sprintf(`{"createContainer":%[1]s,"createRuntime":%[1]s,"poststart":%[1]s,"poststop":%[1]s,"prestart":%[1]s,"startContainer":%[1]s}`, hookCmdJson)
if string(hooks) != h {
t.Errorf("Expected hooks %s to equal %s", string(hooks), h)
}
Expand All @@ -75,16 +84,21 @@ func TestMarshalHooks(t *testing.T) {
func TestMarshalUnmarshalHooks(t *testing.T) {
timeout := time.Second

prestart := configs.NewCommandHook(configs.Command{
Path: "/var/vcap/hooks/prestart",
hookCmd := configs.NewCommandHook(configs.Command{
Path: "/var/vcap/hooks/hook",
Args: []string{"--pid=123"},
Env: []string{"FOO=BAR"},
Dir: "/var/vcap",
Timeout: &timeout,
})

hook := configs.Hooks{
Prestart: []configs.Hook{prestart},
configs.Prestart: configs.HookList{hookCmd},
configs.CreateRuntime: configs.HookList{hookCmd},
configs.CreateContainer: configs.HookList{hookCmd},
configs.StartContainer: configs.HookList{hookCmd},
configs.Poststart: configs.HookList{hookCmd},
configs.Poststop: configs.HookList{hookCmd},
}
hooks, err := hook.MarshalJSON()
if err != nil {
Expand All @@ -96,8 +110,8 @@ func TestMarshalUnmarshalHooks(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(umMhook.Prestart[0], prestart) {
t.Errorf("Expected hooks to be equal after mashaling -> unmarshaling them: %+v, %+v", umMhook.Prestart[0], prestart)
if !reflect.DeepEqual(umMhook, hook) {
t.Errorf("Expected hooks to be equal after mashaling -> unmarshaling them: %+v, %+v", umMhook, hook)
}
}

Expand All @@ -106,14 +120,14 @@ func TestMarshalHooksWithUnexpectedType(t *testing.T) {
return nil
})
hook := configs.Hooks{
Prestart: []configs.Hook{fHook},
configs.CreateRuntime: configs.HookList{fHook},
}
hooks, err := hook.MarshalJSON()
if err != nil {
t.Fatal(err)
}

h := `{"poststart":null,"poststop":null,"prestart":null}`
h := `{"createContainer":null,"createRuntime":null,"poststart":null,"poststop":null,"prestart":null,"startContainer":null}`
if string(hooks) != h {
t.Errorf("Expected hooks %s to equal %s", string(hooks), h)
}
Expand Down
Loading