from util.gzipwrap import GzipWrap from util.streamlayerformat import StreamLayerMerger from app import app import copy import json import tarfile def build_docker_load_stream(namespace, repository, tag, synthetic_image_id, layer_json, get_image_iterator, get_layer_iterator): """ Builds and streams a synthetic .tar.gz that represents a squashed version of the given layers, in `docker load` V1 format. """ return GzipWrap(_import_format_generator(namespace, repository, tag, synthetic_image_id, layer_json, get_image_iterator, get_layer_iterator)) def _import_format_generator(namespace, repository, tag, synthetic_image_id, layer_json, get_image_iterator, get_layer_iterator): # Docker import V1 Format (.tar): # repositories - JSON file containing a repo -> tag -> image map # {image ID folder}: # json - The layer JSON # layer.tar - The TARed contents of the layer # VERSION - The docker import version: '1.0' layer_merger = StreamLayerMerger(get_layer_iterator) # Yield the repositories file: synthetic_layer_info = {} synthetic_layer_info[tag + '.squash'] = synthetic_image_id hostname = app.config['SERVER_HOSTNAME'] repositories = {} repositories[hostname + '/' + namespace + '/' + repository] = synthetic_layer_info yield _tar_file('repositories', json.dumps(repositories)) # Yield the image ID folder. yield _tar_folder(synthetic_image_id) # Yield the JSON layer data. layer_json = _build_layer_json(layer_json, synthetic_image_id) yield _tar_file(synthetic_image_id + '/json', json.dumps(layer_json)) # Yield the VERSION file. yield _tar_file(synthetic_image_id + '/VERSION', '1.0') # Yield the merged layer data's header. estimated_file_size = 0 for image in get_image_iterator(): estimated_file_size += image.storage.uncompressed_size or 0 yield _tar_file_header(synthetic_image_id + '/layer.tar', estimated_file_size) # Yield the contents of the merged layer. yielded_size = 0 for entry in layer_merger.get_generator(): yield entry yielded_size += len(entry) # If the yielded size is less than the estimated size (which is likely), fill the rest with # zeros. if yielded_size < estimated_file_size: yield '\0' * (estimated_file_size - yielded_size) # Yield any file padding to 512 bytes that is necessary. yield _tar_file_padding(estimated_file_size) # Last two records are empty in TAR spec. yield '\0' * 512 yield '\0' * 512 def _build_layer_json(layer_json, synthetic_image_id): updated_json = copy.deepcopy(layer_json) updated_json['id'] = synthetic_image_id if 'parent' in updated_json: del updated_json['parent'] if 'config' in updated_json and 'Image' in updated_json['config']: updated_json['config']['Image'] = synthetic_image_id if 'container_config' in updated_json and 'Image' in updated_json['container_config']: updated_json['container_config']['Image'] = synthetic_image_id return updated_json def _tar_file(name, contents): length = len(contents) tar_data = _tar_file_header(name, length) tar_data += contents tar_data += _tar_file_padding(length) return tar_data def _tar_file_padding(length): if length % 512 != 0: return '\0' * (512 - (length % 512)) def _tar_file_header(name, file_size): info = tarfile.TarInfo(name=name) info.type = tarfile.REGTYPE info.size = file_size return info.tobuf() def _tar_folder(name): info = tarfile.TarInfo(name=name) info.type = tarfile.DIRTYPE return info.tobuf()