mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2024-12-19 13:47:54 +00:00
Add more typescript conversions and export utilities from existing
Change-type: patch Signed-off-by: Cameron Diver <cameron@balena.io>
This commit is contained in:
parent
4522d1ad86
commit
20a83e8e0a
@ -9,7 +9,7 @@ import { PinejsClientRequest, StatusError } from 'pinejs-client-request';
|
||||
import * as deviceRegister from 'resin-register-device';
|
||||
import * as url from 'url';
|
||||
|
||||
import Config from './config';
|
||||
import Config, { ConfigType } from './config';
|
||||
import Database from './db';
|
||||
import DeviceConfig from './device-config';
|
||||
import { EventTracker } from './event-tracker';
|
||||
@ -25,10 +25,10 @@ import { request, requestOpts } from './lib/request';
|
||||
import { writeLock } from './lib/update-lock';
|
||||
import { DeviceApplicationState } from './types/state';
|
||||
|
||||
import { SchemaReturn as ConfigSchemaType } from './config/schema-type';
|
||||
|
||||
import log from './lib/supervisor-console';
|
||||
|
||||
import DeviceState = require('./device-state');
|
||||
|
||||
const REPORT_SUCCESS_DELAY = 1000;
|
||||
const MAX_REPORT_RETRY_DELAY = 60000;
|
||||
|
||||
@ -42,11 +42,7 @@ export interface APIBinderConstructOpts {
|
||||
config: Config;
|
||||
// FIXME: Remove this
|
||||
db: Database;
|
||||
// TODO: Typings
|
||||
deviceState: {
|
||||
deviceConfig: DeviceConfig;
|
||||
[key: string]: any;
|
||||
};
|
||||
deviceState: DeviceState;
|
||||
eventTracker: EventTracker;
|
||||
}
|
||||
|
||||
@ -67,7 +63,7 @@ interface DeviceTag {
|
||||
value: string;
|
||||
}
|
||||
|
||||
type KeyExchangeOpts = ConfigSchemaType<'provisioningOptions'>;
|
||||
type KeyExchangeOpts = ConfigType<'provisioningOptions'>;
|
||||
|
||||
export class APIBinder {
|
||||
public router: express.Router;
|
||||
@ -79,7 +75,7 @@ export class APIBinder {
|
||||
};
|
||||
private eventTracker: EventTracker;
|
||||
|
||||
private balenaApi: PinejsClientRequest | null = null;
|
||||
public balenaApi: PinejsClientRequest | null = null;
|
||||
private cachedBalenaApi: PinejsClientRequest | null = null;
|
||||
private lastReportedState: DeviceApplicationState = {
|
||||
local: {},
|
||||
@ -972,3 +968,5 @@ export class APIBinder {
|
||||
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 images: Images;
|
||||
|
||||
public proxyvisor: any;
|
||||
|
||||
public getCurrentApp(appId: number): Bluebird<Application | null>;
|
||||
|
||||
// TODO: This actually returns an object, but we don't need the values just yet
|
||||
|
@ -23,7 +23,7 @@ import {
|
||||
|
||||
interface ConfigOpts {
|
||||
db: DB;
|
||||
configPath: string;
|
||||
configPath?: string;
|
||||
}
|
||||
|
||||
type ConfigMap<T extends SchemaTypeKey> = { [key in T]: SchemaReturn<key> };
|
||||
@ -31,6 +31,10 @@ type ConfigChangeMap<T extends SchemaTypeKey> = {
|
||||
[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 {
|
||||
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 { ConfigType } from './config';
|
||||
import log from './lib/supervisor-console';
|
||||
import supervisorVersion = require('./lib/supervisor-version');
|
||||
|
||||
export type EventTrackProperties = Dictionary<any>;
|
||||
|
||||
interface InitArgs {
|
||||
uuid: string;
|
||||
unmanaged: boolean;
|
||||
mixpanelHost: { host: string; path: string } | null;
|
||||
mixpanelToken: string;
|
||||
uuid: ConfigType<'uuid'>;
|
||||
unmanaged: ConfigType<'unmanaged'>;
|
||||
mixpanelHost: ConfigType<'mixpanelHost'>;
|
||||
mixpanelToken: ConfigType<'mixpanelToken'>;
|
||||
}
|
||||
|
||||
// The minimum amount of time to wait between sending
|
||||
@ -112,3 +113,5 @@ export class EventTracker {
|
||||
return _.merge({}, properties, this.defaultProperties);
|
||||
}
|
||||
}
|
||||
|
||||
export default EventTracker;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as Bluebird from 'bluebird';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { ConfigType } from './config';
|
||||
import DB from './db';
|
||||
import { EventTracker } from './event-tracker';
|
||||
import Docker from './lib/docker-utils';
|
||||
@ -18,12 +19,13 @@ import LogMonitor from './logging/monitor';
|
||||
import log from './lib/supervisor-console';
|
||||
|
||||
interface LoggerSetupOptions {
|
||||
apiEndpoint: string;
|
||||
uuid: string;
|
||||
deviceApiKey: string;
|
||||
unmanaged: boolean;
|
||||
apiEndpoint: ConfigType<'apiEndpoint'>;
|
||||
uuid: ConfigType<'uuid'>;
|
||||
deviceApiKey: ConfigType<'deviceApiKey'>;
|
||||
unmanaged: ConfigType<'unmanaged'>;
|
||||
localMode: ConfigType<'localMode'>;
|
||||
|
||||
enableLogs: boolean;
|
||||
localMode: boolean;
|
||||
}
|
||||
|
||||
type LogEventObject = Dictionary<any> | null;
|
||||
@ -58,7 +60,16 @@ export class Logger {
|
||||
enableLogs,
|
||||
localMode,
|
||||
}: LoggerSetupOptions) {
|
||||
this.balenaBackend = new BalenaLogBackend(apiEndpoint, uuid, deviceApiKey);
|
||||
this.balenaBackend = new BalenaLogBackend(
|
||||
apiEndpoint,
|
||||
// This is definitely not correct, but this is indeed
|
||||
// what has been happening before the conversion of
|
||||
// `supervisor.coffee` to typescript. We need to fix
|
||||
// the wider problem of the backend attempting to
|
||||
// communicate with the api without being provisioned
|
||||
uuid || '',
|
||||
deviceApiKey,
|
||||
);
|
||||
this.localBackend = new LocalLogBackend();
|
||||
|
||||
this.backend = localMode ? this.localBackend : this.balenaBackend;
|
||||
|
@ -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
|
114
src/supervisor.ts
Normal file
114
src/supervisor.ts
Normal file
@ -0,0 +1,114 @@
|
||||
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, ...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;
|
@ -69,7 +69,7 @@ module.exports = function(env) {
|
||||
return {
|
||||
mode: env == null || !env.noOptimize ? 'production' : 'development',
|
||||
devtool: 'none',
|
||||
entry: './src/app.coffee',
|
||||
entry: './src/app.ts',
|
||||
output: {
|
||||
filename: 'app.js',
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
|
Loading…
Reference in New Issue
Block a user