Browse Source

merge with upstream

master
Marius Pana 1 year ago
parent
commit
75ec73a31c
36 changed files with 3163 additions and 120 deletions
  1. +1
    -0
      .gitignore
  2. +33
    -0
      CHANGES.md
  3. +4
    -0
      lib/cli.js
  4. +334
    -7
      lib/cloudapi2.js
  5. +16
    -2
      lib/common.js
  6. +5
    -5
      lib/config.js
  7. +107
    -0
      lib/do_image/do_clone.js
  8. +119
    -0
      lib/do_image/do_copy.js
  9. +59
    -13
      lib/do_image/do_list.js
  10. +4
    -0
      lib/do_image/index.js
  11. +8
    -0
      lib/do_instance/do_create.js
  12. +11
    -2
      lib/do_instance/do_get.js
  13. +187
    -0
      lib/do_network/do_create.js
  14. +85
    -0
      lib/do_network/do_delete.js
  15. +78
    -0
      lib/do_network/do_get_default.js
  16. +11
    -2
      lib/do_network/do_list.js
  17. +92
    -0
      lib/do_network/do_set_default.js
  18. +9
    -1
      lib/do_network/index.js
  19. +2
    -1
      lib/do_profile/do_list.js
  20. +123
    -0
      lib/do_vlan/do_create.js
  21. +85
    -0
      lib/do_vlan/do_delete.js
  22. +88
    -0
      lib/do_vlan/do_get.js
  23. +123
    -0
      lib/do_vlan/do_list.js
  24. +52
    -0
      lib/do_vlan/do_networks.js
  25. +201
    -0
      lib/do_vlan/do_update.js
  26. +55
    -0
      lib/do_vlan/index.js
  27. +399
    -16
      lib/tritonapi.js
  28. +65
    -5
      test/integration/api-networks.test.js
  29. +31
    -21
      test/integration/api-nics.test.js
  30. +120
    -0
      test/integration/api-vlans.test.js
  31. +2
    -1
      test/integration/cli-affinity.test.js
  32. +159
    -2
      test/integration/cli-networks.test.js
  33. +64
    -40
      test/integration/cli-nics.test.js
  34. +12
    -2
      test/integration/cli-subcommands.test.js
  35. +418
    -0
      test/integration/cli-vlans.test.js
  36. +1
    -0
      test/integration/helpers.js

+ 1
- 0
.gitignore View File

@@ -5,3 +5,4 @@
/triton-*.tgz
.DS_Store
.git
*.swp

+ 33
- 0
CHANGES.md View File

@@ -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


+ 4
- 0
lib/cli.js View File

@@ -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');

+ 334
- 7
lib/cloudapi2.js View File

@@ -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');
@@ -794,6 +1058,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.
*
* @param {Object} options
@@ -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);
});
};

+ 16
- 2
lib/common.js View File

@@ -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:

+ 5
- 5
lib/config.js View File

@@ -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
- 0
lib/do_image/do_clone.js View 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
- 0
lib/do_image/do_copy.js View 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;

+ 59
- 13
lib/do_image/do_list.js View File

@@ -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;
}
tritonapi.listImages(listOpts, function onRes(err, imgs, res) {
if (err) {
return callback(err);
}

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) {
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',

+ 4
- 0
lib/do_image/index.js View File

@@ -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');

+ 8
- 0
lib/do_instance/do_create.js View File

@@ -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'

+ 11
- 2
lib/do_instance/do_get.js View File

@@ -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));
@@ -45,6 +48,13 @@ do_get.options = [
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',
help: 'JSON output.'
@@ -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
- 0
lib/do_network/do_create.js View 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
- 0
lib/do_network/do_delete.js View 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
- 0
lib/do_network/do_get_default.js View 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;

+ 11
- 2
lib/do_network/do_list.js View File

@@ -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
- 0
lib/do_network/do_set_default.js View 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;

+ 9
- 1
lib/do_network/index.js View File

@@ -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;

+ 2
- 1
lib/do_profile/do_list.js View File

@@ -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
- 0
lib/do_vlan/do_create.js View 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
- 0
lib/do_vlan/do_delete.js View 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
- 0
lib/do_vlan/do_get.js View 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
- 0
lib/do_vlan/do_list.js View 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
- 0
lib/do_vlan/do_networks.js View 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
- 0
lib/do_vlan/do_update.js View 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);
}