node-triton#78 'triton image delete IMAGE'
This commit is contained in:
parent
879e86efa3
commit
810f0add56
@ -1,7 +1,8 @@
|
||||
# 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
|
||||
field.
|
||||
- PUBAPI-1227: Note that `triton image list` doesn't include Docker images, at
|
||||
|
@ -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>
|
||||
@ -684,16 +704,16 @@ CloudApi.prototype.getMachine = function getMachine(opts, cb) {
|
||||
/**
|
||||
* 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)`
|
||||
*/
|
||||
CloudApi.prototype.deleteMachine = function deleteMachine(uuid, callback) {
|
||||
CloudApi.prototype.deleteMachine = function deleteMachine(id, callback) {
|
||||
var self = this;
|
||||
assert.string(uuid, 'uuid');
|
||||
assert.uuid(id, 'id');
|
||||
assert.func(callback, 'callback');
|
||||
|
||||
var opts = {
|
||||
path: format('/%s/machines/%s', self.account, uuid),
|
||||
path: format('/%s/machines/%s', self.account, id),
|
||||
method: 'DELETE'
|
||||
};
|
||||
this._request(opts, function (err, req, res) {
|
||||
|
155
lib/do_image/do_delete.js
Normal file
155
lib/do_image/do_delete.js
Normal 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;
|
@ -23,7 +23,7 @@ function ImageCLI(top) {
|
||||
name: top.name + ' image',
|
||||
/* BEGIN JSSTYLED */
|
||||
desc: [
|
||||
'List, get, create and update Triton images.'
|
||||
'List, get, create and manage Triton images.'
|
||||
].join('\n'),
|
||||
/* END JSSTYLED */
|
||||
helpOpts: {
|
||||
@ -34,6 +34,7 @@ function ImageCLI(top) {
|
||||
'list',
|
||||
'get',
|
||||
'create',
|
||||
'delete',
|
||||
'wait'
|
||||
]
|
||||
});
|
||||
@ -48,6 +49,7 @@ ImageCLI.prototype.init = function init(opts, args, cb) {
|
||||
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_wait = require('./do_wait');
|
||||
|
||||
|
||||
|
@ -362,16 +362,20 @@ TritonApi.prototype.getImage = function getImage(opts, cb) {
|
||||
var s = opts.name.split('@');
|
||||
var name = s[0];
|
||||
var version = s[1];
|
||||
var nameSelector;
|
||||
|
||||
var listOpts = {
|
||||
// Explicitly include inactive images.
|
||||
state: 'all'
|
||||
};
|
||||
if (version) {
|
||||
nameSelector = name + '@' + version;
|
||||
listOpts.name = name;
|
||||
listOpts.version = version;
|
||||
// XXX This is bogus now?
|
||||
listOpts.useCache = opts.useCache;
|
||||
} else {
|
||||
nameSelector = name;
|
||||
}
|
||||
this.cloudapi.listImages(listOpts, function (err, imgs) {
|
||||
if (err) {
|
||||
@ -399,11 +403,13 @@ TritonApi.prototype.getImage = function getImage(opts, cb) {
|
||||
cb(null, shortIdMatches[0]);
|
||||
} else if (shortIdMatches.length === 0) {
|
||||
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 {
|
||||
cb(new errors.ResourceNotFoundError(
|
||||
format('no image with name "%s" was found '
|
||||
+ 'and "%s" is an ambiguous short id', name)));
|
||||
format('no image with %s "%s" was found '
|
||||
+ 'and "%s" is an ambiguous short id',
|
||||
nameSelector, name, name)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "triton",
|
||||
"description": "Joyent Triton CLI and client (https://www.joyent.com/triton)",
|
||||
"version": "4.3.2",
|
||||
"version": "4.4.0",
|
||||
"author": "Joyent (joyent.com)",
|
||||
"dependencies": {
|
||||
"assert-plus": "0.2.0",
|
||||
|
Reference in New Issue
Block a user