From 3781cde61ff10b1d9114ae5b4c5c1d1b2c20a1ee Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 18 Aug 2015 10:30:44 -0700 Subject: [PATCH] Add `STOPSIGNAL` instruction to dockerfiles. This way, images creators can set the exit signal their programs use. Signed-off-by: David Calavera --- builder/command/command.go | 2 ++ builder/dispatchers.go | 19 +++++++++++++++++++ builder/evaluator.go | 18 ++++++++++-------- builder/parser/parser.go | 1 + contrib/syntax/kate/Dockerfile.xml | 1 + .../Syntaxes/Dockerfile.tmLanguage | 2 +- contrib/syntax/vim/syntax/dockerfile.vim | 2 +- daemon/container.go | 2 +- daemon/container_unit_test.go | 7 ++++++- daemon/daemon.go | 12 +++++++----- docs/reference/api/docker_remote_api_v1.21.md | 5 ++++- docs/reference/builder.md | 9 +++++++++ docs/reference/commandline/create.md | 1 + docs/reference/commandline/run.md | 7 +++++++ integration-cli/docker_cli_build_test.go | 15 +++++++++++++++ integration-cli/docker_cli_create_test.go | 12 ++++++++++++ man/docker-inspect.1.md | 2 +- 17 files changed, 98 insertions(+), 19 deletions(-) diff --git a/builder/command/command.go b/builder/command/command.go index 93b1c8bd81..968dba1f8d 100644 --- a/builder/command/command.go +++ b/builder/command/command.go @@ -17,6 +17,7 @@ const ( Expose = "expose" Volume = "volume" User = "user" + StopSignal = "stopsignal" ) // Commands is list of all Dockerfile commands @@ -35,4 +36,5 @@ var Commands = map[string]struct{}{ Expose: {}, Volume: {}, User: {}, + StopSignal: {}, } diff --git a/builder/dispatchers.go b/builder/dispatchers.go index 78e3f87229..b00001260f 100644 --- a/builder/dispatchers.go +++ b/builder/dispatchers.go @@ -20,6 +20,7 @@ import ( "github.com/Sirupsen/logrus" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/nat" + "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/stringutils" "github.com/docker/docker/pkg/system" "github.com/docker/docker/runconfig" @@ -534,3 +535,21 @@ func volume(b *builder, args []string, attributes map[string]bool, original stri } return nil } + +// STOPSIGNAL signal +// +// Set the signal that will be used to kill the container. +func stopSignal(b *builder, args []string, attributes map[string]bool, original string) error { + if len(args) != 1 { + return fmt.Errorf("STOPSIGNAL requires exactly one argument") + } + + sig := args[0] + _, err := signal.ParseSignal(sig) + if err != nil { + return err + } + + b.Config.StopSignal = sig + return b.commit("", b.Config.Cmd, fmt.Sprintf("STOPSIGNAL %v", args)) +} diff --git a/builder/evaluator.go b/builder/evaluator.go index 2b0c038a01..18d2c36a63 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -45,14 +45,15 @@ import ( // Environment variable interpolation will happen on these statements only. var replaceEnvAllowed = map[string]struct{}{ - command.Env: {}, - command.Label: {}, - command.Add: {}, - command.Copy: {}, - command.Workdir: {}, - command.Expose: {}, - command.Volume: {}, - command.User: {}, + command.Env: {}, + command.Label: {}, + command.Add: {}, + command.Copy: {}, + command.Workdir: {}, + command.Expose: {}, + command.Volume: {}, + command.User: {}, + command.StopSignal: {}, } var evaluateTable map[string]func(*builder, []string, map[string]bool, string) error @@ -73,6 +74,7 @@ func init() { command.Expose: expose, command.Volume: volume, command.User: user, + command.StopSignal: stopSignal, } } diff --git a/builder/parser/parser.go b/builder/parser/parser.go index c72d9df4d8..37548401d3 100644 --- a/builder/parser/parser.go +++ b/builder/parser/parser.go @@ -61,6 +61,7 @@ func init() { command.Entrypoint: parseMaybeJSON, command.Expose: parseStringsWhitespaceDelimited, command.Volume: parseMaybeJSONToList, + command.StopSignal: parseString, } } diff --git a/contrib/syntax/kate/Dockerfile.xml b/contrib/syntax/kate/Dockerfile.xml index 4fdef2393b..05692504e7 100644 --- a/contrib/syntax/kate/Dockerfile.xml +++ b/contrib/syntax/kate/Dockerfile.xml @@ -23,6 +23,7 @@ WORKDIR USER LABEL + STOPSIGNAL diff --git a/contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage b/contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage index 61e45ccbf6..0ca231c319 100644 --- a/contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage +++ b/contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage @@ -25,7 +25,7 @@ match - ^\s*(?:(ONBUILD)\s+)?(FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|VOLUME|USER|WORKDIR|COPY|LABEL)\s + ^\s*(?:(ONBUILD)\s+)?(FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|VOLUME|USER|WORKDIR|COPY|LABEL|STOPSIGNAL)\s captures diff --git a/contrib/syntax/vim/syntax/dockerfile.vim b/contrib/syntax/vim/syntax/dockerfile.vim index 220a4db3a2..3cb1ecfddf 100644 --- a/contrib/syntax/vim/syntax/dockerfile.vim +++ b/contrib/syntax/vim/syntax/dockerfile.vim @@ -11,7 +11,7 @@ let b:current_syntax = "dockerfile" syntax case ignore -syntax match dockerfileKeyword /\v^\s*(ONBUILD\s+)?(ADD|CMD|ENTRYPOINT|ENV|EXPOSE|FROM|MAINTAINER|RUN|USER|LABEL|VOLUME|WORKDIR|COPY)\s/ +syntax match dockerfileKeyword /\v^\s*(ONBUILD\s+)?(ADD|CMD|ENTRYPOINT|ENV|EXPOSE|FROM|MAINTAINER|RUN|USER|LABEL|VOLUME|WORKDIR|COPY|STOPSIGNAL)\s/ highlight link dockerfileKeyword Keyword syntax region dockerfileString start=/\v"/ skip=/\v\\./ end=/\v"/ diff --git a/daemon/container.go b/daemon/container.go index 52465be590..c73e7aadb7 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -524,7 +524,7 @@ func (container *Container) Stop(seconds int) error { } } - container.LogEvent("stop") + container.logEvent("stop") return nil } diff --git a/daemon/container_unit_test.go b/daemon/container_unit_test.go index ff45f71276..71d37cf436 100644 --- a/daemon/container_unit_test.go +++ b/daemon/container_unit_test.go @@ -1,6 +1,11 @@ package daemon -import "testing" +import ( + "testing" + + "github.com/docker/docker/pkg/signal" + "github.com/docker/docker/runconfig" +) func TestGetFullName(t *testing.T) { name, err := GetFullContainerName("testing") diff --git a/daemon/daemon.go b/daemon/daemon.go index bf4727cba1..3dcf93b629 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1076,6 +1076,13 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *runconfig.HostConfig, return nil, fmt.Errorf("The working directory '%s' is invalid. It needs to be an absolute path.", config.WorkingDir) } } + + if len(config.StopSignal) > 0 { + _, err := signal.ParseSignal(config.StopSignal) + if err != nil { + return nil, err + } + } } if hostConfig == nil { @@ -1095,11 +1102,6 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *runconfig.HostConfig, } } - _, err := signal.ParseSignal(config.StopSignal) - if err != nil { - return nil, err - } - // Now do platform-specific verification return verifyPlatformContainerSettings(daemon, hostConfig, config) } diff --git a/docs/reference/api/docker_remote_api_v1.21.md b/docs/reference/api/docker_remote_api_v1.21.md index 6a9e37faf1..4fdb330010 100644 --- a/docs/reference/api/docker_remote_api_v1.21.md +++ b/docs/reference/api/docker_remote_api_v1.21.md @@ -166,6 +166,7 @@ Create a container "ExposedPorts": { "22/tcp": {} }, + "StopSignal": "SIGTERM", "HostConfig": { "Binds": ["/tmp:/tmp"], "Links": ["redis3:redis"], @@ -250,6 +251,7 @@ Json Parameters: container - **ExposedPorts** - An object mapping ports to an empty object in the form of: `"ExposedPorts": { "/: {}" }` +- **StopSignal** - Signal to stop a container as a string or unsigned integer. `SIGTERM` by default. - **HostConfig** - **Binds** – A list of volume bindings for this container. Each volume binding is a string in one of these forms: + `container_path` to create a new volume for the container @@ -367,7 +369,8 @@ Return low-level information on the container `id` "Tty": false, "User": "", "Volumes": null, - "WorkingDir": "" + "WorkingDir": "", + "StopSignal": "SIGTERM" }, "Created": "2015-01-06T15:47:31.485331387Z", "Driver": "devicemapper", diff --git a/docs/reference/builder.md b/docs/reference/builder.md index 3a73b87d29..195139758f 100644 --- a/docs/reference/builder.md +++ b/docs/reference/builder.md @@ -158,6 +158,7 @@ the `Dockerfile`: * `USER` * `WORKDIR` * `VOLUME` +* `STOPSIGNAL` as well as: @@ -1012,6 +1013,14 @@ For example you might add something like this: > **Warning**: The `ONBUILD` instruction may not trigger `FROM` or `MAINTAINER` instructions. +## STOPSIGNAL + + STOPSIGNAL signal + +The `STOPSIGNAL` instruction sets the system call signal that will be sent to the container to exit. +This signal can be a valid unsigned number that matches a position in the kernel's syscall table, for instance 9, +or a signal name in the format SIGNAME, for instance SIGKILL. + ## Dockerfile examples # Nginx diff --git a/docs/reference/commandline/create.md b/docs/reference/commandline/create.md index 74d5185a34..7c64d7297d 100644 --- a/docs/reference/commandline/create.md +++ b/docs/reference/commandline/create.md @@ -61,6 +61,7 @@ Creates a new container. --read-only=false Mount the container's root filesystem as read only --restart="no" Restart policy (no, on-failure[:max-retry], always, unless-stopped) --security-opt=[] Security options + --stop-signal="SIGTERM" Signal to stop a container -t, --tty=false Allocate a pseudo-TTY --disable-content-trust=true Skip image verification -u, --user="" Username or UID diff --git a/docs/reference/commandline/run.md b/docs/reference/commandline/run.md index 6ee97906d9..c7726497cc 100644 --- a/docs/reference/commandline/run.md +++ b/docs/reference/commandline/run.md @@ -62,6 +62,7 @@ weight=1 --restart="no" Restart policy (no, on-failure[:max-retry], always, unless-stopped) --rm=false Automatically remove the container when it exits --security-opt=[] Security Options + --stop-signal="SIGTERM" Signal to stop a container --sig-proxy=true Proxy received signals to the process -t, --tty=false Allocate a pseudo-TTY -u, --user="" Username or UID (format: [:]) @@ -531,3 +532,9 @@ containers with `daemon` user: The 4th container fails and reports "[8] System error: resource temporarily unavailable" error. This fails because the caller set `nproc=3` resulting in the first three containers using up the three processes quota set for the `daemon` user. + +### Stopping a container with a specific signal + +The `--stop-signal` flag sets the system call signal that will be sent to the container to exit. +This signal can be a valid unsigned number that matches a position in the kernel's syscall table, for instance 9, +or a signal name in the format SIGNAME, for instance SIGKILL. diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 1e5ef40148..308e9407a7 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -5660,3 +5660,18 @@ func (s *DockerSuite) TestBuildNullStringInAddCopyVolume(c *check.C) { _, err = buildImageFromContext(name, ctx, true) c.Assert(err, check.IsNil) } + +func (s *DockerSuite) TestBuildStopSignal(c *check.C) { + name := "test_build_stop_signal" + _, err := buildImage(name, + `FROM busybox + STOPSIGNAL SIGKILL`, + true) + c.Assert(err, check.IsNil) + res, err := inspectFieldJSON(name, "Config.StopSignal") + c.Assert(err, check.IsNil) + + if res != `"SIGKILL"` { + c.Fatalf("Signal %s, expected SIGKILL", res) + } +} diff --git a/integration-cli/docker_cli_create_test.go b/integration-cli/docker_cli_create_test.go index f854d0d6fe..ac36320848 100644 --- a/integration-cli/docker_cli_create_test.go +++ b/integration-cli/docker_cli_create_test.go @@ -458,3 +458,15 @@ func (s *DockerTrustSuite) TestTrustedCreateFromBadTrustServer(c *check.C) { c.Fatalf("Missing expected output on trusted push:\n%s", out) } } + +func (s *DockerSuite) TestCreateStopSignal(c *check.C) { + name := "test_create_stop_signal" + dockerCmd(c, "create", "--name", name, "--stop-signal", "9", "busybox") + + res, err := inspectFieldJSON(name, "Config.StopSignal") + c.Assert(err, check.IsNil) + + if res != `"9"` { + c.Fatalf("Expected 9, got %s", res) + } +} diff --git a/man/docker-inspect.1.md b/man/docker-inspect.1.md index e289899bbb..f7faf4a7b2 100644 --- a/man/docker-inspect.1.md +++ b/man/docker-inspect.1.md @@ -181,7 +181,7 @@ To get information on a container use its ID or instance name: "MemorySwap": 0, "CpuShares": 0, "Cpuset": "", - "StopSignal": 15, + "StopSignal": "SIGTERM" } } ]