Always convert to docker media format

Previously if the container config mediaType was unset or set to
something other than a docker format, we would just pass that on. This
caused problem with stricter checking of the metadata performed by some
versions of Podman. Since we're always converting the layer metadata
information to docker, we should ensure that the config media type is
properly set to a valid docker distribution format. This change ensure
that when we push the image config, that the mediaType is set to match
the contents type.

Change-Id: I6b6a8231f6881fdb53084a437ee24fd852ebdd55
Closes-Bug: #186585
(cherry picked from commit e7d1e59283)
This commit is contained in:
Alex Schultz 2020-01-27 09:41:36 -07:00
parent 467f89e3c5
commit 82ebd0680c
2 changed files with 180 additions and 2 deletions

View File

@ -82,6 +82,8 @@ MEDIA_TYPES = (
MEDIA_MANIFEST_V1_SIGNED,
MEDIA_MANIFEST_V2,
MEDIA_MANIFEST_V2_LIST,
MEDIA_OCI_MANIFEST_V1,
MEDIA_OCI_INDEX_V1,
MEDIA_CONFIG,
MEDIA_BLOB,
MEDIA_BLOB_COMPRESSED
@ -90,6 +92,8 @@ MEDIA_TYPES = (
'application/vnd.docker.distribution.manifest.v1+prettyjws',
'application/vnd.docker.distribution.manifest.v2+json',
'application/vnd.docker.distribution.manifest.list.v2+json',
'application/vnd.oci.image.manifest.v1+json',
'application/vnd.oci.image.index.v1+json',
'application/vnd.docker.container.image.v1+json',
'application/vnd.docker.image.rootfs.diff.tar',
'application/vnd.docker.image.rootfs.diff.tar.gzip'
@ -1753,8 +1757,16 @@ class PythonImageUploader(BaseImageUploader):
else:
manifest_type = MEDIA_MANIFEST_V1
else:
manifest_type = manifest.get(
'mediaType', MEDIA_MANIFEST_V2)
# NOTE(mwhahaha): always force docker media format if not set or
# is explicitly OCI because buildah uses OCI by default but we
# convert the metadata to Docker format in the uploader.
# See LP#1860585
manifest_type = manifest.get('mediaType', False)
if not manifest_type or manifest_type == MEDIA_OCI_MANIFEST_V1:
manifest_type = MEDIA_MANIFEST_V2
elif manifest_type == MEDIA_OCI_INDEX_V1:
manifest_type = MEDIA_MANIFEST_V2_LIST
manifest['mediaType'] = manifest_type
manifest_str = json.dumps(manifest, indent=3)
export = target_url.netloc in cls.export_registries

View File

@ -2034,6 +2034,172 @@ class TestPythonImageUploader(base.TestCase):
)
self.assertEqual(target_manifest, put_manifest)
@mock.patch('tripleo_common.image.image_uploader.'
'BaseImageUploader.check_status')
@mock.patch('tripleo_common.image.image_uploader.'
'BaseImageUploader._build_url')
@mock.patch('tripleo_common.image.image_uploader.'
'BaseImageUploader._image_tag_from_url')
def test_copy_manifest_config_to_registry(self, image_tag_mock,
build_url_mock, status_mock):
target_url = urlparse('docker://192.168.2.1:5000/t/nova-api:latest')
image_tag_mock.return_value = ('t/nova-api', 'latest')
build_url = 'https://192.168.2.1:5000/v2/t/nova-api'
build_url_mock.return_value = build_url
target_session = mock.Mock()
target_put = mock.Mock()
target_put.return_value.text = '{}'
target_session.put = target_put
config_str = None
manifest_str = json.dumps({
'config': {
'digest': 'sha256:1234',
'size': 2,
'mediaType': image_uploader.MEDIA_CONFIG
},
'layers': [
{'digest': 'sha256:aaaa'},
{'digest': 'sha256:bbbb'},
],
'mediaType': image_uploader.MEDIA_MANIFEST_V2
})
expected_manifest_str = json.dumps({
'config': {
'digest': 'sha256:1234',
'size': 2,
'mediaType': image_uploader.MEDIA_CONFIG
},
'layers': [
{'digest': 'sha256:aaaa'},
{'digest': 'sha256:bbbb'},
],
'mediaType': image_uploader.MEDIA_MANIFEST_V2
}, indent=3)
expected_headers = {
'Content-Type': image_uploader.MEDIA_MANIFEST_V2
}
self.uploader._copy_manifest_config_to_registry(
target_url, manifest_str, config_str,
target_session=target_session
)
calls = [mock.call(build_url,
data=expected_manifest_str.encode('utf-8'),
headers=expected_headers,
timeout=30)]
target_put.assert_has_calls(calls)
@mock.patch('tripleo_common.image.image_export.export_manifest_config')
def test_copy_manifest_config_to_registry_export(self, export_mock):
target_url = urlparse('docker://192.168.2.1:5000/t/nova-api:latest')
self.uploader.export_registries.add('192.168.2.1:5000')
target_session = mock.Mock()
config_str = None
manifest_str = json.dumps({
'config': {
'digest': 'sha256:1234',
'size': 2,
'mediaType': image_uploader.MEDIA_CONFIG
},
'layers': [
{'digest': 'sha256:aaaa'},
{'digest': 'sha256:bbbb'},
],
})
expected_manifest_str = json.dumps({
'config': {
'digest': 'sha256:1234',
'size': 2,
'mediaType': image_uploader.MEDIA_CONFIG
},
'layers': [
{'digest': 'sha256:aaaa'},
{'digest': 'sha256:bbbb'},
],
'mediaType': image_uploader.MEDIA_MANIFEST_V2
}, indent=3)
self.uploader._copy_manifest_config_to_registry(
target_url, manifest_str, config_str,
target_session=target_session
)
calls = [mock.call(target_url,
expected_manifest_str,
image_uploader.MEDIA_MANIFEST_V2,
config_str,
multi_arch=False)]
export_mock.assert_has_calls(calls)
@mock.patch('tripleo_common.image.image_uploader.'
'BaseImageUploader.check_status')
@mock.patch('tripleo_common.image.image_uploader.'
'BaseImageUploader._build_url')
@mock.patch('tripleo_common.image.image_uploader.'
'BaseImageUploader._image_tag_from_url')
def test_copy_manifest_config_to_registry_oci(self, image_tag_mock,
build_url_mock, status_mock):
target_url = urlparse('docker://192.168.2.1:5000/t/nova-api:latest')
image_tag_mock.return_value = ('t/nova-api', 'latest')
build_url = 'https://192.168.2.1:5000/v2/t/nova-api'
build_url_mock.return_value = build_url
target_session = mock.Mock()
target_put = mock.Mock()
target_put.return_value.text = '{}'
target_session.put = target_put
config_str = None
manifest_str = json.dumps({
'config': {
'digest': 'sha256:1234',
'size': 2,
'mediaType': image_uploader.MEDIA_CONFIG
},
'layers': [
{'digest': 'sha256:aaaa'},
{'digest': 'sha256:bbbb'},
],
'mediaType': image_uploader.MEDIA_OCI_MANIFEST_V1
})
expected_manifest_str = json.dumps({
'config': {
'digest': 'sha256:1234',
'size': 2,
'mediaType': image_uploader.MEDIA_CONFIG
},
'layers': [
{'digest': 'sha256:aaaa'},
{'digest': 'sha256:bbbb'},
],
'mediaType': image_uploader.MEDIA_MANIFEST_V2
}, indent=3)
expected_headers = {
'Content-Type': image_uploader.MEDIA_MANIFEST_V2
}
self.uploader._copy_manifest_config_to_registry(
target_url, manifest_str, config_str,
target_session=target_session
)
calls = [mock.call(build_url,
data=expected_manifest_str.encode('utf-8'),
headers=expected_headers,
timeout=30)]
target_put.assert_has_calls(calls)
@mock.patch('os.environ')
@mock.patch('subprocess.Popen')
def test_copy_registry_to_local(self, mock_popen, mock_environ):