Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions bodhi-server/bodhi/server/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,8 +337,8 @@ class BodhiConfig(dict):
'value': True,
'validator': _validate_bool},
'container.destination_registry': {
'value': 'registry.fedoraproject.org',
'validator': str},
'value': ['registry.fedoraproject.org'],
'validator': _generate_list_validator()},
'container.source_registry': {
'value': 'candidate-registry.fedoraproject.org',
'validator': str},
Expand Down
32 changes: 19 additions & 13 deletions bodhi-server/bodhi/server/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -1199,23 +1199,29 @@ def copy_container(build, destination_registry=None, destination_tag=None):
"""
source_registry = config['container.source_registry']

repository, digest = _get_build_repository_and_digest(build)
source_url = _container_image_url(source_registry, repository, digest=digest)

if destination_tag is None:
destination_tag = '{}-{}'.format(build.nvr_version, build.nvr_release)
if destination_registry is None:
destination_registry = config['container.destination_registry']

repository, digest = _get_build_repository_and_digest(build)
destination_registry_list = []
if destination_registry:
destination_registry_list.append(destination_registry)
else:
destination_registry_list = config['container.destination_registry']

source_url = _container_image_url(source_registry, repository, digest=digest)
destination_url = _container_image_url(destination_registry, repository,
tag=make_valid_container_tag(destination_tag))

skopeo_cmd = [
config.get('skopeo.cmd'), 'copy', source_url, destination_url]
if config.get('skopeo.extra_copy_flags'):
for flag in reversed(config.get('skopeo.extra_copy_flags').split(',')):
skopeo_cmd.insert(2, flag)
cmd(skopeo_cmd, raise_on_error=True)
for destination_registry in destination_registry_list:

destination_url = _container_image_url(
destination_registry, repository, tag=make_valid_container_tag(destination_tag))

skopeo_cmd = [
config.get('skopeo.cmd'), 'copy', source_url, destination_url]
if config.get('skopeo.extra_copy_flags'):
for flag in reversed(config.get('skopeo.extra_copy_flags').split(',')):
skopeo_cmd.insert(2, flag)
cmd(skopeo_cmd, raise_on_error=True)


def _container_image_url(registry, repository, *, tag=None, digest=None):
Expand Down
41 changes: 23 additions & 18 deletions bodhi-server/tests/tasks/test_composer.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,21 +81,21 @@
mock_exc.side_effect = Exception


def _expected_skopeo_args(config, source, dtag, extra_args=[]):
def _expected_skopeo_args(config, source, destination, dtag, extra_args=[]):
source_repository, source_version_release = source.split(':')
# Match how buildsys.py creates a fake digest
source_digest = hashlib.sha256(source_version_release.encode("UTF-8")).hexdigest()

return [config['skopeo.cmd'], 'copy'] + extra_args + [
'docker://{}/{}@sha256:{}'.format(config['container.source_registry'],
source_repository, source_digest),
'docker://{}/{}:{}'.format(config['container.destination_registry'],
source_repository, dtag)
'docker://{}/{}:{}'.format(destination, source_repository, dtag)
]


def _expected_skopeo_call(config, source, dtag, extra_args=[]):
return mock.call(_expected_skopeo_args(config, source, dtag, extra_args=extra_args),
def _expected_skopeo_call(config, source, destination, dtag, extra_args=[]):
return mock.call(_expected_skopeo_args(config, source, destination, dtag,
extra_args=extra_args),
shell=False, stderr=-1, stdout=-1, cwd=None)


Expand Down Expand Up @@ -2252,9 +2252,10 @@ def test_request_not_stable(self, Popen):
for source in ('f28/testcontainer1:2.0.1-71.fc28container',
'f28/testcontainer2:1.0.1-1.fc28container'):
for dtag in [source.split(':')[1], source.split(':')[1].split('-')[0], 'testing']:
mock_call = _expected_skopeo_call(config, source, dtag)
expected_mock_calls.append(mock_call)
expected_mock_calls.append(mock.call().communicate())
for destination in config['container.destination_registry']:
mock_call = _expected_skopeo_call(config, source, destination, dtag)
expected_mock_calls.append(mock_call)
expected_mock_calls.append(mock.call().communicate())
assert Popen.mock_calls == expected_mock_calls

@mock.patch('bodhi.server.tasks.composer.subprocess.Popen')
Expand All @@ -2276,9 +2277,10 @@ def test_request_stable(self, Popen):
for source in ('f28/testcontainer1:2.0.1-71.fc28container',
'f28/testcontainer2:1.0.1-1.fc28container'):
for dtag in [source.split(':')[1], source.split(':')[1].split('-')[0], 'latest']:
mock_call = _expected_skopeo_call(config, source, dtag)
expected_mock_calls.append(mock_call)
expected_mock_calls.append(mock.call().communicate())
for destination in config['container.destination_registry']:
mock_call = _expected_skopeo_call(config, source, destination, dtag)
expected_mock_calls.append(mock_call)
expected_mock_calls.append(mock.call().communicate())
assert Popen.mock_calls == expected_mock_calls

@mock.patch('bodhi.server.tasks.composer.subprocess.Popen')
Expand All @@ -2298,6 +2300,7 @@ def test_skopeo_error_code(self, Popen):
# Popen should have been called once.
skopeo_cmd = _expected_skopeo_args(config,
"f28/testcontainer1:2.0.1-71.fc28container",
", ".join(config['container.destination_registry']),
"2.0.1-71.fc28container")
Popen.assert_called_once_with(skopeo_cmd, shell=False, stderr=-1, stdout=-1, cwd=None)
assert f"{' '.join(skopeo_cmd)} returned a non-0 exit code: 1" in str(exc.value)
Expand All @@ -2321,10 +2324,11 @@ def test_skopeo_extra_copy_flags(self, Popen):
for source in ('f28/testcontainer1:2.0.1-71.fc28container',
'f28/testcontainer2:1.0.1-1.fc28container'):
for dtag in [source.split(':')[1], source.split(':')[1].split('-')[0], 'testing']:
mock_call = _expected_skopeo_call(config, source, dtag,
extra_args=['--dest-tls-verify=false'])
expected_mock_calls.append(mock_call)
expected_mock_calls.append(mock.call().communicate())
for destination in config['container.destination_registry']:
mock_call = _expected_skopeo_call(config, source, destination, dtag,
extra_args=['--dest-tls-verify=false'])
expected_mock_calls.append(mock_call)
expected_mock_calls.append(mock.call().communicate())
assert Popen.mock_calls == expected_mock_calls


Expand Down Expand Up @@ -2408,9 +2412,10 @@ def test_flatpak_compose(self, Popen):
expected_mock_calls = []
for source in ('testflatpak1:2.0.1-71.fc28flatpak', 'testflatpak2:1.0.1-1.fc28flatpak'):
for dtag in [source.split(':')[1], source.split(':')[1].split('-')[0], 'testing']:
mock_call = _expected_skopeo_call(config, source, dtag)
expected_mock_calls.append(mock_call)
expected_mock_calls.append(mock.call().communicate())
for destination in config['container.destination_registry']:
mock_call = _expected_skopeo_call(config, source, destination, dtag)
expected_mock_calls.append(mock_call)
expected_mock_calls.append(mock.call().communicate())
assert Popen.mock_calls == expected_mock_calls


Expand Down
42 changes: 32 additions & 10 deletions bodhi-server/tests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -1381,17 +1381,23 @@ def setup_method(self, method):
self.build.nvr_release = '1'
config.update({
'container.source_registry': 'src',
'container.destination_registry': 'dest',
'container.destination_registry': ['dest1', 'dest2'],
'skopeo.cmd': 'skopeo',
})

def test_default(self, cmd):
"""Test the default code path."""
util.copy_container(self.build)

cmd.assert_called_once_with(['skopeo', 'copy',
'src:testrepo@sha256:f00d00', 'dest:testrepo:1-1'],
raise_on_error=True)
assert mock.call(
['skopeo', 'copy', 'src:testrepo@sha256:f00d00', 'dest1:testrepo:1-1'],
raise_on_error=True
) in cmd.mock_calls

assert mock.call(
['skopeo', 'copy', 'src:testrepo@sha256:f00d00', 'dest2:testrepo:1-1'],
raise_on_error=True
) in cmd.mock_calls

def test_with_destination_registry(self, cmd):
"""Test with specified destination_registry."""
Expand All @@ -1405,18 +1411,34 @@ def test_with_destination_tag(self, cmd):
"""Test with specified destination_tag."""
util.copy_container(self.build, destination_tag='2-2')

cmd.assert_called_once_with(['skopeo', 'copy',
'src:testrepo@sha256:f00d00', 'dest:testrepo:2-2'],
raise_on_error=True)
assert mock.call(
['skopeo', 'copy', 'src:testrepo@sha256:f00d00', 'dest1:testrepo:2-2'],
raise_on_error=True
) in cmd.mock_calls
assert mock.call(
['skopeo', 'copy', 'src:testrepo@sha256:f00d00', 'dest2:testrepo:2-2'],
raise_on_error=True
) in cmd.mock_calls

def test_with_extra_copy_flags(self, cmd):
"""Test with extra copy flags configured."""
config['skopeo.extra_copy_flags'] = '--quiet,--remove-signatures'
util.copy_container(self.build)

cmd.assert_called_once_with(['skopeo', 'copy', '--quiet', '--remove-signatures',
'src:testrepo@sha256:f00d00', 'dest:testrepo:1-1'],
raise_on_error=True)
assert mock.call(
[
'skopeo', 'copy', '--quiet', '--remove-signatures',
'src:testrepo@sha256:f00d00', 'dest1:testrepo:1-1'
],
raise_on_error=True
) in cmd.mock_calls
assert mock.call(
[
'skopeo', 'copy', '--quiet', '--remove-signatures',
'src:testrepo@sha256:f00d00', 'dest2:testrepo:1-1'
],
raise_on_error=True
) in cmd.mock_calls


class TestTransactionalSessionMaker(base.BasePyTestCase):
Expand Down
2 changes: 1 addition & 1 deletion bodhi-server/tests/testing.ini
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ cache.second.expire = 1
cache.short_term.expire = 60
cache.default_term.expire = 300
cache.long_term.expire = 3600
container.destination_registry = localhost:5000
container.destination_registry = [localhost:5000]
# skopeo.extra_copy_flags = --dest-tls-verify=false
openid.provider = https://id.stg.fedoraproject.org/openid/
openid.url = https://id.stg.fedoraproject.org/
Expand Down
3 changes: 3 additions & 0 deletions news/PR6077.bic
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Support multiple destination registries for containers

`container.destination_registry` config value can now be specified as list
Loading