mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-06-07 01:51:40 +00:00
Merge pull request #1015 from balena-io/1014-delay-logstream
Dont setup a logstream until we're provisioned
This commit is contained in:
commit
e46fec2118
81
package-lock.json
generated
81
package-lock.json
generated
@ -243,9 +243,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/dockerode": {
|
"@types/dockerode": {
|
||||||
"version": "2.5.13",
|
"version": "2.5.20",
|
||||||
"resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-2.5.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-2.5.20.tgz",
|
||||||
"integrity": "sha512-TgSP2nhCZgKOYcuMyuUs1SvLWZCd20z6SczPadLL11iCEEMDiblE23cwIyc1BR7FPpntwT9Z+IcdFNAXUAKmKQ==",
|
"integrity": "sha512-g2eM9q+pur7iZc897K/OSq8sCL7VdVcCPzNkdeTukUokfvgl3TaP+nT7G8BMpnSSojrJFKl7VdTciP7hbVgfKA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
@ -1609,8 +1609,17 @@
|
|||||||
"deep-equal": "^1.0.1",
|
"deep-equal": "^1.0.1",
|
||||||
"dns-equal": "^1.0.0",
|
"dns-equal": "^1.0.0",
|
||||||
"dns-txt": "^2.0.2",
|
"dns-txt": "^2.0.2",
|
||||||
"multicast-dns": "git+https://github.com/resin-io-modules/multicast-dns.git#a15c63464eb43e8925b187ed5cb9de6892e8aacc",
|
|
||||||
"multicast-dns-service-types": "^1.1.0"
|
"multicast-dns-service-types": "^1.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"multicast-dns": {
|
||||||
|
"version": "git+https://github.com/resin-io-modules/multicast-dns.git#a15c63464eb43e8925b187ed5cb9de6892e8aacc",
|
||||||
|
"from": "git+https://github.com/resin-io-modules/multicast-dns.git#a15c63464eb43e8925b187ed5cb9de6892e8aacc",
|
||||||
|
"requires": {
|
||||||
|
"dns-packet": "^1.0.1",
|
||||||
|
"thunky": "^0.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
@ -2794,16 +2803,6 @@
|
|||||||
"integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=",
|
"integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"dns-packet": {
|
|
||||||
"version": "1.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz",
|
|
||||||
"integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"ip": "^1.1.0",
|
|
||||||
"safe-buffer": "^5.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dns-txt": {
|
"dns-txt": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz",
|
||||||
@ -3993,8 +3992,7 @@
|
|||||||
"ansi-regex": {
|
"ansi-regex": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"aproba": {
|
"aproba": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
@ -4015,14 +4013,12 @@
|
|||||||
"balanced-match": {
|
"balanced-match": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"concat-map": "0.0.1"
|
||||||
@ -4037,14 +4033,12 @@
|
|||||||
"code-point-at": {
|
"code-point-at": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"concat-map": {
|
"concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"console-control-strings": {
|
"console-control-strings": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
@ -4166,8 +4160,7 @@
|
|||||||
"inherits": {
|
"inherits": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"ini": {
|
"ini": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
@ -4179,7 +4172,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"number-is-nan": "^1.0.0"
|
"number-is-nan": "^1.0.0"
|
||||||
}
|
}
|
||||||
@ -4194,7 +4186,6 @@
|
|||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
}
|
}
|
||||||
@ -4202,8 +4193,7 @@
|
|||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "0.0.8",
|
"version": "0.0.8",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"minipass": {
|
"minipass": {
|
||||||
"version": "2.3.5",
|
"version": "2.3.5",
|
||||||
@ -4307,8 +4297,7 @@
|
|||||||
"number-is-nan": {
|
"number-is-nan": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"object-assign": {
|
"object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
@ -4460,7 +4449,6 @@
|
|||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-regex": "^2.0.0"
|
"ansi-regex": "^2.0.0"
|
||||||
}
|
}
|
||||||
@ -7319,15 +7307,6 @@
|
|||||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"multicast-dns": {
|
|
||||||
"version": "git+https://github.com/resin-io-modules/multicast-dns.git#a15c63464eb43e8925b187ed5cb9de6892e8aacc",
|
|
||||||
"from": "git+https://github.com/resin-io-modules/multicast-dns.git#listen-on-all-interfaces",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"dns-packet": "^1.0.1",
|
|
||||||
"thunky": "^0.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"multicast-dns-service-types": {
|
"multicast-dns-service-types": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
|
||||||
@ -10074,12 +10053,6 @@
|
|||||||
"xtend": "~4.0.1"
|
"xtend": "~4.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"thunky": {
|
|
||||||
"version": "0.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/thunky/-/thunky-0.1.0.tgz",
|
|
||||||
"integrity": "sha1-vzAUaCTituZ7Dy16Ssi+smkIaE4=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"tildify": {
|
"tildify": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/tildify/-/tildify-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/tildify/-/tildify-1.2.0.tgz",
|
||||||
@ -10810,14 +10783,12 @@
|
|||||||
"balanced-match": {
|
"balanced-match": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"concat-map": "0.0.1"
|
||||||
@ -10837,8 +10808,7 @@
|
|||||||
"concat-map": {
|
"concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"console-control-strings": {
|
"console-control-strings": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
@ -10986,7 +10956,6 @@
|
|||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
}
|
}
|
||||||
@ -10994,8 +10963,7 @@
|
|||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "0.0.8",
|
"version": "0.0.8",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"minipass": {
|
"minipass": {
|
||||||
"version": "2.3.5",
|
"version": "2.3.5",
|
||||||
@ -11099,8 +11067,7 @@
|
|||||||
"number-is-nan": {
|
"number-is-nan": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"object-assign": {
|
"object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
"@types/bluebird": "^3.5.25",
|
"@types/bluebird": "^3.5.25",
|
||||||
"@types/chai": "^4.1.7",
|
"@types/chai": "^4.1.7",
|
||||||
"@types/common-tags": "^1.8.0",
|
"@types/common-tags": "^1.8.0",
|
||||||
"@types/dockerode": "^2.5.13",
|
"@types/dockerode": "^2.5.20",
|
||||||
"@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",
|
||||||
|
@ -9,7 +9,7 @@ import { PinejsClientRequest, StatusError } from 'pinejs-client-request';
|
|||||||
import * as deviceRegister from 'resin-register-device';
|
import * as deviceRegister from 'resin-register-device';
|
||||||
import * as url from 'url';
|
import * as url from 'url';
|
||||||
|
|
||||||
import Config from './config';
|
import Config, { ConfigType } from './config';
|
||||||
import Database from './db';
|
import Database from './db';
|
||||||
import DeviceConfig from './device-config';
|
import DeviceConfig from './device-config';
|
||||||
import { EventTracker } from './event-tracker';
|
import { EventTracker } from './event-tracker';
|
||||||
@ -25,10 +25,10 @@ import { request, requestOpts } from './lib/request';
|
|||||||
import { writeLock } from './lib/update-lock';
|
import { writeLock } from './lib/update-lock';
|
||||||
import { DeviceApplicationState } from './types/state';
|
import { DeviceApplicationState } from './types/state';
|
||||||
|
|
||||||
import { SchemaReturn as ConfigSchemaType } from './config/schema-type';
|
|
||||||
|
|
||||||
import log from './lib/supervisor-console';
|
import log from './lib/supervisor-console';
|
||||||
|
|
||||||
|
import DeviceState = require('./device-state');
|
||||||
|
|
||||||
const REPORT_SUCCESS_DELAY = 1000;
|
const REPORT_SUCCESS_DELAY = 1000;
|
||||||
const MAX_REPORT_RETRY_DELAY = 60000;
|
const MAX_REPORT_RETRY_DELAY = 60000;
|
||||||
|
|
||||||
@ -42,11 +42,7 @@ export interface APIBinderConstructOpts {
|
|||||||
config: Config;
|
config: Config;
|
||||||
// FIXME: Remove this
|
// FIXME: Remove this
|
||||||
db: Database;
|
db: Database;
|
||||||
// TODO: Typings
|
deviceState: DeviceState;
|
||||||
deviceState: {
|
|
||||||
deviceConfig: DeviceConfig;
|
|
||||||
[key: string]: any;
|
|
||||||
};
|
|
||||||
eventTracker: EventTracker;
|
eventTracker: EventTracker;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +63,7 @@ interface DeviceTag {
|
|||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type KeyExchangeOpts = ConfigSchemaType<'provisioningOptions'>;
|
type KeyExchangeOpts = ConfigType<'provisioningOptions'>;
|
||||||
|
|
||||||
export class APIBinder {
|
export class APIBinder {
|
||||||
public router: express.Router;
|
public router: express.Router;
|
||||||
@ -79,7 +75,7 @@ export class APIBinder {
|
|||||||
};
|
};
|
||||||
private eventTracker: EventTracker;
|
private eventTracker: EventTracker;
|
||||||
|
|
||||||
private balenaApi: PinejsClientRequest | null = null;
|
public balenaApi: PinejsClientRequest | null = null;
|
||||||
private cachedBalenaApi: PinejsClientRequest | null = null;
|
private cachedBalenaApi: PinejsClientRequest | null = null;
|
||||||
private lastReportedState: DeviceApplicationState = {
|
private lastReportedState: DeviceApplicationState = {
|
||||||
local: {},
|
local: {},
|
||||||
@ -532,7 +528,9 @@ export class APIBinder {
|
|||||||
// the watchdog to kill the supervisor - and killing the supervisor will
|
// the watchdog to kill the supervisor - and killing the supervisor will
|
||||||
// not help in this situation
|
// not help in this situation
|
||||||
log.error(
|
log.error(
|
||||||
'Non-200 response from the API! Status code: ${e.statusCode} - message:',
|
`Non-200 response from the API! Status code: ${
|
||||||
|
e.statusCode
|
||||||
|
} - message:`,
|
||||||
e,
|
e,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -972,3 +970,5 @@ export class APIBinder {
|
|||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default APIBinder;
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
do ->
|
|
||||||
# Make NodeJS RFC 3484 compliant for properly handling IPv6
|
|
||||||
# See: https://github.com/nodejs/node/pull/14731
|
|
||||||
# https://github.com/nodejs/node/pull/17793
|
|
||||||
dns = require('dns')
|
|
||||||
{ lookup } = dns
|
|
||||||
dns.lookup = (name, opts, cb) ->
|
|
||||||
if typeof cb isnt 'function'
|
|
||||||
return lookup(name, { verbatim: true }, opts)
|
|
||||||
return lookup(name, Object.assign({ verbatim: true }, opts), cb)
|
|
||||||
|
|
||||||
Supervisor = require './supervisor'
|
|
||||||
|
|
||||||
supervisor = new Supervisor()
|
|
||||||
supervisor.init()
|
|
21
src/app.ts
Normal file
21
src/app.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// This was originally wrapped in a do block in
|
||||||
|
// coffeescript, and it's not clear now why that was the
|
||||||
|
// case, so I'm going to maintain that behaviour
|
||||||
|
(() => {
|
||||||
|
// Make NodeJS RFC 3484 compliant for properly handling IPv6
|
||||||
|
// See: https://github.com/nodejs/node/pull/14731
|
||||||
|
// https://github.com/nodejs/node/pull/17793
|
||||||
|
const dns = require('dns');
|
||||||
|
const { lookup } = dns;
|
||||||
|
dns.lookup = (name: string, opts: any, cb: (err?: Error) => void) => {
|
||||||
|
if (typeof cb !== 'function') {
|
||||||
|
return lookup(name, { verbatim: true }, opts);
|
||||||
|
}
|
||||||
|
return lookup(name, Object.assign({ verbatim: true }, opts), cb);
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
import Supervisor from './supervisor';
|
||||||
|
|
||||||
|
const supervisor = new Supervisor();
|
||||||
|
supervisor.init();
|
2
src/application-manager.d.ts
vendored
2
src/application-manager.d.ts
vendored
@ -45,6 +45,8 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
public db: DB;
|
public db: DB;
|
||||||
public images: Images;
|
public images: Images;
|
||||||
|
|
||||||
|
public proxyvisor: any;
|
||||||
|
|
||||||
public getCurrentApp(appId: number): Bluebird<Application | null>;
|
public getCurrentApp(appId: number): Bluebird<Application | null>;
|
||||||
|
|
||||||
// TODO: This actually returns an object, but we don't need the values just yet
|
// TODO: This actually returns an object, but we don't need the values just yet
|
||||||
|
@ -23,14 +23,20 @@ import {
|
|||||||
|
|
||||||
interface ConfigOpts {
|
interface ConfigOpts {
|
||||||
db: DB;
|
db: DB;
|
||||||
configPath: string;
|
configPath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConfigMap<T extends SchemaTypeKey> = { [key in T]: SchemaReturn<key> };
|
export type ConfigMap<T extends SchemaTypeKey> = {
|
||||||
type ConfigChangeMap<T extends SchemaTypeKey> = {
|
[key in T]: SchemaReturn<key>
|
||||||
|
};
|
||||||
|
export type ConfigChangeMap<T extends SchemaTypeKey> = {
|
||||||
[key in T]?: SchemaReturn<key>
|
[key in T]?: SchemaReturn<key>
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Export this type renamed, for storing config keys
|
||||||
|
export type ConfigKey = SchemaTypeKey;
|
||||||
|
export type ConfigType<T extends ConfigKey> = SchemaReturn<T>;
|
||||||
|
|
||||||
interface ConfigEvents {
|
interface ConfigEvents {
|
||||||
change: ConfigChangeMap<SchemaTypeKey>;
|
change: ConfigChangeMap<SchemaTypeKey>;
|
||||||
}
|
}
|
||||||
|
31
src/device-state.d.ts
vendored
Normal file
31
src/device-state.d.ts
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { EventEmitter } from 'events';
|
||||||
|
import { Router } from 'express';
|
||||||
|
|
||||||
|
import ApplicationManager from './application-manager';
|
||||||
|
import Config from './config';
|
||||||
|
import Database from './db';
|
||||||
|
import DeviceConfig from './device-config';
|
||||||
|
import EventTracker from './event-tracker';
|
||||||
|
import Logger from './logger';
|
||||||
|
|
||||||
|
// This is a very incomplete definition of the device state
|
||||||
|
// class, which should be rewritten in typescript soon
|
||||||
|
class DeviceState extends EventEmitter {
|
||||||
|
public applications: ApplicationManager;
|
||||||
|
public router: Router;
|
||||||
|
public deviceConfig: DeviceConfig;
|
||||||
|
|
||||||
|
public constructor(args: {
|
||||||
|
config: Config;
|
||||||
|
db: Database;
|
||||||
|
eventTracker: EventTracker;
|
||||||
|
logger: Logger;
|
||||||
|
});
|
||||||
|
|
||||||
|
public healthcheck(): Promise<void>;
|
||||||
|
public normaliseLegacy(client: PinejsClientRequest): Promise<void>;
|
||||||
|
|
||||||
|
public async init();
|
||||||
|
}
|
||||||
|
|
||||||
|
export = DeviceState;
|
@ -5,16 +5,17 @@ import * as memoizee from 'memoizee';
|
|||||||
|
|
||||||
import Mixpanel = require('mixpanel');
|
import Mixpanel = require('mixpanel');
|
||||||
|
|
||||||
|
import { ConfigType } from './config';
|
||||||
import log from './lib/supervisor-console';
|
import log from './lib/supervisor-console';
|
||||||
import supervisorVersion = require('./lib/supervisor-version');
|
import supervisorVersion = require('./lib/supervisor-version');
|
||||||
|
|
||||||
export type EventTrackProperties = Dictionary<any>;
|
export type EventTrackProperties = Dictionary<any>;
|
||||||
|
|
||||||
interface InitArgs {
|
interface InitArgs {
|
||||||
uuid: string;
|
uuid: ConfigType<'uuid'>;
|
||||||
unmanaged: boolean;
|
unmanaged: ConfigType<'unmanaged'>;
|
||||||
mixpanelHost: { host: string; path: string } | null;
|
mixpanelHost: ConfigType<'mixpanelHost'>;
|
||||||
mixpanelToken: string;
|
mixpanelToken: ConfigType<'mixpanelToken'>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The minimum amount of time to wait between sending
|
// The minimum amount of time to wait between sending
|
||||||
@ -112,3 +113,5 @@ export class EventTracker {
|
|||||||
return _.merge({}, properties, this.defaultProperties);
|
return _.merge({}, properties, this.defaultProperties);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default EventTracker;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import * as Bluebird from 'bluebird';
|
import * as Bluebird from 'bluebird';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import Config, { ConfigChangeMap, ConfigKey, ConfigType } from './config';
|
||||||
import DB from './db';
|
import DB from './db';
|
||||||
import { EventTracker } from './event-tracker';
|
import { EventTracker } from './event-tracker';
|
||||||
import Docker from './lib/docker-utils';
|
import Docker from './lib/docker-utils';
|
||||||
@ -18,12 +19,14 @@ import LogMonitor from './logging/monitor';
|
|||||||
import log from './lib/supervisor-console';
|
import log from './lib/supervisor-console';
|
||||||
|
|
||||||
interface LoggerSetupOptions {
|
interface LoggerSetupOptions {
|
||||||
apiEndpoint: string;
|
apiEndpoint: ConfigType<'apiEndpoint'>;
|
||||||
uuid: string;
|
uuid: ConfigType<'uuid'>;
|
||||||
deviceApiKey: string;
|
deviceApiKey: ConfigType<'deviceApiKey'>;
|
||||||
unmanaged: boolean;
|
unmanaged: ConfigType<'unmanaged'>;
|
||||||
|
localMode: ConfigType<'localMode'>;
|
||||||
|
|
||||||
enableLogs: boolean;
|
enableLogs: boolean;
|
||||||
localMode: boolean;
|
config: Config;
|
||||||
}
|
}
|
||||||
|
|
||||||
type LogEventObject = Dictionary<any> | null;
|
type LogEventObject = Dictionary<any> | null;
|
||||||
@ -57,6 +60,7 @@ export class Logger {
|
|||||||
unmanaged,
|
unmanaged,
|
||||||
enableLogs,
|
enableLogs,
|
||||||
localMode,
|
localMode,
|
||||||
|
config,
|
||||||
}: LoggerSetupOptions) {
|
}: LoggerSetupOptions) {
|
||||||
this.balenaBackend = new BalenaLogBackend(apiEndpoint, uuid, deviceApiKey);
|
this.balenaBackend = new BalenaLogBackend(apiEndpoint, uuid, deviceApiKey);
|
||||||
this.localBackend = new LocalLogBackend();
|
this.localBackend = new LocalLogBackend();
|
||||||
@ -65,6 +69,44 @@ export class Logger {
|
|||||||
|
|
||||||
this.backend.unmanaged = unmanaged;
|
this.backend.unmanaged = unmanaged;
|
||||||
this.backend.publishEnabled = enableLogs;
|
this.backend.publishEnabled = enableLogs;
|
||||||
|
|
||||||
|
// Only setup a config listener if we have to
|
||||||
|
if (!this.balenaBackend.isIntialised()) {
|
||||||
|
const handler = async (values: ConfigChangeMap<ConfigKey>) => {
|
||||||
|
if (
|
||||||
|
'uuid' in values ||
|
||||||
|
'apiEndpoint' in values ||
|
||||||
|
'deviceApiKey' in values
|
||||||
|
) {
|
||||||
|
// If any of the values we're interested in have
|
||||||
|
// changed, retrieve all of the values, check that
|
||||||
|
// they're all set, and provide them to the
|
||||||
|
// balenaBackend
|
||||||
|
|
||||||
|
const conf = await config.getMany([
|
||||||
|
'uuid',
|
||||||
|
'apiEndpoint',
|
||||||
|
'deviceApiKey',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// We use Boolean here, as deviceApiKey when unset
|
||||||
|
// is '' for legacy reasons. Once we're totally
|
||||||
|
// typescript, we can make it have a default value
|
||||||
|
// of undefined.
|
||||||
|
if (_.every(conf, Boolean)) {
|
||||||
|
// Everything is set, provide the values to the
|
||||||
|
// balenaBackend, and remove our listener
|
||||||
|
this.balenaBackend!.assignFields(
|
||||||
|
conf.apiEndpoint,
|
||||||
|
conf.uuid!,
|
||||||
|
conf.deviceApiKey,
|
||||||
|
);
|
||||||
|
config.removeListener('change', handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
config.on('change', handler);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public switchBackend(localMode: boolean) {
|
public switchBackend(localMode: boolean) {
|
||||||
|
@ -31,17 +31,18 @@ export class BalenaLogBackend extends LogBackend {
|
|||||||
private stream: stream.PassThrough;
|
private stream: stream.PassThrough;
|
||||||
private timeout: NodeJS.Timer;
|
private timeout: NodeJS.Timer;
|
||||||
|
|
||||||
public constructor(apiEndpoint: string, uuid: string, deviceApiKey: string) {
|
public initialised = false;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
apiEndpoint: string,
|
||||||
|
uuid: Nullable<string>,
|
||||||
|
deviceApiKey: string,
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.opts = url.parse(`${apiEndpoint}/device/v2/${uuid}/log-stream`) as any;
|
if (uuid != null && deviceApiKey !== '') {
|
||||||
this.opts.method = 'POST';
|
this.assignFields(apiEndpoint, uuid, deviceApiKey);
|
||||||
this.opts.headers = {
|
}
|
||||||
Authorization: `Bearer ${deviceApiKey}`,
|
|
||||||
'Content-Type': 'application/x-ndjson',
|
|
||||||
'Content-Encoding': 'gzip',
|
|
||||||
};
|
|
||||||
|
|
||||||
// This stream serves serves as a message buffer during reconnections
|
// This stream serves serves as a message buffer during reconnections
|
||||||
// while we unpipe the old, malfunctioning connection and then repipe a
|
// while we unpipe the old, malfunctioning connection and then repipe a
|
||||||
// new one.
|
// new one.
|
||||||
@ -71,8 +72,15 @@ export class BalenaLogBackend extends LogBackend {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isIntialised(): boolean {
|
||||||
|
return this.initialised;
|
||||||
|
}
|
||||||
|
|
||||||
public log(message: LogMessage) {
|
public log(message: LogMessage) {
|
||||||
if (this.unmanaged || !this.publishEnabled) {
|
// TODO: Perhaps don't just drop logs when we haven't
|
||||||
|
// yet initialised (this happens when a device has not yet
|
||||||
|
// been provisioned)
|
||||||
|
if (this.unmanaged || !this.publishEnabled || !this.initialised) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,6 +108,18 @@ export class BalenaLogBackend extends LogBackend {
|
|||||||
this.write(message);
|
this.write(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public assignFields(apiEndpoint: string, uuid: string, deviceApiKey: string) {
|
||||||
|
this.opts = url.parse(`${apiEndpoint}/device/v2/${uuid}/log-stream`) as any;
|
||||||
|
this.opts.method = 'POST';
|
||||||
|
this.opts.headers = {
|
||||||
|
Authorization: `Bearer ${deviceApiKey}`,
|
||||||
|
'Content-Type': 'application/x-ndjson',
|
||||||
|
'Content-Encoding': 'gzip',
|
||||||
|
};
|
||||||
|
|
||||||
|
this.initialised = true;
|
||||||
|
}
|
||||||
|
|
||||||
private setup = _.throttle(() => {
|
private setup = _.throttle(() => {
|
||||||
this.req = https.request(this.opts);
|
this.req = https.request(this.opts);
|
||||||
|
|
||||||
|
@ -1,101 +0,0 @@
|
|||||||
EventEmitter = require 'events'
|
|
||||||
|
|
||||||
{ EventTracker } = require './event-tracker'
|
|
||||||
{ DB } = require './db'
|
|
||||||
{ Config } = require './config'
|
|
||||||
{ APIBinder } = require './api-binder'
|
|
||||||
DeviceState = require './device-state'
|
|
||||||
{ SupervisorAPI } = require './supervisor-api'
|
|
||||||
{ Logger } = require './logger'
|
|
||||||
|
|
||||||
version = require './lib/supervisor-version'
|
|
||||||
{ log } = require './lib/supervisor-console'
|
|
||||||
|
|
||||||
constants = require './lib/constants'
|
|
||||||
|
|
||||||
startupConfigFields = [
|
|
||||||
'uuid'
|
|
||||||
'listenPort'
|
|
||||||
'apiEndpoint'
|
|
||||||
'apiSecret'
|
|
||||||
'apiTimeout'
|
|
||||||
'unmanaged'
|
|
||||||
'deviceApiKey'
|
|
||||||
'mixpanelToken'
|
|
||||||
'mixpanelHost'
|
|
||||||
'loggingEnabled'
|
|
||||||
'localMode'
|
|
||||||
'legacyAppsPresent'
|
|
||||||
]
|
|
||||||
|
|
||||||
module.exports = class Supervisor extends EventEmitter
|
|
||||||
constructor: ->
|
|
||||||
@db = new DB()
|
|
||||||
@config = new Config({ @db })
|
|
||||||
@eventTracker = new EventTracker()
|
|
||||||
@logger = new Logger({ @db, @eventTracker })
|
|
||||||
@deviceState = new DeviceState({ @config, @db, @eventTracker, @logger })
|
|
||||||
@apiBinder = new APIBinder({ @config, @db, @deviceState, @eventTracker })
|
|
||||||
|
|
||||||
# FIXME: rearchitect proxyvisor to avoid this circular dependency
|
|
||||||
# by storing current state and having the APIBinder query and report it / provision devices
|
|
||||||
@deviceState.applications.proxyvisor.bindToAPI(@apiBinder)
|
|
||||||
# We could also do without the below dependency, but it's part of a much larger refactor
|
|
||||||
@deviceState.applications.apiBinder = @apiBinder
|
|
||||||
|
|
||||||
@api = new SupervisorAPI({
|
|
||||||
@config,
|
|
||||||
@eventTracker,
|
|
||||||
routers: [
|
|
||||||
@apiBinder.router,
|
|
||||||
@deviceState.router
|
|
||||||
],
|
|
||||||
healthchecks: [
|
|
||||||
@apiBinder.healthcheck.bind(@apiBinder),
|
|
||||||
@deviceState.healthcheck.bind(@deviceState)
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
init: =>
|
|
||||||
|
|
||||||
log.info("Supervisor v#{version} starting up...")
|
|
||||||
|
|
||||||
@db.init()
|
|
||||||
.tap =>
|
|
||||||
@config.init() # Ensures uuid, deviceApiKey, apiSecret
|
|
||||||
.then =>
|
|
||||||
@config.getMany(startupConfigFields)
|
|
||||||
.then (conf) =>
|
|
||||||
# We can't print to the dashboard until the logger has started up,
|
|
||||||
# so we leave a trail of breadcrumbs in the logs in case runtime
|
|
||||||
# fails to get to the first dashboard logs
|
|
||||||
log.debug('Starting event tracker')
|
|
||||||
@eventTracker.init(conf)
|
|
||||||
.then =>
|
|
||||||
log.debug('Starting up api binder')
|
|
||||||
@apiBinder.initClient()
|
|
||||||
.then =>
|
|
||||||
log.debug('Starting logging infrastructure')
|
|
||||||
@logger.init({
|
|
||||||
apiEndpoint: conf.apiEndpoint,
|
|
||||||
uuid: conf.uuid,
|
|
||||||
deviceApiKey: conf.deviceApiKey,
|
|
||||||
unmanaged: conf.unmanaged,
|
|
||||||
enableLogs: conf.loggingEnabled,
|
|
||||||
localMode: conf.localMode
|
|
||||||
})
|
|
||||||
.then =>
|
|
||||||
@logger.logSystemMessage('Supervisor starting', {}, 'Supervisor start')
|
|
||||||
.then =>
|
|
||||||
if conf.legacyAppsPresent
|
|
||||||
log.info('Legacy app detected, running migration')
|
|
||||||
@deviceState.normaliseLegacy(@apiBinder.balenaApi)
|
|
||||||
.then =>
|
|
||||||
@deviceState.init()
|
|
||||||
.then =>
|
|
||||||
# initialize API
|
|
||||||
log.info('Starting API server')
|
|
||||||
@api.listen(constants.allowedInterfaces, conf.listenPort, conf.apiTimeout)
|
|
||||||
@deviceState.on('shutdown', => @api.stop())
|
|
||||||
.then =>
|
|
||||||
@apiBinder.start() # this will first try to provision if it's a new device
|
|
118
src/supervisor.ts
Normal file
118
src/supervisor.ts
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import APIBinder from './api-binder';
|
||||||
|
import Config, { ConfigKey } from './config';
|
||||||
|
import Database from './db';
|
||||||
|
import EventTracker from './event-tracker';
|
||||||
|
import Logger from './logger';
|
||||||
|
import SupervisorAPI from './supervisor-api';
|
||||||
|
|
||||||
|
import DeviceState = require('./device-state');
|
||||||
|
|
||||||
|
import constants = require('./lib/constants');
|
||||||
|
import log from './lib/supervisor-console';
|
||||||
|
import version = require('./lib/supervisor-version');
|
||||||
|
|
||||||
|
const startupConfigFields: ConfigKey[] = [
|
||||||
|
'uuid',
|
||||||
|
'listenPort',
|
||||||
|
'apiEndpoint',
|
||||||
|
'apiSecret',
|
||||||
|
'apiTimeout',
|
||||||
|
'unmanaged',
|
||||||
|
'deviceApiKey',
|
||||||
|
'mixpanelToken',
|
||||||
|
'mixpanelHost',
|
||||||
|
'loggingEnabled',
|
||||||
|
'localMode',
|
||||||
|
'legacyAppsPresent',
|
||||||
|
];
|
||||||
|
|
||||||
|
export class Supervisor {
|
||||||
|
private db: Database;
|
||||||
|
private config: Config;
|
||||||
|
private eventTracker: EventTracker;
|
||||||
|
private logger: Logger;
|
||||||
|
private deviceState: DeviceState;
|
||||||
|
private apiBinder: APIBinder;
|
||||||
|
private api: SupervisorAPI;
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
this.db = new Database();
|
||||||
|
this.config = new Config({ db: this.db });
|
||||||
|
this.eventTracker = new EventTracker();
|
||||||
|
this.logger = new Logger({ db: this.db, eventTracker: this.eventTracker });
|
||||||
|
this.deviceState = new DeviceState({
|
||||||
|
config: this.config,
|
||||||
|
db: this.db,
|
||||||
|
eventTracker: this.eventTracker,
|
||||||
|
logger: this.logger,
|
||||||
|
});
|
||||||
|
this.apiBinder = new APIBinder({
|
||||||
|
config: this.config,
|
||||||
|
db: this.db,
|
||||||
|
deviceState: this.deviceState,
|
||||||
|
eventTracker: this.eventTracker,
|
||||||
|
});
|
||||||
|
|
||||||
|
// FIXME: rearchitect proxyvisor to avoid this circular dependency
|
||||||
|
// by storing current state and having the APIBinder query and report it / provision devices
|
||||||
|
this.deviceState.applications.proxyvisor.bindToAPI(this.apiBinder);
|
||||||
|
// We could also do without the below dependency, but it's part of a much larger refactor
|
||||||
|
this.deviceState.applications.apiBinder = this.apiBinder;
|
||||||
|
|
||||||
|
this.api = new SupervisorAPI({
|
||||||
|
config: this.config,
|
||||||
|
eventTracker: this.eventTracker,
|
||||||
|
routers: [this.apiBinder.router, this.deviceState.router],
|
||||||
|
healthchecks: [
|
||||||
|
this.apiBinder.healthcheck.bind(this.apiBinder),
|
||||||
|
this.deviceState.healthcheck.bind(this.deviceState),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async init() {
|
||||||
|
log.info(`Supervisor v${version} starting up...`);
|
||||||
|
|
||||||
|
await this.db.init();
|
||||||
|
await this.config.init();
|
||||||
|
|
||||||
|
const conf = await this.config.getMany(startupConfigFields);
|
||||||
|
|
||||||
|
// We can't print to the dashboard until the logger
|
||||||
|
// has started up, so we leave a trail of breadcrumbs
|
||||||
|
// in the logs in case runtime fails to get to the
|
||||||
|
// first dashboard logs
|
||||||
|
log.debug('Starting event tracker');
|
||||||
|
await this.eventTracker.init(conf);
|
||||||
|
|
||||||
|
log.debug('Starting api binder');
|
||||||
|
await this.apiBinder.initClient();
|
||||||
|
|
||||||
|
log.debug('Starting logging infrastructure');
|
||||||
|
this.logger.init({
|
||||||
|
enableLogs: conf.loggingEnabled,
|
||||||
|
config: this.config,
|
||||||
|
...conf,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logger.logSystemMessage('Supervisor starting', {}, 'Supervisor start');
|
||||||
|
if (conf.legacyAppsPresent) {
|
||||||
|
log.info('Legacy app detected, running migration');
|
||||||
|
this.deviceState.normaliseLegacy(this.apiBinder.balenaApi);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.deviceState.init();
|
||||||
|
|
||||||
|
log.info('Starting API server');
|
||||||
|
this.api.listen(
|
||||||
|
constants.allowedInterfaces,
|
||||||
|
conf.listenPort,
|
||||||
|
conf.apiTimeout,
|
||||||
|
);
|
||||||
|
this.deviceState.on('shutdown', () => this.api.stop());
|
||||||
|
|
||||||
|
await this.apiBinder.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Supervisor;
|
@ -1,6 +1,6 @@
|
|||||||
{ expect } = require './lib/chai-config'
|
{ expect } = require './lib/chai-config'
|
||||||
|
|
||||||
Supervisor = require '../src/supervisor'
|
{ Supervisor } = require '../src/supervisor'
|
||||||
|
|
||||||
describe 'Startup', ->
|
describe 'Startup', ->
|
||||||
it 'should startup correctly', ->
|
it 'should startup correctly', ->
|
||||||
|
@ -69,7 +69,7 @@ module.exports = function(env) {
|
|||||||
return {
|
return {
|
||||||
mode: env == null || !env.noOptimize ? 'production' : 'development',
|
mode: env == null || !env.noOptimize ? 'production' : 'development',
|
||||||
devtool: 'none',
|
devtool: 'none',
|
||||||
entry: './src/app.coffee',
|
entry: './src/app.ts',
|
||||||
output: {
|
output: {
|
||||||
filename: 'app.js',
|
filename: 'app.js',
|
||||||
path: path.resolve(__dirname, 'dist'),
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user