balena-supervisor/application.coffee
2013-07-23 11:20:47 +01:00

136 lines
3.5 KiB
CoffeeScript

{spawn} = require('child_process')
{getpwnam} = require('posix')
async = require('async')
{EventEmitter} = require('events')
_ = require('lodash')
state = require('./state')
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}"
_init: (callback) ->
tasks = [
# Create the directory for the project
(callback) =>
spawn('mkdir', ['-p', @path]).on('exit', callback).on('error', callback)
# Change the owner to the user
(callback) =>
spawn('chown', [@user, @path]).on('exit', callback).on('error', callback)
# Initalize a new empty git repo
(callback) =>
spawn('git', ['init'], @options).on('exit', callback).on('error', callback)
# 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...)
)
_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...)
)
)
module.exports = Application