mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2024-12-19 05:37:53 +00:00
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:
parent
0dc0fc77b6
commit
1d7381327e
@ -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",
|
||||
|
@ -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;
|
||||
}
|
||||
|
5
src/application-manager.d.ts
vendored
5
src/application-manager.d.ts
vendored
@ -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;
|
||||
|
@ -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(() => {
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
81
src/db.ts
81
src/db.ts
@ -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);
|
||||
}
|
||||
|
8
src/device-api/common.d.ts
vendored
8
src/device-api/common.d.ts
vendored
@ -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>;
|
||||
|
@ -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,
|
||||
|
@ -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>;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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');
|
||||
|
@ -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' })
|
||||
|
@ -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();
|
||||
|
@ -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] });
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
@ -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)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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: (
|
||||
|
@ -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', () =>
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
});
|
||||
|
||||
|
@ -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();
|
||||
});
|
||||
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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,
|
||||
});
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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!);
|
||||
|
4
typings/balena-register-device.d.ts
vendored
4
typings/balena-register-device.d.ts
vendored
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user