unfinished and broken work :)
This commit is contained in:
		
							parent
							
								
									1882dbf18e
								
							
						
					
					
						commit
						dfca3e0ace
					
				
							
								
								
									
										86
									
								
								TODO.md
									
									
									
									
									
								
							
							
						
						
									
										86
									
								
								TODO.md
									
									
									
									
									
								
							@ -1,18 +1,56 @@
 | 
				
			|||||||
# first
 | 
					# first
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Adding/removing DCs. Want this to work reasonably mainly to support dogfooding
 | 
				
			||||||
 | 
					  with internal DCs. Also to allow this to be a general tool for *SDC*,
 | 
				
			||||||
 | 
					  with default values for JPC, but not restricted to. Also allow the right thing
 | 
				
			||||||
 | 
					  to happen if JPC adds new DCs.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    - Don't use "all" catch all DC. Use "joyent" alias for the default set.
 | 
				
			||||||
 | 
					    - Add DC aliases (starting a generic aliasing).
 | 
				
			||||||
 | 
					    - Show the aliases in `sdc dcs`
 | 
				
			||||||
 | 
					    - support aliases in the command lookups. Method to get the DCs for the current
 | 
				
			||||||
 | 
					      profile
 | 
				
			||||||
 | 
					    XXX START HERE
 | 
				
			||||||
 | 
					    - changing dcs:
 | 
				
			||||||
 | 
					        sdc dcs add us-beta-4 https://beta4-cloudapi.joyent.us
 | 
				
			||||||
 | 
					        sdc dcs set-url us-beta-4 https://beta4-cloudapi.joyent.us
 | 
				
			||||||
 | 
					        sdc dcs rm us-beta-4
 | 
				
			||||||
 | 
					      Note: If having config.dcs override this means that any DC change means
 | 
				
			||||||
 | 
					      that user doesn't "see" DC changes by new node-sdc versions.
 | 
				
			||||||
 | 
					    - Impl 'sdc config' to edit these easily on the CLI.
 | 
				
			||||||
 | 
					          sdc config alias.dc.<alias> <dc-name-1> <dc-name-2> ...
 | 
				
			||||||
 | 
					          sdc config alias.image.<alias> <image-uuid> ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- machines:
 | 
					- machines:
 | 
				
			||||||
    - short default output
 | 
					    - short default output
 | 
				
			||||||
 | 
					        - 'cdate'   short created, just the date
 | 
				
			||||||
 | 
					        - 'img'   is 'name/version'
 | 
				
			||||||
 | 
					        - 'sid'   is the short id prefix
 | 
				
			||||||
    - long '-l' output, -H, -o, -s
 | 
					    - long '-l' output, -H, -o, -s
 | 
				
			||||||
    - get image defaults and fill those in
 | 
					    - get image defaults and fill those in
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- few more commands?  provision (create-machine?)
 | 
					- few more commands?  provision (create-machine?)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- uuid caching
 | 
					- uuid caching
 | 
				
			||||||
- UUID prefix support
 | 
					- UUID prefix support
 | 
				
			||||||
- profile command (adding profile, edit, etc.)
 | 
					- profile command (adding profile, edit, etc.)
 | 
				
			||||||
 | 
					- `sdc config` command similar to git config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# account vs user vs subuser vs role
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					See MANTA-2401 and scrum discussion from 14 Aug 2014..
 | 
				
			||||||
 | 
					Suggestion: use "account" and "user" since "since those are the documented
 | 
				
			||||||
 | 
					tools for the abstractions and that's what smartdc uses."
 | 
				
			||||||
 | 
					Envvars: SDC_ACCOUNT and SDC_USER.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# later (in no particular order)
 | 
					# later (in no particular order)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- adding a dc:
 | 
				
			||||||
 | 
					        sdc dcs -a us-beta-4 https://beta4-cloudapi.joyent.us
 | 
				
			||||||
 | 
					  or
 | 
				
			||||||
- signing: should sigstr include more than just the date? How about the request
 | 
					- signing: should sigstr include more than just the date? How about the request
 | 
				
			||||||
  path??? Not according to the cloudapi docs.
 | 
					  path??? Not according to the cloudapi docs.
 | 
				
			||||||
- restify-client and bunyan-light without dtrace-provider
 | 
					- restify-client and bunyan-light without dtrace-provider
 | 
				
			||||||
@ -47,3 +85,51 @@
 | 
				
			|||||||
  add a "joyentcloud foo" subcmd. Reasonable?
 | 
					  add a "joyentcloud foo" subcmd. Reasonable?
 | 
				
			||||||
- windows testing
 | 
					- windows testing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ideas
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- `sdc whatsnew` grabs current images and packages and compares to last time
 | 
				
			||||||
 | 
					  it was called to short new images/packages. Perhaps for other resources too.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# notes on `sdc provision` (in progress)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Lame: I <# that our packages are separate for kvm vs smartos usage. Do they
 | 
				
			||||||
 | 
					  have conflicting data?
 | 
				
			||||||
 | 
					- Q: "package" or "instance-type"? Probably package for now.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Need: dc (if profile has multiple, have a settable preferred dc for provisions),
 | 
				
			||||||
 | 
					image (uuid, name to get latest, have a settable preferred?), package (settable
 | 
				
			||||||
 | 
					preferred, settable preferred ram).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					What about using "same as last time" or a way to say that?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Want interactive asking for missing params if TTY? -f to avoid.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $ sdc provision ...
 | 
				
			||||||
 | 
					    Datacenter [us-west-1]: <prompt>
 | 
				
			||||||
 | 
					    ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Name: AWS equiv is 'aws-cli ec2 run-instances'
 | 
				
			||||||
 | 
					http://docs.aws.amazon.com/cli/latest/reference/ec2/run-instances.html
 | 
				
			||||||
 | 
					E.g.:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    aws ec2 run-instances --image-id ami-c3b8d6aa --count 1 --instance-type t1.micro --key-name MyKeyPair --security-groups MySecurityGroup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sdc create-machine ...
 | 
				
			||||||
 | 
					    sdc provision ...
 | 
				
			||||||
 | 
					    sdc provision -i IMAGE -p PACKAGE
 | 
				
			||||||
 | 
					    shortcut?
 | 
				
			||||||
 | 
					        sdc provision IMAGE:PKG     ?
 | 
				
			||||||
 | 
					        sdc provision IMAGE PKG     ?
 | 
				
			||||||
 | 
					        sdc provision image=IMAGE package=PKG  ? no
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sdc provision -i IMAGE -p PKG -c 3 --name 'test%d'  # printf codes for the count
 | 
				
			||||||
 | 
					    sdc provision -d east -i base -p g3-standard-1 -n shirley  # -d|--dc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Clarify what IMAGE can be. "Name" matching is first against one's own private
 | 
				
			||||||
 | 
					images, then against public ones. UUID. UUID prefix. "Name/version" matching.
 | 
				
			||||||
 | 
					Image alias (`sdc config alias.bob $uuid`, though for git that alias is for
 | 
				
			||||||
 | 
					*commands*. Perhaps `sdc alias image.bob $uuid`. Dunno. Later.).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Similar matching for PKG.
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,12 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    "defaultProfile": "env",
 | 
					    "defaultProfile": "env",
 | 
				
			||||||
    "dcs": {
 | 
					    "dc": {
 | 
				
			||||||
        "us-east-1": "https://us-east-1.api.joyent.com",
 | 
					        "us-east-1": "https://us-east-1.api.joyent.com",
 | 
				
			||||||
        "us-west-1": "https://us-west-1.api.joyent.com",
 | 
					        "us-west-1": "https://us-west-1.api.joyent.com",
 | 
				
			||||||
        "us-sw-1": "https://us-sw-1.api.joyent.com",
 | 
					        "us-sw-1": "https://us-sw-1.api.joyent.com",
 | 
				
			||||||
        "eu-ams-1": "https://eu-ams-1.api.joyent.com"
 | 
					        "eu-ams-1": "https://eu-ams-1.api.joyent.com"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "dcAlias": {
 | 
				
			||||||
 | 
					        "joyent": ["us-east-1", "us-sw-1", "us-west-1", "eu-ams-1"]
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										301
									
								
								lib/cli.js
									
									
									
									
									
								
							
							
						
						
									
										301
									
								
								lib/cli.js
									
									
									
									
									
								
							@ -87,6 +87,102 @@ CLI.prototype.init = function (opts, args, callback) {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CLI.prototype.do_config = function (subcmd, opts, args, callback) {
 | 
				
			||||||
 | 
					    if (opts.help) {
 | 
				
			||||||
 | 
					        this.do_help('help', {}, [subcmd], callback);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var action;
 | 
				
			||||||
 | 
					    var actions = [];
 | 
				
			||||||
 | 
					    if (opts.add) actions.push('add');
 | 
				
			||||||
 | 
					    if (opts['delete']) actions.push('delete');
 | 
				
			||||||
 | 
					    if (opts.edit) actions.push('edit');
 | 
				
			||||||
 | 
					    if (actions.length === 0) {
 | 
				
			||||||
 | 
					        action = 'show';
 | 
				
			||||||
 | 
					    } else if (actions.length > 1) {
 | 
				
			||||||
 | 
					        return callback(new errors.UsageError(
 | 
				
			||||||
 | 
					            'cannot specify more than one action: ' + actions.join(', ')));
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        action = actions[0];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    var numArgs = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (action === 'show') {
 | 
				
			||||||
 | 
					        var c = common.objCopy(this.sdc.config);
 | 
				
			||||||
 | 
					        delete c._defaults;
 | 
				
			||||||
 | 
					        delete c._user;
 | 
				
			||||||
 | 
					        if (args.length > 1) {
 | 
				
			||||||
 | 
					            return callback(new errors.UsageError('too many args'));
 | 
				
			||||||
 | 
					        } else if (args.length === 1) {
 | 
				
			||||||
 | 
					            var lookups = args[0].split(/\./g);
 | 
				
			||||||
 | 
					            for (var i = 0; i < lookups.length; i++) {
 | 
				
			||||||
 | 
					                c = c[lookups[i]];
 | 
				
			||||||
 | 
					                if (c === undefined) {
 | 
				
			||||||
 | 
					                    return callback(new errors.UsageError(
 | 
				
			||||||
 | 
					                        'no such config var: ' + args[0]));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (typeof(c) === 'string') {
 | 
				
			||||||
 | 
					            console.log(c)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            console.log(JSON.stringify(c, null, 4));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else if (action === 'add') {
 | 
				
			||||||
 | 
					        if (args.length !== 2)
 | 
				
			||||||
 | 
					            return callback(new errors.UsageError('incorrect number of args'));
 | 
				
			||||||
 | 
					        XXX
 | 
				
			||||||
 | 
					    } else if (action === 'delete') {
 | 
				
			||||||
 | 
					        if (args.length !== 1)
 | 
				
			||||||
 | 
					            return callback(new errors.UsageError('incorrect number of args'));
 | 
				
			||||||
 | 
					        XXX
 | 
				
			||||||
 | 
					    } else if (action === 'edit') {
 | 
				
			||||||
 | 
					        if (args.length !== 0)
 | 
				
			||||||
 | 
					            return callback(new errors.UsageError('incorrect number of args'));
 | 
				
			||||||
 | 
					        XXX
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    callback();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					CLI.prototype.do_config.options = [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        names: ['help', 'h'],
 | 
				
			||||||
 | 
					        type: 'bool',
 | 
				
			||||||
 | 
					        help: 'Show this help.'
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        names: ['add', 'a'],
 | 
				
			||||||
 | 
					        type: 'bool',
 | 
				
			||||||
 | 
					        help: 'Add a config var.'
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        names: ['delete', 'd'],
 | 
				
			||||||
 | 
					        type: 'bool',
 | 
				
			||||||
 | 
					        help: 'Delete a config var.'
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        names: ['edit', 'e'],
 | 
				
			||||||
 | 
					        type: 'bool',
 | 
				
			||||||
 | 
					        help: 'Edit config in $EDITOR.'
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					CLI.prototype.do_config.help = (
 | 
				
			||||||
 | 
					    'Show and edit the `sdc` CLI config.\n'
 | 
				
			||||||
 | 
					    + '\n'
 | 
				
			||||||
 | 
					    + 'Usage:\n'
 | 
				
			||||||
 | 
					    + '     {{name}} config                     # show config\n'
 | 
				
			||||||
 | 
					    + '     {{name}} config <name>              # show particular config var\n'
 | 
				
			||||||
 | 
					    + '     {{name}} config -a <name> <value>   # add/set a config var\n'
 | 
				
			||||||
 | 
					    + '     {{name}} config -d <name>           # delete a config var\n'
 | 
				
			||||||
 | 
					    + '     {{name}} config -e                  # edit config in $EDITOR\n'
 | 
				
			||||||
 | 
					    + '\n'
 | 
				
			||||||
 | 
					    + '{{options}}'
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CLI.prototype.do_profile = function (subcmd, opts, args, callback) {
 | 
					CLI.prototype.do_profile = function (subcmd, opts, args, callback) {
 | 
				
			||||||
    if (opts.help) {
 | 
					    if (opts.help) {
 | 
				
			||||||
        this.do_help('help', {}, [subcmd], callback);
 | 
					        this.do_help('help', {}, [subcmd], callback);
 | 
				
			||||||
@ -136,26 +232,65 @@ CLI.prototype.do_profile.help = (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CLI.prototype.do_dcs = function (subcmd, opts, args, callback) {
 | 
					CLI.prototype.do_dcs = function (subcmd, opts, args, callback) {
 | 
				
			||||||
 | 
					    var self = this;
 | 
				
			||||||
    if (opts.help) {
 | 
					    if (opts.help) {
 | 
				
			||||||
        this.do_help('help', {}, [subcmd], callback);
 | 
					        this.do_help('help', {}, [subcmd], callback);
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    } else if (args.length > 1) {
 | 
					 | 
				
			||||||
        return callback(new Error('too many args: ' + args));
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var dcs = this.sdc.config.dcs;
 | 
					    var action = args[0] || 'list';
 | 
				
			||||||
 | 
					    var name;
 | 
				
			||||||
 | 
					    var url;
 | 
				
			||||||
 | 
					    switch (action) {
 | 
				
			||||||
 | 
					    case 'list':
 | 
				
			||||||
 | 
					        if (args.length !== 0) {
 | 
				
			||||||
 | 
					            return callback(new errors.UsageError('too many args: ' + args));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        var dcs = self.sdc.config.dc;
 | 
				
			||||||
        var dcsArray = Object.keys(dcs).map(
 | 
					        var dcsArray = Object.keys(dcs).map(
 | 
				
			||||||
            function (n) { return {name: n, url: dcs[n]}; });
 | 
					            function (n) { return {name: n, url: dcs[n]}; });
 | 
				
			||||||
 | 
					        if (self.sdc.config.dcAlias) {
 | 
				
			||||||
 | 
					            Object.keys(self.sdc.config.dcAlias).forEach(function (alias) {
 | 
				
			||||||
 | 
					                dcsArray.push(
 | 
				
			||||||
 | 
					                    {alias: alias, names: self.sdc.config.dcAlias[alias]});
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        if (opts.json) {
 | 
					        if (opts.json) {
 | 
				
			||||||
            p(JSON.stringify(dcsArray, null, 4));
 | 
					            p(JSON.stringify(dcsArray, null, 4));
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
 | 
					            for (var i = 0; i < dcsArray.length; i++) {
 | 
				
			||||||
 | 
					                var d = dcsArray[i];
 | 
				
			||||||
 | 
					                d.name = (d.name ? d.name : d.alias + '*');
 | 
				
			||||||
 | 
					                d.url = d.url || d.names.join(', ');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            common.tabulate(dcsArray, {
 | 
					            common.tabulate(dcsArray, {
 | 
				
			||||||
                columns: 'name,url',
 | 
					                columns: 'name,url',
 | 
				
			||||||
            sort: 'name',
 | 
					                sort: 'alias,name',
 | 
				
			||||||
            validFields: 'name,url'
 | 
					                validFields: 'name,url,alias,names'
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        callback();
 | 
					        callback();
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    case 'rm':
 | 
				
			||||||
 | 
					        if (args.length !== 2) {
 | 
				
			||||||
 | 
					            return callback(new errors.UsageError(
 | 
				
			||||||
 | 
					                'incorrect number of args: ' + args));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        name = args[1];
 | 
				
			||||||
 | 
					        XXX
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    case 'add':
 | 
				
			||||||
 | 
					        if (args.length !== 3) {
 | 
				
			||||||
 | 
					            return callback(new errors.UsageError(
 | 
				
			||||||
 | 
					                'incorrect number of args: ' + args));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        name = args[1];
 | 
				
			||||||
 | 
					        url = args[2];
 | 
				
			||||||
 | 
					        XXX
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					        return callback(new errors.UsageError('unknown dcs command: ' + args))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
CLI.prototype.do_dcs.options = [
 | 
					CLI.prototype.do_dcs.options = [
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@ -173,12 +308,107 @@ CLI.prototype.do_dcs.help = (
 | 
				
			|||||||
    'List, add or remove datacenters.\n'
 | 
					    'List, add or remove datacenters.\n'
 | 
				
			||||||
    + '\n'
 | 
					    + '\n'
 | 
				
			||||||
    + 'Usage:\n'
 | 
					    + 'Usage:\n'
 | 
				
			||||||
    + '     {{name}} dcs\n'
 | 
					    + '     {{name}} dcs                    # list DCs (and DC aliases marked with "*")\n'
 | 
				
			||||||
 | 
					    + '     {{name}} dcs add <name> <url>   # add an SDC cloudapi endpoint\n'
 | 
				
			||||||
 | 
					    + '     {{name}} dcs rm <name>          # remove a DC\n'
 | 
				
			||||||
    + '\n'
 | 
					    + '\n'
 | 
				
			||||||
    + '{{options}}'
 | 
					    + '{{options}}'
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CLI.prototype.do_provision = 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 sdc = this.sdc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert.string(opts.image, '--image <img>');
 | 
				
			||||||
 | 
					    assert.string(opts['package'], '--package <pkg>');
 | 
				
			||||||
 | 
					    assert.number(opts.count)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // XXX
 | 
				
			||||||
 | 
					    /*
 | 
				
			||||||
 | 
					     * Should all this move into sdc.createMachine? yes
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * - lookup image, package, networks from args
 | 
				
			||||||
 | 
					     * - assign names
 | 
				
			||||||
 | 
					     * - start provisions (slight stagger, max N at a time)
 | 
				
			||||||
 | 
					     * - return immediately, or '-w|--wait'
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async.series([
 | 
				
			||||||
 | 
					        function lookups(next) {
 | 
				
			||||||
 | 
					            async.parallel([
 | 
				
			||||||
 | 
					                //XXX
 | 
				
			||||||
 | 
					                //sdc.lookup(image)
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        function provisions(next) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        function wait(next) {
 | 
				
			||||||
 | 
					            next();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ], function (err) {
 | 
				
			||||||
 | 
					        callback(err);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					CLI.prototype.do_provision.options = [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        names: ['help', 'h'],
 | 
				
			||||||
 | 
					        type: 'bool',
 | 
				
			||||||
 | 
					        help: 'Show this help.'
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        names: ['dc', 'd'],
 | 
				
			||||||
 | 
					        type: 'string',
 | 
				
			||||||
 | 
					        helpArg: '<dc>',
 | 
				
			||||||
 | 
					        help: 'The datacenter in which to provision. Required if the current'
 | 
				
			||||||
 | 
					            + ' profile includes more than one datacenter. Use `sdc profile`'
 | 
				
			||||||
 | 
					            + ' to list profiles and `sdc dcs` to list available datacenters.'
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        names: ['image', 'i'],
 | 
				
			||||||
 | 
					        type: 'string',
 | 
				
			||||||
 | 
					        helpArg: '<img>',
 | 
				
			||||||
 | 
					        help: 'The machine image with which to provision. Required.'
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        names: ['package', 'p'],
 | 
				
			||||||
 | 
					        type: 'string',
 | 
				
			||||||
 | 
					        helpArg: '<pkg>',
 | 
				
			||||||
 | 
					        help: 'The package or instance type for the new machine(s). Required.'
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        names: ['name', 'n'],
 | 
				
			||||||
 | 
					        type: 'string',
 | 
				
			||||||
 | 
					        helpArg: '<name>',
 | 
				
			||||||
 | 
					        help: 'A name for the machine. If not specified, a short random name'
 | 
				
			||||||
 | 
					            + ' will be generated.',
 | 
				
			||||||
 | 
					        // TODO: for count>1 support '%d' code in name: foo0, foo1, ...
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        names: ['count', 'c'],
 | 
				
			||||||
 | 
					        type: 'positiveInteger',
 | 
				
			||||||
 | 
					        'default': 1,
 | 
				
			||||||
 | 
					        helpArg: '<n>',
 | 
				
			||||||
 | 
					        help: 'The number of machines to provision. Default is 1.'
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					CLI.prototype.do_provision.help = (
 | 
				
			||||||
 | 
					    'Provision a new virtual machine instance.\n'
 | 
				
			||||||
 | 
					    + 'Alias: create-machine.\n'
 | 
				
			||||||
 | 
					    + '\n'
 | 
				
			||||||
 | 
					    + 'Usage:\n'
 | 
				
			||||||
 | 
					    + '     {{name}} provision <options>\n'
 | 
				
			||||||
 | 
					    + '\n'
 | 
				
			||||||
 | 
					    + '{{options}}'
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					CLI.prototype.do_provision.aliases = ['create-machine'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CLI.prototype.do_machines = function (subcmd, opts, args, callback) {
 | 
					CLI.prototype.do_machines = function (subcmd, opts, args, callback) {
 | 
				
			||||||
    var self = this;
 | 
					    var self = this;
 | 
				
			||||||
    if (opts.help) {
 | 
					    if (opts.help) {
 | 
				
			||||||
@ -211,7 +441,7 @@ CLI.prototype.do_machines = function (subcmd, opts, args, callback) {
 | 
				
			|||||||
            //  'us-west-1  e91897cf  testforyunong2  ubuntu/13.3.0  running       2013-11-08'
 | 
					            //  'us-west-1  e91897cf  testforyunong2  ubuntu/13.3.0  running       2013-11-08'
 | 
				
			||||||
            /* END JSSTYLED */
 | 
					            /* END JSSTYLED */
 | 
				
			||||||
            common.tabulate(machines, {
 | 
					            common.tabulate(machines, {
 | 
				
			||||||
                columns: 'dc,id,name,state,created',
 | 
					                columns: 'dc,id,name,image,state,created',
 | 
				
			||||||
                sort: 'created',
 | 
					                sort: 'created',
 | 
				
			||||||
                validFields: 'dc,id,name,type,state,image,package,memory,'
 | 
					                validFields: 'dc,id,name,type,state,image,package,memory,'
 | 
				
			||||||
                    + 'disk,created,updated,compute_node,primaryIp'
 | 
					                    + 'disk,created,updated,compute_node,primaryIp'
 | 
				
			||||||
@ -249,6 +479,63 @@ CLI.prototype.do_machines.help = (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CLI.prototype.do_machine_audit = function (subcmd, opts, args, callback) {
 | 
				
			||||||
 | 
					    var self = this;
 | 
				
			||||||
 | 
					    if (opts.help) {
 | 
				
			||||||
 | 
					        this.do_help('help', {}, [subcmd], callback);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    } else if (args.length > 1) {
 | 
				
			||||||
 | 
					        //XXX Support multiple machines.
 | 
				
			||||||
 | 
					        return callback(new Error('too many args: ' + args));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var id = args[0];
 | 
				
			||||||
 | 
					    this.sdc.machineAudit({machine: id}, function (err, audit, dc) {
 | 
				
			||||||
 | 
					        if (err) {
 | 
				
			||||||
 | 
					            return callback(err);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (var i = 0; i < audit.length; i++) {
 | 
				
			||||||
 | 
					            audit[i].dc = dc;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (opts.json) {
 | 
				
			||||||
 | 
					            p(JSON.stringify(audit, null, 4));
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            return callback(new error.InternalError("tabular output for audit NYI")); // XXX
 | 
				
			||||||
 | 
					            //common.tabulate(audit, {
 | 
				
			||||||
 | 
					            //    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_machine_audit.options = [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        names: ['help', 'h'],
 | 
				
			||||||
 | 
					        type: 'bool',
 | 
				
			||||||
 | 
					        help: 'Show this help.'
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        names: ['json', 'j'],
 | 
				
			||||||
 | 
					        type: 'bool',
 | 
				
			||||||
 | 
					        help: 'JSON output.'
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					CLI.prototype.do_machine_audit.help = (
 | 
				
			||||||
 | 
					    'List machine actions.\n'
 | 
				
			||||||
 | 
					    + '\n'
 | 
				
			||||||
 | 
					    + 'Note: On the *client*-side, this adds the "dc" attribute to each\n'
 | 
				
			||||||
 | 
					    + 'audit record.\n'
 | 
				
			||||||
 | 
					    + '\n'
 | 
				
			||||||
 | 
					    + 'Usage:\n'
 | 
				
			||||||
 | 
					    + '     {{name}} machine-audit <machine>\n'
 | 
				
			||||||
 | 
					    + '\n'
 | 
				
			||||||
 | 
					    + '{{options}}'
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//---- exports
 | 
					//---- exports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -189,6 +189,43 @@ CloudAPI.prototype.getAccount = function (options, callback) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// ---- machines
 | 
					// ---- machines
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get a machine by id.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * XXX add getCredentials equivalent
 | 
				
			||||||
 | 
					 * XXX cloudapi docs don't doc the credentials=true option
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} options
 | 
				
			||||||
 | 
					 *      - {String} id (required) The machine id.
 | 
				
			||||||
 | 
					 * @param {Function} callback of the form `function (err, machine, response)`
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					CloudAPI.prototype.getMachine = function getMachine(options, callback) {
 | 
				
			||||||
 | 
					    var self = this;
 | 
				
			||||||
 | 
					    assert.object(options, 'options');
 | 
				
			||||||
 | 
					    assert.string(options.id, 'options.id');
 | 
				
			||||||
 | 
					    assert.func(callback, 'callback');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var path = sprintf('/%s/machines/%s', self.user, options.id);
 | 
				
			||||||
 | 
					    self._getAuthHeaders(function (hErr, headers) {
 | 
				
			||||||
 | 
					        if (hErr) {
 | 
				
			||||||
 | 
					            callback(hErr);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        var opts = {
 | 
				
			||||||
 | 
					            path: path,
 | 
				
			||||||
 | 
					            headers: headers
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        self.client.get(opts, function (err, req, res, body) {
 | 
				
			||||||
 | 
					            if (err) {
 | 
				
			||||||
 | 
					                callback(err, null, res);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                callback(null, body, res);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * List the user's machines.
 | 
					 * List the user's machines.
 | 
				
			||||||
 * <http://apidocs.joyent.com/cloudapi/#ListMachines>
 | 
					 * <http://apidocs.joyent.com/cloudapi/#ListMachines>
 | 
				
			||||||
@ -206,7 +243,7 @@ CloudAPI.prototype.getAccount = function (options, callback) {
 | 
				
			|||||||
 *      the machines. ListMachines has a max number of machines, so can require
 | 
					 *      the machines. ListMachines has a max number of machines, so can require
 | 
				
			||||||
 *      multiple requests to list all of them.
 | 
					 *      multiple requests to list all of them.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
CloudAPI.prototype.listMachines = function (options, callback) {
 | 
					CloudAPI.prototype.listMachines = function listMachines(options, callback) {
 | 
				
			||||||
    var self = this;
 | 
					    var self = this;
 | 
				
			||||||
    if (callback === undefined) {
 | 
					    if (callback === undefined) {
 | 
				
			||||||
        callback = options;
 | 
					        callback = options;
 | 
				
			||||||
@ -272,6 +309,44 @@ CloudAPI.prototype.listMachines = function (options, callback) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * List machine audit (successful actions on the machine).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * XXX IMO this endpoint should be called ListMachineAudit in cloudapi.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} options
 | 
				
			||||||
 | 
					 *      - {String} id (required) The machine id.
 | 
				
			||||||
 | 
					 * @param {Function} callback of the form `function (err, audit, response)`
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					CloudAPI.prototype.machineAudit = function machineAudit(options, callback) {
 | 
				
			||||||
 | 
					    var self = this;
 | 
				
			||||||
 | 
					    assert.object(options, 'options');
 | 
				
			||||||
 | 
					    assert.string(options.id, 'options.id');
 | 
				
			||||||
 | 
					    assert.func(callback, 'callback');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var path = sprintf('/%s/machines/%s/audit', self.user, options.id);
 | 
				
			||||||
 | 
					    //XXX This `client.get` block is duplicated. Add a convenience function for it:
 | 
				
			||||||
 | 
					    self._getAuthHeaders(function (hErr, headers) {
 | 
				
			||||||
 | 
					        if (hErr) {
 | 
				
			||||||
 | 
					            callback(hErr);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        var opts = {
 | 
				
			||||||
 | 
					            path: path,
 | 
				
			||||||
 | 
					            headers: headers
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        self.client.get(opts, function (err, req, res, body) {
 | 
				
			||||||
 | 
					            if (err) {
 | 
				
			||||||
 | 
					                callback(err, null, res);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                callback(null, body, res);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// --- Exports
 | 
					// --- Exports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
 | 
				
			|||||||
@ -113,7 +113,7 @@ function tabulate(items, options) {
 | 
				
			|||||||
    // Function to lookup each column field in a row.
 | 
					    // Function to lookup each column field in a row.
 | 
				
			||||||
    var colFuncs = columns.map(function (lookup) {
 | 
					    var colFuncs = columns.map(function (lookup) {
 | 
				
			||||||
        return new Function(
 | 
					        return new Function(
 | 
				
			||||||
            'try { return (this.' + lookup + '); } catch (e) {}');
 | 
					            'try { return (this["' + lookup + '"]); } catch (e) {}');
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Determine columns and widths.
 | 
					    // Determine columns and widths.
 | 
				
			||||||
 | 
				
			|||||||
@ -10,18 +10,52 @@ var path = require('path');
 | 
				
			|||||||
var sprintf = require('extsprintf').sprintf;
 | 
					var sprintf = require('extsprintf').sprintf;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var common = require('./common');
 | 
					var common = require('./common');
 | 
				
			||||||
 | 
					var errors = require('./errors');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var CONFIG_PATH = path.resolve(process.env.HOME, '.sdcconfig.json');
 | 
					var CONFIG_PATH = path.resolve(process.env.HOME, '.sdcconfig.json');
 | 
				
			||||||
var DEFAULTS_PATH = path.resolve(__dirname, '..', 'etc', 'defaults.json');
 | 
					var DEFAULTS_PATH = path.resolve(__dirname, '..', 'etc', 'defaults.json');
 | 
				
			||||||
 | 
					var OVERRIDE_KEYS = ['dc', 'dcAlias'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Load the 'sdc' config. This is a merge of the built-in "defaults" (at
 | 
				
			||||||
 | 
					 * etc/defaults.json) and the "user" config (at ~/.sdcconfig.json if it
 | 
				
			||||||
 | 
					 * exists).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This includes some internal data on keys with a leading underscore.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
function loadConfigSync() {
 | 
					function loadConfigSync() {
 | 
				
			||||||
    var config = JSON.parse(fs.readFileSync(DEFAULTS_PATH, 'utf8'));
 | 
					    var c = fs.readFileSync(DEFAULTS_PATH, 'utf8');
 | 
				
			||||||
 | 
					    var _defaults = JSON.parse(c);
 | 
				
			||||||
 | 
					    var config = JSON.parse(c);
 | 
				
			||||||
    if (fs.existsSync(CONFIG_PATH)) {
 | 
					    if (fs.existsSync(CONFIG_PATH)) {
 | 
				
			||||||
        var userConfig = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
 | 
					        c = fs.readFileSync(CONFIG_PATH, 'utf8');
 | 
				
			||||||
        common.objCopy(userConfig, config);
 | 
					        var _user = JSON.parse(c);
 | 
				
			||||||
 | 
					        var userConfig = JSON.parse(c);
 | 
				
			||||||
 | 
					        if (typeof(userConfig) !== 'object' || Array.isArray(userConfig)) {
 | 
				
			||||||
 | 
					            throw new errors.ConfigError(
 | 
				
			||||||
 | 
					                sprintf('"%s" is not an object', CONFIG_PATH));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        // These special keys are merged into the key of the same name in the
 | 
				
			||||||
 | 
					        // base "defaults.json".
 | 
				
			||||||
 | 
					        Object.keys(userConfig).forEach(function (key) {
 | 
				
			||||||
 | 
					            if (~OVERRIDE_KEYS.indexOf(key) && config[key] !== undefined) {
 | 
				
			||||||
 | 
					                Object.keys(userConfig[key]).forEach(function (subKey) {
 | 
				
			||||||
 | 
					                    if (userConfig[key][subKey] === null) {
 | 
				
			||||||
 | 
					                        delete config[key][subKey];
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        config[key][subKey] = userConfig[key][subKey];
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                config[key] = userConfig[key];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        config._user = _user;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    config._defaults = _defaults;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Add 'env' profile.
 | 
					    // Add 'env' profile.
 | 
				
			||||||
    if (!config.profiles) {
 | 
					    if (!config.profiles) {
 | 
				
			||||||
@ -29,6 +63,7 @@ function loadConfigSync() {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    config.profiles.push({
 | 
					    config.profiles.push({
 | 
				
			||||||
        name: 'env',
 | 
					        name: 'env',
 | 
				
			||||||
 | 
					        dcs: ['joyent'],
 | 
				
			||||||
        user: process.env.SDC_USER || process.env.SDC_ACCOUNT,
 | 
					        user: process.env.SDC_USER || process.env.SDC_ACCOUNT,
 | 
				
			||||||
        keyId: process.env.SDC_KEY_ID,
 | 
					        keyId: process.env.SDC_KEY_ID,
 | 
				
			||||||
        rejectUnauthorized: common.boolFromString(
 | 
					        rejectUnauthorized: common.boolFromString(
 | 
				
			||||||
@ -39,11 +74,24 @@ function loadConfigSync() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Apply the given key:value updates to the user config and save it out.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param config {Object} The loaded config, as from `loadConfigSync`.
 | 
				
			||||||
 | 
					 * @param updates {Object} key/value pairs to update.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function updateUserConfigSync(config, updates) {
 | 
				
			||||||
 | 
					    XXX
 | 
				
			||||||
 | 
					    ///XXX START HERE: to implement for 'sdc dcs add foo bar'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//---- exports
 | 
					//---- exports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
    CONFIG_PATH: CONFIG_PATH,
 | 
					    CONFIG_PATH: CONFIG_PATH,
 | 
				
			||||||
    loadConfigSync: loadConfigSync
 | 
					    loadConfigSync: loadConfigSync,
 | 
				
			||||||
 | 
					    //XXX
 | 
				
			||||||
 | 
					    //updateConfigSync: updateConfigSync
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
// vim: set softtabstop=4 shiftwidth=4:
 | 
					// vim: set softtabstop=4 shiftwidth=4:
 | 
				
			||||||
 | 
				
			|||||||
@ -56,6 +56,25 @@ function InternalError(cause, message) {
 | 
				
			|||||||
util.inherits(InternalError, SDCError);
 | 
					util.inherits(InternalError, SDCError);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * CLI usage error
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function ConfigError(cause, message) {
 | 
				
			||||||
 | 
					    if (message === undefined) {
 | 
				
			||||||
 | 
					        message = cause;
 | 
				
			||||||
 | 
					        cause = undefined;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    assert.string(message);
 | 
				
			||||||
 | 
					    SDCError.call(this, {
 | 
				
			||||||
 | 
					        cause: cause,
 | 
				
			||||||
 | 
					        message: message,
 | 
				
			||||||
 | 
					        code: 'Config',
 | 
				
			||||||
 | 
					        exitStatus: 1
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					util.inherits(ConfigError, SDCError);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * CLI usage error
 | 
					 * CLI usage error
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@ -116,6 +135,7 @@ util.inherits(MultiError, SDCError);
 | 
				
			|||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
    SDCError: SDCError,
 | 
					    SDCError: SDCError,
 | 
				
			||||||
    InternalError: InternalError,
 | 
					    InternalError: InternalError,
 | 
				
			||||||
 | 
					    ConfigError: ConfigError,
 | 
				
			||||||
    UsageError: UsageError,
 | 
					    UsageError: UsageError,
 | 
				
			||||||
    SigningError: SigningError,
 | 
					    SigningError: SigningError,
 | 
				
			||||||
    MultiError: MultiError
 | 
					    MultiError: MultiError
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										123
									
								
								lib/sdc.js
									
									
									
									
									
								
							
							
						
						
									
										123
									
								
								lib/sdc.js
									
									
									
									
									
								
							@ -9,13 +9,15 @@ var assert = require('assert-plus');
 | 
				
			|||||||
var async = require('async');
 | 
					var async = require('async');
 | 
				
			||||||
var auth = require('smartdc-auth');
 | 
					var auth = require('smartdc-auth');
 | 
				
			||||||
var EventEmitter = require('events').EventEmitter;
 | 
					var EventEmitter = require('events').EventEmitter;
 | 
				
			||||||
var format = require('util').format;
 | 
					 | 
				
			||||||
var fs = require('fs');
 | 
					var fs = require('fs');
 | 
				
			||||||
 | 
					var once = require('once');
 | 
				
			||||||
var path = require('path');
 | 
					var path = require('path');
 | 
				
			||||||
var restify = require('restify');
 | 
					var restify = require('restify');
 | 
				
			||||||
 | 
					var sprintf = require('util').format;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var cloudapi = require('./cloudapi2');
 | 
					var cloudapi = require('./cloudapi2');
 | 
				
			||||||
var common = require('./common');
 | 
					var common = require('./common');
 | 
				
			||||||
 | 
					var errors = require('./errors');
 | 
				
			||||||
var loadConfigSync = require('./config').loadConfigSync;
 | 
					var loadConfigSync = require('./config').loadConfigSync;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -163,6 +165,91 @@ SDC.prototype._clientFromDc = function _clientFromDc(dc) {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Return the resolved array of `{name: <dc-name>, url: <dc-url>}` for all
 | 
				
			||||||
 | 
					 * DCs for the current profile.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @throws {Error} If an unknown DC name is encountered.
 | 
				
			||||||
 | 
					 *  XXX make that UnknownDcError.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					SDC.prototype.dcs = function dcs() {
 | 
				
			||||||
 | 
					    var self = this;
 | 
				
			||||||
 | 
					    var aliases = self.config.dcAlias || {};
 | 
				
			||||||
 | 
					    var resolved = [];
 | 
				
			||||||
 | 
					    (self.profile.dcs || Object.keys(self.config.dcs)).forEach(function (n) {
 | 
				
			||||||
 | 
					        var names = aliases[n] || [n];
 | 
				
			||||||
 | 
					        names.forEach(function (name) {
 | 
				
			||||||
 | 
					            if (!self.config.dcs[name]) {
 | 
				
			||||||
 | 
					                throw new Error(sprintf('unknown dc "%s" for "%s" profile',
 | 
				
			||||||
 | 
					                    name, self.profile.name));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            resolved.push({
 | 
				
			||||||
 | 
					                name: name,
 | 
				
			||||||
 | 
					                url: self.config.dcs[name]
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return resolved;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Find a machine in the set of DCs for the current profile.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} options
 | 
				
			||||||
 | 
					 *      - {String} machine (required) The machine id.
 | 
				
			||||||
 | 
					 *        XXX support name matching, prefix, etc.
 | 
				
			||||||
 | 
					 * @param {Function} callback  `function (err, machine, dc)`
 | 
				
			||||||
 | 
					 *      Returns the machine object (as from cloudapi GetMachine) and the `dc`,
 | 
				
			||||||
 | 
					 *      e.g. "us-west-1".
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					SDC.prototype.findMachine = function findMachine(options, callback) {
 | 
				
			||||||
 | 
					    //XXX Eventually this can be cached for a *full* uuid. Arguably for a
 | 
				
			||||||
 | 
					    //  uuid prefix or machine alias match, it cannot be cached, because an
 | 
				
			||||||
 | 
					    //  ambiguous machine could have been added.
 | 
				
			||||||
 | 
					    var self = this;
 | 
				
			||||||
 | 
					    assert.object(options, 'options');
 | 
				
			||||||
 | 
					    assert.string(options.machine, 'options.machine');
 | 
				
			||||||
 | 
					    assert.func(callback, 'callback');
 | 
				
			||||||
 | 
					    var callback = once(callback);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var errs = [];
 | 
				
			||||||
 | 
					    var foundMachine;
 | 
				
			||||||
 | 
					    var foundDc;
 | 
				
			||||||
 | 
					    async.each(
 | 
				
			||||||
 | 
					        self.dcs(),
 | 
				
			||||||
 | 
					        function oneDc(dc, next) {
 | 
				
			||||||
 | 
					            var client = self._clientFromDc(dc.name);
 | 
				
			||||||
 | 
					            client.getMachine({id: options.machine}, function (err, machine) {
 | 
				
			||||||
 | 
					                if (err) {
 | 
				
			||||||
 | 
					                    errs.push(err);
 | 
				
			||||||
 | 
					                } else if (machine) {
 | 
				
			||||||
 | 
					                    foundMachine = machine;
 | 
				
			||||||
 | 
					                    foundDc = dc.name;
 | 
				
			||||||
 | 
					                    // Return early on an unambiguous match.
 | 
				
			||||||
 | 
					                    // XXX When other than full 'id' is supported, this isn't unambiguous.
 | 
				
			||||||
 | 
					                    callback(null, foundMachine, foundDc);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                next();
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        function done(surpriseErr) {
 | 
				
			||||||
 | 
					            if (surpriseErr) {
 | 
				
			||||||
 | 
					                callback(surpriseErr);
 | 
				
			||||||
 | 
					            } else if (foundMachine) {
 | 
				
			||||||
 | 
					                callback(null, foundMachine, foundDc)
 | 
				
			||||||
 | 
					            } else if (errs.length) {
 | 
				
			||||||
 | 
					                callback(errs.length === 1 ?
 | 
				
			||||||
 | 
					                    errs[0] : new errors.MultiError(errs));
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                callback(new errors.InternalError(
 | 
				
			||||||
 | 
					                    'unexpected error finding machine ' + options.id));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * List machines for the current profile.
 | 
					 * List machines for the current profile.
 | 
				
			||||||
@ -188,16 +275,15 @@ SDC.prototype.listMachines = function listMachines(options) {
 | 
				
			|||||||
    assert.object(options, 'options');
 | 
					    assert.object(options, 'options');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var emitter = new EventEmitter();
 | 
					    var emitter = new EventEmitter();
 | 
				
			||||||
 | 
					 | 
				
			||||||
    async.each(
 | 
					    async.each(
 | 
				
			||||||
        self.profile.dcs || Object.keys(self.config.dcs),
 | 
					        self.dcs(),
 | 
				
			||||||
        function oneDc(dc, next) {
 | 
					        function oneDc(dc, next) {
 | 
				
			||||||
            var client = self._clientFromDc(dc);
 | 
					            var client = self._clientFromDc(dc.name);
 | 
				
			||||||
            client.listMachines(function (err, machines) {
 | 
					            client.listMachines(function (err, machines) {
 | 
				
			||||||
                if (err) {
 | 
					                if (err) {
 | 
				
			||||||
                    emitter.emit('dcError', dc, err);
 | 
					                    emitter.emit('dcError', dc.name, err);
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    emitter.emit('data', dc, machines);
 | 
					                    emitter.emit('data', dc.name, machines);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                next();
 | 
					                next();
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
@ -210,6 +296,31 @@ SDC.prototype.listMachines = function listMachines(options) {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Return the audit for the given machine.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} options
 | 
				
			||||||
 | 
					 *      - {String} machine (required) The machine id.
 | 
				
			||||||
 | 
					 *        XXX support `machine` being more than just the UUID.
 | 
				
			||||||
 | 
					 * @param {Function} callback of the form `function (err, audit, dc)`
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					SDC.prototype.machineAudit = function machineAudit(options, callback) {
 | 
				
			||||||
 | 
					    var self = this;
 | 
				
			||||||
 | 
					    assert.object(options, 'options');
 | 
				
			||||||
 | 
					    assert.string(options.machine, 'options.machine');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.findMachine({machine: options.machine}, function (err, machine, dc) {
 | 
				
			||||||
 | 
					        if (err) {
 | 
				
			||||||
 | 
					            return callback(err);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        var client = self._clientFromDc(dc);
 | 
				
			||||||
 | 
					        client.machineAudit({id: machine.id}, function (err, audit) {
 | 
				
			||||||
 | 
					            callback(err, audit, dc);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//---- exports
 | 
					//---- exports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user