From 17669f35eac0a45bdff365aadbc2cf4c208fe9ba Mon Sep 17 00:00:00 2001 From: Jason King Date: Fri, 7 Apr 2017 15:44:35 -0500 Subject: [PATCH] joyent/node-triton#197 Create triton image export command --- CHANGES.md | 4 ++ lib/cloudapi2.js | 27 ++++++++- lib/do_image/do_export.js | 120 ++++++++++++++++++++++++++++++++++++++ lib/do_image/index.js | 4 +- lib/tritonapi.js | 78 ++++++++++++++++++++++++- package.json | 2 +- 6 files changed, 230 insertions(+), 5 deletions(-) create mode 100644 lib/do_image/do_export.js diff --git a/CHANGES.md b/CHANGES.md index ae4f132..b0da44c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,10 @@ Known issues: ## not yet released +## 5.2.0 + +- [joyent/node-triton#197] Create triton image export command + ## 5.1.1 - [joyent/node-triton#190] Fix `triton profile create|docker-setup` breakage diff --git a/lib/cloudapi2.js b/lib/cloudapi2.js index f872baa..556a074 100644 --- a/lib/cloudapi2.js +++ b/lib/cloudapi2.js @@ -5,7 +5,7 @@ */ /* - * Copyright 2015 Joyent, Inc. + * Copyright 2017 Joyent, Inc. * * Client library for the SmartDataCenter Cloud API (cloudapi). * http://apidocs.joyent.com/cloudapi/ @@ -669,7 +669,32 @@ function createImageFromMachine(opts, cb) { }); }; +/** + * Export an image to Manta. + * + * + * @param {Object} opts + * - {UUID} id Required. The id of the image to export. + * - {String} manta_path Required. The path in Manta to write the image. + * @param {Function} cb of the form `function (err, exportInfo, res)` + */ +CloudApi.prototype.exportImage = function exportImage(opts, cb) { + assert.uuid(opts.id, 'id'); + assert.string(opts.manta_path, 'manta_path'); + assert.func(cb, 'cb'); + var data = { + action: 'export', + manta_path: opts.manta_path + }; + this._request({ + method: 'POST', + path: format('/%s/images/%s', this.account, opts.id), + data: data + }, function (err, req, res, body) { + cb(err, body, res); + }); +}; /** * Wait for an image to go one of a set of specfic states. diff --git a/lib/do_image/do_export.js b/lib/do_image/do_export.js new file mode 100644 index 0000000..77d07c2 --- /dev/null +++ b/lib/do_image/do_export.js @@ -0,0 +1,120 @@ +/* + * 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/. + */ + +/* + * Copyright 2017 Joyent, Inc. + * + * `triton image export ...` + */ + +var assert = require('assert-plus'); +var format = require('util').format; +var vasync = require('vasync'); + +var common = require('../common'); +var errors = require('../errors'); + +// ---- the command + +function do_export(subcmd, opts, args, cb) { + if (opts.help) { + this.do_help('help', {}, [subcmd], cb); + return; + } else if (args.length !== 2) { + cb(new errors.UsageError( + 'incorrect number of args: expect 2, got ' + args.length)); + return; + } + + var log = this.top.log; + var tritonapi = this.top.tritonapi; + + vasync.pipeline({arg: {cli: this.top}, funcs: [ + common.cliSetupTritonApi, + function exportImage(ctx, next) { + log.trace({dryRun: opts.dry_run, manta_path: ctx.manta_path}, + 'image export path'); + + console.log('Exporting image %s to %s', args[0], args[1]); + + if (opts.dry_run) { + next(); + return; + } + + tritonapi.exportImage({ + image: args[0], + manta_path: args[1] + }, function (err, exportInfo) { + if (err) { + next(new errors.TritonError(err, + 'error exporting image to manta')); + return; + } + + log.trace({exportInfo: exportInfo}, 'image export: exportInfo'); + ctx.exportInfo = exportInfo; + next(); + }); + }, + function outputResults(ctx, next) { + if (opts.json) { + console.log(JSON.stringify(ctx.exportInfo)); + } else { + console.log(' Manta URL: %s', ctx.exportInfo.manta_url); + console.log('Manifest path: %s', ctx.exportInfo.manifest_path); + console.log(' Image path: %s', ctx.exportInfo.image_path); + } + next(); + } + ]}, function (err) { + cb(err); + }); +} + +do_export.options = [ + { + names: ['help', 'h'], + type: 'bool', + help: 'Show this help.' + }, + { + group: 'Other options' + }, + { + names: ['dry-run'], + type: 'bool', + help: 'Go through the motions without actually exporting.' + }, + { + names: ['json', 'j'], + type: 'bool', + help: 'JSON stream output.' + } +]; + +do_export.synopses = [ + '{{name}} {{cmd}} [OPTIONS] IMAGE MANTA_PATH' +]; + +do_export.help = [ + /* BEGIN JSSTYLED */ + 'Export an image.', + '', + '{{usage}}', + '', + '{{options}}', + 'Where "IMAGE" is an image id (a full UUID), an image name (selects the', + 'latest, by "published_at", image with that name), an image "name@version"', + '(selects latest match by "published_at"), or an image short ID (ID prefix).', + '', + 'Note: Only images that are owned by the account can be exported.' + /* END JSSTYLED */ +].join('\n'); + +do_export.completionArgtypes = ['tritonimage', 'none']; + +module.exports = do_export; diff --git a/lib/do_image/index.js b/lib/do_image/index.js index 35fb759..f427e6d 100644 --- a/lib/do_image/index.js +++ b/lib/do_image/index.js @@ -5,7 +5,7 @@ */ /* - * Copyright 2015 Joyent, Inc. + * Copyright 2017 Joyent, Inc. * * `triton image ...` */ @@ -35,6 +35,7 @@ function ImageCLI(top) { 'get', 'create', 'delete', + 'export', 'wait' ] }); @@ -50,6 +51,7 @@ ImageCLI.prototype.do_list = require('./do_list'); ImageCLI.prototype.do_get = require('./do_get'); ImageCLI.prototype.do_create = require('./do_create'); ImageCLI.prototype.do_delete = require('./do_delete'); +ImageCLI.prototype.do_export = require('./do_export'); ImageCLI.prototype.do_wait = require('./do_wait'); diff --git a/lib/tritonapi.js b/lib/tritonapi.js index c5f159a..10db0f3 100644 --- a/lib/tritonapi.js +++ b/lib/tritonapi.js @@ -205,6 +205,25 @@ function _stepPkgId(arg, next) { }); } +/** + * A function appropriate for `vasync.pipeline` funcs that takes a `arg.image` + * image name, shortid, or uuid, and determines the image id (setting it + * as arg.imgId). + */ +function _stepImgId(arg, next) { + assert.object(arg.client, 'arg.client'); + assert.string(arg.image, 'arg.image'); + + arg.client.getImage(arg.image, function (err, img) { + if (err) { + next(err); + } else { + arg.imgId = img.id; + next(); + } + }); +} + /** * A function appropriate for `vasync.pipeline` funcs that takes a `arg.id` * fwrule shortid or uuid, and determines the fwrule id (setting it @@ -641,6 +660,62 @@ TritonApi.prototype.getImage = function getImage(opts, cb) { } }; +/** + * Export and image to Manta. + * + * @param {Object} opts + * - {String} image The image UUID, name, or short ID. Required. + * - {String} manta_path The path in Manta where the image will be + * exported. Required. + * @param {Function} cb `function (err, exportInfo, res)` + * On failure `err` is an error instance, else it is null. + * On success: `exportInfo` is an object with three properties: + * - {String} manta_url The url of the Manta API endpoint where the + * image was exported. + * - {String} manifest_path The pathname in Manta of the exported image + * manifest. + * - {String} image_path The pathname in Manta of the exported image. + * and `res` is the CloudAPI `ExportImage` response. + */ +TritonApi.prototype.exportImage = function exportImage(opts, cb) +{ + var self = this; + assert.object(opts, 'opts'); + assert.string(opts.image, 'opts.image'); + assert.string(opts.manta_path, 'opts.manta_path'); + assert.func(cb, 'cb'); + + var res = null; + var exportInfo = null; + var arg = { + image: opts.image, + client: self + }; + + vasync.pipeline({arg: arg, funcs: [ + _stepImgId, + function cloudApiExportImage(ctx, next) { + self.cloudapi.exportImage({ + id: ctx.imgId, manta_path: opts.manta_path }, + function (err, exportInfo_, res_) { + if (err) { + next(err); + return; + } + + exportInfo = exportInfo_; + res = res_; + next(); + }); + } + ]}, function (err) { + if (err) { + cb(err, exportInfo, res); + } else { + cb(null, exportInfo, res); + } + }); +}; /** * Get an active package by ID, exact name, or short ID, in that order. @@ -2389,8 +2464,7 @@ TritonApi.prototype.rebootInstance = function rebootInstance(opts, cb) { var theRecord = null; for (var i = 0; i < audit.length; i++) { if (audit[i].action === 'reboot' && - Date.parse(audit[i].time) > resTime) - { + Date.parse(audit[i].time) > resTime) { theRecord = audit[i]; break; } diff --git a/package.json b/package.json index 4677827..bff9b76 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "triton", "description": "Joyent Triton CLI and client (https://www.joyent.com/triton)", - "version": "5.1.1", + "version": "5.2.0", "author": "Joyent (joyent.com)", "homepage": "https://github.com/joyent/node-triton", "dependencies": {