Skip to content
Closed
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
10 changes: 8 additions & 2 deletions libcontainer/configs/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -535,13 +535,19 @@ func (hooks *Hooks) MarshalJSON() ([]byte, error) {
// Run executes all hooks for the given hook name.
func (hooks Hooks) Run(name HookName, state *specs.State) error {
list := hooks[name]
var errs []error
for i, h := range list {
if err := h.Run(state); err != nil {
return fmt.Errorf("error running %s hook #%d: %w", name, i, err)
err = fmt.Errorf("error running %s hook #%d: %w", name, i, err)
if name != Poststop {
return err
}
// Poststop hooks must all be run, even if one fails.
errs = append(errs, err)
}
}

return nil
return errors.Join(errs...)
}

// SetDefaultEnv sets the environment for those CommandHook entries
Expand Down
58 changes: 58 additions & 0 deletions libcontainer/configs/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package configs_test

import (
"encoding/json"
"errors"
"fmt"
"os"
"reflect"
Expand Down Expand Up @@ -155,6 +156,63 @@ func TestFuncHookRun(t *testing.T) {
}
}

func TestPoststopHooksRunAfterErrors(t *testing.T) {
state := &specs.State{Status: specs.StateStopped}
firstErr := errors.New("first hook failed")
lastErr := errors.New("last hook failed")
var executed []int

hooks := configs.Hooks{
configs.Poststop: configs.HookList{
configs.NewFunctionHook(func(*specs.State) error {
executed = append(executed, 1)
return firstErr
}),
configs.NewFunctionHook(func(*specs.State) error {
executed = append(executed, 2)
return nil
}),
configs.NewFunctionHook(func(*specs.State) error {
executed = append(executed, 3)
return lastErr
}),
},
}

err := hooks.Run(configs.Poststop, state)
if !reflect.DeepEqual(executed, []int{1, 2, 3}) {
t.Fatalf("expected every poststop hook to run, got %v", executed)
}
if !errors.Is(err, firstErr) || !errors.Is(err, lastErr) {
t.Fatalf("expected both hook errors to be returned, got %v", err)
}
}

func TestOtherHooksStopAfterError(t *testing.T) {
hookErr := errors.New("prestart hook failed")
executed := 0
hooks := configs.Hooks{
configs.Prestart: configs.HookList{
configs.NewFunctionHook(func(*specs.State) error {
executed++
return hookErr
}),
configs.NewFunctionHook(func(*specs.State) error {
executed++
return nil
}),
},
}

err := hooks.Run(configs.Prestart, &specs.State{})
if !errors.Is(err, hookErr) {
t.Fatalf("expected hook error %v, got %v", hookErr, err)
}
if executed != 1 {
t.Fatalf("expected later prestart hooks not to run, got %d hooks", executed)
}
}

func TestCommandHookRun(t *testing.T) {
state := &specs.State{
Version: "1",
Expand Down