Make the db module a singleton

We were treating the database class as a singleton, but still having to pass
around the db instance. Now we can simply require the db module and have
access to the database handle.

Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
This commit is contained in:
Cameron Diver 2020-05-28 18:15:33 +01:00
parent 0dc0fc77b6
commit 1d7381327e
32 changed files with 274 additions and 352 deletions

View File

@ -12,7 +12,8 @@
"build": "npm run release && webpack",
"build:debug": "npm run release && npm run packagejson:copy",
"lint": "balena-lint -e ts -e js --typescript src/ test/ typings/ build-utils/ && tsc --noEmit && tsc --noEmit --project tsconfig.js.json",
"test": "npm run lint && npm run test-nolint",
"test": "npm run test-nolint",
"posttest": "npm run lint",
"test-nolint": "npm run test:build && TEST=1 mocha",
"test:build": "npm run test-build && npm run testitems:copy && npm run packagejson:copy",
"test:fast": "TEST=1 mocha --opts test/fast-mocha.opts",

View File

@ -9,7 +9,6 @@ import * as url from 'url';
import * as deviceRegister from './lib/register-device';
import Config, { ConfigType } from './config';
import Database from './db';
import { EventTracker } from './event-tracker';
import { loadBackupFromMigration } from './lib/migration';
@ -17,9 +16,9 @@ import constants = require('./lib/constants');
import {
ContractValidationError,
ContractViolationError,
isHttpConflictError,
ExchangeKeyError,
InternalInconsistencyError,
isHttpConflictError,
} from './lib/errors';
import * as request from './lib/request';
import { writeLock } from './lib/update-lock';
@ -42,8 +41,6 @@ const INTERNAL_STATE_KEYS = [
export interface APIBinderConstructOpts {
config: Config;
// FIXME: Remove this
db: Database;
eventTracker: EventTracker;
logger: Logger;
}

View File

@ -10,7 +10,6 @@ import { DeviceStatus, InstancedAppState } from './types/state';
import ImageManager, { Image } from './compose/images';
import ServiceManager from './compose/service-manager';
import DB from './db';
import DeviceState from './device-state';
import { APIBinder } from './api-binder';
@ -59,7 +58,6 @@ class ApplicationManager extends EventEmitter {
public volumes: VolumeManager;
public networks: NetworkManager;
public config: Config;
public db: DB;
public images: ImageManager;
public proxyvisor: any;
@ -73,7 +71,6 @@ class ApplicationManager extends EventEmitter {
public constructor({
logger: Logger,
config: Config,
db: DB,
eventTracker: EventTracker,
deviceState: DeviceState,
apiBinder: APIBinder,
@ -96,7 +93,7 @@ class ApplicationManager extends EventEmitter {
dependent: any,
source: string,
transaction: Knex.Transaction,
): Bluebird<void>;
): Promise<void>;
public getStatus(): Promise<{
local: DeviceStatus.local.apps;

View File

@ -38,6 +38,8 @@ import { createV1Api } from './device-api/v1';
import { createV2Api } from './device-api/v2';
import { serviceAction } from './device-api/common';
import * as db from './db';
/** @type {Function} */
const readFileAsync = Promise.promisify(fs.readFile);
@ -75,7 +77,7 @@ const createApplicationManagerRouter = function (applications) {
};
export class ApplicationManager extends EventEmitter {
constructor({ logger, config, db, eventTracker, deviceState, apiBinder }) {
constructor({ logger, config, eventTracker, deviceState, apiBinder }) {
super();
this.serviceAction = serviceAction;
@ -167,7 +169,6 @@ export class ApplicationManager extends EventEmitter {
this.reportOptionalContainers = this.reportOptionalContainers.bind(this);
this.logger = logger;
this.config = config;
this.db = db;
this.eventTracker = eventTracker;
this.deviceState = deviceState;
this.apiBinder = apiBinder;
@ -175,7 +176,6 @@ export class ApplicationManager extends EventEmitter {
this.images = new Images({
docker: this.docker,
logger: this.logger,
db: this.db,
config: this.config,
});
this.services = new ServiceManager({
@ -194,7 +194,6 @@ export class ApplicationManager extends EventEmitter {
this.proxyvisor = new Proxyvisor({
config: this.config,
logger: this.logger,
db: this.db,
docker: this.docker,
images: this.images,
applications: this,
@ -203,18 +202,13 @@ export class ApplicationManager extends EventEmitter {
this.config,
this.docker,
this.logger,
this.db,
);
this.timeSpentFetching = 0;
this.fetchesInProgress = 0;
this._targetVolatilePerImageId = {};
this._containerStarted = {};
this.targetStateWrapper = new TargetStateAccessor(
this,
this.config,
this.db,
);
this.targetStateWrapper = new TargetStateAccessor(this, this.config);
this.config.on('change', (changedConfig) => {
if (changedConfig.appUpdatePollInterval) {
@ -1240,7 +1234,7 @@ export class ApplicationManager extends EventEmitter {
if (maybeTrx != null) {
promise = setInTransaction(filteredApps, maybeTrx);
} else {
promise = this.db.transaction(setInTransaction);
promise = db.transaction(setInTransaction);
}
return promise
.then(() => {

View File

@ -5,7 +5,7 @@ import * as _ from 'lodash';
import StrictEventEmitter from 'strict-event-emitter-types';
import Config from '../config';
import Database from '../db';
import * as db from '../db';
import * as constants from '../lib/constants';
import {
DeltaFetchOptions,
@ -29,7 +29,6 @@ type ImageEventEmitter = StrictEventEmitter<EventEmitter, ImageEvents>;
interface ImageConstructOpts {
docker: DockerUtils;
logger: Logger;
db: Database;
config: Config;
}
@ -61,7 +60,6 @@ type NormalisedDockerImage = Docker.ImageInfo & {
export class Images extends (EventEmitter as new () => ImageEventEmitter) {
private docker: DockerUtils;
private logger: Logger;
private db: Database;
public appUpdatePollInterval: number;
@ -78,7 +76,6 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
this.docker = opts.docker;
this.logger = opts.logger;
this.db = opts.db;
}
public async triggerFetch(
@ -123,10 +120,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
await this.markAsSupervised(image);
const img = await this.inspectByName(image.name);
await this.db
.models('image')
.update({ dockerImageId: img.Id })
.where(image);
await db.models('image').update({ dockerImageId: img.Id }).where(image);
onFinish(true);
return null;
@ -150,10 +144,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
id = await this.fetchImage(image, opts, onProgress);
}
await this.db
.models('image')
.update({ dockerImageId: id })
.where(image);
await db.models('image').update({ dockerImageId: id }).where(image);
this.logger.logSystemEvent(LogTypes.downloadImageSuccess, { image });
success = true;
@ -194,7 +185,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
}
public async getByDockerId(id: string): Promise<Image> {
return await this.db.models('image').where({ dockerImageId: id }).first();
return await db.models('image').where({ dockerImageId: id }).first();
}
public async removeByDockerId(id: string): Promise<void> {
@ -218,7 +209,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
newImage.NormalisedRepoTags = await this.getNormalisedTags(image);
return newImage;
}),
this.db.models('image').select(),
db.models('image').select(),
]);
return cb(normalisedImages, dbImages);
}
@ -292,7 +283,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
);
if (id != null) {
await this.db
await db
.models('image')
.update({ dockerImageId: id })
.where(supervisedImage);
@ -307,7 +298,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
);
const ids = _(imagesToRemove).map('id').compact().value();
await this.db.models('image').del().whereIn('id', ids);
await db.models('image').del().whereIn('id', ids);
}
public async getStatus() {
@ -327,7 +318,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
public async update(image: Image): Promise<void> {
const formattedImage = this.format(image);
await this.db
await db
.models('image')
.update(formattedImage)
.where({ name: formattedImage.name });
@ -350,7 +341,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
] = await Promise.all([
this.docker.getRegistryAndName(constants.supervisorImage),
this.docker.getImage(constants.supervisorImage).inspect(),
this.db
db
.models('image')
.select('dockerImageId')
.then((vals) => vals.map((img: Image) => img.dockerImageId)),
@ -417,11 +408,11 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
const digest = imageName.split('@')[1];
let imagesFromDb: Image[];
if (digest != null) {
imagesFromDb = await this.db
imagesFromDb = await db
.models('image')
.where('name', 'like', `%@${digest}`);
} else {
imagesFromDb = await this.db
imagesFromDb = await db
.models('image')
.where({ name: imageName })
.select();
@ -496,7 +487,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
// We first fetch the image from the DB to ensure it exists,
// and get the dockerImageId and any other missing fields
const images = await this.db.models('image').select().where(image);
const images = await db.models('image').select().where(image);
if (images.length === 0) {
removed = false;
@ -510,7 +501,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
await this.docker.getImage(img.name).remove({ force: true });
removed = true;
} else {
const imagesFromDb = await this.db
const imagesFromDb = await db
.models('image')
.where({ dockerImageId: img.dockerImageId })
.select();
@ -556,7 +547,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
this.reportChange(image.imageId);
}
await this.db.models('image').del().where({ id: img.id });
await db.models('image').del().where({ id: img.id });
if (removed) {
this.logger.logSystemEvent(LogTypes.deleteImageSuccess, { image });
@ -565,7 +556,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
private async markAsSupervised(image: Image): Promise<void> {
const formattedImage = this.format(image);
await this.db.upsertModel(
await db.upsertModel(
'image',
formattedImage,
// TODO: Upsert to new values only when they already match? This is likely a bug

View File

@ -15,14 +15,13 @@ import * as FnSchema from './functions';
import * as Schema from './schema';
import { SchemaReturn, SchemaTypeKey, schemaTypes } from './schema-type';
import DB from '../db';
import * as db from '../db';
import {
ConfigurationValidationError,
InternalInconsistencyError,
} from '../lib/errors';
interface ConfigOpts {
db: DB;
configPath?: string;
}
@ -44,12 +43,10 @@ interface ConfigEvents {
type ConfigEventEmitter = StrictEventEmitter<EventEmitter, ConfigEvents>;
export class Config extends (EventEmitter as new () => ConfigEventEmitter) {
private db: DB;
private configJsonBackend: ConfigJsonConfigBackend;
public constructor({ db, configPath }: ConfigOpts) {
public constructor({ configPath }: ConfigOpts = {}) {
super();
this.db = db;
this.configJsonBackend = new ConfigJsonConfigBackend(
Schema.schema,
configPath,
@ -64,13 +61,13 @@ export class Config extends (EventEmitter as new () => ConfigEventEmitter) {
key: T,
trx?: Transaction,
): Bluebird<SchemaReturn<T>> {
const db = trx || this.db.models.bind(this.db);
const $db = trx || db.models.bind(db);
return Bluebird.try(() => {
if (Schema.schema.hasOwnProperty(key)) {
const schemaKey = key as Schema.SchemaKey;
return this.getSchema(schemaKey, db).then((value) => {
return this.getSchema(schemaKey, $db).then((value) => {
if (value == null) {
const defaultValue = schemaTypes[key].default;
if (defaultValue instanceof t.Type) {
@ -159,12 +156,7 @@ export class Config extends (EventEmitter as new () => ConfigEventEmitter) {
const strValue = Config.valueToString(value, key);
if (oldValues[key] !== value) {
await this.db.upsertModel(
'config',
{ key, value: strValue },
{ key },
tx,
);
await db.upsertModel('config', { key, value: strValue }, { key }, tx);
}
});
@ -184,9 +176,7 @@ export class Config extends (EventEmitter as new () => ConfigEventEmitter) {
if (trx != null) {
await setValuesInTransaction(trx);
} else {
await this.db.transaction((tx: Transaction) =>
setValuesInTransaction(tx),
);
await db.transaction((tx: Transaction) => setValuesInTransaction(tx));
}
this.emit('change', keyValues as ConfigMap<SchemaTypeKey>);
}
@ -198,7 +188,7 @@ export class Config extends (EventEmitter as new () => ConfigEventEmitter) {
if (Schema.schema[key].source === 'config.json') {
return this.configJsonBackend.remove(key);
} else if (Schema.schema[key].source === 'db') {
await this.db.models('config').del().where({ key });
await db.models('config').del().where({ key });
} else {
throw new Error(
`Unknown or unsupported config backend: ${Schema.schema[key].source}`,
@ -236,7 +226,7 @@ export class Config extends (EventEmitter as new () => ConfigEventEmitter) {
private async getSchema<T extends Schema.SchemaKey>(
key: T,
db: Transaction,
$db: Transaction,
): Promise<unknown> {
let value: unknown;
switch (Schema.schema[key].source) {
@ -244,7 +234,7 @@ export class Config extends (EventEmitter as new () => ConfigEventEmitter) {
value = await this.configJsonBackend.get(key);
break;
case 'db':
const [conf] = await db('config').select('value').where({ key });
const [conf] = await $db('config').select('value').where({ key });
if (conf != null) {
return conf.value;
}

View File

@ -3,61 +3,50 @@ import * as path from 'path';
import * as constants from './lib/constants';
interface DBOpts {
databasePath?: string;
}
type DBTransactionCallback = (trx: Knex.Transaction) => void;
export type Transaction = Knex.Transaction;
export class DB {
private databasePath: string;
private knex: Knex;
const databasePath = constants.databasePath;
const knex = Knex({
client: 'sqlite3',
connection: {
filename: databasePath,
},
useNullAsDefault: true,
});
public constructor({ databasePath }: DBOpts = {}) {
this.databasePath = databasePath || constants.databasePath;
this.knex = Knex({
client: 'sqlite3',
connection: {
filename: this.databasePath,
},
useNullAsDefault: true,
});
export const initialized = (async () => {
try {
await knex('knex_migrations_lock').update({ is_locked: 0 });
} catch {
/* ignore */
}
return knex.migrate.latest({
directory: path.join(__dirname, 'migrations'),
});
})();
public async init(): Promise<void> {
try {
await this.knex('knex_migrations_lock').update({ is_locked: 0 });
} catch {
/* ignore */
}
return this.knex.migrate.latest({
directory: path.join(__dirname, 'migrations'),
});
}
export function models(modelName: string): Knex.QueryBuilder {
return knex(modelName);
}
public models(modelName: string): Knex.QueryBuilder {
return this.knex(modelName);
}
export async function upsertModel(
modelName: string,
obj: any,
id: Dictionary<unknown>,
trx?: Knex.Transaction,
): Promise<any> {
const k = trx || knex;
public async upsertModel(
modelName: string,
obj: any,
id: Dictionary<unknown>,
trx?: Knex.Transaction,
): Promise<any> {
const knex = trx || this.knex;
const n = await knex(modelName).update(obj).where(id);
if (n === 0) {
return knex(modelName).insert(obj);
}
}
public transaction(cb: DBTransactionCallback): Promise<Knex.Transaction> {
return this.knex.transaction(cb);
const n = await k(modelName).update(obj).where(id);
if (n === 0) {
return k(modelName).insert(obj);
}
}
export default DB;
export function transaction(
cb: DBTransactionCallback,
): Promise<Knex.Transaction> {
return knex.transaction(cb);
}

View File

@ -32,7 +32,7 @@ declare function serviceAction(
declare function safeStateClone(
targetState: InstancedDeviceState,
// Use an any here, because it's not an InstancedDeviceState,
// and it's also not the exact same type as the API serves from
// state endpoint (more details in the function)
): Dictionary<any>;
): // Use an any here, because it's not an InstancedDeviceState,
// and it's also not the exact same type as the API serves from
// state endpoint (more details in the function)
Dictionary<any>;

View File

@ -5,6 +5,7 @@ import * as _ from 'lodash';
import { ApplicationManager } from '../application-manager';
import { Service } from '../compose/service';
import Volume from '../compose/volume';
import * as db from '../db';
import { spawnJournalctl } from '../lib/journald';
import {
appNotFoundMessage,
@ -150,7 +151,7 @@ export function createV2Api(router: Router, applications: ApplicationManager) {
Bluebird.join(
applications.services.getStatus(),
applications.images.getStatus(),
applications.db.models('app').select(['appId', 'commit', 'name']),
db.models('app').select(['appId', 'commit', 'name']),
(
services,
images,

View File

@ -3,7 +3,7 @@ import { inspect } from 'util';
import Config from './config';
import { SchemaTypeKey } from './config/schema-type';
import Database, { Transaction } from './db';
import * as db from './db';
import Logger from './logger';
import { ConfigOptions, DeviceConfigBackend } from './config/backend';
@ -17,7 +17,6 @@ import { DeviceStatus } from './types/state';
const vpnServiceName = 'openvpn';
interface DeviceConfigConstructOpts {
db: Database;
config: Config;
logger: Logger;
}
@ -57,7 +56,6 @@ interface DeviceActionExecutors {
}
export class DeviceConfig {
private db: Database;
private config: Config;
private logger: Logger;
private rebootRequired = false;
@ -150,8 +148,7 @@ export class DeviceConfig {
},
};
public constructor({ db, config, logger }: DeviceConfigConstructOpts) {
this.db = db;
public constructor({ config, logger }: DeviceConfigConstructOpts) {
this.config = config;
this.logger = logger;
@ -233,9 +230,9 @@ export class DeviceConfig {
public async setTarget(
target: Dictionary<string>,
trx?: Transaction,
trx?: db.Transaction,
): Promise<void> {
const db = trx != null ? trx : this.db.models.bind(this.db);
const $db = trx ?? db.models.bind(db);
const formatted = await this.formatConfigKeys(target);
// check for legacy keys
@ -247,13 +244,13 @@ export class DeviceConfig {
targetValues: JSON.stringify(formatted),
};
await db('deviceConfig').update(confToUpdate);
await $db('deviceConfig').update(confToUpdate);
}
public async getTarget({ initial = false }: { initial?: boolean } = {}) {
const [unmanaged, [devConfig]] = await Promise.all([
this.config.get('unmanaged'),
this.db.models('deviceConfig').select('targetValues'),
db.models('deviceConfig').select('targetValues'),
]);
let conf: Dictionary<string>;

View File

@ -8,7 +8,7 @@ import StrictEventEmitter from 'strict-event-emitter-types';
import prettyMs = require('pretty-ms');
import Config, { ConfigType } from './config';
import Database from './db';
import * as db from './db';
import EventTracker from './event-tracker';
import Logger from './logger';
@ -176,7 +176,6 @@ function createDeviceStateRouter(deviceState: DeviceState) {
}
interface DeviceStateConstructOpts {
db: Database;
config: Config;
eventTracker: EventTracker;
logger: Logger;
@ -219,7 +218,6 @@ type DeviceStateStep<T extends PossibleStepTargets> =
| ConfigStep;
export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmitter) {
public db: Database;
public config: Config;
public eventTracker: EventTracker;
public logger: Logger;
@ -246,26 +244,22 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
public router: express.Router;
constructor({
db,
config,
eventTracker,
logger,
apiBinder,
}: DeviceStateConstructOpts) {
super();
this.db = db;
this.config = config;
this.eventTracker = eventTracker;
this.logger = logger;
this.deviceConfig = new DeviceConfig({
db: this.db,
config: this.config,
logger: this.logger,
});
this.applications = new ApplicationManager({
config: this.config,
logger: this.logger,
db: this.db,
eventTracker: this.eventTracker,
deviceState: this,
apiBinder,
@ -463,7 +457,7 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
const apiEndpoint = await this.config.get('apiEndpoint');
await this.usingWriteLockTarget(async () => {
await this.db.transaction(async (trx) => {
await db.transaction(async (trx) => {
await this.config.set({ name: target.local.name }, trx);
await this.deviceConfig.setTarget(target.local.config, trx);

View File

@ -1,6 +1,6 @@
import { endsWith, map } from 'lodash';
import TypedError = require('typed-error');
import { Response } from 'request';
import TypedError = require('typed-error');
import { checkInt } from './validation';

View File

@ -11,7 +11,7 @@ const rimrafAsync = Bluebird.promisify(rimraf);
import { ApplicationManager } from '../application-manager';
import Config from '../config';
import Database, { Transaction } from '../db';
import * as db from '../db';
import DeviceState from '../device-state';
import * as constants from '../lib/constants';
import { BackupError, DatabaseParseError, NotFoundError } from '../lib/errors';
@ -108,7 +108,6 @@ export function convertLegacyAppsJson(appsArray: any[]): AppsJsonFormat {
export async function normaliseLegacyDatabase(
config: Config,
application: ApplicationManager,
db: Database,
balenaApi: PinejsClientRequest,
) {
// When legacy apps are present, we kill their containers and migrate their /data to a named volume
@ -196,7 +195,7 @@ export async function normaliseLegacyDatabase(
.where({ name: service.image })
.select();
await db.transaction(async (trx: Transaction) => {
await db.transaction(async (trx: db.Transaction) => {
try {
if (imagesFromDatabase.length > 0) {
log.debug('Deleting existing image entry in db');

View File

@ -3,7 +3,7 @@ import * as Docker from 'dockerode';
import * as _ from 'lodash';
import Config from './config';
import Database from './db';
import * as db from './db';
import * as constants from './lib/constants';
import { SupervisorContainerNotFoundError } from './lib/errors';
import log from './lib/supervisor-console';
@ -74,7 +74,6 @@ export class LocalModeManager {
public config: Config,
public docker: Docker,
public logger: Logger,
public db: Database,
private containerId: string | undefined = constants.containerId,
) {}
@ -182,7 +181,7 @@ export class LocalModeManager {
}
private async cleanEngineSnapshots() {
await this.db.models('engineSnapshot').delete();
await db.models('engineSnapshot').delete();
}
// Store engine snapshot data in the local database.
@ -192,7 +191,7 @@ export class LocalModeManager {
`Storing engine snapshot in the database. Timestamp: ${timestamp}`,
);
await this.cleanEngineSnapshots();
await this.db.models('engineSnapshot').insert({
await db.models('engineSnapshot').insert({
snapshot: JSON.stringify(record.snapshot),
timestamp,
});
@ -210,7 +209,7 @@ export class LocalModeManager {
// Read the latest stored snapshot from the database.
public async retrieveLatestSnapshot(): Promise<EngineSnapshotRecord | null> {
const r = await this.db
const r = await db
.models('engineSnapshot')
.select()
.orderBy('rowid', 'DESC')
@ -263,7 +262,7 @@ export class LocalModeManager {
});
// Remove any local mode state added to the database.
await this.db
await db
.models('app')
.del()
.where({ source: 'local' })

View File

@ -2,7 +2,7 @@ import * as Bluebird from 'bluebird';
import * as _ from 'lodash';
import Config, { ConfigType } from './config';
import DB from './db';
import * as db from './db';
import { EventTracker } from './event-tracker';
import Docker from './lib/docker-utils';
import { LogType } from './lib/log-types';
@ -33,7 +33,6 @@ interface LoggerSetupOptions {
type LogEventObject = Dictionary<any> | null;
interface LoggerConstructOptions {
db: DB;
eventTracker: EventTracker;
}
@ -43,15 +42,13 @@ export class Logger {
private localBackend: LocalLogBackend | null = null;
private eventTracker: EventTracker;
private db: DB;
private containerLogs: { [containerId: string]: ContainerLogs } = {};
private logMonitor: LogMonitor;
public constructor({ db, eventTracker }: LoggerConstructOptions) {
public constructor({ eventTracker }: LoggerConstructOptions) {
this.backend = null;
this.eventTracker = eventTracker;
this.db = db;
this.logMonitor = new LogMonitor(db);
this.logMonitor = new LogMonitor();
}
public init({
@ -247,7 +244,7 @@ export class Logger {
public async clearOutOfDateDBLogs(containerIds: string[]) {
log.debug('Performing database cleanup for container log timestamps');
await this.db
await db
.models('containerLogs')
.whereNotIn('containerId', containerIds)
.delete();

View File

@ -1,6 +1,6 @@
import * as _ from 'lodash';
import Database from '../db';
import * as db from '../db';
import log from '../lib/supervisor-console';
@ -15,7 +15,7 @@ export class LogMonitor {
private timestamps: { [containerId: string]: number } = {};
private writeRequired: { [containerId: string]: boolean } = {};
public constructor(private db: Database) {
public constructor() {
setInterval(() => this.flushDb(), DB_FLUSH_INTERVAL);
}
@ -35,7 +35,7 @@ export class LogMonitor {
// db to avoid multiple db actions at once
this.timestamps[containerId] = 0;
try {
const timestampObj = await this.db
const timestampObj = await db
.models('containerLogs')
.select('lastSentTimestamp')
.where({ containerId });
@ -43,7 +43,7 @@ export class LogMonitor {
if (timestampObj == null || _.isEmpty(timestampObj)) {
// Create a row in the db so there's something to
// update
await this.db
await db
.models('containerLogs')
.insert({ containerId, lastSentTimestamp: 0 });
} else {
@ -69,7 +69,7 @@ export class LogMonitor {
continue;
}
await this.db
await db
.models('containerLogs')
.where({ containerId })
.update({ lastSentTimestamp: this.timestamps[containerId] });

View File

@ -15,6 +15,7 @@ import * as bodyParser from 'body-parser';
import * as url from 'url';
import { log } from './lib/supervisor-console';
import * as db from './db';
const mkdirpAsync = Promise.promisify(mkdirp);
@ -80,20 +81,18 @@ const formatCurrentAsState = (device) => ({
});
const createProxyvisorRouter = function (proxyvisor) {
const { db } = proxyvisor;
const router = express.Router();
router.use(bodyParser.urlencoded({ limit: '10mb', extended: true }));
router.use(bodyParser.json({ limit: '10mb' }));
router.get('/v1/devices', (_req, res) =>
db
.models('dependentDevice')
.select()
.map(parseDeviceFields)
.then((devices) => res.json(devices))
.catch((err) =>
res.status(503).send(err?.message || err || 'Unknown error'),
),
);
router.get('/v1/devices', async (_req, res) => {
try {
const fields = await db.models('dependentDevice').select();
const devices = fields.map(parseDeviceFields);
res.json(devices);
} catch (err) {
res.status(503).send(err?.message || err || 'Unknown error');
}
});
router.post('/v1/devices', function (req, res) {
let { appId, device_type } = req.body;
@ -297,54 +296,54 @@ const createProxyvisorRouter = function (proxyvisor) {
});
});
router.get('/v1/dependent-apps/:appId/assets/:commit', (req, res) =>
db
.models('dependentApp')
.select()
.where(_.pick(req.params, 'appId', 'commit'))
.then(function ([app]) {
if (!app) {
return res.status(404).send('Not found');
}
const dest = tarPath(app.appId, app.commit);
return fs
.lstat(dest)
.catch(() =>
Promise.using(
proxyvisor.docker.imageRootDirMounted(app.image),
(rootDir) => getTarArchive(rootDir + '/assets', dest),
),
)
.then(() => res.sendFile(dest));
})
.catch(function (err) {
log.error(`Error on ${req.method} ${url.parse(req.url).pathname}`, err);
return res.status(503).send(err?.message || err || 'Unknown error');
}),
);
router.get('/v1/dependent-apps/:appId/assets/:commit', async (req, res) => {
try {
const [app] = await db
.models('dependentApp')
.select()
.where(_.pick(req.params, 'appId', 'commit'));
router.get('/v1/dependent-apps', (req, res) =>
db
.models('dependentApp')
.select()
.map((app) => ({
if (!app) {
return res.status(404).send('Not found');
}
const dest = tarPath(app.appId, app.commit);
try {
await fs.lstat(dest);
} catch {
await Promise.using(
proxyvisor.docker.imageRootDirMounted(app.image),
(rootDir) => getTarArchive(rootDir + '/assets', dest),
);
}
res.sendFile(dest);
} catch (err) {
log.error(`Error on ${req.method} ${url.parse(req.url).pathname}`, err);
return res.status(503).send(err?.message || err || 'Unknown error');
}
});
router.get('/v1/dependent-apps', async (req, res) => {
try {
const apps = await db.models('dependentApp').select();
const $apps = apps.map((app) => ({
id: parseInt(app.appId, 10),
commit: app.commit,
name: app.name,
config: JSON.parse(app.config ?? '{}'),
}))
.then((apps) => res.json(apps))
.catch(function (err) {
log.error(`Error on ${req.method} ${url.parse(req.url).pathname}`, err);
return res.status(503).send(err?.message || err || 'Unknown error');
}),
);
}));
res.json($apps);
} catch (err) {
log.error(`Error on ${req.method} ${url.parse(req.url).pathname}`, err);
return res.status(503).send(err?.message || err || 'Unknown error');
}
});
return router;
};
export class Proxyvisor {
constructor({ config, logger, db, docker, images, applications }) {
constructor({ config, logger, docker, images, applications }) {
this.bindToAPI = this.bindToAPI.bind(this);
this.executeStepAction = this.executeStepAction.bind(this);
this.getCurrentStates = this.getCurrentStates.bind(this);
@ -362,7 +361,6 @@ export class Proxyvisor {
this.sendUpdates = this.sendUpdates.bind(this);
this.config = config;
this.logger = logger;
this.db = db;
this.docker = docker;
this.images = images;
this.applications = applications;
@ -392,7 +390,7 @@ export class Proxyvisor {
device.apps[appId].environment,
);
const targetConfig = JSON.stringify(device.apps[appId].config);
return this.db
return db
.models('dependentDevice')
.update({
appId,
@ -423,14 +421,12 @@ export class Proxyvisor {
targetConfig,
targetEnvironment,
};
return this.db
.models('dependentDevice')
.insert(deviceForDB);
return db.models('dependentDevice').insert(deviceForDB);
});
});
})
.then(() => {
return this.db
return db
.models('dependentDevice')
.where({ appId: step.appId })
.whereNotIn('uuid', _.map(step.devices, 'uuid'))
@ -440,7 +436,7 @@ export class Proxyvisor {
return this.normaliseDependentAppForDB(step.app);
})
.then((appForDB) => {
return this.db.upsertModel('dependentApp', appForDB, {
return db.upsertModel('dependentApp', appForDB, {
appId: step.appId,
});
})
@ -478,7 +474,7 @@ export class Proxyvisor {
removeDependentApp: (step) => {
// find step.app and delete it from the DB
// find devices with step.appId and delete them from the DB
return this.db.transaction((trx) =>
return db.transaction((trx) =>
trx('dependentApp')
.where({ appId: step.appId })
.del()
@ -509,10 +505,10 @@ export class Proxyvisor {
getCurrentStates() {
return Promise.join(
Promise.map(
this.db.models('dependentApp').select(),
db.models('dependentApp').select(),
this.normaliseDependentAppFromDB,
),
this.db.models('dependentDevice').select(),
db.models('dependentDevice').select(),
function (apps, devicesFromDB) {
const devices = _.map(devicesFromDB, function (device) {
const dev = {
@ -590,7 +586,7 @@ export class Proxyvisor {
return Promise.map(appsArray, this.normaliseDependentAppForDB)
.tap((appsForDB) => {
return Promise.map(appsForDB, (app) => {
return this.db.upsertModel(
return db.upsertModel(
'dependentAppTarget',
app,
{ appId: app.appId },
@ -619,7 +615,7 @@ export class Proxyvisor {
);
}).then((devicesForDB) => {
return Promise.map(devicesForDB, (device) => {
return this.db.upsertModel(
return db.upsertModel(
'dependentDeviceTarget',
device,
{ uuid: device.uuid },
@ -686,11 +682,11 @@ export class Proxyvisor {
getTarget() {
return Promise.props({
apps: Promise.map(
this.db.models('dependentAppTarget').select(),
db.models('dependentAppTarget').select(),
this.normaliseDependentAppFromDB,
),
devices: Promise.map(
this.db.models('dependentDeviceTarget').select(),
db.models('dependentDeviceTarget').select(),
this.normaliseDependentDeviceTargetFromDB,
),
});
@ -899,7 +895,7 @@ export class Proxyvisor {
}
getHookEndpoint(appId) {
return this.db
return db
.models('dependentApp')
.select('parentApp')
.where({ appId })
@ -958,7 +954,7 @@ export class Proxyvisor {
.timeout(timeout)
.spread((response, body) => {
if (response.statusCode === 200) {
return this.db.models('dependentDevice').del().where({ uuid });
return db.models('dependentDevice').del().where({ uuid });
} else {
throw new Error(`Hook returned ${response.statusCode}: ${body}`);
}
@ -968,7 +964,7 @@ export class Proxyvisor {
sendUpdates({ uuid }) {
return Promise.join(
this.db.models('dependentDevice').where({ uuid }).select(),
db.models('dependentDevice').where({ uuid }).select(),
this.config.get('apiTimeout'),
([dev], apiTimeout) => {
if (dev == null) {

View File

@ -1,6 +1,6 @@
import APIBinder from './api-binder';
import Config, { ConfigKey } from './config';
import Database from './db';
import * as db from './db';
import DeviceState from './device-state';
import EventTracker from './event-tracker';
import { intialiseContractRequirements } from './lib/contracts';
@ -29,7 +29,6 @@ const startupConfigFields: ConfigKey[] = [
];
export class Supervisor {
private db: Database;
private config: Config;
private eventTracker: EventTracker;
private logger: Logger;
@ -38,19 +37,16 @@ export class Supervisor {
private api: SupervisorAPI;
public constructor() {
this.db = new Database();
this.config = new Config({ db: this.db });
this.config = new Config();
this.eventTracker = new EventTracker();
this.logger = new Logger({ db: this.db, eventTracker: this.eventTracker });
this.logger = new Logger({ eventTracker: this.eventTracker });
this.apiBinder = new APIBinder({
config: this.config,
db: this.db,
eventTracker: this.eventTracker,
logger: this.logger,
});
this.deviceState = new DeviceState({
config: this.config,
db: this.db,
eventTracker: this.eventTracker,
logger: this.logger,
apiBinder: this.apiBinder,
@ -76,7 +72,7 @@ export class Supervisor {
public async init() {
log.info(`Supervisor v${version} starting up...`);
await this.db.init();
await db.initialized;
await this.config.init();
const conf = await this.config.getMany(startupConfigFields);
@ -110,7 +106,6 @@ export class Supervisor {
await normaliseLegacyDatabase(
this.deviceState.config,
this.deviceState.applications,
this.db,
this.apiBinder.balenaApi,
);
}

View File

@ -2,7 +2,7 @@ import * as _ from 'lodash';
import { ApplicationManager } from './application-manager';
import Config from './config';
import Database, { Transaction } from './db';
import * as db from './db';
// Once we have correct types for both applications and the
// incoming target state this should be changed
@ -26,7 +26,6 @@ export class TargetStateAccessor {
public constructor(
protected applications: ApplicationManager,
protected config: Config,
protected db: Database,
) {
// If we switch backend, the target state also needs to
// be invalidated (this includes switching to and from
@ -56,23 +55,21 @@ export class TargetStateAccessor {
]);
const source = localMode ? 'local' : apiEndpoint;
this.targetState = await this.db.models('app').where({ source });
this.targetState = await db.models('app').where({ source });
}
return this.targetState!;
}
public async setTargetApps(
apps: DatabaseApps,
trx: Transaction,
trx: db.Transaction,
): Promise<void> {
// We can't cache the value here, as it could be for a
// different source
this.targetState = undefined;
await Promise.all(
apps.map((app) =>
this.db.upsertModel('app', app, { appId: app.appId }, trx),
),
apps.map((app) => db.upsertModel('app', app, { appId: app.appId }, trx)),
);
}
}

View File

@ -9,6 +9,25 @@ process.env.LED_FILE = './test/data/led_file';
import * as dbus from 'dbus';
import { DBusError, DBusInterface } from 'dbus';
import { stub } from 'sinon';
import * as fs from 'fs';
// Make sure they are no database files left over from
// previous runs
try {
fs.unlinkSync(process.env.DATABASE_PATH);
} catch {
/* noop */
}
try {
fs.unlinkSync(process.env.DATABASE_PATH_2);
} catch {
/* noop */
}
try {
fs.unlinkSync(process.env.DATABASE_PATH_3);
} catch {
/* noop */
}
stub(dbus, 'getBus').returns({
getInterface: (

View File

@ -1,12 +1,10 @@
import ChaiConfig = require('./lib/chai-config');
import prepare = require('./lib/prepare');
const { expect } = ChaiConfig;
import constants = require('../src/lib/constants');
describe('constants', function () {
before(() => prepare());
it('has the correct configJsonPathOnHost', () =>
expect(constants.configJsonPathOnHost).to.equal('/config.json'));
it('has the correct rootMountPoint', () =>

View File

@ -5,7 +5,7 @@ import { fs } from 'mz';
import ChaiConfig = require('./lib/chai-config');
import prepare = require('./lib/prepare');
import { DB } from '../src/db';
import * as constants from '../src/lib/constants';
const { expect } = ChaiConfig;
@ -44,37 +44,37 @@ async function createOldDatabase(path: string) {
return knex;
}
describe('DB', () => {
let db: DB;
before(() => {
prepare();
db = new DB();
describe('Database Migrations', () => {
before(async () => {
await prepare();
});
it('initializes correctly, running the migrations', () => {
return expect(db.init()).to.be.fulfilled;
after(() => {
// @ts-ignore
constants.databasePath = process.env.DATABASE_PATH;
delete require.cache[require.resolve('../src/db')];
});
it('creates a database at the path from an env var', () => {
const promise = fs.stat(process.env.DATABASE_PATH!);
return expect(promise).to.be.fulfilled;
});
it('creates a database at the path passed on creation', async () => {
const databasePath = process.env.DATABASE_PATH_2!;
// @ts-ignore
constants.databasePath = databasePath;
delete require.cache[require.resolve('../src/db')];
it('creates a database at the path passed on creation', () => {
const db2 = new DB({ databasePath: process.env.DATABASE_PATH_2 });
const promise = db2
.init()
.then(() => fs.stat(process.env.DATABASE_PATH_2!));
return expect(promise).to.be.fulfilled;
const testDb = await import('../src/db');
await testDb.initialized;
await fs.stat(databasePath);
});
it('adds new fields and removes old ones in an old database', async () => {
const databasePath = process.env.DATABASE_PATH_3!;
const knexForDB = await createOldDatabase(databasePath);
const testDb = new DB({ databasePath });
await testDb.init();
// @ts-ignore
constants.databasePath = databasePath;
delete require.cache[require.resolve('../src/db')];
const testDb = await import('../src/db');
await testDb.initialized;
await Bluebird.all([
expect(knexForDB.schema.hasColumn('app', 'appId')).to.eventually.be.true,
expect(knexForDB.schema.hasColumn('app', 'releaseId')).to.eventually.be
@ -97,7 +97,22 @@ describe('DB', () => {
.to.eventually.be.true,
]);
});
});
describe('Database', () => {
let db: typeof import('../src/db');
before(async () => {
await prepare();
db = await import('../src/db');
});
it('initializes correctly, running the migrations', () => {
return expect(db.initialized).to.be.fulfilled;
});
it('creates a database at the path from an env var', () => {
const promise = fs.stat(process.env.DATABASE_PATH!);
return expect(promise).to.be.fulfilled;
});
it('creates a deviceConfig table with a single default value', async () => {
const deviceConfig = await db.models('deviceConfig').select();
expect(deviceConfig).to.have.lengthOf(1);

View File

@ -9,19 +9,15 @@ chai.use(require('chai-events'));
const { expect } = chai;
import Config from '../src/config';
import DB from '../src/db';
import constants = require('../src/lib/constants');
describe('Config', () => {
let db: DB;
let conf: Config;
before(async () => {
prepare();
db = new DB();
conf = new Config({ db });
await prepare();
conf = new Config();
await db.init();
await conf.init();
});
@ -32,7 +28,7 @@ describe('Config', () => {
});
it('uses the correct config.json path from the root mount when passed as argument to the constructor', async () => {
const conf2 = new Config({ db, configPath: '/foo.json' });
const conf2 = new Config({ configPath: '/foo.json' });
expect(await (conf2 as any).configJsonBackend.path()).to.equal(
'test/data/foo.json',
);
@ -131,10 +127,8 @@ describe('Config', () => {
describe('Function config providers', () => {
before(async () => {
prepare();
db = new DB();
conf = new Config({ db });
await db.init();
await prepare();
conf = new Config();
await conf.init();
});

View File

@ -11,7 +11,6 @@ const { expect } = chai;
import Config from '../src/config';
import { RPiConfigBackend } from '../src/config/backend';
import DB from '../src/db';
import DeviceState from '../src/device-state';
import { loadTargetFromFile } from '../src/device-state/preload';
@ -209,8 +208,7 @@ const testTargetInvalid = {
};
describe('deviceState', () => {
const db = new DB();
const config = new Config({ db });
const config = new Config();
const logger = {
clearOutOfDateDBLogs() {
/* noop */
@ -218,7 +216,7 @@ describe('deviceState', () => {
};
let deviceState: DeviceState;
before(async () => {
prepare();
await prepare();
const eventTracker = {
track: console.log,
};
@ -234,7 +232,6 @@ describe('deviceState', () => {
});
deviceState = new DeviceState({
db,
config,
eventTracker: eventTracker as any,
logger: logger as any,
@ -252,7 +249,6 @@ describe('deviceState', () => {
});
(deviceState as any).deviceConfig.configBackend = new RPiConfigBackend();
await db.init();
await config.init();
});

View File

@ -1,23 +1,21 @@
import { fs } from 'mz';
import { Server } from 'net';
import { SinonSpy, spy, stub, SinonStub } from 'sinon';
import { SinonSpy, SinonStub, spy, stub } from 'sinon';
import ApiBinder from '../src/api-binder';
import Config from '../src/config';
import DeviceState from '../src/device-state';
import Log from '../src/lib/supervisor-console';
import chai = require('./lib/chai-config');
import balenaAPI = require('./lib/mocked-balena-api');
import prepare = require('./lib/prepare');
import ApiBinder from '../src/api-binder';
import Config from '../src/config';
import Log from '../src/lib/supervisor-console';
import DB from '../src/db';
import DeviceState from '../src/device-state';
const { expect } = chai;
const initModels = async (obj: Dictionary<any>, filename: string) => {
prepare();
await prepare();
obj.db = new DB();
obj.config = new Config({ db: obj.db, configPath: filename });
obj.config = new Config({ configPath: filename });
obj.eventTracker = {
track: stub().callsFake((ev, props) => console.log(ev, props)),
@ -30,14 +28,12 @@ const initModels = async (obj: Dictionary<any>, filename: string) => {
} as any;
obj.apiBinder = new ApiBinder({
db: obj.db,
config: obj.config,
logger: obj.logger,
eventTracker: obj.eventTracker,
});
obj.deviceState = new DeviceState({
db: obj.db,
config: obj.config,
eventTracker: obj.eventTracker,
logger: obj.logger,
@ -46,7 +42,6 @@ const initModels = async (obj: Dictionary<any>, filename: string) => {
obj.apiBinder.setDeviceState(obj.deviceState);
await obj.db.init();
await obj.config.init();
await obj.apiBinder.initClient(); // Initializes the clients but doesn't trigger provisioning
};

View File

@ -14,9 +14,8 @@ const extlinuxBackend = new ExtlinuxConfigBackend();
const rpiConfigBackend = new RPiConfigBackend();
describe('DeviceConfig', function () {
before(function () {
prepare();
this.fakeDB = {};
before(async function () {
await prepare();
this.fakeConfig = {
get(key: string) {
return Promise.try(function () {
@ -33,7 +32,6 @@ describe('DeviceConfig', function () {
};
return (this.deviceConfig = new DeviceConfig({
logger: this.fakeLogger,
db: this.fakeDB,
config: this.fakeConfig,
}));
});
@ -411,7 +409,6 @@ APPEND \${cbootargs} \${resin_kernel_root} ro rootwait isolcpus=2\n\
};
this.upboardConfig = new DeviceConfig({
logger: this.fakeLogger,
db: this.fakeDB,
config: fakeConfig as any,
});

View File

@ -3,7 +3,6 @@ import * as _ from 'lodash';
import { stub } from 'sinon';
import Config from '../src/config';
import DB from '../src/db';
import Network from '../src/compose/network';
@ -126,10 +125,9 @@ const dependentDBFormat = {
};
describe('ApplicationManager', function () {
before(function () {
prepare();
this.db = new DB();
this.config = new Config({ db: this.db });
before(async function () {
await prepare();
this.config = new Config();
const eventTracker = new EventTracker();
this.logger = {
clearOutOfDateDBLogs: () => {
@ -137,7 +135,6 @@ describe('ApplicationManager', function () {
},
} as any;
this.deviceState = new DeviceState({
db: this.db,
config: this.config,
eventTracker,
logger: this.logger,
@ -229,9 +226,7 @@ describe('ApplicationManager', function () {
return targetCloned;
});
};
return this.db.init().then(() => {
return this.config.init();
});
return this.config.init();
});
beforeEach(

View File

@ -2,11 +2,9 @@ import * as assert from 'assert';
import { expect } from 'chai';
import * as Docker from 'dockerode';
import * as sinon from 'sinon';
import * as tmp from 'tmp';
import Config from '../src/config';
import DB from '../src/db';
import log from '../src/lib/supervisor-console';
import * as db from '../src/db';
import LocalModeManager, {
EngineSnapshot,
EngineSnapshotRecord,
@ -15,8 +13,6 @@ import Logger from '../src/logger';
import ShortStackError from './lib/errors';
describe('LocalModeManager', () => {
let dbFile: tmp.FileResult;
let db: DB;
let localMode: LocalModeManager;
let dockerStub: sinon.SinonStubbedInstance<Docker>;
@ -35,10 +31,7 @@ describe('LocalModeManager', () => {
});
before(async () => {
dbFile = tmp.fileSync();
log.debug(`Test db: ${dbFile.name}`);
db = new DB({ databasePath: dbFile.name });
await db.init();
await db.initialized;
dockerStub = sinon.createStubInstance(Docker);
const configStub = (sinon.createStubInstance(Config) as unknown) as Config;
@ -48,7 +41,6 @@ describe('LocalModeManager', () => {
configStub,
(dockerStub as unknown) as Docker,
loggerStub,
db,
supervisorContainerId,
);
});
@ -441,6 +433,5 @@ describe('LocalModeManager', () => {
after(async () => {
sinon.restore();
dbFile.removeCallback();
});
});

View File

@ -1,12 +1,3 @@
// TODO: This file was created by bulk-decaffeinate.
// Sanity-check the conversion and remove this comment.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS103: Rewrite code to no longer use __guard__
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
import * as express from 'express';
import * as _ from 'lodash';

View File

@ -3,17 +3,17 @@ import { fs } from 'mz';
import { stub } from 'sinon';
import { ApplicationManager } from '../../src/application-manager';
import { Images } from '../../src/compose/images';
import { NetworkManager } from '../../src/compose/network-manager';
import { ServiceManager } from '../../src/compose/service-manager';
import { VolumeManager } from '../../src/compose/volume-manager';
import Config from '../../src/config';
import Database from '../../src/db';
import * as db from '../../src/db';
import { createV1Api } from '../../src/device-api/v1';
import { createV2Api } from '../../src/device-api/v2';
import DeviceState from '../../src/device-state';
import EventTracker from '../../src/event-tracker';
import SupervisorAPI from '../../src/supervisor-api';
import { Images } from '../../src/compose/images';
import { ServiceManager } from '../../src/compose/service-manager';
import { NetworkManager } from '../../src/compose/network-manager';
import { VolumeManager } from '../../src/compose/volume-manager';
const DB_PATH = './test/data/supervisor-api.sqlite';
// Holds all values used for stubbing
@ -67,12 +67,11 @@ const STUBBED_VALUES = {
async function create(): Promise<SupervisorAPI> {
// Get SupervisorAPI construct options
const { db, config, eventTracker, deviceState } = await createAPIOpts();
const { config, eventTracker, deviceState } = await createAPIOpts();
// Stub functions
setupStubs();
// Create ApplicationManager
const appManager = new ApplicationManager({
db,
config,
eventTracker,
logger: null,
@ -102,27 +101,21 @@ async function cleanUp(): Promise<void> {
}
async function createAPIOpts(): Promise<SupervisorAPIOpts> {
// Create database
const db = new Database({
databasePath: DB_PATH,
});
await db.init();
await db.initialized;
// Create config
const mockedConfig = new Config({ db });
const mockedConfig = new Config();
// Initialize and set values for mocked Config
await initConfig(mockedConfig);
// Create EventTracker
const tracker = new EventTracker();
// Create deviceState
const deviceState = new DeviceState({
db,
config: mockedConfig,
eventTracker: tracker,
logger: null as any,
apiBinder: null as any,
});
return {
db,
config: mockedConfig,
eventTracker: tracker,
deviceState,
@ -172,7 +165,6 @@ function restoreStubs() {
}
interface SupervisorAPIOpts {
db: Database;
config: Config;
eventTracker: EventTracker;
deviceState: DeviceState;

View File

@ -1,18 +1,23 @@
// TODO: This file was created by bulk-decaffeinate.
// Sanity-check the conversion and remove this comment.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
import * as fs from 'fs';
import * as db from '../../src/db';
export = function () {
try {
fs.unlinkSync(process.env.DATABASE_PATH!);
} catch (e) {
/* ignore /*/
}
export = async function () {
await db.initialized;
await db.transaction(async (trx) => {
const result = await trx.raw(`
SELECT name, sql
FROM sqlite_master
WHERE type='table'`);
for (const r of result) {
// We don't run the migrations again
if (r.name !== 'knex_migrations') {
await trx.raw(`DELETE FROM ${r.name}`);
}
}
// The supervisor expects this value to already have
// been pre-populated
await trx('deviceConfig').insert({ targetValues: '{}' });
});
try {
fs.unlinkSync(process.env.DATABASE_PATH_2!);

View File

@ -2,8 +2,8 @@
// TODO: Upstream types to the repo
declare module 'balena-register-device' {
import TypedError = require('typed-error');
import { Response } from 'request';
import TypedError = require('typed-error');
function factory({
request,
@ -13,7 +13,7 @@ declare module 'balena-register-device' {
};
factory.ApiError = class ApiError extends TypedError {
response: Response;
public response: Response;
};
export = factory;
}