├── .gitignore ├── .travis.yml ├── package.json ├── LICENSE ├── index.js ├── README.md ├── cli.js └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | sandbox 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docker-build", 3 | "version": "2.2.0", 4 | "description": "docker build as a duplex stream", 5 | "main": "index.js", 6 | "bin": { 7 | "docker-build": "cli.js" 8 | }, 9 | "dependencies": { 10 | "concat-stream": "^1.4.6", 11 | "docker-remote-api": "^4.4.0", 12 | "duplexify": "^3.2.0", 13 | "ignore-file": "^1.1.2", 14 | "minimist": "^1.1.0", 15 | "tar-fs": "^1.0.0", 16 | "tar-stream": "^1.0.0", 17 | "through-json": "^1.1.0" 18 | }, 19 | "devDependencies": { 20 | "tape": "^2.14.0" 21 | }, 22 | "scripts": { 23 | "test": "tape test.js" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git://github.com/mafintosh/docker-build" 28 | }, 29 | "keywords": [ 30 | "docker", 31 | "build", 32 | "tar", 33 | "duplex", 34 | "stream" 35 | ], 36 | "author": "Mathias Buus", 37 | "license": "MIT", 38 | "bugs": { 39 | "url": "https://github.com/mafintosh/docker-build/issues" 40 | }, 41 | "homepage": "https://github.com/mafintosh/docker-build" 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Mathias Buus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var docker = require('docker-remote-api') 2 | var throughJSON = require('through-json') 3 | var duplexify = require('duplexify') 4 | 5 | var build = function(tag, opts) { 6 | if (!opts) opts = {} 7 | 8 | var dup = duplexify() 9 | var request = opts.request || docker(opts.host) 10 | 11 | var qs = {} 12 | qs.t = tag 13 | 14 | if (opts.cache === false) qs.nocache = 'true' 15 | if (opts.quiet) qs.q = 'true' 16 | if (opts.remove === false) qs.rm = 'false' 17 | if (opts.forceremove) qs.forcerm = 'true' 18 | 19 | var onerror = function(err) { 20 | dup.destroy(err || new Error('Premature close')) 21 | } 22 | 23 | var post = request.post('/build', { 24 | qs: qs, 25 | version: opts.version || 'v1.15', 26 | headers: { 27 | 'Content-Type': 'application/tar', 28 | 'X-Registry-Config': opts.registry 29 | } 30 | }, function(err, response) { 31 | if (err) return onerror(err) 32 | 33 | var parser = throughJSON(function(data) { 34 | if (!data.error) return data.stream 35 | onerror(new Error(data.error.trim())) 36 | }) 37 | 38 | post.removeListener('close', onerror) 39 | post.on('close', function() { // to avoid premature close when stuff is buffered 40 | if (!response._readableState.ended) onerror() 41 | }) 42 | 43 | dup.setReadable(response.pipe(parser)) 44 | }) 45 | 46 | dup.setWritable(post) 47 | 48 | post.on('error', onerror) 49 | post.on('close', onerror) 50 | 51 | return dup 52 | } 53 | 54 | module.exports = build 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docker-build 2 | 3 | Docker build as a duplex stream. Pipe in a tar stream and pipe out the build output 4 | 5 | ``` js 6 | npm install docker-build 7 | ``` 8 | 9 | [![build status](http://img.shields.io/travis/mafintosh/docker-build.svg?style=flat)](http://travis-ci.org/mafintosh/docker-build) 10 | 11 | ## Usage 12 | 13 | ``` js 14 | var build = require('docker-build') 15 | var fs = require('fs') 16 | 17 | fs.createReadStream('a-tar-file-with-a-dockerfile.tar') 18 | .pipe(build('my-new-image')) 19 | .pipe(process.stdout) 20 | ``` 21 | 22 | The above example will build a docker image from the input tarball 23 | and pipe the build output to stdout using docker running locally on port 2375. 24 | 25 | ## API 26 | 27 | ``` js 28 | var stream = build(tag, [options]) 29 | ``` 30 | 31 | `options` can contain the following: 32 | 33 | ``` js 34 | { 35 | host: '/var/run/docker.sock', // host to docker 36 | cache: true, // whether or not to use docker fs cache (defaults to true) 37 | quiet: false, // be quiet - defaults to false, 38 | registry: conf, // add a registry config 39 | remove: true, // automatically removes intermediate contaners (defaults to true) 40 | forceremove: false // always remove intermediate containers, even if the build fails (defaults to false) 41 | } 42 | ``` 43 | 44 | ## CLI 45 | 46 | There is a command line too available as well 47 | 48 | ``` 49 | $ npm install -g docker-build 50 | $ docker-build --help 51 | ``` 52 | 53 | Running `docker-build some-image-tag` will build current working directory 54 | 55 | ## License 56 | 57 | MIT -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var build = require('./') 4 | var minimist = require('minimist') 5 | var tstream = require('tar-stream') 6 | var tar = require('tar-fs') 7 | var concat = require('concat-stream') 8 | var ignore = require('ignore-file') 9 | 10 | var join = function(a, b) { 11 | if (!a) return b 12 | if (!b) return a 13 | return function(filename) { 14 | return a(filename) || b(filename) 15 | } 16 | } 17 | 18 | var argv = minimist(process.argv, { 19 | boolean: ['cache', 'version'], 20 | alias: { 21 | tag: 't', 22 | quiet: 'q', 23 | version: 'v', 24 | host: 'H' 25 | }, 26 | default: { 27 | cache: true 28 | } 29 | }) 30 | 31 | var tag = argv._[2] 32 | var path = argv._[3] || '.' 33 | 34 | if (argv.version) { 35 | console.log(require('./package').version) 36 | process.exit(0) 37 | } 38 | 39 | if (argv.help || !tag) { 40 | console.error( 41 | 'Usage: docker-build [tag] [path?] [options]\n'+ 42 | '\n'+ 43 | ' --host, -H [docker-host]\n'+ 44 | ' --quiet, -q\n'+ 45 | ' --version, -v\n'+ 46 | ' --no-cache\n'+ 47 | ' --no-ignore\n' 48 | ) 49 | process.exit(1) 50 | } 51 | 52 | var onerror = function(err) { 53 | console.error(err.message) 54 | process.exit(2) 55 | } 56 | 57 | var opts = { 58 | host: argv.host, 59 | cache: argv.cache, 60 | quiet: argv.quiet 61 | } 62 | 63 | var input = function() { 64 | if (path !== '-') { 65 | var filter = ignore.sync('.dockerignore') || join(ignore.compile('.git'), ignore.sync('.gitignore')) 66 | if (argv.ignore === false) filter = null 67 | return tar.pack(path, {ignore:filter}) 68 | } 69 | 70 | var pack = tstream.pack() 71 | 72 | process.stdin.pipe(concat(function(data) { 73 | pack.entry({name:'Dockerfile', type:'file'}, data) 74 | pack.finalize() 75 | })) 76 | 77 | return pack 78 | } 79 | 80 | input() 81 | .on('error', onerror) 82 | .pipe(build(tag, opts)) 83 | .on('error', onerror) 84 | .pipe(process.stdout) -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape') 2 | var http = require('http') 3 | var path = require('path') 4 | var concat = require('concat-stream') 5 | var fs = require('fs') 6 | var build = require('./') 7 | 8 | var server = http.createServer(function(req, res) { // fakish docker thing 9 | req.resume() 10 | req.on('end', function() { 11 | if (req.url.indexOf('parse-error') > -1) { 12 | res.statusCode = 500 13 | res.write('bad thing') 14 | res.end() 15 | return 16 | } 17 | if (req.url.indexOf('stream-error') > -1) { 18 | res.write(JSON.stringify({stream:'hello'})) 19 | res.write(JSON.stringify({error:'test'})) 20 | res.end() 21 | return 22 | } 23 | res.write(JSON.stringify({stream:'hello'})) 24 | res.write(JSON.stringify({stream:' world'})) 25 | res.end() 26 | }) 27 | }) 28 | 29 | server.listen(0, function() { 30 | server.unref() 31 | 32 | var host = ':'+server.address().port 33 | 34 | tape('output', function(t) { 35 | var img = build('test', {host:host}) 36 | 37 | img.write('i am a tar file') 38 | img.end() 39 | 40 | img.pipe(concat(function(data) { 41 | t.same(data.toString(), 'hello world') 42 | t.end() 43 | })) 44 | }) 45 | 46 | tape('parse error', function(t) { 47 | var img = build('parse-error', { 48 | host: host 49 | }) 50 | 51 | img.write('i am a tar file') 52 | img.end() 53 | 54 | img.on('error', function(err) { 55 | t.ok(err) 56 | t.same(err.message, 'bad thing') 57 | t.end() 58 | }) 59 | }) 60 | 61 | tape('stream error', function(t) { 62 | t.plan(2) 63 | 64 | var img = build('stream-error', { 65 | host: host 66 | }) 67 | 68 | img.write('i am a tar file') 69 | img.end() 70 | 71 | img.on('data', function(data) { 72 | t.same(data.toString(), 'hello') 73 | }) 74 | img.on('error', function(err) { 75 | t.same(err.message, 'test') 76 | t.end() 77 | }) 78 | }) 79 | }) 80 | --------------------------------------------------------------------------------