From 33241a84c1dbc73d284197ed89d4aba7de70e864 Mon Sep 17 00:00:00 2001 From: Steve Baker Date: Wed, 12 Apr 2017 10:09:06 +1200 Subject: [PATCH] docker-cmd hook switch to the paunch library paunch is a library and utility which is now in OpenStack under the TripleO umbrella. It contains the logic currently in the docker-cmd hook exposed as a python library and command utility. This change switches the docker-cmd hook to paunch. Asserting --label arguments has been split out from the other docker run arguments so that paunch can add new --label arguments in future releases without breaking these tests. paunch-1.1.0 has just been release which contains new idempotency behaviour, so the tests have been modified to work with the new docker command behaviour that idempotency requires. Change-Id: I884c38ade06ab0e01432837c43f29b123e65fa3c --- heat-config-docker-cmd/README.rst | 3 +- .../install.d/hook-docker-cmd.py | 176 +----- .../configure.d/50-heat-config-docker-cmd | 101 +--- .../docker-cmd-paunch-0a674fc1b84b2dfc.yaml | 5 + test-requirements.txt | 1 + tests/test_hook_docker_cmd.py | 503 ++++++++++++------ 6 files changed, 356 insertions(+), 433 deletions(-) create mode 100644 releasenotes/notes/docker-cmd-paunch-0a674fc1b84b2dfc.yaml diff --git a/heat-config-docker-cmd/README.rst b/heat-config-docker-cmd/README.rst index 34876af..45e549a 100644 --- a/heat-config-docker-cmd/README.rst +++ b/heat-config-docker-cmd/README.rst @@ -1,4 +1,5 @@ -A hook which uses the `docker` command to deploy containers. +A hook which uses the `docker` command via +`paunch `_ to deploy containers. The hook currently supports specifying containers in the `docker-compose v1 format `_. The diff --git a/heat-config-docker-cmd/install.d/hook-docker-cmd.py b/heat-config-docker-cmd/install.d/hook-docker-cmd.py index 6155434..bd4f73d 100755 --- a/heat-config-docker-cmd/install.d/hook-docker-cmd.py +++ b/heat-config-docker-cmd/install.d/hook-docker-cmd.py @@ -12,15 +12,14 @@ # License for the specific language governing permissions and limitations # under the License. +import collections import json import logging import os -import random -import string -import subprocess import sys -import yaml +import paunch +import yaml DOCKER_CMD = os.environ.get('HEAT_DOCKER_CMD', 'docker') @@ -36,135 +35,6 @@ def build_response(deploy_stdout, deploy_stderr, deploy_status_code): } -def docker_run_args(cmd, container, config): - cconfig = config[container] - if cconfig.get('detach', True): - cmd.append('--detach=true') - if 'env_file' in cconfig: - if isinstance(cconfig['env_file'], list): - for f in cconfig.get('env_file', []): - if f: - cmd.append('--env-file=%s' % f) - else: - cmd.append('--env-file=%s' % cconfig['env_file']) - for v in cconfig.get('environment', []): - if v: - cmd.append('--env=%s' % v) - if 'net' in cconfig: - cmd.append('--net=%s' % cconfig['net']) - if 'pid' in cconfig: - cmd.append('--pid=%s' % cconfig['pid']) - if 'privileged' in cconfig: - cmd.append('--privileged=%s' % str(cconfig['privileged']).lower()) - if 'restart' in cconfig: - cmd.append('--restart=%s' % cconfig['restart']) - if 'user' in cconfig: - cmd.append('--user=%s' % cconfig['user']) - for v in cconfig.get('volumes', []): - if v: - cmd.append('--volume=%s' % v) - for v in cconfig.get('volumes_from', []): - if v: - cmd.append('--volumes_from=%s' % v) - - cmd.append(cconfig.get('image', '')) - cmd.extend(command_argument(cmd, cconfig.get('command'))) - - -def docker_exec_args(cmd, container, config, cid): - cconfig = config[container] - if 'privileged' in cconfig: - cmd.append('--privileged=%s' % str(cconfig['privileged']).lower()) - if 'user' in cconfig: - cmd.append('--user=%s' % cconfig['user']) - command = command_argument(cmd, cconfig.get('command')) - # for exec, the first argument is the container name, - # make sure the correct one is used - command[0] = discover_container_name(command[0], cid) - cmd.extend(command) - - -def command_argument(cmd, command): - if not command: - return [] - if not isinstance(command, list): - return command.split() - return command - - -def execute(cmd): - log.debug(' '.join(cmd)) - subproc = subprocess.Popen(cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - cmd_stdout, cmd_stderr = subproc.communicate() - log.debug(cmd_stdout) - log.debug(cmd_stderr) - return cmd_stdout, cmd_stderr, subproc.returncode - - -def label_arguments(cmd, container, cid, iv): - cmd.extend([ - '--label', - 'deploy_stack_id=%s' % iv.get('deploy_stack_id'), - '--label', - 'deploy_resource_name=%s' % iv.get('deploy_resource_name'), - '--label', - 'config_id=%s' % cid, - '--label', - 'container_name=%s' % container, - '--label', - 'managed_by=docker-cmd' - ]) - - -def inspect(container, format=None): - cmd = [DOCKER_CMD, 'inspect'] - if format: - cmd.append('--format') - cmd.append(format) - cmd.append(container) - (cmd_stdout, cmd_stderr, returncode) = execute(cmd) - if returncode != 0: - return - try: - if format: - return cmd_stdout - else: - return json.loads(cmd_stdout)[0] - except Exception as e: - log.error('Problem parsing docker inspect: %s' % e) - - -def unique_container_name(container): - container_name = container - while inspect(container_name, format='exists'): - suffix = ''.join(random.choice( - string.ascii_lowercase + string.digits) for i in range(8)) - container_name = '%s-%s' % (container, suffix) - return container_name - - -def discover_container_name(container, cid): - cmd = [ - DOCKER_CMD, - 'ps', - '-a', - '--filter', - 'label=container_name=%s' % container, - '--filter', - 'label=config_id=%s' % cid, - '--format', - '{{.Names}}' - ] - (cmd_stdout, cmd_stderr, returncode) = execute(cmd) - if returncode != 0: - return container - names = cmd_stdout.split() - if names: - return names[0] - return container - - def main(argv=sys.argv): global log log = logging.getLogger('heat-config') @@ -200,36 +70,16 @@ def main(argv=sys.argv): if not isinstance(config, dict): config = yaml.safe_load(config) - key_fltr = lambda k: config[k].get('start_order', 0) - for container in sorted(config, key=key_fltr): - log.debug("Running container: %s" % container) - action = config[container].get('action', 'run') - exit_codes = config[container].get('exit_codes', [0]) - - if action == 'run': - cmd = [ - DOCKER_CMD, - 'run', - '--name', - unique_container_name(container) - ] - label_arguments(cmd, container, cid, input_values) - docker_run_args(cmd, container, config) - elif action == 'exec': - cmd = [DOCKER_CMD, 'exec'] - docker_exec_args(cmd, container, config, cid) - - (cmd_stdout, cmd_stderr, returncode) = execute(cmd) - if cmd_stdout: - stdout.append(cmd_stdout) - if cmd_stderr: - stderr.append(cmd_stderr) - - if returncode not in exit_codes: - log.error("Error running %s. [%s]\n" % (cmd, returncode)) - deploy_status_code = returncode - else: - log.debug('Completed %s' % cmd) + labels = collections.OrderedDict() + labels['deploy_stack_id'] = input_values.get('deploy_stack_id') + labels['deploy_resource_name'] = input_values.get('deploy_resource_name') + (stdout, stderr, deploy_status_code) = paunch.apply( + cid, + config, + managed_by='docker-cmd', + labels=labels, + docker_cmd=DOCKER_CMD + ) json.dump(build_response( '\n'.join(stdout), '\n'.join(stderr), deploy_status_code), sys.stdout) diff --git a/heat-config-docker-cmd/os-refresh-config/configure.d/50-heat-config-docker-cmd b/heat-config-docker-cmd/os-refresh-config/configure.d/50-heat-config-docker-cmd index b169ed9..b2a778b 100755 --- a/heat-config-docker-cmd/os-refresh-config/configure.d/50-heat-config-docker-cmd +++ b/heat-config-docker-cmd/os-refresh-config/configure.d/50-heat-config-docker-cmd @@ -15,9 +15,10 @@ import json import logging import os -import subprocess import sys +import paunch + CONF_FILE = os.environ.get('HEAT_SHELL_CONFIG', '/var/run/heat-config/heat-config') @@ -49,100 +50,12 @@ def main(argv=sys.argv): cmd_config_ids = [c['id'] for c in configs if c['group'] == 'docker-cmd'] - try: - delete_missing_configs(cmd_config_ids) - except Exception as e: - log.exception(e) - try: - rename_containers() - except Exception as e: - log.exception(e) - -def delete_missing_configs(config_ids): - for conf_id in current_config_ids(): - if conf_id not in config_ids: - log.debug('%s no longer exists, deleting containers' % conf_id) - remove_containers(conf_id) - - -def execute(cmd): - log.debug(' '.join(cmd)) - subproc = subprocess.Popen(cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - cmd_stdout, cmd_stderr = subproc.communicate() - return cmd_stdout, cmd_stderr, subproc.returncode - - -def current_config_ids(): - # List all config_id labels for containers managed by docker-cmd - cmd = [ - DOCKER_CMD, 'ps', '-a', - '--filter', 'label=managed_by=docker-cmd', - '--format', '{{.Label "config_id"}}' - ] - cmd_stdout, cmd_stderr, returncode = execute(cmd) - if returncode != 0: - return set() - return set(cmd_stdout.split()) - - -def remove_containers(conf_id): - cmd = [ - DOCKER_CMD, 'ps', '-q', '-a', - '--filter', 'label=managed_by=docker-cmd', - '--filter', 'label=config_id=%s' % conf_id - ] - cmd_stdout, cmd_stderr, returncode = execute(cmd) - if returncode == 0: - for container in cmd_stdout.split(): - remove_container(container) - - -def remove_container(container): - cmd = [DOCKER_CMD, 'rm', '-f', container] - cmd_stdout, cmd_stderr, returncode = execute(cmd) - if returncode != 0: - log.error('Error removing container: %s' % container) - log.error(cmd_stderr) - - -def rename_containers(): - # list every container name, and its container_name label - cmd = [ - DOCKER_CMD, 'ps', '-a', - '--format', '{{.Names}} {{.Label "container_name"}}' - ] - cmd_stdout, cmd_stderr, returncode = execute(cmd) - if returncode != 0: - return - - lines = cmd_stdout.split("\n") - current_containers = [] - need_renaming = {} - for line in lines: - entry = line.split() - if not entry: - continue - current_containers.append(entry[0]) - - # ignore if container_name label not set - if len(entry) < 2: - continue - - # ignore if desired name is already actual name - if entry[0] == entry[-1]: - continue - - need_renaming[entry[0]] = entry[-1] - - for current, desired in sorted(need_renaming.items()): - if desired in current_containers: - log.info('Cannot rename "%s" since "%s" still exists' % ( - current, desired)) - else: - cmd = [DOCKER_CMD, 'rename', current, desired] - execute(cmd) + paunch.cleanup( + cmd_config_ids, + managed_by='docker-cmd', + docker_cmd=DOCKER_CMD + ) if __name__ == '__main__': diff --git a/releasenotes/notes/docker-cmd-paunch-0a674fc1b84b2dfc.yaml b/releasenotes/notes/docker-cmd-paunch-0a674fc1b84b2dfc.yaml new file mode 100644 index 0000000..6fb0577 --- /dev/null +++ b/releasenotes/notes/docker-cmd-paunch-0a674fc1b84b2dfc.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + The logic for the docker-cmd hook is now provided by the paunch library, + where further feature work will occur. diff --git a/test-requirements.txt b/test-requirements.txt index e1364a1..2c06087 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,6 +5,7 @@ fixtures>=3.0.0 # Apache-2.0/BSD # Hacking already pins down pep8, pyflakes and flake8 hacking>=0.12.0,!=0.13.0,<0.14 # Apache-2.0 mock>=2.0 # BSD +paunch>=1.0.0 # Apache-2.0 requests>=2.10.0,!=2.12.2,!=2.13.0 # Apache-2.0 requests-mock>=1.1 # Apache-2.0 salt diff --git a/tests/test_hook_docker_cmd.py b/tests/test_hook_docker_cmd.py index 97319d0..71854dc 100644 --- a/tests/test_hook_docker_cmd.py +++ b/tests/test_hook_docker_cmd.py @@ -112,28 +112,54 @@ class HookDockerCmdTest(common.RunScriptTest): 'TEST_STATE_PATH': self.test_state_path, }) + def assert_args_and_labels(self, expected_args, expected_labels, observed): + '''Assert the labels arguments separately to other arguments. + + Tests that each expected_labels label exists, and remaining + expected arguments match exactly. + + This allows paunch to add new label arguments without breaking these + tests. + ''' + + args = [] + labels = [] + j = 0 + while j < len(observed): + if observed[j] == '--label': + j += 1 + labels.append(observed[j]) + else: + args.append(observed[j]) + j += 1 + + self.assertEqual(expected_args, args) + for label in expected_labels: + self.assertIn(label, labels) + def test_hook(self): self.env.update({ - 'TEST_RESPONSE': json.dumps([{ - 'stderr': 'Error: No such image, container or task: db', - 'returncode': 1 - }, { - 'stdout': '', - 'stderr': 'Creating db...' - }, { - 'stderr': 'Error: No such image, container or task: web', - 'returncode': 1 - }, { - 'stdout': '', - 'stderr': 'Creating web...' - }, { - 'stdout': 'web', - }, { - - 'stdout': '', - 'stderr': 'one.txt\ntwo.txt\nthree.txt' - }]) + 'TEST_RESPONSE': json.dumps([ + # ps for delete missing + {}, + # ps for renames + {}, + # ps for currently running containers + {}, + # inspect for db unique container name + {}, + # docker run db + {'stderr': 'Creating db...'}, + # inspect for web unique container name + {}, + # docker run web + {'stderr': 'Creating web...'}, + # name lookup for exec web + {'stdout': 'web'}, + # docker exec web + {'stderr': 'one.txt\ntwo.txt\nthree.txt'}, + ]) }) returncode, stdout, stderr = self.run_cmd( [self.hook_path], self.env, json.dumps(self.data)) @@ -148,58 +174,75 @@ class HookDockerCmdTest(common.RunScriptTest): 'deploy_status_code': 0 }, json.loads(stdout)) - state = list(self.json_from_files(self.test_state_path, 6)) + state = list(self.json_from_files(self.test_state_path, 9)) + self.assertEqual([ + self.fake_tool_path, + 'ps', + '-a', + '--filter', + 'label=managed_by=docker-cmd', + '--filter', + 'label=config_id=abc123', + '--format', + '{{.Names}} {{.Label "container_name"}}' + ], state[0]['args']) + self.assertEqual([ + self.fake_tool_path, + 'ps', + '-a', + '--filter', + 'label=managed_by=docker-cmd', + '--format', + '{{.Names}} {{.Label "container_name"}}' + ], state[1]['args']) + self.assertEqual([ + self.fake_tool_path, + 'ps', + '-a', + '--filter', + 'label=managed_by=docker-cmd', + '--filter', + 'label=config_id=abc123', + '--format', + '{{.Names}} {{.Label "container_name"}}' + ], state[2]['args']) self.assertEqual([ self.fake_tool_path, 'inspect', '--format', 'exists', - 'db', - ], state[0]['args']) - self.assertEqual([ + 'db' + ], state[3]['args']) + self.assert_args_and_labels([ self.fake_tool_path, 'run', '--name', 'db', - '--label', - 'deploy_stack_id=the_stack', - '--label', - 'deploy_resource_name=the_deployment', - '--label', - 'config_id=abc123', - '--label', - 'container_name=db', - '--label', - 'managed_by=docker-cmd', '--detach=true', '--env-file=env.file', '--env=foo=bar', '--privileged=false', 'xxx' '' - ], state[1]['args']) + ], [ + 'deploy_stack_id=the_stack', + 'deploy_resource_name=the_deployment', + 'config_id=abc123', + 'container_name=db', + 'managed_by=docker-cmd', + ], state[4]['args']) self.assertEqual([ self.fake_tool_path, 'inspect', '--format', 'exists', 'web', - ], state[2]['args']) - self.assertEqual([ + ], state[5]['args']) + self.assert_args_and_labels([ self.fake_tool_path, 'run', '--name', 'web', - '--label', - 'deploy_stack_id=the_stack', - '--label', - 'deploy_resource_name=the_deployment', - '--label', - 'config_id=abc123', - '--label', - 'container_name=web', - '--label', - 'managed_by=docker-cmd', '--detach=true', '--env-file=foo.env', '--env-file=bar.conf', @@ -214,7 +257,13 @@ class HookDockerCmdTest(common.RunScriptTest): 'yyy', '/bin/webserver', 'start' - ], state[3]['args']) + ], [ + 'deploy_stack_id=the_stack', + 'deploy_resource_name=the_deployment', + 'config_id=abc123', + 'container_name=web', + 'managed_by=docker-cmd', + ], state[6]['args']) self.assertEqual([ self.fake_tool_path, 'ps', @@ -225,25 +274,32 @@ class HookDockerCmdTest(common.RunScriptTest): 'label=config_id=abc123', '--format', '{{.Names}}', - ], state[4]['args']) + ], state[7]['args']) self.assertEqual([ self.fake_tool_path, 'exec', 'web', '/bin/ls', '-l' - ], state[5]['args']) + ], state[8]['args']) def test_hook_exit_codes(self): self.env.update({ - 'TEST_RESPONSE': json.dumps([{ - 'stdout': 'web', - }, { - 'stdout': '', - 'stderr': 'Warning: custom exit code', - 'returncode': 1 - }]) + 'TEST_RESPONSE': json.dumps([ + # ps for delete missing + {}, + # ps for renames + {}, + # ps for currently running containers + {}, + {'stdout': 'web'}, + { + 'stdout': '', + 'stderr': 'Warning: custom exit code', + 'returncode': 1 + } + ]) }) returncode, stdout, stderr = self.run_cmd( [self.hook_path], self.env, json.dumps(self.data_exit_code)) @@ -254,7 +310,38 @@ class HookDockerCmdTest(common.RunScriptTest): 'deploy_status_code': 0 }, json.loads(stdout)) - state = list(self.json_from_files(self.test_state_path, 2)) + state = list(self.json_from_files(self.test_state_path, 5)) + self.assertEqual([ + self.fake_tool_path, + 'ps', + '-a', + '--filter', + 'label=managed_by=docker-cmd', + '--filter', + 'label=config_id=abc123', + '--format', + '{{.Names}} {{.Label "container_name"}}' + ], state[0]['args']) + self.assertEqual([ + self.fake_tool_path, + 'ps', + '-a', + '--filter', + 'label=managed_by=docker-cmd', + '--format', + '{{.Names}} {{.Label "container_name"}}' + ], state[1]['args']) + self.assertEqual([ + self.fake_tool_path, + 'ps', + '-a', + '--filter', + 'label=managed_by=docker-cmd', + '--filter', + 'label=config_id=abc123', + '--format', + '{{.Names}} {{.Label "container_name"}}' + ], state[2]['args']) self.assertEqual([ self.fake_tool_path, 'ps', @@ -265,37 +352,42 @@ class HookDockerCmdTest(common.RunScriptTest): 'label=config_id=abc123', '--format', '{{.Names}}', - ], state[0]['args']) + ], state[3]['args']) self.assertEqual([ self.fake_tool_path, 'exec', 'web', '/bin/ls', '-l' - ], state[1]['args']) + ], state[4]['args']) def test_hook_failed(self): self.env.update({ - 'TEST_RESPONSE': json.dumps([{ - 'stderr': 'Error: No such image, container or task: db', - 'returncode': 1 - }, { - 'stdout': '', - 'stderr': 'Creating db...' - }, { - 'stderr': 'Error: No such image, container or task: web', - 'returncode': 1 - }, { - 'stdout': '', - 'stderr': 'Creating web...' - }, { - 'stdout': 'web', - }, { - 'stdout': '', - 'stderr': 'No such file or directory', - 'returncode': 2 - }]) + 'TEST_RESPONSE': json.dumps([ + # ps for delete missing + {}, + # ps for renames + {}, + # ps for currently running containers + {}, + # inspect for db unique container name + {}, + # docker run db + {'stderr': 'Creating db...'}, + # inspect for web unique container name + {}, + # docker run web + {'stderr': 'Creating web...'}, + # name lookup for exec web + {'stdout': 'web'}, + # docker exec web fails + { + 'stdout': '', + 'stderr': 'No such file or directory', + 'returncode': 2 + } + ]) }) returncode, stdout, stderr = self.run_cmd( [self.hook_path], self.env, json.dumps(self.data)) @@ -308,57 +400,75 @@ class HookDockerCmdTest(common.RunScriptTest): 'deploy_status_code': 2 }, json.loads(stdout)) - state = list(self.json_from_files(self.test_state_path, 6)) + state = list(self.json_from_files(self.test_state_path, 9)) + self.assertEqual([ + self.fake_tool_path, + 'ps', + '-a', + '--filter', + 'label=managed_by=docker-cmd', + '--filter', + 'label=config_id=abc123', + '--format', + '{{.Names}} {{.Label "container_name"}}' + ], state[0]['args']) + self.assertEqual([ + self.fake_tool_path, + 'ps', + '-a', + '--filter', + 'label=managed_by=docker-cmd', + '--format', + '{{.Names}} {{.Label "container_name"}}' + ], state[1]['args']) + self.assertEqual([ + self.fake_tool_path, + 'ps', + '-a', + '--filter', + 'label=managed_by=docker-cmd', + '--filter', + 'label=config_id=abc123', + '--format', + '{{.Names}} {{.Label "container_name"}}' + ], state[2]['args']) self.assertEqual([ self.fake_tool_path, 'inspect', '--format', 'exists', - 'db', - ], state[0]['args']) - self.assertEqual([ + 'db' + ], state[3]['args']) + self.assert_args_and_labels([ self.fake_tool_path, 'run', '--name', 'db', - '--label', - 'deploy_stack_id=the_stack', - '--label', - 'deploy_resource_name=the_deployment', - '--label', - 'config_id=abc123', - '--label', - 'container_name=db', - '--label', - 'managed_by=docker-cmd', '--detach=true', '--env-file=env.file', '--env=foo=bar', '--privileged=false', 'xxx' - ], state[1]['args']) + '' + ], [ + 'deploy_stack_id=the_stack', + 'deploy_resource_name=the_deployment', + 'config_id=abc123', + 'container_name=db', + 'managed_by=docker-cmd', + ], state[4]['args']) self.assertEqual([ self.fake_tool_path, 'inspect', '--format', 'exists', 'web', - ], state[2]['args']) - self.assertEqual([ + ], state[5]['args']) + self.assert_args_and_labels([ self.fake_tool_path, 'run', '--name', 'web', - '--label', - 'deploy_stack_id=the_stack', - '--label', - 'deploy_resource_name=the_deployment', - '--label', - 'config_id=abc123', - '--label', - 'container_name=web', - '--label', - 'managed_by=docker-cmd', '--detach=true', '--env-file=foo.env', '--env-file=bar.conf', @@ -373,7 +483,13 @@ class HookDockerCmdTest(common.RunScriptTest): 'yyy', '/bin/webserver', 'start' - ], state[3]['args']) + ], [ + 'deploy_stack_id=the_stack', + 'deploy_resource_name=the_deployment', + 'config_id=abc123', + 'container_name=web', + 'managed_by=docker-cmd', + ], state[6]['args']) self.assertEqual([ self.fake_tool_path, 'ps', @@ -384,43 +500,47 @@ class HookDockerCmdTest(common.RunScriptTest): 'label=config_id=abc123', '--format', '{{.Names}}', - ], state[4]['args']) + ], state[7]['args']) self.assertEqual([ self.fake_tool_path, 'exec', 'web', '/bin/ls', '-l' - ], state[5]['args']) + ], state[8]['args']) def test_hook_unique_names(self): - self.env.update({ - 'TEST_RESPONSE': json.dumps([{ - 'stdout': 'exists\n', - 'returncode': 0 - }, { - 'stderr': 'Error: No such image, container or task: db-blah', - 'returncode': 1 - }, { - 'stdout': '', - 'stderr': 'Creating db...' - }, { - 'stdout': 'exists\n', - 'returncode': 0 - }, { - 'stderr': 'Error: No such image, container or task: web-blah', - 'returncode': 1 - }, { - 'stdout': '', - 'stderr': 'Creating web...' - }, { - 'stdout': 'web-asdf1234', - }, { - 'stdout': '', - 'stderr': 'one.txt\ntwo.txt\nthree.txt' - }]) + 'TEST_RESPONSE': json.dumps([ + # ps for delete missing in this config id + {}, + # ps for renames + {'stdout': 'web web\ndb db\n'}, + # ps for currently running containers in this config id + {}, + # inspect for db unique container name + {'stdout': 'exists'}, + { + 'stderr': 'Error: No such container: db-blah', + 'returncode': 1 + }, + # docker run db + {'stderr': 'Creating db...'}, + # # inspect for web unique container name + {'stdout': 'exists'}, + { + 'stderr': 'Error: No such container: web-blah', + 'returncode': 1 + }, + # # docker run web + {'stderr': 'Creating web...'}, + # name lookup for exec web + {'stdout': 'web-asdf1234'}, + # docker exec web-asdf1234 + {'stderr': 'one.txt\ntwo.txt\nthree.txt'}, + ]) }) + returncode, stdout, stderr = self.run_cmd( [self.hook_path], self.env, json.dumps(self.data)) @@ -434,75 +554,92 @@ class HookDockerCmdTest(common.RunScriptTest): 'deploy_status_code': 0 }, json.loads(stdout)) - state = list(self.json_from_files(self.test_state_path, 8)) - db_container_name = state[1]['args'][4] - web_container_name = state[4]['args'][4] + state = list(self.json_from_files(self.test_state_path, 11)) + db_container_name = state[4]['args'][4] + web_container_name = state[7]['args'][4] self.assertRegex(db_container_name, 'db-[0-9a-z]{8}') self.assertRegex(web_container_name, 'web-[0-9a-z]{8}') self.assertEqual([ self.fake_tool_path, - 'inspect', + 'ps', + '-a', + '--filter', + 'label=managed_by=docker-cmd', + '--filter', + 'label=config_id=abc123', '--format', - 'exists', - 'db', + '{{.Names}} {{.Label "container_name"}}' ], state[0]['args']) self.assertEqual([ self.fake_tool_path, - 'inspect', + 'ps', + '-a', + '--filter', + 'label=managed_by=docker-cmd', '--format', - 'exists', - db_container_name, + '{{.Names}} {{.Label "container_name"}}' ], state[1]['args']) self.assertEqual([ self.fake_tool_path, - 'run', - '--name', - db_container_name, - '--label', - 'deploy_stack_id=the_stack', - '--label', - 'deploy_resource_name=the_deployment', - '--label', - 'config_id=abc123', - '--label', - 'container_name=db', - '--label', - 'managed_by=docker-cmd', - '--detach=true', - '--env-file=env.file', - '--env=foo=bar', - '--privileged=false', - 'xxx' + 'ps', + '-a', + '--filter', + 'label=managed_by=docker-cmd', + '--filter', + 'label=config_id=abc123', + '--format', + '{{.Names}} {{.Label "container_name"}}' ], state[2]['args']) self.assertEqual([ self.fake_tool_path, 'inspect', '--format', 'exists', - 'web', + 'db' ], state[3]['args']) self.assertEqual([ self.fake_tool_path, 'inspect', '--format', 'exists', - web_container_name, + db_container_name, ], state[4]['args']) + self.assert_args_and_labels([ + self.fake_tool_path, + 'run', + '--name', + db_container_name, + '--detach=true', + '--env-file=env.file', + '--env=foo=bar', + '--privileged=false', + 'xxx' + ], [ + 'deploy_stack_id=the_stack', + 'deploy_resource_name=the_deployment', + 'config_id=abc123', + 'container_name=db', + 'managed_by=docker-cmd', + ], state[5]['args']) self.assertEqual([ + self.fake_tool_path, + 'inspect', + '--format', + 'exists', + 'web', + ], state[6]['args']) + self.assertEqual([ + self.fake_tool_path, + 'inspect', + '--format', + 'exists', + web_container_name, + ], state[7]['args']) + self.assert_args_and_labels([ self.fake_tool_path, 'run', '--name', web_container_name, - '--label', - 'deploy_stack_id=the_stack', - '--label', - 'deploy_resource_name=the_deployment', - '--label', - 'config_id=abc123', - '--label', - 'container_name=web', - '--label', - 'managed_by=docker-cmd', '--detach=true', '--env-file=foo.env', '--env-file=bar.conf', @@ -517,7 +654,13 @@ class HookDockerCmdTest(common.RunScriptTest): 'yyy', '/bin/webserver', 'start' - ], state[5]['args']) + ], [ + 'deploy_stack_id=the_stack', + 'deploy_resource_name=the_deployment', + 'config_id=abc123', + 'container_name=web', + 'managed_by=docker-cmd', + ], state[8]['args']) self.assertEqual([ self.fake_tool_path, 'ps', @@ -528,14 +671,14 @@ class HookDockerCmdTest(common.RunScriptTest): 'label=config_id=abc123', '--format', '{{.Names}}', - ], state[6]['args']) + ], state[9]['args']) self.assertEqual([ self.fake_tool_path, 'exec', 'web-asdf1234', '/bin/ls', '-l' - ], state[7]['args']) + ], state[10]['args']) def test_cleanup_deleted(self): self.env.update({ @@ -571,6 +714,8 @@ class HookDockerCmdTest(common.RunScriptTest): self.fake_tool_path, 'ps', '-a', + '--filter', + 'label=managed_by=docker-cmd', '--format', '{{.Names}} {{.Label "container_name"}}' ], state[1]['args']) @@ -647,6 +792,8 @@ class HookDockerCmdTest(common.RunScriptTest): self.fake_tool_path, 'ps', '-a', + '--filter', + 'label=managed_by=docker-cmd', '--format', '{{.Names}} {{.Label "container_name"}}' ], state[5]['args']) @@ -687,6 +834,8 @@ class HookDockerCmdTest(common.RunScriptTest): self.fake_tool_path, 'ps', '-a', + '--filter', + 'label=managed_by=docker-cmd', '--format', '{{.Names}} {{.Label "container_name"}}' ], state[1]['args']) @@ -765,6 +914,8 @@ class HookDockerCmdTest(common.RunScriptTest): self.fake_tool_path, 'ps', '-a', + '--filter', + 'label=managed_by=docker-cmd', '--format', '{{.Names}} {{.Label "container_name"}}' ], state[5]['args']) @@ -805,6 +956,8 @@ class HookDockerCmdTest(common.RunScriptTest): self.fake_tool_path, 'ps', '-a', + '--filter', + 'label=managed_by=docker-cmd', '--format', '{{.Names}} {{.Label "container_name"}}' ], state[1]['args'])