mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-02-20 09:46:19 +00:00
Run application when supervisor starts
This commit is contained in:
parent
563df5386d
commit
12b3e194b5
@ -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"
|
||||
|
@ -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)
|
||||
)
|
||||
)
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user