mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-02-21 10:01:55 +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",
|
||||
"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": {
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -42,3 +42,5 @@ export class InvalidAppIdError extends TypedError {
|
||||
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> {
|
||||
[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