Vendor in containers/(storage,image, common, buildah)

Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
This commit is contained in:
Daniel J Walsh
2022-07-11 10:03:44 -04:00
parent 5f848d89ed
commit f67ab1eb20
576 changed files with 40399 additions and 10219 deletions

View File

@ -0,0 +1,88 @@
// Package hook is the 0.1.0 hook configuration structure.
package hook
import (
"encoding/json"
"errors"
"strings"
current "github.com/containers/common/pkg/hooks/1.0.0"
rspec "github.com/opencontainers/runtime-spec/specs-go"
)
// Version is the hook configuration version defined in this package.
const Version = "0.1.0"
// Hook is the hook configuration structure.
type Hook struct {
Hook *string `json:"hook"`
Arguments []string `json:"arguments,omitempty"`
// https://github.com/cri-o/cri-o/pull/1235
Stages []string `json:"stages"`
Stage []string `json:"stage"`
Cmds []string `json:"cmds,omitempty"`
Cmd []string `json:"cmd,omitempty"`
Annotations []string `json:"annotations,omitempty"`
Annotation []string `json:"annotation,omitempty"`
HasBindMounts *bool `json:"hasbindmounts,omitempty"`
}
func Read(content []byte) (hook *current.Hook, err error) {
var raw Hook
if err = json.Unmarshal(content, &raw); err != nil {
return nil, err
}
if raw.Hook == nil {
return nil, errors.New("missing required property: hook")
}
if raw.Stages == nil {
raw.Stages = raw.Stage
} else if raw.Stage != nil {
return nil, errors.New("cannot set both 'stage' and 'stages'")
}
if raw.Stages == nil {
return nil, errors.New("missing required property: stages")
}
if raw.Cmds == nil {
raw.Cmds = raw.Cmd
} else if raw.Cmd != nil {
return nil, errors.New("cannot set both 'cmd' and 'cmds'")
}
if raw.Annotations == nil {
raw.Annotations = raw.Annotation
} else if raw.Annotation != nil {
return nil, errors.New("cannot set both 'annotation' and 'annotations'")
}
hook = &current.Hook{
Version: current.Version,
Hook: rspec.Hook{
Path: *raw.Hook,
},
When: current.When{
Commands: raw.Cmds,
HasBindMounts: raw.HasBindMounts,
Or: true,
},
Stages: raw.Stages,
}
if raw.Arguments != nil {
hook.Hook.Args = append([]string{*raw.Hook}, raw.Arguments...)
}
if raw.Annotations != nil {
hook.When.Annotations = map[string]string{
".*": strings.Join(raw.Annotations, "|"),
}
}
return hook, nil
}

View File

@ -0,0 +1,89 @@
// Package hook is the 1.0.0 hook configuration structure.
package hook
import (
"encoding/json"
"errors"
"fmt"
"os"
"regexp"
rspec "github.com/opencontainers/runtime-spec/specs-go"
)
// Version is the hook configuration version defined in this package.
const Version = "1.0.0"
// Hook is the hook configuration structure.
type Hook struct {
Version string `json:"version"`
Hook rspec.Hook `json:"hook"`
When When `json:"when"`
Stages []string `json:"stages"`
}
// Read reads hook JSON bytes, verifies them, and returns the hook configuration.
func Read(content []byte) (hook *Hook, err error) {
if err = json.Unmarshal(content, &hook); err != nil {
return nil, err
}
return hook, nil
}
// Validate performs load-time hook validation.
func (hook *Hook) Validate(extensionStages []string) (err error) {
if hook == nil {
return errors.New("nil hook")
}
if hook.Version != Version {
return fmt.Errorf("unexpected hook version %q (expecting %v)", hook.Version, Version)
}
if hook.Hook.Path == "" {
return errors.New("missing required property: hook.path")
}
if _, err := os.Stat(hook.Hook.Path); err != nil {
return err
}
for key, value := range hook.When.Annotations {
if _, err = regexp.Compile(key); err != nil {
return fmt.Errorf("invalid annotation key %q: %w", key, err)
}
if _, err = regexp.Compile(value); err != nil {
return fmt.Errorf("invalid annotation value %q: %w", value, err)
}
}
for _, command := range hook.When.Commands {
if _, err = regexp.Compile(command); err != nil {
return fmt.Errorf("invalid command %q: %w", command, err)
}
}
if hook.Stages == nil {
return errors.New("missing required property: stages")
}
validStages := map[string]bool{
"createContainer": true,
"createRuntime": true,
"prestart": true,
"poststart": true,
"poststop": true,
"startContainer": true,
}
for _, stage := range extensionStages {
validStages[stage] = true
}
for _, stage := range hook.Stages {
if !validStages[stage] {
return fmt.Errorf("unknown stage %q", stage)
}
}
return nil
}

View File

@ -0,0 +1,96 @@
package hook
import (
"errors"
"fmt"
"regexp"
rspec "github.com/opencontainers/runtime-spec/specs-go"
)
// When holds hook-injection conditions.
type When struct {
Always *bool `json:"always,omitempty"`
Annotations map[string]string `json:"annotations,omitempty"`
Commands []string `json:"commands,omitempty"`
HasBindMounts *bool `json:"hasBindMounts,omitempty"`
// Or enables any-of matching.
//
// Deprecated: this property is for is backwards-compatibility with
// 0.1.0 hooks. It will be removed when we drop support for them.
Or bool `json:"-"`
}
// Match returns true if the given conditions match the configuration.
func (when *When) Match(config *rspec.Spec, annotations map[string]string, hasBindMounts bool) (match bool, err error) {
matches := 0
if when.Always != nil {
if *when.Always {
if when.Or {
return true, nil
}
matches++
} else if !when.Or {
return false, nil
}
}
if when.HasBindMounts != nil {
if *when.HasBindMounts && hasBindMounts {
if when.Or {
return true, nil
}
matches++
} else if !when.Or {
return false, nil
}
}
for keyPattern, valuePattern := range when.Annotations {
match := false
for key, value := range annotations {
match, err = regexp.MatchString(keyPattern, key)
if err != nil {
return false, fmt.Errorf("annotation key: %w", err)
}
if match {
match, err = regexp.MatchString(valuePattern, value)
if err != nil {
return false, fmt.Errorf("annotation value: %w", err)
}
if match {
break
}
}
}
if match {
if when.Or {
return true, nil
}
matches++
} else if !when.Or {
return false, nil
}
}
if config.Process != nil && len(when.Commands) > 0 {
if len(config.Process.Args) == 0 {
return false, errors.New("process.args must have at least one entry")
}
command := config.Process.Args[0]
for _, cmdPattern := range when.Commands {
match, err := regexp.MatchString(cmdPattern, command)
if err != nil {
return false, fmt.Errorf("command: %w", err)
}
if match {
return true, nil
}
}
return false, nil
}
return matches > 0, nil
}

View File

@ -0,0 +1,22 @@
# OCI Hooks Configuration
For POSIX platforms, the [OCI runtime configuration][runtime-spec] supports [hooks][spec-hooks] for configuring custom actions related to the life cycle of the container.
The way you enable the hooks above is by editing the OCI runtime configuration before running the OCI runtime (e.g. [`runc`][runc]).
CRI-O and `podman create` create the OCI configuration for you, and this documentation allows developers to configure them to set their intended hooks.
One problem with hooks is that the runtime actually stalls execution of the container before running the hooks and stalls completion of the container, until all hooks complete.
This can cause some performance issues.
Also a lot of hooks just check if certain configuration is set and then exit early, without doing anything.
For example the [oci-systemd-hook][] only executes if the command is `init` or `systemd`, otherwise it just exits.
This means if we automatically enabled all hooks, every container would have to execute `oci-systemd-hook`, even if they don't run systemd inside of the container.
Performance would also suffer if we executed each hook at each stage ([pre-start][], [post-start][], and [post-stop][]).
The hooks configuration is documented in [`oci-hooks.5`](docs/oci-hooks.5.md).
[oci-systemd-hook]: https://github.com/projectatomic/oci-systemd-hook
[post-start]: https://github.com/opencontainers/runtime-spec/blob/v1.0.1/config.md#poststart
[post-stop]: https://github.com/opencontainers/runtime-spec/blob/v1.0.1/config.md#poststop
[pre-start]: https://github.com/opencontainers/runtime-spec/blob/v1.0.1/config.md#prestart
[runc]: https://github.com/opencontainers/runc
[runtime-spec]: https://github.com/opencontainers/runtime-spec/blob/v1.0.1/spec.md
[spec-hooks]: https://github.com/opencontainers/runtime-spec/blob/v1.0.1/config.md#posix-platform-hooks

View File

@ -0,0 +1,69 @@
// Package exec provides utilities for executing Open Container Initiative runtime hooks.
package exec
import (
"bytes"
"context"
"fmt"
"io"
osexec "os/exec"
"time"
rspec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
)
// DefaultPostKillTimeout is the recommended default post-kill timeout.
const DefaultPostKillTimeout = time.Duration(10) * time.Second
// Run executes the hook and waits for it to complete or for the
// context or hook-specified timeout to expire.
func Run(ctx context.Context, hook *rspec.Hook, state []byte, stdout io.Writer, stderr io.Writer, postKillTimeout time.Duration) (hookErr, err error) {
cmd := osexec.Cmd{
Path: hook.Path,
Args: hook.Args,
Env: hook.Env,
Stdin: bytes.NewReader(state),
Stdout: stdout,
Stderr: stderr,
}
if cmd.Env == nil {
cmd.Env = []string{}
}
if hook.Timeout != nil {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, time.Duration(*hook.Timeout)*time.Second)
defer cancel()
}
err = cmd.Start()
if err != nil {
return err, err
}
exit := make(chan error, 1)
go func() {
err := cmd.Wait()
if err != nil {
err = fmt.Errorf("executing %v: %w", cmd.Args, err)
}
exit <- err
}()
select {
case err = <-exit:
return err, err
case <-ctx.Done():
if err := cmd.Process.Kill(); err != nil {
logrus.Errorf("Failed to kill pid %v", cmd.Process)
}
timer := time.NewTimer(postKillTimeout)
defer timer.Stop()
select {
case <-timer.C:
err = fmt.Errorf("failed to reap process within %s of the kill signal", postKillTimeout)
case err = <-exit:
}
return err, ctx.Err()
}
}

View File

@ -0,0 +1,72 @@
package exec
import (
"bytes"
"context"
"encoding/json"
"fmt"
"reflect"
"time"
"github.com/davecgh/go-spew/spew"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pmezard/go-difflib/difflib"
"github.com/sirupsen/logrus"
)
var spewConfig = spew.ConfigState{
Indent: " ",
DisablePointerAddresses: true,
DisableCapacities: true,
SortKeys: true,
}
// RuntimeConfigFilter calls a series of hooks. But instead of
// passing container state on their standard input,
// RuntimeConfigFilter passes the proposed runtime configuration (and
// reads back a possibly-altered form from their standard output).
func RuntimeConfigFilter(ctx context.Context, hooks []spec.Hook, config *spec.Spec, postKillTimeout time.Duration) (hookErr, err error) {
data, err := json.Marshal(config)
if err != nil {
return nil, err
}
for i, hook := range hooks {
hook := hook
var stdout bytes.Buffer
hookErr, err = Run(ctx, &hook, data, &stdout, nil, postKillTimeout)
if err != nil {
return hookErr, err
}
data = stdout.Bytes()
var newConfig spec.Spec
err = json.Unmarshal(data, &newConfig)
if err != nil {
logrus.Debugf("invalid JSON from config-filter hook %d:\n%s", i, string(data))
return nil, fmt.Errorf("unmarshal output from config-filter hook %d: %w", i, err)
}
if !reflect.DeepEqual(config, &newConfig) {
oldConfig := spewConfig.Sdump(config)
newConfig := spewConfig.Sdump(&newConfig)
diff, err := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
A: difflib.SplitLines(oldConfig),
B: difflib.SplitLines(newConfig),
FromFile: "Old",
FromDate: "",
ToFile: "New",
ToDate: "",
Context: 1,
})
if err == nil {
logrus.Debugf("precreate hook %d made configuration changes:\n%s", i, diff)
} else {
logrus.Warnf("Precreate hook %d made configuration changes, but we could not compute a diff: %v", i, err)
}
}
*config = newConfig
}
return nil, nil
}

146
vendor/github.com/containers/common/pkg/hooks/hooks.go generated vendored Normal file
View File

@ -0,0 +1,146 @@
// Package hooks implements hook configuration and handling for CRI-O and libpod.
package hooks
import (
"context"
"errors"
"fmt"
"os"
"sort"
"strings"
"sync"
current "github.com/containers/common/pkg/hooks/1.0.0"
rspec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
)
// 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
lock sync.Mutex
}
type namedHook struct {
name string
hook *current.Hook
}
// 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).
//
// extensionStages allows callers to add additional stages beyond
// those specified in the OCI Runtime Specification and to control
// OCI-defined stages instead of delegating to the OCI runtime. See
// Hooks() for more information.
func New(ctx context.Context, directories []string, extensionStages []string) (manager *Manager, err error) {
manager = &Manager{
hooks: map[string]*current.Hook{},
directories: directories,
extensionStages: extensionStages,
}
for _, dir := range directories {
err = ReadDir(dir, manager.extensionStages, manager.hooks)
if err != nil && !errors.Is(err, os.ErrNotExist) {
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.
//
// If extensionStages was set when initializing the Manager,
// matching hooks requesting those stages will be returned in
// extensionStageHooks. This takes precedence over their inclusion in
// the OCI configuration. For example:
//
// manager, err := New(ctx, []string{DefaultDir}, []string{"poststop"})
// extensionStageHooks, err := manager.Hooks(config, annotations, hasBindMounts)
//
// will have any matching post-stop hooks in extensionStageHooks and
// will not insert them into config.Hooks.Poststop.
func (m *Manager) Hooks(config *rspec.Spec, annotations map[string]string, hasBindMounts bool) (extensionStageHooks map[string][]rspec.Hook, err error) {
hooks := m.namedHooks()
sort.Slice(hooks, func(i, j int) bool { return strings.ToLower(hooks[i].name) < strings.ToLower(hooks[j].name) })
localStages := map[string]bool{} // stages destined for extensionStageHooks
for _, stage := range m.extensionStages {
localStages[stage] = true
}
for _, namedHook := range hooks {
match, err := namedHook.hook.When.Match(config, annotations, hasBindMounts)
if err != nil {
return extensionStageHooks, fmt.Errorf("matching hook %q: %w", namedHook.name, err)
}
if match {
logrus.Debugf("hook %s matched; adding to stages %v", namedHook.name, namedHook.hook.Stages)
if config.Hooks == nil {
config.Hooks = &rspec.Hooks{}
}
for _, stage := range namedHook.hook.Stages {
if _, ok := localStages[stage]; ok {
if extensionStageHooks == nil {
extensionStageHooks = map[string][]rspec.Hook{}
}
extensionStageHooks[stage] = append(extensionStageHooks[stage], namedHook.hook.Hook)
} else {
switch stage {
case "createContainer":
config.Hooks.CreateContainer = append(config.Hooks.CreateContainer, namedHook.hook.Hook)
case "createRuntime":
config.Hooks.CreateRuntime = append(config.Hooks.CreateRuntime, namedHook.hook.Hook)
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)
case "startContainer":
config.Hooks.StartContainer = append(config.Hooks.StartContainer, namedHook.hook.Hook)
default:
return extensionStageHooks, fmt.Errorf("hook %q: unknown stage %q", namedHook.name, stage)
}
}
}
} else {
logrus.Debugf("hook %s did not match", namedHook.name)
}
}
return extensionStageHooks, nil
}

View File

@ -0,0 +1,66 @@
package hooks
import (
"context"
current "github.com/containers/common/pkg/hooks/1.0.0"
"github.com/fsnotify/fsnotify"
"github.com/sirupsen/logrus"
)
// Monitor dynamically monitors hook directories for additions,
// updates, and removals.
//
// This function writes two empty structs to the sync channel: the
// first is written after the watchers are established and the second
// when this function exits. The expected usage is:
//
// ctx, cancel := context.WithCancel(context.Background())
// sync := make(chan error, 2)
// go m.Monitor(ctx, sync)
// err := <-sync // block until writers are established
// if err != nil {
// return err // failed to establish watchers
// }
// // do stuff
// cancel()
// err = <-sync // block until monitor finishes
func (m *Manager) Monitor(ctx context.Context, sync chan<- error) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
sync <- err
return
}
defer watcher.Close()
for _, dir := range m.directories {
err = watcher.Add(dir)
if err != nil {
logrus.Errorf("Failed to watch %q for hooks", dir)
sync <- err
return
}
logrus.Debugf("monitoring %q for hooks", dir)
}
sync <- nil
for {
select {
case event := <-watcher.Events:
m.hooks = make(map[string]*current.Hook)
for _, dir := range m.directories {
err = ReadDir(dir, m.extensionStages, m.hooks)
if err != nil {
logrus.Errorf("Failed loading hooks for %s: %v", event.Name, err)
}
}
case <-ctx.Done():
err = ctx.Err()
logrus.Debugf("hook monitoring canceled: %v", err)
sync <- err
close(sync)
return
}
}
}

101
vendor/github.com/containers/common/pkg/hooks/read.go generated vendored Normal file
View File

@ -0,0 +1,101 @@
// Package hooks implements CRI-O's hook handling.
package hooks
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
old "github.com/containers/common/pkg/hooks/0.1.0"
current "github.com/containers/common/pkg/hooks/1.0.0"
"github.com/sirupsen/logrus"
)
type reader func(content []byte) (*current.Hook, error)
var (
// ErrNoJSONSuffix represents hook-add attempts where the filename
// does not end in '.json'.
ErrNoJSONSuffix = errors.New("hook filename does not end in '.json'")
// Readers registers per-version hook readers.
Readers = map[string]reader{}
)
// Read reads a hook JSON file, verifies it, and returns the hook configuration.
func Read(path string, extensionStages []string) (*current.Hook, error) {
if !strings.HasSuffix(path, ".json") {
return nil, ErrNoJSONSuffix
}
content, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
hook, err := read(content)
if err != nil {
return nil, fmt.Errorf("parsing hook %q: %w", path, err)
}
err = hook.Validate(extensionStages)
return hook, err
}
func read(content []byte) (hook *current.Hook, err error) {
var ver version
if err := json.Unmarshal(content, &ver); err != nil {
return nil, fmt.Errorf("version check: %w", err)
}
reader, ok := Readers[ver.Version]
if !ok {
return nil, fmt.Errorf("unrecognized hook version: %q", ver.Version)
}
hook, err = reader(content)
if err != nil {
return hook, fmt.Errorf("%s: %v", ver.Version, err)
}
return hook, err
}
// ReadDir reads hook JSON files from a directory into the given map,
// clobbering any previous entries with the same filenames.
func ReadDir(path string, extensionStages []string, hooks map[string]*current.Hook) error {
logrus.Debugf("reading hooks from %s", path)
files, err := ioutil.ReadDir(path)
if err != nil {
return err
}
res := err
for _, file := range files {
filePath := filepath.Join(path, file.Name())
hook, err := Read(filePath, extensionStages)
if err != nil {
if err == ErrNoJSONSuffix {
continue
}
if errors.Is(err, os.ErrNotExist) {
if err2, ok := err.(*os.PathError); ok && err2.Path == filePath {
continue
}
}
if res == nil {
res = err
} else {
res = fmt.Errorf("%v: %w", err, res)
}
continue
}
hooks[file.Name()] = hook
logrus.Debugf("added hook %s", filePath)
}
return res
}
func init() {
Readers[current.Version] = current.Read
Readers[old.Version] = old.Read
Readers[""] = old.Read
}

View File

@ -0,0 +1,6 @@
package hooks
// version a structure for checking the version of a hook configuration.
type version struct {
Version string `json:"version"`
}