├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── example.js ├── index.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docker-remote-api 2 | 3 | Basic http wrapper to call the docker remote api from node 4 | 5 | ``` 6 | npm install docker-remote-api 7 | ``` 8 | 9 | [![build status](http://img.shields.io/travis/mafintosh/docker-remote-api.svg?style=flat)](http://travis-ci.org/mafintosh/docker-remote-api) 10 | [![install size](https://packagephobia.now.sh/badge?p=docker-remote-api)](https://packagephobia.now.sh/result?p=docker-remote-api) 11 | 12 | ## Usage 13 | 14 | ``` js 15 | var docker = require('docker-remote-api') 16 | var request = docker({ 17 | host: '/var/run/docker.sock' 18 | }) 19 | 20 | request.get('/images/json', {json:true}, function(err, images) { 21 | if (err) throw err 22 | console.log('images', images) 23 | }) 24 | 25 | request.get('/images/json', function(err, stream) { 26 | if (err) throw err 27 | // stream is a raw response stream 28 | }) 29 | ``` 30 | 31 | ## API 32 | 33 | #### `request = docker(options)` 34 | 35 | `options.host` should be an address to a docker instance i.e. `/var/run/docker.sock` or `127.0.0.1:2375`. 36 | All other options will be used as default values for `get`, `post`, `put`, `delete`. 37 | 38 | If you omit the `options.host` it will be set to `$DOCKER_HOST` or `/var/run/docker.sock` 39 | 40 | If `options.ssl` is `true`, the library will look in `$DOCKER_CERT_PATH` for a certificate (`cert.pem`), key (`key.pem`), and certificate authority (`ca.pem`). 41 | 42 | #### `request.get(path, [options], cb)` 43 | 44 | Send a `GET` request to the remote api. `path` should be the request path i.e. `/images/json`. 45 | `options` can contain the following 46 | 47 | ``` js 48 | { 49 | qs: {foo:'bar'}, // set querystring parameters 50 | headers: {name: '...'}, // set request headers 51 | json: true, // return json instead of a stream 52 | buffer: true, // return a buffer instead of a stream 53 | drain: true, // will drain the response stream before calling cb 54 | timeout: 20000, // set request timeout 55 | version: 'v1.14' // set explicit api version 56 | } 57 | ``` 58 | 59 | #### `request.delete(path, [options], cb)` 60 | 61 | Send a `DELETE` request. Similar options as `request.get` 62 | 63 | #### `post = request.post(path, [options], cb)` 64 | 65 | Send a `POST` request. Similar options as `request.get` except it returns a request stream 66 | that you can pipe a request body to. If you are sending json you can set `options.json = body` 67 | and `body` will be stringified and sent as the request body. 68 | 69 | If you do not have a request body set `body: null` or remember to call `post.end()` 70 | 71 | #### `put = request.put(path, [options], cb)` 72 | 73 | Send a `PUT` request. Similar options as `request.post` 74 | 75 | ## License 76 | 77 | MIT 78 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | var api = require('./') 2 | 3 | var request = api({version:'v1.12'}) 4 | 5 | request.get('/images/json', {json:true}, console.log) 6 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var host = require('docker-host') 2 | var xtend = require('xtend') 3 | var once = require('once') 4 | var querystring = require('querystring') 5 | var concat = require('concat-stream') 6 | 7 | var noop = function() {} 8 | 9 | var readSync = function(dir, name) { 10 | try { 11 | return require('fs').readFileSync(require('path').join(dir, name)) 12 | } catch (err) { 13 | return null 14 | } 15 | } 16 | 17 | var PATH = process.env.DOCKER_CERT_PATH 18 | var CERT = PATH && readSync(PATH, 'cert.pem') 19 | var KEY = PATH && readSync(PATH, 'key.pem') 20 | var CA = PATH && readSync(PATH, 'ca.pem') 21 | var TLS = process.env.DOCKER_TLS_VERIFY === '1' || process.env.DOCKER_TLS_VERIFY === 'true' 22 | 23 | var onjson = function(req, res, cb) { 24 | res.pipe(concat({encoding:'buffer'}, function(buf) { 25 | try { 26 | buf = JSON.parse(buf) 27 | } catch (err) { 28 | return cb(err) 29 | } 30 | cb(null, buf) 31 | })) 32 | } 33 | 34 | var onempty = function(req, res, cb) { 35 | res.on('end', function() { 36 | cb(null, null) 37 | }) 38 | res.resume() 39 | } 40 | 41 | var onbuffer = function(req, res, cb) { 42 | res.pipe(concat({encoding:'buffer'}, function(buf) { 43 | cb(null, buf) 44 | })) 45 | } 46 | 47 | var onstream = function(req, res, cb) { 48 | req.on('close', function() { 49 | res.emit('close') 50 | }) 51 | req.on('error', function(err) { 52 | res.emit('error', err) 53 | }) 54 | cb(null, res) 55 | } 56 | 57 | var onerror = function(req, res, cb) { 58 | res.pipe(concat({encoding:'buffer'}, function(buf) { 59 | var err = new Error(buf.toString().trim() || 'Bad status code: '+res.statusCode) 60 | err.status = res.statusCode 61 | cb(err) 62 | })) 63 | } 64 | 65 | var destroyer = function(req) { 66 | return function() { 67 | req.destroy() 68 | } 69 | } 70 | 71 | var API = function(opts) { 72 | if (!(this instanceof API)) return new API(opts) 73 | if (typeof opts === 'string' || typeof opts === 'number') opts = {host:opts} 74 | if (!opts) opts = {} 75 | 76 | this.defaults = xtend({cert:CERT, ca:CA, key:KEY, ssl:TLS}, opts, host(opts.host)) // TODO: move the defaults stuff to docker-host? 77 | if (this.defaults.ssl || this.defaults.tls || this.defaults.https) this.defaults.protocol = 'https:' 78 | 79 | this.http = (this.defaults.protocol === 'https:' ? require('https') : require('http')).request 80 | this.host = this.defaults.socketPath ? 'http+unix://'+this.defaults.socketPath : this.defaults.protocol+'//'+this.defaults.host+':'+this.defaults.port 81 | } 82 | 83 | API.prototype.type = 'docker-remote-api' 84 | 85 | API.prototype.get = function(path, opts, cb) { 86 | return this.request('GET', path, opts, cb) 87 | } 88 | 89 | API.prototype.put = function(path, opts, cb) { 90 | return this.request('PUT', path, opts, cb) 91 | } 92 | 93 | API.prototype.post = function(path, opts, cb) { 94 | return this.request('POST', path, opts, cb) 95 | } 96 | 97 | API.prototype.head = function(path, opts, cb) { 98 | return this.request('HEAD', path, opts, cb) 99 | } 100 | 101 | API.prototype.del = API.prototype.delete = function(path, opts, cb) { 102 | return this.request('DELETE', path, opts, cb) 103 | } 104 | 105 | API.prototype.request = function(method, path, opts, cb) { 106 | if (typeof opts === 'function') { 107 | cb = opts 108 | opts = null 109 | } 110 | 111 | cb = once(cb || noop) 112 | opts = xtend(this.defaults, opts) 113 | 114 | if (opts.qs) path += '?'+querystring.stringify(opts.qs) 115 | if (opts.version) path = '/'+opts.version+path 116 | 117 | opts.method = method 118 | opts.path = path 119 | 120 | var headers = opts.headers 121 | if (headers) { 122 | Object.keys(headers).forEach(function(name) { 123 | if (typeof headers[name] === 'object' && headers[name]) headers[name] = Buffer.from(JSON.stringify(headers[name])+'\n').toString('base64') 124 | if (!headers[name]) delete headers[name] 125 | }) 126 | } 127 | 128 | var req = this.http(opts) 129 | 130 | if (opts.timeout) req.setTimeout(opts.timeout, destroyer(req)) 131 | 132 | if (opts.json && opts.json !== true) { 133 | req.setHeader('Content-Type', 'application/json') 134 | opts.body = JSON.stringify(opts.json) 135 | } 136 | 137 | req.on('response', function(res) { 138 | if (res.statusCode === 304) return onempty(req, res, cb) 139 | else if (res.statusCode > 299) onerror(req, res, cb) 140 | else if (res.statusCode === 204 || opts.drain) onempty(req, res, cb) 141 | else if (opts.buffer) onbuffer(req, res, cb) 142 | else if (opts.json) onjson(req, res, cb) 143 | else onstream(req, res, cb) 144 | }) 145 | 146 | req.on('error', cb) 147 | req.on('close', function() { 148 | cb(new Error('Premature close')) 149 | }) 150 | 151 | if (method !== 'POST' && method !== 'PUT') req.end() 152 | else if (opts.body === null) { 153 | req.setHeader('Content-Length', 0) 154 | req.end() 155 | } else if (opts.body) { 156 | req.setHeader('Content-Length', Buffer.isBuffer(opts.body) ? opts.body.length : Buffer.byteLength(opts.body)) 157 | req.end(opts.body) 158 | } 159 | 160 | return req 161 | } 162 | 163 | module.exports = API 164 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docker-remote-api", 3 | "description": "Basic http wrapper to call the docker remote api from node", 4 | "version": "5.0.0", 5 | "main": "index.js", 6 | "dependencies": { 7 | "concat-stream": "^1.4.6", 8 | "docker-host": "^3.0.0", 9 | "once": "^1.3.0", 10 | "xtend": "^3.0.0" 11 | }, 12 | "devDependencies": { 13 | "tape": "^2.13.3" 14 | }, 15 | "scripts": { 16 | "test": "tape test.js" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git://github.com/mafintosh/docker-remote-api" 21 | }, 22 | "keywords": [ 23 | "docker", 24 | "remote", 25 | "api", 26 | "http", 27 | "https", 28 | "request" 29 | ], 30 | "author": "Mathias Buus", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/mafintosh/docker-remote-api/issues" 34 | }, 35 | "homepage": "https://github.com/mafintosh/docker-remote-api" 36 | } 37 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape') 2 | var concat = require('concat-stream') 3 | var http = require('http') 4 | var docker = require('./') 5 | 6 | var server = http.createServer(function(req, res) { 7 | if (req.method === 'POST') return req.pipe(res) 8 | 9 | if (req.url === '/error') { 10 | res.statusCode = 500 11 | res.end('error message') 12 | return 13 | } 14 | 15 | res.end(JSON.stringify({ 16 | method: req.method, 17 | path: req.url 18 | })) 19 | }) 20 | 21 | server.listen(0, function() { 22 | server.unref() 23 | 24 | var request = docker({ssl:false, host:server.address().port}) 25 | 26 | tape('get json', function(t) { 27 | request.get('/', {json:true, qs:{foo:'bar'}}, function(err, body) { 28 | t.ok(!err) 29 | t.same(body.method, 'GET') 30 | t.same(body.path, '/?foo=bar') 31 | t.end() 32 | }) 33 | }) 34 | 35 | tape('get stream', function(t) { 36 | request.get('/', function(err, response) { 37 | t.ok(!err) 38 | response.pipe(concat(function(buf) { 39 | t.same(buf.toString(), '{"method":"GET","path":"/"}') 40 | t.end() 41 | })) 42 | }) 43 | }) 44 | 45 | tape('post stream', function(t) { 46 | var post = request.post('/', function(err, response) { 47 | t.ok(!err) 48 | response.pipe(concat(function(buf) { 49 | t.same(buf.toString(), 'hello world') 50 | t.end() 51 | })) 52 | }) 53 | 54 | post.write('hello') 55 | post.end(' world') 56 | }) 57 | 58 | tape('post json', function(t) { 59 | request.post('/', {json:{method:'GET', path:'/'}}, function(err, body) { 60 | t.ok(!err) 61 | t.same(body.method, 'GET') 62 | t.same(body.path, '/') 63 | t.end() 64 | }) 65 | }) 66 | 67 | tape('get error', function(t) { 68 | request.get('/error', function(err) { 69 | t.ok(err) 70 | t.same(err.message, 'error message') 71 | t.end() 72 | }) 73 | }) 74 | }) 75 | --------------------------------------------------------------------------------