Merge pull request #849 from balena-io/typescript

More typescript conversions
This commit is contained in:
CameronDiver 2018-12-19 14:26:13 +00:00 committed by GitHub
commit fccd66c773
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 521 additions and 231 deletions

57
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "balena-supervisor",
"version": "8.6.11",
"version": "9.0.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -74,6 +74,12 @@
"@types/node": "*"
}
},
"@types/caseless": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.1.tgz",
"integrity": "sha512-FhlMa34NHp9K5MY1Uz8yb+ZvuX0pnvn3jScRSNAb75KHGB8d3rEU6hqMs3Z2vjuytcMfRg6c5CHMc3wtYyD2/A==",
"dev": true
},
"@types/common-tags": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@types/common-tags/-/common-tags-1.8.0.tgz",
@ -141,6 +147,15 @@
"@types/range-parser": "*"
}
},
"@types/form-data": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz",
"integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/glob": {
"version": "5.0.36",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-5.0.36.tgz",
@ -174,6 +189,12 @@
"@types/node": "*"
}
},
"@types/lockfile": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/lockfile/-/lockfile-1.0.0.tgz",
"integrity": "sha512-pD6JuijPmrfi84qF3/TzGQ7zi0QIX+d7ZdetD6jUA6cp+IsCzAquXZfi5viesew+pfpOTIdAVKuh1SHA7KeKzg==",
"dev": true
},
"@types/lodash": {
"version": "4.14.119",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.119.tgz",
@ -240,6 +261,18 @@
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==",
"dev": true
},
"@types/request": {
"version": "2.48.1",
"resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.1.tgz",
"integrity": "sha512-ZgEZ1TiD+KGA9LiAAPPJL68Id2UWfeSO62ijSXZjFJArVV+2pKcsVHmrcu+1oiE3q6eDGiFiSolRc4JHoerBBg==",
"dev": true,
"requires": {
"@types/caseless": "*",
"@types/form-data": "*",
"@types/node": "*",
"@types/tough-cookie": "*"
}
},
"@types/rwlock": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@types/rwlock/-/rwlock-5.0.2.tgz",
@ -268,6 +301,12 @@
"integrity": "sha512-BFonQx849sYB2YOJZBUEfbWdaJcqRb6+ASvgUBtcmg2JRTjBaV2Wgn0SD0gWNIZ+rd7KPysPCjLUOUXnBDUlBg==",
"dev": true
},
"@types/tough-cookie": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.4.tgz",
"integrity": "sha512-Set5ZdrAaKI/qHdFlVMgm/GsAv/wkXhSTuZFkJ+JI7HK+wIkIlOaUXSXieIvJ0+OvGIqtREFoE+NHJtEq0gtEw==",
"dev": true
},
"JSONStream": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz",
@ -390,7 +429,7 @@
},
"ansi-escapes": {
"version": "1.4.0",
"resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz",
"integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=",
"dev": true
},
@ -1072,7 +1111,7 @@
"dependencies": {
"lru-cache": {
"version": "3.2.0",
"resolved": "http://registry.npmjs.org/lru-cache/-/lru-cache-3.2.0.tgz",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-3.2.0.tgz",
"integrity": "sha1-cXibO39Tmb7IVl3aOKow0qCX7+4=",
"dev": true,
"requires": {
@ -1647,7 +1686,7 @@
},
"resolve": {
"version": "0.6.3",
"resolved": "http://registry.npmjs.org/resolve/-/resolve-0.6.3.tgz",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-0.6.3.tgz",
"integrity": "sha1-3ZV5gufnNt699TtYpN2RdUV13UY=",
"dev": true
},
@ -6599,7 +6638,7 @@
},
"path-browserify": {
"version": "0.0.0",
"resolved": "http://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz",
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz",
"integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=",
"dev": true
},
@ -7417,7 +7456,7 @@
},
"resolve": {
"version": "1.1.7",
"resolved": "http://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
"integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=",
"dev": true
},
@ -8085,7 +8124,7 @@
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "http://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
},
@ -8663,7 +8702,7 @@
},
"tty-browserify": {
"version": "0.0.0",
"resolved": "http://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
"resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
"integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
"dev": true
},
@ -9014,7 +9053,7 @@
},
"vm-browserify": {
"version": "0.0.4",
"resolved": "http://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz",
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz",
"integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=",
"dev": true,
"requires": {

View File

@ -34,10 +34,12 @@
"@types/event-stream": "^3.3.34",
"@types/express": "^4.11.1",
"@types/knex": "^0.14.14",
"@types/lockfile": "^1.0.0",
"@types/lodash": "^4.14.109",
"@types/memoizee": "^0.4.2",
"@types/mz": "0.0.32",
"@types/node": "^10.3.1",
"@types/request": "^2.48.1",
"@types/rwlock": "^5.0.2",
"@types/shell-quote": "^1.6.0",
"blinking": "~0.0.2",

View File

@ -17,7 +17,7 @@ validation = require './lib/validation'
systemd = require './lib/systemd'
updateLock = require './lib/update-lock'
{ singleToMulticontainerApp } = require './lib/migration'
{ ENOENT, EISDIR, NotFoundError } = require './lib/errors'
{ ENOENT, EISDIR, NotFoundError, UpdatesLockedError } = require './lib/errors'
DeviceConfig = require './device-config'
ApplicationManager = require './application-manager'
@ -60,7 +60,7 @@ createDeviceStateRouter = (deviceState) ->
.then (response) ->
res.status(202).json(response)
.catch (err) ->
if err instanceof updateLock.UpdatesLockedError
if err instanceof UpdatesLockedError
status = 423
else
status = 500

View File

@ -42,3 +42,5 @@ export class InvalidAppIdError extends TypedError {
super(`Invalid appId: ${appId}`);
}
}
export class UpdatesLockedError extends TypedError {}

View File

@ -1,53 +0,0 @@
exports.defaultLegacyVolume = -> 'resin-data'
exports.singleToMulticontainerApp = (app) ->
environment = {}
for key of app.env
if !/^RESIN_/.test(key)
environment[key] = app.env[key]
appId = app.appId
conf = app.config ? {}
newApp = {
appId: appId
commit: app.commit
name: app.name
releaseId: 1
networks: {}
volumes: {}
}
defaultVolume = exports.defaultLegacyVolume()
newApp.volumes[defaultVolume] = {}
updateStrategy = conf['RESIN_SUPERVISOR_UPDATE_STRATEGY'] ? 'download-then-kill'
handoverTimeout = conf['RESIN_SUPERVISOR_HANDOVER_TIMEOUT'] ? ''
restartPolicy = conf['RESIN_APP_RESTART_POLICY'] ? 'always'
newApp.services = {
'1': {
appId: appId
serviceName: 'main'
imageId: 1
commit: app.commit
releaseId: 1
image: app.imageId
privileged: true
networkMode: 'host'
volumes: [
"#{defaultVolume}:/data"
],
labels: {
'io.resin.features.kernel-modules': '1'
'io.resin.features.firmware': '1'
'io.resin.features.dbus': '1',
'io.resin.features.supervisor-api': '1'
'io.resin.features.resin-api': '1'
'io.resin.update.strategy': updateStrategy
'io.resin.update.handover-timeout': handoverTimeout
'io.resin.legacy-container': '1'
},
environment: environment
restart: restartPolicy
running: true
}
}
return newApp

69
src/lib/migration.ts Normal file
View File

@ -0,0 +1,69 @@
import * as _ from 'lodash';
import { Application } from '../types/application';
export const defaultLegacyVolume = () => 'resin-data';
export function singleToMulticontainerApp(app: Dictionary<any>): Application {
const environment: Dictionary<string> = {};
for (const key in app.env) {
if (!/^RESIN_/.test(key)) {
environment[key] = app.env[key];
}
}
const { appId } = app;
const conf = app.config != null ? app.config : {};
const newApp = new Application();
_.assign(newApp, {
appId,
commit: app.commit,
name: app.name,
releaseId: 1,
networks: {},
volumes: {},
});
const defaultVolume = exports.defaultLegacyVolume();
newApp.volumes[defaultVolume] = {};
const updateStrategy =
conf['RESIN_SUPERVISOR_UPDATE_STRATEGY'] != null
? conf['RESIN_SUPERVISOR_UPDATE_STRATEGY']
: 'download-then-kill';
const handoverTimeout =
conf['RESIN_SUPERVISOR_HANDOVER_TIMEOUT'] != null
? conf['RESIN_SUPERVISOR_HANDOVER_TIMEOUT']
: '';
const restartPolicy =
conf['RESIN_APP_RESTART_POLICY'] != null
? conf['RESIN_APP_RESTART_POLICY']
: 'always';
newApp.services = {
// Disable the next line, as this *has* to be a string
// tslint:disable-next-line
'1': {
appId,
serviceName: 'main',
imageId: 1,
commit: app.commit,
releaseId: 1,
image: app.imageId,
privileged: true,
networkMode: 'host',
volumes: [`${defaultVolume}:/data`],
labels: {
'io.resin.features.kernel-modules': '1',
'io.resin.features.firmware': '1',
'io.resin.features.dbus': '1',
'io.resin.features.supervisor-api': '1',
'io.resin.features.resin-api': '1',
'io.resin.update.strategy': updateStrategy,
'io.resin.update.handover-timeout': handoverTimeout,
'io.resin.legacy-container': '1',
},
environment,
restart: restartPolicy,
running: true,
},
};
return newApp;
}

View File

@ -1,40 +0,0 @@
Promise = require 'bluebird'
request = require 'request'
resumable = require 'resumable-request'
constants = require './constants'
osRelease = require './os-release'
osVersion = osRelease.getOSVersionSync(constants.hostOSVersionPath)
osVariant = osRelease.getOSVariantSync(constants.hostOSVersionPath)
supervisorVersion = require('./supervisor-version')
userAgent = "Supervisor/#{supervisorVersion}"
if osVersion?
if osVariant?
userAgent += " (Linux; #{osVersion}; #{osVariant})"
else
userAgent += " (Linux; #{osVersion})"
# With these settings, the device must be unable to receive a single byte
# from the network for a continuous period of 20 minutes before we give up.
# (reqTimeout + retryInterval) * retryCount / 1000ms / 60sec ~> minutes
DEFAULT_REQUEST_TIMEOUT = 30000 # ms
DEFAULT_REQUEST_RETRY_INTERVAL = 10000 # ms
DEFAULT_REQUEST_RETRY_COUNT = 30
exports.requestOpts =
gzip: true
timeout: DEFAULT_REQUEST_TIMEOUT
headers:
'User-Agent': userAgent
resumableOpts =
timeout: DEFAULT_REQUEST_TIMEOUT
maxRetries: DEFAULT_REQUEST_RETRY_COUNT
retryInterval: DEFAULT_REQUEST_RETRY_INTERVAL
request = request.defaults(exports.requestOpts)
exports.request = Promise.promisifyAll(request, multiArgs: true)
exports.resumable = resumable.defaults(resumableOpts)

48
src/lib/request.ts Normal file
View File

@ -0,0 +1,48 @@
import * as Bluebird from 'bluebird';
import * as requestLib from 'request';
import * as resumableRequestLib from 'resumable-request';
import * as constants from './constants';
import * as osRelease from './os-release';
import supervisorVersion = require('./supervisor-version');
const osVersion = osRelease.getOSVersionSync(constants.hostOSVersionPath);
const osVariant = osRelease.getOSVariantSync(constants.hostOSVersionPath);
let userAgent = `Supervisor/${supervisorVersion}`;
if (osVersion != null) {
if (osVariant != null) {
userAgent += ` (Linux; ${osVersion}; ${osVariant})`;
} else {
userAgent += ` (Linux; ${osVersion})`;
}
}
// With these settings, the device must be unable to receive a single byte
// from the network for a continuous period of 20 minutes before we give up.
// (reqTimeout + retryInterval) * retryCount / 1000ms / 60sec ~> minutes
const DEFAULT_REQUEST_TIMEOUT = 30000; // ms
const DEFAULT_REQUEST_RETRY_INTERVAL = 10000; // ms
const DEFAULT_REQUEST_RETRY_COUNT = 30;
export const requestOpts = {
gzip: true,
timeout: DEFAULT_REQUEST_TIMEOUT,
headers: {
'User-Agent': userAgent,
},
};
const resumableOpts = {
timeout: DEFAULT_REQUEST_TIMEOUT,
maxRetries: DEFAULT_REQUEST_RETRY_COUNT,
retryInterval: DEFAULT_REQUEST_RETRY_INTERVAL,
};
const requestHandle = requestLib.defaults(exports.requestOpts);
export const request = Bluebird.promisifyAll(requestHandle, {
multiArgs: true,
});
export const resumable = resumableRequestLib.defaults(resumableOpts);

View File

@ -1,55 +0,0 @@
dbus = require 'dbus-native'
Promise = require 'bluebird'
bus = Promise.promisifyAll(dbus.systemBus())
systemdManagerMethodCall = (method, signature = '', body = []) ->
bus.invokeAsync({
path: '/org/freedesktop/systemd1'
destination: 'org.freedesktop.systemd1'
interface: 'org.freedesktop.systemd1.Manager'
member: method
signature
body
})
exports.restartService = (serviceName) ->
systemdManagerMethodCall('RestartUnit', 'ss', [ "#{serviceName}.service", 'fail' ])
exports.startService = (serviceName) ->
systemdManagerMethodCall('StartUnit', 'ss', [ "#{serviceName}.service", 'fail' ])
exports.stopService = (serviceName) ->
systemdManagerMethodCall('StopUnit', 'ss', [ "#{serviceName}.service", 'fail' ])
exports.enableService = (serviceName) ->
systemdManagerMethodCall('EnableUnitFiles', 'asbb', [ [ "#{serviceName}.service" ], false, false ])
exports.disableService = (serviceName) ->
systemdManagerMethodCall('DisableUnitFiles', 'asb', [ [ "#{serviceName}.service" ], false ])
exports.reboot = Promise.method ->
setTimeout( ->
systemdManagerMethodCall('Reboot')
, 1000)
exports.shutdown = Promise.method ->
setTimeout( ->
systemdManagerMethodCall('PowerOff')
, 1000)
getUnitProperty = (unitName, property) ->
systemdManagerMethodCall('GetUnit', 's', [ unitName ])
.then (unitPath) ->
bus.invokeAsync({
path: unitPath
destination: 'org.freedesktop.systemd1'
interface: 'org.freedesktop.DBus.Properties'
member: 'Get'
signature: 'ss'
body: [ 'org.freedesktop.systemd1.Unit', property ]
})
.get(1).get(0)
exports.serviceActiveState = (serviceName) ->
getUnitProperty("#{serviceName}.service", 'ActiveState')

90
src/lib/systemd.ts Normal file
View File

@ -0,0 +1,90 @@
import * as Bluebird from 'bluebird';
import * as dbus from 'dbus-native';
const bus = dbus.systemBus();
const invokeAsync = Bluebird.promisify(bus.invoke).bind(bus);
const systemdManagerMethodCall = (
method: string,
signature?: string,
body?: any[],
) => {
if (signature == null) {
signature = '';
}
if (body == null) {
body = [];
}
return invokeAsync({
path: '/org/freedesktop/systemd1',
destination: 'org.freedesktop.systemd1',
interface: 'org.freedesktop.systemd1.Manager',
member: method,
signature,
body,
});
};
export function restartService(serviceName: string) {
return systemdManagerMethodCall('RestartUnit', 'ss', [
`${serviceName}.service`,
'fail',
]);
}
export function startService(serviceName: string) {
return systemdManagerMethodCall('StartUnit', 'ss', [
`${serviceName}.service`,
'fail',
]);
}
export function stopService(serviceName: string) {
return systemdManagerMethodCall('StopUnit', 'ss', [
`${serviceName}.service`,
'fail',
]);
}
export function enableService(serviceName: string) {
return systemdManagerMethodCall('EnableUnitFiles', 'asbb', [
[`${serviceName}.service`],
false,
false,
]);
}
export function disableService(serviceName: string) {
return systemdManagerMethodCall('DisableUnitFiles', 'asb', [
[`${serviceName}.service`],
false,
]);
}
export const reboot = Bluebird.method(() =>
setTimeout(() => systemdManagerMethodCall('Reboot'), 1000),
);
export const shutdown = Bluebird.method(() =>
setTimeout(() => systemdManagerMethodCall('PowerOff'), 1000),
);
function getUnitProperty(unitName: string, property: string) {
return systemdManagerMethodCall('GetUnit', 's', [unitName])
.then((unitPath: string) =>
invokeAsync({
path: unitPath,
destination: 'org.freedesktop.systemd1',
interface: 'org.freedesktop.DBus.Properties',
member: 'Get',
signature: 'ss',
body: ['org.freedesktop.systemd1.Unit', property],
}),
)
.get(1)
.get(0);
}
export function serviceActiveState(serviceName: string) {
return getUnitProperty(`${serviceName}.service`, 'ActiveState');
}

View File

@ -1,62 +0,0 @@
Promise = require 'bluebird'
_ = require 'lodash'
TypedError = require 'typed-error'
lockFile = Promise.promisifyAll(require('lockfile'))
Lock = require 'rwlock'
fs = Promise.promisifyAll(require('fs'))
path = require 'path'
constants = require './constants'
{ ENOENT } = require './errors'
baseLockPath = (appId) ->
return path.join('/tmp/balena-supervisor/services', appId.toString())
exports.lockPath = (appId, serviceName) ->
return path.join(baseLockPath(appId), serviceName)
lockFilesOnHost = (appId, serviceName) ->
return _.map [ 'updates.lock', 'resin-updates.lock' ], (fileName) ->
path.join(constants.rootMountPoint, exports.lockPath(appId, serviceName), fileName)
exports.UpdatesLockedError = class UpdatesLockedError extends TypedError
locksTaken = {}
# Try to clean up any existing locks when the program exits
process.on 'exit', ->
for lockName of locksTaken
try
lockFile.unlockSync(lockName)
exports.lock = do ->
_lock = new Lock()
_writeLock = Promise.promisify(_lock.async.writeLock)
return (appId, { force = false } = {}, fn) ->
takeTheLock = ->
Promise.try ->
return if !appId?
dispose = (release) ->
Promise.map _.keys(locksTaken), (lockName) ->
delete locksTaken[lockName]
lockFile.unlockAsync(lockName)
.finally(release)
_writeLock(appId)
.tap (release) ->
theLockDir = path.join(constants.rootMountPoint, baseLockPath(appId))
fs.readdirAsync(theLockDir)
.catchReturn(ENOENT, [])
.mapSeries (serviceName) ->
Promise.mapSeries lockFilesOnHost(appId, serviceName), (tmpLockName) ->
Promise.try ->
lockFile.unlockAsync(tmpLockName) if force == true
.then ->
lockFile.lockAsync(tmpLockName)
.then ->
locksTaken[tmpLockName] = true
.catchReturn(ENOENT, null)
.catch (err) ->
dispose(release)
.throw(new exports.UpdatesLockedError("Updates are locked: #{err.message}"))
.disposer(dispose)
Promise.using takeTheLock(), -> fn()

View File

@ -1,10 +0,0 @@
import TypedError = require('typed-error');
export interface LockCallback {
(appId: number, opts: { force: boolean }, fn: () => void): Promise<void>;
}
export class UpdatesLockedError extends TypedError {}
export function lock(): LockCallback;
export function lockPath(appId: number, serviceName: string): string;

105
src/lib/update-lock.ts Normal file
View File

@ -0,0 +1,105 @@
import * as Bluebird from 'bluebird';
import * as lockFileLib from 'lockfile';
import * as _ from 'lodash';
import { fs } from 'mz';
import * as path from 'path';
import * as Lock from 'rwlock';
import constants = require('./constants');
import { ENOENT, UpdatesLockedError } from './errors';
type asyncLockFile = typeof lockFileLib & {
unlockAsync(path: string): Bluebird<void>;
lockAsync(path: string): Bluebird<void>;
};
const lockFile = Bluebird.promisifyAll(lockFileLib) as asyncLockFile;
export type LockCallback = (
appId: number,
opts: { force: boolean },
fn: () => PromiseLike<void>,
) => Bluebird<void>;
function baseLockPath(appId: number): string {
return path.join('/tmp/balena-supervisor/services', appId.toString());
}
export function lockPath(appId: number, serviceName: string): string {
return path.join(baseLockPath(appId), serviceName);
}
function lockFilesOnHost(appId: number, serviceName: string): string[] {
return ['updates.lock', 'resin-updates.lock'].map(filename =>
path.join(constants.rootMountPoint, lockPath(appId, serviceName), filename),
);
}
const locksTaken: { [lockName: string]: boolean } = {};
// Try to clean up any existing locks when the program exits
process.on('exit', () => {
for (const lockName of _.keys(locksTaken)) {
try {
lockFile.unlockSync(lockName);
} catch (e) {
// Ignore unlocking errors
}
}
});
const locker = new Lock();
const writeLock = Bluebird.promisify(locker.async.writeLock).bind(locker);
function dispose(release: () => void): Bluebird<void> {
return Bluebird.map(_.keys(locksTaken), lockName => {
delete locksTaken[lockName];
return lockFile.unlockAsync(lockName);
})
.finally(release)
.return();
}
export function lock(
appId: number | null,
{ force = false }: { force: boolean },
fn: () => PromiseLike<void>,
): Bluebird<void> {
const takeTheLock = () => {
if (appId == null) {
return;
}
return writeLock(appId)
.tap((release: () => void) => {
const lockDir = path.join(
constants.rootMountPoint,
baseLockPath(appId),
);
return Bluebird.resolve(fs.readdir(lockDir))
.catchReturn(ENOENT, [])
.mapSeries(serviceName => {
return Bluebird.mapSeries(
lockFilesOnHost(appId, serviceName),
tmpLockName => {
return Bluebird.try(() => {
if (force) {
return lockFile.unlockAsync(tmpLockName);
}
})
.then(() => lockFile.lockAsync(tmpLockName))
.then(() => {
locksTaken[tmpLockName] = true;
})
.catchReturn(ENOENT, undefined);
},
).catch(err => {
return dispose(release).throw(
new UpdatesLockedError(`Updates are locked: ${err.message}`),
);
});
});
})
.disposer(dispose);
};
return Bluebird.using(takeTheLock(), fn);
}

12
src/types/application.ts Normal file
View File

@ -0,0 +1,12 @@
import { ServiceComposeConfig } from '../compose/types/service';
export class Application {
public appId: number;
public commit: string;
public name: string;
public releaseId: number;
public networks: Dictionary<any>;
public volumes: Dictionary<any>;
public services: Dictionary<ServiceComposeConfig>;
}

134
typings/dbus-native.d.ts vendored Normal file
View File

@ -0,0 +1,134 @@
// Adapted from: https://gist.github.com/TianyiLi/a231afa2f804d8fa0805baa4830f9242
declare module 'dbus-native' {
import * as net from 'net';
import * as events from 'events';
interface msg {
destination?: string;
path?: string;
interface?: any;
member: string;
signature?: any;
body?: Array<any>;
}
interface MessageBus {
connection: any;
serial: number;
cookies: Object;
methodCallHandlers: Object;
signals: events.EventEmitter;
exportedObjects: Object;
invoke(msg: msg, callback?: Callback<any>): void;
invokeDbus(msg: msg, callback?: Callback<any>): void;
mangle(path: any, iface: any, member: any): string;
mangle(obj: { path: any; iface: any; member: any }): string;
sendSignal(
path: any,
iface: any,
name: any,
signature: any,
args: any,
): void;
sendError(msg: any, signature: any, body: any): void;
setMethodCallHandler(
objectPath: any,
iface: any,
member: any,
handler: any,
): void;
exportInterface(
obj: Object,
path: string,
ifaceDesc: {
name: string;
signals: Object;
method: Object;
properties: Object;
},
): void;
getService(serviceName: string): DBusService;
getObject(path: string, name: string, callback: Callback<any>): DBusService;
getInterface(
path: string,
objname: string,
name: string,
callback: Callback<any>,
): DBusService;
addMatch(match: string, callback?: Callback<any>): void;
removeMatch(match: string, callback?: Callback<any>): void;
getId(callback?: Callback<any>): void;
requestName(name: string, flags: any, callback?: Callback<any>): void;
releaseName(name: string, callback?: Callback<any>): void;
listNames(callback?: Callback<any>): void;
listActivatableNames(callback?: Callback<any>): void;
updateActivationEnvironment(env: any, callback?: Callback<any>): void;
startServiceByName(name: any, flags: any, callback?: Callback<any>): void;
getConnectionUnixUser(name: any, callback?: Callback<any>): void;
getConnectionUnixProcessId(name: any, callback?: Callback<any>): void;
getNameOwner(name: any, callback?: Callback<any>): void;
nameHasOwner(name: any, callback?: Callback<any>): void;
}
/**
* This Should Not Used
*
* TODO: Fix this
*
* @interface DBusService
*/
interface DBusService {
name: string;
bus: MessageBus;
getObject(name: any, callback: Callback<any>): void;
getInterface(
objName: string,
ifaceName: string,
callback: Callback<any>,
): void;
}
interface Server {
server: net.Server;
listen: void;
}
const messageType: {
error: number;
invalid: number;
methodCall: number;
methodReturn: number;
signal: number;
};
enum flags {
noReplyExpected = 1,
noAutoStart,
}
interface StreamOptions {
socket: string;
host: any;
port: any;
busAddress: string;
}
class CreateConnection extends events.EventEmitter {
message(msg: { path: string }): void;
end(): void;
}
function createClient(options?: StreamOptions): MessageBus;
function createConnection(opts?: StreamOptions): CreateConnection;
/**
* Default is /var/run/dbus/system_bus_socket
*
* @export
* @returns {MessageBus}
*/
function systemBus(): MessageBus;
function sessionBus(options?: StreamOptions): MessageBus;
function createServer(): Server;
}

4
typings/global.d.ts vendored
View File

@ -1,3 +1,7 @@
interface Dictionary<T> {
[key: string]: T;
}
interface Callback<T> {
(err?: Error, res?: T): void;
}

5
typings/resumable-request.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
declare module 'resumable-request' {
import * as request from 'request';
// Not technically correct, but they do share an interface
export = request;
}