Browse Source

first commit

dave.eddy-1440571419
Trent Mick 6 years ago
commit
120f3198cf
24 changed files with 1520 additions and 0 deletions
  1. +5
    -0
      .gitignore
  2. +6
    -0
      .gitmodules
  3. +1
    -0
      CHANGES.md
  4. +41
    -0
      Makefile
  5. +22
    -0
      README.md
  6. +24
    -0
      TODO.md
  7. +14
    -0
      bin/joyent
  8. +1
    -0
      deps/javascriptlint
  9. +1
    -0
      deps/jsstyle
  10. +10
    -0
      docs/index.md
  11. +1
    -0
      docs/link-patterns.txt
  12. +9
    -0
      etc/defaults.json
  13. +241
    -0
      lib/cli.js
  14. +201
    -0
      lib/common.js
  15. +50
    -0
      lib/config.js
  16. +104
    -0
      lib/errors.js
  17. +201
    -0
      lib/joyent.js
  18. +24
    -0
      package.json
  19. +139
    -0
      tools/jsl.node.conf
  20. +5
    -0
      tools/jsstyle.conf
  21. +43
    -0
      tools/mk/Makefile.defs
  22. +44
    -0
      tools/mk/Makefile.deps
  23. +309
    -0
      tools/mk/Makefile.targ
  24. +24
    -0
      tools/rsync-to

+ 5
- 0
.gitignore View File

@@ -0,0 +1,5 @@
/node_modules
/tmp
/docs/*.json
/docs/*.html
/build

+ 6
- 0
.gitmodules View File

@@ -0,0 +1,6 @@
[submodule "deps/javascriptlint"]
path = deps/javascriptlint
url = git://github.com/davepacheco/javascriptlint.git
[submodule "deps/jsstyle"]
path = deps/jsstyle
url = git://github.com/davepacheco/jsstyle.git

+ 1
- 0
CHANGES.md View File

@@ -0,0 +1 @@
# joyent CLI Changelog

+ 41
- 0
Makefile View File

@@ -0,0 +1,41 @@
#
# Copyright (c) 2014, Joyent, Inc. All rights reserved.
#
# Makefile for joyent/joyent
#

#
# Vars, Tools, Files, Flags
#
JS_FILES := bin/joyent \
$(shell find lib -name '*.js' | grep -v '/tmp/')
JSL_CONF_NODE = tools/jsl.node.conf
JSL_FILES_NODE = $(JS_FILES)
JSSTYLE_FILES = $(JS_FILES)
JSSTYLE_FLAGS = -f tools/jsstyle.conf
CLEAN_FILES += ./node_modules

include ./tools/mk/Makefile.defs

#
# Targets
#
.PHONY: all
all:
npm install

.PHONY: test
test:
./test/runtests

.PHONY: dumpvar
dumpvar:
@if [[ -z "$(VAR)" ]]; then \
echo "error: set 'VAR' to dump a var"; \
exit 1; \
fi
@echo "$(VAR) is '$($(VAR))'"


include ./tools/mk/Makefile.deps
include ./tools/mk/Makefile.targ

+ 22
- 0
README.md View File

@@ -0,0 +1,22 @@
`joyent` is a CLI for the Joyent Public Cloud (https://my.joyentcloud.com).

# Installation

1. Install [node.js](http://nodejs.org/).
2. `npm install -g joyent`

Verify that installed and is on your PATH:

$ joyent --version
joyent CLI 1.0.0

Before you can used the CLI you'll need a Joyent account, an SSH key uploaded
and `joyent` configured with those account details.

# Setup

TODO

# Getting Started

TODO

+ 24
- 0
TODO.md View File

@@ -0,0 +1,24 @@
# first

- machines:
- short default output
- long '-l' output, -H, -o, -s
- get image defaults and fill those in
- couple commands: machines, machine, provision (create-machine?)
- re-write of cloudapi.js (eventually a separate module)
- uuid caching
- UUID prefix support
- profile command (adding profile, edit, etc.)
- multi-dc support... profile.dcs


# naming

node-joyentcloud.git that installs joyentcloud. Suggest 'jc' as alias if people want to.

node-smartdc installs joyentcloud and warns about deprecation on stderr.

# later (in no particular order)

- how to add/exclude DCs?
- cmdln.js support for bash tab completion

+ 14
- 0
bin/joyent View File

@@ -0,0 +1,14 @@
#!/usr/bin/env node
/*
* Copyright (c) 2014 Joyent Inc. All rights reserved.
*
* joyent - Joyent public cloud (https://my.joyentcloud.com/) CLI
*/

var p = console.log;
var cmdln = require('cmdln');
var CLI = require('../lib/cli');

if (require.main === module) {
cmdln.main(CLI, process.arg, {showCode: true});
}

+ 1
- 0
deps/javascriptlint

@@ -0,0 +1 @@
Subproject commit e1bd0abfd424811af469d1ece3af131d95443924

+ 1
- 0
deps/jsstyle

@@ -0,0 +1 @@
Subproject commit 07d4f68251063be6496a42dd00a7f5bacd65c5e4

+ 10
- 0
docs/index.md View File

@@ -0,0 +1,10 @@
---
title: sdcadm (Administer a SDC standup)
markdown2extras: wiki-tables, code-friendly, cuddled-lists, link-patterns
markdown2linkpatternsfile: link-patterns.txt
apisections:
---

# sdcadm

TODO

+ 1
- 0
docs/link-patterns.txt View File

@@ -0,0 +1 @@
/([A-Z]+-\d+)/ https://devhub.joyent.com/jira/browse/\1

+ 9
- 0
etc/defaults.json View File

@@ -0,0 +1,9 @@
{
"defaultProfile": "env",
"dcs": {
"us-east-1": "https://us-east-1.api.joyentcloud.com",
"us-west-1": "https://us-west-1.api.joyentcloud.com",
"us-sw-1": "https://us-sw-1.api.joyentcloud.com",
"eu-ams-1": "https://eu-ams-1.api.joyentcloud.com"
}
}

+ 241
- 0
lib/cli.js View File

@@ -0,0 +1,241 @@
/*
* Copyright (c) 2014 Joyent Inc. All rights reserved.
*
* The 'joyent' CLI class.
*/

var p = console.log;
var e = console.error;
var util = require('util'),
format = util.format;
var child_process = require('child_process'),
spawn = child_process.spawn,
exec = child_process.exec;
var fs = require('fs');


var assert = require('assert-plus');
var async = require('async');
var bunyan = require('bunyan');
var cmdln = require('cmdln'),
Cmdln = cmdln.Cmdln;

var common = require('./common');
var Joyent = require('./joyent');



//---- globals

var pkg = require('../package.json');
var name = 'joyent';
var log = bunyan.createLogger({
name: name,
serializers: bunyan.stdSerializers,
stream: process.stderr,
level: 'warn'
});



//---- CLI class

function CLI() {
Cmdln.call(this, {
name: pkg.name,
desc: pkg.description,
options: [
{names: ['help', 'h'], type: 'bool', help: 'Print help and exit.'},
{name: 'version', type: 'bool', help: 'Print version and exit.'},
{names: ['verbose', 'v'], type: 'bool',
help: 'Verbose/debug output.'}
],
helpOpts: {
includeEnv: true,
minHelpCol: 23 /* line up with option help */
}
});
}
util.inherits(CLI, Cmdln);

CLI.prototype.init = function (opts, args, callback) {
var self = this;

if (opts.version) {
p(this.name, pkg.version);
callback(false);
return;
}
this.opts = opts;
if (opts.verbose) {
process.env.DEBUG = 1; //TODO This is a lame requirement of cmdln.main().
log.level('trace');
log.src = true;
}

this.__defineGetter__('joyent', function () {
if (self._joyent === undefined) {
self._joyent = new Joyent({log: log});
}
return self._joyent;
});

// Cmdln class handles `opts.help`.
Cmdln.prototype.init.apply(this, arguments);
};


CLI.prototype.do_profile = function (subcmd, opts, args, callback) {
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
} else if (args.length > 1) {
return callback(new Error('too many args: ' + args));
}

var profs = common.deepObjCopy(this.joyent.profiles);
var currProfileName = this.joyent.profile.name;
for (var i = 0; i < profs.length; i++) {
profs[i].curr = (profs[i].name === currProfileName ? '*' : ' ');
profs[i].dcs = (profs[i].dcs ? Object.keys(profs[i].dcs) : ['all'])
.join(',');
}
if (opts.json) {
p(JSON.stringify(profs, null, 4));
} else {
common.tabulate(profs, {
columns: 'curr,name,dcs,account,keyId',
sort: 'name,account',
validFields: 'curr,name,dcs,account,keyId'
});
}
callback();
};
CLI.prototype.do_profile.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON output.'
}
];
CLI.prototype.do_profile.help = (
'Create, update or inpect joyent CLI profiles.\n'
+ '\n'
+ 'Usage:\n'
+ ' {{name}} profile\n'
+ '\n'
+ '{{options}}'
);


CLI.prototype.do_dcs = function (subcmd, opts, args, callback) {
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
} else if (args.length > 1) {
return callback(new Error('too many args: ' + args));
}

var dcs = this.joyent.config.dcs;
var dcsArray = Object.keys(dcs).map(
function (n) { return {name: n, url: dcs[n]}; });
if (opts.json) {
p(JSON.stringify(dcsArray, null, 4));
} else {
common.tabulate(dcsArray, {
columns: 'name,url',
sort: 'name',
validFields: 'name,url'
});
}
callback();
};
CLI.prototype.do_dcs.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON output.'
}
];
CLI.prototype.do_dcs.help = (
'List, add or remove datacenters.\n'
+ '\n'
+ 'Usage:\n'
+ ' {{name}} dcs\n'
+ '\n'
+ '{{options}}'
);


CLI.prototype.do_machines = function (subcmd, opts, args, callback) {
var self = this;
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
} else if (args.length > 1) {
return callback(new Error('too many args: ' + args));
}

//XXX joyent.listMachines should change to return a 'res' event emitter
// that emits 'dcError' and 'data'.
var listOpts = {
onDcError: function (dc, dcErr) {
console.warn('%s machines: dc %s error: %s', self.name, dc, dcErr);
}
};
this.joyent.listMachines(listOpts, function (err, machines) {
if (err)
return callback(err);
if (opts.json) {
p(JSON.stringify(machines, null, 4));
} else {
// TODO: get short output down to something like
// 'us-west-1 e91897cf testforyunong2 linux running 2013-11-08'
// 'us-west-1 e91897cf testforyunong2 ubuntu/13.3.0 running 2013-11-08'
common.tabulate(machines, {
//columns: 'dc,id,name,type,state,created,image,package,memory,disk',
columns: 'dc,id,name,state,created',
sort: 'created',
validFields: 'dc,id,name,type,state,image,package,memory,disk,created,updated,compute_node,primaryIp'
});
}
callback();
});
};
CLI.prototype.do_machines.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON output.'
}
];
CLI.prototype.do_machines.help = (
'List machines.\n'
+ '\n'
+ 'Usage:\n'
+ ' {{name}} machines [<filters>...]\n'
+ '\n'
+ '{{options}}'
);




//---- exports

module.exports = CLI;

+ 201
- 0
lib/common.js View File

@@ -0,0 +1,201 @@
#!/usr/bin/env node
/**
* Copyright (c) 2014 Joyent Inc. All rights reserved.
*/

var p = console.log;
var assert = require('assert-plus');
var async = require('async');
var backoff = require('backoff');
var fs = require('fs');
var once = require('once');
var sprintf = require('extsprintf').sprintf;
var util = require('util'),
format = util.format;
var verror = require('verror');

var errors = require('./errors'),
InternalError = errors.InternalError;


function objCopy(obj, target) {
if (target === undefined) {
target = {};
}
Object.keys(obj).forEach(function (k) {
target[k] = obj[k];
});
return target;
}


function deepObjCopy(obj) {
return JSON.parse(JSON.stringify(obj));
}


function zeroPad(n, width) {
var s = String(n);
while (s.length < width) {
s = '0' + s;
}
return s;
}


/**
* Convert a boolean or string representation into a boolean, or
* raise TypeError trying.
*
* @param value {Boolean|String} The input value to convert.
* @param default_ {Boolean} The default value is `value` is undefined.
* @param errName {String} The variable name to quote in the possibly
* raised TypeError.
*/
function boolFromString(value, default_, errName) {
if (value === undefined) {
return default_;
} else if (value === 'false' || value === '0') {
return false;
} else if (value === 'true' || value === '1') {
return true;
} else if (typeof (value) === 'boolean') {
return value;
} else {
throw new TypeError(
format('invalid value for "%s": %j', errName, value));
}
}



/**
* Print a table of the given items.
*
* @params items {Array} of row objects.
* @params options {Object}
* - `columns` {String} of comma-separated field names for columns
* - `skipHeader` {Boolean} Default false.
* - `sort` {String} of comma-separate fields on which to alphabetically
* sort the rows. Optional.
* - `validFields` {String} valid fields for `columns` and `sort`
*/
function tabulate(items, options) {
assert.arrayOfObject(items, 'items');
assert.object(options, 'options');
assert.string(options.columns, 'options.columns');
assert.optionalBool(options.skipHeader, 'options.skipHeader');
assert.optionalString(options.sort, 'options.sort');
assert.optionalString(options.validFields, 'options.validFields');

if (items.length === 0) {
return;
}

// Validate.
var validFields = options.validFields && options.validFields.split(',');
var columns = options.columns.split(',');
var sort = options.sort ? options.sort.split(',') : [];
if (validFields) {
columns.forEach(function (c) {
if (validFields.indexOf(c) === -1) {
throw new TypeError(sprintf('invalid output field: "%s"', c));
}
});
}
sort.forEach(function (s) {
if (s[0] === '-') s = s.slice(1);
if (validFields && validFields.indexOf(s) === -1) {
throw new TypeError(sprintf('invalid sort field: "%s"', s));
}
});

// Function to lookup each column field in a row.
var colFuncs = columns.map(function (lookup) {
return new Function(
'try { return (this.' + lookup + '); } catch (e) {}');
});

// Determine columns and widths.
var widths = {};
columns.forEach(function (c) { widths[c] = c.length; });
items.forEach(function (item) {
for (var j = 0; j < columns.length; j++) {
var col = columns[j];
var cell = colFuncs[j].call(item);
if (cell === null || cell === undefined) {
continue;
}
widths[col] = Math.max(
widths[col], (cell ? String(cell).length : 0));
}
});

var template = '';
for (var i = 0; i < columns.length; i++) {
if (i === columns.length - 1) {
// Last column, don't have trailing whitespace.
template += '%s';
} else {
template += '%-' + String(widths[columns[i]]) + 's ';
}
}

function cmp(a, b) {
for (var j = 0; j < sort.length; j++) {
var field = sort[j];
var invert = false;
if (field[0] === '-') {
invert = true;
field = field.slice(1);
}
assert.ok(field.length, 'zero-length sort field: ' + options.sort);
var a_cmp = Number(a[field]);
var b_cmp = Number(b[field]);
if (isNaN(a_cmp) || isNaN(b_cmp)) {
a_cmp = a[field] || '';
b_cmp = b[field] || '';
}
if (a_cmp < b_cmp) {
return (invert ? 1 : -1);
} else if (a_cmp > b_cmp) {
return (invert ? -1 : 1);
}
}
return 0;
}
if (sort.length) {
items.sort(cmp);
}

if (!options.skipHeader) {
var header = columns.map(function (c) { return c.toUpperCase(); });
header.unshift(template);
console.log(sprintf.apply(null, header));
}
items.forEach(function (item) {
var row = [];
for (var j = 0; j < colFuncs.length; j++) {
var cell = colFuncs[j].call(item);
if (cell === null || cell === undefined) {
row.push('-');
} else {
row.push(String(cell));
}
}
row.unshift(template);
console.log(sprintf.apply(null, row));
});
}


//---- exports

module.exports = {
objCopy: objCopy,
deepObjCopy: deepObjCopy,
zeroPad: zeroPad,
boolFromString: boolFromString,
tabulate: tabulate
};
// vim: set softtabstop=4 shiftwidth=4:

+ 50
- 0
lib/config.js View File

@@ -0,0 +1,50 @@
#!/usr/bin/env node
/**
* Copyright (c) 2014 Joyent Inc. All rights reserved.
*/

var p = console.log;
var assert = require('assert-plus');
var fs = require('fs');
var path = require('path');
var sprintf = require('extsprintf').sprintf;

var common = require('./common');


var CONFIG_PATH = path.resolve(process.env.HOME, '.joyentconfig.json');
var DEFAULTS_PATH = path.resolve(__dirname, '..', 'etc', 'defaults.json');


function loadConfigSync() {
var config = JSON.parse(fs.readFileSync(DEFAULTS_PATH, 'utf8'));
if (fs.existsSync(CONFIG_PATH)) {
var userConfig = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
common.objCopy(userConfig, config);
}

// Add 'env' profile.
if (!config.profiles) {
config.profiles = [];
config.profiles.push({
name: 'env',
account: process.env.SDC_USER || process.env.SDC_ACCOUNT,
keyId: process.env.SDC_KEY_ID,
//XXX true/false 0/1 handling
rejectUnauthorized: common.boolFromString(
process.env.SDC_TESTING || process.env.SDC_TLS_INSECURE)
});
}

return config;
}



//---- exports

module.exports = {
CONFIG_PATH: CONFIG_PATH,
loadConfigSync: loadConfigSync
};
// vim: set softtabstop=4 shiftwidth=4:

+ 104
- 0
lib/errors.js View File

@@ -0,0 +1,104 @@
/**
* Copyright (c) 2014, Joyent, Inc. All rights reserved.
*
* Error classes that the joyent CLI may produce.
*/

var util = require('util'),
format = util.format;
var assert = require('assert-plus');
var verror = require('verror'),
WError = verror.WError,
VError = verror.VError;



// ---- error classes

/**
* Base error. Instances will always have a string `message` and
* a string `code` (a CamelCase string).
*/
function JoyentError(options) {
assert.object(options, 'options');
assert.string(options.message, 'options.message');
assert.string(options.code, 'options.code');
assert.optionalObject(options.cause, 'options.cause');
assert.optionalNumber(options.statusCode, 'options.statusCode');
var self = this;

var args = [];
if (options.cause) args.push(options.cause);
args.push(options.message);
WError.apply(this, args);

var extra = Object.keys(options).filter(
function (k) { return ['cause', 'message'].indexOf(k) === -1; });
extra.forEach(function (k) {
self[k] = options[k];
});
}
util.inherits(JoyentError, VError);

function InternalError(cause, message) {
if (message === undefined) {
message = cause;
cause = undefined;
}
assert.string(message);
JoyentError.call(this, {
cause: cause,
message: message,
code: 'InternalError',
exitStatus: 1
});
}
util.inherits(InternalError, JoyentError);

function UsageError(cause, message) {
if (message === undefined) {
message = cause;
cause = undefined;
}
assert.string(message);
JoyentError.call(this, {
cause: cause,
message: message,
code: 'Usage',
exitStatus: 1
});
}
util.inherits(UsageError, JoyentError);


/**
* Multiple errors in a group.
*/
function MultiError(errs) {
assert.arrayOfObject(errs, 'errs');
var lines = [format('multiple (%d) errors', errs.length)];
for (var i = 0; i < errs.length; i++) {
var err = errs[i];
lines.push(format(' error (%s): %s', err.code, err.message));
}
JoyentError.call(this, {
cause: errs[0],
message: lines.join('\n'),
code: 'MultiError',
exitStatus: 1
});
}
MultiError.description = 'Multiple errors.';
util.inherits(MultiError, JoyentError);



// ---- exports

module.exports = {
JoyentError: JoyentError,
InternalError: InternalError,
UsageError: UsageError,
MultiError: MultiError
};
// vim: set softtabstop=4 shiftwidth=4:

+ 201
- 0
lib/joyent.js View File

@@ -0,0 +1,201 @@
/*
* Copyright (c) 2014, Joyent, Inc. All rights reserved.
*
* Core Joyent driver class.
*/

var p = console.log;
var assert = require('assert-plus');
var async = require('async');
var format = require('util').format;
var fs = require('fs');
var path = require('path');
var smartdc = require('smartdc');

var common = require('./common');
var loadConfigSync = require('./config').loadConfigSync;



//---- Joyent class

/**
* Create a Joyent.
*
* @param options {Object}
* - log {Bunyan Logger}
* - profile {String} Optional. Name of profile to use. Defaults to
* 'defaultProfile' in the config.
*/
function Joyent(options) {
assert.object(options, 'options');
assert.object(options.log, 'options.log');
assert.optionalString(options.profile, 'options.profile');
var self = this;

this.config = loadConfigSync();
this.profiles = this.config.profiles;
this.profile = this.getProfile(
options.profile || this.config.defaultProfile);

this.log = options.log;
this.log.trace({profile: this.profile}, 'profile data');
}


Joyent.prototype.setDefaultProfile = function setDefaultProfile(name, callback) {
if (!this.getProfile(name)) {
return callback(new Error('no such profile: ' + name));
}
this.defaultProfileName = this.config.defaultProfile = name;
common.saveConfigSync(this.config);
callback();
};

Joyent.prototype.getProfile = function getProfile(name) {
for (var i = 0; i < this.profiles.length; i++) {
if (this.profiles[i].name === name) {
return this.profiles[i];
}
}
};

/**
* Create or update a profile.
*
* @param profile {Object}
* @param options {Object}
* - setDefault {Boolean}
* @param callback {Function} `function (err)`
*/
Joyent.prototype.createOrUpdateProfile = function createOrUpdateProfile(
profile, options, callback) {
assert.object(profile, 'profile');
if (typeof(options) === 'function') {
callback = options;
options = {};
}
assert.object(options, 'options')
assert.optionalBool(options.setDefault, 'options.setDefault')
assert.func(callback, 'callback')

var found = false;
for (var i = 0; i < this.profiles.length; i++) {
if (this.profiles[i].name === profile.name) {
this.profiles[i] = profile;
found = true;
}
}
if (!found) {
this.profiles.push(profile);
}
if (options.setDefault) {
this.defaultProfileName = this.config.defaultProfile = profile.name;
}
common.saveConfigSync(this.config);
callback();
};

Joyent.prototype.deleteProfile = function deleteProfile(name, callback) {
var found = false;
for (var i = 0; i < this.profiles.length; i++) {
if (this.profiles[i].name === name) {
found = true;
this.profiles.splice(i, 1);
}
}
if (!found) {
return callback(new Error('no such profile: ' + name));
}
if (this.defaultProfileName === name) {
this.defaultProfileName = this.config.defaultProfile = null;
}
common.saveConfigSync(this.config);
callback();
};


Joyent.prototype._clientFromDc = function _clientFromDc(dc) {
assert.string(dc, 'dc');

if (!this._clientFromDcCache) {
this._clientFromDcCache = {};
}
if (!this._clientFromDcCache[dc]) {
var prof = this.profile;
var sign;
if (prof.privKey) {
sign = smartdc.privateKeySigner({
user: prof.account,
keyId: prof.keyId,
key: prof.privKey
});
} else {
sign = smartdc.cliSigner({keyId: prof.keyId, user: prof.account});
}
var client = smartdc.createClient({
url: this.config.dcs[dc],
account: prof.account,
version: '*',
noCache: true, //XXX
rejectUnauthorized: Boolean(prof.rejectUnauthorized),
sign: sign,
// XXX cloudapi.js stupidly uses its own logger, but takes logLevel
logLevel: this.log && this.log.level(),
// Pass our logger to underlying restify client.
log: this.log
});
this._clientFromDcCache[dc] = client;
}
return this._clientFromDcCache[dc];
};



/**
* List machines for the current profile.
*
* @param {Object} options Optional
* - {Function} onDcError `function (dc, err)` called for each DC client
* error.
*/
Joyent.prototype.listMachines = function listMachines(options, callback) {
var self = this;
if (callback === undefined) {
callback = options;
options = {}
}
assert.object(options, 'options');
assert.optionalFunc(options.onDcError, 'options.onDcError');
assert.func(callback, 'callback');

var allMachines = [];
async.each(
self.profile.dcs || Object.keys(self.config.dcs),
function oneDc(dc, next) {
var client = self._clientFromDc(dc);
client.listMachines(function (err, machines) {
if (err) {
if (options.onDcError) {
options.onDcError(dc, err);
}
} else {
for (var i = 0; i < machines.length; i++) {
machines[i].dc = dc;
allMachines.push(machines[i]);
}
}
next();
});
},
function done(err) {
callback(err, allMachines);
}
);
};



//---- exports

module.exports = Joyent;

+ 24
- 0
package.json View File

@@ -0,0 +1,24 @@
{
"name": "joyent",
"description": "Joyent Public Cloud CLI (https://my.joyentcloud.com/)",
"version": "1.0.0",
"author": "Joyent (joyent.com)",
"private": true,
"dependencies": {
"async": "0.2.9",
"assert-plus": "0.1.4",
"backoff": "2.3.0",
"bunyan": "0.22.0",
"cmdln": "1.3.1",
"dashdash": "1.3.2",
"extsprintf": "1.0.2",
"mkdirp": "0.3.5",
"node-uuid": "1.4.1",
"once": "1.3.0",
"smartdc": "git+ssh://git@github.com:joyent/node-smartdc.git#master",
"verror": "1.3.7"
},
"engines": {
"node": ">=0.10"
}
}

+ 139
- 0
tools/jsl.node.conf View File

@@ -0,0 +1,139 @@
#
# Configuration File for JavaScript Lint
#
# This configuration file can be used to lint a collection of scripts, or to enable
# or disable warnings for scripts that are linted via the command line.
#

### Warnings
# Enable or disable warnings based on requirements.
# Use "+WarningName" to display or "-WarningName" to suppress.
#
+ambiguous_else_stmt # the else statement could be matched with one of multiple if statements (use curly braces to indicate intent
+ambiguous_nested_stmt # block statements containing block statements should use curly braces to resolve ambiguity
+ambiguous_newline # unexpected end of line; it is ambiguous whether these lines are part of the same statement
-anon_no_return_value # anonymous function does not always return value
+assign_to_function_call # assignment to a function call
-block_without_braces # block statement without curly braces
+comma_separated_stmts # multiple statements separated by commas (use semicolons?)
+comparison_type_conv # comparisons against null, 0, true, false, or an empty string allowing implicit type conversion (use === or !==)
+default_not_at_end # the default case is not at the end of the switch statement
+dup_option_explicit # duplicate "option explicit" control comment
+duplicate_case_in_switch # duplicate case in switch statement
+duplicate_formal # duplicate formal argument {name}
+empty_statement # empty statement or extra semicolon
+identifier_hides_another # identifer {name} hides an identifier in a parent scope
-inc_dec_within_stmt # increment (++) and decrement (--) operators used as part of greater statement
+incorrect_version # Expected /*jsl:content-type*/ control comment. The script was parsed with the wrong version.
+invalid_fallthru # unexpected "fallthru" control comment
+invalid_pass # unexpected "pass" control comment
+jsl_cc_not_understood # couldn't understand control comment using /*jsl:keyword*/ syntax
+leading_decimal_point # leading decimal point may indicate a number or an object member
+legacy_cc_not_understood # couldn't understand control comment using /*@keyword@*/ syntax
+meaningless_block # meaningless block; curly braces have no impact
+mismatch_ctrl_comments # mismatched control comment; "ignore" and "end" control comments must have a one-to-one correspondence
+misplaced_regex # regular expressions should be preceded by a left parenthesis, assignment, colon, or comma
+missing_break # missing break statement
+missing_break_for_last_case # missing break statement for last case in switch
+missing_default_case # missing default case in switch statement
+missing_option_explicit # the "option explicit" control comment is missing
+missing_semicolon # missing semicolon
+missing_semicolon_for_lambda # missing semicolon for lambda assignment
+multiple_plus_minus # unknown order of operations for successive plus (e.g. x+++y) or minus (e.g. x---y) signs
+nested_comment # nested comment
-no_return_value # function {name} does not always return a value
-octal_number # leading zeros make an octal number
+parseint_missing_radix # parseInt missing radix parameter
+partial_option_explicit # the "option explicit" control comment, if used, must be in the first script tag
+redeclared_var # redeclaration of {name}
+trailing_comma_in_array # extra comma is not recommended in array initializers
+trailing_decimal_point # trailing decimal point may indicate a number or an object member
+undeclared_identifier # undeclared identifier: {name}
+unreachable_code # unreachable code
-unreferenced_argument # argument declared but never referenced: {name}
-unreferenced_function # function is declared but never referenced: {name}
+unreferenced_variable # variable is declared but never referenced: {name}
+unsupported_version # JavaScript {version} is not supported
+use_of_label # use of label
+useless_assign # useless assignment
+useless_comparison # useless comparison; comparing identical expressions
-useless_quotes # the quotation marks are unnecessary
+useless_void # use of the void type may be unnecessary (void is always undefined)
+var_hides_arg # variable {name} hides argument
+want_assign_or_call # expected an assignment or function call
+with_statement # with statement hides undeclared variables; use temporary variable instead


### Output format
# Customize the format of the error message.
# __FILE__ indicates current file path
# __FILENAME__ indicates current file name
# __LINE__ indicates current line
# __COL__ indicates current column
# __ERROR__ indicates error message (__ERROR_PREFIX__: __ERROR_MSG__)
# __ERROR_NAME__ indicates error name (used in configuration file)
# __ERROR_PREFIX__ indicates error prefix
# __ERROR_MSG__ indicates error message
#
# For machine-friendly output, the output format can be prefixed with
# "encode:". If specified, all items will be encoded with C-slashes.
#
# Visual Studio syntax (default):
+output-format __FILE__(__LINE__): __ERROR__
# Alternative syntax:
#+output-format __FILE__:__LINE__: __ERROR__


### Context
# Show the in-line position of the error.
# Use "+context" to display or "-context" to suppress.
#
+context


### Control Comments
# Both JavaScript Lint and the JScript interpreter confuse each other with the syntax for
# the /*@keyword@*/ control comments and JScript conditional comments. (The latter is
# enabled in JScript with @cc_on@). The /*jsl:keyword*/ syntax is preferred for this reason,
# although legacy control comments are enabled by default for backward compatibility.
#
-legacy_control_comments


### Defining identifiers
# By default, "option explicit" is enabled on a per-file basis.
# To enable this for all files, use "+always_use_option_explicit"
-always_use_option_explicit

# Define certain identifiers of which the lint is not aware.
# (Use this in conjunction with the "undeclared identifier" warning.)
#
# Common uses for webpages might be:
+define __dirname
+define __filename
+define clearInterval
+define clearTimeout
+define console
+define exports
+define global
+define module
+define process
+define require
+define setInterval
+define setTimeout
+define Buffer
+define JSON
+define Math

### JavaScript Version
# To change the default JavaScript version:
#+default-type text/javascript;version=1.5
#+default-type text/javascript;e4x=1

### Files
# Specify which files to lint
# Use "+recurse" to enable recursion (disabled by default).
# To add a set of files, use "+process FileName", "+process Folder\Path\*.js",
# or "+process Folder\Path\*.htm".
#


+ 5
- 0
tools/jsstyle.conf View File

@@ -0,0 +1,5 @@
indent=4
doxygen
unparenthesized-return=0
blank-after-start-comment=0
leading-right-paren-ok=1

+ 43
- 0
tools/mk/Makefile.defs View File

@@ -0,0 +1,43 @@
# -*- mode: makefile -*-
#
# Copyright (c) 2012, Joyent, Inc. All rights reserved.
#
# Makefile.defs: common defines.
#
# NOTE: This makefile comes from the "eng" repo. It's designed to be dropped
# into other repos as-is without requiring any modifications. If you find
# yourself changing this file, you should instead update the original copy in
# eng.git and then update your repo to use the new version.
#
# This makefile defines some useful defines. Include it at the top of
# your Makefile.
#
# Definitions in this Makefile:
#
# TOP The absolute path to the project directory. The top dir.
# BRANCH The current git branch.
# TIMESTAMP The timestamp for the build. This can be set via
# the TIMESTAMP envvar (used by MG-based builds).
# STAMP A build stamp to use in built package names.
#

TOP := $(shell pwd)

#
# Mountain Gorilla-spec'd versioning.
# See "Package Versioning" in MG's README.md:
# <https://mo.joyent.com/mountain-gorilla/blob/master/README.md#L139-200>
#
# Need GNU awk for multi-char arg to "-F".
_AWK := $(shell (which gawk >/dev/null && echo gawk) \
|| (which nawk >/dev/null && echo nawk) \
|| echo awk)
BRANCH := $(shell git symbolic-ref HEAD | $(_AWK) -F/ '{print $$3}')
ifeq ($(TIMESTAMP),)
TIMESTAMP := $(shell date -u "+%Y%m%dT%H%M%SZ")
endif
_GITDESCRIBE := g$(shell git describe --all --long --dirty | $(_AWK) -F'-g' '{print $$NF}')
STAMP := $(BRANCH)-$(TIMESTAMP)-$(_GITDESCRIBE)

# node-gyp will print build info useful for debugging with V=1
export V=1

+ 44
- 0
tools/mk/Makefile.deps View File

@@ -0,0 +1,44 @@
# -*- mode: makefile -*-
#
# Copyright (c) 2012, Joyent, Inc. All rights reserved.
#
# Makefile.deps: Makefile for including common tools as dependencies
#
# NOTE: This makefile comes from the "eng" repo. It's designed to be dropped
# into other repos as-is without requiring any modifications. If you find
# yourself changing this file, you should instead update the original copy in
# eng.git and then update your repo to use the new version.
#
# This file is separate from Makefile.targ so that teams can choose
# independently whether to use the common targets in Makefile.targ and the
# common tools here.
#

#
# javascriptlint
#
JSL_EXEC ?= deps/javascriptlint/build/install/jsl
JSL ?= $(JSL_EXEC)

$(JSL_EXEC): | deps/javascriptlint/.git
cd deps/javascriptlint && make install

distclean::
if [[ -f deps/javascriptlint/Makefile ]]; then \
cd deps/javascriptlint && make clean; \
fi

#
# jsstyle
#
JSSTYLE_EXEC ?= deps/jsstyle/jsstyle
JSSTYLE ?= $(JSSTYLE_EXEC)

$(JSSTYLE_EXEC): | deps/jsstyle/.git

#
# restdown
#
RESTDOWN_EXEC ?= deps/restdown/bin/restdown
RESTDOWN ?= python $(RESTDOWN_EXEC)
$(RESTDOWN_EXEC): | deps/restdown/.git

+ 309
- 0
tools/mk/Makefile.targ View File

@@ -0,0 +1,309 @@
# -*- mode: makefile -*-
#
# Copyright (c) 2012, Joyent, Inc. All rights reserved.
#
# Makefile.targ: common targets.
#
# NOTE: This makefile comes from the "eng" repo. It's designed to be dropped
# into other repos as-is without requiring any modifications. If you find
# yourself changing this file, you should instead update the original copy in
# eng.git and then update your repo to use the new version.
#
# This Makefile defines several useful targets and rules. You can use it by
# including it from a Makefile that specifies some of the variables below.
#
# Targets defined in this Makefile:
#
# check Checks JavaScript files for lint and style
# Checks bash scripts for syntax
# Checks SMF manifests for validity against the SMF DTD
#
# clean Removes built files
#
# docs Builds restdown documentation in docs/
#
# prepush Depends on "check" and "test"
#
# test Does nothing (you should override this)
#
# xref Generates cscope (source cross-reference index)
#
# For details on what these targets are supposed to do, see the Joyent
# Engineering Guide.
#
# To make use of these targets, you'll need to set some of these variables. Any
# variables left unset will simply not be used.
#
# BASH_FILES Bash scripts to check for syntax
# (paths relative to top-level Makefile)
#
# CLEAN_FILES Files to remove as part of the "clean" target. Note
# that files generated by targets in this Makefile are
# automatically included in CLEAN_FILES. These include
# restdown-generated HTML and JSON files.
#
# DOC_FILES Restdown (documentation source) files. These are
# assumed to be contained in "docs/", and must NOT
# contain the "docs/" prefix.
#
# JSL_CONF_NODE Specify JavaScriptLint configuration files
# JSL_CONF_WEB (paths relative to top-level Makefile)
#
# Node.js and Web configuration files are separate
# because you'll usually want different global variable
# configurations. If no file is specified, none is given
# to jsl, which causes it to use a default configuration,
# which probably isn't what you want.
#
# JSL_FILES_NODE JavaScript files to check with Node config file.
# JSL_FILES_WEB JavaScript files to check with Web config file.
#
# JSON_FILES JSON files to be validated
#
# JSSTYLE_FILES JavaScript files to be style-checked
#
# You can also override these variables:
#
# BASH Path to bash (default: "bash")
#
# CSCOPE_DIRS Directories to search for source files for the cscope
# index. (default: ".")
#
# JSL Path to JavaScriptLint (default: "jsl")
#
# JSL_FLAGS_NODE Additional flags to pass through to JSL
# JSL_FLAGS_WEB
# JSL_FLAGS
#
# JSON Path to json tool (default: "json")
#
# JSSTYLE Path to jsstyle (default: "jsstyle")
#
# JSSTYLE_FLAGS Additional flags to pass through to jsstyle
#
# RESTDOWN_EXT By default '.restdown' is required for DOC_FILES
# (see above). If you want to use, say, '.md' instead, then
# set 'RESTDOWN_EXT=.md' in your Makefile.
#

#
# Defaults for the various tools we use.
#
BASH ?= bash
BASHSTYLE ?= tools/bashstyle
CP ?= cp
CSCOPE ?= cscope
CSCOPE_DIRS ?= .
JSL ?= jsl
JSON ?= json
JSSTYLE ?= jsstyle
MKDIR ?= mkdir -p
MV ?= mv
RESTDOWN_FLAGS ?=
RESTDOWN_EXT ?= .restdown
RMTREE ?= rm -rf
JSL_FLAGS ?= --nologo --nosummary

ifeq ($(shell uname -s),SunOS)
TAR ?= gtar
else
TAR ?= tar
endif


#
# Defaults for other fixed values.
#
BUILD = build
DISTCLEAN_FILES += $(BUILD)
DOC_BUILD = $(BUILD)/docs/public

#
# Configure JSL_FLAGS_{NODE,WEB} based on JSL_CONF_{NODE,WEB}.
#
ifneq ($(origin JSL_CONF_NODE), undefined)
JSL_FLAGS_NODE += --conf=$(JSL_CONF_NODE)
endif

ifneq ($(origin JSL_CONF_WEB), undefined)
JSL_FLAGS_WEB += --conf=$(JSL_CONF_WEB)
endif

#
# Targets. For descriptions on what these are supposed to do, see the
# Joyent Engineering Guide.
#

#
# Instruct make to keep around temporary files. We have rules below that
# automatically update git submodules as needed, but they employ a deps/*/.git
# temporary file. Without this directive, make tries to remove these .git
# directories after the build has completed.
#
.SECONDARY: $($(wildcard deps/*):%=%/.git)

#
# This rule enables other rules that use files from a git submodule to have
# those files depend on deps/module/.git and have "make" automatically check
# out the submodule as needed.
#
deps/%/.git:
git submodule update --init deps/$*

#
# These recipes make heavy use of dynamically-created phony targets. The parent
# Makefile defines a list of input files like BASH_FILES. We then say that each
# of these files depends on a fake target called filename.bashchk, and then we
# define a pattern rule for those targets that runs bash in check-syntax-only
# mode. This mechanism has the nice properties that if you specify zero files,
# the rule becomes a noop (unlike a single rule to check all bash files, which
# would invoke bash with zero files), and you can check individual files from
# the command line with "make filename.bashchk".
#
.PHONY: check-bash
check-bash: $(BASH_FILES:%=%.bashchk) $(BASH_FILES:%=%.bashstyle)

%.bashchk: %
$(BASH) -n $^

%.bashstyle: %
$(BASHSTYLE) $^

.PHONY: check-json
check-json: $(JSON_FILES:%=%.jsonchk)

%.jsonchk: %
$(JSON) --validate -f $^

#
# The above approach can be slow when there are many files to check because it
# requires that "make" invoke the check tool once for each file, rather than
# passing in several files at once. For the JavaScript check targets, we define
# a variable for the target itself *only if* the list of input files is
# non-empty. This avoids invoking the tool if there are no files to check.
#
JSL_NODE_TARGET = $(if $(JSL_FILES_NODE), check-jsl-node)
.PHONY: check-jsl-node
check-jsl-node: $(JSL_EXEC)
$(JSL) $(JSL_FLAGS) $(JSL_FLAGS_NODE) $(JSL_FILES_NODE)

JSL_WEB_TARGET = $(if $(JSL_FILES_WEB), check-jsl-web)
.PHONY: check-jsl-web
check-jsl-web: $(JSL_EXEC)
$(JSL) $(JSL_FLAGS) $(JSL_FLAGS_WEB) $(JSL_FILES_WEB)

.PHONY: check-jsl
check-jsl: $(JSL_NODE_TARGET) $(JSL_WEB_TARGET)

JSSTYLE_TARGET = $(if $(JSSTYLE_FILES), check-jsstyle)
.PHONY: check-jsstyle
check-jsstyle: $(JSSTYLE_EXEC)
$(JSSTYLE) $(JSSTYLE_FLAGS) $(JSSTYLE_FILES)

.PHONY: check
check: check-jsl check-json $(JSSTYLE_TARGET) check-bash
@echo check ok

.PHONY: clean
clean::
-$(RMTREE) $(CLEAN_FILES)

.PHONY: distclean
distclean:: clean
-$(RMTREE) $(DISTCLEAN_FILES)

CSCOPE_FILES = cscope.in.out cscope.out cscope.po.out
CLEAN_FILES += $(CSCOPE_FILES)

.PHONY: xref
xref: cscope.files
$(CSCOPE) -bqR

.PHONY: cscope.files
cscope.files:
find $(CSCOPE_DIRS) -name '*.c' -o -name '*.h' -o -name '*.cc' \
-o -name '*.js' -o -name '*.s' -o -name '*.cpp' > $@

#
# The "docs" target is complicated because we do several things here:
#
# (1) Use restdown to build HTML and JSON files from each of DOC_FILES.
#
# (2) Copy these files into $(DOC_BUILD) (build/docs/public), which
# functions as a complete copy of the documentation that could be
# mirrored or served over HTTP.
#
# (3) Then copy any directories and media from docs/media into
# $(DOC_BUILD)/media. This allows projects to include their own media,
# including files that will override same-named files provided by
# restdown.
#
# Step (3) is the surprisingly complex part: in order to do this, we need to
# identify the subdirectories in docs/media, recreate them in
# $(DOC_BUILD)/media, then do the same with the files.
#
DOC_MEDIA_DIRS := $(shell find docs/media -type d 2>/dev/null | grep -v "^docs/media$$")
DOC_MEDIA_DIRS := $(DOC_MEDIA_DIRS:docs/media/%=%)
DOC_MEDIA_DIRS_BUILD := $(DOC_MEDIA_DIRS:%=$(DOC_BUILD)/media/%)

DOC_MEDIA_FILES := $(shell find docs/media -type f 2>/dev/null)
DOC_MEDIA_FILES := $(DOC_MEDIA_FILES:docs/media/%=%)
DOC_MEDIA_FILES_BUILD := $(DOC_MEDIA_FILES:%=$(DOC_BUILD)/media/%)

#
# Like the other targets, "docs" just depends on the final files we want to
# create in $(DOC_BUILD), leveraging other targets and recipes to define how
# to get there.
#
.PHONY: docs
docs: \
$(DOC_FILES:%$(RESTDOWN_EXT)=$(DOC_BUILD)/%.html) \
$(DOC_FILES:%$(RESTDOWN_EXT)=$(DOC_BUILD)/%.json) \
$(DOC_MEDIA_FILES_BUILD)

#
# We keep the intermediate files so that the next build can see whether the
# files in DOC_BUILD are up to date.
#
.PRECIOUS: \
$(DOC_FILES:%$(RESTDOWN_EXT)=docs/%.html) \
$(DOC_FILES:%$(RESTDOWN_EXT)=docs/%json)

#
# We do clean those intermediate files, as well as all of DOC_BUILD.
#
CLEAN_FILES += \
$(DOC_BUILD) \
$(DOC_FILES:%$(RESTDOWN_EXT)=docs/%.html) \
$(DOC_FILES:%$(RESTDOWN_EXT)=docs/%.json)

#
# Before installing the files, we must make sure the directories exist. The |
# syntax tells make that the dependency need only exist, not be up to date.
# Otherwise, it might try to rebuild spuriously because the directory itself
# appears out of date.
#
$(DOC_MEDIA_FILES_BUILD): | $(DOC_MEDIA_DIRS_BUILD)

$(DOC_BUILD)/%: docs/% | $(DOC_BUILD)
$(CP) $< $@

docs/%.json docs/%.html: docs/%$(RESTDOWN_EXT) | $(DOC_BUILD) $(RESTDOWN_EXEC)
$(RESTDOWN) $(RESTDOWN_FLAGS) -m $(DOC_BUILD) $<

$(DOC_BUILD):
$(MKDIR) $@

$(DOC_MEDIA_DIRS_BUILD):
$(MKDIR) $@

#
# The default "test" target does nothing. This should usually be overridden by
# the parent Makefile. It's included here so we can define "prepush" without
# requiring the repo to define "test".
#
.PHONY: test
test:

.PHONY: prepush
prepush: check test

+ 24
- 0
tools/rsync-to View File

@@ -0,0 +1,24 @@
#!/bin/bash
#
# Rsync the master in this working copy to the install on the given HN.
#

#set -o xtrace
set -o errexit

TOP=$(cd $(dirname $0)/../; pwd)
NODE=$1
[[ -z "$NODE" ]] && echo 'rsync-to: error: no headnode given' && exit 1
BASEDIR=/opt/smartdc/sdcadm

extraOpts=
if [[ $(uname -s) != "SunOS" ]]; then
extraOpts="--exclude *.node --exclude build"
else
# Clean node_modules everytime.
ssh $NODE rm -rf $BASEDIR/node_modules
fi

for f in bin lib node_modules package.json; do
rsync -av ${TOP}/$f $NODE:$BASEDIR/$f $extraOpts
done