diff --git a/index.js b/index.js index 08cc213..f196fa8 100644 --- a/index.js +++ b/index.js @@ -29,19 +29,31 @@ const configToArgs = config => { * @param {?object} [options.env] */ const execCompose = (command, args, options) => new Promise((resolve, reject) => { - const composeArgs = configToArgs(options.config); + const composeArgs = configToArgs(options.config).concat([ command ], args); const cwd = options.cwd; const env = options.env || null; - const childProc = childProcess.spawn('docker-compose', composeArgs.concat([ command ], args), { cwd, env }, (err, stdout, stderr) => { - if (err) { - reject(err); - } else { - resolve({ - err: stderr, - out: stdout - }); - } + const childProc = childProcess.spawn('docker-compose', composeArgs, { cwd, env }); + + childProc.on('error', err => { + reject(err); + }); + + const result = { + err: '', + out: '' + }; + + childProc.stdout.on('data', chunk => { + result.out += chunk.toString(); + }); + + childProc.stderr.on('data', chunk => { + result.err += chunk.toString(); + }); + + childProc.on('close', () => { + resolve(result); }); if (options.log) { @@ -118,7 +130,9 @@ const rm = function (options) { * @return {object} std.out / std.err */ const exec = function (container, command, options) { - return execCompose('exec', [ '-T', container, command ], options); + const args = command.split(/\s+/); + + return execCompose('exec', [ '-T', container ].concat(args), options); }; /** @@ -134,7 +148,53 @@ const exec = function (container, command, options) { * @return {object} std.out / std.err */ const run = function (container, command, options) { - return execCompose('run', [ '-T', container, command ], options); + const args = command.split(/\s+/); + + return execCompose('run', [ '-T', container ].concat(args), options); }; -module.exports = { up, kill, down, stop, rm, exec, run }; +/** + * Build command + * @param {object} options + * @param {string} options.cwd + * @param {boolean} [options.log] + * @param {?(string|string[])} [options.config] + * @param {?object} [options.env] + * + * @return {object} std.out / std.err + */ +const buildAll = function (options) { + return execCompose('build', [], options); +}; + +/** + * Build command + * @param {string[]} services list of service names + * @param {object} options + * @param {string} options.cwd + * @param {boolean} [options.log] + * @param {?(string|string[])} [options.config] + * @param {?object} [options.env] + * + * @return {object} std.out / std.err + */ +const buildMany = function (services, options) { + return execCompose('build', services, options); +}; + +/** + * Build command + * @param {string} service service name + * @param {object} options + * @param {string} options.cwd + * @param {boolean} [options.log] + * @param {?(string|string[])} [options.config] + * @param {?object} [options.env] + * + * @return {object} std.out / std.err + */ +const buildOne = function (service, options) { + return execCompose('build', [ service ], options); +}; + +module.exports = { up, kill, down, stop, rm, exec, run, buildAll, buildMany, buildOne }; diff --git a/test/build-test.Dockerfile b/test/build-test.Dockerfile new file mode 100644 index 0000000..fbc0740 --- /dev/null +++ b/test/build-test.Dockerfile @@ -0,0 +1,3 @@ +FROM alpine:3.7 + +RUN echo "hello build test" \ No newline at end of file diff --git a/test/docker-compose-build.yml b/test/docker-compose-build.yml new file mode 100644 index 0000000..806cf1b --- /dev/null +++ b/test/docker-compose-build.yml @@ -0,0 +1,23 @@ +version: '2' + +services: + build_test_1: + image: compose-test-build-image-1:test + build: + context: ./ + dockerfile: build-test.Dockerfile + build_test_2: + image: compose-test-build-image-2:test + build: + context: ./ + dockerfile: build-test.Dockerfile + build_test_3: + image: compose-test-build-image-3:test + build: + context: ./ + dockerfile: build-test.Dockerfile + build_test_4: + image: compose-test-build-image-4:test + build: + context: ./ + dockerfile: build-test.Dockerfile \ No newline at end of file diff --git a/test/index.js b/test/index.js index 4f6d7c0..b59521b 100644 --- a/test/index.js +++ b/test/index.js @@ -20,6 +20,29 @@ const isContainerRunning = async name => new Promise((resolve, reject) => { }); }); +const imageExists = async name => { + const images = await docker.listImages(); + + const foundImage = images.findIndex(imageInfo => imageInfo.RepoTags.includes(name)); + + return foundImage > -1; +}; + +const removeImagesStartingWith = async searchString => { + const images = await docker.listImages(); + + for (const image of images) { + for (const repoTag of image.RepoTags) { + if (repoTag.startsWith(searchString)) { + const dockerImage = docker.getImage(repoTag); + + console.log(`removing image ${repoTag} ${dockerImage.id || ''}`); + await dockerImage.remove(); + } + } + } +}; + test('ensure container gets started', async assert => { await compose.up({ cwd: path.join(__dirname), log: true }); @@ -97,7 +120,66 @@ test('ensure run and exec are working', async assert => { assert.end(); }); -test('teardown', assert => { +test('build single service', async assert => { + const opts = { + cwd: path.join(__dirname), + log: false, + config: 'docker-compose-build.yml' + }; + + await removeImagesStartingWith('compose-test-build-image'); + + await compose.buildOne('build_test_1', opts); + + assert.true(await imageExists('compose-test-build-image-1:test')); + assert.false(await imageExists('compose-test-build-image-2:test')); + assert.false(await imageExists('compose-test-build-image-3:test')); + assert.false(await imageExists('compose-test-build-image-4:test')); + + await removeImagesStartingWith('compose-test-build-image'); + + assert.end(); +}); + +test('build multiple services', async assert => { + const opts = { + cwd: path.join(__dirname), + log: false, + config: 'docker-compose-build.yml' + }; + + await compose.buildMany([ 'build_test_2', 'build_test_3' ], opts); + + assert.false(await imageExists('compose-test-build-image-1:test')); + assert.true(await imageExists('compose-test-build-image-2:test')); + assert.true(await imageExists('compose-test-build-image-3:test')); + assert.false(await imageExists('compose-test-build-image-4:test')); + + await removeImagesStartingWith('compose-test-build-image'); + + assert.end(); +}); + +test('build all services', async assert => { + const opts = { + cwd: path.join(__dirname), + log: false, + config: 'docker-compose-build.yml' + }; + + await compose.buildAll(opts); + + assert.true(await imageExists('compose-test-build-image-1:test')); + assert.true(await imageExists('compose-test-build-image-2:test')); + assert.true(await imageExists('compose-test-build-image-3:test')); + assert.true(await imageExists('compose-test-build-image-4:test')); + + await removeImagesStartingWith('compose-test-build-image'); + + assert.end(); +}); + +test('teardown', async assert => { docker.listContainers((err, containers) => { if (err) { throw err; @@ -113,5 +195,7 @@ test('teardown', assert => { }); }); + await removeImagesStartingWith('compose-test-build-image'); + assert.end(); });