node-docker
Version:
Node wrapper around the docker REST API - NOTE: alpha status
593 lines (479 loc) • 17.1 kB
JavaScript
(function(exports) {
// node-docker.js
//------------------------------
//
// 2013-10-29, Jonas Colmsjö
//
// Wrapper around the Docker.io REST API
//
// Using Google JavaScript Style Guide - http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml
//
//------------------------------
//
// In browsers (not a likely scenario):
//---------------
// <script type="text/javascript" src="https://raw.github.com/colmsjo/node-docker/master/node-docker.js"></script>
// ...
// var h = node-docker.create();
// h.test("Testing to use the plates library.", function(){
// ...
//
// In Node:
//---------------
// var node_docker = require('node-docker').create();
/*
// Includes
// ================
var helpers = require('helpersjs').create();
var fs = require('fs');
var http = require('http');
var async = require('async');
var nconf = require('nconf');
var prettyjson = require('prettyjson');
// Some general setup
// ================
// TODO: Should remove logging from module
var hostname,
port;
nconf.use('file', { file: __dirname + '/jacc_config.json' });
nconf.load();
this.hostname = nconf.get('hostname');
this.port = nconf.get('port');
// set logging level
switch(nconf.get('logging')) {
case 'debug':
helpers.logging_threshold = helpers.logging.debug;
break;
case 'warning':
helpers.logging_threshold = helpers.logging.warning;
break;
default:
console.log('error: incorrect logging level in config.json - should be warning or debug!');
}
helpers.logDebug('setup: hostname: ' + this.hostname + ' port: ' + this.port);
*/
"use strict";
exports.create = function() {
return {
// Properties
//==============
_imageID : "",
_containerID : "",
_name : "",
_containerPort : null, //previously undefined
_use_export : false,
_settings : {},
_helpers : require('helpersjs').create(),
// helpers
//======================================================================
_isset : function(a, message, dontexit){
if (!this._helpers.isset(a)) {
console.log(message);
if(dontexit !== undefined && dontexit) {
return false;
} else {
return false;
// No good - process.exit();
}
}
this._helpers.logDebug('_isset: returning true ');
return true;
},
// Docker functions
//======================================================================
_dockerRemoteAPI : function(options, funcResData, funcResEnd, funcReq, asyncCallback){
var http = require('http');
if(options.hostname === undefined) {
options.hostname = "localhost";
}
if(options.port === undefined) {
options.port = 4243;
}
var req = http.request(options, function(res) {
res.setEncoding('utf8');
res.on('data', funcResData.bind(this));
if(funcResEnd !== null) {
res.on('end', funcResEnd.bind(this));
} else {
res.on('end', function () {
if(asyncCallback !== undefined) {
asyncCallback(null, '_dockerRemoteAPI:');
}
}.bind(this));
}
}.bind(this));
req.on('error', function(e) {
if(asyncCallback !== undefined) {
asyncCallback(null, '_dockerRemoteAPI:error');
}
console.log("ERROR: Problem doing docker call!");
}.bind());
req.on('end', function(e) {
// Doing nothing right now
if(asyncCallback !== undefined) {
asyncCallback(null, '_dockerRemoteAPI:end');
}
}.bind(this));
if(funcReq !== null) {
funcReq(req);
} else {
req.end();
}
},
// import
//-------------------------------------------------------------------------------------------------
//
// Equivalent of: curl -H "Content-type: application/tar" --data-binary @webapp.tar http://localhost:4243/build
//
import : function(asyncCallback){
var options = {
path: '/images/create?fromSrc=-',
method: 'POST',
headers: {
'Content-Type': 'application/tar',
}
};
var _imageID;
this._dockerRemoteAPI(options,
function(chunk) {
_imageID = JSON.parse(chunk).status;
}.bind(this),
function() {
this._isset(_imageID, 'Import failed! No image was created.');
if(asyncCallback !== undefined) {
asyncCallback(null, 'image:'+_imageID);
}
}.bind(this),
function(req) {
// write data to the http.ClientRequest (which is a stream) returned by http.request()
var fs = require('fs');
var stream;
try {
stream = fs.createReadStream('webapp.export.tar');
} catch (e) {
console.log('ERROR: Could not open webapp.export.tar!');
return;
}
// Close the request when the stream is closed
stream.on('end', function() {
req.end();
}.bind(this));
// send the data
stream.pipe(req);
}.bind(this),
asyncCallback);
},
// build
//-------------------------------------------------------------------------------------------------
//
// Equivalent of: curl -H "Content-type: application/tar" --data-binary @webapp.tar http://localhost:4243/build
//
build : function(asyncCallback){
var options = {
path: '/build?nocache',
method: 'POST',
headers: {
'Content-Type': 'application/tar',
}
};
var _imageID;
this._dockerRemoteAPI(options,
function(chunk) {
console.log('build: ' + chunk);
// The last row looks like this 'Successfully built 3df239699c83'
if (chunk.slice(0,18) === 'Successfully built') {
_imageID = chunk.slice(19,31);
}
}.bind(this),
function() {
this._isset(_imageID, 'Build failed! No image was created.');
if(asyncCallback !== undefined) {
asyncCallback(null, 'image:'+_imageID);
}
}.bind(this),
function(req) {
// write data to the http.ClientRequest (which is a stream) returned by http.request()
var fs = require('fs');
var stream;
try {
stream = fs.createReadStream('webapp.tar');
} catch (e) {
console.log('ERROR: Could not open webapp.tar!');
return;
}
// Close the request when the stream is closed
stream.on('end', function() {
req.end();
}.bind(this));
// send the data
stream.pipe(req);
}.bind(this),
asyncCallback);
return _imageID;
},
// createContainer
//-------------------------------------------------------------------------------------------------
//
// create a container with the new image
// curl -H "Content-Type: application/json" -d @create.json http://localhost:4243/containers/create
// {"Id":"c6bfd6da99d3"}
createContainer : function(asyncCallback, imageID){
this._isset(imageID, 'createContainer: imageID not set');
var container = {
"Hostname":"",
"User":"",
"Memory":0,
"MemorySwap":0,
"AttachStdin":false,
"AttachStdout":true,
"AttachStderr":true,
"PortSpecs":null,
"Tty":false,
"OpenStdin":false,
"StdinOnce":false,
"Env":null,
"Dns":null,
"Image":imageID,
"Volumes":{},
"VolumesFrom":""
};
var options = {
path: '/containers/create',
method: 'POST'
};
this._dockerRemoteAPI(options,
function(chunk) {
// The result should look like this '{"Id":"c6bfd6da99d3"}'
try {
this._containerID = JSON.parse(chunk).Id;
} catch (e) {
console.log('Create container failed: '+chunk);
asyncCallback();
process.exit();
}
}.bind(this),
console.log, //null,
function(req) {
req.write(JSON.stringify(container));
req.end();
}.bind(this),
asyncCallback
);
},
// start
//-------------------------------------------------------------------------------------------------
//
// Equivalent of: curl -H "Content-Type: application/json" -d @start.json http://localhost:4243/containers/c6bfd6da99d3/start
//
start : function(asyncCallback){
this._isset(this._containerID, 'start: this._containerID not set');
var binds = {
"Binds":["/tmp:/tmp"]
};
var options = {
path: '/containers/'+this._containerID+'/start',
method: 'POST'
};
this._dockerRemoteAPI(options,
function(chunk) {
console.log('start: ' + chunk);
}.bind(this),
null,
function(req) {
req.write(JSON.stringify(binds));
req.end();
}.bind(this),
asyncCallback
);
},
// delete
//-------------------------------------------------------------------------------------------------
//
// Equivalent of: curl -d '' http://localhost:4243/containers/c6bfd6da99d3/stop?t=10
//
delete : function(asyncCallback, containerID){
var async = require('async');
var _settings;
async.series([
// Get the container ID for the name
//function(fn){ this._inspect(fn, containerID); }.bind(this),
// Fetch the container settings
function(fn) {
if (this._isset(containerID, "Container missing, can't inspect", true)) {
_settings = this.inspect(fn, containerID);
}
fn(null, 'second func');
}.bind(this),
// stop the container
function(fn) {
if (this._isset(containerID, "Container missing, can't stop", true)) {
var options = {
path: '/containers/'+containerID+'/stop?t=10',
method: 'POST'
};
this._dockerRemoteAPI(options,
function(chunk) {
this._helpers.logInfo('delete: ' + chunk);
}.bind(this),
null,
null,
fn);
}
fn(null, 'third func');
}.bind(this),
// Delete the container
function(fn) {
if (this._isset(containerID, "Container missing, can't delete", true)) {
var options = {
path: '/containers/'+containerID+'?v=1',
method: 'DELETE'
};
this._dockerRemoteAPI(options,
function(chunk) {
this._helpers.logInfo('delete: ' + chunk);
}.bind(this),
null,
null,
fn);
}
fn(null, 'fourth func');
}.bind(this),
// Delete the image
function(fn) {
if (this._isset(_settings, "Settngs with image ID missing, can't remove",true)) {
var options = {
path: '/images/'+_settings.Image,
method: 'DELETE'
};
this._dockerRemoteAPI(options,
function(chunk) {
this._helpers.logDebug('delete: ' + chunk);
}.bind(this),
null,
null,
fn);
}
fn(null, 'fith func');
}.bind(this),
// Finish async series
function(fn) {
if(asyncCallback !== undefined) {
asyncCallback(null, 'delete finished (outer)');
}
fn(null, 'delete finished (inner)');
}
]);
},
// inspect
//-------------------------------------------------------------------------------------------------
//
// Equivalent of: curl -G http://localhost:4243/containers/c6bfd6da99d3/json
//
inspect : function(asyncCallback, containerID){
if (!this._isset(containerID, 'inspect: containerID not set', true)) {
asyncCallback(null,'inspect not possible without continer');
return;
}
var options = {
path: '/containers/'+containerID+'/json',
method: 'GET'
};
var _settings;
this._dockerRemoteAPI(options, function(chunk) {
try {
_settings = this._settings = JSON.parse(chunk);
this._imageID = this._settings.Image;
} catch (e) {
console.log('inspect: error fetching data for - ' + containerID);
}
}.bind(this),
null,
null,
asyncCallback);
return _settings;
},
// logs
//-------------------------------------------------------------------------------------------------
//
// Get the logs of the started container (should show the current date since that's all the container does)
// Equivalent of: curl -H "Content-Type: application/vnd.docker.raw-stream" -d '' "http://localhost:4243/containers/c6bfd6da99d3/attach?logs=1&stream=0&stdout=1"
//
logs : function(asyncCallback){
this._isset(this._containerID, 'logs: this._containerID not set');
var options = {
path: '/containers/'+this._containerID+'/attach?logs=1&stream=0&stdout=1',
method: 'POST',
headers: {
'Content-Type': 'application/vnd.docker.raw-stream',
}
};
this._dockerRemoteAPI(options, function(chunk) {
console.log('logs: ' + chunk);
},
null,
null,
asyncCallback
);
},
// status functions
//-------------------------------------------------------------------------------------------------
//
// Show logs and settings
//
// status helper function
// curl -G http://localhost:4243/containers/json
containers : function(asyncCallback) {
var options = {
path: '/containers/json',
method: 'GET',
};
var prettyjson = require('prettyjson');
this._dockerRemoteAPI(options, function(chunk) {
var containers = JSON.parse(chunk);
containers.forEach(function(container) {
var containerID = container.Id;
this._inspect(asyncCallback, containerID);
});
console.log('containers: ' + prettyjson.render(containers));
},
null,
null,
asyncCallback
);
},
status : function(containerID){
var async = require('async'),
prettyjson = require('prettyjson');
// List all containers
if (containerID === undefined) {
async.series([
function(fn){ this._proxyStatus(fn); }.bind(this),
/*function(fn){ this._containers(fn); }.bind(this)*/
],
function(err, results){
console.log('status: results of async functions - ' + results);
console.log('status: errors (if any) - ' + err);
});
}
// Show status for a specific container
else {
async.series([
function(fn){
this._isset(containerID, 'There is no app with the name: '+this._name);
this._inspect(fn, containerID);
}.bind(this),
function(fn){ this._logs(fn, containerID); }.bind(this),
function(fn){
console.log(prettyjson.render(this._settings));
fn(null, 'settings printed');
}.bind(this)
],
function(err, results){
console.log('status: results of async functions - ' + results);
console.log('status: errors (if any) - ' + err);
});
}
}
};
};
}(typeof exports === 'undefined' ? this['node-docker']={} : exports));