node-triton#78 'triton image delete IMAGE'

This commit is contained in:
Trent Mick 2016-01-25 23:23:36 -08:00
parent 879e86efa3
commit 810f0add56
6 changed files with 194 additions and 10 deletions

View File

@ -1,7 +1,8 @@
# node-triton changelog # node-triton changelog
## 4.3.2 (not yet released) ## 4.4.0 (not yet released)
- #78 `triton image delete IMAGE`
- #79 Fix `triton instance get NAME` to make sure it gets the `dns_names` CNS - #79 Fix `triton instance get NAME` to make sure it gets the `dns_names` CNS
field. field.
- PUBAPI-1227: Note that `triton image list` doesn't include Docker images, at - PUBAPI-1227: Note that `triton image list` doesn't include Docker images, at

View File

@ -539,6 +539,26 @@ CloudApi.prototype.getImage = function getImage(opts, cb) {
}); });
}; };
/**
* Delete an image by id.
* <http://apidocs.joyent.com/cloudapi/#DeleteImage>
*
* @param {String} id (required) The image id.
* @param {Function} callback of the form `function (err, res)`
*/
CloudApi.prototype.deleteImage = function deleteImage(id, callback) {
var self = this;
assert.uuid(id, 'id');
assert.func(callback, 'callback');
var opts = {
path: format('/%s/images/%s', self.account, id),
method: 'DELETE'
};
this._request(opts, function (err, req, res) {
callback(err, res);
});
};
/** /**
* <http://apidocs.joyent.com/cloudapi/#CreateImageFromMachine> * <http://apidocs.joyent.com/cloudapi/#CreateImageFromMachine>
@ -684,16 +704,16 @@ CloudApi.prototype.getMachine = function getMachine(opts, cb) {
/** /**
* delete a machine by id. * delete a machine by id.
* *
* @param {String} uuid (required) The machine id. * @param {String} id (required) The machine id.
* @param {Function} callback of the form `function (err, res)` * @param {Function} callback of the form `function (err, res)`
*/ */
CloudApi.prototype.deleteMachine = function deleteMachine(uuid, callback) { CloudApi.prototype.deleteMachine = function deleteMachine(id, callback) {
var self = this; var self = this;
assert.string(uuid, 'uuid'); assert.uuid(id, 'id');
assert.func(callback, 'callback'); assert.func(callback, 'callback');
var opts = { var opts = {
path: format('/%s/machines/%s', self.account, uuid), path: format('/%s/machines/%s', self.account, id),
method: 'DELETE' method: 'DELETE'
}; };
this._request(opts, function (err, req, res) { this._request(opts, function (err, req, res) {

155
lib/do_image/do_delete.js Normal file
View File

@ -0,0 +1,155 @@
/*
* 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 2016 Joyent, Inc.
*
* `triton image delete ...`
*/
var format = require('util').format;
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
function do_delete(subcmd, opts, args, cb) {
var self = this;
if (opts.help) {
return this.do_help('help', {}, [subcmd], cb);
} else if (args.length < 1) {
return cb(new errors.UsageError('missing IMAGE arg(s)'));
}
var ids = args;
vasync.pipeline({arg: {}, funcs: [
/*
* Lookup images, if not given UUIDs: we'll need to do it anyway
* for the DeleteImage call(s), and doing so explicitly here allows
* us to emit better output.
*/
function getImgs(ctx, next) {
ctx.imgFromId = {};
ctx.missingIds = [];
// TODO: this should have a concurrency
vasync.forEachParallel({
inputs: ids,
func: function getImg(id, nextImg) {
if (common.isUUID(id)) {
// TODO: get info from cache if we have it
ctx.imgFromId[id] = {
id: id,
_repr: id
};
nextImg();
return;
}
// TODO: allow use of cache here
self.top.tritonapi.getImage(id, function (err, img) {
if (err) {
if (err.statusCode === 404) {
ctx.missingIds.push(id);
nextImg();
} else {
nextImg(err);
}
} else {
ctx.imgFromId[img.id] = img;
img._repr = format('%s (%s@%s)', img.id,
img.name, img.version);
nextImg();
}
});
}
}, next);
},
function errOnMissingIds(ctx, next) {
if (ctx.missingIds.length === 1) {
next(new errors.TritonError('no such image: '
+ ctx.missingIds[0]));
} else if (ctx.missingIds.length > 1) {
next(new errors.TritonError('no such images: '
+ ctx.missingIds.join(', ')));
} else {
next();
}
},
function confirm(ctx, next) {
if (opts.force) {
next();
return;
}
var keys = Object.keys(ctx.imgFromId);
var msg;
if (keys.length === 1) {
msg = format('Delete image %s? [y/n] ',
ctx.imgFromId[keys[0]]._repr);
} else {
msg = format('Delete %d images? [y/n] ', keys.length);
}
common.promptYesNo({msg: msg}, function (answer) {
if (answer !== 'y') {
console.error('Aborting');
next(true); // early abort signal
} else {
next();
}
});
},
function deleteThem(ctx, next) {
// TODO: forEachParallel with concurrency
vasync.forEachPipeline({
inputs: Object.keys(ctx.imgFromId),
func: function deleteOne(id, nextOne) {
self.top.tritonapi.cloudapi.deleteImage(id, function (err) {
if (!err) {
console.log('Deleted image %s',
ctx.imgFromId[id]._repr);
}
nextOne(err);
});
}
}, next);
}
]}, function (err) {
cb(err);
});
}
do_delete.help = [
/* BEGIN JSSTYLED */
'Delete one or more images.',
'',
'Usage:',
' {{name}} delete IMAGE [IMAGE...]',
'',
'{{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).'
/* END JSSTYLED */
].join('\n');
do_delete.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['force', 'f'],
type: 'bool',
help: 'Skip confirmation of delete.'
}
];
do_delete.aliases = ['rm'];
module.exports = do_delete;

View File

@ -23,7 +23,7 @@ function ImageCLI(top) {
name: top.name + ' image', name: top.name + ' image',
/* BEGIN JSSTYLED */ /* BEGIN JSSTYLED */
desc: [ desc: [
'List, get, create and update Triton images.' 'List, get, create and manage Triton images.'
].join('\n'), ].join('\n'),
/* END JSSTYLED */ /* END JSSTYLED */
helpOpts: { helpOpts: {
@ -34,6 +34,7 @@ function ImageCLI(top) {
'list', 'list',
'get', 'get',
'create', 'create',
'delete',
'wait' 'wait'
] ]
}); });
@ -48,6 +49,7 @@ ImageCLI.prototype.init = function init(opts, args, cb) {
ImageCLI.prototype.do_list = require('./do_list'); ImageCLI.prototype.do_list = require('./do_list');
ImageCLI.prototype.do_get = require('./do_get'); ImageCLI.prototype.do_get = require('./do_get');
ImageCLI.prototype.do_create = require('./do_create'); ImageCLI.prototype.do_create = require('./do_create');
ImageCLI.prototype.do_delete = require('./do_delete');
ImageCLI.prototype.do_wait = require('./do_wait'); ImageCLI.prototype.do_wait = require('./do_wait');

View File

@ -362,16 +362,20 @@ TritonApi.prototype.getImage = function getImage(opts, cb) {
var s = opts.name.split('@'); var s = opts.name.split('@');
var name = s[0]; var name = s[0];
var version = s[1]; var version = s[1];
var nameSelector;
var listOpts = { var listOpts = {
// Explicitly include inactive images. // Explicitly include inactive images.
state: 'all' state: 'all'
}; };
if (version) { if (version) {
nameSelector = name + '@' + version;
listOpts.name = name; listOpts.name = name;
listOpts.version = version; listOpts.version = version;
// XXX This is bogus now? // XXX This is bogus now?
listOpts.useCache = opts.useCache; listOpts.useCache = opts.useCache;
} else {
nameSelector = name;
} }
this.cloudapi.listImages(listOpts, function (err, imgs) { this.cloudapi.listImages(listOpts, function (err, imgs) {
if (err) { if (err) {
@ -399,11 +403,13 @@ TritonApi.prototype.getImage = function getImage(opts, cb) {
cb(null, shortIdMatches[0]); cb(null, shortIdMatches[0]);
} else if (shortIdMatches.length === 0) { } else if (shortIdMatches.length === 0) {
cb(new errors.ResourceNotFoundError(format( cb(new errors.ResourceNotFoundError(format(
'no image with name or short id "%s" was found', name))); 'no image with %s or short id "%s" was found',
nameSelector, name)));
} else { } else {
cb(new errors.ResourceNotFoundError( cb(new errors.ResourceNotFoundError(
format('no image with name "%s" was found ' format('no image with %s "%s" was found '
+ 'and "%s" is an ambiguous short id', name))); + 'and "%s" is an ambiguous short id',
nameSelector, name, name)));
} }
}); });
} }

View File

@ -1,7 +1,7 @@
{ {
"name": "triton", "name": "triton",
"description": "Joyent Triton CLI and client (https://www.joyent.com/triton)", "description": "Joyent Triton CLI and client (https://www.joyent.com/triton)",
"version": "4.3.2", "version": "4.4.0",
"author": "Joyent (joyent.com)", "author": "Joyent (joyent.com)",
"dependencies": { "dependencies": {
"assert-plus": "0.2.0", "assert-plus": "0.2.0",