Run application when supervisor starts

This commit is contained in:
Petros Aggelatos 2013-12-23 04:33:16 +00:00 committed by Pablo Carranza Vélez
parent 563df5386d
commit 12b3e194b5
3 changed files with 47 additions and 132 deletions

View File

@ -6,16 +6,15 @@
},
"dependencies": {
"coffee-script": "~1.6.3",
"async": "~0.2.9",
"request": "~2.22.0",
"posix": "~1.0.2",
"express": "~3.2.6",
"lodash": "~1.3.1",
"csr-gen": "~0.2.1",
"dockerode": "~0.2.3",
"sqlite3": "~2.1.19",
"dockerode": "~0.2.5",
"knex": "~0.5.1",
"bluebird": "~0.11.5-0"
"bluebird": "~0.11.5-0",
"JSONStream": "~0.7.1",
"event-stream": "~3.0.20"
},
"engines": [
"node >= 0.10.x"

View File

@ -4,16 +4,22 @@ os = require 'os'
api = require './api'
knex = require './db'
utils = require './utils'
Docker = require 'dockerode'
crypto = require 'crypto'
{spawn} = require 'child_process'
bootstrap = require './bootstrap'
application = require './application'
console.log('Supervisor started..')
# Connect to the host docker instance
docker = Promise.promisifyAll(new Docker(socketPath: '/hostrun/docker.sock'))
newUuid = utils.getDeviceUuid()
oldUuid = knex('config').select('value').where(key: 'uuid')
Promise.all([newUuid, oldUuid]).then(([newUuid, [oldUuid]]) ->
oldUuid = oldUuid?.value
if newUuid is oldUuid
return true
@ -37,4 +43,11 @@ Promise.all([newUuid, oldUuid]).then(([newUuid, [oldUuid]]) ->
console.log('Starting API server..')
api.listen(80)
console.log('Starting Apps..')
knex('app').select().then((apps) ->
Promise.all(apps.map(application.start))
).catch((error) ->
console.log(error)
)
)

View File

@ -1,135 +1,38 @@
{spawn} = require('child_process')
{getpwnam} = require('posix')
async = require('async')
{EventEmitter} = require('events')
_ = require('lodash')
state = require('./state')
Promise = require 'bluebird'
Docker = require 'dockerode'
JSONStream = require 'JSONStream'
es = require 'event-stream'
http = require 'http'
class Application extends EventEmitter
constructor: (@repo, @path, @user) ->
EventEmitter.call(this)
@process = null
@inprogress = false
@queue = []
@options =
cwd: @path
stdio: 'inherit'
uid: getpwnam(@user).uid
gid: getpwnam(@user).gid
env:
USER: @user
USERNAME: @user
HOME: "/home/#{@user}"
docker = Promise.promisifyAll(new Docker(socketPath: '/hostrun/docker.sock'))
# Hack dockerode to promisify internal classes' prototype
Promise.promisifyAll(docker.getImage().__proto__)
_init: (callback) ->
tasks = [
# Create the directory for the project
(callback) =>
spawn('mkdir', ['-p', @path]).on('exit', callback).on('error', callback)
exports.start = (app) ->
docker.getImage(app.imageId).inspectAsync().catch(->
deferred = Promise.defer()
options =
method: 'POST'
path: "/v1.8/images/create?fromImage=#{app.imageId}"
socketPath: '/hostrun/docker.sock'
# Change the owner to the user
(callback) =>
spawn('chown', [@user, @path]).on('exit', callback).on('error', callback)
req = http.request(options, (res) ->
if res.statusCode isnt 200
return deferred.reject()
# Initalize a new empty git repo
(callback) =>
spawn('git', ['init'], @options).on('exit', callback).on('error', callback)
res.pipe(JSONStream.parse('error'))
.pipe(es.mapSync((error) ->
deferred.reject(error)
))
# Add the remote origin to the repo
(callback) =>
spawn('git', ['remote', 'add', 'origin', @repo], @options).on('exit', callback).on('error', callback)
]
@emit('pre-init')
async.series(tasks, =>
@emit('post-init')
callback?(arguments...)
res.on('end', ->
deferred.resolve()
)
)
req.end()
req.on('error', (error) -> deferred.reject(error))
_exitHandler: (code, signal) ->
@process = null
_start: (callback) ->
if not @process
options =
cwd: @path
stdio: 'inherit'
@process = spawn('sudo', ['-u', @user, 'foreman', 'start'], options)
@process.on('exit', @_exitHandler.bind(@))
@process.on('error', @_exitHandler.bind(@))
@emit('start')
callback?()
_stop: (callback) ->
# Kill will return false if process has already died
handler = =>
@process = null
@emit('stop')
callback?(arguments...)
spawn('pkill', ['-TERM', '-P', @process.pid], @options).on('exit', handler).on('error', handler)
_update: (callback) ->
tasks = [
# Stop the application if running
(callback) =>
if @process
@_stop(callback)
else
callback()
# Fetch new commits
(callback) =>
spawn('git', ['fetch'], @options).on('exit', callback).on('error', callback)
# Reset our master branch to origin/master
(callback) =>
spawn('git', ['reset', '--hard', 'origin/master'], @options).on('exit', callback).on('error', callback)
# Save the new commit hash
(callback) =>
options = _.clone(@options)
delete options.stdio
ps = spawn('git', ['rev-parse', 'HEAD'], options).on('close', callback).on('error', callback)
# The hash will always be on the first chunk as I/O buffers are always larger than 40 bytes
ps.stdout.on('data', (hash) ->
hash = '' + hash
state.set('gitHash', hash.trim())
)
# Prune npm dependencies
(callback) =>
spawn('npm', ['prune'], @options).on('exit', callback).on('error', callback)
# Install npm dependencies
(callback) =>
spawn('npm', ['install'], @options).on('exit', callback).on('error', callback)
# Start the app
(callback) =>
@_start(callback)
]
@emit('pre-update')
async.series(tasks, =>
@emit('post-update')
callback?(arguments...)
)
# These methods shouldn't be called in parallel, queue them if they conflict
['start', 'stop', 'init', 'update'].forEach((method) ->
Application::[method] = (callback) ->
if @inprogress
@queue.push([method, arguments])
else
@inprogress = true
@['_' + method](=>
@inprogress = false
if @queue.length isnt 0
[next, args] = @queue.shift()
@[next](args...)
callback?(arguments...)
)
return deferred.promise
).then(->
docker.runAsync(app.imageId, ['/bin/bash', '-c', '/start web'], process.stdout, true)
)
module.exports = Application