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
This commit is contained in:
Steve Baker 2017-04-12 10:09:06 +12:00
parent 3a86f8789c
commit 33241a84c1
6 changed files with 356 additions and 433 deletions

View File

@ -1,4 +1,5 @@
A hook which uses the `docker` command to deploy containers. A hook which uses the `docker` command via
`paunch <https://docs.openstack.org/developer/paunch/>`_ to deploy containers.
The hook currently supports specifying containers in the `docker-compose v1 The hook currently supports specifying containers in the `docker-compose v1
format <https://docs.docker.com/compose/compose-file/#/version-1>`_. The format <https://docs.docker.com/compose/compose-file/#/version-1>`_. The

View File

@ -12,15 +12,14 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import collections
import json import json
import logging import logging
import os import os
import random
import string
import subprocess
import sys import sys
import yaml
import paunch
import yaml
DOCKER_CMD = os.environ.get('HEAT_DOCKER_CMD', 'docker') 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): def main(argv=sys.argv):
global log global log
log = logging.getLogger('heat-config') log = logging.getLogger('heat-config')
@ -200,36 +70,16 @@ def main(argv=sys.argv):
if not isinstance(config, dict): if not isinstance(config, dict):
config = yaml.safe_load(config) config = yaml.safe_load(config)
key_fltr = lambda k: config[k].get('start_order', 0) labels = collections.OrderedDict()
for container in sorted(config, key=key_fltr): labels['deploy_stack_id'] = input_values.get('deploy_stack_id')
log.debug("Running container: %s" % container) labels['deploy_resource_name'] = input_values.get('deploy_resource_name')
action = config[container].get('action', 'run') (stdout, stderr, deploy_status_code) = paunch.apply(
exit_codes = config[container].get('exit_codes', [0]) cid,
config,
if action == 'run': managed_by='docker-cmd',
cmd = [ labels=labels,
DOCKER_CMD, docker_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)
json.dump(build_response( json.dump(build_response(
'\n'.join(stdout), '\n'.join(stderr), deploy_status_code), sys.stdout) '\n'.join(stdout), '\n'.join(stderr), deploy_status_code), sys.stdout)

View File

@ -15,9 +15,10 @@
import json import json
import logging import logging
import os import os
import subprocess
import sys import sys
import paunch
CONF_FILE = os.environ.get('HEAT_SHELL_CONFIG', CONF_FILE = os.environ.get('HEAT_SHELL_CONFIG',
'/var/run/heat-config/heat-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 cmd_config_ids = [c['id'] for c in configs
if c['group'] == 'docker-cmd'] 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)
paunch.cleanup(
def delete_missing_configs(config_ids): cmd_config_ids,
for conf_id in current_config_ids(): managed_by='docker-cmd',
if conf_id not in config_ids: docker_cmd=DOCKER_CMD
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)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -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.

View File

@ -5,6 +5,7 @@ fixtures>=3.0.0 # Apache-2.0/BSD
# Hacking already pins down pep8, pyflakes and flake8 # Hacking already pins down pep8, pyflakes and flake8
hacking>=0.12.0,!=0.13.0,<0.14 # Apache-2.0 hacking>=0.12.0,!=0.13.0,<0.14 # Apache-2.0
mock>=2.0 # BSD 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>=2.10.0,!=2.12.2,!=2.13.0 # Apache-2.0
requests-mock>=1.1 # Apache-2.0 requests-mock>=1.1 # Apache-2.0
salt salt

View File

@ -112,28 +112,54 @@ class HookDockerCmdTest(common.RunScriptTest):
'TEST_STATE_PATH': self.test_state_path, '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): def test_hook(self):
self.env.update({ self.env.update({
'TEST_RESPONSE': json.dumps([{ 'TEST_RESPONSE': json.dumps([
'stderr': 'Error: No such image, container or task: db', # ps for delete missing
'returncode': 1 {},
}, { # ps for renames
'stdout': '', {},
'stderr': 'Creating db...' # ps for currently running containers
}, { {},
'stderr': 'Error: No such image, container or task: web', # inspect for db unique container name
'returncode': 1 {},
}, { # docker run db
'stdout': '', {'stderr': 'Creating db...'},
'stderr': 'Creating web...' # inspect for web unique container name
}, { {},
'stdout': 'web', # docker run web
}, { {'stderr': 'Creating web...'},
# name lookup for exec web
'stdout': '', {'stdout': 'web'},
'stderr': 'one.txt\ntwo.txt\nthree.txt' # docker exec web
}]) {'stderr': 'one.txt\ntwo.txt\nthree.txt'},
])
}) })
returncode, stdout, stderr = self.run_cmd( returncode, stdout, stderr = self.run_cmd(
[self.hook_path], self.env, json.dumps(self.data)) [self.hook_path], self.env, json.dumps(self.data))
@ -148,58 +174,75 @@ class HookDockerCmdTest(common.RunScriptTest):
'deploy_status_code': 0 'deploy_status_code': 0
}, json.loads(stdout)) }, 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.assertEqual([
self.fake_tool_path, self.fake_tool_path,
'inspect', 'inspect',
'--format', '--format',
'exists', 'exists',
'db', 'db'
], state[0]['args']) ], state[3]['args'])
self.assertEqual([ self.assert_args_and_labels([
self.fake_tool_path, self.fake_tool_path,
'run', 'run',
'--name', '--name',
'db', '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', '--detach=true',
'--env-file=env.file', '--env-file=env.file',
'--env=foo=bar', '--env=foo=bar',
'--privileged=false', '--privileged=false',
'xxx' '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.assertEqual([
self.fake_tool_path, self.fake_tool_path,
'inspect', 'inspect',
'--format', '--format',
'exists', 'exists',
'web', 'web',
], state[2]['args']) ], state[5]['args'])
self.assertEqual([ self.assert_args_and_labels([
self.fake_tool_path, self.fake_tool_path,
'run', 'run',
'--name', '--name',
'web', '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', '--detach=true',
'--env-file=foo.env', '--env-file=foo.env',
'--env-file=bar.conf', '--env-file=bar.conf',
@ -214,7 +257,13 @@ class HookDockerCmdTest(common.RunScriptTest):
'yyy', 'yyy',
'/bin/webserver', '/bin/webserver',
'start' '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.assertEqual([
self.fake_tool_path, self.fake_tool_path,
'ps', 'ps',
@ -225,25 +274,32 @@ class HookDockerCmdTest(common.RunScriptTest):
'label=config_id=abc123', 'label=config_id=abc123',
'--format', '--format',
'{{.Names}}', '{{.Names}}',
], state[4]['args']) ], state[7]['args'])
self.assertEqual([ self.assertEqual([
self.fake_tool_path, self.fake_tool_path,
'exec', 'exec',
'web', 'web',
'/bin/ls', '/bin/ls',
'-l' '-l'
], state[5]['args']) ], state[8]['args'])
def test_hook_exit_codes(self): def test_hook_exit_codes(self):
self.env.update({ self.env.update({
'TEST_RESPONSE': json.dumps([{ 'TEST_RESPONSE': json.dumps([
'stdout': 'web', # ps for delete missing
}, { {},
'stdout': '', # ps for renames
'stderr': 'Warning: custom exit code', {},
'returncode': 1 # ps for currently running containers
}]) {},
{'stdout': 'web'},
{
'stdout': '',
'stderr': 'Warning: custom exit code',
'returncode': 1
}
])
}) })
returncode, stdout, stderr = self.run_cmd( returncode, stdout, stderr = self.run_cmd(
[self.hook_path], self.env, json.dumps(self.data_exit_code)) [self.hook_path], self.env, json.dumps(self.data_exit_code))
@ -254,7 +310,38 @@ class HookDockerCmdTest(common.RunScriptTest):
'deploy_status_code': 0 'deploy_status_code': 0
}, json.loads(stdout)) }, 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.assertEqual([
self.fake_tool_path, self.fake_tool_path,
'ps', 'ps',
@ -265,37 +352,42 @@ class HookDockerCmdTest(common.RunScriptTest):
'label=config_id=abc123', 'label=config_id=abc123',
'--format', '--format',
'{{.Names}}', '{{.Names}}',
], state[0]['args']) ], state[3]['args'])
self.assertEqual([ self.assertEqual([
self.fake_tool_path, self.fake_tool_path,
'exec', 'exec',
'web', 'web',
'/bin/ls', '/bin/ls',
'-l' '-l'
], state[1]['args']) ], state[4]['args'])
def test_hook_failed(self): def test_hook_failed(self):
self.env.update({ self.env.update({
'TEST_RESPONSE': json.dumps([{ 'TEST_RESPONSE': json.dumps([
'stderr': 'Error: No such image, container or task: db', # ps for delete missing
'returncode': 1 {},
}, { # ps for renames
'stdout': '', {},
'stderr': 'Creating db...' # ps for currently running containers
}, { {},
'stderr': 'Error: No such image, container or task: web', # inspect for db unique container name
'returncode': 1 {},
}, { # docker run db
'stdout': '', {'stderr': 'Creating db...'},
'stderr': 'Creating web...' # inspect for web unique container name
}, { {},
'stdout': 'web', # docker run web
}, { {'stderr': 'Creating web...'},
'stdout': '', # name lookup for exec web
'stderr': 'No such file or directory', {'stdout': 'web'},
'returncode': 2 # docker exec web fails
}]) {
'stdout': '',
'stderr': 'No such file or directory',
'returncode': 2
}
])
}) })
returncode, stdout, stderr = self.run_cmd( returncode, stdout, stderr = self.run_cmd(
[self.hook_path], self.env, json.dumps(self.data)) [self.hook_path], self.env, json.dumps(self.data))
@ -308,57 +400,75 @@ class HookDockerCmdTest(common.RunScriptTest):
'deploy_status_code': 2 'deploy_status_code': 2
}, json.loads(stdout)) }, 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.assertEqual([
self.fake_tool_path, self.fake_tool_path,
'inspect', 'inspect',
'--format', '--format',
'exists', 'exists',
'db', 'db'
], state[0]['args']) ], state[3]['args'])
self.assertEqual([ self.assert_args_and_labels([
self.fake_tool_path, self.fake_tool_path,
'run', 'run',
'--name', '--name',
'db', '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', '--detach=true',
'--env-file=env.file', '--env-file=env.file',
'--env=foo=bar', '--env=foo=bar',
'--privileged=false', '--privileged=false',
'xxx' '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.assertEqual([
self.fake_tool_path, self.fake_tool_path,
'inspect', 'inspect',
'--format', '--format',
'exists', 'exists',
'web', 'web',
], state[2]['args']) ], state[5]['args'])
self.assertEqual([ self.assert_args_and_labels([
self.fake_tool_path, self.fake_tool_path,
'run', 'run',
'--name', '--name',
'web', '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', '--detach=true',
'--env-file=foo.env', '--env-file=foo.env',
'--env-file=bar.conf', '--env-file=bar.conf',
@ -373,7 +483,13 @@ class HookDockerCmdTest(common.RunScriptTest):
'yyy', 'yyy',
'/bin/webserver', '/bin/webserver',
'start' '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.assertEqual([
self.fake_tool_path, self.fake_tool_path,
'ps', 'ps',
@ -384,43 +500,47 @@ class HookDockerCmdTest(common.RunScriptTest):
'label=config_id=abc123', 'label=config_id=abc123',
'--format', '--format',
'{{.Names}}', '{{.Names}}',
], state[4]['args']) ], state[7]['args'])
self.assertEqual([ self.assertEqual([
self.fake_tool_path, self.fake_tool_path,
'exec', 'exec',
'web', 'web',
'/bin/ls', '/bin/ls',
'-l' '-l'
], state[5]['args']) ], state[8]['args'])
def test_hook_unique_names(self): def test_hook_unique_names(self):
self.env.update({ self.env.update({
'TEST_RESPONSE': json.dumps([{ 'TEST_RESPONSE': json.dumps([
'stdout': 'exists\n', # ps for delete missing in this config id
'returncode': 0 {},
}, { # ps for renames
'stderr': 'Error: No such image, container or task: db-blah', {'stdout': 'web web\ndb db\n'},
'returncode': 1 # ps for currently running containers in this config id
}, { {},
'stdout': '', # inspect for db unique container name
'stderr': 'Creating db...' {'stdout': 'exists'},
}, { {
'stdout': 'exists\n', 'stderr': 'Error: No such container: db-blah',
'returncode': 0 'returncode': 1
}, { },
'stderr': 'Error: No such image, container or task: web-blah', # docker run db
'returncode': 1 {'stderr': 'Creating db...'},
}, { # # inspect for web unique container name
'stdout': '', {'stdout': 'exists'},
'stderr': 'Creating web...' {
}, { 'stderr': 'Error: No such container: web-blah',
'stdout': 'web-asdf1234', 'returncode': 1
}, { },
'stdout': '', # # docker run web
'stderr': 'one.txt\ntwo.txt\nthree.txt' {'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( returncode, stdout, stderr = self.run_cmd(
[self.hook_path], self.env, json.dumps(self.data)) [self.hook_path], self.env, json.dumps(self.data))
@ -434,75 +554,92 @@ class HookDockerCmdTest(common.RunScriptTest):
'deploy_status_code': 0 'deploy_status_code': 0
}, json.loads(stdout)) }, json.loads(stdout))
state = list(self.json_from_files(self.test_state_path, 8)) state = list(self.json_from_files(self.test_state_path, 11))
db_container_name = state[1]['args'][4] db_container_name = state[4]['args'][4]
web_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(db_container_name, 'db-[0-9a-z]{8}')
self.assertRegex(web_container_name, 'web-[0-9a-z]{8}') self.assertRegex(web_container_name, 'web-[0-9a-z]{8}')
self.assertEqual([ self.assertEqual([
self.fake_tool_path, self.fake_tool_path,
'inspect', 'ps',
'-a',
'--filter',
'label=managed_by=docker-cmd',
'--filter',
'label=config_id=abc123',
'--format', '--format',
'exists', '{{.Names}} {{.Label "container_name"}}'
'db',
], state[0]['args']) ], state[0]['args'])
self.assertEqual([ self.assertEqual([
self.fake_tool_path, self.fake_tool_path,
'inspect', 'ps',
'-a',
'--filter',
'label=managed_by=docker-cmd',
'--format', '--format',
'exists', '{{.Names}} {{.Label "container_name"}}'
db_container_name,
], state[1]['args']) ], state[1]['args'])
self.assertEqual([ self.assertEqual([
self.fake_tool_path, self.fake_tool_path,
'run', 'ps',
'--name', '-a',
db_container_name, '--filter',
'--label', 'label=managed_by=docker-cmd',
'deploy_stack_id=the_stack', '--filter',
'--label', 'label=config_id=abc123',
'deploy_resource_name=the_deployment', '--format',
'--label', '{{.Names}} {{.Label "container_name"}}'
'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[2]['args']) ], state[2]['args'])
self.assertEqual([ self.assertEqual([
self.fake_tool_path, self.fake_tool_path,
'inspect', 'inspect',
'--format', '--format',
'exists', 'exists',
'web', 'db'
], state[3]['args']) ], state[3]['args'])
self.assertEqual([ self.assertEqual([
self.fake_tool_path, self.fake_tool_path,
'inspect', 'inspect',
'--format', '--format',
'exists', 'exists',
web_container_name, db_container_name,
], state[4]['args']) ], 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.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, self.fake_tool_path,
'run', 'run',
'--name', '--name',
web_container_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', '--detach=true',
'--env-file=foo.env', '--env-file=foo.env',
'--env-file=bar.conf', '--env-file=bar.conf',
@ -517,7 +654,13 @@ class HookDockerCmdTest(common.RunScriptTest):
'yyy', 'yyy',
'/bin/webserver', '/bin/webserver',
'start' '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.assertEqual([
self.fake_tool_path, self.fake_tool_path,
'ps', 'ps',
@ -528,14 +671,14 @@ class HookDockerCmdTest(common.RunScriptTest):
'label=config_id=abc123', 'label=config_id=abc123',
'--format', '--format',
'{{.Names}}', '{{.Names}}',
], state[6]['args']) ], state[9]['args'])
self.assertEqual([ self.assertEqual([
self.fake_tool_path, self.fake_tool_path,
'exec', 'exec',
'web-asdf1234', 'web-asdf1234',
'/bin/ls', '/bin/ls',
'-l' '-l'
], state[7]['args']) ], state[10]['args'])
def test_cleanup_deleted(self): def test_cleanup_deleted(self):
self.env.update({ self.env.update({
@ -571,6 +714,8 @@ class HookDockerCmdTest(common.RunScriptTest):
self.fake_tool_path, self.fake_tool_path,
'ps', 'ps',
'-a', '-a',
'--filter',
'label=managed_by=docker-cmd',
'--format', '--format',
'{{.Names}} {{.Label "container_name"}}' '{{.Names}} {{.Label "container_name"}}'
], state[1]['args']) ], state[1]['args'])
@ -647,6 +792,8 @@ class HookDockerCmdTest(common.RunScriptTest):
self.fake_tool_path, self.fake_tool_path,
'ps', 'ps',
'-a', '-a',
'--filter',
'label=managed_by=docker-cmd',
'--format', '--format',
'{{.Names}} {{.Label "container_name"}}' '{{.Names}} {{.Label "container_name"}}'
], state[5]['args']) ], state[5]['args'])
@ -687,6 +834,8 @@ class HookDockerCmdTest(common.RunScriptTest):
self.fake_tool_path, self.fake_tool_path,
'ps', 'ps',
'-a', '-a',
'--filter',
'label=managed_by=docker-cmd',
'--format', '--format',
'{{.Names}} {{.Label "container_name"}}' '{{.Names}} {{.Label "container_name"}}'
], state[1]['args']) ], state[1]['args'])
@ -765,6 +914,8 @@ class HookDockerCmdTest(common.RunScriptTest):
self.fake_tool_path, self.fake_tool_path,
'ps', 'ps',
'-a', '-a',
'--filter',
'label=managed_by=docker-cmd',
'--format', '--format',
'{{.Names}} {{.Label "container_name"}}' '{{.Names}} {{.Label "container_name"}}'
], state[5]['args']) ], state[5]['args'])
@ -805,6 +956,8 @@ class HookDockerCmdTest(common.RunScriptTest):
self.fake_tool_path, self.fake_tool_path,
'ps', 'ps',
'-a', '-a',
'--filter',
'label=managed_by=docker-cmd',
'--format', '--format',
'{{.Names}} {{.Label "container_name"}}' '{{.Names}} {{.Label "container_name"}}'
], state[1]['args']) ], state[1]['args'])