From a0f9219cb3bfa3d284213849636ee840a59575ed Mon Sep 17 00:00:00 2001 From: Page Date: Fri, 6 Jun 2014 22:46:23 +0100 Subject: [PATCH] Initial version of self-updates, adds an "/v1/update-supervisor" endpoint that can be posted to in order to trigger a supervisor update, which it then fetches as the resin/rpi-supervisor repo of the registry endpoint. --- package.json | 2 +- src/api.coffee | 6 +++ src/supervisor-update.coffee | 71 ++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 src/supervisor-update.coffee diff --git a/package.json b/package.json index ec37b733..a38a6b74 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "express": "~3.2.6", "lodash": "~2.4.1", "csr-gen": "~0.2.1", - "dockerode": "~0.2.5", + "dockerode": "~2.0.0", "knex": "~0.5.1", "bluebird": "~1.1.1", "JSONStream": "~0.7.1", diff --git a/src/api.coffee b/src/api.coffee index 93126c6e..51b279c9 100644 --- a/src/api.coffee +++ b/src/api.coffee @@ -3,6 +3,7 @@ fs = Promise.promisifyAll require 'fs' utils = require './utils' express = require 'express' application = require './application' +supervisor = require './supervisor-update' api = express() @@ -25,4 +26,9 @@ api.post '/v1/update', (req, res) -> application.update() res.send(204) +api.post '/v1/update-supervisor', (req, res) -> + console.log('Got supervisor update') + supervisor.update() + res.send(204) + module.exports = api diff --git a/src/supervisor-update.coffee b/src/supervisor-update.coffee new file mode 100644 index 00000000..1506ff94 --- /dev/null +++ b/src/supervisor-update.coffee @@ -0,0 +1,71 @@ +es = require 'event-stream' +Docker = require 'dockerode' +Promise = require 'bluebird' +JSONStream = require 'JSONStream' +config = require './config' + +DOCKER_SOCKET = '/run/docker.sock' + +docker = Promise.promisifyAll(new Docker(socketPath: DOCKER_SOCKET)) +# Hack dockerode to promisify internal classes' prototypes +Promise.promisifyAll(docker.getImage().__proto__) +Promise.promisifyAll(docker.getContainer().__proto__) + +supervisorTag = 'resin/rpi-supervisor' +updateImage = config.REGISTRY_ENDPOINT + '/' + supervisorTag + +supervisorUpdating = Promise.resolve() +exports.update = -> + # Make sure only one attempt to update the full supervisor is running at a time, ignoring any errors from previous update attempts + supervisorUpdating = supervisorUpdating.catch(->).then -> + console.log('Fetching updated supervisor:', updateImage) + docker.createImageAsync(fromImage: updateImage) + .then (stream) -> + deferred = Promise.defer() + + if stream.headers['content-type'] is 'application/json' + stream.pipe(JSONStream.parse('error')) + .pipe es.mapSync (error) -> + deferred.reject(error) + else + stream.pipe es.wait (error, text) -> deferred.reject(text) + + stream.on 'end', -> deferred.resolve() + + return deferred.promise + .then -> + console.log('Tagging updated supervisor:', updateImage) + docker.getImage(updateImage).tagAsync( + repo: supervisorTag + force: true + ) + .then -> + console.log('Creating updated supervisor container:', supervisorTag) + docker.createContainerAsync( + Image: supervisorTag + Cmd: ['/start'] + Volumes: + '/boot/config.json': '/mnt/mmcblk0p1/config.json', + '/data': '/var/lib/docker/data', + '/run/docker.sock': '/var/run/docker.sock' + Env: [ + 'API_ENDPOINT=' + config.API_ENDPOINT + 'REGISTRY_ENDPOINT=' + config.REGISTRY_ENDPOINT + ] + ) + .then (container) -> + console.log('Starting updated supervisor container:', supervisorTag) + container.startAsync( + Privileged: true + Binds: [ + '/mnt/mmcblk0p1/config.json:/boot/config.json' + '/var/run/docker.sock:/run/docker.sock' + '/var/lib/docker/data:/data' + ] + ) + .then -> + # We've started the new container, so we're done here! #pray + process.exit() + .catch (err) -> + console.error('Error updating supervisor:', err) + throw err