Actually store the generated image storage in the database, and allow it to be garbage collected when the parent image storage is collected.

This commit is contained in:
Jake Moshenko 2014-09-18 17:26:40 -04:00
parent 43555af63d
commit 11bb8e6448
5 changed files with 120 additions and 50 deletions

View file

@ -1,26 +1,24 @@
import logging
import json
import hashlib
from flask import (make_response, request, session, Response, redirect,
Blueprint, abort, send_file, make_response)
from flask import redirect, Blueprint, abort, send_file
from app import storage as store, app
from auth.auth import process_auth
from auth.permissions import ReadRepositoryPermission
from data import model
from endpoints.registry import set_cache_headers
from data import database
from util.queuefile import QueueFile
from util.queueprocess import QueueProcess
from util.gzipwrap import GzipWrap
from util.streamlayerformat import StreamLayerMerger
from werkzeug.wsgi import wrap_file
verbs = Blueprint('verbs', __name__)
logger = logging.getLogger(__name__)
def _open_stream(namespace, repository, image_list):
def get_next_layer():
for current_image_id in image_list:
@ -32,20 +30,25 @@ def _open_stream(namespace, repository, image_list):
logger.debug('Returning image layer %s: %s' % (current_image_id, current_image_path))
yield current_image_stream
database.configure(app.config)
stream = GzipWrap(StreamLayerMerger(get_next_layer).get_generator())
return stream.read
def _write_synthetic_image_to_storage(namespace, repository, locations,
synthetic_image_id, queue_file):
# TODO: make sure this synthetic image expires!
image_path = store.image_layer_path(synthetic_image_id)
store.stream_write(locations, image_path, queue_file)
def _write_synthetic_image_to_storage(linked_storage_uuid, linked_locations, queue_file):
image_path = store.image_layer_path(linked_storage_uuid)
store.stream_write(linked_locations, image_path, queue_file)
queue_file.close()
database.configure(app.config)
done_uploading = model.get_storage_by_uuid(linked_storage_uuid)
done_uploading.uploading = False
done_uploading.save()
@verbs.route('/<namespace>/<repository>/<tag>/squash', methods=['GET'])
@process_auth
@set_cache_headers
def get_squashed_tag(namespace, repository, tag, headers):
def get_squashed_tag(namespace, repository, tag):
permission = ReadRepositoryPermission(namespace, repository)
if permission.can() or model.repository_is_public(namespace, repository):
# Lookup the requested tag.
@ -57,34 +60,26 @@ def get_squashed_tag(namespace, repository, tag, headers):
repo_image = model.get_repo_image(namespace, repository, tag_image.docker_image_id)
if not repo_image:
abort(404)
# Calculate a synthetic image ID by hashing the *image storage ID* with our
# secret. This is done to prevent the ID being guessable/overwritable by
# external pushes.
unhashed = str(repo_image.storage.id) + ':' + app.config['SECRET_KEY']
synthetic_image_id = hashlib.sha256(unhashed).hexdigest()
# Check to see if the synthetic image ID exists in storage. If so, we just return a 302.
logger.debug('Looking up synthetic image %s', synthetic_image_id)
locations = repo_image.storage.locations
saved_image_path = store.image_layer_path(synthetic_image_id)
if store.exists(locations, saved_image_path):
logger.debug('Synthetic image %s exists in storage', synthetic_image_id)
download_url = store.get_direct_download_url(locations, saved_image_path)
derived = model.find_or_create_derived_storage(repo_image.storage, 'squash',
store.preferred_locations[0])
if not derived.uploading:
logger.debug('Derived image %s exists in storage', derived.uuid)
derived_layer_path = store.image_layer_path(derived.uuid)
download_url = store.get_direct_download_url(derived.locations, derived_layer_path)
if download_url:
logger.debug('Redirecting to download URL for synthetic image %s', synthetic_image_id)
return redirect(download_url, code=302)
logger.debug('Redirecting to download URL for derived image %s', derived.uuid)
return redirect(download_url)
logger.debug('Sending cached synthetic image %s', synthetic_image_id)
return send_file(store.stream_read_file(locations, saved_image_path))
logger.debug('Sending cached derived image %s', derived.uuid)
return send_file(store.stream_read_file(derived.locations, derived_layer_path))
# Load the ancestry for the image.
logger.debug('Building and returning synthetic image %s', synthetic_image_id)
logger.debug('Building and returning derived image %s', derived.uuid)
uuid = repo_image.storage.uuid
ancestry_data = store.get_content(repo_image.storage.locations, store.image_ancestry_path(uuid))
full_image_list = json.loads(ancestry_data)
# Create a queue process to generate the data. The queue files will read from the process
# and send the results to the client and storage.
args = (namespace, repository, full_image_list)
@ -92,12 +87,12 @@ def get_squashed_tag(namespace, repository, tag, headers):
client_queue_file = QueueFile(queue_process.create_queue(), 'client')
storage_queue_file = QueueFile(queue_process.create_queue(), 'storage')
# Start building.
queue_process.run()
# Start the storage saving.
storage_args = (namespace, repository, locations, synthetic_image_id, storage_queue_file)
storage_args = (derived.uuid, derived.locations, storage_queue_file)
QueueProcess.run_process(_write_synthetic_image_to_storage, storage_args)
# Return the client's data.