Skip to content

Commit fdcf633

Browse files
rhatdanrh-atomic-bot
authored andcommitted
Add hooks support to podman
Signed-off-by: Daniel J Walsh <[email protected]> Closes: #155 Approved by: mheon
1 parent ca3b241 commit fdcf633

File tree

17 files changed

+677
-2
lines changed

17 files changed

+677
-2
lines changed

cmd/podman/main.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/containers/storage/pkg/reexec"
99
"github.com/pkg/errors"
10+
"github.com/projectatomic/libpod/pkg/hooks"
1011
"github.com/projectatomic/libpod/version"
1112
"github.com/sirupsen/logrus"
1213
"github.com/urfave/cli"
@@ -122,6 +123,12 @@ func main() {
122123
Name: "cpu-profile",
123124
Usage: "path for the cpu profiling results",
124125
},
126+
cli.StringFlag{
127+
Name: "hooks-dir-path",
128+
Usage: "set the OCI hooks directory path",
129+
Value: hooks.DefaultHooksDir,
130+
Hidden: true,
131+
},
125132
cli.StringFlag{
126133
Name: "log-level",
127134
Usage: "log messages above specified level: debug, info, warn, error (default), fatal or panic",

cmd/podman/spec.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,6 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) {
378378
}
379379

380380
/*
381-
Hooks: &configSpec.Hooks{},
382381
//Annotations
383382
Resources: &configSpec.LinuxResources{
384383
BlockIO: &blkio,

cmd/podman/utils.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ func getRuntime(c *cli.Context) (*libpod.Runtime, error) {
4848
if c.GlobalIsSet("cni-config-dir") {
4949
options = append(options, libpod.WithCNIConfigDir(c.GlobalString("cni-config-dir")))
5050
}
51+
options = append(options, libpod.WithHooksDir(c.GlobalString("hooks-dir-path")))
5152

5253
// TODO flag to set CNI plugins dir?
5354

libpod/container_internal.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"io/ioutil"
88
"os"
99
"path/filepath"
10+
"regexp"
1011
"strings"
1112
"syscall"
1213
"time"
@@ -22,6 +23,7 @@ import (
2223
"github.com/pkg/errors"
2324
crioAnnotations "github.com/projectatomic/libpod/pkg/annotations"
2425
"github.com/projectatomic/libpod/pkg/chrootuser"
26+
"github.com/projectatomic/libpod/pkg/hooks"
2527
"github.com/projectatomic/libpod/pkg/secrets"
2628
"github.com/projectatomic/libpod/pkg/util"
2729
"github.com/sirupsen/logrus"
@@ -931,6 +933,9 @@ func (c *Container) generateSpec() (*spec.Spec, error) {
931933
}
932934
}
933935

936+
if err := c.setupOCIHooks(&g); err != nil {
937+
return nil, errors.Wrapf(err, "error setting up OCI Hooks")
938+
}
934939
// Bind builtin image volumes
935940
if c.config.ImageVolumes {
936941
if err := c.addImageVolumes(&g); err != nil {
@@ -1103,3 +1108,58 @@ func (c *Container) saveSpec(spec *spec.Spec) error {
11031108

11041109
return nil
11051110
}
1111+
1112+
func (c *Container) setupOCIHooks(g *generate.Generator) error {
1113+
addedHooks := map[string]struct{}{}
1114+
ocihooks, err := hooks.SetupHooks(c.runtime.config.HooksDir)
1115+
if err != nil {
1116+
return err
1117+
}
1118+
addHook := func(hook hooks.HookParams) error {
1119+
// Only add a hook once
1120+
if _, ok := addedHooks[hook.Hook]; !ok {
1121+
if err := hooks.AddOCIHook(g, hook); err != nil {
1122+
return err
1123+
}
1124+
addedHooks[hook.Hook] = struct{}{}
1125+
}
1126+
return nil
1127+
}
1128+
for _, hook := range ocihooks {
1129+
logrus.Debugf("SetupOCIHooks", hook)
1130+
if hook.HasBindMounts && len(c.config.Spec.Mounts) > 0 {
1131+
if err := addHook(hook); err != nil {
1132+
return err
1133+
}
1134+
continue
1135+
}
1136+
for _, cmd := range hook.Cmds {
1137+
match, err := regexp.MatchString(cmd, c.config.Spec.Process.Args[0])
1138+
if err != nil {
1139+
logrus.Errorf("Invalid regex %q:%q", cmd, err)
1140+
continue
1141+
}
1142+
if match {
1143+
if err := addHook(hook); err != nil {
1144+
return err
1145+
}
1146+
}
1147+
}
1148+
annotations := c.Spec().Annotations
1149+
for _, annotationRegex := range hook.Annotations {
1150+
for _, annotation := range annotations {
1151+
match, err := regexp.MatchString(annotationRegex, annotation)
1152+
if err != nil {
1153+
logrus.Errorf("Invalid regex %q:%q", annotationRegex, err)
1154+
continue
1155+
}
1156+
if match {
1157+
if err := addHook(hook); err != nil {
1158+
return err
1159+
}
1160+
}
1161+
}
1162+
}
1163+
}
1164+
return nil
1165+
}

libpod/options.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,20 @@ func WithStaticDir(dir string) RuntimeOption {
172172
}
173173
}
174174

175+
// WithHooksDir sets the directory to look for OCI runtime hooks config
176+
// Note we are not saving this in database, since this is really just for used
177+
// for testing
178+
func WithHooksDir(hooksDir string) RuntimeOption {
179+
return func(rt *Runtime) error {
180+
if rt.valid {
181+
return ErrRuntimeFinalized
182+
}
183+
184+
rt.config.HooksDir = hooksDir
185+
return nil
186+
}
187+
}
188+
175189
// WithTmpDir sets the directory that temporary runtime files which are not
176190
// expected to survive across reboots will be stored
177191
// This should be located on a tmpfs mount (/tmp or /var/run for example)

libpod/runtime.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/docker/docker/pkg/namesgenerator"
1616
"github.com/pkg/errors"
1717
"github.com/projectatomic/libpod/libpod/image"
18+
"github.com/projectatomic/libpod/pkg/hooks"
1819
"github.com/sirupsen/logrus"
1920
"github.com/ulule/deepcopier"
2021
)
@@ -127,6 +128,8 @@ type RuntimeConfig struct {
127128
// CNIPluginDir sets a number of directories where the CNI network
128129
// plugins can be located
129130
CNIPluginDir []string `toml:"cni_plugin_dir"`
131+
// HooksDir Path to the directory containing hooks configuration files
132+
HooksDir string `toml:"hooks_dir"`
130133
}
131134

132135
var (
@@ -153,6 +156,7 @@ var (
153156
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
154157
},
155158
CgroupManager: "cgroupfs",
159+
HooksDir: hooks.DefaultHooksDir,
156160
StaticDir: filepath.Join(storage.DefaultStoreOptions.GraphRoot, "libpod"),
157161
TmpDir: "/var/run/libpod",
158162
MaxLogSize: -1,

libpod/testdata/config.toml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
[crio]
2+
root = "/var/lib/containers/storage"
3+
runroot = "/var/run/containers/storage"
4+
storage_driver = "overlay2"
5+
log_dir = "/var/log/crio/pods"
6+
file_locking = true
7+
[crio.runtime]
8+
runtime = "/usr/bin/runc"
9+
runtime_untrusted_workload = ""
10+
default_workload_trust = "trusted"
11+
conmon = "/usr/local/libexec/crio/conmon"
12+
conmon_env = ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]
13+
selinux = true
14+
seccomp_profile = "/etc/crio/seccomp.json"
15+
apparmor_profile = "crio-default"
16+
cgroup_manager = "cgroupfs"
17+
hooks_dir_path = "/usr/share/containers/oci/hooks.d"
18+
pids_limit = 2048
19+
container_exits_dir = "/var/run/podman/exits"
20+
[crio.image]
21+
default_transport = "docker://"
22+
pause_image = "kubernetes/pause"
23+
pause_command = "/pause"
24+
signature_policy = ""
25+
image_volumes = "mkdir"
26+
[crio.network]
27+
network_dir = "/etc/cni/net.d/"
28+
plugin_dir = "/opt/cni/bin/"

pkg/hooks/hooks.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package hooks
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io/ioutil"
7+
"os"
8+
"path/filepath"
9+
"regexp"
10+
"strings"
11+
"syscall"
12+
13+
spec "github.com/opencontainers/runtime-spec/specs-go"
14+
"github.com/opencontainers/runtime-tools/generate"
15+
"github.com/pkg/errors"
16+
"github.com/sirupsen/logrus"
17+
)
18+
19+
const (
20+
// DefaultHooksDir Default directory containing hooks config files
21+
DefaultHooksDir = "/usr/share/containers/oci/hooks.d"
22+
// OverrideHooksDir Directory where admin can override the default configuration
23+
OverrideHooksDir = "/etc/containers/oci/hooks.d"
24+
)
25+
26+
// HookParams is the structure returned from read the hooks configuration
27+
type HookParams struct {
28+
Hook string `json:"hook"`
29+
Stage []string `json:"stage"`
30+
Cmds []string `json:"cmd"`
31+
Annotations []string `json:"annotation"`
32+
HasBindMounts bool `json:"hasbindmounts"`
33+
Arguments []string `json:"arguments"`
34+
}
35+
36+
// readHook reads hooks json files, verifies it and returns the json config
37+
func readHook(hookPath string) (HookParams, error) {
38+
var hook HookParams
39+
raw, err := ioutil.ReadFile(hookPath)
40+
if err != nil {
41+
return hook, errors.Wrapf(err, "error Reading hook %q", hookPath)
42+
}
43+
if err := json.Unmarshal(raw, &hook); err != nil {
44+
return hook, errors.Wrapf(err, "error Unmarshalling JSON for %q", hookPath)
45+
}
46+
if _, err := os.Stat(hook.Hook); err != nil {
47+
return hook, errors.Wrapf(err, "unable to stat hook %q in hook config %q", hook.Hook, hookPath)
48+
}
49+
validStage := map[string]bool{"prestart": true, "poststart": true, "poststop": true}
50+
for _, cmd := range hook.Cmds {
51+
if _, err = regexp.Compile(cmd); err != nil {
52+
return hook, errors.Wrapf(err, "invalid cmd regular expression %q defined in hook config %q", cmd, hookPath)
53+
}
54+
}
55+
for _, cmd := range hook.Annotations {
56+
if _, err = regexp.Compile(cmd); err != nil {
57+
return hook, errors.Wrapf(err, "invalid cmd regular expression %q defined in hook config %q", cmd, hookPath)
58+
}
59+
}
60+
for _, stage := range hook.Stage {
61+
if !validStage[stage] {
62+
return hook, errors.Wrapf(err, "unknown stage %q defined in hook config %q", stage, hookPath)
63+
}
64+
}
65+
return hook, nil
66+
}
67+
68+
// readHooks reads hooks json files in directory to setup OCI Hooks
69+
// adding hooks to the passedin hooks map.
70+
func readHooks(hooksPath string, hooks map[string]HookParams) error {
71+
if _, err := os.Stat(hooksPath); err != nil {
72+
if os.IsNotExist(err) {
73+
logrus.Warnf("hooks path: %q does not exist", hooksPath)
74+
return nil
75+
}
76+
return errors.Wrapf(err, "unable to stat hooks path %q", hooksPath)
77+
}
78+
79+
files, err := ioutil.ReadDir(hooksPath)
80+
if err != nil {
81+
return err
82+
}
83+
84+
for _, file := range files {
85+
if !strings.HasSuffix(file.Name(), ".json") {
86+
continue
87+
}
88+
hook, err := readHook(filepath.Join(hooksPath, file.Name()))
89+
if err != nil {
90+
return err
91+
}
92+
for key, h := range hooks {
93+
// hook.Hook can only be defined in one hook file, unless it has the
94+
// same name in the override path.
95+
if hook.Hook == h.Hook && key != file.Name() {
96+
return errors.Wrapf(syscall.EINVAL, "duplicate path, hook %q from %q already defined in %q", hook.Hook, hooksPath, key)
97+
}
98+
}
99+
hooks[file.Name()] = hook
100+
}
101+
return nil
102+
}
103+
104+
// SetupHooks takes a hookspath and reads all of the hooks in that directory.
105+
// returning a map of the configured hooks
106+
func SetupHooks(hooksPath string) (map[string]HookParams, error) {
107+
hooksMap := make(map[string]HookParams)
108+
if err := readHooks(hooksPath, hooksMap); err != nil {
109+
return nil, err
110+
}
111+
if hooksPath == DefaultHooksDir {
112+
if err := readHooks(OverrideHooksDir, hooksMap); err != nil {
113+
return nil, err
114+
}
115+
}
116+
117+
return hooksMap, nil
118+
}
119+
120+
// AddOCIHook generates OCI specification using the included hook
121+
func AddOCIHook(g *generate.Generator, hook HookParams) error {
122+
for _, stage := range hook.Stage {
123+
h := spec.Hook{
124+
Path: hook.Hook,
125+
Args: append([]string{hook.Hook}, hook.Arguments...),
126+
Env: []string{fmt.Sprintf("stage=%s", stage)},
127+
}
128+
logrus.Debugf("AddOCIHook", h)
129+
switch stage {
130+
case "prestart":
131+
g.AddPreStartHook(h)
132+
133+
case "poststart":
134+
g.AddPostStartHook(h)
135+
136+
case "poststop":
137+
g.AddPostStopHook(h)
138+
}
139+
}
140+
return nil
141+
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"cmd" : [".*"],
3-
"hook" : "HOOKSDIR/checkhook.sh",
3+
"hook" : "/tmp/checkhook.sh",
44
"stage" : [ "prestart" ]
55
}

0 commit comments

Comments
 (0)