Browse code

Add `STOPSIGNAL` instruction to dockerfiles.

This way, images creators can set the exit signal their programs use.

Signed-off-by: David Calavera <david.calavera@gmail.com>

David Calavera authored on 2015/08/19 02:30:44
Showing 17 changed files
... ...
@@ -17,6 +17,7 @@ const (
17 17
 	Expose     = "expose"
18 18
 	Volume     = "volume"
19 19
 	User       = "user"
20
+	StopSignal = "stopsignal"
20 21
 )
21 22
 
22 23
 // Commands is list of all Dockerfile commands
... ...
@@ -35,4 +36,5 @@ var Commands = map[string]struct{}{
35 35
 	Expose:     {},
36 36
 	Volume:     {},
37 37
 	User:       {},
38
+	StopSignal: {},
38 39
 }
... ...
@@ -20,6 +20,7 @@ import (
20 20
 	"github.com/Sirupsen/logrus"
21 21
 	flag "github.com/docker/docker/pkg/mflag"
22 22
 	"github.com/docker/docker/pkg/nat"
23
+	"github.com/docker/docker/pkg/signal"
23 24
 	"github.com/docker/docker/pkg/stringutils"
24 25
 	"github.com/docker/docker/pkg/system"
25 26
 	"github.com/docker/docker/runconfig"
... ...
@@ -534,3 +535,21 @@ func volume(b *builder, args []string, attributes map[string]bool, original stri
534 534
 	}
535 535
 	return nil
536 536
 }
537
+
538
+// STOPSIGNAL signal
539
+//
540
+// Set the signal that will be used to kill the container.
541
+func stopSignal(b *builder, args []string, attributes map[string]bool, original string) error {
542
+	if len(args) != 1 {
543
+		return fmt.Errorf("STOPSIGNAL requires exactly one argument")
544
+	}
545
+
546
+	sig := args[0]
547
+	_, err := signal.ParseSignal(sig)
548
+	if err != nil {
549
+		return err
550
+	}
551
+
552
+	b.Config.StopSignal = sig
553
+	return b.commit("", b.Config.Cmd, fmt.Sprintf("STOPSIGNAL %v", args))
554
+}
... ...
@@ -45,14 +45,15 @@ import (
45 45
 
46 46
 // Environment variable interpolation will happen on these statements only.
47 47
 var replaceEnvAllowed = map[string]struct{}{
48
-	command.Env:     {},
49
-	command.Label:   {},
50
-	command.Add:     {},
51
-	command.Copy:    {},
52
-	command.Workdir: {},
53
-	command.Expose:  {},
54
-	command.Volume:  {},
55
-	command.User:    {},
48
+	command.Env:        {},
49
+	command.Label:      {},
50
+	command.Add:        {},
51
+	command.Copy:       {},
52
+	command.Workdir:    {},
53
+	command.Expose:     {},
54
+	command.Volume:     {},
55
+	command.User:       {},
56
+	command.StopSignal: {},
56 57
 }
57 58
 
58 59
 var evaluateTable map[string]func(*builder, []string, map[string]bool, string) error
... ...
@@ -73,6 +74,7 @@ func init() {
73 73
 		command.Expose:     expose,
74 74
 		command.Volume:     volume,
75 75
 		command.User:       user,
76
+		command.StopSignal: stopSignal,
76 77
 	}
77 78
 }
78 79
 
... ...
@@ -61,6 +61,7 @@ func init() {
61 61
 		command.Entrypoint: parseMaybeJSON,
62 62
 		command.Expose:     parseStringsWhitespaceDelimited,
63 63
 		command.Volume:     parseMaybeJSONToList,
64
+		command.StopSignal: parseString,
64 65
 	}
65 66
 }
66 67
 
... ...
@@ -23,6 +23,7 @@
23 23
       <item> WORKDIR </item>
24 24
       <item> USER </item>
25 25
       <item> LABEL </item>
26
+      <item> STOPSIGNAL </item>
26 27
     </list>
27 28
 
28 29
     <contexts>
... ...
@@ -25,7 +25,7 @@
25 25
 				</dict>
26 26
 			</dict>
27 27
 			<key>match</key>
28
-			<string>^\s*(?:(ONBUILD)\s+)?(FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|VOLUME|USER|WORKDIR|COPY|LABEL)\s</string>
28
+			<string>^\s*(?:(ONBUILD)\s+)?(FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|VOLUME|USER|WORKDIR|COPY|LABEL|STOPSIGNAL)\s</string>
29 29
 		</dict>
30 30
 		<dict>
31 31
 			<key>captures</key>
... ...
@@ -11,7 +11,7 @@ let b:current_syntax = "dockerfile"
11 11
 
12 12
 syntax case ignore
13 13
 
14
-syntax match dockerfileKeyword /\v^\s*(ONBUILD\s+)?(ADD|CMD|ENTRYPOINT|ENV|EXPOSE|FROM|MAINTAINER|RUN|USER|LABEL|VOLUME|WORKDIR|COPY)\s/
14
+syntax match dockerfileKeyword /\v^\s*(ONBUILD\s+)?(ADD|CMD|ENTRYPOINT|ENV|EXPOSE|FROM|MAINTAINER|RUN|USER|LABEL|VOLUME|WORKDIR|COPY|STOPSIGNAL)\s/
15 15
 highlight link dockerfileKeyword Keyword
16 16
 
17 17
 syntax region dockerfileString start=/\v"/ skip=/\v\\./ end=/\v"/
... ...
@@ -524,7 +524,7 @@ func (container *Container) Stop(seconds int) error {
524 524
 		}
525 525
 	}
526 526
 
527
-	container.LogEvent("stop")
527
+	container.logEvent("stop")
528 528
 	return nil
529 529
 }
530 530
 
... ...
@@ -1,6 +1,11 @@
1 1
 package daemon
2 2
 
3
-import "testing"
3
+import (
4
+	"testing"
5
+
6
+	"github.com/docker/docker/pkg/signal"
7
+	"github.com/docker/docker/runconfig"
8
+)
4 9
 
5 10
 func TestGetFullName(t *testing.T) {
6 11
 	name, err := GetFullContainerName("testing")
... ...
@@ -1076,6 +1076,13 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *runconfig.HostConfig,
1076 1076
 				return nil, fmt.Errorf("The working directory '%s' is invalid. It needs to be an absolute path.", config.WorkingDir)
1077 1077
 			}
1078 1078
 		}
1079
+
1080
+		if len(config.StopSignal) > 0 {
1081
+			_, err := signal.ParseSignal(config.StopSignal)
1082
+			if err != nil {
1083
+				return nil, err
1084
+			}
1085
+		}
1079 1086
 	}
1080 1087
 
1081 1088
 	if hostConfig == nil {
... ...
@@ -1095,11 +1102,6 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *runconfig.HostConfig,
1095 1095
 		}
1096 1096
 	}
1097 1097
 
1098
-	_, err := signal.ParseSignal(config.StopSignal)
1099
-	if err != nil {
1100
-		return nil, err
1101
-	}
1102
-
1103 1098
 	// Now do platform-specific verification
1104 1099
 	return verifyPlatformContainerSettings(daemon, hostConfig, config)
1105 1100
 }
... ...
@@ -166,6 +166,7 @@ Create a container
166 166
            "ExposedPorts": {
167 167
                    "22/tcp": {}
168 168
            },
169
+           "StopSignal": "SIGTERM",
169 170
            "HostConfig": {
170 171
              "Binds": ["/tmp:/tmp"],
171 172
              "Links": ["redis3:redis"],
... ...
@@ -250,6 +251,7 @@ Json Parameters:
250 250
       container
251 251
 -   **ExposedPorts** - An object mapping ports to an empty object in the form of:
252 252
       `"ExposedPorts": { "<port>/<tcp|udp>: {}" }`
253
+-   **StopSignal** - Signal to stop a container as a string or unsigned integer. `SIGTERM` by default.
253 254
 -   **HostConfig**
254 255
     -   **Binds** – A list of volume bindings for this container. Each volume binding is a string in one of these forms:
255 256
            + `container_path` to create a new volume for the container
... ...
@@ -367,7 +369,8 @@ Return low-level information on the container `id`
367 367
 			"Tty": false,
368 368
 			"User": "",
369 369
 			"Volumes": null,
370
-			"WorkingDir": ""
370
+			"WorkingDir": "",
371
+			"StopSignal": "SIGTERM"
371 372
 		},
372 373
 		"Created": "2015-01-06T15:47:31.485331387Z",
373 374
 		"Driver": "devicemapper",
... ...
@@ -158,6 +158,7 @@ the `Dockerfile`:
158 158
 * `USER`
159 159
 * `WORKDIR`
160 160
 * `VOLUME`
161
+* `STOPSIGNAL`
161 162
 
162 163
 as well as:
163 164
 
... ...
@@ -1012,6 +1013,14 @@ For example you might add something like this:
1012 1012
 
1013 1013
 > **Warning**: The `ONBUILD` instruction may not trigger `FROM` or `MAINTAINER` instructions.
1014 1014
 
1015
+## STOPSIGNAL
1016
+
1017
+	STOPSIGNAL signal
1018
+
1019
+The `STOPSIGNAL` instruction sets the system call signal that will be sent to the container to exit.
1020
+This signal can be a valid unsigned number that matches a position in the kernel's syscall table, for instance 9,
1021
+or a signal name in the format SIGNAME, for instance SIGKILL.
1022
+
1015 1023
 ## Dockerfile examples
1016 1024
 
1017 1025
     # Nginx
... ...
@@ -61,6 +61,7 @@ Creates a new container.
61 61
       --read-only=false             Mount the container's root filesystem as read only
62 62
       --restart="no"                Restart policy (no, on-failure[:max-retry], always, unless-stopped)
63 63
       --security-opt=[]             Security options
64
+      --stop-signal="SIGTERM"       Signal to stop a container
64 65
       -t, --tty=false               Allocate a pseudo-TTY
65 66
       --disable-content-trust=true  Skip image verification
66 67
       -u, --user=""                 Username or UID
... ...
@@ -62,6 +62,7 @@ weight=1
62 62
       --restart="no"                Restart policy (no, on-failure[:max-retry], always, unless-stopped)
63 63
       --rm=false                    Automatically remove the container when it exits
64 64
       --security-opt=[]             Security Options
65
+      --stop-signal="SIGTERM"       Signal to stop a container
65 66
       --sig-proxy=true              Proxy received signals to the process
66 67
       -t, --tty=false               Allocate a pseudo-TTY
67 68
       -u, --user=""                 Username or UID (format: <name|uid>[:<group|gid>])
... ...
@@ -531,3 +532,9 @@ containers with `daemon` user:
531 531
 The 4th container fails and reports "[8] System error: resource temporarily unavailable" error. 
532 532
 This fails because the caller set `nproc=3` resulting in the first three containers using up 
533 533
 the three processes quota set for the `daemon` user.
534
+
535
+### Stopping a container with a specific signal
536
+
537
+The `--stop-signal` flag sets the system call signal that will be sent to the container to exit.
538
+This signal can be a valid unsigned number that matches a position in the kernel's syscall table, for instance 9,
539
+or a signal name in the format SIGNAME, for instance SIGKILL.
... ...
@@ -5660,3 +5660,18 @@ func (s *DockerSuite) TestBuildNullStringInAddCopyVolume(c *check.C) {
5660 5660
 	_, err = buildImageFromContext(name, ctx, true)
5661 5661
 	c.Assert(err, check.IsNil)
5662 5662
 }
5663
+
5664
+func (s *DockerSuite) TestBuildStopSignal(c *check.C) {
5665
+	name := "test_build_stop_signal"
5666
+	_, err := buildImage(name,
5667
+		`FROM busybox
5668
+		 STOPSIGNAL SIGKILL`,
5669
+		true)
5670
+	c.Assert(err, check.IsNil)
5671
+	res, err := inspectFieldJSON(name, "Config.StopSignal")
5672
+	c.Assert(err, check.IsNil)
5673
+
5674
+	if res != `"SIGKILL"` {
5675
+		c.Fatalf("Signal %s, expected SIGKILL", res)
5676
+	}
5677
+}
... ...
@@ -458,3 +458,15 @@ func (s *DockerTrustSuite) TestTrustedCreateFromBadTrustServer(c *check.C) {
458 458
 		c.Fatalf("Missing expected output on trusted push:\n%s", out)
459 459
 	}
460 460
 }
461
+
462
+func (s *DockerSuite) TestCreateStopSignal(c *check.C) {
463
+	name := "test_create_stop_signal"
464
+	dockerCmd(c, "create", "--name", name, "--stop-signal", "9", "busybox")
465
+
466
+	res, err := inspectFieldJSON(name, "Config.StopSignal")
467
+	c.Assert(err, check.IsNil)
468
+
469
+	if res != `"9"` {
470
+		c.Fatalf("Expected 9, got %s", res)
471
+	}
472
+}
... ...
@@ -181,7 +181,7 @@ To get information on a container use its ID or instance name:
181 181
         "MemorySwap": 0,
182 182
         "CpuShares": 0,
183 183
         "Cpuset": "",
184
-        "StopSignal": 15,
184
+        "StopSignal": "SIGTERM"
185 185
     }
186 186
     }
187 187
     ]