From c595a71108f2aa379f43c56f6ab8f2b13261b2aa Mon Sep 17 00:00:00 2001 From: Todd Whiteman Date: Thu, 6 Nov 2014 20:03:24 -0800 Subject: [PATCH] DOCKER-5: Add downloading of images to filesystem --- examples/registry-image-download.js | 110 ++++++++++++++++++++++++++++ lib/clients/registry.js | 21 ++++++ package.json | 1 + 3 files changed, 132 insertions(+) create mode 100644 examples/registry-image-download.js diff --git a/examples/registry-image-download.js b/examples/registry-image-download.js new file mode 100644 index 0000000..41270a8 --- /dev/null +++ b/examples/registry-image-download.js @@ -0,0 +1,110 @@ +#!/usr/bin/env node + +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +var fs = require('fs'); +var path = require('path'); +var clients = require('../lib/clients'); + +var idx = clients.index.create({}); +var g_repo = 'library/mongo'; +var g_tag = 'latest'; +var g_dir = path.resolve(__dirname, '..', 'images'); + +// Create the images directory if it doesn't exist yet. +if (!fs.existsSync(g_dir)) { + try { + fs.mkdirSync(g_dir); + } catch (ex) { + console.error('Unable to create images directory: ' + ex); + return; + } +} + +console.log("Images dir: " + g_dir); + +/** + * Returns an array of dependencies (ancestors) for the given repo:tag. + */ +function getImageAncestry(client, repo, tag, callback) { + client.getRepoTags(function (err2, tags) { + if (err2) { + return console.error('Error getting repo tags: ' + err2.message); + } + + if (!tags.hasOwnProperty(tag)) { + console.error('repo "%s" does not have tag "%s"', repo, tags); + console.error('available tags: ' + Object.keys(tags).join(', ')); + return; + } + + client.getImageAncestry(tags[tag], callback); + }); +} + +/** + * Downloads the given registry repo:tag (and it's dependencies) into the given + * directory. + */ +function downloadRegistryImage(client, repo, tag, dirpath) { + getImageAncestry(client, repo, tag, function(err, images) { + if (err) { + console.error('Error getting image ancestry: ' + err.message); + return; + } + + // Download all images in the ancestry. + // TODO: Perhaps queuing, to avoid downloading all at once? + images.forEach(function(image) { + // Check if the image already exists. + var filepath = path.join(dirpath, image); + if (fs.existsSync(filepath)) { + // TODO: Verify if image is up-to-date (or will docker use a + // different hash if it's modified)? + console.log("Already downloaded image: " + image); + return; + } + + //console.log("Getting meta data for image " + image); + + client.getImageMetadata(image, function(err2, imgData) { + if (err2) { + console.error('Error getting image metadata: ' + err2.message); + return; + } + + //console.log(imgData); + console.log("Downloading image: " + image); + + // Download to a temporary file, then atomic rename on success. + // This ensures that if the image file exists, then it's been + // fully downloaded. + var tmp_filepath = path.join(dirpath, image + ".partial"); + + // TODO: If a tmp_filepath exists, could perform HTTP reget. + client.downloadImageLayer(image, tmp_filepath, + function(err3, resp, body) { + if (err3) { + console.error('Error downloading image: ' + err3.message); + return; + } + + // TODO: Verify download - is there a checksum somewhere? + + // Download was successful - rename the tmp file. + fs.rename(tmp_filepath, filepath); + console.log("Successfully downloaded image: " + image); + }); + }); + }); + }); +} + +clients.registry.createWithToken({ repo: g_repo }, function (err, client) { + // TODO: g_dir may not exist yet. + downloadRegistryImage(client, g_repo, g_tag, g_dir); +}); diff --git a/lib/clients/registry.js b/lib/clients/registry.js index 3a69f8a..22db92c 100644 --- a/lib/clients/registry.js +++ b/lib/clients/registry.js @@ -13,6 +13,8 @@ var bunyan = require('bunyan'); var fmt = require('util').format; var idxClient = require('./idx'); var restify = require('restify'); +var request = require('request'); +var fs = require('fs'); var VError = require('verror').VError; @@ -101,6 +103,25 @@ Registry.prototype.getImageMetadata = }; +/** + * Downloads the remote image layer. + */ +Registry.prototype.downloadImageLayer = + function downloadImageLayer(img, filepath, callback) { + // Uses the request library for following reasons: + // - automatically following http redirects (e.g. status code 302) + // - to easily redirect (pipe) the response to a file + // + // TODO: Add retry capability? + request({ + url: fmt('%s/v1/images/%s/layer', REGISTRY_URL, img), + headers: { + Authorization: this.authHeader + } + }, callback).pipe(fs.createWriteStream(filepath)); +}; + + /** * Gets the remote image's JSON */ diff --git a/package.json b/package.json index 9ace7fe..f9d9e34 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "assert-plus": "0.1.5", "bunyan": "1.2.0", "restify": "2.8.3", + "request": "2.47.0", "vasync": "1.6.1", "verror": "1.4.0" }