From e96549c95d44120d885bcacfeacd3d6a56fce579 Mon Sep 17 00:00:00 2001 From: Andrew Pashkin <andrew.pashkin@gmx.co.uk> Date: Fri, 2 Oct 2015 00:44:52 +0300 Subject: [PATCH 1/2] Harden matching running containers by "command" in the Docker module Before this patch: - Command was matched if 'Command' field of docker-py representation of Docker container ends with 'command' passed to Ansible docker module by user. - That can give false positives and false negatives. - For example: a) If 'command' was set up with more than one spaces, like 'command=sleep 123', it would be never matched again with a container(s) launched by this task. Because after launching, command would be normalized and appear, in docker-py API call, just as 'sleep 123' - with one space. This is false negative case. b) If 'entrypoint + command = command', for example 'sleep + 123 = sleep 123', module would give false positive match. This patch fixes it, by making matching more explicit - against 'Config'->Cmd' field of 'docker inspect' output, provided by docker-py API and with proper normalization of user input by splitting it to tokens with 'shlex.split()'. --- cloud/docker/docker.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/cloud/docker/docker.py b/cloud/docker/docker.py index 0ab564208ba..cefae3db3df 100644 --- a/cloud/docker/docker.py +++ b/cloud/docker/docker.py @@ -1314,8 +1314,8 @@ class DockerManager(object): """ command = self.module.params.get('command') - if command: - command = command.strip() + if command is not None: + command = shlex.split(command) name = self.module.params.get('name') if name and not name.startswith('/'): name = '/' + name @@ -1342,13 +1342,10 @@ class DockerManager(object): details = _docker_id_quirk(details) running_image = normalize_image(details['Config']['Image']) - running_command = container['Command'].strip() image_matches = running_image in repo_tags - # if a container has an entrypoint, `command` will actually equal - # '{} {}'.format(entrypoint, command) - command_matches = (not command or running_command.endswith(command)) + command_matches = command == details['Config']['Cmd'] matches = image_matches and command_matches From cee7e928fc2cb911480aae0c3ed53501034f4611 Mon Sep 17 00:00:00 2001 From: Andrew Pashkin <andrew.pashkin@gmx.co.uk> Date: Fri, 2 Oct 2015 01:09:08 +0300 Subject: [PATCH 2/2] Add 'entrypoint' parameter to Docker module --- cloud/docker/docker.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/cloud/docker/docker.py b/cloud/docker/docker.py index cefae3db3df..3bc42629709 100644 --- a/cloud/docker/docker.py +++ b/cloud/docker/docker.py @@ -46,6 +46,14 @@ options: default: missing choices: [ "missing", "always" ] version_added: "1.9" + entrypoint: + description: + - Corresponds to ``--entrypoint`` option of ``docker run`` command and + ``ENTRYPOINT`` directive of Dockerfile. + Used to match and launch containers. + default: null + required: false + version_added: "2.0" command: description: - Command used to match and launch containers. @@ -1043,6 +1051,21 @@ class DockerManager(object): differing.append(container) continue + # ENTRYPOINT + + expected_entrypoint = self.module.params.get('entrypoint') + if expected_entrypoint: + expected_entrypoint = shlex.split(expected_entrypoint) + actual_entrypoint = container["Config"]["Entrypoint"] + + if actual_entrypoint != expected_entrypoint: + self.reload_reasons.append( + 'entrypoint ({0} => {1})' + .format(actual_entrypoint, expected_entrypoint) + ) + differing.append(container) + continue + # COMMAND expected_command = self.module.params.get('command') @@ -1313,6 +1336,9 @@ class DockerManager(object): Return any matching containers that are already present. """ + entrypoint = self.module.params.get('entrypoint') + if entrypoint is not None: + entrypoint = shlex.split(entrypoint) command = self.module.params.get('command') if command is not None: command = shlex.split(command) @@ -1346,8 +1372,12 @@ class DockerManager(object): image_matches = running_image in repo_tags command_matches = command == details['Config']['Cmd'] + entrypoint_matches = ( + entrypoint == details['Config']['Entrypoint'] + ) - matches = image_matches and command_matches + matches = (image_matches and command_matches and + entrypoint_matches) if matches: if not details: @@ -1407,6 +1437,7 @@ class DockerManager(object): api_version = self.client.version()['ApiVersion'] params = {'image': self.module.params.get('image'), + 'entrypoint': self.module.params.get('entrypoint'), 'command': self.module.params.get('command'), 'ports': self.exposed_ports, 'volumes': self.volumes, @@ -1619,6 +1650,7 @@ def main(): count = dict(default=1), image = dict(required=True), pull = dict(required=False, default='missing', choices=['missing', 'always']), + entrypoint = dict(required=False, default=None, type='str'), command = dict(required=False, default=None), expose = dict(required=False, default=None, type='list'), ports = dict(required=False, default=None, type='list'),