merge with upstream
This commit is contained in:
commit
75ec73a31c
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@
|
||||
/triton-*.tgz
|
||||
.DS_Store
|
||||
.git
|
||||
*.swp
|
||||
|
33
CHANGES.md
33
CHANGES.md
@ -6,10 +6,43 @@ Known issues:
|
||||
|
||||
## not yet released
|
||||
|
||||
## 6.1.2
|
||||
|
||||
- [joyent/node-triton#249] Error when creating or deleting profiles when
|
||||
using node v10.
|
||||
|
||||
## 6.1.1
|
||||
|
||||
- [TRITON-598] Fix error handling for `triton network get-default` when
|
||||
no default network is set on the account.
|
||||
|
||||
## 6.1.0
|
||||
|
||||
- [joyent/node-triton#250] Avoid an error from `triton profile list` if
|
||||
only *some* of the minimal `TRITON_` or `SDC_` envvars are defined.
|
||||
- [TRITON-401] Add `triton network` and `triton vlan` commands, for
|
||||
creating/changing/removing network fabrics and VLANs.
|
||||
- [TRITON-524] Add `triton inst get --credentials ...` option to match
|
||||
`triton inst list --credentials ...` for including generated credentials
|
||||
in instance metadata.
|
||||
- [joyent/node-triton#245] `triton profile` now generates fresh new keys during
|
||||
Docker setup and signs them with an account key, rather than copying (and
|
||||
decrypting) the account key itself. This makes using Docker simpler with keys
|
||||
in an SSH Agent.
|
||||
- [TRITON-53] x-account image clone. A user can make a copy of a shared image
|
||||
using the `triton image clone` command.
|
||||
- [TRITON-53] A shared image (i.e. when the user is on the image.acl) is no
|
||||
longer provisionable by default - you will need to explicitly add the
|
||||
--allow-shared-images cli option when calling `triton create` command to
|
||||
provision from a shared image (or clone the image then provision from the
|
||||
clone).
|
||||
- [TRITON-52] x-DC image copy. A user can copy an image that they own into
|
||||
another datacenter within the same cloud using the `triton image copy` cli
|
||||
command. Example:
|
||||
|
||||
```
|
||||
triton -p us-east-1 image cp my-custom-image us-sw-1
|
||||
```
|
||||
|
||||
## 6.0.0
|
||||
|
||||
|
@ -206,6 +206,7 @@ function CLI() {
|
||||
'package',
|
||||
'network',
|
||||
'fwrule',
|
||||
'vlan',
|
||||
{ group: 'Other Commands' },
|
||||
'info',
|
||||
'account',
|
||||
@ -700,6 +701,9 @@ CLI.prototype.do_package = require('./do_package');
|
||||
CLI.prototype.do_networks = require('./do_networks');
|
||||
CLI.prototype.do_network = require('./do_network');
|
||||
|
||||
// VLANs
|
||||
CLI.prototype.do_vlan = require('./do_vlan');
|
||||
|
||||
// Hidden commands
|
||||
CLI.prototype.do_cloudapi = require('./do_cloudapi');
|
||||
CLI.prototype.do_badger = require('./do_badger');
|
||||
|
341
lib/cloudapi2.js
341
lib/cloudapi2.js
@ -357,6 +357,48 @@ CloudApi.prototype.ping = function ping(opts, cb) {
|
||||
};
|
||||
|
||||
|
||||
// ---- config
|
||||
|
||||
/**
|
||||
* Get config object for the current user.
|
||||
*
|
||||
* @param {Object} opts
|
||||
* @param {Function} cb of the form `function (err, config, res)`
|
||||
*/
|
||||
CloudApi.prototype.getConfig = function getConfig(opts, cb) {
|
||||
assert.object(opts, 'opts');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
var endpoint = this._path(format('/%s/config', this.account));
|
||||
this._request(endpoint, function (err, req, res, body) {
|
||||
cb(err, body, res);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set config object for the current user.
|
||||
*
|
||||
* @param {Object} opts
|
||||
* - {String} default_network: network fabric docker containers are
|
||||
* provisioned on. Optional.
|
||||
* @param {Function} cb of the form `function (err, config, res)`
|
||||
*/
|
||||
CloudApi.prototype.updateConfig = function updateConfig(opts, cb) {
|
||||
assert.object(opts, 'opts');
|
||||
assert.optionalUuid(opts.default_network, 'opts.default_network');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
this._request({
|
||||
method: 'PUT',
|
||||
path: format('/%s/config', this.account),
|
||||
data: opts
|
||||
}, function (err, req, res, body) {
|
||||
cb(err, body, res);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// ---- networks
|
||||
|
||||
/**
|
||||
@ -458,6 +500,228 @@ CloudApi.prototype.UPDATE_NETWORK_IP_FIELDS = {
|
||||
reserved: 'boolean'
|
||||
};
|
||||
|
||||
|
||||
// --- Fabric VLANs
|
||||
|
||||
/**
|
||||
* Creates a network on a fabric (specifically: a fabric VLAN).
|
||||
*
|
||||
* @param {Object} options object containing:
|
||||
* - {Integer} vlan_id (required) VLAN's id, between 0-4095.
|
||||
* - {String} name (required) A name to identify the network.
|
||||
* - {String} subnet (required) CIDR description of the network.
|
||||
* - {String} provision_start_ip (required) First assignable IP addr.
|
||||
* - {String} provision_end_ip (required) Last assignable IP addr.
|
||||
* - {String} gateway (optional) Gateway IP address.
|
||||
* - {String} resolvers (optional) Static routes for hosts on network.
|
||||
* - {String} description (optional)
|
||||
* - {Boolean} internet_nat (optional) Whether to provision an Internet
|
||||
* NAT on the gateway address (default: true).
|
||||
* @param {Function} callback of the form f(err, vlan, res).
|
||||
*/
|
||||
CloudApi.prototype.createFabricNetwork =
|
||||
function createFabricNetwork(opts, cb) {
|
||||
assert.object(opts, 'opts');
|
||||
assert.number(opts.vlan_id, 'opts.vlan_id');
|
||||
assert.string(opts.name, 'opts.name');
|
||||
assert.string(opts.subnet, 'opts.subnet');
|
||||
assert.string(opts.provision_start_ip, 'opts.provision_start_ip');
|
||||
assert.string(opts.provision_end_ip, 'opts.provision_end_ip');
|
||||
assert.optionalString(opts.gateway, 'opts.gateway');
|
||||
assert.optionalString(opts.resolvers, 'opts.resolvers');
|
||||
assert.optionalString(opts.routes, 'opts.routes');
|
||||
assert.optionalBool(opts.internet_nat, 'opts.internet_nat');
|
||||
|
||||
var data = common.objCopy(opts);
|
||||
var vlanId = data.vlan_id;
|
||||
delete data.vlan_id;
|
||||
|
||||
this._request({
|
||||
method: 'POST',
|
||||
path: format('/%s/fabrics/default/vlans/%d/networks', this.account,
|
||||
vlanId),
|
||||
data: data
|
||||
}, function reqCb(err, req, res, body) {
|
||||
cb(err, body, res);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Lists all networks on a VLAN.
|
||||
*
|
||||
* Returns an array of objects.
|
||||
*
|
||||
* @param {Object} options object containing:
|
||||
* - {Integer} vlan_id (required) VLAN's id, between 0-4095.
|
||||
* @param {Function} callback of the form f(err, networks, res).
|
||||
*/
|
||||
CloudApi.prototype.listFabricNetworks =
|
||||
function listFabricNetworks(opts, cb) {
|
||||
assert.object(opts, 'opts');
|
||||
assert.number(opts.vlan_id, 'opts.vlan_id');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
var endpoint = format('/%s/fabrics/default/vlans/%d/networks',
|
||||
this.account, opts.vlan_id);
|
||||
this._passThrough(endpoint, opts, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a fabric network
|
||||
*
|
||||
* @param {Object} opts (object)
|
||||
* - {String} id: The network id. Required.
|
||||
* - {Integer} vlan_id: The VLAN id. Required.
|
||||
* @param {Function} cb of the form `function (err, res)`
|
||||
*/
|
||||
CloudApi.prototype.deleteFabricNetwork =
|
||||
function deleteFabricNetwork(opts, cb) {
|
||||
assert.object(opts, 'opts');
|
||||
assert.uuid(opts.id, 'opts.id');
|
||||
assert.number(opts.vlan_id, 'opts.vlan_id');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
this._request({
|
||||
method: 'DELETE',
|
||||
path: format('/%s/fabrics/default/vlans/%d/networks/%s', this.account,
|
||||
opts.vlan_id, opts.id)
|
||||
}, function (err, req, res) {
|
||||
cb(err, res);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a VLAN on a fabric.
|
||||
*
|
||||
* @param {Object} options object containing:
|
||||
* - {Integer} vlan_id (required) VLAN's id, between 0-4095.
|
||||
* - {String} name (required) A name to identify the VLAN.
|
||||
* - {String} description (optional)
|
||||
* @param {Function} callback of the form f(err, vlan, res).
|
||||
*/
|
||||
CloudApi.prototype.createFabricVlan =
|
||||
function createFabricVlan(opts, cb) {
|
||||
assert.object(opts, 'opts');
|
||||
assert.number(opts.vlan_id, 'opts.vlan_id');
|
||||
assert.string(opts.name, 'opts.name');
|
||||
assert.optionalString(opts.description, 'opts.description');
|
||||
|
||||
var data = {
|
||||
vlan_id: opts.vlan_id
|
||||
};
|
||||
|
||||
Object.keys(this.UPDATE_VLAN_FIELDS).forEach(function (attr) {
|
||||
if (opts[attr] !== undefined)
|
||||
data[attr] = opts[attr];
|
||||
});
|
||||
|
||||
this._request({
|
||||
method: 'POST',
|
||||
path: format('/%s/fabrics/default/vlans', this.account),
|
||||
data: data
|
||||
}, function reqCb(err, req, res, body) {
|
||||
cb(err, body, res);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Lists all the VLANs.
|
||||
*
|
||||
* Returns an array of objects.
|
||||
*
|
||||
* @param opts {Object} Options
|
||||
* @param {Function} callback of the form f(err, vlans, res).
|
||||
*/
|
||||
CloudApi.prototype.listFabricVlans =
|
||||
function listFabricVlans(opts, cb) {
|
||||
assert.object(opts, 'opts');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
var endpoint = format('/%s/fabrics/default/vlans', this.account);
|
||||
this._passThrough(endpoint, opts, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves a VLAN.
|
||||
*
|
||||
* @param {Integer} id: The VLAN id.
|
||||
* @param {Function} callback of the form `function (err, vlan, res)`
|
||||
*/
|
||||
CloudApi.prototype.getFabricVlan =
|
||||
function getFabricVlan(opts, cb) {
|
||||
assert.object(opts, 'opts');
|
||||
assert.number(opts.vlan_id, 'opts.vlan_id');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
var endpoint = format('/%s/fabrics/default/vlans/%d', this.account,
|
||||
opts.vlan_id);
|
||||
this._request(endpoint, function (err, req, res, body) {
|
||||
cb(err, body, res);
|
||||
});
|
||||
};
|
||||
|
||||
// <updatable account field> -> <expected typeof>
|
||||
CloudApi.prototype.UPDATE_VLAN_FIELDS = {
|
||||
name: 'string',
|
||||
description: 'string'
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates a VLAN.
|
||||
*
|
||||
* @param {Object} opts object containing:
|
||||
* - {Integer} id: The VLAN id. Required.
|
||||
* - {String} name: The VLAN name. Optional.
|
||||
* - {String} description: Description of the VLAN. Optional.
|
||||
* @param {Function} callback of the form `function (err, vlan, res)`
|
||||
*/
|
||||
CloudApi.prototype.updateFabricVlan =
|
||||
function updateFabricVlan(opts, cb) {
|
||||
assert.object(opts, 'opts');
|
||||
assert.number(opts.vlan_id, 'opts.vlan_id');
|
||||
assert.optionalString(opts.rule, 'opts.name');
|
||||
assert.optionalString(opts.description, 'opts.description');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
var data = {};
|
||||
Object.keys(this.UPDATE_VLAN_FIELDS).forEach(function (attr) {
|
||||
if (opts[attr] !== undefined)
|
||||
data[attr] = opts[attr];
|
||||
});
|
||||
|
||||
var vlanId = opts.vlan_id;
|
||||
|
||||
this._request({
|
||||
method: 'POST',
|
||||
path: format('/%s/fabrics/default/vlans/%d', this.account, vlanId),
|
||||
data: data
|
||||
}, function onReq(err, req, res, body) {
|
||||
cb(err, body, res);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a VLAN.
|
||||
*
|
||||
* @param {Object} opts (object)
|
||||
* - {Integer} vlan_id: The vlan id. Required.
|
||||
* @param {Function} cb of the form `function (err, res)`
|
||||
*/
|
||||
CloudApi.prototype.deleteFabricVlan =
|
||||
function deleteFabricVlan(opts, cb) {
|
||||
assert.object(opts, 'opts');
|
||||
assert.number(opts.vlan_id, 'opts.vlan_id');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
this._request({
|
||||
method: 'DELETE',
|
||||
path: format('/%s/fabrics/default/vlans/%d', this.account, opts.vlan_id)
|
||||
}, function onReq(err, req, res) {
|
||||
cb(err, res);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// ---- datacenters
|
||||
|
||||
/**
|
||||
@ -775,7 +1039,7 @@ CloudApi.prototype.exportImage = function exportImage(opts, cb) {
|
||||
* - {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) {
|
||||
CloudApi.prototype.updateImage = function updateImage(opts, cb) {
|
||||
assert.uuid(opts.id, 'id');
|
||||
assert.object(opts.fields, 'fields');
|
||||
assert.func(cb, 'cb');
|
||||
@ -793,6 +1057,65 @@ CloudApi.prototype.updateImage = function shareImage(opts, cb) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Clone an image.
|
||||
* <http://apidocs.joyent.com/cloudapi/#CloneImage>
|
||||
*
|
||||
* @param {Object} opts
|
||||
* - {UUID} id Required. The id of the image to update.
|
||||
* @param {Function} cb of the form `function (err, body, res)`
|
||||
*/
|
||||
CloudApi.prototype.cloneImage = function cloneImage(opts, cb) {
|
||||
assert.uuid(opts.id, 'id');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
this._request({
|
||||
method: 'POST',
|
||||
path: format('/%s/images/%s?action=clone', this.account, opts.id),
|
||||
data: {}
|
||||
}, function (err, req, res, body) {
|
||||
if (err) {
|
||||
cb(err, null, res);
|
||||
return;
|
||||
}
|
||||
cb(null, body, res);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Import image from another datacenter in the same cloud.
|
||||
* <http://apidocs.joyent.com/cloudapi/#ImportImageFromDatacenter>
|
||||
*
|
||||
* @param {Object} opts
|
||||
* - {String} datacenter Required. The datacenter to import from.
|
||||
* - {UUID} id Required. The id of the image to update.
|
||||
* @param {Function} cb of the form `function (err, body, res)`
|
||||
*/
|
||||
CloudApi.prototype.importImageFromDatacenter =
|
||||
function importImageFromDatacenter(opts, cb) {
|
||||
assert.string(opts.datacenter, 'datacenter');
|
||||
assert.uuid(opts.id, 'id');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
var p = this._path(format('/%s/images', this.account), {
|
||||
action: 'import-from-datacenter',
|
||||
datacenter: opts.datacenter,
|
||||
id: opts.id
|
||||
});
|
||||
|
||||
this._request({
|
||||
method: 'POST',
|
||||
path: p,
|
||||
data: {}
|
||||
}, 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.
|
||||
*
|
||||
@ -872,13 +1195,12 @@ CloudApi.prototype.getPackage = function getPackage(opts, cb) {
|
||||
/**
|
||||
* Get a machine by id.
|
||||
*
|
||||
* XXX add getCredentials equivalent
|
||||
* XXX cloudapi docs don't doc the credentials=true option
|
||||
*
|
||||
* For backwards compat, calling with `getMachine(id, cb)` is allowed.
|
||||
*
|
||||
* @param {Object} opts
|
||||
* - id {UUID} Required. The machine id.
|
||||
* - {UUID} id - Required. The machine id.
|
||||
* - {Boolean} credentials - Optional. Set to true to include generated
|
||||
* credentials for this machine in `machine.metadata.credentials`.
|
||||
* @param {Function} cb of the form `function (err, machine, res)`
|
||||
*/
|
||||
CloudApi.prototype.getMachine = function getMachine(opts, cb) {
|
||||
@ -887,9 +1209,14 @@ CloudApi.prototype.getMachine = function getMachine(opts, cb) {
|
||||
}
|
||||
assert.object(opts, 'opts');
|
||||
assert.uuid(opts.id, 'opts.id');
|
||||
assert.optionalBool(opts.credentials, 'opts.credentials');
|
||||
|
||||
var endpoint = format('/%s/machines/%s', this.account, opts.id);
|
||||
this._request(endpoint, function (err, req, res, body) {
|
||||
var query = {};
|
||||
if (opts.credentials) {
|
||||
query.credentials = 'true';
|
||||
}
|
||||
var p = this._path(format('/%s/machines/%s', this.account, opts.id), query);
|
||||
this._request(p, function (err, req, res, body) {
|
||||
cb(err, body, res);
|
||||
});
|
||||
};
|
||||
|
@ -618,9 +618,9 @@ function promptYesNo(opts_, cb) {
|
||||
stdin.on('data', onData);
|
||||
|
||||
function postInput() {
|
||||
stdout.write('\n');
|
||||
stdin.setRawMode(false);
|
||||
stdin.pause();
|
||||
stdin.write('\n');
|
||||
stdin.removeListener('data', onData);
|
||||
}
|
||||
|
||||
@ -1461,6 +1461,19 @@ function parseNicStr(nic) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return a short image string that represents the given image object.
|
||||
*
|
||||
* @param img {Object} The image object.
|
||||
* @returns {String} A network object. E.g.
|
||||
* 'a6cf222d-73f4-414c-a427-5c238ef8e1b7 (jillmin@1.0.0)'
|
||||
*/
|
||||
function imageRepr(img) {
|
||||
assert.object(img);
|
||||
|
||||
return format('%s (%s@%s)', img.id, img.name, img.version);
|
||||
}
|
||||
|
||||
|
||||
//---- exports
|
||||
|
||||
@ -1502,6 +1515,7 @@ module.exports = {
|
||||
readStdin: readStdin,
|
||||
validateObject: validateObject,
|
||||
ipv4ToLong: ipv4ToLong,
|
||||
parseNicStr: parseNicStr
|
||||
parseNicStr: parseNicStr,
|
||||
imageRepr: imageRepr
|
||||
};
|
||||
// vim: set softtabstop=4 shiftwidth=4:
|
||||
|
@ -296,12 +296,11 @@ function _loadEnvProfile(profileOverrides) {
|
||||
for (var attr in profileOverrides) {
|
||||
envProfile[attr] = profileOverrides[attr];
|
||||
}
|
||||
|
||||
/*
|
||||
* If none of the above envvars are defined, then there is no env profile.
|
||||
* If missing any of the required vars, then there is no env profile.
|
||||
*/
|
||||
if (!envProfile.account && !envProfile.user && !envProfile.url &&
|
||||
!envProfile.keyId)
|
||||
{
|
||||
if (!envProfile.account || !envProfile.url || !envProfile.keyId) {
|
||||
return null;
|
||||
}
|
||||
validateProfile(envProfile, 'environment variables');
|
||||
@ -363,10 +362,11 @@ function loadProfile(opts) {
|
||||
function loadAllProfiles(opts) {
|
||||
assert.string(opts.configDir, 'opts.configDir');
|
||||
assert.object(opts.log, 'opts.log');
|
||||
assert.optionalObject(opts.profileOverrides, 'opts.profileOverrides');
|
||||
|
||||
var profiles = [];
|
||||
|
||||
var envProfile = _loadEnvProfile();
|
||||
var envProfile = _loadEnvProfile(opts.profileOverrides);
|
||||
if (envProfile) {
|
||||
profiles.push(envProfile);
|
||||
}
|
||||
|
107
lib/do_image/do_clone.js
Normal file
107
lib/do_image/do_clone.js
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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 clone ...`
|
||||
*/
|
||||
|
||||
var vasync = require('vasync');
|
||||
|
||||
var common = require('../common');
|
||||
var errors = require('../errors');
|
||||
|
||||
// ---- the command
|
||||
|
||||
function do_clone(subcmd, opts, args, cb) {
|
||||
if (opts.help) {
|
||||
this.do_help('help', {}, [subcmd], cb);
|
||||
return;
|
||||
} else if (args.length !== 1) {
|
||||
cb(new errors.UsageError(
|
||||
'incorrect number of args: expected 1, got ' + args.length));
|
||||
return;
|
||||
}
|
||||
|
||||
var log = this.top.log;
|
||||
var tritonapi = this.top.tritonapi;
|
||||
|
||||
vasync.pipeline({arg: {cli: this.top}, funcs: [
|
||||
common.cliSetupTritonApi,
|
||||
function cloneImage(ctx, next) {
|
||||
log.trace({dryRun: opts.dry_run, account: ctx.account},
|
||||
'image clone account');
|
||||
|
||||
if (opts.dry_run) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
tritonapi.cloneImage({image: args[0]}, function _cloneCb(err, img) {
|
||||
if (err) {
|
||||
next(new errors.TritonError(err, 'error cloning image'));
|
||||
return;
|
||||
}
|
||||
|
||||
log.trace({img: img}, 'image clone result');
|
||||
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(img));
|
||||
} else {
|
||||
console.log('Cloned image %s to %s',
|
||||
args[0], common.imageRepr(img));
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
]}, cb);
|
||||
}
|
||||
|
||||
do_clone.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 cloning.'
|
||||
},
|
||||
{
|
||||
names: ['json', 'j'],
|
||||
type: 'bool',
|
||||
help: 'JSON stream output.'
|
||||
}
|
||||
];
|
||||
|
||||
do_clone.synopses = [
|
||||
'{{name}} {{cmd}} [OPTIONS] IMAGE'
|
||||
];
|
||||
|
||||
do_clone.help = [
|
||||
/* BEGIN JSSTYLED */
|
||||
'Clone a shared 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 shared images can be cloned.'
|
||||
/* END JSSTYLED */
|
||||
].join('\n');
|
||||
|
||||
do_clone.completionArgtypes = ['tritonimage', 'none'];
|
||||
|
||||
module.exports = do_clone;
|
119
lib/do_image/do_copy.js
Normal file
119
lib/do_image/do_copy.js
Normal file
@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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 copy ...`
|
||||
*/
|
||||
|
||||
var vasync = require('vasync');
|
||||
|
||||
var common = require('../common');
|
||||
var errors = require('../errors');
|
||||
|
||||
// ---- the command
|
||||
|
||||
function do_copy(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: expected 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 copyImage(ctx, next) {
|
||||
log.trace({dryRun: opts.dry_run, account: ctx.account, args: args},
|
||||
'image copy');
|
||||
|
||||
if (opts.dry_run) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
tritonapi.copyImageToDatacenter(
|
||||
{image: args[0], datacenter: args[1]},
|
||||
function (err, img) {
|
||||
if (err) {
|
||||
next(new errors.TritonError(err, 'error copying image'));
|
||||
return;
|
||||
}
|
||||
|
||||
log.trace({img: img}, 'image copy result');
|
||||
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(img));
|
||||
} else {
|
||||
console.log('Copied image %s to datacenter %s',
|
||||
common.imageRepr(img), args[1]);
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
]}, function (err) {
|
||||
cb(err);
|
||||
});
|
||||
}
|
||||
|
||||
do_copy.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 copying.'
|
||||
},
|
||||
{
|
||||
names: ['json', 'j'],
|
||||
type: 'bool',
|
||||
help: 'JSON stream output.'
|
||||
}
|
||||
];
|
||||
|
||||
do_copy.synopses = [
|
||||
'{{name}} {{cmd}} [OPTIONS] IMAGE DATACENTER'
|
||||
];
|
||||
|
||||
do_copy.help = [
|
||||
/* BEGIN JSSTYLED */
|
||||
'Copy image to another datacenter.',
|
||||
'',
|
||||
'{{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).',
|
||||
'You must be the owner of the image to copy it. (You can use `triton image',
|
||||
'clone` to get your own image clone of an image shared to you.)',
|
||||
'',
|
||||
'"DATACENTER" is the name of the datacenter to which to copy your image.',
|
||||
'Use `triton datacenters` to show the available datacenter names.'
|
||||
/* END JSSTYLED */
|
||||
].join('\n');
|
||||
|
||||
do_copy.aliases = ['cp'];
|
||||
|
||||
// TODO: tritonimage should really be 'tritonownedimage' or something to
|
||||
// limit to images owned by this account
|
||||
// TODO: tritondatacenter bash completion
|
||||
do_copy.completionArgtypes = ['tritonimage', 'tritondatacenter', 'none'];
|
||||
|
||||
module.exports = do_copy;
|
@ -5,13 +5,15 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2016 Joyent, Inc.
|
||||
* Copyright 2018 Joyent, Inc.
|
||||
*
|
||||
* `triton image list ...`
|
||||
*/
|
||||
|
||||
var assert = require('assert-plus');
|
||||
var format = require('util').format;
|
||||
var tabula = require('tabula');
|
||||
var vasync = require('vasync');
|
||||
|
||||
var common = require('../common');
|
||||
var errors = require('../errors');
|
||||
@ -67,17 +69,45 @@ function do_list(subcmd, opts, args, callback) {
|
||||
listOpts.state = 'all';
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var tritonapi = this.top.tritonapi;
|
||||
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||
if (setupErr) {
|
||||
callback(setupErr);
|
||||
return;
|
||||
}
|
||||
|
||||
vasync.pipeline({ arg: {}, funcs: [
|
||||
function setupTritonApi(_, next) {
|
||||
common.cliSetupTritonApi({cli: self.top}, next);
|
||||
},
|
||||
function getImages(ctx, next) {
|
||||
tritonapi.listImages(listOpts, function onRes(err, imgs, res) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
next(err);
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.imgs = imgs;
|
||||
next();
|
||||
});
|
||||
},
|
||||
function getUserAccount(ctx, next) {
|
||||
// If using json output, or when there are no images that use an ACL
|
||||
// - we don't need to fetch the account, as the account is only used
|
||||
// to check if the image is shared (i.e. the account is in the image
|
||||
// ACL) so it can output image flags in non-json mode.
|
||||
if (opts.json || ctx.imgs.every(function _checkAcl(img) {
|
||||
return !Array.isArray(img.acl) || img.acl.length === 0;
|
||||
})) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
tritonapi.cloudapi.getAccount(function _accountCb(err, account) {
|
||||
if (err) {
|
||||
next(err);
|
||||
return;
|
||||
}
|
||||
ctx.account = account;
|
||||
next();
|
||||
});
|
||||
},
|
||||
function formatImages(ctx, next) {
|
||||
var imgs = ctx.imgs;
|
||||
if (opts.json) {
|
||||
common.jsonStream(imgs);
|
||||
} else {
|
||||
@ -99,6 +129,20 @@ function do_list(subcmd, opts, args, callback) {
|
||||
if (img.origin) flags.push('I');
|
||||
if (img['public']) flags.push('P');
|
||||
if (img.state !== 'active') flags.push('X');
|
||||
|
||||
// Add image sharing flags.
|
||||
if (Array.isArray(img.acl) && img.acl.length > 0) {
|
||||
assert.string(ctx.account.id, 'ctx.account.id');
|
||||
if (img.owner === ctx.account.id) {
|
||||
// This image has been shared with other accounts.
|
||||
flags.push('+');
|
||||
}
|
||||
if (img.acl.indexOf(ctx.account.id) !== -1) {
|
||||
// This image has been shared with this account.
|
||||
flags.push('S');
|
||||
}
|
||||
}
|
||||
|
||||
img.flags = flags.length ? flags.join('') : undefined;
|
||||
}
|
||||
|
||||
@ -108,9 +152,9 @@ function do_list(subcmd, opts, args, callback) {
|
||||
sort: sort
|
||||
});
|
||||
}
|
||||
callback();
|
||||
});
|
||||
});
|
||||
next();
|
||||
}
|
||||
]}, callback);
|
||||
}
|
||||
|
||||
do_list.options = [
|
||||
@ -156,6 +200,8 @@ do_list.help = [
|
||||
' shortid* A short ID prefix.',
|
||||
' flags* Single letter flags summarizing some fields:',
|
||||
' "P" image is public',
|
||||
' "+" you are sharing this image with others',
|
||||
' "S" this image has been shared with you',
|
||||
' "I" an incremental image (i.e. has an origin)',
|
||||
' "X" has a state *other* than "active"',
|
||||
' pubdate* Short form of "published_at" with just the date',
|
||||
|
@ -33,6 +33,8 @@ function ImageCLI(top) {
|
||||
'help',
|
||||
'list',
|
||||
'get',
|
||||
'clone',
|
||||
'copy',
|
||||
'create',
|
||||
'delete',
|
||||
'export',
|
||||
@ -51,6 +53,8 @@ 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_clone = require('./do_clone');
|
||||
ImageCLI.prototype.do_copy = require('./do_copy');
|
||||
ImageCLI.prototype.do_create = require('./do_create');
|
||||
ImageCLI.prototype.do_delete = require('./do_delete');
|
||||
ImageCLI.prototype.do_export = require('./do_export');
|
||||
|
@ -289,6 +289,9 @@ function do_create(subcmd, opts, args, cb) {
|
||||
createOpts['tag.'+key] = ctx.tags[key];
|
||||
});
|
||||
}
|
||||
if (opts.allow_shared_images) {
|
||||
createOpts.allow_shared_images = true;
|
||||
}
|
||||
|
||||
for (var i = 0; i < opts._order.length; i++) {
|
||||
var opt = opts._order[i];
|
||||
@ -498,6 +501,11 @@ do_create.options = [
|
||||
'Joyent-provided images, the user-script is run at every boot ' +
|
||||
'of the instance. This is a shortcut for `-M user-script=FILE`.'
|
||||
},
|
||||
{
|
||||
names: ['allow-shared-images'],
|
||||
type: 'bool',
|
||||
help: 'Allow instance creation to use a shared image.'
|
||||
},
|
||||
|
||||
{
|
||||
group: 'Other options'
|
||||
|
@ -25,7 +25,10 @@ function do_get(subcmd, opts, args, cb) {
|
||||
cb(setupErr);
|
||||
return;
|
||||
}
|
||||
tritonapi.getInstance(args[0], function (err, inst) {
|
||||
tritonapi.getInstance({
|
||||
id: args[0],
|
||||
credentials: opts.credentials
|
||||
}, function onInst(err, inst) {
|
||||
if (inst) {
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(inst));
|
||||
@ -44,6 +47,13 @@ do_get.options = [
|
||||
type: 'bool',
|
||||
help: 'Show this help.'
|
||||
},
|
||||
{
|
||||
names: ['credentials'],
|
||||
type: 'bool',
|
||||
help: 'Include generated credentials, in the "metadata.credentials" ' +
|
||||
'field, if any. Typically used with "-j", though one can show ' +
|
||||
'values with "-o metadata.credentials".'
|
||||
},
|
||||
{
|
||||
names: ['json', 'j'],
|
||||
type: 'bool',
|
||||
@ -58,7 +68,6 @@ do_get.help = [
|
||||
'{{usage}}',
|
||||
'',
|
||||
'{{options}}',
|
||||
'',
|
||||
'Where "INST" is an instance name, id, or short id.',
|
||||
'',
|
||||
'A *deleted* instance may still respond with the instance object. In that',
|
||||
|
187
lib/do_network/do_create.js
Normal file
187
lib/do_network/do_create.js
Normal file
@ -0,0 +1,187 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* `triton network create ...`
|
||||
*/
|
||||
|
||||
var assert = require('assert-plus');
|
||||
var format = require('util').format;
|
||||
var jsprim = require('jsprim');
|
||||
|
||||
var common = require('../common');
|
||||
var errors = require('../errors');
|
||||
|
||||
|
||||
var OPTIONAL_OPTS = ['description', 'gateway', 'resolvers', 'routes'];
|
||||
|
||||
|
||||
function do_create(subcmd, opts, args, cb) {
|
||||
assert.optionalString(opts.name, 'opts.name');
|
||||
assert.optionalString(opts.subnet, 'opts.subnet');
|
||||
assert.optionalString(opts.start_ip, 'opts.start_ip');
|
||||
assert.optionalString(opts.end_ip, 'opts.end_ip');
|
||||
assert.optionalString(opts.description, 'opts.description');
|
||||
assert.optionalString(opts.gateway, 'opts.gateway');
|
||||
assert.optionalString(opts.resolvers, 'opts.resolvers');
|
||||
assert.optionalString(opts.routes, 'opts.routes');
|
||||
assert.optionalBool(opts.no_nat, 'opts.no_nat');
|
||||
assert.optionalBool(opts.json, 'opts.json');
|
||||
assert.optionalBool(opts.help, 'opts.help');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
if (opts.help) {
|
||||
this.do_help('help', {}, [subcmd], cb);
|
||||
return;
|
||||
}
|
||||
if (args.length === 0) {
|
||||
cb(new errors.UsageError('missing VLAN argument'));
|
||||
return;
|
||||
} else if (args.length > 1) {
|
||||
cb(new errors.UsageError('incorrect number of arguments'));
|
||||
return;
|
||||
}
|
||||
|
||||
var vlanId = jsprim.parseInteger(args[0], { allowSign: false });
|
||||
if (typeof (vlanId) !== 'number') {
|
||||
cb(new errors.UsageError('VLAN must be an integer'));
|
||||
return;
|
||||
}
|
||||
|
||||
var createOpts = {
|
||||
vlan_id: vlanId,
|
||||
name: opts.name,
|
||||
subnet: opts.subnet,
|
||||
provision_start_ip: opts.start_ip,
|
||||
provision_end_ip: opts.end_ip
|
||||
};
|
||||
|
||||
if (opts.no_nat) {
|
||||
createOpts.internet_nat = false;
|
||||
}
|
||||
|
||||
OPTIONAL_OPTS.forEach(function (attr) {
|
||||
if (opts[attr]) {
|
||||
createOpts[attr] = opts[attr];
|
||||
}
|
||||
});
|
||||
|
||||
var cli = this.top;
|
||||
|
||||
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
|
||||
if (setupErr) {
|
||||
cb(setupErr);
|
||||
return;
|
||||
}
|
||||
|
||||
var cloudapi = cli.tritonapi.cloudapi;
|
||||
|
||||
cloudapi.createFabricNetwork(createOpts, function onCreate(err, net) {
|
||||
if (err) {
|
||||
cb(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(net));
|
||||
} else {
|
||||
console.log('Created network %s (%s)', net.name, net.id);
|
||||
}
|
||||
|
||||
cb();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
do_create.options = [
|
||||
{
|
||||
names: ['help', 'h'],
|
||||
type: 'bool',
|
||||
help: 'Show this help.'
|
||||
},
|
||||
{
|
||||
names: ['json', 'j'],
|
||||
type: 'bool',
|
||||
help: 'JSON stream output.'
|
||||
},
|
||||
{
|
||||
names: ['name', 'n'],
|
||||
type: 'string',
|
||||
helpArg: 'NAME',
|
||||
help: 'Name of the NETWORK.'
|
||||
},
|
||||
{
|
||||
names: ['description', 'D'],
|
||||
type: 'string',
|
||||
helpArg: 'DESC',
|
||||
help: 'Description of the NETWORK.'
|
||||
},
|
||||
{
|
||||
names: ['subnet'],
|
||||
type: 'string',
|
||||
helpArg: 'SUBNET',
|
||||
help: 'A CIDR string describing the NETWORK.'
|
||||
},
|
||||
{
|
||||
names: ['start_ip'],
|
||||
type: 'string',
|
||||
helpArg: 'START_IP',
|
||||
help: 'First assignable IP address on NETWORK.'
|
||||
},
|
||||
{
|
||||
names: ['end_ip'],
|
||||
type: 'string',
|
||||
helpArg: 'END_IP',
|
||||
help: 'Last assignable IP address on NETWORK.'
|
||||
},
|
||||
{
|
||||
names: ['gateway'],
|
||||
type: 'string',
|
||||
helpArg: 'GATEWAY',
|
||||
help: 'Gateway IP address.'
|
||||
},
|
||||
{
|
||||
names: ['resolvers'],
|
||||
type: 'string',
|
||||
helpArg: 'RESOLVERS',
|
||||
help: 'Resolver IP addresses.'
|
||||
},
|
||||
{
|
||||
names: ['routes'],
|
||||
type: 'string',
|
||||
helpArg: 'ROUTES',
|
||||
help: 'Static routes for hosts on NETWORK.'
|
||||
},
|
||||
{
|
||||
names: ['no_nat'],
|
||||
type: 'bool',
|
||||
helpArg: 'NO_NAT',
|
||||
help: 'Disable creation of an Internet NAT zone on GATEWAY.'
|
||||
}
|
||||
];
|
||||
|
||||
do_create.synopses = ['{{name}} {{cmd}} [OPTIONS] VLAN'];
|
||||
|
||||
do_create.help = [
|
||||
'Create a network on a VLAN.',
|
||||
'',
|
||||
'{{usage}}',
|
||||
'',
|
||||
'{{options}}',
|
||||
'',
|
||||
'Example:',
|
||||
' triton network create -n accounting --subnet=192.168.0.0/24',
|
||||
' --start_ip=192.168.0.1 --end_ip=192.168.0.254 2'
|
||||
].join('\n');
|
||||
|
||||
do_create.helpOpts = {
|
||||
helpCol: 25
|
||||
};
|
||||
|
||||
module.exports = do_create;
|
85
lib/do_network/do_delete.js
Normal file
85
lib/do_network/do_delete.js
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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 network delete ...`
|
||||
*/
|
||||
|
||||
var assert = require('assert-plus');
|
||||
var format = require('util').format;
|
||||
var vasync = require('vasync');
|
||||
|
||||
var common = require('../common');
|
||||
var errors = require('../errors');
|
||||
|
||||
|
||||
function do_delete(subcmd, opts, args, cb) {
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
if (opts.help) {
|
||||
this.do_help('help', {}, [subcmd], cb);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.length < 1) {
|
||||
cb(new errors.UsageError('missing NETWORK argument(s)'));
|
||||
return;
|
||||
}
|
||||
|
||||
var cli = this.top;
|
||||
var networks = args;
|
||||
|
||||
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
|
||||
if (setupErr) {
|
||||
cb(setupErr);
|
||||
return;
|
||||
}
|
||||
|
||||
vasync.forEachParallel({
|
||||
inputs: networks,
|
||||
func: function deleteOne(id, next) {
|
||||
cli.tritonapi.deleteFabricNetwork({ id: id },
|
||||
function onDelete(err) {
|
||||
if (err) {
|
||||
next(err);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Deleted network %s', id);
|
||||
next();
|
||||
});
|
||||
}
|
||||
}, cb);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
do_delete.options = [
|
||||
{
|
||||
names: ['help', 'h'],
|
||||
type: 'bool',
|
||||
help: 'Show this help.'
|
||||
}
|
||||
];
|
||||
|
||||
do_delete.synopses = ['{{name}} {{cmd}} NETWORK [NETWORK ...]'];
|
||||
|
||||
do_delete.help = [
|
||||
'Remove a fabric network.',
|
||||
'',
|
||||
'{{usage}}',
|
||||
'',
|
||||
'{{options}}',
|
||||
'Where NETWORK is a network id (full UUID), name, or short id.'
|
||||
].join('\n');
|
||||
|
||||
do_delete.aliases = ['rm'];
|
||||
|
||||
do_delete.completionArgtypes = ['tritonnetwork'];
|
||||
|
||||
module.exports = do_delete;
|
78
lib/do_network/do_get_default.js
Normal file
78
lib/do_network/do_get_default.js
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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 network get-default ...`
|
||||
*/
|
||||
|
||||
var assert = require('assert-plus');
|
||||
var vasync = require('vasync');
|
||||
|
||||
var common = require('../common');
|
||||
var errors = require('../errors');
|
||||
|
||||
|
||||
function do_get_default(subcmd, opts, args, cb) {
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
if (opts.help) {
|
||||
this.do_help('help', {}, [subcmd], cb);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.length > 0) {
|
||||
cb(new errors.UsageError('incorrect number of arguments'));
|
||||
return;
|
||||
}
|
||||
|
||||
var cli = this.top;
|
||||
|
||||
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
|
||||
if (setupErr) {
|
||||
cb(setupErr);
|
||||
return;
|
||||
}
|
||||
|
||||
cli.tritonapi.cloudapi.getConfig({}, function getConf(err, conf) {
|
||||
if (err) {
|
||||
cb(err);
|
||||
return;
|
||||
}
|
||||
|
||||
var defaultNetwork = conf.default_network;
|
||||
|
||||
if (!defaultNetwork) {
|
||||
cb(new Error('account has no default network configured'));
|
||||
return;
|
||||
}
|
||||
|
||||
cli.handlerFromSubcmd('network').dispatch({
|
||||
subcmd: 'get',
|
||||
opts: opts,
|
||||
args: [defaultNetwork]
|
||||
}, cb);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
do_get_default.options = require('./do_get').options;
|
||||
|
||||
do_get_default.synopses = ['{{name}} {{cmd}}'];
|
||||
|
||||
do_get_default.help = [
|
||||
'Get default network.',
|
||||
'',
|
||||
'{{usage}}',
|
||||
'',
|
||||
'{{options}}'
|
||||
].join('\n');
|
||||
|
||||
do_get_default.completionArgtypes = ['tritonnetwork'];
|
||||
|
||||
module.exports = do_get_default;
|
@ -66,14 +66,23 @@ function do_list(subcmd, opts, args, callback) {
|
||||
common.cliSetupTritonApi,
|
||||
|
||||
function searchNetworks(arg, next) {
|
||||
self.top.tritonapi.cloudapi.listNetworks(function (err, networks) {
|
||||
// since this command is also used by do_vlan/do_networks.js
|
||||
if (opts.vlan_id) {
|
||||
self.top.tritonapi.listFabricNetworks({
|
||||
vlan_id: opts.vlan_id
|
||||
}, listedNetworks);
|
||||
} else {
|
||||
self.top.tritonapi.cloudapi.listNetworks({}, listedNetworks);
|
||||
}
|
||||
|
||||
function listedNetworks(err, networks) {
|
||||
if (err) {
|
||||
next(err);
|
||||
return;
|
||||
}
|
||||
arg.networks = networks;
|
||||
next();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
function filterNetworks(arg, next) {
|
||||
|
92
lib/do_network/do_set_default.js
Normal file
92
lib/do_network/do_set_default.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 2017 Joyent, Inc.
|
||||
*
|
||||
* `triton network set-default ...`
|
||||
*/
|
||||
|
||||
var assert = require('assert-plus');
|
||||
var vasync = require('vasync');
|
||||
|
||||
var common = require('../common');
|
||||
var errors = require('../errors');
|
||||
|
||||
|
||||
function do_set_default(subcmd, opts, args, cb) {
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
if (opts.help) {
|
||||
this.do_help('help', {}, [subcmd], cb);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.length === 0) {
|
||||
cb(new errors.UsageError('missing NETWORK argument'));
|
||||
return;
|
||||
} else if (args.length > 1) {
|
||||
cb(new errors.UsageError('incorrect number of arguments'));
|
||||
return;
|
||||
}
|
||||
|
||||
var cli = this.top;
|
||||
|
||||
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
|
||||
if (setupErr) {
|
||||
cb(setupErr);
|
||||
return;
|
||||
}
|
||||
|
||||
cli.tritonapi.getNetwork(args[0], function onNetwork(err, net) {
|
||||
if (err) {
|
||||
cb(err);
|
||||
return;
|
||||
}
|
||||
|
||||
var params = {
|
||||
default_network: net.id
|
||||
};
|
||||
|
||||
var cloudapi = cli.tritonapi.cloudapi;
|
||||
|
||||
cloudapi.updateConfig(params, function onUpdate(err2) {
|
||||
if (err2) {
|
||||
cb(err2);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Set network %s (%s) as default.', net.name,
|
||||
net.id);
|
||||
cb();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
do_set_default.options = [
|
||||
{
|
||||
names: ['help', 'h'],
|
||||
type: 'bool',
|
||||
help: 'Show this help.'
|
||||
}
|
||||
];
|
||||
|
||||
do_set_default.synopses = ['{{name}} {{cmd}} NETWORK'];
|
||||
|
||||
do_set_default.help = [
|
||||
'Set default network.',
|
||||
'',
|
||||
'{{usage}}',
|
||||
'',
|
||||
'{{options}}',
|
||||
'Where NETWORK is a network id (full UUID), name, or short id.'
|
||||
].join('\n');
|
||||
|
||||
do_set_default.completionArgtypes = ['tritonnetwork'];
|
||||
|
||||
module.exports = do_set_default;
|
@ -33,7 +33,11 @@ function NetworkCLI(top) {
|
||||
'help',
|
||||
'list',
|
||||
'get',
|
||||
'ip'
|
||||
'ip',
|
||||
'create',
|
||||
'delete',
|
||||
'get-default',
|
||||
'set-default'
|
||||
]
|
||||
});
|
||||
}
|
||||
@ -47,6 +51,10 @@ NetworkCLI.prototype.init = function init(opts, args, cb) {
|
||||
NetworkCLI.prototype.do_list = require('./do_list');
|
||||
NetworkCLI.prototype.do_get = require('./do_get');
|
||||
NetworkCLI.prototype.do_ip = require('./do_ip');
|
||||
NetworkCLI.prototype.do_create = require('./do_create');
|
||||
NetworkCLI.prototype.do_delete = require('./do_delete');
|
||||
NetworkCLI.prototype.do_get_default = require('./do_get_default');
|
||||
NetworkCLI.prototype.do_set_default = require('./do_set_default');
|
||||
|
||||
|
||||
module.exports = NetworkCLI;
|
||||
|
@ -31,7 +31,8 @@ function _listProfiles(cli, opts, args, cb) {
|
||||
try {
|
||||
profiles = mod_config.loadAllProfiles({
|
||||
configDir: cli.configDir,
|
||||
log: cli.log
|
||||
log: cli.log,
|
||||
profileOverrides: cli._cliOptsAsProfile()
|
||||
});
|
||||
} catch (e) {
|
||||
return cb(e);
|
||||
|
123
lib/do_vlan/do_create.js
Normal file
123
lib/do_vlan/do_create.js
Normal file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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 vlan create ...`
|
||||
*/
|
||||
|
||||
var assert = require('assert-plus');
|
||||
var format = require('util').format;
|
||||
var vasync = require('vasync');
|
||||
|
||||
var common = require('../common');
|
||||
var errors = require('../errors');
|
||||
|
||||
|
||||
function do_create(subcmd, opts, args, cb) {
|
||||
assert.optionalString(opts.name, 'opts.name');
|
||||
assert.optionalString(opts.description, 'opts.description');
|
||||
assert.optionalBool(opts.json, 'opts.json');
|
||||
assert.optionalBool(opts.help, 'opts.help');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
if (opts.help) {
|
||||
this.do_help('help', {}, [subcmd], cb);
|
||||
return;
|
||||
}
|
||||
if (args.length === 0) {
|
||||
cb(new errors.UsageError('missing VLAN argument'));
|
||||
return;
|
||||
} else if (args.length > 1) {
|
||||
cb(new errors.UsageError('incorrect number of arguments'));
|
||||
return;
|
||||
}
|
||||
|
||||
var createOpts = {
|
||||
vlan_id: +args[0]
|
||||
};
|
||||
if (opts.name) {
|
||||
createOpts.name = opts.name;
|
||||
}
|
||||
if (opts.description) {
|
||||
createOpts.description = opts.description;
|
||||
}
|
||||
|
||||
var cli = this.top;
|
||||
|
||||
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
|
||||
if (setupErr) {
|
||||
cb(setupErr);
|
||||
return;
|
||||
}
|
||||
|
||||
var cloudapi = cli.tritonapi.cloudapi;
|
||||
cloudapi.createFabricVlan(createOpts, function onCreate(err, vlan) {
|
||||
if (err) {
|
||||
cb(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(vlan));
|
||||
} else {
|
||||
if (vlan.name) {
|
||||
console.log('Created vlan %s (%d)', vlan.name,
|
||||
vlan.vlan_id);
|
||||
} else {
|
||||
console.log('Created vlan %d', vlan.vlan_id);
|
||||
}
|
||||
}
|
||||
|
||||
cb();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
do_create.options = [
|
||||
{
|
||||
names: ['help', 'h'],
|
||||
type: 'bool',
|
||||
help: 'Show this help.'
|
||||
},
|
||||
{
|
||||
names: ['json', 'j'],
|
||||
type: 'bool',
|
||||
help: 'JSON stream output.'
|
||||
},
|
||||
{
|
||||
names: ['name', 'n'],
|
||||
type: 'string',
|
||||
helpArg: 'NAME',
|
||||
help: 'Name of the VLAN.'
|
||||
},
|
||||
{
|
||||
names: ['description', 'D'],
|
||||
type: 'string',
|
||||
helpArg: 'DESC',
|
||||
help: 'Description of the VLAN.'
|
||||
}
|
||||
];
|
||||
|
||||
do_create.synopses = ['{{name}} {{cmd}} [OPTIONS] VLAN'];
|
||||
|
||||
do_create.help = [
|
||||
'Create a VLAN.',
|
||||
'',
|
||||
'{{usage}}',
|
||||
'',
|
||||
'{{options}}',
|
||||
'Example:',
|
||||
' triton vlan create -n "dmz" -D "Demilitarized zone" 73'
|
||||
].join('\n');
|
||||
|
||||
do_create.helpOpts = {
|
||||
helpCol: 25
|
||||
};
|
||||
|
||||
module.exports = do_create;
|
85
lib/do_vlan/do_delete.js
Normal file
85
lib/do_vlan/do_delete.js
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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 vlan delete ...`
|
||||
*/
|
||||
|
||||
var assert = require('assert-plus');
|
||||
var format = require('util').format;
|
||||
var vasync = require('vasync');
|
||||
|
||||
var common = require('../common');
|
||||
var errors = require('../errors');
|
||||
|
||||
|
||||
function do_delete(subcmd, opts, args, cb) {
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
if (opts.help) {
|
||||
this.do_help('help', {}, [subcmd], cb);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.length < 1) {
|
||||
cb(new errors.UsageError('missing VLAN argument(s)'));
|
||||
return;
|
||||
}
|
||||
|
||||
var cli = this.top;
|
||||
var vlanIds = args;
|
||||
|
||||
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
|
||||
if (setupErr) {
|
||||
cb(setupErr);
|
||||
return;
|
||||
}
|
||||
|
||||
vasync.forEachParallel({
|
||||
inputs: vlanIds,
|
||||
func: function deleteOne(id, next) {
|
||||
cli.tritonapi.deleteFabricVlan({ vlan_id: id },
|
||||
function onDelete(err) {
|
||||
if (err) {
|
||||
next(err);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Deleted vlan %s', id);
|
||||
next();
|
||||
});
|
||||
}
|
||||
}, cb);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
do_delete.options = [
|
||||
{
|
||||
names: ['help', 'h'],
|
||||
type: 'bool',
|
||||
help: 'Show this help.'
|
||||
}
|
||||
];
|
||||
|
||||
do_delete.synopses = ['{{name}} {{cmd}} VLAN [VLAN ...]'];
|
||||
|
||||
do_delete.help = [
|
||||
'Remove a VLAN.',
|
||||
'',
|
||||
'{{usage}}',
|
||||
'',
|
||||
'{{options}}',
|
||||
'Where VLAN is a VLAN id or name.'
|
||||
].join('\n');
|
||||
|
||||
do_delete.aliases = ['rm'];
|
||||
|
||||
do_delete.completionArgtypes = ['tritonvlan'];
|
||||
|
||||
module.exports = do_delete;
|
88
lib/do_vlan/do_get.js
Normal file
88
lib/do_vlan/do_get.js
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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 vlan get ...`
|
||||
*/
|
||||
|
||||
var assert = require('assert-plus');
|
||||
|
||||
var common = require('../common');
|
||||
var errors = require('../errors');
|
||||
|
||||
|
||||
function do_get(subcmd, opts, args, cb) {
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
if (opts.help) {
|
||||
this.do_help('help', {}, [subcmd], cb);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.length === 0) {
|
||||
cb(new errors.UsageError('missing VLAN argument'));
|
||||
return;
|
||||
} else if (args.length > 1) {
|
||||
cb(new errors.UsageError('incorrect number of arguments'));
|
||||
return;
|
||||
}
|
||||
|
||||
var id = args[0];
|
||||
var cli = this.top;
|
||||
|
||||
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
|
||||
if (setupErr) {
|
||||
cb(setupErr);
|
||||
return;
|
||||
}
|
||||
|
||||
cli.tritonapi.getFabricVlan(id, function onGet(err, vlan) {
|
||||
if (err) {
|
||||
cb(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(vlan));
|
||||
} else {
|
||||
console.log(JSON.stringify(vlan, null, 4));
|
||||
}
|
||||
|
||||
cb();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
do_get.options = [
|
||||
{
|
||||
names: ['help', 'h'],
|
||||
type: 'bool',
|
||||
help: 'Show this help.'
|
||||
},
|
||||
{
|
||||
names: ['json', 'j'],
|
||||
type: 'bool',
|
||||
help: 'JSON stream output.'
|
||||
}
|
||||
];
|
||||
|
||||
do_get.synopses = ['{{name}} {{cmd}} VLAN'];
|
||||
|
||||
do_get.help = [
|
||||
'Show a specific VLAN.',
|
||||
'',
|
||||
'{{usage}}',
|
||||
'',
|
||||
'{{options}}',
|
||||
'Where VLAN is a VLAN id or name.'
|
||||
].join('\n');
|
||||
|
||||
do_get.completionArgtypes = ['tritonvlan', 'none'];
|
||||
|
||||
module.exports = do_get;
|
123
lib/do_vlan/do_list.js
Normal file
123
lib/do_vlan/do_list.js
Normal file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* `triton vlan list ...`
|
||||
*/
|
||||
|
||||
var assert = require('assert-plus');
|
||||
var tabula = require('tabula');
|
||||
|
||||
var common = require('../common');
|
||||
var errors = require('../errors');
|
||||
|
||||
|
||||
var COLUMNS_DEFAULT = 'vlan_id,name,description';
|
||||
var SORT_DEFAULT = 'vlan_id';
|
||||
var VALID_FILTERS = ['vlan_id', 'name', 'description'];
|
||||
|
||||
|
||||
function do_list(subcmd, opts, args, cb) {
|
||||
assert.object(opts, 'opts');
|
||||
assert.array(args, 'args');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
if (opts.help) {
|
||||
this.do_help('help', {}, [subcmd], cb);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var filters = common.objFromKeyValueArgs(args, {
|
||||
validKeys: VALID_FILTERS,
|
||||
disableDotted: true
|
||||
});
|
||||
} catch (e) {
|
||||
cb(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (filters.vlan_id !== undefined) {
|
||||
filters.vlan_id = +filters.vlan_id;
|
||||
}
|
||||
|
||||
var cli = this.top;
|
||||
|
||||
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
|
||||
if (setupErr) {
|
||||
cb(setupErr);
|
||||
return;
|
||||
}
|
||||
|
||||
var cloudapi = cli.tritonapi.cloudapi;
|
||||
cloudapi.listFabricVlans({}, function onList(err, vlans) {
|
||||
if (err) {
|
||||
cb(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// do filtering
|
||||
Object.keys(filters).forEach(function doFilter(key) {
|
||||
var val = filters[key];
|
||||
vlans = vlans.filter(function (vlan) {
|
||||
return vlan[key] === val;
|
||||
});
|
||||
});
|
||||
|
||||
if (opts.json) {
|
||||
common.jsonStream(vlans);
|
||||
} else {
|
||||
var columns = COLUMNS_DEFAULT;
|
||||
|
||||
if (opts.o) {
|
||||
columns = opts.o;
|
||||
}
|
||||
|
||||
columns = columns.split(',');
|
||||
var sort = opts.s.split(',');
|
||||
|
||||
tabula(vlans, {
|
||||
skipHeader: opts.H,
|
||||
columns: columns,
|
||||
sort: sort
|
||||
});
|
||||
}
|
||||
cb();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
do_list.options = [
|
||||
{
|
||||
names: ['help', 'h'],
|
||||
type: 'bool',
|
||||
help: 'Show this help.'
|
||||
}
|
||||
].concat(common.getCliTableOptions({
|
||||
sortDefault: SORT_DEFAULT
|
||||
}));
|
||||
|
||||
do_list.synopses = ['{{name}} {{cmd}} [OPTIONS] [FILTERS]'];
|
||||
|
||||
do_list.help = [
|
||||
'List VLANs.',
|
||||
'',
|
||||
'{{usage}}',
|
||||
'',
|
||||
'Filters:',
|
||||
' FIELD=<integer> Number filter. Supported fields: vlan_id',
|
||||
' FIELD=<string> String filter. Supported fields: name, description',
|
||||
'',
|
||||
'{{options}}',
|
||||
'Filters are applied client-side (i.e. done by the triton command itself).'
|
||||
].join('\n');
|
||||
|
||||
do_list.aliases = ['ls'];
|
||||
|
||||
module.exports = do_list;
|
52
lib/do_vlan/do_networks.js
Normal file
52
lib/do_vlan/do_networks.js
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 vlan networks ...`
|
||||
*/
|
||||
|
||||
var errors = require('../errors');
|
||||
|
||||
|
||||
function do_networks(subcmd, opts, args, cb) {
|
||||
if (opts.help) {
|
||||
this.do_help('help', {}, [subcmd], cb);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.length === 0) {
|
||||
cb(new errors.UsageError('missing VLAN argument'));
|
||||
return;
|
||||
} else if (args.length > 1) {
|
||||
cb(new errors.UsageError('incorrect number of arguments'));
|
||||
return;
|
||||
}
|
||||
|
||||
opts.vlan_id = args[0];
|
||||
|
||||
this.top.handlerFromSubcmd('network').dispatch({
|
||||
subcmd: 'list',
|
||||
opts: opts,
|
||||
args: []
|
||||
}, cb);
|
||||
}
|
||||
|
||||
do_networks.synopses = ['{{name}} {{cmd}} [OPTIONS] VLAN'];
|
||||
|
||||
do_networks.help = [
|
||||
'Show all networks on a VLAN.',
|
||||
'',
|
||||
'{{usage}}',
|
||||
'',
|
||||
'{{options}}',
|
||||
'Where VLAN is a VLAN id or name.'
|
||||
].join('\n');
|
||||
|
||||
do_networks.options = require('../do_network/do_list').options;
|
||||
|
||||
module.exports = do_networks;
|
201
lib/do_vlan/do_update.js
Normal file
201
lib/do_vlan/do_update.js
Normal file
@ -0,0 +1,201 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* `triton vlan update ...`
|
||||
*/
|
||||
|
||||
var assert = require('assert-plus');
|
||||
var format = require('util').format;
|
||||
var fs = require('fs');
|
||||
var vasync = require('vasync');
|
||||
|
||||
var common = require('../common');
|
||||
var errors = require('../errors');
|
||||
|
||||
|
||||
var UPDATE_VLAN_FIELDS
|
||||
= require('../cloudapi2').CloudApi.prototype.UPDATE_VLAN_FIELDS;
|
||||
|
||||
|
||||
function do_update(subcmd, opts, args, cb) {
|
||||
if (opts.help) {
|
||||
this.do_help('help', {}, [subcmd], cb);
|
||||
return;
|
||||
}
|
||||
|
||||
var log = this.log;
|
||||
var tritonapi = this.top.tritonapi;
|
||||
|
||||
if (args.length === 0) {
|
||||
cb(new errors.UsageError('missing VLAN argument'));
|
||||
return;
|
||||
}
|
||||
|
||||
var id = args.shift();
|
||||
|
||||
vasync.pipeline({arg: {}, funcs: [
|
||||
function gatherDataArgs(ctx, next) {
|
||||
if (opts.file) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ctx.data = common.objFromKeyValueArgs(args, {
|
||||
disableDotted: true,
|
||||
typeHintFromKey: UPDATE_VLAN_FIELDS
|
||||
});
|
||||
} catch (err) {
|
||||
next(err);
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
},
|
||||
|
||||
function gatherDataFile(ctx, next) {
|
||||
if (!opts.file || opts.file === '-') {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
var input = fs.readFileSync(opts.file, 'utf8');
|
||||
|
||||
try {
|
||||
ctx.data = JSON.parse(input);
|
||||
} catch (err) {
|
||||
next(new errors.TritonError(format(
|
||||
'invalid JSON for vlan update in "%s": %s',
|
||||
opts.file, err)));
|
||||
return;
|
||||
}
|
||||
next();
|
||||
},
|
||||
|
||||
function gatherDataStdin(ctx, next) {
|
||||
if (opts.file !== '-') {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
var stdin = '';
|
||||
|
||||
process.stdin.resume();
|
||||
process.stdin.on('data', function (chunk) {
|
||||
stdin += chunk;
|
||||
});
|
||||
|
||||
process.stdin.on('error', console.error);
|
||||
|
||||
process.stdin.on('end', function () {
|
||||
try {
|
||||
ctx.data = JSON.parse(stdin);
|
||||
} catch (err) {
|
||||
log.trace({stdin: stdin},
|
||||
'invalid VLAN update JSON on stdin');
|
||||
next(new errors.TritonError(format(
|
||||
'invalid JSON for VLAN update on stdin: %s',
|
||||
err)));
|
||||
return;
|
||||
}
|
||||
next();
|
||||
});
|
||||
},
|
||||
|
||||
function validateIt(ctx, next) {
|
||||
assert.object(ctx.data, 'ctx.data');
|
||||
|
||||
var keys = Object.keys(ctx.data);
|
||||
|
||||
if (keys.length === 0) {
|
||||
console.log('No fields given for VLAN update');
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var key = keys[i];
|
||||
var value = ctx.data[key];
|
||||
var type = UPDATE_VLAN_FIELDS[key];
|
||||
if (!type) {
|
||||
next(new errors.UsageError(format('unknown or ' +
|
||||
'unupdateable field: %s (updateable fields are: %s)',
|
||||
key,
|
||||
Object.keys(UPDATE_VLAN_FIELDS).sort().join(', '))));
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof (value) !== type) {
|
||||
next(new errors.UsageError(format('field "%s" must be ' +
|
||||
'of type "%s", but got a value of type "%s"', key,
|
||||
type, typeof (value))));
|
||||
return;
|
||||
}
|
||||
}
|
||||
next();
|
||||
},
|
||||
|
||||
function updateAway(ctx, next) {
|
||||
var data = ctx.data;
|
||||
data.vlan_id = id;
|
||||
|
||||
tritonapi.updateFabricVlan(data, function onUpdate(err) {
|
||||
if (err) {
|
||||
next(err);
|
||||
return;
|
||||
}
|
||||
|
||||
delete data.vlan_id;
|
||||
console.log('Updated vlan %s (fields: %s)', id,
|
||||
Object.keys(data).join(', '));
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
]}, cb);
|
||||
}
|
||||
|
||||
do_update.options = [
|
||||
{
|
||||
names: ['help', 'h'],
|
||||
type: 'bool',
|
||||
help: 'Show this help.'
|
||||
},
|
||||
{
|
||||
names: ['file', 'f'],
|
||||
type: 'string',
|
||||
helpArg: 'JSON-FILE',
|
||||
help: 'A file holding a JSON file of updates, or "-" to read ' +
|
||||
'JSON from stdin.'
|
||||
}
|
||||
];
|
||||
|
||||
do_update.synopses = [
|
||||
'{{name}} {{cmd}} VLAN [FIELD=VALUE ...]',
|
||||
'{{name}} {{cmd}} -f JSON-FILE VLAN'
|
||||
];
|
||||
|
||||
do_update.help = [
|
||||
'Update a VLAN.',
|
||||
'',
|
||||
'{{usage}}',
|
||||
'',
|
||||
'{{options}}',
|
||||
|
||||
'Updateable fields:',
|
||||
' ' + Object.keys(UPDATE_VLAN_FIELDS).sort().map(function (f) {
|
||||
return f + ' (' + UPDATE_VLAN_FIELDS[f] + ')';
|
||||
}).join(', '),
|
||||
'',
|
||||
'Where VLAN is a VLAN id or name.'
|
||||
].join('\n');
|
||||
|
||||
do_update.completionArgtypes = ['tritonvlan', 'tritonupdatevlanfield'];
|
||||
|
||||
module.exports = do_update;
|
55
lib/do_vlan/index.js
Normal file
55
lib/do_vlan/index.js
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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 vlan ...`
|
||||
*/
|
||||
|
||||
var Cmdln = require('cmdln').Cmdln;
|
||||
var util = require('util');
|
||||
|
||||
|
||||
|
||||
// ---- CLI class
|
||||
|
||||
function VlanCLI(top) {
|
||||
this.top = top;
|
||||
|
||||
Cmdln.call(this, {
|
||||
name: top.name + ' vlan',
|
||||
desc: 'List and manage Triton fabric VLANs.',
|
||||
helpSubcmds: [
|
||||
'help',
|
||||
'list',
|
||||
'get',
|
||||
'create',
|
||||
'update',
|
||||
'delete',
|
||||
{ group: '' },
|
||||
'networks'
|
||||
],
|
||||
helpOpts: {
|
||||
minHelpCol: 23
|
||||
}
|
||||
});
|
||||
}
|
||||
util.inherits(VlanCLI, Cmdln);
|
||||
|
||||
VlanCLI.prototype.init = function init(opts, args, cb) {
|
||||
this.log = this.top.log;
|
||||
Cmdln.prototype.init.apply(this, arguments);
|
||||
};
|
||||
|
||||
VlanCLI.prototype.do_list = require('./do_list');
|
||||
VlanCLI.prototype.do_create = require('./do_create');
|
||||
VlanCLI.prototype.do_get = require('./do_get');
|
||||
VlanCLI.prototype.do_update = require('./do_update');
|
||||
VlanCLI.prototype.do_delete = require('./do_delete');
|
||||
VlanCLI.prototype.do_networks = require('./do_networks');
|
||||
|
||||
module.exports = VlanCLI;
|
415
lib/tritonapi.js
415
lib/tritonapi.js
@ -133,7 +133,7 @@ var errors = require('./errors');
|
||||
|
||||
// ---- globals
|
||||
|
||||
var CLOUDAPI_ACCEPT_VERSION = '~8';
|
||||
var CLOUDAPI_ACCEPT_VERSION = '~9||~8';
|
||||
|
||||
|
||||
|
||||
@ -282,7 +282,9 @@ function _stepFwRuleId(arg, next) {
|
||||
* and determines the network id (setting it as `arg.netId`).
|
||||
*/
|
||||
function _stepNetId(arg, next) {
|
||||
assert.object(arg, 'arg');
|
||||
assert.object(arg.client, 'arg.client');
|
||||
assert.func(next, 'next');
|
||||
|
||||
var id = arg.network || arg.id;
|
||||
assert.string(id, 'arg.network || arg.id');
|
||||
@ -291,7 +293,7 @@ function _stepNetId(arg, next) {
|
||||
arg.netId = id;
|
||||
next();
|
||||
} else {
|
||||
arg.client.getNetwork(id, function (err, net) {
|
||||
arg.client.getNetwork(id, function onGet(err, net) {
|
||||
if (err) {
|
||||
next(err);
|
||||
} else {
|
||||
@ -302,6 +304,98 @@ function _stepNetId(arg, next) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A function appropriate for `vasync.pipeline` funcs that takes a `arg.id` and
|
||||
* optionally a `arg.vlan_id`, where `arg.id` is a network name, shortid or
|
||||
* uuid, and `arg.vlan_id` is a VLAN's id or name. Sets the network id as
|
||||
* `arg.netId` and the VLAN id as `arg.vlanId`.
|
||||
*/
|
||||
function _stepFabricNetId(arg, next) {
|
||||
assert.object(arg, 'arg');
|
||||
assert.object(arg.client, 'arg.client');
|
||||
assert.string(arg.id, 'arg.id');
|
||||
assert.func(next, 'next');
|
||||
|
||||
var id = arg.id;
|
||||
var vlanId = arg.vlan_id;
|
||||
var vlanIdType = typeof (vlanId);
|
||||
|
||||
if (common.isUUID(id) && vlanIdType === 'number') {
|
||||
arg.netId = id;
|
||||
arg.vlanId = vlanId;
|
||||
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
arg.client.getNetwork(id, function onGetNetwork(err, net) {
|
||||
if (err) {
|
||||
next(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (vlanIdType === 'number') {
|
||||
assert.equal(net.vlan_id, vlanId, 'VLAN belongs to network');
|
||||
}
|
||||
|
||||
if (vlanIdType === 'number' || vlanIdType === 'undefined') {
|
||||
arg.netId = net.id;
|
||||
arg.vlanId = net.vlan_id;
|
||||
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
// at this point the only type left we support are strings
|
||||
assert.string(vlanId, 'arg.vlan_id');
|
||||
|
||||
arg.client.getFabricVlan(vlanId, function onGetFabric(err2, vlan) {
|
||||
if (err2) {
|
||||
next(err2);
|
||||
return;
|
||||
}
|
||||
|
||||
assert.equal(net.vlan_id, vlan.vlan_id, 'VLAN belongs to network');
|
||||
arg.netId = net.id;
|
||||
arg.vlanId = net.vlan_id;
|
||||
next();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A function appropriate for `vasync.pipeline` funcs that takes a
|
||||
* `arg.vlan_id`, where that is either a VLAN's id or name. Sets the
|
||||
* VLAN id as `arg.vlanId`.
|
||||
*/
|
||||
function _stepFabricVlanId(arg, next) {
|
||||
assert.object(arg, 'arg');
|
||||
assert.object(arg.client, 'arg.client');
|
||||
assert.ok(typeof (arg.vlan_id) === 'string' ||
|
||||
typeof (arg.vlan_id) === 'number', 'arg.vlan_id');
|
||||
assert.func(next, 'next');
|
||||
|
||||
var vlanId = arg.vlan_id;
|
||||
|
||||
if (typeof (vlanId) === 'number') {
|
||||
arg.vlanId = vlanId;
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
arg.client.getFabricVlan(vlanId, function onGet(err, vlan) {
|
||||
if (err) {
|
||||
next(err);
|
||||
return;
|
||||
}
|
||||
|
||||
arg.vlanId = vlan.vlan_id;
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
//---- TritonApi class
|
||||
|
||||
/**
|
||||
@ -399,7 +493,7 @@ TritonApi.prototype._setupProfile = function _setupProfile(cb) {
|
||||
? true : !profile.insecure);
|
||||
var acceptVersion = profile.acceptVersion || CLOUDAPI_ACCEPT_VERSION;
|
||||
|
||||
var opts = {
|
||||
self._cloudapiOpts = {
|
||||
url: profile.url,
|
||||
account: profile.actAsAccount || profile.account,
|
||||
principal: {
|
||||
@ -415,9 +509,9 @@ TritonApi.prototype._setupProfile = function _setupProfile(cb) {
|
||||
if (profile.privKey) {
|
||||
var key = sshpk.parsePrivateKey(profile.privKey);
|
||||
this.keyPair =
|
||||
opts.principal.keyPair =
|
||||
self._cloudapiOpts.principal.keyPair =
|
||||
auth.KeyPair.fromPrivateKey(key);
|
||||
this.cloudapi = cloudapi.createClient(opts);
|
||||
this.cloudapi = cloudapi.createClient(self._cloudapiOpts);
|
||||
cb(null);
|
||||
} else {
|
||||
var kr = new auth.KeyRing();
|
||||
@ -427,8 +521,8 @@ TritonApi.prototype._setupProfile = function _setupProfile(cb) {
|
||||
cb(err);
|
||||
return;
|
||||
}
|
||||
self.keyPair = opts.principal.keyPair = kp;
|
||||
self.cloudapi = cloudapi.createClient(opts);
|
||||
self.keyPair = self._cloudapiOpts.principal.keyPair = kp;
|
||||
self.cloudapi = cloudapi.createClient(self._cloudapiOpts);
|
||||
cb(null);
|
||||
});
|
||||
}
|
||||
@ -864,6 +958,132 @@ TritonApi.prototype.unshareImage = function unshareImage(opts, cb)
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Clone a shared image.
|
||||
*
|
||||
* @param {Object} opts
|
||||
* - {String} image The image UUID, name, or short ID. Required.
|
||||
* @param {Function} cb `function (err, img)`
|
||||
* On failure `err` is an error instance, else it is null.
|
||||
* On success: `img` is the cloned image object.
|
||||
*/
|
||||
TritonApi.prototype.cloneImage = function cloneImage(opts, cb)
|
||||
{
|
||||
var self = this;
|
||||
assert.object(opts, 'opts');
|
||||
assert.string(opts.image, 'opts.image');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
var arg = {
|
||||
image: opts.image,
|
||||
client: self
|
||||
};
|
||||
var img;
|
||||
|
||||
vasync.pipeline({arg: arg, funcs: [
|
||||
_stepImg,
|
||||
function cloudApiCloneImage(ctx, next) {
|
||||
self.cloudapi.cloneImage({id: ctx.img.id},
|
||||
function _cloneImageCb(err, img_) {
|
||||
img = img_;
|
||||
next(err);
|
||||
});
|
||||
}
|
||||
]}, function (err) {
|
||||
cb(err, img);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Copy an image to another Datacenter.
|
||||
*
|
||||
* Note: This somewhat flips the sense of the CloudAPI ImportImageFromDatacenter
|
||||
* endpoint, in that it instead calls *the target DC* to pull from this
|
||||
* profile's DC. The target DC's CloudAPI URL is determined from this DC's
|
||||
* `ListDatacenters` endpoint. It is assumed that all other Triton profile
|
||||
* attributes (account, keyId) suffice to auth with the target DC.
|
||||
*
|
||||
* @param {Object} opts
|
||||
* - {String} datacenter The datacenter name to copy to. Required.
|
||||
* - {String} image The image UUID, name, or short ID. Required.
|
||||
* @param {Function} cb `function (err, img)`
|
||||
* On failure `err` is an error instance, else it is null.
|
||||
* On success: `img` is the copied image object.
|
||||
*/
|
||||
TritonApi.prototype.copyImageToDatacenter =
|
||||
function copyImageToDatacenter(opts, cb) {
|
||||
var self = this;
|
||||
assert.object(opts, 'opts');
|
||||
assert.string(opts.datacenter, 'opts.datacenter');
|
||||
assert.string(opts.image, 'opts.image');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
var arg = {
|
||||
client: self,
|
||||
datacenter: opts.datacenter,
|
||||
image: opts.image
|
||||
};
|
||||
var img;
|
||||
|
||||
vasync.pipeline({arg: arg, funcs: [
|
||||
_stepImg,
|
||||
function getDatacenters(ctx, next) {
|
||||
self.cloudapi.listDatacenters({}, function (err, dcs, res) {
|
||||
if (err) {
|
||||
next(err);
|
||||
return;
|
||||
}
|
||||
if (!dcs.hasOwnProperty(ctx.datacenter)) {
|
||||
next(new errors.TritonError(format(
|
||||
'"%s" is not a valid datacenter name, possible ' +
|
||||
'names are: %s',
|
||||
ctx.datacenter,
|
||||
Object.keys(dcs).join(', '))));
|
||||
return;
|
||||
}
|
||||
ctx.datacenterUrl = dcs[ctx.datacenter];
|
||||
assert.string(ctx.datacenterUrl, 'ctx.datacenterUrl');
|
||||
|
||||
// CloudAPI added image copying in 9.2.0, which is also
|
||||
// the version that included this header.
|
||||
var currentDcName = res.headers['triton-datacenter-name'];
|
||||
if (!currentDcName) {
|
||||
next(new errors.TritonError(err, format(
|
||||
'this datacenter does not support image copying (%s)',
|
||||
res.headers['server'])));
|
||||
return;
|
||||
}
|
||||
// Note: currentDcName is where the image currently resides.
|
||||
ctx.currentDcName = currentDcName;
|
||||
|
||||
next();
|
||||
});
|
||||
},
|
||||
function cloudApiImportImageFromDatacenter(ctx, next) {
|
||||
var targetCloudapiOpts = jsprim.mergeObjects(
|
||||
{
|
||||
url: ctx.datacenterUrl,
|
||||
log: self.log.child({datacenter: opts.datacenter}, true)
|
||||
},
|
||||
null,
|
||||
self._cloudapiOpts
|
||||
);
|
||||
var targetCloudapi = cloudapi.createClient(targetCloudapiOpts);
|
||||
|
||||
targetCloudapi.importImageFromDatacenter({
|
||||
datacenter: ctx.currentDcName,
|
||||
id: ctx.img.id
|
||||
}, function _importImageCb(err, img_) {
|
||||
targetCloudapi.close();
|
||||
img = img_;
|
||||
next(err);
|
||||
});
|
||||
}
|
||||
]}, function (err) {
|
||||
cb(err, img);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get an active package by ID, exact name, or short ID, in that order.
|
||||
*
|
||||
@ -934,7 +1154,7 @@ TritonApi.prototype.getNetwork = function getNetwork(name, cb) {
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
if (common.isUUID(name)) {
|
||||
this.cloudapi.getNetwork(name, function (err, net) {
|
||||
this.cloudapi.getNetwork(name, function onGet(err, net) {
|
||||
if (err) {
|
||||
if (err.restCode === 'ResourceNotFound') {
|
||||
// Wrap with *our* ResourceNotFound for exitStatus=3.
|
||||
@ -947,7 +1167,7 @@ TritonApi.prototype.getNetwork = function getNetwork(name, cb) {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.cloudapi.listNetworks(function (err, nets) {
|
||||
this.cloudapi.listNetworks({}, function onList(err, nets) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
@ -984,6 +1204,72 @@ TritonApi.prototype.getNetwork = function getNetwork(name, cb) {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* List all fabric networks on a VLAN. Takes a network's VLAN ID or name as an
|
||||
* argument.
|
||||
*/
|
||||
TritonApi.prototype.listFabricNetworks =
|
||||
function listFabricNetworks(opts, cb) {
|
||||
assert.object(opts, 'opts');
|
||||
assert.ok(typeof (opts.vlan_id) === 'string' ||
|
||||
typeof (opts.vlan_id) === 'number', 'opts.vlan_id');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
var self = this;
|
||||
var networks;
|
||||
|
||||
vasync.pipeline({
|
||||
arg: {client: self, vlan_id: opts.vlan_id}, funcs: [
|
||||
_stepFabricVlanId,
|
||||
|
||||
function listNetworks(arg, next) {
|
||||
self.cloudapi.listFabricNetworks({
|
||||
vlan_id: arg.vlanId
|
||||
}, function listCb(err, nets) {
|
||||
if (err) {
|
||||
next(err);
|
||||
return;
|
||||
}
|
||||
|
||||
networks = nets;
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
]}, function (err) {
|
||||
cb(err, networks);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Delete a fabric network by ID, exact name, or short ID, in that order.
|
||||
* Can accept a network's VLAN ID or name as an optional argument.
|
||||
*
|
||||
* If the name is ambiguous, then this errors out.
|
||||
*/
|
||||
TritonApi.prototype.deleteFabricNetwork =
|
||||
function deleteFabricNetwork(opts, cb) {
|
||||
assert.object(opts, 'opts');
|
||||
assert.string(opts.id, 'opts.id');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
var self = this;
|
||||
|
||||
vasync.pipeline({
|
||||
arg: {client: self, id: opts.id, vlan_id: opts.vlan_id},
|
||||
funcs: [
|
||||
_stepFabricNetId,
|
||||
|
||||
function deleteNetwork(arg, next) {
|
||||
self.cloudapi.deleteFabricNetwork({
|
||||
id: arg.netId, vlan_id: arg.vlanId
|
||||
}, next);
|
||||
}
|
||||
]}, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* List a network's IPs.
|
||||
*
|
||||
@ -1123,6 +1409,8 @@ TritonApi.prototype.updateNetworkIp = function updateNetworkIp(opts, cb) {
|
||||
* - {Array} fields: Optional. An array of instance field names that are
|
||||
* wanted by the caller. This *can* allow the implementation to avoid
|
||||
* extra API calls. E.g. `['id', 'name']`.
|
||||
* - {Boolean} credentials: Optional. Set to true to include generated
|
||||
* credentials for this instance in `inst.metadata.credentials`.
|
||||
* @param {Function} cb `function (err, inst, res)`
|
||||
* Note that deleted instances will result in `err` being a
|
||||
* `InstanceDeletedError` and `inst` being defined. On success, `res` is
|
||||
@ -1137,6 +1425,7 @@ TritonApi.prototype.getInstance = function getInstance(opts, cb) {
|
||||
assert.object(opts, 'opts');
|
||||
assert.string(opts.id, 'opts.id');
|
||||
assert.optionalArrayOfString(opts.fields, 'opts.fields');
|
||||
assert.optionalBool(opts.credentials, 'opts.credentials');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
/*
|
||||
@ -1176,7 +1465,10 @@ TritonApi.prototype.getInstance = function getInstance(opts, cb) {
|
||||
return next();
|
||||
}
|
||||
}
|
||||
self.cloudapi.getMachine(uuid, function (err, inst_, res_) {
|
||||
self.cloudapi.getMachine({
|
||||
id: uuid,
|
||||
credentials: opts.credentials
|
||||
}, function onMachine(err, inst_, res_) {
|
||||
res = res_;
|
||||
inst = inst_;
|
||||
err = errFromGetMachineErr(err);
|
||||
@ -1264,7 +1556,10 @@ TritonApi.prototype.getInstance = function getInstance(opts, cb) {
|
||||
}
|
||||
|
||||
var uuid = instFromList.id;
|
||||
self.cloudapi.getMachine(uuid, function (err, inst_, res_) {
|
||||
self.cloudapi.getMachine({
|
||||
id: uuid,
|
||||
credentials: opts.credentials
|
||||
}, function onMachine(err, inst_, res_) {
|
||||
res = res_;
|
||||
inst = inst_;
|
||||
err = errFromGetMachineErr(err);
|
||||
@ -2128,14 +2423,15 @@ function deleteAllInstanceTags(opts, cb) {
|
||||
*/
|
||||
TritonApi.prototype.addNic =
|
||||
function addNic(opts, cb) {
|
||||
assert.object(opts, 'opts');
|
||||
assert.string(opts.id, 'opts.id');
|
||||
assert.ok(opts.network, 'opts.network');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
var self = this;
|
||||
var nic;
|
||||
var pipeline = [];
|
||||
var res;
|
||||
var nic;
|
||||
|
||||
switch (typeof (opts.network)) {
|
||||
case 'string':
|
||||
@ -2189,8 +2485,8 @@ function listNics(opts, cb) {
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
var self = this;
|
||||
var res;
|
||||
var nics;
|
||||
var res;
|
||||
|
||||
vasync.pipeline({arg: {client: self, id: opts.id}, funcs: [
|
||||
_stepInstId,
|
||||
@ -2198,7 +2494,7 @@ function listNics(opts, cb) {
|
||||
function list(arg, next) {
|
||||
self.cloudapi.listNics({
|
||||
id: arg.instId
|
||||
}, function (err, _nics, _res) {
|
||||
}, function onList(err, _nics, _res) {
|
||||
res = _res;
|
||||
res.instId = arg.instId; // gross hack, in case caller needs it
|
||||
nics = _nics;
|
||||
@ -2236,7 +2532,7 @@ function getNic(opts, cb) {
|
||||
self.cloudapi.getNic({
|
||||
id: arg.instId,
|
||||
mac: arg.mac
|
||||
}, function (err, _nic, _res) {
|
||||
}, function onGet(err, _nic, _res) {
|
||||
res = _res;
|
||||
res.instId = arg.instId; // gross hack, in case caller needs it
|
||||
nic = _nic;
|
||||
@ -2274,7 +2570,7 @@ function removeNic(opts, cb) {
|
||||
self.cloudapi.removeNic({
|
||||
id: arg.instId,
|
||||
mac: arg.mac
|
||||
}, function (err, _res) {
|
||||
}, function onRemove(err, _res) {
|
||||
res = _res;
|
||||
res.instId = arg.instId; // gross hack, in case caller needs it
|
||||
next(err);
|
||||
@ -2626,6 +2922,93 @@ TritonApi.prototype.deleteFirewallRule = function deleteFirewallRule(opts, cb) {
|
||||
};
|
||||
|
||||
|
||||
// ---- VLANs
|
||||
|
||||
/**
|
||||
* Get a VLAN by ID or exact name, in that order.
|
||||
*
|
||||
* If the name is ambiguous, then this errors out.
|
||||
*/
|
||||
TritonApi.prototype.getFabricVlan = function getFabricVlan(name, cb) {
|
||||
assert.ok(typeof (name) === 'string' ||
|
||||
typeof (name) === 'number', 'name');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
if (+name >= 0 && +name < 4096) {
|
||||
this.cloudapi.getFabricVlan({vlan_id: +name}, function on(err, vlan) {
|
||||
if (err) {
|
||||
if (err.restCode === 'ResourceNotFound') {
|
||||
// Wrap with our own ResourceNotFound for exitStatus=3.
|
||||
err = new errors.ResourceNotFoundError(err,
|
||||
format('vlan with id %s was not found', name));
|
||||
}
|
||||
cb(err);
|
||||
} else {
|
||||
cb(null, vlan);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.cloudapi.listFabricVlans({}, function onList(err, vlans) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
var nameMatches = [];
|
||||
for (var i = 0; i < vlans.length; i++) {
|
||||
var vlan = vlans[i];
|
||||
if (vlan.name === name) {
|
||||
nameMatches.push(vlan);
|
||||
}
|
||||
}
|
||||
|
||||
if (nameMatches.length === 1) {
|
||||
cb(null, nameMatches[0]);
|
||||
} else if (nameMatches.length > 1) {
|
||||
cb(new errors.TritonError(format(
|
||||
'vlan name "%s" is ambiguous: matches %d vlans',
|
||||
name, nameMatches.length)));
|
||||
} else {
|
||||
cb(new errors.ResourceNotFoundError(format(
|
||||
'no vlan with name "%s" was found', name)));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Delete a VLAN by ID or exact name, in that order.
|
||||
*
|
||||
* If the name is ambiguous, then this errors out.
|
||||
*/
|
||||
TritonApi.prototype.deleteFabricVlan = function deleteFabricVlan(opts, cb) {
|
||||
assert.object(opts, 'opts');
|
||||
assert.ok(typeof (opts.vlan_id) === 'string' ||
|
||||
typeof (opts.vlan_id) === 'number', 'opts.vlan_id');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
var self = this;
|
||||
var vlanId = opts.vlan_id;
|
||||
|
||||
if (+vlanId >= 0 && +vlanId < 4096) {
|
||||
deleteVlan(+vlanId);
|
||||
} else {
|
||||
self.getFabricVlan(vlanId, function onGet(err, vlan) {
|
||||
if (err) {
|
||||
cb(err);
|
||||
return;
|
||||
}
|
||||
|
||||
deleteVlan(vlan.vlan_id);
|
||||
});
|
||||
}
|
||||
|
||||
function deleteVlan(id) {
|
||||
self.cloudapi.deleteFabricVlan({vlan_id: id}, cb);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// ---- RBAC
|
||||
|
||||
/**
|
||||
|
@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2016 Joyent, Inc.
|
||||
* Copyright 2018 Joyent, Inc.
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -18,6 +18,8 @@ var test = require('tape');
|
||||
|
||||
// --- Globals
|
||||
|
||||
var NET_NAME = 'node-triton-testnet967';
|
||||
|
||||
var CLIENT;
|
||||
var NET;
|
||||
|
||||
@ -33,11 +35,14 @@ test('TritonApi networks', function (tt) {
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' cleanup: rm network ' + NET_NAME + ' if exists', function (t) {
|
||||
CLIENT.deleteFabricNetwork({id: NET_NAME}, function () {
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' setup: net', function (t) {
|
||||
var opts = {
|
||||
account: CLIENT.profile.account
|
||||
};
|
||||
CLIENT.cloudapi.listNetworks(opts, function (err, nets) {
|
||||
CLIENT.cloudapi.listNetworks({}, function (err, nets) {
|
||||
if (h.ifErr(t, err))
|
||||
return t.end();
|
||||
|
||||
@ -78,6 +83,61 @@ test('TritonApi networks', function (tt) {
|
||||
});
|
||||
|
||||
|
||||
tt.test(' TritonApi deleteFabricNetwork', function (t) {
|
||||
function check(genId, idType, vlanId, cb) {
|
||||
CLIENT.cloudapi.createFabricNetwork({
|
||||
name: NET_NAME,
|
||||
subnet: '192.168.97.0/24',
|
||||
provision_start_ip: '192.168.97.1',
|
||||
provision_end_ip: '192.168.97.254',
|
||||
vlan_id: vlanId
|
||||
}, function onCreate(err, net) {
|
||||
if (h.ifErr(t, err, 'Error creating network')) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var id = genId(net);
|
||||
CLIENT.deleteFabricNetwork({id: id}, function onDelete(err2) {
|
||||
if (h.ifErr(t, err, 'Error deleting net by ' + idType)) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
CLIENT.cloudapi.getNetwork(net.id, function onGet(err3) {
|
||||
t.ok(err3, 'Network should be gone');
|
||||
cb();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// get a VLAN, then create and delete a set of fabrics to check it's
|
||||
// possible to delete by id, shortId and name
|
||||
CLIENT.cloudapi.listFabricVlans({}, function onList(err, vlans) {
|
||||
if (vlans.length === 0) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
function getId(net) { return net.id; }
|
||||
function getName(net) { return net.name; }
|
||||
function getShort(net) { return net.id.split('-')[0]; }
|
||||
|
||||
var vlanId = +vlans[0].vlan_id;
|
||||
|
||||
check(getId, 'id', vlanId, function onId() {
|
||||
check(getName, 'name', vlanId, function onName() {
|
||||
check(getShort, 'shortId', vlanId, function onShort() {
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
tt.test(' teardown: client', function (t) {
|
||||
CLIENT.close();
|
||||
t.end();
|
||||
|
@ -25,9 +25,9 @@ var NIC;
|
||||
|
||||
// --- Tests
|
||||
|
||||
test('TritonApi networks', function (tt) {
|
||||
test('TritonApi nics', function (tt) {
|
||||
tt.test(' setup', function (t) {
|
||||
h.createClient(function (err, client_) {
|
||||
h.createClient(function onCreate(err, client_) {
|
||||
t.error(err);
|
||||
CLIENT = client_;
|
||||
t.end();
|
||||
@ -36,9 +36,11 @@ test('TritonApi networks', function (tt) {
|
||||
|
||||
|
||||
tt.test(' setup: inst', function (t) {
|
||||
CLIENT.cloudapi.listMachines({}, function (err, vms) {
|
||||
if (vms.length === 0)
|
||||
return t.end();
|
||||
CLIENT.cloudapi.listMachines({}, function onList(err, vms) {
|
||||
if (vms.length === 0) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
t.ok(Array.isArray(vms), 'vms array');
|
||||
INST = vms[0];
|
||||
@ -49,13 +51,17 @@ test('TritonApi networks', function (tt) {
|
||||
|
||||
|
||||
tt.test(' TritonApi listNics', function (t) {
|
||||
if (!INST)
|
||||
return t.end();
|
||||
if (!INST) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
function check(val, valName, next) {
|
||||
CLIENT.listNics({id: val}, function (err, nics) {
|
||||
if (h.ifErr(t, err, 'no err ' + valName))
|
||||
return t.end();
|
||||
CLIENT.listNics({id: val}, function onList(err, nics) {
|
||||
if (h.ifErr(t, err, 'no err ' + valName)) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
t.ok(Array.isArray(nics), 'nics array');
|
||||
NIC = nics[0];
|
||||
@ -66,9 +72,9 @@ test('TritonApi networks', function (tt) {
|
||||
|
||||
var shortId = INST.id.split('-')[0];
|
||||
|
||||
check(INST.id, 'id', function () {
|
||||
check(INST.name, 'name', function () {
|
||||
check(shortId, 'shortId', function () {
|
||||
check(INST.id, 'id', function doId() {
|
||||
check(INST.name, 'name', function doName() {
|
||||
check(shortId, 'shortId', function doShort() {
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
@ -77,13 +83,17 @@ test('TritonApi networks', function (tt) {
|
||||
|
||||
|
||||
tt.test(' TritonApi getNic', function (t) {
|
||||
if (!NIC)
|
||||
return t.end();
|
||||
if (!NIC) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
function check(inst, mac, instValName, next) {
|
||||
CLIENT.getNic({id: inst, mac: mac}, function (err, nic) {
|
||||
if (h.ifErr(t, err, 'no err for ' + instValName))
|
||||
return t.end();
|
||||
CLIENT.getNic({id: inst, mac: mac}, function onGet(err, nic) {
|
||||
if (h.ifErr(t, err, 'no err for ' + instValName)) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
t.deepEqual(nic, NIC, instValName);
|
||||
|
||||
@ -93,9 +103,9 @@ test('TritonApi networks', function (tt) {
|
||||
|
||||
var shortId = INST.id.split('-')[0];
|
||||
|
||||
check(INST.id, NIC.mac, 'id', function () {
|
||||
check(INST.name, NIC.mac, 'name', function () {
|
||||
check(shortId, NIC.mac, 'shortId', function () {
|
||||
check(INST.id, NIC.mac, 'id', function doId() {
|
||||
check(INST.name, NIC.mac, 'name', function doName() {
|
||||
check(shortId, NIC.mac, 'shortId', function doShort() {
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
120
test/integration/api-vlans.test.js
Normal file
120
test/integration/api-vlans.test.js
Normal file
@ -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 2018 Joyent, Inc.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Integration tests for using VLAN-related APIs as a module.
|
||||
*/
|
||||
|
||||
var h = require('./helpers');
|
||||
var test = require('tape');
|
||||
|
||||
|
||||
// --- Globals
|
||||
|
||||
var CLIENT;
|
||||
var VLAN;
|
||||
|
||||
|
||||
// --- Tests
|
||||
|
||||
test('TritonApi vlan', function (tt) {
|
||||
tt.test(' setup', function (t) {
|
||||
h.createClient(function onCreate(err, client_) {
|
||||
t.error(err);
|
||||
CLIENT = client_;
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
tt.test(' setup: vlan', function (t) {
|
||||
CLIENT.cloudapi.listFabricVlans({}, function onList(err, vlans) {
|
||||
if (vlans.length === 0) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
VLAN = vlans[0];
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
tt.test(' TritonApi getFabricVlan', function (t) {
|
||||
if (!VLAN) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
function check(val, valName, next) {
|
||||
CLIENT.getFabricVlan(val, function onGet(err, vlan) {
|
||||
if (h.ifErr(t, err, 'no err')) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
t.deepEqual(vlan, VLAN, valName);
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
check(VLAN.vlan_id, 'vlan_id', function onId() {
|
||||
check(VLAN.name, 'name', function onName() {
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
tt.test(' TritonApi deleteFabricVlan', function (t) {
|
||||
function check(genId, idType, cb) {
|
||||
CLIENT.cloudapi.createFabricVlan({
|
||||
vlan_id: 3291,
|
||||
name: 'test3291'
|
||||
}, function onCreate(err, vlan) {
|
||||
if (h.ifErr(t, err, 'Error creating VLAN')) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var id = genId(vlan);
|
||||
CLIENT.deleteFabricVlan({vlan_id: id}, function onDel(err2) {
|
||||
if (h.ifErr(t, err, 'Error deleting VLAN by ' + idType)) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
CLIENT.cloudapi.getFabricVlan({vlan_id: vlan.vlan_id},
|
||||
function onGet(err3) {
|
||||
t.ok(err3, 'VLAN should be gone');
|
||||
cb();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getVlanId(net) { return net.vlan_id; }
|
||||
function getName(net) { return net.name; }
|
||||
|
||||
check(getVlanId, 'vlan_id', function onId() {
|
||||
check(getName, 'name', function onName() {
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
tt.test(' teardown: client', function (t) {
|
||||
CLIENT.close();
|
||||
t.end();
|
||||
});
|
||||
});
|
@ -110,7 +110,8 @@ test('affinity (triton create -a RULE ...)', testOpts, function (tt) {
|
||||
var db2Alias = ALIAS_PREFIX + '-db2';
|
||||
var db2;
|
||||
tt.test(' triton create -n db2 -a \'instance!=db*\'', function (t) {
|
||||
var argv = ['create', '-wj', '-n', db2Alias, '-a', 'instance!=db*',
|
||||
var argv = ['create', '-wj', '-n', db2Alias, '-a',
|
||||
'instance!=' + ALIAS_PREFIX + '-db*',
|
||||
imgId, pkgId];
|
||||
h.safeTriton(t, argv, function (err, stdout) {
|
||||
var lines = h.jsonStreamParse(stdout);
|
||||
|
@ -5,28 +5,53 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2017 Joyent, Inc.
|
||||
* Copyright 2018 Joyent, Inc.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Integration tests for `triton network(s)`
|
||||
*/
|
||||
|
||||
var h = require('./helpers');
|
||||
var f = require('util').format;
|
||||
var os = require('os');
|
||||
var test = require('tape');
|
||||
var h = require('./helpers');
|
||||
|
||||
var common = require('../../lib/common');
|
||||
|
||||
|
||||
// --- Globals
|
||||
|
||||
var NET_NAME = f('nodetritontest-network-%s', os.hostname());
|
||||
|
||||
var networks;
|
||||
var vlan;
|
||||
|
||||
var OPTS = {
|
||||
skip: !h.CONFIG.allowWriteActions
|
||||
};
|
||||
|
||||
|
||||
// --- Tests
|
||||
|
||||
if (OPTS.skip) {
|
||||
console.error('** skipping some %s tests', __filename);
|
||||
console.error('** set "allowWriteActions" in test config to enable');
|
||||
}
|
||||
|
||||
test('triton networks', function (tt) {
|
||||
|
||||
tt.test(' setup: find a test VLAN', function (t) {
|
||||
h.triton('vlan list -j', function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err))
|
||||
return t.end();
|
||||
|
||||
vlan = JSON.parse(stdout.trim().split('\n')[0]);
|
||||
t.ok(vlan, 'vlan for testing found');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton network list -h', function (t) {
|
||||
h.triton('networks -h', function (err, stdout, stderr) {
|
||||
if (h.ifErr(t, err))
|
||||
@ -209,3 +234,135 @@ test('triton network get', function (tt) {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
test('triton network create', OPTS, function (tt) {
|
||||
|
||||
tt.test(' cleanup: rm network ' + NET_NAME + ' if exists', function (t) {
|
||||
h.triton('network delete ' + NET_NAME, function (err, stdout) {
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton network create -h', function (t) {
|
||||
h.triton('network create -h', function (err, stdout, stderr) {
|
||||
if (h.ifErr(t, err))
|
||||
return t.end();
|
||||
t.ok(/Usage:\s+triton network\b/.test(stdout));
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton network help create', function (t) {
|
||||
h.triton('network help create', function (err, stdout, stderr) {
|
||||
if (h.ifErr(t, err))
|
||||
return t.end();
|
||||
t.ok(/Usage:\s+triton network create\b/.test(stdout));
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton network create', function (t) {
|
||||
h.triton('network create', function (err, stdout, stderr) {
|
||||
t.ok(err);
|
||||
t.ok(/error \(Usage\)/.test(stderr));
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton network create VLAN', function (t) {
|
||||
h.triton('network create --name=' + NET_NAME +
|
||||
' --subnet=192.168.97.0/24 --start_ip=192.168.97.1' +
|
||||
' --end_ip=192.168.97.254 -j ' + vlan.vlan_id,
|
||||
function (err, stdout) {
|
||||
if (h.ifErr(t, err))
|
||||
return t.end();
|
||||
|
||||
var network = JSON.parse(stdout.trim().split('\n')[0]);
|
||||
|
||||
t.equal(network.name, NET_NAME);
|
||||
t.equal(network.subnet, '192.168.97.0/24');
|
||||
t.equal(network.provision_start_ip, '192.168.97.1');
|
||||
t.equal(network.provision_end_ip, '192.168.97.254');
|
||||
t.equal(network.vlan_id, vlan.vlan_id);
|
||||
|
||||
h.triton('network delete ' + network.id, function (err2) {
|
||||
h.ifErr(t, err2);
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
test('triton network delete', OPTS, function (tt) {
|
||||
|
||||
tt.test(' triton network delete -h', function (t) {
|
||||
h.triton('network delete -h', function (err, stdout, stderr) {
|
||||
if (h.ifErr(t, err))
|
||||
return t.end();
|
||||
t.ok(/Usage:\s+triton network\b/.test(stdout));
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton network help delete', function (t) {
|
||||
h.triton('network help delete', function (err, stdout, stderr) {
|
||||
if (h.ifErr(t, err))
|
||||
return t.end();
|
||||
t.ok(/Usage:\s+triton network delete\b/.test(stdout));
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton network delete', function (t) {
|
||||
h.triton('network delete', function (err, stdout, stderr) {
|
||||
t.ok(err);
|
||||
t.ok(/error \(Usage\)/.test(stderr));
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
function deleteNetworkTester(t, deleter) {
|
||||
h.triton('network create --name=' + NET_NAME +
|
||||
' --subnet=192.168.97.0/24 --start_ip=192.168.97.1' +
|
||||
' --end_ip=192.168.97.254 -j ' + vlan.vlan_id,
|
||||
function (err, stdout) {
|
||||
if (h.ifErr(t, err, 'create test network'))
|
||||
return t.end();
|
||||
|
||||
var network = JSON.parse(stdout.trim().split('\n')[0]);
|
||||
|
||||
deleter(null, network, function (err2) {
|
||||
if (h.ifErr(t, err2, 'deleting test network'))
|
||||
return t.end();
|
||||
|
||||
h.triton('network get ' + network.id, function (err3) {
|
||||
t.ok(err3, 'network should be gone');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
tt.test(' triton network delete ID', function (t) {
|
||||
deleteNetworkTester(t, function (err, network, cb) {
|
||||
h.triton('network delete ' + network.id, cb);
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton network delete NAME', function (t) {
|
||||
deleteNetworkTester(t, function (err, network, cb) {
|
||||
h.triton('network delete ' + network.name, cb);
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton network delete SHORTID', function (t) {
|
||||
deleteNetworkTester(t, function (err, network, cb) {
|
||||
var shortid = network.id.split('-')[0];
|
||||
h.triton('network delete ' + shortid, cb);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -41,7 +41,7 @@ test('triton instance nics', OPTS, function (tt) {
|
||||
h.printConfig(tt);
|
||||
|
||||
tt.test(' cleanup existing inst with alias ' + INST_ALIAS, function (t) {
|
||||
h.deleteTestInst(t, INST_ALIAS, function (err) {
|
||||
h.deleteTestInst(t, INST_ALIAS, function onDelete(err) {
|
||||
t.ifErr(err);
|
||||
t.end();
|
||||
});
|
||||
@ -49,8 +49,10 @@ test('triton instance nics', OPTS, function (tt) {
|
||||
|
||||
tt.test(' setup: triton instance create', function (t) {
|
||||
h.createTestInst(t, INST_ALIAS, {}, function onInst(err, instId) {
|
||||
if (h.ifErr(t, err, 'triton instance create'))
|
||||
return t.end();
|
||||
if (h.ifErr(t, err, 'triton instance create')) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
t.ok(instId, 'created instance ' + instId);
|
||||
INST = instId;
|
||||
@ -61,8 +63,10 @@ test('triton instance nics', OPTS, function (tt) {
|
||||
|
||||
tt.test(' setup: find network for tests', function (t) {
|
||||
h.triton('network list -j', function onNetworks(err, stdout) {
|
||||
if (h.ifErr(t, err, 'triton network list'))
|
||||
return t.end();
|
||||
if (h.ifErr(t, err, 'triton network list')) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
NETWORK = JSON.parse(stdout.trim().split('\n')[0]);
|
||||
t.ok(NETWORK, 'NETWORK');
|
||||
@ -74,9 +78,11 @@ test('triton instance nics', OPTS, function (tt) {
|
||||
tt.test(' triton instance nic create', function (t) {
|
||||
var cmd = 'instance nic create -j -w ' + INST + ' ' + NETWORK.id;
|
||||
|
||||
h.triton(cmd, function (err, stdout, stderr) {
|
||||
if (h.ifErr(t, err, 'triton instance nic create'))
|
||||
return t.end();
|
||||
h.triton(cmd, function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err, 'triton instance nic create')) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
NIC = JSON.parse(stdout);
|
||||
t.ok(NIC, 'created NIC: ' + stdout.trim());
|
||||
@ -88,9 +94,11 @@ test('triton instance nics', OPTS, function (tt) {
|
||||
tt.test(' triton instance nic get', function (t) {
|
||||
var cmd = 'instance nic get ' + INST + ' ' + NIC.mac;
|
||||
|
||||
h.triton(cmd, function (err, stdout, stderr) {
|
||||
if (h.ifErr(t, err, 'triton instance nic get'))
|
||||
return t.end();
|
||||
h.triton(cmd, function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err, 'triton instance nic get')) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var obj = JSON.parse(stdout);
|
||||
t.equal(obj.mac, NIC.mac, 'nic MAC is correct');
|
||||
@ -104,9 +112,11 @@ test('triton instance nics', OPTS, function (tt) {
|
||||
tt.test(' triton instance nic list', function (t) {
|
||||
var cmd = 'instance nic list ' + INST;
|
||||
|
||||
h.triton(cmd, function (err, stdout, stderr) {
|
||||
if (h.ifErr(t, err, 'triton instance nic list'))
|
||||
return t.end();
|
||||
h.triton(cmd, function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err, 'triton instance nic list')) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var nics = stdout.trim().split('\n');
|
||||
t.ok(nics[0].match(/IP\s+MAC\s+STATE\s+NETWORK/), 'nic list' +
|
||||
@ -115,7 +125,7 @@ test('triton instance nics', OPTS, function (tt) {
|
||||
|
||||
t.ok(nics.length >= 1, 'triton nic list expected nic num');
|
||||
|
||||
var testNics = nics.filter(function (nic) {
|
||||
var testNics = nics.filter(function doFilter(nic) {
|
||||
return nic.match(NIC.mac);
|
||||
});
|
||||
|
||||
@ -128,17 +138,19 @@ test('triton instance nics', OPTS, function (tt) {
|
||||
tt.test(' triton instance nic list -j', function (t) {
|
||||
var cmd = 'instance nic list -j ' + INST;
|
||||
|
||||
h.triton(cmd, function (err, stdout, stderr) {
|
||||
if (h.ifErr(t, err, 'triton instance nic list'))
|
||||
return t.end();
|
||||
h.triton(cmd, function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err, 'triton instance nic list')) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var nics = stdout.trim().split('\n').map(function (line) {
|
||||
var nics = stdout.trim().split('\n').map(function doParse(line) {
|
||||
return JSON.parse(line);
|
||||
});
|
||||
|
||||
t.ok(nics.length >= 1, 'triton nic list expected nic num');
|
||||
|
||||
var testNics = nics.filter(function (nic) {
|
||||
var testNics = nics.filter(function doFilter(nic) {
|
||||
return nic.mac === NIC.mac;
|
||||
});
|
||||
|
||||
@ -150,11 +162,13 @@ test('triton instance nics', OPTS, function (tt) {
|
||||
|
||||
tt.test(' triton instance nic list mac=<...>', function (t) {
|
||||
var cmd = 'instance nic list -j ' + INST + ' mac=' + NIC.mac;
|
||||
h.triton(cmd, function (err, stdout, stderr) {
|
||||
if (h.ifErr(t, err))
|
||||
return t.end();
|
||||
h.triton(cmd, function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err)) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var nics = stdout.trim().split('\n').map(function (str) {
|
||||
var nics = stdout.trim().split('\n').map(function doParse(str) {
|
||||
return JSON.parse(str);
|
||||
});
|
||||
|
||||
@ -169,11 +183,13 @@ test('triton instance nics', OPTS, function (tt) {
|
||||
tt.test(' triton nic list mac=<...>', function (t) {
|
||||
var cmd = 'instance nic list -j ' + INST + ' mac=' + NIC.mac;
|
||||
|
||||
h.triton(cmd, function (err, stdout, stderr) {
|
||||
if (h.ifErr(t, err))
|
||||
return t.end();
|
||||
h.triton(cmd, function doTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err)) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var nics = stdout.trim().split('\n').map(function (str) {
|
||||
var nics = stdout.trim().split('\n').map(function doParse(str) {
|
||||
return JSON.parse(str);
|
||||
});
|
||||
|
||||
@ -188,9 +204,11 @@ test('triton instance nics', OPTS, function (tt) {
|
||||
tt.test(' triton instance nic delete', function (t) {
|
||||
var cmd = 'instance nic delete --force ' + INST + ' ' + NIC.mac;
|
||||
|
||||
h.triton(cmd, function (err, stdout, stderr) {
|
||||
if (h.ifErr(t, err, 'triton instance nic delete'))
|
||||
return t.end();
|
||||
h.triton(cmd, function doTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err, 'triton instance nic delete')) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
t.ok(stdout.match('Deleted NIC ' + NIC.mac, 'deleted nic'));
|
||||
|
||||
@ -202,9 +220,11 @@ test('triton instance nics', OPTS, function (tt) {
|
||||
var cmd = 'instance nic create -j -w ' + INST + ' ipv4_uuid=' +
|
||||
NETWORK.id;
|
||||
|
||||
h.triton(cmd, function (err, stdout, stderr) {
|
||||
if (h.ifErr(t, err, 'triton instance nic create'))
|
||||
return t.end();
|
||||
h.triton(cmd, function doTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err, 'triton instance nic create')) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
NIC2 = JSON.parse(stdout);
|
||||
|
||||
@ -215,9 +235,11 @@ test('triton instance nics', OPTS, function (tt) {
|
||||
tt.test(' triton instance nic with ip get', function (t) {
|
||||
var cmd = 'instance nic get ' + INST + ' ' + NIC2.mac;
|
||||
|
||||
h.triton(cmd, function (err, stdout, stderr) {
|
||||
if (h.ifErr(t, err, 'triton instance nic get'))
|
||||
return t.end();
|
||||
h.triton(cmd, function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err, 'triton instance nic get')) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var obj = JSON.parse(stdout);
|
||||
t.equal(obj.mac, NIC2.mac, 'nic MAC is correct');
|
||||
@ -231,9 +253,11 @@ test('triton instance nics', OPTS, function (tt) {
|
||||
tt.test(' triton instance nic with ip delete', function (t) {
|
||||
var cmd = 'instance nic delete --force ' + INST + ' ' + NIC2.mac;
|
||||
|
||||
h.triton(cmd, function (err, stdout, stderr) {
|
||||
if (h.ifErr(t, err, 'triton instance nic with ip delete'))
|
||||
return t.end();
|
||||
h.triton(cmd, function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err, 'triton instance nic with ip delete')) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
t.ok(stdout.match('Deleted NIC ' + NIC2.mac, 'deleted nic'));
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2015, Joyent, Inc.
|
||||
* Copyright (c) 2018, Joyent, Inc.
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -65,8 +65,18 @@ var subs = [
|
||||
['ip'],
|
||||
['ssh'],
|
||||
['network'],
|
||||
['network list', 'networks'],
|
||||
['network create'],
|
||||
['network list', 'network ls', 'networks'],
|
||||
['network get'],
|
||||
['network get-default'],
|
||||
['network set-default'],
|
||||
['network delete', 'network rm'],
|
||||
['vlan'],
|
||||
['vlan create'],
|
||||
['vlan list', 'vlan ls'],
|
||||
['vlan get'],
|
||||
['vlan update'],
|
||||
['vlan delete', 'vlan rm'],
|
||||
['key'],
|
||||
['key add'],
|
||||
['key list', 'key ls', 'keys'],
|
||||
|
418
test/integration/cli-vlans.test.js
Normal file
418
test/integration/cli-vlans.test.js
Normal file
@ -0,0 +1,418 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Integration tests for `triton vlans`
|
||||
*/
|
||||
|
||||
var f = require('util').format;
|
||||
var os = require('os');
|
||||
var test = require('tape');
|
||||
var h = require('./helpers');
|
||||
|
||||
var common = require('../../lib/common');
|
||||
|
||||
|
||||
// --- Globals
|
||||
|
||||
var VLAN_NAME = f('nodetritontest-vlan-%s', os.hostname());
|
||||
var VLAN_ID = 3197;
|
||||
|
||||
var VLAN;
|
||||
|
||||
var OPTS = {
|
||||
skip: !h.CONFIG.allowWriteActions
|
||||
};
|
||||
|
||||
|
||||
// --- Tests
|
||||
|
||||
if (OPTS.skip) {
|
||||
console.error('** skipping some %s tests', __filename);
|
||||
console.error('** set "allowWriteActions" in test config to enable');
|
||||
}
|
||||
|
||||
test('triton vlan list', function (tt) {
|
||||
|
||||
tt.test(' triton vlan list -h', function (t) {
|
||||
h.triton('vlan list -h', function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err)) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
t.ok(/Usage:\s+triton vlan list/.test(stdout));
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton vlan list', function (t) {
|
||||
h.triton('vlan list', function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err)) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
t.ok(/^VLAN_ID\b/.test(stdout));
|
||||
t.ok(/\bNAME\b/.test(stdout));
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton vlan list -j', function (t) {
|
||||
h.triton('vlan list -j', function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err)) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
VLAN = JSON.parse(stdout.trim().split('\n')[0]);
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton vlan list vlan_id=<...>', function (t) {
|
||||
var cmd = 'vlan list -j vlan_id=' + VLAN.vlan_id;
|
||||
h.triton(cmd, function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err)) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var vlans = stdout.trim().split('\n').map(function onParse(str) {
|
||||
return JSON.parse(str);
|
||||
});
|
||||
|
||||
t.deepEqual(vlans, [VLAN]);
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton vlan list vlan_id=<...> name=<...> (good)', function (t) {
|
||||
var cmd = 'vlan list -j vlan_id=' + VLAN.vlan_id + ' name=' + VLAN.name;
|
||||
|
||||
h.triton(cmd, function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err)) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var vlans = stdout.trim().split('\n').map(function onParse(str) {
|
||||
return JSON.parse(str);
|
||||
});
|
||||
|
||||
t.deepEqual(vlans, [VLAN]);
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton vlan list vlan_id=<...> name=<...> (bad)', function (t) {
|
||||
// search for a mismatch, should return nada
|
||||
var cmd = 'vlan list -j vlan_id=' + VLAN.vlan_id + ' name=' +
|
||||
VLAN.name + 'a';
|
||||
|
||||
h.triton(cmd, function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err)) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
t.equal(stdout, '');
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
test('triton vlan get', function (tt) {
|
||||
|
||||
tt.test(' triton vlan get -h', function (t) {
|
||||
h.triton('vlan get -h', function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err)) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
t.ok(/Usage:\s+triton vlan\b/.test(stdout));
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton vlan help get', function (t) {
|
||||
h.triton('vlan help get', function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err)) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
t.ok(/Usage:\s+triton vlan get\b/.test(stdout));
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton vlan get', function (t) {
|
||||
h.triton('vlan get', function onTriton(err, stdout, stderr) {
|
||||
t.ok(err);
|
||||
t.ok(/error \(Usage\)/.test(stderr));
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton vlan get ID', function (t) {
|
||||
h.triton('vlan get ' + VLAN.vlan_id,
|
||||
function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err)) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var vlan = JSON.parse(stdout);
|
||||
t.equal(vlan.vlan_id, VLAN.vlan_id);
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton vlan get NAME', function (t) {
|
||||
h.triton('vlan get ' + VLAN.name,
|
||||
function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err)) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var vlan = JSON.parse(stdout);
|
||||
t.equal(vlan.vlan_id, VLAN.vlan_id);
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
test('triton vlan networks', function (tt) {
|
||||
|
||||
tt.test(' triton vlan networks -h', function (t) {
|
||||
h.triton('vlan networks -h', function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err)) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
t.ok(/Usage:\s+triton vlan networks/.test(stdout));
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton vlan help networks', function (t) {
|
||||
h.triton('vlan help networks', function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err)) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
t.ok(/Usage:\s+triton vlan networks/.test(stdout));
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton vlan networks', function (t) {
|
||||
h.triton('vlan networks', function onTriton(err, stdout, stderr) {
|
||||
t.ok(err);
|
||||
t.ok(/error \(Usage\)/.test(stderr));
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton vlan networks ID', function (t) {
|
||||
h.triton('vlan networks -j ' + VLAN.vlan_id,
|
||||
function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err)) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var vlan = JSON.parse(stdout);
|
||||
t.equal(vlan.vlan_id, VLAN.vlan_id);
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton vlan networks NAME', function (t) {
|
||||
h.triton('vlan networks -j ' + VLAN.name,
|
||||
function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err)) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var vlan = JSON.parse(stdout);
|
||||
t.equal(vlan.vlan_id, VLAN.vlan_id);
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
test('triton vlan create', OPTS, function (tt) {
|
||||
|
||||
tt.test(' cleanup: rm vlan ' + VLAN_NAME + ' if exists', function (t) {
|
||||
h.triton('vlan delete ' + VLAN_NAME, function onTriton(err, stdout) {
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton vlan create -h', function (t) {
|
||||
h.triton('vlan create -h', function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err)) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
t.ok(/Usage:\s+triton vlan\b/.test(stdout));
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton vlan help create', function (t) {
|
||||
h.triton('vlan help create', function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err)) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
t.ok(/Usage:\s+triton vlan create\b/.test(stdout));
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton vlan create', function (t) {
|
||||
h.triton('vlan create', function onTriton(err, stdout, stderr) {
|
||||
t.ok(err);
|
||||
t.ok(/error \(Usage\)/.test(stderr));
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton vlan create VLAN', function (t) {
|
||||
h.triton('vlan create -j --name=' + VLAN_NAME + ' ' + VLAN_ID,
|
||||
function onTriton(err, stdout) {
|
||||
if (h.ifErr(t, err)) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var vlan = JSON.parse(stdout.trim().split('\n')[0]);
|
||||
|
||||
t.equal(vlan.name, VLAN_NAME);
|
||||
t.equal(vlan.vlan_id, VLAN_ID);
|
||||
|
||||
h.triton('vlan delete ' + vlan.vlan_id, function onTriton2(err2) {
|
||||
h.ifErr(t, err2);
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
test('triton vlan delete', OPTS, function (tt) {
|
||||
|
||||
tt.test(' triton vlan delete -h', function (t) {
|
||||
h.triton('vlan delete -h', function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err)) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
t.ok(/Usage:\s+triton vlan\b/.test(stdout));
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton vlan help delete', function (t) {
|
||||
h.triton('vlan help delete', function onTriton(err, stdout, stderr) {
|
||||
if (h.ifErr(t, err)) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
t.ok(/Usage:\s+triton vlan delete\b/.test(stdout));
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton vlan delete', function (t) {
|
||||
h.triton('vlan delete', function onTriton(err, stdout, stderr) {
|
||||
t.ok(err);
|
||||
t.ok(/error \(Usage\)/.test(stderr));
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
function deleteNetworkTester(t, deleter) {
|
||||
h.triton('vlan create -j --name=' + VLAN_NAME + ' ' + VLAN_ID,
|
||||
function onTriton(err, stdout) {
|
||||
if (h.ifErr(t, err, 'create test vlan')) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var vlan = JSON.parse(stdout.trim().split('\n')[0]);
|
||||
|
||||
deleter(null, vlan, function onDelete(err2) {
|
||||
if (h.ifErr(t, err2, 'deleting test vlan')) {
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
h.triton('vlan get ' + vlan.vlan_id, function onTriton2(err3) {
|
||||
t.ok(err3, 'vlan should be gone');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
tt.test(' triton vlan delete ID', function (t) {
|
||||
deleteNetworkTester(t, function doDelete(err, vlan, cb) {
|
||||
h.triton('vlan delete ' + vlan.vlan_id, cb);
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' triton vlan delete NAME', function (t) {
|
||||
deleteNetworkTester(t, function doDelete(err, vlan, cb) {
|
||||
h.triton('vlan delete ' + vlan.name, cb);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@ -183,6 +183,7 @@ function getTestImg(t, cb) {
|
||||
var candidateImageNames = {
|
||||
'base-64-lts': true,
|
||||
'base-64': true,
|
||||
'minimal-64-lts': true,
|
||||
'minimal-64': true,
|
||||
'base-32-lts': true,
|
||||
'base-32': true,
|
||||
|
Reference in New Issue
Block a user