mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-06-11 12:01:40 +00:00
Merge pull request #849 from balena-io/typescript
More typescript conversions
This commit is contained in:
commit
fccd66c773
57
package-lock.json
generated
57
package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "balena-supervisor",
|
"name": "balena-supervisor",
|
||||||
"version": "8.6.11",
|
"version": "9.0.2",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -74,6 +74,12 @@
|
|||||||
"@types/node": "*"
|
"@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": {
|
"@types/common-tags": {
|
||||||
"version": "1.8.0",
|
"version": "1.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/common-tags/-/common-tags-1.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/common-tags/-/common-tags-1.8.0.tgz",
|
||||||
@ -141,6 +147,15 @@
|
|||||||
"@types/range-parser": "*"
|
"@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": {
|
"@types/glob": {
|
||||||
"version": "5.0.36",
|
"version": "5.0.36",
|
||||||
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-5.0.36.tgz",
|
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-5.0.36.tgz",
|
||||||
@ -174,6 +189,12 @@
|
|||||||
"@types/node": "*"
|
"@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": {
|
"@types/lodash": {
|
||||||
"version": "4.14.119",
|
"version": "4.14.119",
|
||||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.119.tgz",
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.119.tgz",
|
||||||
@ -240,6 +261,18 @@
|
|||||||
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==",
|
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==",
|
||||||
"dev": true
|
"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": {
|
"@types/rwlock": {
|
||||||
"version": "5.0.2",
|
"version": "5.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/rwlock/-/rwlock-5.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/rwlock/-/rwlock-5.0.2.tgz",
|
||||||
@ -268,6 +301,12 @@
|
|||||||
"integrity": "sha512-BFonQx849sYB2YOJZBUEfbWdaJcqRb6+ASvgUBtcmg2JRTjBaV2Wgn0SD0gWNIZ+rd7KPysPCjLUOUXnBDUlBg==",
|
"integrity": "sha512-BFonQx849sYB2YOJZBUEfbWdaJcqRb6+ASvgUBtcmg2JRTjBaV2Wgn0SD0gWNIZ+rd7KPysPCjLUOUXnBDUlBg==",
|
||||||
"dev": true
|
"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": {
|
"JSONStream": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz",
|
||||||
@ -390,7 +429,7 @@
|
|||||||
},
|
},
|
||||||
"ansi-escapes": {
|
"ansi-escapes": {
|
||||||
"version": "1.4.0",
|
"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=",
|
"integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
@ -1072,7 +1111,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lru-cache": {
|
"lru-cache": {
|
||||||
"version": "3.2.0",
|
"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=",
|
"integrity": "sha1-cXibO39Tmb7IVl3aOKow0qCX7+4=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -1647,7 +1686,7 @@
|
|||||||
},
|
},
|
||||||
"resolve": {
|
"resolve": {
|
||||||
"version": "0.6.3",
|
"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=",
|
"integrity": "sha1-3ZV5gufnNt699TtYpN2RdUV13UY=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
@ -6599,7 +6638,7 @@
|
|||||||
},
|
},
|
||||||
"path-browserify": {
|
"path-browserify": {
|
||||||
"version": "0.0.0",
|
"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=",
|
"integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
@ -7417,7 +7456,7 @@
|
|||||||
},
|
},
|
||||||
"resolve": {
|
"resolve": {
|
||||||
"version": "1.1.7",
|
"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=",
|
"integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
@ -8085,7 +8124,7 @@
|
|||||||
},
|
},
|
||||||
"sprintf-js": {
|
"sprintf-js": {
|
||||||
"version": "1.0.3",
|
"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=",
|
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
@ -8663,7 +8702,7 @@
|
|||||||
},
|
},
|
||||||
"tty-browserify": {
|
"tty-browserify": {
|
||||||
"version": "0.0.0",
|
"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=",
|
"integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
@ -9014,7 +9053,7 @@
|
|||||||
},
|
},
|
||||||
"vm-browserify": {
|
"vm-browserify": {
|
||||||
"version": "0.0.4",
|
"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=",
|
"integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -34,10 +34,12 @@
|
|||||||
"@types/event-stream": "^3.3.34",
|
"@types/event-stream": "^3.3.34",
|
||||||
"@types/express": "^4.11.1",
|
"@types/express": "^4.11.1",
|
||||||
"@types/knex": "^0.14.14",
|
"@types/knex": "^0.14.14",
|
||||||
|
"@types/lockfile": "^1.0.0",
|
||||||
"@types/lodash": "^4.14.109",
|
"@types/lodash": "^4.14.109",
|
||||||
"@types/memoizee": "^0.4.2",
|
"@types/memoizee": "^0.4.2",
|
||||||
"@types/mz": "0.0.32",
|
"@types/mz": "0.0.32",
|
||||||
"@types/node": "^10.3.1",
|
"@types/node": "^10.3.1",
|
||||||
|
"@types/request": "^2.48.1",
|
||||||
"@types/rwlock": "^5.0.2",
|
"@types/rwlock": "^5.0.2",
|
||||||
"@types/shell-quote": "^1.6.0",
|
"@types/shell-quote": "^1.6.0",
|
||||||
"blinking": "~0.0.2",
|
"blinking": "~0.0.2",
|
||||||
|
@ -17,7 +17,7 @@ validation = require './lib/validation'
|
|||||||
systemd = require './lib/systemd'
|
systemd = require './lib/systemd'
|
||||||
updateLock = require './lib/update-lock'
|
updateLock = require './lib/update-lock'
|
||||||
{ singleToMulticontainerApp } = require './lib/migration'
|
{ singleToMulticontainerApp } = require './lib/migration'
|
||||||
{ ENOENT, EISDIR, NotFoundError } = require './lib/errors'
|
{ ENOENT, EISDIR, NotFoundError, UpdatesLockedError } = require './lib/errors'
|
||||||
|
|
||||||
DeviceConfig = require './device-config'
|
DeviceConfig = require './device-config'
|
||||||
ApplicationManager = require './application-manager'
|
ApplicationManager = require './application-manager'
|
||||||
@ -60,7 +60,7 @@ createDeviceStateRouter = (deviceState) ->
|
|||||||
.then (response) ->
|
.then (response) ->
|
||||||
res.status(202).json(response)
|
res.status(202).json(response)
|
||||||
.catch (err) ->
|
.catch (err) ->
|
||||||
if err instanceof updateLock.UpdatesLockedError
|
if err instanceof UpdatesLockedError
|
||||||
status = 423
|
status = 423
|
||||||
else
|
else
|
||||||
status = 500
|
status = 500
|
||||||
|
@ -42,3 +42,5 @@ export class InvalidAppIdError extends TypedError {
|
|||||||
super(`Invalid appId: ${appId}`);
|
super(`Invalid appId: ${appId}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class UpdatesLockedError extends TypedError {}
|
||||||
|
@ -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
69
src/lib/migration.ts
Normal 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;
|
||||||
|
}
|
@ -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
48
src/lib/request.ts
Normal 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);
|
@ -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
90
src/lib/systemd.ts
Normal 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');
|
||||||
|
}
|
@ -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()
|
|
10
src/lib/update-lock.d.ts
vendored
10
src/lib/update-lock.d.ts
vendored
@ -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
105
src/lib/update-lock.ts
Normal 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
12
src/types/application.ts
Normal 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
134
typings/dbus-native.d.ts
vendored
Normal 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
4
typings/global.d.ts
vendored
@ -1,3 +1,7 @@
|
|||||||
interface Dictionary<T> {
|
interface Dictionary<T> {
|
||||||
[key: string]: T;
|
[key: string]: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Callback<T> {
|
||||||
|
(err?: Error, res?: T): void;
|
||||||
|
}
|
||||||
|
5
typings/resumable-request.d.ts
vendored
Normal file
5
typings/resumable-request.d.ts
vendored
Normal 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;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user