Merge branch 'master' of https://github.com/joyent/node-triton
This commit is contained in:
commit
be74f307e0
12
CHANGES.md
12
CHANGES.md
@ -6,6 +6,18 @@ Known issues:
|
||||
|
||||
## not yet released
|
||||
|
||||
## 5.8.0
|
||||
|
||||
- [TRITON-124] add node-triton support for bhyve. This adds a `triton instance
|
||||
create --brand=bhyve ...` option that can be used for zvol images that support
|
||||
it. Note that bhyve support is alpha in TritonDC -- most datacenters won't yet
|
||||
support this option.
|
||||
|
||||
## 5.7.0
|
||||
|
||||
- [TRITON-116] node-triton image sharing. Adds `triton image share` and
|
||||
`triton image unshare` commands.
|
||||
|
||||
## 5.6.1
|
||||
|
||||
- [PUBAPI-1470] volume objects should expose their creation timestamp in a
|
||||
|
@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2017 Joyent, Inc.
|
||||
* Copyright (c) 2018, Joyent, Inc.
|
||||
*
|
||||
* Client library for the SmartDataCenter Cloud API (cloudapi).
|
||||
* http://apidocs.joyent.com/cloudapi/
|
||||
@ -767,6 +767,33 @@ CloudApi.prototype.exportImage = function exportImage(opts, cb) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Update an image.
|
||||
* <http://apidocs.joyent.com/cloudapi/#UpdateImage>
|
||||
*
|
||||
* @param {Object} opts
|
||||
* - {UUID} id Required. The id of the image to update.
|
||||
* - {Object} fields Required. The fields to update in the image.
|
||||
* @param {Function} cb of the form `function (err, body, res)`
|
||||
*/
|
||||
CloudApi.prototype.updateImage = function shareImage(opts, cb) {
|
||||
assert.uuid(opts.id, 'id');
|
||||
assert.object(opts.fields, 'fields');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
this._request({
|
||||
method: 'POST',
|
||||
path: format('/%s/images/%s?action=update', this.account, opts.id),
|
||||
data: opts.fields
|
||||
}, function (err, req, res, body) {
|
||||
if (err) {
|
||||
cb(err, null, res);
|
||||
return;
|
||||
}
|
||||
cb(null, body, res);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Wait for an image to go one of a set of specfic states.
|
||||
*
|
||||
|
@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2016 Joyent, Inc.
|
||||
* Copyright 2018 Joyent, Inc.
|
||||
*
|
||||
* `triton fwrule instances ...`
|
||||
*/
|
||||
@ -111,6 +111,7 @@ function do_instances(subcmd, opts, args, cb) {
|
||||
common.uuidToShortId(inst.image);
|
||||
inst.shortid = inst.id.split('-', 1)[0];
|
||||
var flags = [];
|
||||
if (inst.brand === 'bhyve') flags.push('B');
|
||||
if (inst.docker) flags.push('D');
|
||||
if (inst.firewall_enabled) flags.push('F');
|
||||
if (inst.brand === 'kvm') flags.push('K');
|
||||
@ -159,6 +160,7 @@ do_instances.help = [
|
||||
'for convenience):',
|
||||
' shortid* A short ID prefix.',
|
||||
' flags* Single letter flags summarizing some fields:',
|
||||
' "B" the brand is "bhyve"',
|
||||
' "D" docker instance',
|
||||
' "F" firewall is enabled',
|
||||
' "K" the brand is "kvm"',
|
||||
|
115
lib/do_image/do_share.js
Normal file
115
lib/do_image/do_share.js
Normal file
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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 (c) 2018, Joyent, Inc.
|
||||
*
|
||||
* `triton image share ...`
|
||||
*/
|
||||
|
||||
var format = require('util').format;
|
||||
var vasync = require('vasync');
|
||||
|
||||
var common = require('../common');
|
||||
var errors = require('../errors');
|
||||
|
||||
// ---- the command
|
||||
|
||||
function do_share(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 shareImage(ctx, next) {
|
||||
log.trace({dryRun: opts.dry_run, account: ctx.account},
|
||||
'image share account');
|
||||
|
||||
if (opts.dry_run) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
tritonapi.shareImage({
|
||||
image: args[0],
|
||||
account: args[1]
|
||||
}, function (err, img) {
|
||||
if (err) {
|
||||
next(new errors.TritonError(err, 'error sharing image'));
|
||||
return;
|
||||
}
|
||||
|
||||
log.trace({img: img}, 'image share result');
|
||||
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(img));
|
||||
} else {
|
||||
console.log('Shared image %s with account %s',
|
||||
args[0], args[1]);
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
]}, function (err) {
|
||||
cb(err);
|
||||
});
|
||||
}
|
||||
|
||||
do_share.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 sharing.'
|
||||
},
|
||||
{
|
||||
names: ['json', 'j'],
|
||||
type: 'bool',
|
||||
help: 'JSON stream output.'
|
||||
}
|
||||
];
|
||||
|
||||
do_share.synopses = [
|
||||
'{{name}} {{cmd}} [OPTIONS] IMAGE ACCOUNT'
|
||||
];
|
||||
|
||||
do_share.help = [
|
||||
/* BEGIN JSSTYLED */
|
||||
'Share an image with another account.',
|
||||
'',
|
||||
'{{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).',
|
||||
'',
|
||||
'Where "ACCOUNT" is the full account UUID.',
|
||||
'',
|
||||
'Note: Only images that are owned by the account can be shared.'
|
||||
/* END JSSTYLED */
|
||||
].join('\n');
|
||||
|
||||
do_share.completionArgtypes = ['tritonimage', 'none'];
|
||||
|
||||
module.exports = do_share;
|
115
lib/do_image/do_unshare.js
Normal file
115
lib/do_image/do_unshare.js
Normal file
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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 (c) 2018, Joyent, Inc.
|
||||
*
|
||||
* `triton image unshare ...`
|
||||
*/
|
||||
|
||||
var format = require('util').format;
|
||||
var vasync = require('vasync');
|
||||
|
||||
var common = require('../common');
|
||||
var errors = require('../errors');
|
||||
|
||||
// ---- the command
|
||||
|
||||
function do_unshare(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 unshareImage(ctx, next) {
|
||||
log.trace({dryRun: opts.dry_run, account: ctx.account},
|
||||
'image unshare account');
|
||||
|
||||
if (opts.dry_run) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
tritonapi.unshareImage({
|
||||
image: args[0],
|
||||
account: args[1]
|
||||
}, function (err, img) {
|
||||
if (err) {
|
||||
next(new errors.TritonError(err, 'error unsharing image'));
|
||||
return;
|
||||
}
|
||||
|
||||
log.trace({img: img}, 'image unshare result');
|
||||
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(img));
|
||||
} else {
|
||||
console.log('Unshared image %s with account %s',
|
||||
args[0], args[1]);
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
]}, function (err) {
|
||||
cb(err);
|
||||
});
|
||||
}
|
||||
|
||||
do_unshare.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 sharing.'
|
||||
},
|
||||
{
|
||||
names: ['json', 'j'],
|
||||
type: 'bool',
|
||||
help: 'JSON stream output.'
|
||||
}
|
||||
];
|
||||
|
||||
do_unshare.synopses = [
|
||||
'{{name}} {{cmd}} [OPTIONS] IMAGE ACCOUNT'
|
||||
];
|
||||
|
||||
do_unshare.help = [
|
||||
/* BEGIN JSSTYLED */
|
||||
'Unshare an image with another account.',
|
||||
'',
|
||||
'{{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).',
|
||||
'',
|
||||
'Where "ACCOUNT" is the full account UUID.',
|
||||
'',
|
||||
'Note: Only images that are owned by the account can be unshared.'
|
||||
/* END JSSTYLED */
|
||||
].join('\n');
|
||||
|
||||
do_unshare.completionArgtypes = ['tritonimage', 'none'];
|
||||
|
||||
module.exports = do_unshare;
|
@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2017 Joyent, Inc.
|
||||
* Copyright (c) 2018, Joyent, Inc.
|
||||
*
|
||||
* `triton image ...`
|
||||
*/
|
||||
@ -36,6 +36,8 @@ function ImageCLI(top) {
|
||||
'create',
|
||||
'delete',
|
||||
'export',
|
||||
'share',
|
||||
'unshare',
|
||||
'wait'
|
||||
]
|
||||
});
|
||||
@ -52,6 +54,8 @@ 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_share = require('./do_share');
|
||||
ImageCLI.prototype.do_unshare = require('./do_unshare');
|
||||
ImageCLI.prototype.do_wait = require('./do_wait');
|
||||
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2017 Joyent, Inc.
|
||||
* Copyright 2018 Joyent, Inc.
|
||||
*
|
||||
* `triton instance create ...`
|
||||
*/
|
||||
@ -376,6 +376,9 @@ function do_create(subcmd, opts, args, cb) {
|
||||
function (net) { return net.id; })
|
||||
};
|
||||
|
||||
if (opts.brand) {
|
||||
createOpts.brand = opts.brand;
|
||||
}
|
||||
if (ctx.volMounts) {
|
||||
createOpts.volumes = ctx.volMounts;
|
||||
}
|
||||
@ -492,6 +495,13 @@ do_create.options = [
|
||||
{
|
||||
group: 'Create options'
|
||||
},
|
||||
{
|
||||
names: ['brand'],
|
||||
helpArg: 'BRAND',
|
||||
type: 'string',
|
||||
help: 'Override the default brand for this instance. Most users will ' +
|
||||
'not need this option.'
|
||||
},
|
||||
{
|
||||
names: ['name', 'n'],
|
||||
helpArg: 'NAME',
|
||||
|
@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2017, Joyent, Inc.
|
||||
* Copyright (c) 2018, Joyent, Inc.
|
||||
*
|
||||
* `triton instance list ...`
|
||||
*/
|
||||
@ -150,6 +150,7 @@ function do_list(subcmd, opts, args, callback) {
|
||||
common.uuidToShortId(inst.image);
|
||||
inst.shortid = inst.id.split('-', 1)[0];
|
||||
var flags = [];
|
||||
if (inst.brand === 'bhyve') flags.push('B');
|
||||
if (inst.docker) flags.push('D');
|
||||
if (inst.firewall_enabled) flags.push('F');
|
||||
if (inst.brand === 'kvm') flags.push('K');
|
||||
@ -208,6 +209,7 @@ do_list.help = [
|
||||
'for convenience):',
|
||||
' shortid* A short ID prefix.',
|
||||
' flags* Single letter flags summarizing some fields:',
|
||||
' "B" the brand is "bhyve"',
|
||||
' "D" docker instance',
|
||||
' "F" firewall is enabled',
|
||||
' "K" the brand is "kvm"',
|
||||
|
@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2016 Joyent, Inc.
|
||||
* Copyright 2018 Joyent, Inc.
|
||||
*
|
||||
* `triton snapshot create ...`
|
||||
*/
|
||||
@ -133,7 +133,7 @@ do_create.help = [
|
||||
'{{usage}}',
|
||||
'',
|
||||
'{{options}}',
|
||||
'Snapshot do not work for instances of type "kvm".'
|
||||
'Snapshots do not work for instances of type "bhyve" or "kvm".'
|
||||
].join('\n');
|
||||
|
||||
do_create.completionArgtypes = ['tritoninstance', 'none'];
|
||||
|
109
lib/tritonapi.js
109
lib/tritonapi.js
@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2017 Joyent, Inc.
|
||||
* Copyright (c) 2018, Joyent, Inc.
|
||||
*/
|
||||
|
||||
/* BEGIN JSSTYLED */
|
||||
@ -233,10 +233,10 @@ 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).
|
||||
* image name, shortid, or uuid, and determines the image object (setting it
|
||||
* as arg.img).
|
||||
*/
|
||||
function _stepImgId(arg, next) {
|
||||
function _stepImg(arg, next) {
|
||||
assert.object(arg.client, 'arg.client');
|
||||
assert.string(arg.image, 'arg.image');
|
||||
|
||||
@ -244,7 +244,7 @@ function _stepImgId(arg, next) {
|
||||
if (err) {
|
||||
next(err);
|
||||
} else {
|
||||
arg.imgId = img.id;
|
||||
arg.img = img;
|
||||
next();
|
||||
}
|
||||
});
|
||||
@ -745,10 +745,10 @@ TritonApi.prototype.exportImage = function exportImage(opts, cb)
|
||||
};
|
||||
|
||||
vasync.pipeline({arg: arg, funcs: [
|
||||
_stepImgId,
|
||||
_stepImg,
|
||||
function cloudApiExportImage(ctx, next) {
|
||||
self.cloudapi.exportImage({
|
||||
id: ctx.imgId, manta_path: opts.manta_path },
|
||||
id: ctx.img.id, manta_path: opts.manta_path },
|
||||
function (err, exportInfo_, res_) {
|
||||
if (err) {
|
||||
next(err);
|
||||
@ -769,6 +769,101 @@ TritonApi.prototype.exportImage = function exportImage(opts, cb)
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Share an image with another account.
|
||||
*
|
||||
* @param {Object} opts
|
||||
* - {String} image The image UUID, name, or short ID. Required.
|
||||
* - {String} account The account UUID. Required.
|
||||
* @param {Function} cb `function (err, img)`
|
||||
* On failure `err` is an error instance, else it is null.
|
||||
* On success: `img` is an image object.
|
||||
*/
|
||||
TritonApi.prototype.shareImage = function shareImage(opts, cb)
|
||||
{
|
||||
var self = this;
|
||||
assert.object(opts, 'opts');
|
||||
assert.string(opts.image, 'opts.image');
|
||||
assert.string(opts.account, 'opts.account');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
var arg = {
|
||||
image: opts.image,
|
||||
client: self
|
||||
};
|
||||
var res;
|
||||
|
||||
vasync.pipeline({arg: arg, funcs: [
|
||||
_stepImg,
|
||||
function validateAcl(ctx, next) {
|
||||
ctx.acl = ctx.img.acl && ctx.img.acl.slice() || [];
|
||||
if (ctx.acl.indexOf(opts.account) === -1) {
|
||||
ctx.acl.push(opts.account);
|
||||
}
|
||||
next();
|
||||
},
|
||||
function cloudApiShareImage(ctx, next) {
|
||||
self.cloudapi.updateImage({id: ctx.img.id, fields: {acl: ctx.acl}},
|
||||
function _updateImageCb(err, img) {
|
||||
res = img;
|
||||
next(err);
|
||||
});
|
||||
}
|
||||
]}, function (err) {
|
||||
cb(err, res);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Unshare an image with another account.
|
||||
*
|
||||
* @param {Object} opts
|
||||
* - {String} image The image UUID, name, or short ID. Required.
|
||||
* - {String} account The account UUID. Required.
|
||||
* @param {Function} cb `function (err, img)`
|
||||
* On failure `err` is an error instance, else it is null.
|
||||
* On success: `img` is an image object.
|
||||
*/
|
||||
TritonApi.prototype.unshareImage = function unshareImage(opts, cb)
|
||||
{
|
||||
var self = this;
|
||||
assert.object(opts, 'opts');
|
||||
assert.string(opts.image, 'opts.image');
|
||||
assert.string(opts.account, 'opts.account');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
var arg = {
|
||||
image: opts.image,
|
||||
client: self
|
||||
};
|
||||
var res;
|
||||
|
||||
vasync.pipeline({arg: arg, funcs: [
|
||||
_stepImg,
|
||||
function validateAcl(ctx, next) {
|
||||
assert.object(ctx.img, 'img');
|
||||
ctx.acl = ctx.img.acl && ctx.img.acl.slice() || [];
|
||||
var aclIdx = ctx.acl.indexOf(opts.account);
|
||||
if (aclIdx === -1) {
|
||||
cb(new errors.TritonError(format('image is not shared with %s',
|
||||
opts.account)));
|
||||
return;
|
||||
}
|
||||
ctx.acl.splice(aclIdx, 1);
|
||||
next();
|
||||
},
|
||||
function cloudApiUnshareImage(ctx, next) {
|
||||
self.cloudapi.updateImage({id: ctx.img.id, fields: {acl: ctx.acl}},
|
||||
function _updateImageCb(err, img) {
|
||||
res = img;
|
||||
next(err);
|
||||
});
|
||||
}
|
||||
]}, function (err) {
|
||||
cb(err, res);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get an active package by ID, exact name, or short ID, in that order.
|
||||
*
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "spearhead",
|
||||
"description": "Spearhead Cloud CLI and client (https://spearhead.cloud)",
|
||||
"version": "5.6.4",
|
||||
"version": "5.8.0",
|
||||
"author": "Spearhead Systems (spearhead.systems)",
|
||||
"homepage": "https://code.spearhead.cloud/Spearhead/node-spearhead",
|
||||
"dependencies": {
|
||||
@ -33,7 +33,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"tape": "4.2.0",
|
||||
"tap-summary": "3.0.2"
|
||||
"tap-summary": "3.0.2",
|
||||
"uuid": "3.2.1"
|
||||
},
|
||||
"main": "./lib",
|
||||
"scripts": {
|
||||
|
@ -26,6 +26,11 @@
|
||||
// to true.
|
||||
"skipAffinityTests": false,
|
||||
|
||||
// Optional. Set to 'true' to skip testing of bhyve things. Some DCs might
|
||||
// not support bhyve (no packages or images available, and/or no CNs with
|
||||
// bhyve compatible hardware).
|
||||
"skipBhyveTests": false,
|
||||
|
||||
// Optional. Set to 'true' to skip testing of KVM things. Some DCs might
|
||||
// not support KVM (no KVM packages or images available).
|
||||
"skipKvmTests": false,
|
||||
@ -36,6 +41,12 @@
|
||||
"resizePackage": "<package name>",
|
||||
"image": "<image uuid, name or name@version>"
|
||||
|
||||
// The params used for test *bhyve* provisions. By default the tests use:
|
||||
// the smallest RAM package with "kvm" in the name, the latest
|
||||
// ubuntu-certified image.
|
||||
"bhyvePackage": "<package name or uuid>",
|
||||
"bhyveImage": "<image uuid, name or name@version>",
|
||||
|
||||
// The params used for test *KVM* provisions. By default the tests use:
|
||||
// the smallest RAM package with "kvm" in the name, the latest
|
||||
// ubuntu-certified image.
|
||||
|
@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2016, Joyent, Inc.
|
||||
* Copyright (c) 2018, Joyent, Inc.
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -15,6 +15,7 @@
|
||||
var format = require('util').format;
|
||||
var os = require('os');
|
||||
var test = require('tape');
|
||||
var uuid = require('uuid');
|
||||
var vasync = require('vasync');
|
||||
|
||||
var common = require('../../lib/common');
|
||||
@ -37,7 +38,7 @@ var testOpts = {
|
||||
|
||||
// --- Tests
|
||||
|
||||
test('triton image ...', testOpts, function (tt) {
|
||||
test('spearhead image ...', testOpts, function (tt) {
|
||||
var imgNameVer = IMAGE_DATA.name + '@' + IMAGE_DATA.version;
|
||||
var originInst;
|
||||
var img;
|
||||
@ -48,7 +49,7 @@ test('triton image ...', testOpts, function (tt) {
|
||||
tt.comment(format('- %s: %j', key, value));
|
||||
});
|
||||
|
||||
// TODO: `triton rm -f` would be helpful for this
|
||||
// TODO: `spearhead rm -f` would be helpful for this
|
||||
tt.test(' setup: rm existing origin inst ' + ORIGIN_ALIAS, function (t) {
|
||||
h.triton(['inst', 'get', '-j', ORIGIN_ALIAS],
|
||||
function (err, stdout, stderr) {
|
||||
@ -199,6 +200,62 @@ test('triton image ...', testOpts, function (tt) {
|
||||
}
|
||||
});
|
||||
|
||||
tt.test(' triton image share ...', function (t) {
|
||||
var dummyUuid = uuid.v4();
|
||||
var argv = ['image', 'share', img.id, dummyUuid];
|
||||
h.safeTriton(t, argv, function (err) {
|
||||
if (err) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
argv = ['image', 'get', '-j', img.id];
|
||||
h.safeTriton(t, argv, function (err2, stdout2) {
|
||||
t.ifErr(err2, 'image get response');
|
||||
if (err2) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
var result = JSON.parse(stdout2);
|
||||
t.ok(result, 'image share result');
|
||||
t.ok(result.acl, 'image share result.acl');
|
||||
if (result.acl && Array.isArray(result.acl)) {
|
||||
t.notEqual(result.acl.indexOf(dummyUuid), -1,
|
||||
'image share result.acl contains uuid');
|
||||
} else {
|
||||
t.fail('image share result does not contain acl array');
|
||||
}
|
||||
unshareImage();
|
||||
});
|
||||
});
|
||||
|
||||
function unshareImage() {
|
||||
argv = ['image', 'unshare', img.id, dummyUuid];
|
||||
h.safeTriton(t, argv, function (err) {
|
||||
if (err) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
argv = ['image', 'get', '-j', img.id];
|
||||
h.safeTriton(t, argv, function (err2, stdout2) {
|
||||
t.ifErr(err2, 'image get response');
|
||||
if (err2) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
var result = JSON.parse(stdout2);
|
||||
t.ok(result, 'image unshare result');
|
||||
if (result.acl && Array.isArray(result.acl)) {
|
||||
t.equal(result.acl.indexOf(dummyUuid), -1,
|
||||
'image unshare result.acl should not contain uuid');
|
||||
} else {
|
||||
t.equal(result.acl, undefined, 'image has no acl');
|
||||
}
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: Once have `triton ssh ...` working in test suite without hangs,
|
||||
// then want to check that the created VM has the markerFile.
|
||||
|
||||
|
92
test/integration/cli-instance-create-bhyve.test.js
Normal file
92
test/integration/cli-instance-create-bhyve.test.js
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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 2018, Joyent, Inc.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Test creating a bhyve VM.
|
||||
*/
|
||||
|
||||
var os = require('os');
|
||||
|
||||
var format = require('util').format;
|
||||
var test = require('tape');
|
||||
|
||||
var h = require('./helpers');
|
||||
|
||||
|
||||
// --- globals
|
||||
|
||||
var INST_ALIAS = 'nodetritontest-instance-create-bhyve-' +
|
||||
os.hostname();
|
||||
|
||||
var testOpts = {
|
||||
skip: !h.CONFIG.allowWriteActions || h.CONFIG.skipBhyveTests
|
||||
};
|
||||
|
||||
|
||||
// --- Tests
|
||||
|
||||
test('triton image ...', testOpts, function (tt) {
|
||||
var imgId;
|
||||
var inst;
|
||||
var pkgId;
|
||||
|
||||
tt.comment('Test config:');
|
||||
Object.keys(h.CONFIG).forEach(function (key) {
|
||||
var value = h.CONFIG[key];
|
||||
tt.comment(format('- %s: %j', key, value));
|
||||
});
|
||||
|
||||
// TODO: `triton rm -f` would be helpful for this
|
||||
tt.test(' setup: rm existing inst ' + INST_ALIAS, function (t) {
|
||||
h.deleteTestInst(t, INST_ALIAS, function onDel() {
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' setup: find image', function (t) {
|
||||
h.getTestBhyveImg(t, function (err, _imgId) {
|
||||
t.ifError(err, 'getTestImg' + (err ? ': ' + err : ''));
|
||||
imgId = _imgId;
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' setup: find test package', function (t) {
|
||||
h.getTestBhyvePkg(t, function (err, _pkgId) {
|
||||
t.ifError(err, 'getTestPkg' + (err ? ': ' + err : ''));
|
||||
pkgId = _pkgId;
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' setup: triton create ... -n ' + INST_ALIAS, function (t) {
|
||||
var argv = ['create', '-wj', '--brand=bhyve', '-n', INST_ALIAS,
|
||||
imgId, pkgId];
|
||||
h.safeTriton(t, argv, function (err, stdout) {
|
||||
var lines = h.jsonStreamParse(stdout);
|
||||
inst = lines[1];
|
||||
t.ok(inst.id, 'inst.id: ' + inst.id);
|
||||
t.equal(lines[1].state, 'running', 'inst is running');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Once have `triton ssh ...` working in test suite without hangs,
|
||||
// then want to check that the created VM works.
|
||||
|
||||
// Remove instance. Add a test timeout, because '-w' on delete doesn't
|
||||
// have a way to know if the attempt failed or if it is just taking a
|
||||
// really long time.
|
||||
tt.test(' cleanup: spearhead rm', {timeout: 10 * 60 * 1000}, function (t) {
|
||||
h.safeTriton(t, ['rm', '-w', inst.id], function () {
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
});
|
@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2017 Joyent, Inc.
|
||||
* Copyright 2018 Joyent, Inc.
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -26,8 +26,8 @@ var testcommon = require('../lib/testcommon');
|
||||
|
||||
|
||||
var CONFIG;
|
||||
var configPath = process.env.TRITON_TEST_CONFIG
|
||||
? path.resolve(process.cwd(), process.env.TRITON_TEST_CONFIG)
|
||||
var configPath = process.env.TSC_TEST_CONFIG
|
||||
? path.resolve(process.cwd(), process.env.SC_TEST_CONFIG)
|
||||
: path.resolve(__dirname, '..', 'config.json');
|
||||
try {
|
||||
CONFIG = require(configPath);
|
||||
@ -44,7 +44,7 @@ try {
|
||||
'CONFIG.profile.insecure');
|
||||
} else if (CONFIG.profileName) {
|
||||
CONFIG.profile = mod_triton.loadProfile({
|
||||
configDir: path.join(process.env.HOME, '.triton'),
|
||||
configDir: path.join(process.env.HOME, '.spearhead'),
|
||||
name: CONFIG.profileName
|
||||
});
|
||||
} else {
|
||||
@ -55,11 +55,11 @@ try {
|
||||
'test/config.json#allowWriteActions');
|
||||
} catch (e) {
|
||||
error('* * *');
|
||||
error('node-triton integration tests require a config file. By default');
|
||||
error('node-spearhead integration tests require a config file. By default');
|
||||
error('it looks for "test/config.json". Or you can set the');
|
||||
error('TRITON_TEST_CONFIG envvar. E.g.:');
|
||||
error('SC_TEST_CONFIG envvar. E.g.:');
|
||||
error('');
|
||||
error(' TRITON_TEST_CONFIG=test/coal.json make test');
|
||||
error(' SC_TEST_CONFIG=test/coal.json make test');
|
||||
error('');
|
||||
error('See "test/config.json.sample" for a starting point for a config.');
|
||||
error('');
|
||||
@ -75,7 +75,7 @@ if (CONFIG.profile.insecure === undefined)
|
||||
if (CONFIG.allowWriteActions === undefined)
|
||||
CONFIG.allowWriteActions = false;
|
||||
|
||||
var TRITON = [process.execPath, path.resolve(__dirname, '../../bin/triton')];
|
||||
var TRITON = [process.execPath, path.resolve(__dirname, '../../bin/spearhead')];
|
||||
var UA = 'node-triton-test';
|
||||
|
||||
var LOG = require('../lib/log');
|
||||
@ -83,10 +83,10 @@ var LOG = require('../lib/log');
|
||||
|
||||
|
||||
/*
|
||||
* Call the `triton` CLI with the given args.
|
||||
* Call the `spearhead` CLI with the given args.
|
||||
*
|
||||
* @param args {String|Array} Required. CLI arguments to `triton ...` (without
|
||||
* the "triton"). This can be an array of args, or a string.
|
||||
* @param args {String|Array} Required. CLI arguments to `spearhead ...` (without
|
||||
* the "spearhead"). This can be an array of args, or a string.
|
||||
* @param opts {Object} Optional.
|
||||
* - opts.cwd {String} cwd option to exec.
|
||||
* @param cb {Function}
|
||||
@ -111,11 +111,11 @@ function triton(args, opts, cb) {
|
||||
PATH: process.env.PATH,
|
||||
HOME: process.env.HOME,
|
||||
SSH_AUTH_SOCK: process.env.SSH_AUTH_SOCK,
|
||||
TRITON_PROFILE: 'env',
|
||||
TRITON_URL: CONFIG.profile.url,
|
||||
TRITON_ACCOUNT: CONFIG.profile.account,
|
||||
TRITON_KEY_ID: CONFIG.profile.keyId,
|
||||
TRITON_TLS_INSECURE: CONFIG.profile.insecure
|
||||
SC_PROFILE: 'env',
|
||||
SC_URL: CONFIG.profile.url,
|
||||
SC_ACCOUNT: CONFIG.profile.account,
|
||||
SC_KEY_ID: CONFIG.profile.keyId,
|
||||
SC_TLS_INSECURE: CONFIG.profile.insecure
|
||||
},
|
||||
cwd: opts.cwd
|
||||
},
|
||||
@ -126,12 +126,12 @@ function triton(args, opts, cb) {
|
||||
|
||||
|
||||
/*
|
||||
* `triton ...` wrapper that:
|
||||
* `spearhead ...` wrapper that:
|
||||
* - tests non-error exit
|
||||
* - tests stderr is empty
|
||||
*
|
||||
* @param {Tape} t - tape test object
|
||||
* @param {Object|Array} opts - options object, or just the `triton` args
|
||||
* @param {Object|Array} opts - options object, or just the `spearhead` args
|
||||
* @param {Function} cb - `function (err, stdout)`
|
||||
* Note that `err` will already have been tested to be falsey via
|
||||
* `t.error(err, ...)`, so it may be fine for the calling test case
|
||||
@ -149,7 +149,7 @@ function safeTriton(t, opts, cb) {
|
||||
|
||||
// t.comment(f('running: triton %s', opts.args.join(' ')));
|
||||
triton(opts.args, function (err, stdout, stderr) {
|
||||
t.error(err, f('ran "triton %s", err=%s', opts.args.join(' '), err));
|
||||
t.error(err, f('ran "spearhead %s", err=%s', opts.args.join(' '), err));
|
||||
t.equal(stderr, '', 'empty stderr');
|
||||
if (opts.json) {
|
||||
try {
|
||||
@ -211,6 +211,46 @@ function getTestImg(t, cb) {
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Find and return an image that can be used for test *bhyve* provisions.
|
||||
*
|
||||
* @param {Tape} t - tape test object
|
||||
* @param {Function} cb - `function (err, imgId)`
|
||||
* where `imgId` is an image identifier (an image name, shortid, or id).
|
||||
*/
|
||||
function getTestBhyveImg(t, cb) {
|
||||
if (CONFIG.bhyveImage) {
|
||||
assert.string(CONFIG.bhyvePackage, 'CONFIG.bhyvePackage');
|
||||
t.ok(CONFIG.bhyveImage, 'bhyveImage from config: ' + CONFIG.bhyveImage);
|
||||
cb(null, CONFIG.bhyveImage);
|
||||
return;
|
||||
}
|
||||
|
||||
var candidateImageNames = {
|
||||
'ubuntu-certified-16.04': true
|
||||
};
|
||||
safeTriton(t, ['img', 'ls', '-j'], function (err, stdout) {
|
||||
var imgId;
|
||||
var imgs = jsonStreamParse(stdout);
|
||||
// Newest images first.
|
||||
tabula.sortArrayOfObjects(imgs, ['-published_at']);
|
||||
var imgRepr;
|
||||
for (var i = 0; i < imgs.length; i++) {
|
||||
var img = imgs[i];
|
||||
if (candidateImageNames[img.name]) {
|
||||
imgId = img.id;
|
||||
imgRepr = f('%s@%s', img.name, img.version);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
t.ok(imgId,
|
||||
f('latest bhyve image (using subset of supported names): %s (%s)',
|
||||
imgId, imgRepr));
|
||||
cb(err, imgId);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Find and return an image that can be used for test *KVM* provisions.
|
||||
*
|
||||
@ -281,6 +321,38 @@ function getTestPkg(t, cb) {
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Find and return an package that can be used for *bhyve* test provisions.
|
||||
*
|
||||
* @param {Tape} t - tape test object
|
||||
* @param {Function} cb - `function (err, pkgId)`
|
||||
* where `pkgId` is an package identifier (a name, shortid, or id).
|
||||
*/
|
||||
function getTestBhyvePkg(t, cb) {
|
||||
if (CONFIG.bhyvePackage) {
|
||||
assert.string(CONFIG.bhyvePackage, 'CONFIG.bhyvePackage');
|
||||
t.ok(CONFIG.bhyvePackage, 'bhyvePackage from config: ' +
|
||||
CONFIG.bhyvePackage);
|
||||
cb(null, CONFIG.bhyvePackage);
|
||||
return;
|
||||
}
|
||||
|
||||
// bhyve uses the same packages as kvm
|
||||
safeTriton(t, ['pkg', 'ls', '-j'], function (err, stdout) {
|
||||
var pkgs = jsonStreamParse(stdout);
|
||||
// Filter on those with 'kvm' in the name.
|
||||
pkgs = pkgs.filter(function (pkg) {
|
||||
return pkg.name.indexOf('kvm') !== -1;
|
||||
});
|
||||
// Smallest RAM first.
|
||||
tabula.sortArrayOfObjects(pkgs, ['memory']);
|
||||
var pkgId = pkgs[0].id;
|
||||
t.ok(pkgId, f('smallest (RAM) available kvm package: %s (%s)',
|
||||
pkgId, pkgs[0].name));
|
||||
cb(null, pkgId);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Find and return an package that can be used for *KVM* test provisions.
|
||||
*
|
||||
@ -361,7 +433,7 @@ function createClient(cb) {
|
||||
mod_triton.createClient({
|
||||
log: LOG,
|
||||
profile: CONFIG.profile,
|
||||
configDir: '~/.triton' // piggy-back on Triton CLI config dir
|
||||
configDir: '~/.spearhead' // piggy-back on Spearhead CLI config dir
|
||||
}, cb);
|
||||
}
|
||||
|
||||
@ -511,8 +583,10 @@ module.exports = {
|
||||
deleteTestImg: deleteTestImg,
|
||||
|
||||
getTestImg: getTestImg,
|
||||
getTestBhyveImg: getTestBhyveImg,
|
||||
getTestKvmImg: getTestKvmImg,
|
||||
getTestPkg: getTestPkg,
|
||||
getTestBhyvePkg: getTestBhyvePkg,
|
||||
getTestKvmPkg: getTestKvmPkg,
|
||||
getResizeTestPkg: getResizeTestPkg,
|
||||
|
||||
|
Reference in New Issue
Block a user