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:
parent
d4216276cd
commit
c7ffea0745
|
@ -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'))
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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'),
|
||||
]
|
||||
|
|
|
@ -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.'
|
||||
|
|
Loading…
Reference in New Issue