Skip to content

Commit

Permalink
- Automatically detect host and guest OS
Browse files Browse the repository at this point in the history
- Added Windows host support (Fixes #4)
- Added more VM power states (Fixes #5) and related tests
- vmExec() adapts to guest OS (Fixes #6)
- Check for reset/savestate VM before throwing error (Fixes #7)
- jshint and strict mode (Fixes #8)
  • Loading branch information
michaelsanford committed Apr 13, 2014
1 parent c3f7239 commit 2bf68fc
Show file tree
Hide file tree
Showing 13 changed files with 305 additions and 62 deletions.
70 changes: 70 additions & 0 deletions .jshintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
// Settings
"passfail" : false, // Stop on first error.
"maxerr" : 100, // Maximum errors before stopping.


// Predefined globals whom JSHint will ignore.
"browser" : true, // Standard browser globals e.g. `window`, `document`.

"node" : true,
"rhino" : false,
"couch" : false,
"wsh" : false, // Windows Scripting Host.

"jquery" : false,
"prototypejs" : false,
"mootools" : false,
"dojo" : false,

"predef" : [ // Extra globals.
"require",
"define",
"notify"
],


// Development.
"debug" : false, // Allow debugger statements e.g. browser breakpoints.
"devel" : false, // Allow development statements e.g. `console.log();`.


// EcmaScript 5.
"strict" : true, // Require `use strict` pragma in every file.
"globalstrict" : false, // Allow global "use strict" (also enables 'strict').


// The Good Parts.
"asi" : false, // Tolerate Automatic Semicolon Insertion (no semicolons).
"laxbreak" : false, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons.
"bitwise" : true, // Prohibit bitwise operators (&, |, ^, etc.).
"boss" : false, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments.
"curly" : true, // Require {} for every new block or scope.
"eqeqeq" : false, // Require triple equals i.e. `===`.
"eqnull" : false, // Tolerate use of `== null`.
"evil" : true, // Tolerate use of `eval`.
"expr" : false, // Tolerate `ExpressionStatement` as Programs.
"forin" : false, // Tolerate `for in` loops without `hasOwnPrototype`.
"immed" : true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );`
"latedef" : false, // Prohibit variable use before definition.
"loopfunc" : true, // Allow functions to be defined within loops.
"noarg" : true, // Prohibit use of `arguments.caller` and `arguments.callee`.
"regexp" : true, // Prohibit `.` and `[^...]` in regular expressions.
"regexdash" : false, // Tolerate unescaped last dash i.e. `[-...]`.
"scripturl" : true, // Tolerate script-targeted URLs.
"shadow" : false, // Allows re-define variables later in code e.g. `var x=1; x=2;`.
"supernew" : true, // Tolerate `new function () { ... };` and `new Object;`.
"undef" : true, // Require all non-global variables be declared before they are used.


// Styling prefrences.
"newcap" : false, // Require capitalization of all constructor functions e.g. `new F()`.
"noempty" : false, // Prohibit use of empty blocks.
"nonew" : false, // Prohibit use of constructors for side-effects.
"nomen" : false, // Prohibit use of initial or trailing underbars in names.
"onevar" : false, // Allow only one `var` statement per function.
"plusplus" : false, // Prohibit use of `++` & `--`.
"sub" : true, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`.
"trailing" : false, // Prohibit trailing whitespaces.
"white" : false // Check against strict whitespace and indentation rules.
}
194 changes: 143 additions & 51 deletions lib/virtualbox.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,67 @@
"use strict";

var exec = require('child_process').exec,
logging = require('./logging'),
undefined = function(){}();
platform = require('os').platform,
logging = require('./logging');
// undefined = function (){}();

var vBoxManageBinary;

// Host operating system
if (/^win/.test(platform)) {

// Path may not contain VBoxManage.exe but it provides this environment variable
vBoxManageBinary = '"' + process.env.VBOX_INSTALL_PATH + '\\VBoxManage.exe' + '" ';

} else if (/^darwin/.test(platform) || /^linux/.test(platform)) {

var isLinux = (process.platform == 'linux' || process.platform == 'darwin');
// Mac OS X and most Linux use the same binary name, in the path
vBoxManageBinary = 'vboxmanage ';

} else {

// Otherwise (e.g., SunOS) hope it's in the path
vBoxManageBinary = 'vboxmanage ';

}

function command(cmd, callback) {
exec(cmd, function(err, stdout, stderr){
!err && stderr && ( err = new Error(stderr) );
exec(cmd, function (err, stdout, stderr) {

callback(err, stdout);
if (!err && stderr && cmd.indexOf("pause") !== -1 && cmd.indexOf("savestate") !== -1) {
err = new Error(stderr);
}

callback(err, stdout);
});
}

function vboxcontrol(cmd, callback) {
command('VBoxControl '+cmd, callback);
command('VBoxControl ' + cmd, callback);
}

function vboxmanage(cmd, callback){
command('vboxmanage '+cmd, callback);
function vboxmanage(cmd, callback) {
command(vBoxManageBinary + cmd, callback);
}

function pause(vmname, callback){
function pause(vmname, callback) {
logging.info('Pausing VM "%s"', vmname);
vboxmanage('controlvm "'+vmname+'" pause', function(error, stdout){
vboxmanage('controlvm "' + vmname + '" pause', function (error, stdout) {
callback(error);
});
}

function list(callback){
function list(callback) {
logging.info('Listing VMs');
vboxmanage('list "runningvms"', function(error, stdout){
var _list = new Object;
vboxmanage('list "runningvms"', function (error, stdout) {
var _list = {};
var _runningvms = parse_listdata(stdout);
vboxmanage('list "vms"', function(error, full_stdout){
vboxmanage('list "vms"', function (error, full_stdout) {
var _all = parse_listdata(full_stdout);
var _keys = Object.keys(_all);
for(var _i=0; _i < _keys.length; _i += 1){
for (var _i = 0; _i < _keys.length; _i += 1) {
var _key = _keys[_i];
if(_runningvms[_key]) {
if (_runningvms[_key]) {
_all[_key].running = true;
} else {
_all[_key].running = false;
Expand All @@ -50,90 +73,127 @@ function list(callback){
}

function parse_listdata(raw_data) {
var _raw = raw_data.split('\n');
var _data = new Object;
var _raw = raw_data.split('\n');
var _data = {};
if (_raw.length > 0) {
for(var _i=0; _i < _raw.length; _i += 1){
for (var _i = 0; _i < _raw.length; _i += 1) {
var _line = _raw[_i];
if (_line === '') {
continue;
}
// "centos6" {64ec13bb-5889-4352-aee9-0f1c2a17923d}
var rePattern = new RegExp(/^\"(.+)\" \{(.+)\}$/);
var rePattern = new RegExp(/^\"(.+)\" \{(.+)\}$/);
var arrMatches = _line.match(rePattern);
// {'64ec13bb-5889-4352-aee9-0f1c2a17923d': 'centos6'}
if(arrMatches.length === 3) {
_data[arrMatches[2].toString()] = {name : arrMatches[1].toString()};
if (arrMatches.length === 3) {
_data[arrMatches[2].toString()] = {name: arrMatches[1].toString()};
}
}
}
return _data;
}

function reset(vmname, callback){
function reset(vmname, callback) {
logging.info('Resetting VM "%s"', vmname);
vboxmanage('controlvm "'+vmname+'" reset', function(error, stdout){
vboxmanage('controlvm "' + vmname + '" reset', function (error, stdout) {
callback(error);
});
}

function resume(vmname, callback){
function resume(vmname, callback) {
logging.info('Resuming VM "%s"', vmname);
vboxmanage('controlvm "'+vmname+'" resume', function(error, stdout){
vboxmanage('controlvm "' + vmname + '" resume', function (error, stdout) {
callback(error);
});
}

function start(vmname, callback){
function start(vmname, callback) {
logging.info('Starting VM "%s"', vmname);
vboxmanage('-nologo startvm "'+vmname+'" --type headless', function(error, stdout){
vboxmanage('-nologo startvm "' + vmname + '" --type headless', function (error, stdout) {

if(error && /VBOX_E_INVALID_OBJECT_STATE/.test(error.message)){
if (error && /VBOX_E_INVALID_OBJECT_STATE/.test(error.message)) {
error = undefined;
}

callback(error);
});
}

function stop(vmname, callback){
function stop(vmname, callback) {
logging.info('Stopping VM "%s"', vmname);
vboxmanage('controlvm "'+vmname+'" savestate', function(error, stdout){
vboxmanage('controlvm "' + vmname + '" savestate', function (error, stdout) {
callback(error);
});
}

function vmExec(options, callback){
var vm = options.vm || options.name || options.vmname || options.title,
username = options.user || options.username || 'Guest',
password = options.pass || options.passwd || options.password,
path = options.path || options.cmd || options.command || options.exec || options.execute || options.run,
params = options.params || options.parameters || options.args;
function savestate(vmname, callback) {
logging.info('Saving State (alias to stop) VM "%s"', vmname);
stop(vmname, callback);
}

function poweroff(vmname, callback) {
logging.info('Powering off VM "%s"', vmname);
vboxmanage('controlvm "' + vmname + '" poweroff', function (error, stdout) {
callback(error);
});
}

function acpipowerbutton(vmname, callback) {
logging.info('ACPI power button VM "%s"', vmname);
vboxmanage('controlvm "' + vmname + '" acpipowerbutton', function (error, stdout) {
callback(error);
});
}

Array.isArray(params) && ( params = params.join(" ") );
function acpisleepbutton(vmname, callback) {
logging.info('ACPI sleep button VM "%s"', vmname);
vboxmanage('controlvm "' + vmname + '" acpisleepbutton', function (error, stdout) {
callback(error);
});
}

params == undefined && ( params = "" );
function vmExec(options, callback) {
var vm = options.vm || options.name || options.vmname || options.title,
username = options.user || options.username || 'Guest',
password = options.pass || options.passwd || options.password,
path = options.path || options.cmd || options.command || options.exec || options.execute || options.run,
ostype = guestproperty.os(vm),
cmd,
params = options.params || options.parameters || options.args;

if (Array.isArray(params)) {
params = params.join(" ");
}

path = path.replace(/\\/g, '\\\\');
if (params === undefined) {
params = "";
}

var cmd = 'guestcontrol '+vm+' execute --image "cmd.exe" --username ' + username + ( password ? ' --password ' + password : '' ) + ' -- "/c" "'+path+'" "'+params+'"';
if (ostype === "windows") {
path = path.replace(/\\/g, '\\\\');
cmd = 'guestcontrol ' + vm + ' execute --image "cmd.exe" --username ' + username + ( password ? ' --password ' + password : '' ) + ' -- "/c" "' + path + '" "' + params + '"';
} else if (ostype === "mac") {
cmd = 'guestcontrol ' + vm + ' execute --image "/usr/bin/open -a" --username ' + username + ( password ? ' --password ' + password : '' ) + ' -- "/c" "' + path + '" "' + params + '"';
} else {
cmd = 'guestcontrol ' + vm + ' execute --image "/bin/sh" --username ' + username + ( password ? ' --password ' + password : '' ) + ' -- "/c" "' + path + '" "' + params + '"';
}

logging.info('Executing command "vboxmanage %s" on VM "%s"', cmd, vm);
logging.info('Executing command "vboxmanage %s" on VM "%s" detected OS type "%s"', cmd, vm, ostype);

vboxmanage(cmd, function(error, stdout){
vboxmanage(cmd, function (error, stdout) {
callback(error);
});
}

var guestproperty = {
get: function(options, callback){
get: function (options, callback) {
var vm = options.vm || options.name || options.vmname || options.title,
key = options.key,
value = options.defaultValue || options.value;
key = options.key,
value = options.defaultValue || options.value;

if(isLinux){
vboxcontrol('guestproperty get '+key, function(error, stdout){
if(error){
if (this.os(vm) === "linux") {
vboxcontrol('guestproperty get ' + key, function (error, stdout) {
if (error) {
//returning default value
callback(value);
return;
Expand All @@ -147,7 +207,35 @@ var guestproperty = {
callback(value);
}
},
}

os: function (vmname, callback) {
var ostype = null;

try {
exec(vBoxManageBinary + 'showvminfo -machinereadable "' + vmname + '"', function (error, stdout, stderr) {
if (!error) {

// The ostype is matched against the ID attribute of 'vboxmanage list ostypes'
if (stdout.indexOf('ostype="Windows') !== -1) {
ostype = "windows";
} else if (stdout.indexOf('ostype="MacOS') !== -1) {
ostype = "mac";
} else {
ostype = "linux";
}
} else {
ostype = false;

}
});

callback(ostype);

} catch (e) {
logging.info('Could not showvminfo for %s', vmname);
}
}
};

module.exports = {
'exec': vmExec,
Expand All @@ -157,5 +245,9 @@ module.exports = {
'resume': resume,
'start': start,
'stop': stop,
'savestate': savestate,
'poweroff': poweroff,
'acpisleepbutton': acpisleepbutton,
'acpipowerbutton': acpipowerbutton,
'guestproperty': guestproperty
};
10 changes: 10 additions & 0 deletions test/acpipowerbutton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"use strict";

var virtualbox = require('../lib/virtualbox'),
args = process.argv.slice(2);

virtualbox.acpipowerbutton(args[0], function(error){
if(error) {
throw error;
}
});
10 changes: 10 additions & 0 deletions test/acpisleepbutton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"use strict";

var virtualbox = require('../lib/virtualbox'),
args = process.argv.slice(2);

virtualbox.acpisleepbutton(args[0], function(error){
if(error) {
throw error;
}
});
Loading

0 comments on commit 2bf68fc

Please sign in to comment.