github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/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/podman/v2/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 "createContainer":
   124  						config.Hooks.CreateContainer = append(config.Hooks.CreateContainer, namedHook.hook.Hook)
   125  					case "createRuntime":
   126  						config.Hooks.CreateRuntime = append(config.Hooks.CreateRuntime, namedHook.hook.Hook)
   127  					case "prestart":
   128  						config.Hooks.Prestart = append(config.Hooks.Prestart, namedHook.hook.Hook)
   129  					case "poststart":
   130  						config.Hooks.Poststart = append(config.Hooks.Poststart, namedHook.hook.Hook)
   131  					case "poststop":
   132  						config.Hooks.Poststop = append(config.Hooks.Poststop, namedHook.hook.Hook)
   133  					case "startContainer":
   134  						config.Hooks.StartContainer = append(config.Hooks.StartContainer, namedHook.hook.Hook)
   135  					default:
   136  						return extensionStageHooks, fmt.Errorf("hook %q: unknown stage %q", namedHook.name, stage)
   137  					}
   138  				}
   139  			}
   140  		} else {
   141  			logrus.Debugf("hook %s did not match", namedHook.name)
   142  		}
   143  	}
   144  
   145  	return extensionStageHooks, nil
   146  }