Trigger container update on image id update
NOTE: Adapted to queens due to missing backports in rocky.
If you reuse a container tag for a new image, paunch does not currently
rebuild the container to get the new image. This change adds logic to
the updated check to also check the image id defined in the container
instance against the image id for the image to see if the container
image has changed.
Closes-Bug: #1895974
Change-Id: I2710497282eb4d6fa8f68dc6d50044c1ce68eb35
(cherry picked from commit 4ae7d886db
)
This commit is contained in:
parent
e13226d4cf
commit
be9f6a3c46
|
@ -120,25 +120,48 @@ class ComposeV1Builder(object):
|
||||||
list(itertools.chain.from_iterable(container_names))):
|
list(itertools.chain.from_iterable(container_names))):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
ex_data_str = self.runner.inspect(
|
# fetch container inspect info
|
||||||
container, '{{index .Config.Labels "config_data"}}')
|
inspect_info = self.runner.inspect(container)
|
||||||
if not ex_data_str:
|
if not inspect_info:
|
||||||
|
# we shouldn't get here but you never know
|
||||||
|
self.log.debug("Deleting container (no inspect data): "
|
||||||
|
"%s" % container)
|
||||||
|
self.runner.remove_container(container)
|
||||||
|
return True
|
||||||
|
container_config = inspect_info.get('Config', {})
|
||||||
|
config_data = container_config.get('Labels', {}).get('config_data')
|
||||||
|
try:
|
||||||
|
ex_data = yaml.safe_load(str(config_data))
|
||||||
|
except Exception:
|
||||||
|
ex_data = None
|
||||||
|
|
||||||
|
if not ex_data:
|
||||||
self.log.debug("Deleting container (no_config_data): "
|
self.log.debug("Deleting container (no_config_data): "
|
||||||
"%s" % container)
|
"%s" % container)
|
||||||
self.runner.remove_container(container)
|
self.runner.remove_container(container)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
try:
|
# check if config_data has changed
|
||||||
ex_data = yaml.safe_load(str(ex_data_str))
|
|
||||||
except Exception:
|
|
||||||
ex_data = None
|
|
||||||
|
|
||||||
new_data = self.config[container]
|
new_data = self.config[container]
|
||||||
if new_data != ex_data:
|
if new_data != ex_data:
|
||||||
self.log.debug("Deleting container (changed config_data): %s"
|
self.log.debug("Deleting container (changed config_data): %s"
|
||||||
% container)
|
% container)
|
||||||
self.runner.remove_container(container)
|
self.runner.remove_container(container)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
# check if the container image has changed (but tag as not)
|
||||||
|
# e.g. if you use :latest, the name doesn't change but the ID does
|
||||||
|
container_image = container_config.get('Image')
|
||||||
|
|
||||||
|
if container_image:
|
||||||
|
image_id_str = self.runner.inspect(
|
||||||
|
container_image, "{{index .Id}}", type='image')
|
||||||
|
if str(image_id_str).strip() != inspect_info.get('Image'):
|
||||||
|
self.log.debug("Deleting container (image updated): "
|
||||||
|
"%s" % container)
|
||||||
|
self.runner.remove_container(container)
|
||||||
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def label_arguments(self, cmd, container):
|
def label_arguments(self, cmd, container):
|
||||||
|
|
|
@ -243,10 +243,14 @@ three-12345678 three''', '', 0),
|
||||||
('', '', 0),
|
('', '', 0),
|
||||||
('', '', 0), # ps for rename one
|
('', '', 0), # ps for rename one
|
||||||
# inspect one
|
# inspect one
|
||||||
('{"start_order": 0, "image": "centos:7"}', '', 0),
|
('[{"Config": {"Labels": {"config_data": '
|
||||||
|
'"{\\\"start_order\\\": 0, \\\"image\\\": \\\"centos:7\\\"}"'
|
||||||
|
'}}}]', '', 0),
|
||||||
('Created two-12345678', '', 0),
|
('Created two-12345678', '', 0),
|
||||||
# inspect three
|
# inspect three
|
||||||
('{"start_order": 42, "image": "centos:7"}', '', 0),
|
('[{"Config": {"Labels": {"config_data": '
|
||||||
|
'"{\\\"start_order\\\": 42, \\\"image\\\": \\\"centos:7\\\"}"'
|
||||||
|
'}}}]', '', 0),
|
||||||
# stop three, changed config data
|
# stop three, changed config data
|
||||||
('', '', 0),
|
('', '', 0),
|
||||||
# rm three, changed config data
|
# rm three, changed config data
|
||||||
|
@ -284,7 +288,6 @@ three-12345678 three''', '', 0),
|
||||||
mock.ANY),
|
mock.ANY),
|
||||||
# check the renamed one, config hasn't changed
|
# check the renamed one, config hasn't changed
|
||||||
mock.call(['docker', 'inspect', '--type', 'container',
|
mock.call(['docker', 'inspect', '--type', 'container',
|
||||||
'--format', '{{index .Config.Labels "config_data"}}',
|
|
||||||
'one'], mock.ANY),
|
'one'], mock.ANY),
|
||||||
# don't run one, its already running
|
# don't run one, its already running
|
||||||
# run two
|
# run two
|
||||||
|
@ -299,7 +302,6 @@ three-12345678 three''', '', 0),
|
||||||
),
|
),
|
||||||
# rm three, changed config
|
# rm three, changed config
|
||||||
mock.call(['docker', 'inspect', '--type', 'container',
|
mock.call(['docker', 'inspect', '--type', 'container',
|
||||||
'--format', '{{index .Config.Labels "config_data"}}',
|
|
||||||
'three'], mock.ANY),
|
'three'], mock.ANY),
|
||||||
mock.call(['docker', 'stop', 'three'], mock.ANY),
|
mock.call(['docker', 'stop', 'three'], mock.ANY),
|
||||||
mock.call(['docker', 'rm', 'three'], mock.ANY),
|
mock.call(['docker', 'rm', 'three'], mock.ANY),
|
||||||
|
@ -636,3 +638,191 @@ three-12345678 three''', '', 0),
|
||||||
'--volume=/bar:/bar:ro', '--cpuset-cpus=0,1,2,3', 'foo'],
|
'--volume=/bar:/bar:ro', '--cpuset-cpus=0,1,2,3', 'foo'],
|
||||||
cmd
|
cmd
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@mock.patch('paunch.runner.DockerRunner', autospec=True)
|
||||||
|
def test_delete_updated_no_change(self, runner):
|
||||||
|
mock_inspect = mock.MagicMock()
|
||||||
|
mock_inspect.side_effect = [
|
||||||
|
{
|
||||||
|
"Id": ("d038dccebdb0996ed36ab4ff06e7c424b3816d67664aa11e00642"
|
||||||
|
"be5e00cec55"),
|
||||||
|
"Config": {
|
||||||
|
"Labels": {
|
||||||
|
"config_data": """{
|
||||||
|
\"start_order\": 0,
|
||||||
|
\"image": \"centos:7\"
|
||||||
|
}"""
|
||||||
|
},
|
||||||
|
"Image": "127.0.0.1:8787/centos:7"
|
||||||
|
},
|
||||||
|
"Image": "sha256:1"
|
||||||
|
},
|
||||||
|
"sha256:1"
|
||||||
|
]
|
||||||
|
runner.inspect = mock_inspect
|
||||||
|
mock_remove = mock.MagicMock()
|
||||||
|
runner.remove_container = mock_remove
|
||||||
|
|
||||||
|
config = {
|
||||||
|
'one': {
|
||||||
|
'start_order': 0,
|
||||||
|
'image': 'centos:7',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.builder = compose1.ComposeV1Builder(
|
||||||
|
'one', config, runner.return_value)
|
||||||
|
|
||||||
|
self.builder.runner = runner
|
||||||
|
self.assertFalse(self.builder.delete_updated('one', [['one']]))
|
||||||
|
calls = [
|
||||||
|
mock.call('one'),
|
||||||
|
mock.call('127.0.0.1:8787/centos:7',
|
||||||
|
'{{index .Id}}',
|
||||||
|
type='image')
|
||||||
|
]
|
||||||
|
mock_inspect.has_calls(calls)
|
||||||
|
mock_remove.assert_not_called()
|
||||||
|
|
||||||
|
@mock.patch('paunch.runner.DockerRunner', autospec=True)
|
||||||
|
def test_delete_updated_inspect_empty(self, runner):
|
||||||
|
mock_inspect = mock.MagicMock()
|
||||||
|
mock_inspect.return_value = None
|
||||||
|
runner.inspect = mock_inspect
|
||||||
|
mock_remove = mock.MagicMock()
|
||||||
|
runner.remove_container = mock_remove
|
||||||
|
|
||||||
|
config = {
|
||||||
|
'one': {
|
||||||
|
'start_order': 0,
|
||||||
|
'image': 'centos:7',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.builder = compose1.ComposeV1Builder(
|
||||||
|
'one', config, runner.return_value)
|
||||||
|
|
||||||
|
self.builder.runner = runner
|
||||||
|
self.assertTrue(self.builder.delete_updated('one', [['one']]))
|
||||||
|
mock_inspect.assert_called_once_with('one')
|
||||||
|
mock_remove.assert_called_once_with('one')
|
||||||
|
|
||||||
|
@mock.patch('paunch.runner.DockerRunner', autospec=True)
|
||||||
|
def test_delete_updated_no_config_data(self, runner):
|
||||||
|
mock_inspect = mock.MagicMock()
|
||||||
|
mock_inspect.side_effect = [
|
||||||
|
{
|
||||||
|
"Id": ("d038dccebdb0996ed36ab4ff06e7c424b3816d67664aa11e00642"
|
||||||
|
"be5e00cec55"),
|
||||||
|
"Config": {
|
||||||
|
"Labels": {},
|
||||||
|
"Image": "127.0.0.1:8787/centos:7"
|
||||||
|
},
|
||||||
|
"Image": "sha256:1"
|
||||||
|
},
|
||||||
|
"sha256:1"
|
||||||
|
]
|
||||||
|
runner.inspect = mock_inspect
|
||||||
|
mock_remove = mock.MagicMock()
|
||||||
|
runner.remove_container = mock_remove
|
||||||
|
|
||||||
|
config = {
|
||||||
|
'one': {
|
||||||
|
'start_order': 0,
|
||||||
|
'image': 'centos:7',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.builder = compose1.ComposeV1Builder(
|
||||||
|
'one', config, runner.return_value)
|
||||||
|
|
||||||
|
self.builder.runner = runner
|
||||||
|
self.assertTrue(self.builder.delete_updated('one', [['one']]))
|
||||||
|
mock_inspect.assert_called_once_with('one')
|
||||||
|
mock_remove.assert_called_once_with('one')
|
||||||
|
|
||||||
|
@mock.patch('paunch.runner.DockerRunner', autospec=True)
|
||||||
|
def test_delete_updated_update_config(self, runner):
|
||||||
|
mock_inspect = mock.MagicMock()
|
||||||
|
mock_inspect.side_effect = [
|
||||||
|
{
|
||||||
|
"Id": ("d038dccebdb0996ed36ab4ff06e7c424b3816d67664aa11e00642"
|
||||||
|
"be5e00cec55"),
|
||||||
|
"Config": {
|
||||||
|
"Labels": {
|
||||||
|
"config_data": """{
|
||||||
|
\"start_order\": 1,
|
||||||
|
\"image": \"centos:7\"
|
||||||
|
}"""
|
||||||
|
},
|
||||||
|
"Image": "127.0.0.1:8787/centos:7"
|
||||||
|
},
|
||||||
|
"Image": "sha256:1"
|
||||||
|
},
|
||||||
|
"sha256:1"
|
||||||
|
]
|
||||||
|
|
||||||
|
runner.inspect = mock_inspect
|
||||||
|
mock_remove = mock.MagicMock()
|
||||||
|
runner.remove_container = mock_remove
|
||||||
|
|
||||||
|
config = {
|
||||||
|
'one': {
|
||||||
|
'start_order': 0,
|
||||||
|
'image': 'centos:7',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.builder = compose1.ComposeV1Builder(
|
||||||
|
'one', config, runner.return_value)
|
||||||
|
|
||||||
|
self.builder.runner = runner
|
||||||
|
self.assertTrue(self.builder.delete_updated('one', [['one']]))
|
||||||
|
mock_inspect.assert_called_once_with('one')
|
||||||
|
mock_remove.assert_called_once_with('one')
|
||||||
|
|
||||||
|
@mock.patch('paunch.runner.DockerRunner', autospec=True)
|
||||||
|
def test_delete_updated_update_image(self, runner):
|
||||||
|
mock_inspect = mock.MagicMock()
|
||||||
|
mock_inspect.side_effect = [
|
||||||
|
{
|
||||||
|
"Id": ("d038dccebdb0996ed36ab4ff06e7c424b3816d67664aa11e00642"
|
||||||
|
"be5e00cec55"),
|
||||||
|
"Config": {
|
||||||
|
"Labels": {
|
||||||
|
"config_data": """{
|
||||||
|
\"start_order\": 0,
|
||||||
|
\"image": \"centos:7\"
|
||||||
|
}"""
|
||||||
|
},
|
||||||
|
"Image": "127.0.0.1:8787/centos:7"
|
||||||
|
},
|
||||||
|
"Image": "sha256:1"
|
||||||
|
},
|
||||||
|
"sha256:2"
|
||||||
|
]
|
||||||
|
|
||||||
|
runner.inspect = mock_inspect
|
||||||
|
mock_remove = mock.MagicMock()
|
||||||
|
runner.remove_container = mock_remove
|
||||||
|
|
||||||
|
config = {
|
||||||
|
'one': {
|
||||||
|
'start_order': 0,
|
||||||
|
'image': 'centos:7',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.builder = compose1.ComposeV1Builder(
|
||||||
|
'one', config, runner.return_value)
|
||||||
|
|
||||||
|
self.builder.runner = runner
|
||||||
|
self.assertTrue(self.builder.delete_updated('one', [['one']]))
|
||||||
|
calls = [
|
||||||
|
mock.call('one'),
|
||||||
|
mock.call('127.0.0.1:8787/centos:7',
|
||||||
|
'{{index .Id}}',
|
||||||
|
type='image')
|
||||||
|
]
|
||||||
|
mock_inspect.has_calls(calls)
|
||||||
|
mock_remove.assert_called_once_with('one')
|
||||||
|
|
Loading…
Reference in New Issue