Fix support for v1 manifests

v1 manifests store the layer digests in a different way, which the
prepare code has conditional logic to handle, however this is missing
from image copy when discovering source layer digests.

There is also a v1 manifest format error in the image export delete code.

This change fixes both of these issues, which should make it possible
to copy images from quay.io.

Change-Id: I3e6c95beb387dc4b462eb037eeb8cb628016b7e4
Closes-Bug: #1823821
This commit is contained in:
Steve Baker 2019-04-09 13:01:59 +12:00
parent d4216276cd
commit c7ffea0745
4 changed files with 109 additions and 19 deletions

View File

@ -260,8 +260,8 @@ def delete_image(image_url):
v1manifest = manifest.get('schemaVersion', 2) == 1
if v1manifest:
for digest in manifest.get('fsLayers', []):
add_reffed_blob(digest)
for layer in manifest.get('fsLayers', []):
add_reffed_blob(layer.get('blobSum'))
else:
for layer in manifest.get('layers', []):
add_reffed_blob(layer.get('digest'))

View File

@ -938,7 +938,11 @@ class PythonImageUploader(BaseImageUploader):
session=source_session
)
manifest = json.loads(manifest_str)
source_layers = [l['digest'] for l in manifest['layers']]
if manifest.get('schemaVersion', 2) == 1:
source_layers = list(reversed([l['blobSum']
for l in manifest['fsLayers']]))
else:
source_layers = [l['digest'] for l in manifest['layers']]
self._cross_repo_mount(
copy_target_url, self.image_layers, source_layers,

View File

@ -232,18 +232,25 @@ Header set ETag "%s"
image_export.IMAGE_EXPORT_DIR, 'v2', image[1:], 'blobs')
image_export.make_dir(blob_dir)
config_str = '{"config": {}}'
if manifest.get('schemaVersion', 2) == 1:
config_str = None
manifest_type = image_uploader.MEDIA_MANIFEST_V1
layers = list(reversed([l['blobSum']
for l in manifest['fsLayers']]))
else:
config_str = '{"config": {}}'
manifest_type = image_uploader.MEDIA_MANIFEST_V2
layers = [l['digest'] for l in manifest['layers']]
manifest_str = json.dumps(manifest)
calc_digest = hashlib.sha256()
calc_digest.update(manifest_str.encode('utf-8'))
manifest_digest = 'sha256:%s' % calc_digest.hexdigest()
image_export.export_manifest_config(
url, manifest_str,
image_uploader.MEDIA_MANIFEST_V2, config_str
url, manifest_str, manifest_type, config_str
)
for layer in manifest['layers']:
blob_path = os.path.join(blob_dir, '%s.gz' % layer['digest'])
for digest in layers:
blob_path = os.path.join(blob_dir, '%s.gz' % digest)
with open(blob_path, 'w+') as f:
f.write('The Blob')
@ -266,14 +273,10 @@ Header set ETag "%s"
url1 = urlparse('docker://localhost:8787/t/nova-api:latest')
url2 = urlparse('docker://localhost:8787/t/nova-api:abc')
manifest_1 = {
'config': {
'digest': 'sha256:1234',
'size': 2,
'mediaType': 'application/vnd.docker.container.image.v1+json'
},
'layers': [
{'digest': 'sha256:aeb786'},
{'digest': 'sha256:4dc536'},
'schemaVersion': 1,
'fsLayers': [
{'blobSum': 'sha256:aeb786'},
{'blobSum': 'sha256:4dc536'},
],
'mediaType': 'application/vnd.docker.'
'distribution.manifest.v2+json',
@ -319,7 +322,6 @@ Header set ETag "%s"
files=[
os.path.join(m_dir, m1_digest, 'index.json'),
os.path.join(m_dir, m2_digest, 'index.json'),
os.path.join(blob_dir, 'sha256:1234'),
os.path.join(blob_dir, 'sha256:aeb786.gz'),
os.path.join(blob_dir, 'sha256:4dc536.gz'),
os.path.join(blob_dir, 'sha256:5678'),
@ -345,7 +347,6 @@ Header set ETag "%s"
],
files=[
os.path.join(m_dir, m1_digest, 'index.json'),
os.path.join(blob_dir, 'sha256:1234'),
os.path.join(blob_dir, 'sha256:aeb786.gz'),
os.path.join(blob_dir, 'sha256:4dc536.gz'),
],
@ -382,7 +383,6 @@ Header set ETag "%s"
os.path.join(m_dir, m2_digest, 'index.json'),
os.path.join(blob_dir, 'sha256:5678'),
os.path.join(blob_dir, 'sha256:eeeeee.gz'),
os.path.join(blob_dir, 'sha256:1234'),
os.path.join(blob_dir, 'sha256:aeb786.gz'),
os.path.join(blob_dir, 'sha256:4dc536.gz'),
]

View File

@ -1332,6 +1332,92 @@ class TestPythonImageUploader(base.TestCase):
),
])
@mock.patch('tripleo_common.image.image_uploader.'
'PythonImageUploader.authenticate')
@mock.patch('tripleo_common.image.image_uploader.'
'PythonImageUploader._fetch_manifest')
@mock.patch('tripleo_common.image.image_uploader.'
'PythonImageUploader._cross_repo_mount')
@mock.patch('tripleo_common.image.image_uploader.'
'PythonImageUploader._copy_registry_to_registry')
def test_upload_image_v1_manifest(
self, _copy_registry_to_registry, _cross_repo_mount,
_fetch_manifest, authenticate):
target_session = mock.Mock()
source_session = mock.Mock()
authenticate.side_effect = [
target_session,
source_session
]
manifest = json.dumps({
'schemaVersion': 1,
'fsLayers': [
{'blobSum': 'sha256:ccc'},
{'blobSum': 'sha256:bbb'},
{'blobSum': 'sha256:aaa'}
],
})
_fetch_manifest.return_value = manifest
image = 'docker.io/tripleomaster/heat-docker-agents-centos'
tag = 'latest'
push_destination = 'localhost:8787'
# push_image = 'localhost:8787/tripleomaster/heat-docker-agents-centos'
task = image_uploader.UploadTask(
image_name=image + ':' + tag,
pull_source=None,
push_destination=push_destination,
append_tag=None,
modify_role=None,
modify_vars=None,
dry_run=False,
cleanup='full'
)
self.assertEqual(
[],
self.uploader.upload_image(task)
)
source_url = urlparse('docker://docker.io/tripleomaster/'
'heat-docker-agents-centos:latest')
target_url = urlparse('docker://localhost:8787/tripleomaster/'
'heat-docker-agents-centos:latest')
authenticate.assert_has_calls([
mock.call(
target_url,
username=None,
password=None
),
mock.call(
source_url,
username=None,
password=None
),
])
_fetch_manifest.assert_called_once_with(
source_url, session=source_session)
_cross_repo_mount.assert_called_once_with(
target_url,
{
'sha256:aaa': target_url,
'sha256:bbb': target_url,
'sha256:ccc': target_url,
},
['sha256:aaa', 'sha256:bbb', 'sha256:ccc'],
session=target_session)
_copy_registry_to_registry.assert_called_once_with(
source_url,
target_url,
source_manifest=manifest,
source_session=source_session,
target_session=target_session
)
@mock.patch('tripleo_common.image.image_uploader.'
'PythonImageUploader.authenticate')
@mock.patch('tripleo_common.image.image_uploader.'