github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/pkg/hooks/hooks.go (about) 1 // Package hooks implements hook configuration and handling for CRI-O and libpod. 2 package hooks 3 4 import ( 5 "context" 6 "fmt" 7 "os" 8 "sort" 9 "strings" 10 "sync" 11 12 current "github.com/containers/libpod/pkg/hooks/1.0.0" 13 rspec "github.com/opencontainers/runtime-spec/specs-go" 14 "github.com/pkg/errors" 15 "github.com/sirupsen/logrus" 16 ) 17 18 // Version is the current hook configuration version. 19 const Version = current.Version 20 21 const ( 22 // DefaultDir is the default directory containing system hook configuration files. 23 DefaultDir = "/usr/share/containers/oci/hooks.d" 24 25 // OverrideDir is the directory for hook configuration files overriding the default entries. 26 OverrideDir = "/etc/containers/oci/hooks.d" 27 ) 28 29 // Manager provides an opaque interface for managing CRI-O hooks. 30 type Manager struct { 31 hooks map[string]*current.Hook 32 directories []string 33 extensionStages []string 34 lock sync.Mutex 35 } 36 37 type namedHook struct { 38 name string 39 hook *current.Hook 40 } 41 42 // New creates a new hook manager. Directories are ordered by 43 // increasing preference (hook configurations in later directories 44 // override configurations with the same filename from earlier 45 // directories). 46 // 47 // extensionStages allows callers to add additional stages beyond 48 // those specified in the OCI Runtime Specification and to control 49 // OCI-defined stages instead of delagating to the OCI runtime. See 50 // Hooks() for more information. 51 func New(ctx context.Context, directories []string, extensionStages []string) (manager *Manager, err error) { 52 manager = &Manager{ 53 hooks: map[string]*current.Hook{}, 54 directories: directories, 55 extensionStages: extensionStages, 56 } 57 58 for _, dir := range directories { 59 err = ReadDir(dir, manager.extensionStages, manager.hooks) 60 if err != nil && !os.IsNotExist(err) { 61 return nil, err 62 } 63 } 64 65 return manager, nil 66 } 67 68 // filenames returns sorted hook entries. 69 func (m *Manager) namedHooks() (hooks []*namedHook) { 70 m.lock.Lock() 71 defer m.lock.Unlock() 72 73 hooks = make([]*namedHook, len(m.hooks)) 74 i := 0 75 for name, hook := range m.hooks { 76 hooks[i] = &namedHook{ 77 name: name, 78 hook: hook, 79 } 80 i++ 81 } 82 83 return hooks 84 } 85 86 // Hooks injects OCI runtime hooks for a given container configuration. 87 // 88 // If extensionStages was set when initializing the Manager, 89 // matching hooks requesting those stages will be returned in 90 // extensionStageHooks. This takes precedence over their inclusion in 91 // the OCI configuration. For example: 92 // 93 // manager, err := New(ctx, []string{DefaultDir}, []string{"poststop"}) 94 // extensionStageHooks, err := manager.Hooks(config, annotations, hasBindMounts) 95 // 96 // will have any matching post-stop hooks in extensionStageHooks and 97 // will not insert them into config.Hooks.Poststop. 98 func (m *Manager) Hooks(config *rspec.Spec, annotations map[string]string, hasBindMounts bool) (extensionStageHooks map[string][]rspec.Hook, err error) { 99 hooks := m.namedHooks() 100 sort.Slice(hooks, func(i, j int) bool { return strings.ToLower(hooks[i].name) < strings.ToLower(hooks[j].name) }) 101 localStages := map[string]bool{} // stages destined for extensionStageHooks 102 for _, stage := range m.extensionStages { 103 localStages[stage] = true 104 } 105 for _, namedHook := range hooks { 106 match, err := namedHook.hook.When.Match(config, annotations, hasBindMounts) 107 if err != nil { 108 return extensionStageHooks, errors.Wrapf(err, "matching hook %q", namedHook.name) 109 } 110 if match { 111 logrus.Debugf("hook %s matched; adding to stages %v", namedHook.name, namedHook.hook.Stages) 112 if config.Hooks == nil { 113 config.Hooks = &rspec.Hooks{} 114 } 115 for _, stage := range namedHook.hook.Stages { 116 if _, ok := localStages[stage]; ok { 117 if extensionStageHooks == nil { 118 extensionStageHooks = map[string][]rspec.Hook{} 119 } 120 extensionStageHooks[stage] = append(extensionStageHooks[stage], namedHook.hook.Hook) 121 } else { 122 switch stage { 123 case "prestart": 124 config.Hooks.Prestart = append(config.Hooks.Prestart, namedHook.hook.Hook) 125 case "poststart": 126 config.Hooks.Poststart = append(config.Hooks.Poststart, namedHook.hook.Hook) 127 case "poststop": 128 config.Hooks.Poststop = append(config.Hooks.Poststop, namedHook.hook.Hook) 129 default: 130 return extensionStageHooks, fmt.Errorf("hook %q: unknown stage %q", namedHook.name, stage) 131 } 132 } 133 } 134 } else { 135 logrus.Debugf("hook %s did not match", namedHook.name) 136 } 137 } 138 139 return extensionStageHooks, nil 140 }