From d6725e951ee958b61af8c32d5c71d79d2e708432 Mon Sep 17 00:00:00 2001
From: lujingxiao <lujingxiao@huawei.com>
Date: Sat, 19 Jan 2019 11:16:48 +0800
Subject: [PATCH 014/111] hookspec:  Add default hooks for all
 containers

reason: Add new flag `--hook-spec` for daemon, so that we can specify one json
file containing hooks definition for all containers.

You can also add this into `/etc/docker/daemon.json` daemon config file:

```
{
    "hook-spec": "/tmp/hookspec.json"
}
```

Change-Id: I9263d5a912daeb04621e7d2ec991204333c2b931
Signed-off-by: Zhang Wei <zhangwei555@huawei.com>
Signed-off-by: xiadanni <xiadanni@huawei.com>
Signed-off-by: lujingxiao <lujingxiao@huawei.com>
---
 components/cli/cli/command/system/info.go     |  4 +
 components/cli/contrib/completion/bash/docker |  1 +
 .../cli/docs/reference/commandline/dockerd.md |  1 +
 components/cli/man/dockerd.8.md               | 25 ++++-
 .../docker/docker/api/types/types.go          |  1 +
 components/engine/api/types/types.go          |  1 +
 components/engine/cmd/dockerd/config.go       |  1 +
 components/engine/daemon/config/config.go     |  1 +
 components/engine/daemon/container.go         | 94 +++++++++++++++++--
 components/engine/daemon/daemon.go            |  7 ++
 components/engine/daemon/info.go              |  1 +
 11 files changed, 126 insertions(+), 11 deletions(-)

diff --git a/components/cli/cli/command/system/info.go b/components/cli/cli/command/system/info.go
index 92fc2cd3e7..17ccc14aec 100644
--- a/components/cli/cli/command/system/info.go
+++ b/components/cli/cli/command/system/info.go
@@ -204,6 +204,10 @@ func prettyPrintInfo(dockerCli command.Cli, info types.Info) error {
 	}
 
 	fmt.Fprintln(dockerCli.Out(), "Live Restore Enabled:", info.LiveRestoreEnabled)
+	if info.HookSpec != "" {
+		fmt.Fprintf(dockerCli.Out(), "Default hook spec file: %s", info.HookSpec)
+	}
+
 	if info.ProductLicense != "" {
 		fmt.Fprintln(dockerCli.Out(), "Product License:", info.ProductLicense)
 	}
diff --git a/components/cli/contrib/completion/bash/docker b/components/cli/contrib/completion/bash/docker
index 9012988075..64f7fe08dd 100644
--- a/components/cli/contrib/completion/bash/docker
+++ b/components/cli/contrib/completion/bash/docker
@@ -2274,6 +2274,7 @@ _docker_daemon() {
 		--fixed-cidr
 		--fixed-cidr-v6
 		--group -G
+		--hook-spec
 		--init-path
 		--insecure-registry
 		--ip
diff --git a/components/cli/docs/reference/commandline/dockerd.md b/components/cli/docs/reference/commandline/dockerd.md
index 4b50b78b19..bbf6908af3 100644
--- a/components/cli/docs/reference/commandline/dockerd.md
+++ b/components/cli/docs/reference/commandline/dockerd.md
@@ -53,6 +53,7 @@ Options:
       --fixed-cidr-v6 string                  IPv6 subnet for fixed IPs
   -G, --group string                          Group for the unix socket (default "docker")
       --help                                  Print usage
+      --hook-spec                             Default hook spec file applied to all containers
   -H, --host list                             Daemon socket(s) to connect to (default [])
       --icc                                   Enable inter-container communication (default true)
       --init                                  Run an init in the container to forward signals and reap processes
diff --git a/components/cli/man/dockerd.8.md b/components/cli/man/dockerd.8.md
index 0224035970..d075080e78 100644
--- a/components/cli/man/dockerd.8.md
+++ b/components/cli/man/dockerd.8.md
@@ -38,6 +38,7 @@ dockerd - Enable daemon mode
 [**-G**|**--group**[=*docker*]]
 [**-H**|**--host**[=*[]*]]
 [**--help**]
+[**--hook-spec**[=*HOOKFILE*]]
 [**--icc**[=*true*]]
 [**--init**[=*false*]]
 [**--init-path**[=*""*]]
@@ -239,7 +240,29 @@ unix://[/path/to/socket] to use.
 
 **--help**
   Print usage statement
-
+  
+**--hook-spec**=""
+   Add default hooks for all containers.
+
+   With this flag, user can specify a file containing custom hook, an example hook file can be like this:
+
+ ```
+ {
+         "prestart": [
+             {
+                 "path": "/var/lib/docker/hooks/myhook",
+                 "args": ["myhook", "prestart"],
+                 "env": ["container=runc"]
+             }
+         ]
+ }
+ ```
+
+   Then all the containers will run the default hook `myhook` when start.
+
+   currently it supports three hooks: "prestart", "poststart", "poststop".
+   See OCI spec definition for more information about "hooks".
+   
 **--icc**=*true*|*false*
   Allow unrestricted inter\-container and Docker daemon host communication. If
   disabled, containers can still be linked together using the **--link** option
diff --git a/components/cli/vendor/github.com/docker/docker/api/types/types.go b/components/cli/vendor/github.com/docker/docker/api/types/types.go
index a8fae3ba32..2fb6c5478b 100644
--- a/components/cli/vendor/github.com/docker/docker/api/types/types.go
+++ b/components/cli/vendor/github.com/docker/docker/api/types/types.go
@@ -192,6 +192,7 @@ type Info struct {
 	ServerVersion      string
 	ClusterStore       string
 	ClusterAdvertise   string
+	HookSpec           string
 	Runtimes           map[string]Runtime
 	DefaultRuntime     string
 	Swarm              swarm.Info
diff --git a/components/engine/api/types/types.go b/components/engine/api/types/types.go
index 959e9eb447..820d513cbb 100644
--- a/components/engine/api/types/types.go
+++ b/components/engine/api/types/types.go
@@ -194,6 +194,7 @@ type Info struct {
 	ServerVersion      string
 	ClusterStore       string
 	ClusterAdvertise   string
+	HookSpec           string
 	Runtimes           map[string]Runtime
 	DefaultRuntime     string
 	Swarm              swarm.Info
diff --git a/components/engine/cmd/dockerd/config.go b/components/engine/cmd/dockerd/config.go
index 2c8ed8edb4..6f62b97da8 100644
--- a/components/engine/cmd/dockerd/config.go
+++ b/components/engine/cmd/dockerd/config.go
@@ -56,6 +56,7 @@ func installCommonConfigFlags(conf *config.Config, flags *pflag.FlagSet) {
 	flags.StringVar(&conf.ClusterAdvertise, "cluster-advertise", "", "Address or interface name to advertise")
 	flags.StringVar(&conf.ClusterStore, "cluster-store", "", "URL of the distributed storage backend")
 	flags.Var(opts.NewNamedMapOpts("cluster-store-opts", conf.ClusterOpts, nil), "cluster-store-opt", "Set cluster store options")
+	flags.StringVar(&conf.HookSpec, "hook-spec", "", "Default hook spec file applied to all containers")
 	flags.StringVar(&conf.CorsHeaders, "api-cors-header", "", "Set CORS headers in the Engine API")
 	flags.IntVar(&maxConcurrentDownloads, "max-concurrent-downloads", config.DefaultMaxConcurrentDownloads, "Set the max concurrent downloads for each pull")
 	flags.IntVar(&maxConcurrentUploads, "max-concurrent-uploads", config.DefaultMaxConcurrentUploads, "Set the max concurrent uploads for each push")
diff --git a/components/engine/daemon/config/config.go b/components/engine/daemon/config/config.go
index 8b2c844a57..2141ce8c54 100644
--- a/components/engine/daemon/config/config.go
+++ b/components/engine/daemon/config/config.go
@@ -124,6 +124,7 @@ type CommonConfig struct {
 	ExecOptions           []string                  `json:"exec-opts,omitempty"`
 	GraphDriver           string                    `json:"storage-driver,omitempty"`
 	GraphOptions          []string                  `json:"storage-opts,omitempty"`
+	HookSpec              string                    `json:"hook-spec,omitempty"`
 	Labels                []string                  `json:"labels,omitempty"`
 	Mtu                   int                       `json:"mtu,omitempty"`
 	NetworkDiagnosticPort int                       `json:"network-diagnostic-port,omitempty"`
diff --git a/components/engine/daemon/container.go b/components/engine/daemon/container.go
index 0864443513..8f9f6baf25 100644
--- a/components/engine/daemon/container.go
+++ b/components/engine/daemon/container.go
@@ -13,16 +13,19 @@ import (
 	containertypes "github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/strslice"
 	"github.com/docker/docker/container"
+	"github.com/docker/docker/daemon/config"
 	"github.com/docker/docker/daemon/network"
 	"github.com/docker/docker/errdefs"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/opts"
+	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/system"
 	"github.com/docker/docker/pkg/truncindex"
 	"github.com/docker/docker/runconfig"
 	volumemounts "github.com/docker/docker/volume/mounts"
 	"github.com/docker/go-connections/nat"
+	"github.com/opencontainers/runtime-spec/specs-go"
 	"github.com/opencontainers/selinux/go-selinux/label"
 	"github.com/pkg/errors"
 )
@@ -226,7 +229,7 @@ func (daemon *Daemon) setHostConfig(container *container.Container, hostConfig *
 	}
 
 	// register hooks to container
-	if err := daemon.registerHooks(container, hostConfig); err != nil {
+	if err := daemon.registerHooks(container, hostConfig.HookSpec); err != nil {
 		return err
 	}
 
@@ -235,41 +238,112 @@ func (daemon *Daemon) setHostConfig(container *container.Container, hostConfig *
 	return container.CheckpointTo(daemon.containersReplica)
 }
 
+func (daemon *Daemon) sanitizeHookSpec(spec string) (string, error) {
+	if spec != "" {
+		spec = filepath.Clean(spec)
+		if !filepath.IsAbs(spec) {
+			return "", fmt.Errorf("hook spec file must be an absolute path")
+		}
+		fi, err := os.Stat(spec)
+		if err != nil {
+			return "", fmt.Errorf("stat hook spec file failed: %v", err)
+		}
+		if !fi.Mode().IsRegular() {
+			return "", fmt.Errorf("hook spec file must be a regular text file")
+		}
+	}
+	return spec, nil
+}
+
+func (daemon *Daemon) initHooks(config *config.Config, rootIdentity idtools.Identity) error {
+	// create hook store dir
+	var err error
+	hookDir := filepath.Join(config.Root, "hooks")
+	if err = idtools.MkdirAllAndChown(hookDir, 0700, rootIdentity); err != nil && !os.IsExist(err) {
+		return err
+	}
+	daemon.hookStore = hookDir
+
+	if config.HookSpec, err = daemon.sanitizeHookSpec(config.HookSpec); err != nil {
+		return err
+	}
+
+	// setup default hooks
+	if err := daemon.registerDaemonHooks(config.HookSpec); err != nil {
+		return err
+	}
+
+	return nil
+}
 
-func (daemon *Daemon) registerHooks(container *container.Container, hostConfig *containertypes.HostConfig) error {
-	if hostConfig.HookSpec == "" {
+func (daemon *Daemon) registerDaemonHooks(hookspec string) error {
+	if hookspec == "" {
 		return nil
 	}
+
 	// the hook spec has already been sanitized, so no need for validation again
-	f, err := os.Open(hostConfig.HookSpec)
+	f, err := os.Open(hookspec)
 	if err != nil {
 		return fmt.Errorf("open hook spec file error: %v", err)
 	}
 	defer f.Close()
 
-	if err = json.NewDecoder(f).Decode(&container.Hooks); err != nil {
+	if err = json.NewDecoder(f).Decode(&daemon.Hooks); err != nil {
 		return fmt.Errorf("malformed hook spec, is your spec file in json format? error: %v", err)
 	}
 
 	// hook path must be absolute and must be subdir of XXX
-	if err = daemon.validateHook(container); err != nil {
+	if err = daemon.validateHook(&daemon.Hooks); err != nil {
 		return err
 	}
+
+	return nil
+}
+
+func (daemon *Daemon) registerHooks(container *container.Container, hookspec string) error {
+	container.Hooks.Prestart = append(container.Hooks.Prestart, daemon.Hooks.Prestart...)
+	container.Hooks.Poststart = append(container.Hooks.Poststart, daemon.Hooks.Poststart...)
+	container.Hooks.Poststop = append(container.Hooks.Poststop, daemon.Hooks.Poststop...)
+	if hookspec == "" {
+		return nil
+	}
+
+	// the hook spec has already been sanitized, so no need for validation again
+	f, err := os.Open(hookspec)
+	if err != nil {
+		return fmt.Errorf("open hook spec file error: %v", err)
+	}
+	defer f.Close()
+
+	var hooks specs.Hooks
+	if err = json.NewDecoder(f).Decode(&hooks); err != nil {
+		return fmt.Errorf("malformed hook spec, is your spec file in json format? error: %v", err)
+	}
+
+	container.Hooks.Prestart = append(container.Hooks.Prestart, hooks.Prestart...)
+	container.Hooks.Poststart = append(container.Hooks.Poststart, hooks.Poststart...)
+	container.Hooks.Poststop = append(container.Hooks.Poststop, hooks.Poststop...)
+
+	// hook path must be absolute and must be subdir of XXX
+	if err = daemon.validateHook(&container.Hooks); err != nil {
+		return err
+	}
+
 	return nil
 }
 
-func (daemon *Daemon) validateHook(container *container.Container) error {
-	for _, v := range container.Hooks.Prestart {
+func (daemon *Daemon) validateHook(hooks *specs.Hooks) error {
+	for _, v := range hooks.Prestart {
 		if err := daemon.validateHookPath(v.Path); err != nil {
 			return err
 		}
 	}
-	for _, v := range container.Hooks.Poststart {
+	for _, v := range hooks.Poststart {
 		if err := daemon.validateHookPath(v.Path); err != nil {
 			return err
 		}
 	}
-	for _, v := range container.Hooks.Poststop {
+	for _, v := range hooks.Poststop {
 		if err := daemon.validateHookPath(v.Path); err != nil {
 			return err
 		}
diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go
index d1f3131c4f..f7635f27cc 100644
--- a/components/engine/daemon/daemon.go
+++ b/components/engine/daemon/daemon.go
@@ -67,6 +67,7 @@ import (
 	"github.com/docker/libnetwork"
 	"github.com/docker/libnetwork/cluster"
 	nwconfig "github.com/docker/libnetwork/config"
+	"github.com/opencontainers/runtime-spec/specs-go"
 	"github.com/pkg/errors"
 )
 
@@ -109,6 +110,7 @@ type Daemon struct {
 	containerdCli         *containerd.Client
 	containerd            libcontainerd.Client
 	defaultIsolation      containertypes.Isolation // Default isolation mode on Windows
+	Hooks                 specs.Hooks
 	clusterProvider       cluster.Provider
 	cluster               Cluster
 	genericResources      []swarm.GenericResource
@@ -997,6 +999,11 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S
 		return nil, errors.New("Devices cgroup isn't mounted")
 	}
 
+	// setup hooks environment
+	if err := d.initHooks(config, rootIDs); err != nil {
+		return nil, fmt.Errorf("Failed to register default hooks: %v", err)
+	}
+
 	d.ID = trustKey.PublicKey().KeyID()
 	d.repository = daemonRepo
 	d.containers = container.NewMemoryStore()
diff --git a/components/engine/daemon/info.go b/components/engine/daemon/info.go
index 262719d9d1..523a396643 100644
--- a/components/engine/daemon/info.go
+++ b/components/engine/daemon/info.go
@@ -67,6 +67,7 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) {
 		HTTPSProxy:         maskCredentials(sockets.GetProxyEnv("https_proxy")),
 		NoProxy:            sockets.GetProxyEnv("no_proxy"),
 		LiveRestoreEnabled: daemon.configStore.LiveRestoreEnabled,
+		HookSpec:           daemon.configStore.HookSpec,
 		Isolation:          daemon.defaultIsolation,
 	}
 
-- 
2.17.1

