simple-dockerode
Version:
Wraps Dockerode container exec function to make it easier to use
536 lines (481 loc) • 18.5 kB
JavaScript
/* global describe:false, afterEach:false step:false */
const assert = require('chai').assert;
const SimpleDockerode = require('../lib/index.js');
const Stream = require('stream');
const StreamBattery = require('streambattery');
/* eslint max-nested-callbacks:0 no-console:0 */
const nonce = 'test';
const rand = 100;
function getTestString() {
return nonce + Math.floor(Math.random() * rand);
}
function hasFailed(it) {
let failed = false;
let tests = it.test.parent.tests;
for(let i = 0, limit = tests.length; !failed && i < limit; ++i) {
failed = tests[i].state === 'failed';
}
return failed;
}
function checkResults(results) {
assert.property(results, 'inspect', 'inspection results are available');
assert.property(results.inspect, 'ExitCode', 'inspection results contain an exit code');
assert.isNotNull(results.inspect.ExitCode, 'the exec has ended before presenting results');
assert.equal(results.inspect.ExitCode, 0, 'exec exit code is 0');
if( 'tries' in results.inspect ) {
// This means that the workaround had to be engaged, and I want to be warned about that.
console.warn('This process did not return immediately, causing ' + results.inspect.tries + ' extra inspect calls.');
}
}
function checkProperty(results, key, val) {
if( val ) {
assert.property(results, key, `has a ${key} property`);
assert.isNotNull(results[key], `has content in ${key}`);
assert.equal(results[key], val, `${key} is correct`);
}
}
function hasOutput(results, stdout, stderr) {
checkProperty(results, 'stdout', stdout);
checkProperty(results, 'stderr', stderr);
}
let Dockerode;
let container;
describe('Basic', function () {
let d;
step('Object Creation', function () {
assert.doesNotThrow(function () {
d = new SimpleDockerode();
}, Error);
assert.instanceOf(d, SimpleDockerode);
});
});
describe('Usage', function () {
Dockerode = new SimpleDockerode();
const testContainerName = 'simple-dockerode-test';
describe('Normal Dockerode', function () {
step('pull an alpine image', function (done) {
this.timeout(10000);
Dockerode.pull('alpine:latest', (err, stream) => {
if( err ) { done(err); return; }
// stream.pipe(process.stdout);
// Must consume stream's data or it won't exit.
stream.on('data', () => {});
stream.on('end', done);
});
});
step('start an alpine container', function (done) {
Dockerode.createContainer({Image: 'alpine:latest', Cmd: ['tail', '-f', '/dev/null'], name: testContainerName}, function (err, c) {
if( err ) { done(err); return; }
if( c == null ) { done(new Error('Container was null!')); return; }
if( !('start' in c) ) { done(new Error('Container did not have start function!')); return; }
container = c;
container.start(done);
});
});
});
describe('Output Only', function () {
step('get stdout, callback', function (done) {
this.timeout(10000);
const testString = getTestString();
container.exec(['echo', '-n', testString], {stdout: true}, (err, results) => {
if( err ) { done(err); return; }
checkResults(results);
hasOutput(results, testString);
done();
});
});
step('get stdout, promise', function (done) {
this.timeout(10000);
const testString = getTestString();
container.exec(['echo', '-n', testString], {stdout: true}).catch(done).then(results => {
checkResults(results);
hasOutput(results, testString);
done();
});
});
step('get stderr, callback', function (done) {
this.timeout(10000);
const testString = getTestString();
container.exec(['sh', '-c', `echo -n ${testString} >&2`], {stderr: true}, (err, results) => {
if( err ) { done(err); return; }
checkResults(results);
hasOutput(results, null, testString);
done();
});
});
step('get stderr, promise', function (done) {
this.timeout(10000);
const testString = getTestString();
container.exec(['sh', '-c', `echo -n ${testString} >&2`], {stderr: true}).catch(done).then(results => {
checkResults(results);
hasOutput(results, null, testString);
done();
});
});
step('get stdout and stderr, callback', function (done) {
this.timeout(10000);
const testString = getTestString();
const errorString = nonce + Math.floor(Math.random() * rand);
container.exec(['sh', '-c', `echo -n ${errorString} >&2 | echo -n ${testString}`], {stdout: true, stderr: true}, (err, results) => {
if( err ) { done(err); return; }
checkResults(results);
hasOutput(results, testString, errorString);
done();
});
});
step('get stdout and stderr, promise', function (done) {
this.timeout(10000);
const testString = getTestString();
const errorString = nonce + Math.floor(Math.random() * rand);
container.exec(['sh', '-c', `echo -n ${errorString} >&2 | echo -n ${testString}`], {stdout: true, stderr: true}).catch(done).then(results => {
checkResults(results);
hasOutput(results, testString, errorString);
done();
});
});
});
describe('Input Only', function () {
step('send an input string, callback', function (done) {
this.timeout(10000);
const testString = getTestString();
container.exec(['tee', '/tmp/test'], {stdin: testString}, (err, results) => {
if( err ) { done(err); return; }
checkResults(results);
container.exec(['cat', '/tmp/test'], {stdout: true}, (e, r) => {
if( e != null ) { done(e); return; }
checkResults(r);
hasOutput(r, testString);
done();
});
});
});
step('send an input string, promise', function (done) {
this.timeout(10000);
const testString = getTestString();
container.exec(['tee', '/tmp/test'], {stdin: testString}).catch(done).then(results => {
checkResults(results);
return Promise.resolve();
}).then(() => container.exec(['cat', '/tmp/test'], {stdout: true})).catch(done).then(results => {
checkResults(results);
hasOutput(results, testString);
done();
});
});
step('send an input stream, callback', function (done) {
this.timeout(10000);
const testString = getTestString();
// Set up a Stream
const sender = new Stream.Readable();
sender.push(testString);
sender.push(null);
container.exec(['tee', '/tmp/test'], {stdin: sender}, (err, results) => {
if( err ) { done(err); return; }
checkResults(results);
container.exec(['cat', '/tmp/test'], {stdout: true}, (e, r) => {
if( e != null ) { done(e); return; }
checkResults(r);
hasOutput(r, testString);
done();
});
});
});
step('send an input stream, promise', function (done) {
this.timeout(10000);
const testString = getTestString();
// Set up a Stream
const sender = new Stream.Readable();
sender.push(testString);
sender.push(null);
container.exec(['tee', '/tmp/test'], {stdin: sender}).catch(done).then(results => {
checkResults(results);
return Promise.resolve();
}).then(() => container.exec(['cat', '/tmp/test'], {stdout: true})).catch(done).then(results => {
checkResults(results);
hasOutput(results, testString);
done();
});
});
});
describe('Input And Output', function () {
step('send an input string and hear it back, callback', function (done) {
this.timeout(10000);
const testString = getTestString();
container.exec(['tee'], {stdin: testString, stdout: true}, (err, results) => {
if( err ) { done(err); return; }
checkResults(results);
hasOutput(results, testString);
done();
});
});
step('send an input string and hear it back, promise', function (done) {
this.timeout(10000);
const testString = getTestString();
container.exec(['tee'], {stdin: testString, stdout: true}).catch(done).then((results) => {
checkResults(results);
hasOutput(results, testString);
done();
});
});
step('send an input stream and hear it back, callback', function (done) {
this.timeout(10000);
const testString = getTestString();
// Set up a Stream
const sender = new Stream.Readable();
sender.push(testString);
sender.push(null);
container.exec(['tee'], {stdin: sender, stdout: true}, (err, results) => {
if( err ) { done(err); return; }
checkResults(results);
hasOutput(results, testString);
done();
});
});
step('send an input stream and hear it back, promise', function (done) {
this.timeout(10000);
const testString = getTestString();
// Set up a Stream
const sender = new Stream.Readable();
sender.push(testString);
sender.push(null);
container.exec(['tee'], {stdin: sender, stdout: true}).catch(done).then((results) => {
checkResults(results);
hasOutput(results, testString);
done();
});
});
});
describe('Detached I/O', function () {
step('detached exec into the container, callback, no options', function (done) {
this.timeout(10000);
const testString = getTestString();
container.exec(['echo', testString], (err, results) => {
if( err ) { done(err); return; }
checkResults(results);
assert.notProperty(results, 'stdout');
assert.notProperty(results, 'stderr');
done();
});
});
step('detached exec into the container, promise, no options', function (done) {
this.timeout(10000);
const testString = getTestString();
container.exec(['echo', testString]).then((results) => {
checkResults(results);
assert.notProperty(results, 'stdout');
assert.notProperty(results, 'stderr');
done();
});
});
step('detached exec into the container, callback, blank options', function (done) {
this.timeout(10000);
const testString = getTestString();
container.exec(['echo', testString], {}, (err, results) => {
if( err ) { done(err); return; }
checkResults(results);
assert.notProperty(results, 'stdout');
assert.notProperty(results, 'stderr');
done();
});
});
step('detached exec into the container, promise, blank options', function (done) {
this.timeout(10000);
const testString = getTestString();
container.exec(['echo', testString], {}).then((results) => {
checkResults(results);
assert.notProperty(results, 'stdout');
assert.notProperty(results, 'stderr');
done();
});
});
});
describe('Output Only, Live', function () {
step('fail bad arguments', function (done) {
this.timeout(10000);
container.exec(['echo'], {live: true}, err => {
assert.instanceOf(err, Error, 'correctly identified invalid arguments');
done();
});
});
step('get stdout, callback', function (done) {
this.timeout(10000);
const testString = getTestString();
container.exec(['echo', '-n', testString], {live: true, stdout: true}, (err, hose) => {
if( err ) { done(err); return; }
const battery = new StreamBattery(['stdout', 'stderr'], (battError, battResults) => {
if( battError ) { done(battError); }
hasOutput(battResults, testString);
done();
});
hose(...battery.streams).on('end', () => battery.end());
});
});
step('get stdout, promise', function (done) {
this.timeout(10000);
const testString = getTestString();
container.exec(['echo', '-n', testString], {live: true, stdout: true}).catch(done).then(hose => {
const battery = new StreamBattery(['stdout', 'stderr'], (battError, battResults) => {
if( battError ) { done(battError); }
hasOutput(battResults, testString);
done();
});
hose(...battery.streams).on('end', () => battery.end());
});
});
step('get stderr, callback', function (done) {
this.timeout(10000);
const testString = getTestString();
container.exec(['sh', '-c', `echo -n ${testString} >&2`], {live: true, stderr: true}, (err, hose) => {
if( err ) { done(err); return; }
const battery = new StreamBattery(['stdout', 'stderr'], (battError, battResults) => {
if( battError ) { done(battError); }
hasOutput(battResults, null, testString);
done();
});
hose(...battery.streams).on('end', () => battery.end());
});
});
step('get stderr, promise', function (done) {
this.timeout(10000);
const testString = getTestString();
container.exec(['sh', '-c', `echo -n ${testString} >&2`], {live: true, stderr: true}).catch(done).then(hose => {
const battery = new StreamBattery(['stdout', 'stderr'], (battError, battResults) => {
if( battError ) { done(battError); }
hasOutput(battResults, null, testString);
done();
});
hose(...battery.streams).on('end', () => battery.end());
});
});
step('get stdout and stderr, callback', function (done) {
this.timeout(10000);
const testString = getTestString();
const errorString = nonce + Math.floor(Math.random() * rand);
container.exec(['sh', '-c', `echo -n ${errorString} >&2 | echo -n ${testString}`], {live: true, stdout: true, stderr: true}, (err, hose) => {
if( err ) { done(err); return; }
const battery = new StreamBattery(['stdout', 'stderr'], (battError, battResults) => {
if( battError ) { done(battError); }
hasOutput(battResults, testString, errorString);
done();
});
hose(...battery.streams).on('end', () => battery.end());
});
});
step('get stdout and stderr, promise', function (done) {
this.timeout(10000);
const testString = getTestString();
const errorString = nonce + Math.floor(Math.random() * rand);
container.exec(['sh', '-c', `echo -n ${errorString} >&2 | echo -n ${testString}`], {live: true, stdout: true, stderr: true}).catch(done).then(hose => {
const battery = new StreamBattery(['stdout', 'stderr'], (battError, battResults) => {
if( battError ) { done(battError); }
hasOutput(battResults, testString, errorString);
done();
});
hose(...battery.streams).on('end', () => battery.end());
});
});
});
describe('Input Only, Live', function () {
step('send an input stream, callback', function (done) {
this.timeout(10000);
const testString = getTestString();
// Set up a Stream
const sender = new Stream.Readable();
sender.push(testString);
sender.push(null);
container.exec(['tee', '/tmp/test'], {live: true, stdin: true}, (err, hose) => {
if( err ) { done(err); return; }
sender.pipe(hose());
container.exec(['cat', '/tmp/test'], {stdout: true}, (e, r) => {
if( e != null ) { done(e); return; }
checkResults(r);
hasOutput(r, testString);
done();
});
});
});
step('send an input stream, promise', function (done) {
this.timeout(10000);
const testString = getTestString();
// Set up a Stream
const sender = new Stream.Readable();
sender.push(testString);
sender.push(null);
container.exec(['tee', '/tmp/test'], {live: true, stdin: true}).catch(done).then(hose => {
const stream = hose();
sender.pipe(stream);
return new Promise(resolve => {
stream.on('end', resolve);
});
}).then(() => container.exec(['cat', '/tmp/test'], {stdout: true})).catch(done).then(results => {
checkResults(results);
hasOutput(results, testString);
done();
});
});
});
describe('Input And Output, Live', function () {
step('send an input stream and hear it back, callback', function (done) {
this.timeout(10000);
const testString = getTestString();
// Set up a Stream
const sender = new Stream.Readable();
sender.push(testString);
sender.push(null);
container.exec(['tee'], {live: true, stdin: true, stdout: true}, (err, hose) => {
if( err ) { done(err); return; }
const battery = new StreamBattery(['stdout', 'stderr'], (battError, battResults) => {
if( battError ) { done(battError); }
hasOutput(battResults, testString);
done();
});
const stream = hose(...battery.streams);
sender.pipe(stream);
stream.on('end', () => battery.end());
});
});
step('send an input stream and hear it back, promise', function (done) {
this.timeout(10000);
const testString = getTestString();
// Set up a Stream
const sender = new Stream.Readable();
sender.push(testString);
sender.push(null);
container.exec(['tee'], {live: true, stdin: true, stdout: true}).catch(done).then(hose => {
const battery = new StreamBattery(['stdout', 'stderr'], (battError, battResults) => {
if( battError ) { done(battError); }
hasOutput(battResults, testString);
done();
});
const stream = hose(...battery.streams);
sender.pipe(stream);
stream.on('end', () => battery.end());
});
});
});
afterEach(function () {
if( hasFailed(this) ) {
if( container != null ) {
container.stop(err => {
if( err != null ) { throw new Error(err); }
container.remove(e => {
if( e != null ) { throw new Error(e); }
});
});
}
}
});
});
describe('Cleanup', function () {
step('kill container', function (done) {
if( container != null ) {
this.timeout(10000);
container.kill(done);
}
});
step('remove container', function (done) {
if( container != null ) {
this.timeout(10000);
container.remove(done);
}
});
});