From 7aedd7062dc93e5a92544bbe485e228cc732f6f6 Mon Sep 17 00:00:00 2001
From: Pablo Carranza Velez <pablo@resin.io>
Date: Tue, 25 Jul 2017 17:38:42 -0300
Subject: [PATCH] Update docker-delta to 1.1.1, docker-toolbelt to 3.0.1,
 docker-progress to 2.6.0 to add support for deltas and overlay2

This makes the Async suffix for docker functions unnecessary. It also allows us to remove dockerode as an
explicit dependency.

Change-Type: minor
Signed-off-by: Pablo Carranza Velez <pablo@resin.io>
---
 package.json            |  7 +++----
 src/application.coffee  | 23 +++++++++++------------
 src/docker-utils.coffee | 38 +++++++++++++++++++++-----------------
 src/lib/logger.coffee   | 41 +++++++++++++++++------------------------
 src/proxyvisor.coffee   |  2 +-
 5 files changed, 53 insertions(+), 58 deletions(-)

diff --git a/package.json b/package.json
index cd5dfe93..2e8ca6f8 100644
--- a/package.json
+++ b/package.json
@@ -29,10 +29,9 @@
     "buffer-equal-constant-time": "^1.0.1",
     "coffee-loader": "^0.7.3",
     "coffee-script": "~1.11.0",
-    "docker-delta": "1.0.3",
-    "docker-progress": "^2.5.0",
-    "docker-toolbelt": "^1.3.0",
-    "dockerode": "~2.2.9",
+    "docker-delta": "1.1.1",
+    "docker-progress": "^2.6.0",
+    "docker-toolbelt": "^3.0.1",
     "event-stream": "^3.0.20",
     "express": "^4.0.0",
     "knex": "~0.12.3",
diff --git a/src/application.coffee b/src/application.coffee
index 74aeba08..6d77344e 100644
--- a/src/application.coffee
+++ b/src/application.coffee
@@ -156,9 +156,9 @@ application.kill = kill = (app, { updateDB = true, removeContainer = true } = {}
 	logSystemEvent(logTypes.stopApp, app)
 	device.updateState(status: 'Stopping')
 	container = docker.getContainer(app.containerId)
-	container.stopAsync(t: 10)
+	container.stop(t: 10)
 	.then ->
-		container.removeAsync(v: true) if removeContainer
+		container.remove(v: true) if removeContainer
 		return
 	# Bluebird throws OperationalError for errors resulting in the normal execution of a promisified function.
 	.catch Promise.OperationalError, (err) ->
@@ -168,7 +168,7 @@ application.kill = kill = (app, { updateDB = true, removeContainer = true } = {}
 		# 304 means the container was already stopped - so we can just remove it
 		if statusCode is '304'
 			logSystemEvent(logTypes.stopAppNoop, app)
-			container.removeAsync(v: true) if removeContainer
+			container.remove(v: true) if removeContainer
 			return
 		# 404 means the container doesn't exist, precisely what we want! :D
 		if statusCode is '404'
@@ -192,7 +192,7 @@ application.kill = kill = (app, { updateDB = true, removeContainer = true } = {}
 
 application.deleteImage = deleteImage = (app) ->
 	logSystemEvent(logTypes.deleteImageForApp, app)
-	docker.getImage(app.imageId).removeAsync(force: true)
+	docker.getImage(app.imageId).remove(force: true)
 	.then ->
 		logSystemEvent(logTypes.deleteImageForAppSuccess, app)
 	.catch ImageNotFoundError, (err) ->
@@ -209,7 +209,7 @@ fetch = (app, setDeviceUpdateState = true) ->
 	onProgress = (progress) ->
 		device.updateState(download_progress: progress.percentage)
 
-	docker.getImage(app.imageId).inspectAsync()
+	docker.getImage(app.imageId).inspect()
 	.catch (error) ->
 		device.updateState(status: 'Downloading', download_progress: 0)
 
@@ -228,7 +228,7 @@ fetch = (app, setDeviceUpdateState = true) ->
 			logSystemEvent(logTypes.downloadAppSuccess, app)
 			device.updateState(status: 'Idle', download_progress: null)
 			device.setUpdateState(update_downloaded: true) if setDeviceUpdateState
-			docker.getImage(app.imageId).inspectAsync()
+			docker.getImage(app.imageId).inspect()
 		.catch (err) ->
 			logSystemEvent(logTypes.downloadAppError, app, err)
 			throw err
@@ -268,7 +268,7 @@ application.start = start = (app) ->
 			if app.containerId?
 				# If we have a container id then check it exists and if so use it.
 				container = docker.getContainer(app.containerId)
-				containerPromise = container.inspectAsync().return(container)
+				containerPromise = container.inspect().return(container)
 			else
 				containerPromise = Promise.rejected()
 
@@ -295,7 +295,7 @@ application.start = start = (app) ->
 					shouldMountKmod(app.imageId)
 					.then (shouldMount) ->
 						binds.push('/bin/kmod:/bin/kmod:ro') if shouldMount
-						docker.createContainerAsync(
+						docker.createContainer(
 							Image: app.imageId
 							Cmd: cmd
 							Tty: true
@@ -317,7 +317,7 @@ application.start = start = (app) ->
 			.tap (container) ->
 				logSystemEvent(logTypes.startApp, app)
 				device.updateState(status: 'Starting')
-				container.startAsync()
+				container.start()
 				.catch (err) ->
 					statusCode = '' + err.statusCode
 					# 304 means the container was already started, precisely what we want :)
@@ -335,7 +335,7 @@ application.start = start = (app) ->
 						throw err
 				.catch (err) ->
 					# If starting the container failed, we remove it so that it doesn't litter
-					container.removeAsync(v: true)
+					container.remove(v: true)
 					.then ->
 						app.containerId = null
 						knex('app').update(app).where(appId: app.appId)
@@ -827,7 +827,7 @@ application.update = update = (force, scheduled = false) ->
 listenToEvents = do ->
 	appHasDied = {}
 	return ->
-		docker.getEventsAsync()
+		docker.getEvents()
 		.then (stream) ->
 			stream.on 'error', (err) ->
 				console.error('Error on docker events stream:', err, err.stack)
@@ -870,7 +870,6 @@ application.initialize = ->
 
 module.exports = (logsChannel, offlineMode) ->
 	logger.init(
-		dockerSocket: config.dockerSocket
 		pubnub: config.pubnub
 		channel: "device-#{logsChannel}-logs"
 		offlineMode: offlineMode
diff --git a/src/docker-utils.coffee b/src/docker-utils.coffee
index 749736e3..938c3a91 100644
--- a/src/docker-utils.coffee
+++ b/src/docker-utils.coffee
@@ -1,9 +1,12 @@
+config = require './config'
+process.env.DOCKER_HOST ?= "unix://#{config.dockerSocket}"
+
 Docker = require 'docker-toolbelt'
 { DockerProgress } = require 'docker-progress'
 Promise = require 'bluebird'
 progress = require 'request-progress'
 dockerDelta = require 'docker-delta'
-config = require './config'
+
 _ = require 'lodash'
 knex = require './db'
 { request } = require './request'
@@ -11,14 +14,12 @@ Lock = require 'rwlock'
 utils = require './utils'
 rimraf = Promise.promisify(require('rimraf'))
 
-docker = new Docker(socketPath: config.dockerSocket)
-
-exports.docker = docker
-dockerProgress = new DockerProgress(socketPath: config.dockerSocket)
+exports.docker = docker = new Docker()
+dockerProgress = new DockerProgress(dockerToolbelt: docker)
 
 # Create an array of (repoTag, image_id, created) tuples like the output of `docker images`
-listRepoTagsAsync = ->
-	docker.listImagesAsync()
+listRepoTags = ->
+	docker.listImages()
 	.then (images) ->
 		images = _.orderBy(images, 'Created', [ false ])
 		ret = []
@@ -32,7 +33,7 @@ listRepoTagsAsync = ->
 findSimilarImage = (repoTag) ->
 	application = repoTag.split('/')[1]
 
-	listRepoTagsAsync()
+	listRepoTags()
 	.then (repoTags) ->
 		# Find the most recent image of the same application
 		for repoTag in repoTags
@@ -46,8 +47,11 @@ findSimilarImage = (repoTag) ->
 getRepoAndTag = (image) ->
 	docker.getRegistryAndName(image)
 	.then ({ registry, imageName, tagName }) ->
-		registry = registry.toString().replace(':443', '')
-		return { repo: "#{registry}/#{imageName}", tag: tagName }
+		if registry? and registry != 'docker.io'
+			registry = registry.toString().replace(':443', '') + '/'
+		else
+			registry = ''
+		return { repo: "#{registry}#{imageName}", tag: tagName }
 
 do ->
 	_lock = new Lock()
@@ -116,7 +120,7 @@ do ->
 			.then (id) ->
 				getRepoAndTag(imgDest)
 				.then ({ repo, tag }) ->
-					docker.getImage(id).tagAsync({ repo, tag, force: true })
+					docker.getImage(id).tag({ repo, tag, force: true })
 			.catch dockerDelta.OutOfSyncError, (err) ->
 				console.log('Falling back to delta-from-empty')
 				exports.rsyncImageWithProgress(imgDest, { requestTimeout, totalTimeout, uuid, apiKey, startFromEmpty: true }, onProgress)
@@ -149,7 +153,7 @@ do ->
 				.map ({ imageId }) ->
 					normalizeRepoTag(imageId)
 				supervisorTagPromise
-				docker.listImagesAsync()
+				docker.listImages()
 				.map (image) ->
 					image.NormalizedRepoTags = Promise.map(image.RepoTags, normalizeRepoTag)
 					Promise.props(image)
@@ -172,7 +176,7 @@ do ->
 			)
 			.then ({ images, supervisorTags, appTags, extraTags }) ->
 				# Cleanup containers first, so that they don't block image removal.
-				docker.listContainersAsync(all: true)
+				docker.listContainers(all: true)
 				.filter (containerInfo) ->
 					# Do not remove user apps.
 					normalizeRepoTag(containerInfo.Image)
@@ -185,7 +189,7 @@ do ->
 							return true
 						return containerHasExited(containerInfo.Id)
 				.map (containerInfo) ->
-					docker.getContainer(containerInfo.Id).removeAsync(v: true, force: true)
+					docker.getContainer(containerInfo.Id).remove(v: true, force: true)
 					.then ->
 						console.log('Deleted container:', containerInfo.Id, containerInfo.Image)
 					.catch(_.noop)
@@ -195,13 +199,13 @@ do ->
 							return _.includes(appTags, tag) or _.includes(supervisorTags, tag) or _.includes(extraTags, tag)
 					Promise.map imagesToClean, (image) ->
 						Promise.map image.RepoTags.concat(image.Id), (tag) ->
-							docker.getImage(tag).removeAsync(force: true)
+							docker.getImage(tag).remove(force: true)
 							.then ->
 								console.log('Deleted image:', tag, image.Id, image.RepoTags)
 							.catch(_.noop)
 
 	containerHasExited = (id) ->
-		docker.getContainer(id).inspectAsync()
+		docker.getContainer(id).inspect()
 		.then (data) ->
 			return not data.State.Running
 
@@ -217,7 +221,7 @@ do ->
 		return repoTag
 
 	exports.getImageEnv = (id) ->
-		docker.getImage(id).inspectAsync()
+		docker.getImage(id).inspect()
 		.get('Config').get('Env')
 		.then (env) ->
 			# env is an array of strings that say 'key=value'
diff --git a/src/lib/logger.coffee b/src/lib/logger.coffee
index ac947e59..e7e4b7cb 100644
--- a/src/lib/logger.coffee
+++ b/src/lib/logger.coffee
@@ -1,9 +1,10 @@
 _ = require 'lodash'
-Docker = require 'dockerode'
+Docker = require 'docker-toolbelt'
 PUBNUB = require 'pubnub'
 Promise = require 'bluebird'
 es = require 'event-stream'
 Lock = require 'rwlock'
+{ docker } = require '../docker-utils'
 
 LOG_PUBLISH_INTERVAL = 110
 
@@ -19,13 +20,6 @@ initialised = new Promise (resolve) ->
 	exports.init = (config) ->
 		resolve(config)
 
-dockerPromise = initialised.then (config) ->
-	docker = Promise.promisifyAll(new Docker(socketPath: config.dockerSocket))
-	# Hack dockerode to promisify internal classes' prototypes
-	Promise.promisifyAll(docker.getImage().constructor.prototype)
-	Promise.promisifyAll(docker.getContainer().constructor.prototype)
-	return docker
-
 # Queue up any calls to publish logs whilst we wait to be initialised.
 publish = do ->
 	publishQueue = [[]]
@@ -95,19 +89,18 @@ do ->
 	exports.attach = (app) ->
 		Promise.using loggerLock(app.containerId), ->
 			if !attached[app.containerId]
-				dockerPromise.then (docker) ->
-					docker.getContainer(app.containerId)
-					.logsAsync({ follow: true, stdout: true, stderr: true, timestamps: true })
-					.then (stream) ->
-						attached[app.containerId] = true
-						stream.pipe(es.split())
-						.on 'data', (logLine) ->
-							space = logLine.indexOf(' ')
-							if space > 0
-								msg = { t: logLine.substr(0, space), m: logLine.substr(space + 1) }
-								publish(msg)
-						.on 'error', (err) ->
-							console.error('Error on container logs', err, err.stack)
-							attached[app.containerId] = false
-						.on 'end', ->
-							attached[app.containerId] = false
+				docker.getContainer(app.containerId)
+				.logs({ follow: true, stdout: true, stderr: true, timestamps: true })
+				.then (stream) ->
+					attached[app.containerId] = true
+					stream.pipe(es.split())
+					.on 'data', (logLine) ->
+						space = logLine.indexOf(' ')
+						if space > 0
+							msg = { t: logLine.substr(0, space), m: logLine.substr(space + 1) }
+							publish(msg)
+					.on 'error', (err) ->
+						console.error('Error on container logs', err, err.stack)
+						attached[app.containerId] = false
+					.on 'end', ->
+						attached[app.containerId] = false
diff --git a/src/proxyvisor.coffee b/src/proxyvisor.coffee
index f720fdb1..19706c4b 100644
--- a/src/proxyvisor.coffee
+++ b/src/proxyvisor.coffee
@@ -248,7 +248,7 @@ exports.fetchAndSetTargetsForDependentApps = (state, fetchFn, apiKey) ->
 			Promise.map toBeRemoved, (app) ->
 				fs.unlinkAsync(tarPath(app))
 				.then ->
-					docker.getImage(app.imageId).removeAsync()
+					docker.getImage(app.imageId).remove()
 				.catch (err) ->
 					console.error('Could not remove image/artifacts for dependent app', err, err.stack)
 		.then ->