Merge "Explicitly pull images before docker run"

This commit is contained in:
Zuul 2017-12-04 20:19:43 +00:00 committed by Gerrit Code Review
commit e2d2344abb
5 changed files with 138 additions and 13 deletions

View File

@ -27,10 +27,14 @@ class ComposeV1Builder(object):
def apply(self):
self.delete_missing_and_updated()
stdout = []
stderr = []
pull_returncode = self.pull_missing_images(stdout, stderr)
if pull_returncode != 0:
return stdout, stderr, pull_returncode
self.delete_missing_and_updated()
deploy_status_code = 0
key_fltr = lambda k: self.config[k].get('start_order', 0)
@ -195,6 +199,40 @@ class ComposeV1Builder(object):
command[0], self.config_id)
cmd.extend(command)
def pull_missing_images(self, stdout, stderr):
images = set()
for container in self.config:
cconfig = self.config[container]
image = cconfig.get('image')
if image:
images.add(image)
returncode = 0
for image in sorted(images):
# only pull if the image does not exist locally
if self.runner.inspect(image, format='exists', type='image'):
continue
cmd = [self.runner.docker_cmd, 'pull', image]
(cmd_stdout, cmd_stderr, rc) = self.runner.execute(cmd)
if cmd_stdout:
stdout.append(cmd_stdout)
if cmd_stderr:
stderr.append(cmd_stderr)
if rc != 0:
returncode = rc
LOG.error("Error running %s. [%s]\n" % (cmd, returncode))
LOG.error("stdout: %s" % cmd_stdout)
LOG.error("stderr: %s" % cmd_stderr)
else:
LOG.debug('Completed $ %s' % ' '.join(cmd))
LOG.info("stdout: %s" % cmd_stdout)
LOG.info("stderr: %s" % cmd_stderr)
return returncode
@staticmethod
def command_argument(command):
if not command:

View File

@ -132,12 +132,12 @@ class DockerRunner(object):
LOG.error('Error renaming container: %s' % container)
LOG.error(cmd_stderr)
def inspect(self, container, format=None):
cmd = [self.docker_cmd, 'inspect']
def inspect(self, name, format=None, type='container'):
cmd = [self.docker_cmd, 'inspect', '--type', type]
if format:
cmd.append('--format')
cmd.append(format)
cmd.append(container)
cmd.append(name)
(cmd_stdout, cmd_stderr, returncode) = self.execute(cmd)
if returncode != 0:
return

View File

@ -35,7 +35,7 @@ class TestComposeV1Builder(base.TestCase):
},
'three': {
'start_order': 2,
'image': 'centos:7',
'image': 'centos:6',
},
'four': {
'start_order': 10,
@ -51,6 +51,9 @@ class TestComposeV1Builder(base.TestCase):
r = runner.DockerRunner(managed_by='tester', docker_cmd='docker')
exe = mock.Mock()
exe.side_effect = [
('exists', '', 0), # inspect for image centos:6
('', '', 1), # inspect for missing image centos:7
('Pulled centos:7', '', 0), # pull centos:6
('', '', 0), # ps for delete_missing_and_updated container_names
('', '', 0), # ps for after delete_missing_and_updated renames
('', '', 0), # ps to only create containers which don't exist
@ -68,6 +71,7 @@ class TestComposeV1Builder(base.TestCase):
stdout, stderr, deploy_status_code = builder.apply()
self.assertEqual(0, deploy_status_code)
self.assertEqual([
'Pulled centos:7',
'Created one-12345678',
'Created two-12345678',
'Created three-12345678',
@ -77,6 +81,19 @@ class TestComposeV1Builder(base.TestCase):
self.assertEqual([], stderr)
exe.assert_has_calls([
# inspect existing image centos:6
mock.call(
['docker', 'inspect', '--type', 'image',
'--format', 'exists', 'centos:6']
),
# inspect and pull missing image centos:7
mock.call(
['docker', 'inspect', '--type', 'image',
'--format', 'exists', 'centos:7']
),
mock.call(
['docker', 'pull', 'centos:7']
),
# ps for delete_missing_and_updated container_names
mock.call(
['docker', 'ps', '-a',
@ -122,7 +139,7 @@ class TestComposeV1Builder(base.TestCase):
'--label', 'container_name=three',
'--label', 'managed_by=tester',
'--label', 'config_data=%s' % json.dumps(config['three']),
'--detach=true', 'centos:7']
'--detach=true', 'centos:6']
),
# run four
mock.call(
@ -171,6 +188,8 @@ class TestComposeV1Builder(base.TestCase):
r = runner.DockerRunner(managed_by='tester', docker_cmd='docker')
exe = mock.Mock()
exe.side_effect = [
# inspect for image centos:7
('exists', '', 0),
# ps for delete_missing_and_updated container_names
('''five five
six six
@ -211,6 +230,11 @@ three-12345678 three''', '', 0),
self.assertEqual([], stderr)
exe.assert_has_calls([
# inspect image centos:7
mock.call(
['docker', 'inspect', '--type', 'image',
'--format', 'exists', 'centos:7']
),
# ps for delete_missing_and_updated container_names
mock.call(
['docker', 'ps', '-a',
@ -222,13 +246,13 @@ three-12345678 three''', '', 0),
mock.call(['docker', 'rm', '-f', 'five']),
mock.call(['docker', 'rm', '-f', 'six']),
# rm two, changed config
mock.call(['docker', 'inspect', '--format',
'{{index .Config.Labels "config_data"}}',
mock.call(['docker', 'inspect', '--type', 'container',
'--format', '{{index .Config.Labels "config_data"}}',
'two-12345678']),
mock.call(['docker', 'rm', '-f', 'two-12345678']),
# check three, config hasn't changed
mock.call(['docker', 'inspect', '--format',
'{{index .Config.Labels "config_data"}}',
mock.call(['docker', 'inspect', '--type', 'container',
'--format', '{{index .Config.Labels "config_data"}}',
'three-12345678']),
# ps for after delete_missing_and_updated renames
mock.call(
@ -277,6 +301,62 @@ three-12345678 three''', '', 0),
),
])
def test_apply_failed_pull(self):
config = {
'one': {
'start_order': 0,
'image': 'centos:7',
},
'two': {
'start_order': 1,
'image': 'centos:7',
},
'three': {
'start_order': 2,
'image': 'centos:6',
},
'four': {
'start_order': 10,
'image': 'centos:7',
},
'four_ls': {
'action': 'exec',
'start_order': 20,
'command': ['four', 'ls', '-l', '/']
}
}
r = runner.DockerRunner(managed_by='tester', docker_cmd='docker')
exe = mock.Mock()
exe.side_effect = [
('exists', '', 0), # inspect for image centos:6
('', '', 1), # inspect for missing image centos:7
('Pulling centos:7', 'ouch', 1), # pull centos:7 failure
]
r.execute = exe
builder = compose1.ComposeV1Builder('foo', config, r)
stdout, stderr, deploy_status_code = builder.apply()
self.assertEqual(1, deploy_status_code)
self.assertEqual(['Pulling centos:7'], stdout)
self.assertEqual(['ouch'], stderr)
exe.assert_has_calls([
# inspect existing image centos:6
mock.call(
['docker', 'inspect', '--type', 'image',
'--format', 'exists', 'centos:6']
),
# inspect and pull missing image centos:7
mock.call(
['docker', 'inspect', '--type', 'image',
'--format', 'exists', 'centos:7']
),
mock.call(
['docker', 'pull', 'centos:7']
),
])
@mock.patch('paunch.runner.DockerRunner', autospec=True)
def test_label_arguments(self, runner):
r = runner.return_value

View File

@ -179,7 +179,7 @@ four-12345678 four
self.runner.inspect('one')
)
self.assert_execute(
popen, ['docker', 'inspect', 'one']
popen, ['docker', 'inspect', '--type', 'container', 'one']
)
@mock.patch('subprocess.Popen')
@ -191,7 +191,8 @@ four-12345678 four
self.runner.inspect('one', format='{{foo}}')
)
self.assert_execute(
popen, ['docker', 'inspect', '--format', '{{foo}}', 'one']
popen, ['docker', 'inspect', '--type', 'container',
'--format', '{{foo}}', 'one']
)
def test_unique_container_name(self):

View File

@ -0,0 +1,6 @@
---
features:
- All referenced images are now pulled as the first step of applying a
config. Any pull failures will result in failing with an error before any of
the config is applied. This is especially helpful for detached containers,
where pull failures will not be captured during the apply.