Files
podman/pkg/hooks/hooks.go
W. Trevor King c45d4c6d5c hooks: Fix monitoring of multiple directories
This isn't an issue with podman, which will only ever use one
directory.  But CRI-O generally uses two directories, and we want to
make sure that changes to the fallback directory are not clobbering
hooks configured in the override directory.  More background in [1].

I've split the handling into a single-directory block and a
multiple-directory block so we don't waste time polling the filesystem
for single-directory removals.

I'm using the single-directory block for the the zero-directory case
as well.  Managers with zero directories should not be receiving
fsnotify events, so I don't think it really matters which block
handles them.  If we want to handle this case robustly (because we're
concerned about something in the hook package adjusted the private
.directories property on the fly?), then we'll probably want to add an
explicit zero-directory block in future work.

[1]: https://github.com/kubernetes-incubator/cri-o/pull/1470

Signed-off-by: W. Trevor King <wking@tremily.us>

Closes: #757
Approved by: rhatdan
2018-05-17 22:39:13 +00:00

163 lines
4.3 KiB
Go

// Package hooks implements hook configuration and handling for CRI-O and libpod.
package hooks
import (
"context"
"fmt"
"path/filepath"
"sync"
rspec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
current "github.com/projectatomic/libpod/pkg/hooks/1.0.0"
"golang.org/x/text/collate"
"golang.org/x/text/language"
)
// Version is the current hook configuration version.
const Version = current.Version
const (
// DefaultDir is the default directory containing system hook configuration files.
DefaultDir = "/usr/share/containers/oci/hooks.d"
// OverrideDir is the directory for hook configuration files overriding the default entries.
OverrideDir = "/etc/containers/oci/hooks.d"
)
// Manager provides an opaque interface for managing CRI-O hooks.
type Manager struct {
hooks map[string]*current.Hook
directories []string
extensionStages []string
language language.Tag
lock sync.Mutex
}
type namedHook struct {
name string
hook *current.Hook
}
type namedHooks []*namedHook
// New creates a new hook manager. Directories are ordered by
// increasing preference (hook configurations in later directories
// override configurations with the same filename from earlier
// directories).
func New(ctx context.Context, directories []string, extensionStages []string, lang language.Tag) (manager *Manager, err error) {
manager = &Manager{
hooks: map[string]*current.Hook{},
directories: directories,
extensionStages: extensionStages,
language: lang,
}
for _, dir := range directories {
err = ReadDir(dir, manager.extensionStages, manager.hooks)
if err != nil {
return nil, err
}
}
return manager, nil
}
// filenames returns sorted hook entries.
func (m *Manager) namedHooks() (hooks []*namedHook) {
m.lock.Lock()
defer m.lock.Unlock()
hooks = make([]*namedHook, len(m.hooks))
i := 0
for name, hook := range m.hooks {
hooks[i] = &namedHook{
name: name,
hook: hook,
}
i++
}
return hooks
}
// Hooks injects OCI runtime hooks for a given container configuration.
func (m *Manager) Hooks(config *rspec.Spec, annotations map[string]string, hasBindMounts bool) (extensionStages map[string][]rspec.Hook, err error) {
hooks := m.namedHooks()
collator := collate.New(m.language, collate.IgnoreCase, collate.IgnoreWidth)
collator.Sort(namedHooks(hooks))
validStages := map[string]bool{} // beyond the OCI stages
for _, stage := range m.extensionStages {
validStages[stage] = true
}
for _, namedHook := range hooks {
match, err := namedHook.hook.When.Match(config, annotations, hasBindMounts)
if err != nil {
return extensionStages, errors.Wrapf(err, "matching hook %q", namedHook.name)
}
if match {
if config.Hooks == nil {
config.Hooks = &rspec.Hooks{}
}
for _, stage := range namedHook.hook.Stages {
switch stage {
case "prestart":
config.Hooks.Prestart = append(config.Hooks.Prestart, namedHook.hook.Hook)
case "poststart":
config.Hooks.Poststart = append(config.Hooks.Poststart, namedHook.hook.Hook)
case "poststop":
config.Hooks.Poststop = append(config.Hooks.Poststop, namedHook.hook.Hook)
default:
if !validStages[stage] {
return extensionStages, fmt.Errorf("hook %q: unknown stage %q", namedHook.name, stage)
}
if extensionStages == nil {
extensionStages = map[string][]rspec.Hook{}
}
extensionStages[stage] = append(extensionStages[stage], namedHook.hook.Hook)
}
}
}
}
return extensionStages, nil
}
// remove remove a hook by name.
func (m *Manager) remove(hook string) (ok bool) {
m.lock.Lock()
defer m.lock.Unlock()
_, ok = m.hooks[hook]
if ok {
delete(m.hooks, hook)
}
return ok
}
// add adds a hook by path
func (m *Manager) add(path string) (err error) {
m.lock.Lock()
defer m.lock.Unlock()
hook, err := Read(path, m.extensionStages)
if err != nil {
return err
}
m.hooks[filepath.Base(path)] = hook
return nil
}
// Len is part of the collate.Lister interface.
func (hooks namedHooks) Len() int {
return len(hooks)
}
// Swap is part of the collate.Lister interface.
func (hooks namedHooks) Swap(i, j int) {
hooks[i], hooks[j] = hooks[j], hooks[i]
}
// Bytes is part of the collate.Lister interface.
func (hooks namedHooks) Bytes(i int) []byte {
return []byte(hooks[i].name)
}