Merge pull request #1663 from balena-os/remove-deprecated-dependencies

Remove mz, mkdirp, body-parser dependencies
This commit is contained in:
bulldozer-balena[bot] 2021-04-27 22:57:20 +00:00 committed by GitHub
commit dba9bf1576
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 483 additions and 318 deletions

39
package-lock.json generated
View File

@ -592,21 +592,21 @@
"integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
"dev": true "dev": true
}, },
"@types/mkdirp": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-0.5.2.tgz",
"integrity": "sha512-U5icWpv7YnZYGsN4/cmh3WD2onMY0aJIiTE6+51TwJCttdHvtCYmkBNOobHlXwrJRL0nkH9jH4kD+1FAdMN4Tg==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/mocha": { "@types/mocha": {
"version": "5.2.7", "version": "5.2.7",
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz",
"integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==",
"dev": true "dev": true
}, },
"@types/mock-fs": {
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.0.tgz",
"integrity": "sha512-FUqxhURwqFtFBCuUj3uQMp7rPSQs//b3O9XecAVxhqS9y4/W8SIJEZFq2mmpnFVZBXwR/2OyPLE97CpyYiB8Mw==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/morgan": { "@types/morgan": {
"version": "1.9.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.0.tgz", "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.0.tgz",
@ -1569,12 +1569,21 @@
} }
}, },
"blinking": { "blinking": {
"version": "0.0.3", "version": "0.0.4",
"resolved": "https://registry.npmjs.org/blinking/-/blinking-0.0.3.tgz", "resolved": "https://registry.npmjs.org/blinking/-/blinking-0.0.4.tgz",
"integrity": "sha1-c6LX+J2z2lSzYFxJiqXYYGv8Hnc=", "integrity": "sha512-kIC2FbDXmd9ydtCYQjrSEpw/jpvqNKfn+uDSO03sLyEju9Q/ZZyJqvdB/sSSGsyzx3bc5J4MmcqvOY/iL7OgnA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/bluebird": "^3.5.33",
"bluebird": "^3.0.0" "bluebird": "^3.0.0"
},
"dependencies": {
"@types/bluebird": {
"version": "3.5.33",
"resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.33.tgz",
"integrity": "sha512-ndEo1xvnYeHxm7I/5sF6tBvnsA4Tdi3zj1keRKRs12SP+2ye2A27NDJ1B6PqkfMbGAcT+mqQVqbZRIrhfOp5PQ==",
"dev": true
}
} }
}, },
"bluebird": { "bluebird": {
@ -7062,6 +7071,12 @@
} }
} }
}, },
"mock-fs": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.14.0.tgz",
"integrity": "sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==",
"dev": true
},
"morgan": { "morgan": {
"version": "1.10.0", "version": "1.10.0",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",

View File

@ -55,10 +55,9 @@
"@types/lockfile": "^1.0.1", "@types/lockfile": "^1.0.1",
"@types/lodash": "^4.14.159", "@types/lodash": "^4.14.159",
"@types/memoizee": "^0.4.4", "@types/memoizee": "^0.4.4",
"@types/mkdirp": "^0.5.2",
"@types/mocha": "^5.2.7", "@types/mocha": "^5.2.7",
"@types/mock-fs": "^4.13.0",
"@types/morgan": "^1.9.0", "@types/morgan": "^1.9.0",
"@types/mz": "0.0.32",
"@types/node": "^12.12.54", "@types/node": "^12.12.54",
"@types/request": "^2.48.5", "@types/request": "^2.48.5",
"@types/rewire": "^2.5.28", "@types/rewire": "^2.5.28",
@ -74,9 +73,8 @@
"@types/webpack": "^4.41.21", "@types/webpack": "^4.41.21",
"@types/yargs": "^15.0.12", "@types/yargs": "^15.0.12",
"balena-register-device": "^6.1.6", "balena-register-device": "^6.1.6",
"blinking": "~0.0.3", "blinking": "^0.0.4",
"bluebird": "^3.7.2", "bluebird": "^3.7.2",
"body-parser": "^1.19.0",
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
"chai-events": "0.0.1", "chai-events": "0.0.1",
"chai-like": "^1.1.1", "chai-like": "^1.1.1",
@ -105,10 +103,9 @@
"lodash": "^4.17.20", "lodash": "^4.17.20",
"memoizee": "^0.4.14", "memoizee": "^0.4.14",
"mixpanel": "^0.10.3", "mixpanel": "^0.10.3",
"mkdirp": "^0.5.5",
"mocha": "^5.2.0", "mocha": "^5.2.0",
"mock-fs": "^4.14.0",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"mz": "^2.7.0",
"network-checker": "^0.1.1", "network-checker": "^0.1.1",
"nodemon": "^2.0.4", "nodemon": "^2.0.4",
"pinejs-client-request": "^7.2.1", "pinejs-client-request": "^7.2.1",

View File

@ -1,5 +1,4 @@
import * as Bluebird from 'bluebird'; import * as Bluebird from 'bluebird';
import * as bodyParser from 'body-parser';
import { stripIndent } from 'common-tags'; import { stripIndent } from 'common-tags';
import * as express from 'express'; import * as express from 'express';
import { isLeft } from 'fp-ts/lib/Either'; import { isLeft } from 'fp-ts/lib/Either';
@ -578,8 +577,8 @@ export const initialized = (async () => {
})(); })();
export const router = express.Router(); export const router = express.Router();
router.use(bodyParser.urlencoded({ limit: '10mb', extended: true })); router.use(express.urlencoded({ limit: '10mb', extended: true }));
router.use(bodyParser.json({ limit: '10mb' })); router.use(express.json({ limit: '10mb' }));
router.post('/v1/update', (req, res, next) => { router.post('/v1/update', (req, res, next) => {
eventTracker.track('Update notification'); eventTracker.track('Update notification');

View File

@ -1,4 +1,3 @@
import * as bodyParser from 'body-parser';
import * as express from 'express'; import * as express from 'express';
import * as _ from 'lodash'; import * as _ from 'lodash';
@ -61,8 +60,8 @@ const localModeManager = new LocalModeManager();
export const router = (() => { export const router = (() => {
const $router = express.Router(); const $router = express.Router();
$router.use(bodyParser.urlencoded({ extended: true, limit: '10mb' })); $router.use(express.urlencoded({ extended: true, limit: '10mb' }));
$router.use(bodyParser.json({ limit: '10mb' })); $router.use(express.json({ limit: '10mb' }));
createV1Api($router); createV1Api($router);
createV2Api($router); createV2Api($router);

View File

@ -1,15 +1,15 @@
import * as Bluebird from 'bluebird'; import * as Bluebird from 'bluebird';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { fs } from 'mz';
import * as constants from '../lib/constants'; import * as constants from '../lib/constants';
import { docker } from '../lib/docker-utils'; import { docker } from '../lib/docker-utils';
import { ENOENT, NotFoundError } from '../lib/errors'; import { NotFoundError } from '../lib/errors';
import logTypes = require('../lib/log-types'); import logTypes = require('../lib/log-types');
import log from '../lib/supervisor-console';
import { exists } from '../lib/fs-utils';
import * as logger from '../logger'; import * as logger from '../logger';
import { Network } from './network'; import { Network } from './network';
import log from '../lib/supervisor-console';
import { ResourceRecreationAttemptError } from './errors'; import { ResourceRecreationAttemptError } from './errors';
export function getAll(): Bluebird<Network[]> { export function getAll(): Bluebird<Network[]> {
@ -69,23 +69,22 @@ export async function remove(network: Network) {
await network.remove(); await network.remove();
} }
export function supervisorNetworkReady(): Bluebird<boolean> { export async function supervisorNetworkReady(): Promise<boolean> {
return Bluebird.resolve( const networkExists = exists(
fs.stat(`/sys/class/net/${constants.supervisorNetworkInterface}`), `/sys/class/net/${constants.supervisorNetworkInterface}`,
) );
.then(() => { if (!networkExists) {
return docker.getNetwork(constants.supervisorNetworkInterface).inspect(); return false;
}) }
.then((network) => { const network = await docker
.getNetwork(constants.supervisorNetworkInterface)
.inspect();
return ( return (
network.Options['com.docker.network.bridge.name'] === network.Options['com.docker.network.bridge.name'] ===
constants.supervisorNetworkInterface && constants.supervisorNetworkInterface &&
network.IPAM.Config[0].Subnet === constants.supervisorNetworkSubnet && network.IPAM.Config[0].Subnet === constants.supervisorNetworkSubnet &&
network.IPAM.Config[0].Gateway === constants.supervisorNetworkGateway network.IPAM.Config[0].Gateway === constants.supervisorNetworkGateway
); );
})
.catchReturn(NotFoundError, false)
.catchReturn(ENOENT, false);
} }
export function ensureSupervisorNetwork(): Bluebird<void> { export function ensureSupervisorNetwork(): Bluebird<void> {
@ -109,11 +108,13 @@ export function ensureSupervisorNetwork(): Bluebird<void> {
) { ) {
return removeIt(); return removeIt();
} else { } else {
return Bluebird.resolve( return exists(
fs.stat(`/sys/class/net/${constants.supervisorNetworkInterface}`), `/sys/class/net/${constants.supervisorNetworkInterface}`,
) ).then((networkExists) => {
.catch(ENOENT, removeIt) if (!networkExists) {
.return(); return removeIt();
}
});
} }
}) })
.catch(NotFoundError, () => { .catch(NotFoundError, () => {

View File

@ -4,7 +4,7 @@ import { EventEmitter } from 'events';
import { isLeft } from 'fp-ts/lib/Either'; import { isLeft } from 'fp-ts/lib/Either';
import * as JSONStream from 'JSONStream'; import * as JSONStream from 'JSONStream';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { fs } from 'mz'; import { promises as fs } from 'fs';
import StrictEventEmitter from 'strict-event-emitter-types'; import StrictEventEmitter from 'strict-event-emitter-types';
import * as config from '../config'; import * as config from '../config';

View File

@ -18,9 +18,9 @@ import {
} from './types/service'; } from './types/service';
import * as ComposeUtils from './utils'; import * as ComposeUtils from './utils';
import * as constants from '../lib/constants';
import * as updateLock from '../lib/update-lock'; import * as updateLock from '../lib/update-lock';
import { sanitiseComposeConfig } from './sanitise'; import { sanitiseComposeConfig } from './sanitise';
import { getPathOnHost } from '../lib/fs-utils';
import log from '../lib/supervisor-console'; import log from '../lib/supervisor-console';
import { EnvVarObject } from '../lib/types'; import { EnvVarObject } from '../lib/types';
@ -900,16 +900,14 @@ export class Service {
} }
public handoverCompleteFullPathsOnHost(): string[] { public handoverCompleteFullPathsOnHost(): string[] {
return [ const lockPath = updateLock.lockPath(
path.join(this.handoverCompletePathOnHost(), 'handover-complete'), this.appId || 0,
path.join(this.handoverCompletePathOnHost(), 'resin-kill-me'), this.serviceName || '',
]; );
} return getPathOnHost(
...['handover-complete', 'resin-kill-me'].map((tail) =>
private handoverCompletePathOnHost(): string { path.join(lockPath, tail),
return path.join( ),
constants.rootMountPoint,
updateLock.lockPath(this.appId || 0, this.serviceName || ''),
); );
} }

View File

@ -1,8 +1,7 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { child_process } from 'mz';
import * as constants from '../../lib/constants'; import * as constants from '../../lib/constants';
import { writeFileAtomic } from '../../lib/fs-utils'; import { writeFileAtomic, exec } from '../../lib/fs-utils';
export interface ConfigOptions { export interface ConfigOptions {
[key: string]: string | string[]; [key: string]: string | string[];
@ -15,7 +14,7 @@ export async function remountAndWriteAtomic(
data: string | Buffer, data: string | Buffer,
): Promise<void> { ): Promise<void> {
// Here's the dangerous part: // Here's the dangerous part:
await child_process.exec( await exec(
`mount -t vfat -o remount,rw ${constants.bootBlockDevice} ${bootMountPoint}`, `mount -t vfat -o remount,rw ${constants.bootBlockDevice} ${bootMountPoint}`,
); );
await writeFileAtomic(file, data); await writeFileAtomic(file, data);

View File

@ -1,5 +1,5 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { child_process, fs } from 'mz'; import { promises as fs } from 'fs';
import * as path from 'path'; import * as path from 'path';
import { import {
@ -8,6 +8,7 @@ import {
bootMountPoint, bootMountPoint,
remountAndWriteAtomic, remountAndWriteAtomic,
} from './backend'; } from './backend';
import { exec, exists } from '../../lib/fs-utils';
import * as constants from '../../lib/constants'; import * as constants from '../../lib/constants';
import * as logger from '../../logger'; import * as logger from '../../logger';
import log from '../../lib/supervisor-console'; import log from '../../lib/supervisor-console';
@ -56,7 +57,7 @@ export class ConfigFs extends ConfigBackend {
const amlSrcPath = path.join(this.SystemAmlFiles, `${aml}.aml`); const amlSrcPath = path.join(this.SystemAmlFiles, `${aml}.aml`);
// log to system log if the AML doesn't exist... // log to system log if the AML doesn't exist...
if (!(await fs.exists(amlSrcPath))) { if (!(await exists(amlSrcPath))) {
log.error(`Missing AML for \'${aml}\'. Unable to load.`); log.error(`Missing AML for \'${aml}\'. Unable to load.`);
if (logger) { if (logger) {
logger.logSystemMessage( logger.logSystemMessage(
@ -80,9 +81,7 @@ export class ConfigFs extends ConfigBackend {
log.info(`Loading AML ${aml}`); log.info(`Loading AML ${aml}`);
// we use `cat` here as this didn't work when using `cp` and all // we use `cat` here as this didn't work when using `cp` and all
// examples of this loading mechanism use `cat`. // examples of this loading mechanism use `cat`.
await child_process.exec( await exec(`cat ${amlSrcPath} > ${path.join(amlDstPath, 'aml')}`);
`cat ${amlSrcPath} > ${path.join(amlDstPath, 'aml')}`,
);
const [oemId, oemTableId, oemRevision] = await Promise.all([ const [oemId, oemTableId, oemRevision] = await Promise.all([
fs.readFile(path.join(amlDstPath, 'oem_id'), 'utf8'), fs.readFile(path.join(amlDstPath, 'oem_id'), 'utf8'),
@ -101,7 +100,7 @@ export class ConfigFs extends ConfigBackend {
private async readConfigJSON(): Promise<ConfigfsConfig> { private async readConfigJSON(): Promise<ConfigfsConfig> {
// if we don't yet have a config file, just return an empty result... // if we don't yet have a config file, just return an empty result...
if (!(await fs.exists(this.ConfigFilePath))) { if (!(await exists(this.ConfigFilePath))) {
log.info('Empty ConfigFS config file'); log.info('Empty ConfigFS config file');
return {}; return {};
} }
@ -134,7 +133,7 @@ export class ConfigFs extends ConfigBackend {
await super.initialise(); await super.initialise();
// load the acpi_configfs module... // load the acpi_configfs module...
await child_process.exec('modprobe acpi_configfs'); await exec('modprobe acpi_configfs');
// read the existing config file... // read the existing config file...
const config = await this.readConfigJSON(); const config = await this.readConfigJSON();

View File

@ -1,5 +1,5 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { fs } from 'mz'; import { promises as fs } from 'fs';
import { import {
ConfigOptions, ConfigOptions,
@ -9,6 +9,7 @@ import {
} from './backend'; } from './backend';
import * as constants from '../../lib/constants'; import * as constants from '../../lib/constants';
import log from '../../lib/supervisor-console'; import log from '../../lib/supervisor-console';
import { exists } from '../../lib/fs-utils';
/** /**
* A backend to handle Raspberry Pi host configuration * A backend to handle Raspberry Pi host configuration
@ -61,7 +62,7 @@ export class ConfigTxt extends ConfigBackend {
public async getBootConfig(): Promise<ConfigOptions> { public async getBootConfig(): Promise<ConfigOptions> {
let configContents = ''; let configContents = '';
if (await fs.exists(ConfigTxt.bootConfigPath)) { if (await exists(ConfigTxt.bootConfigPath)) {
configContents = await fs.readFile(ConfigTxt.bootConfigPath, 'utf-8'); configContents = await fs.readFile(ConfigTxt.bootConfigPath, 'utf-8');
} else { } else {
await fs.writeFile(ConfigTxt.bootConfigPath, ''); await fs.writeFile(ConfigTxt.bootConfigPath, '');

View File

@ -1,5 +1,5 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { fs } from 'mz'; import { promises as fs } from 'fs';
import * as semver from 'semver'; import * as semver from 'semver';
import { import {

View File

@ -1,5 +1,5 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { fs } from 'mz'; import { promises as fs } from 'fs';
import { import {
ConfigOptions, ConfigOptions,
@ -10,6 +10,7 @@ import {
import * as constants from '../../lib/constants'; import * as constants from '../../lib/constants';
import log from '../../lib/supervisor-console'; import log from '../../lib/supervisor-console';
import { ExtraUEnvError } from '../../lib/errors'; import { ExtraUEnvError } from '../../lib/errors';
import { exists } from '../../lib/fs-utils';
/** /**
* Entry describes the configurable items in an extra_uEnv file * Entry describes the configurable items in an extra_uEnv file
@ -63,7 +64,7 @@ export class ExtraUEnv extends ConfigBackend {
(deviceType.endsWith('-nano') || (deviceType.endsWith('-nano') ||
deviceType.endsWith('-nano-emmc') || deviceType.endsWith('-nano-emmc') ||
deviceType.endsWith('-tx2')) && deviceType.endsWith('-tx2')) &&
(await fs.exists(ExtraUEnv.bootConfigPath)) (await exists(ExtraUEnv.bootConfigPath))
); );
} }

View File

@ -1,9 +1,10 @@
import * as Bluebird from 'bluebird'; import * as Bluebird from 'bluebird';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { fs } from 'mz'; import { promises as fs } from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as constants from '../../lib/constants'; import * as constants from '../../lib/constants';
import { exists } from '../../lib/fs-utils';
import log from '../../lib/supervisor-console'; import log from '../../lib/supervisor-console';
import { import {
bootMountPoint, bootMountPoint,
@ -126,7 +127,7 @@ export class SplashImage extends ConfigBackend {
// The default boot image file has already // The default boot image file has already
// been created // been created
if (await fs.exists(SplashImage.DEFAULT)) { if (await exists(SplashImage.DEFAULT)) {
return this; return this;
} }

View File

@ -1,6 +1,6 @@
import * as Bluebird from 'bluebird'; import * as Bluebird from 'bluebird';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { fs } from 'mz'; import { promises as fs } from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as constants from '../lib/constants'; import * as constants from '../lib/constants';
@ -86,11 +86,6 @@ export default class ConfigJsonConfigBackend {
}); });
} }
public async path(): Promise<string> {
await this.init();
return await this.pathOnHost();
}
private write(): Promise<void> { private write(): Promise<void> {
let atomicWritePossible = true; let atomicWritePossible = true;
return this.pathOnHost() return this.pathOnHost()

View File

@ -1,7 +1,7 @@
import * as Bluebird from 'bluebird'; import * as Bluebird from 'bluebird';
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as memoizee from 'memoizee'; import * as memoizee from 'memoizee';
import { fs } from 'mz'; import { promises as fs } from 'fs';
import { URL } from 'url'; import { URL } from 'url';
import supervisorVersion = require('../lib/supervisor-version'); import supervisorVersion = require('../lib/supervisor-version');

View File

@ -1,5 +1,4 @@
import * as Bluebird from 'bluebird'; import * as Bluebird from 'bluebird';
import * as bodyParser from 'body-parser';
import { stripIndent } from 'common-tags'; import { stripIndent } from 'common-tags';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import * as express from 'express'; import * as express from 'express';
@ -95,8 +94,8 @@ function validateState(state: any): asserts state is TargetState {
// device api stuff in ./device-api // device api stuff in ./device-api
function createDeviceStateRouter() { function createDeviceStateRouter() {
router = express.Router(); router = express.Router();
router.use(bodyParser.urlencoded({ limit: '10mb', extended: true })); router.use(express.urlencoded({ limit: '10mb', extended: true }));
router.use(bodyParser.json({ limit: '10mb' })); router.use(express.json({ limit: '10mb' }));
const rebootOrShutdown = async ( const rebootOrShutdown = async (
req: express.Request, req: express.Request,
@ -463,15 +462,11 @@ export async function loadInitialState() {
// breaks loose due to the liberal any casting // breaks loose due to the liberal any casting
function emitAsync<T extends keyof DeviceStateEvents>( function emitAsync<T extends keyof DeviceStateEvents>(
ev: T, ev: T,
...args: DeviceStateEvents[T] extends (...args: any) => void ...args: DeviceStateEvents[T] extends (...args: infer TArgs) => void
? Parameters<DeviceStateEvents[T]> ? TArgs
: Array<DeviceStateEvents[T]> : Array<DeviceStateEvents[T]>
) { ) {
if (_.isArray(args)) { return setImmediate(() => events.emit(ev as any, ...args));
return setImmediate(() => events.emit(ev as any, ...(args as any)));
} else {
return setImmediate(() => events.emit(ev as any, args));
}
} }
const readLockTarget = () => const readLockTarget = () =>

View File

@ -1,5 +1,5 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { fs } from 'mz'; import { promises as fs } from 'fs';
import { Image, imageFromService } from '../compose/images'; import { Image, imageFromService } from '../compose/images';
import * as deviceState from '../device-state'; import * as deviceState from '../device-state';

View File

@ -1,20 +1,14 @@
import * as Bluebird from 'bluebird'; import * as Bluebird from 'bluebird';
import { stripIndent } from 'common-tags'; import { stripIndent } from 'common-tags';
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as mkdirCb from 'mkdirp'; import { promises as fs } from 'fs';
import { fs } from 'mz';
import * as path from 'path'; import * as path from 'path';
import * as config from './config'; import * as config from './config';
import * as constants from './lib/constants'; import * as constants from './lib/constants';
import * as dbus from './lib/dbus'; import * as dbus from './lib/dbus';
import { ENOENT } from './lib/errors'; import { ENOENT } from './lib/errors';
import { writeFileAtomic } from './lib/fs-utils'; import { writeFileAtomic, mkdirp, unlinkAll } from './lib/fs-utils';
const mkdirp = Bluebird.promisify(mkdirCb) as (
path: string,
opts?: any,
) => Bluebird<mkdirCb.Made>;
const redsocksHeader = stripIndent` const redsocksHeader = stripIndent`
base { base {
@ -131,13 +125,7 @@ function generateRedsocksConfEntries(conf: ProxyConfig): string {
async function setProxy(maybeConf: ProxyConfig | null): Promise<void> { async function setProxy(maybeConf: ProxyConfig | null): Promise<void> {
if (_.isEmpty(maybeConf)) { if (_.isEmpty(maybeConf)) {
try { await unlinkAll(redsocksConfPath, noProxyPath);
await Promise.all([fs.unlink(redsocksConfPath), fs.unlink(noProxyPath)]);
} catch (e) {
if (!ENOENT(e)) {
throw e;
}
}
} else { } else {
// We know that maybeConf is not null due to the _.isEmpty check above, // We know that maybeConf is not null due to the _.isEmpty check above,
// but the compiler doesn't // but the compiler doesn't

View File

@ -1,42 +1,84 @@
import * as Bluebird from 'bluebird';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { fs } from 'mz'; import { promises as fs } from 'fs';
import * as Path from 'path'; import * as path from 'path';
import * as constants from './constants'; import { exec as execSync } from 'child_process';
import { ENOENT } from './errors'; import { promisify } from 'util';
export function writeAndSyncFile( import * as constants from './constants';
path: string,
export const exec = promisify(execSync);
export async function writeAndSyncFile(
pathName: string,
data: string | Buffer, data: string | Buffer,
): Bluebird<void> { ): Promise<void> {
return Bluebird.resolve(fs.open(path, 'w')).then((fd) => { const file = await fs.open(pathName, 'w');
_.isString(data) if (typeof data === 'string') {
? fs.write(fd, data, 0, 'utf8') await file.write(data, 0, 'utf8');
: fs } else {
.write(fd, data, 0, data.length) await file.write(data, 0, data.length);
.then(() => fs.fsync(fd)) }
.then(() => fs.close(fd)); await file.sync();
}); await file.close();
} }
export function writeFileAtomic( export async function writeFileAtomic(
path: string, pathName: string,
data: string | Buffer, data: string | Buffer,
): Bluebird<void> { ): Promise<void> {
return Bluebird.resolve(writeAndSyncFile(`${path}.new`, data)).then(() => await writeAndSyncFile(`${pathName}.new`, data);
fs.rename(`${path}.new`, path), await fs.rename(`${pathName}.new`, pathName);
}
export async function safeRename(src: string, dest: string): Promise<void> {
await fs.rename(src, dest);
const file = await fs.open(path.dirname(dest), 'r');
await file.sync();
await file.close();
}
export async function exists(p: string): Promise<boolean> {
try {
await fs.access(p);
return true;
} catch {
return false;
}
}
/**
* Check if a path exists as a direct child of the device's root mountpoint,
* which is equal to constants.rootMountPoint (`/mnt/root`).
*/
export function pathExistsOnHost(pathName: string): Promise<boolean> {
return exists(path.join(constants.rootMountPoint, pathName));
}
/**
* Recursively create directories until input directory.
* Equivalent to mkdirp package, which uses this under the hood.
*/
export async function mkdirp(pathName: string): Promise<void> {
await fs.mkdir(pathName, { recursive: true });
}
/**
* Safe unlink with built-in catch for invalid paths, to remove need
* for catch implementation everywhere else unlink is needed.
*/
export async function unlinkAll(...paths: string[]): Promise<void> {
await Promise.all(
paths.map((pathName) =>
fs.unlink(pathName).catch(() => {
/* Ignore nonexistent paths */
}),
),
); );
} }
export function safeRename(src: string, dest: string): Bluebird<void> { /**
return Bluebird.resolve(fs.rename(src, dest)) * Get one or more paths as they exist in relation to host OS's root.
.then(() => fs.open(Path.dirname(dest), 'r')) */
.tap(fs.fsync) export function getPathOnHost(...paths: string[]): string[] {
.then(fs.close); return paths.map((p: string) => path.join(constants.rootMountPoint, p));
}
export function pathExistsOnHost(p: string): Bluebird<boolean> {
return Bluebird.resolve(fs.stat(Path.join(constants.rootMountPoint, p)))
.return(true)
.catchReturn(ENOENT, false);
} }

View File

@ -1,5 +1,5 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { child_process } from 'mz'; import { spawn } from 'child_process';
import { Readable } from 'stream'; import { Readable } from 'stream';
import { TypedError } from 'typed-error'; import { TypedError } from 'typed-error';
@ -168,7 +168,7 @@ const iptablesRestoreAdaptor: RuleAdaptor = async (
stdinStream.push(null); stdinStream.push(null);
// run the restore... // run the restore...
const proc = child_process.spawn(cmd, args, { shell: true }); const proc = spawn(cmd, args, { shell: true });
// pipe the rules... // pipe the rules...
stdinStream.pipe(proc.stdin); stdinStream.pipe(proc.stdin);

View File

@ -1,11 +1,12 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { promises as fs, exists } from 'mz/fs'; import { promises as fs } from 'fs';
import * as path from 'path'; import * as path from 'path';
import { TypedError } from 'typed-error';
import log from './supervisor-console'; import log from './supervisor-console';
import { shouldReportInterface } from '../network'; import { shouldReportInterface } from '../network';
import { exists } from './fs-utils';
import { TypedError } from 'typed-error';
export class MacAddressError extends TypedError {} export class MacAddressError extends TypedError {}
export async function getAll(sysClassNet: string): Promise<string | undefined> { export async function getAll(sysClassNet: string): Promise<string | undefined> {

View File

@ -1,11 +1,9 @@
import * as Bluebird from 'bluebird'; import * as Bluebird from 'bluebird';
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as mkdirp from 'mkdirp'; import { promises as fs } from 'fs';
import { child_process, fs } from 'mz';
import * as path from 'path'; import * as path from 'path';
import * as rimraf from 'rimraf'; import * as rimraf from 'rimraf';
const mkdirpAsync = Bluebird.promisify(mkdirp);
const rimrafAsync = Bluebird.promisify(rimraf); const rimrafAsync = Bluebird.promisify(rimraf);
import * as apiBinder from '../api-binder'; import * as apiBinder from '../api-binder';
@ -23,7 +21,7 @@ import {
InternalInconsistencyError, InternalInconsistencyError,
} from '../lib/errors'; } from '../lib/errors';
import { docker } from '../lib/docker-utils'; import { docker } from '../lib/docker-utils';
import { pathExistsOnHost } from '../lib/fs-utils'; import { exec, pathExistsOnHost, mkdirp } from '../lib/fs-utils';
import { log } from '../lib/supervisor-console'; import { log } from '../lib/supervisor-console';
import type { import type {
AppsJsonFormat, AppsJsonFormat,
@ -298,8 +296,8 @@ export async function loadBackupFromMigration(
const backupPath = path.join(constants.rootMountPoint, 'mnt/data/backup'); const backupPath = path.join(constants.rootMountPoint, 'mnt/data/backup');
// We clear this path in case it exists from an incomplete run of this function // We clear this path in case it exists from an incomplete run of this function
await rimrafAsync(backupPath); await rimrafAsync(backupPath);
await mkdirpAsync(backupPath); await mkdirp(backupPath);
await child_process.exec(`tar -xzf backup.tgz -C ${backupPath}`, { await exec(`tar -xzf backup.tgz -C ${backupPath}`, {
cwd: path.join(constants.rootMountPoint, 'mnt/data'), cwd: path.join(constants.rootMountPoint, 'mnt/data'),
}); });

View File

@ -1,7 +1,8 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { child_process, fs } from 'mz'; import { promises as fs } from 'fs';
import { InternalInconsistencyError } from './errors'; import { InternalInconsistencyError } from './errors';
import { exec } from './fs-utils';
import log from './supervisor-console'; import log from './supervisor-console';
// Retrieve the data for the OS once only per path // Retrieve the data for the OS once only per path
@ -69,7 +70,7 @@ const L4T_REGEX = /^.*-l4t-r(\d+\.\d+(\.?\d+)?).*$/;
export async function getL4tVersion(): Promise<string | undefined> { export async function getL4tVersion(): Promise<string | undefined> {
// We call `uname -r` on the host, and look for l4t // We call `uname -r` on the host, and look for l4t
try { try {
const [stdout] = await child_process.exec('uname -r'); const { stdout } = await exec('uname -r');
const match = L4T_REGEX.exec(stdout.toString().trim()); const match = L4T_REGEX.exec(stdout.toString().trim());
if (match == null) { if (match == null) {
return; return;

View File

@ -1,6 +1,8 @@
import * as systeminformation from 'systeminformation'; import * as systeminformation from 'systeminformation';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { fs, child_process } from 'mz'; import { promises as fs } from 'fs';
import { exec } from './fs-utils';
export async function getCpuUsage(): Promise<number> { export async function getCpuUsage(): Promise<number> {
const cpuData = await systeminformation.currentLoad(); const cpuData = await systeminformation.currentLoad();
@ -78,7 +80,7 @@ export async function getCpuId(): Promise<string | undefined> {
const undervoltageRegex = /under.*voltage/; const undervoltageRegex = /under.*voltage/;
export async function undervoltageDetected(): Promise<boolean> { export async function undervoltageDetected(): Promise<boolean> {
try { try {
const [dmesgStdout] = await child_process.exec('dmesg'); const { stdout: dmesgStdout } = await exec('dmesg');
return undervoltageRegex.test(dmesgStdout.toString()); return undervoltageRegex.test(dmesgStdout.toString());
} catch { } catch {
return false; return false;

View File

@ -1,12 +1,12 @@
import * as Bluebird from 'bluebird'; import * as Bluebird from 'bluebird';
import * as lockFileLib from 'lockfile'; import * as lockFileLib from 'lockfile';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { fs } from 'mz'; import { promises as fs } from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as Lock from 'rwlock'; import * as Lock from 'rwlock';
import constants = require('./constants');
import { ENOENT, UpdatesLockedError } from './errors'; import { ENOENT, UpdatesLockedError } from './errors';
import { getPathOnHost } from './fs-utils';
type asyncLockFile = typeof lockFileLib & { type asyncLockFile = typeof lockFileLib & {
unlockAsync(path: string): Bluebird<void>; unlockAsync(path: string): Bluebird<void>;
@ -19,17 +19,19 @@ export type LockCallback = (
fn: () => PromiseLike<void>, fn: () => PromiseLike<void>,
) => Bluebird<void>; ) => Bluebird<void>;
function baseLockPath(appId: number): string { export function lockPath(appId: number, serviceName?: string): string {
return path.join('/tmp/balena-supervisor/services', appId.toString()); return path.join(
} '/tmp/balena-supervisor/services',
appId.toString(),
export function lockPath(appId: number, serviceName: string): string { serviceName ?? '',
return path.join(baseLockPath(appId), serviceName); );
} }
function lockFilesOnHost(appId: number, serviceName: string): string[] { function lockFilesOnHost(appId: number, serviceName: string): string[] {
return ['updates.lock', 'resin-updates.lock'].map((filename) => return getPathOnHost(
path.join(constants.rootMountPoint, lockPath(appId, serviceName), filename), ...['updates.lock', 'resin-updates.lock'].map((filename) =>
path.join(lockPath(appId), serviceName, filename),
),
); );
} }
@ -79,10 +81,7 @@ export function lock(
} }
return writeLock(appId) return writeLock(appId)
.tap((release: () => void) => { .tap((release: () => void) => {
const lockDir = path.join( const [lockDir] = getPathOnHost(lockPath(appId));
constants.rootMountPoint,
baseLockPath(appId),
);
return Bluebird.resolve(fs.readdir(lockDir)) return Bluebird.resolve(fs.readdir(lockDir))
.catchReturn(ENOENT, []) .catchReturn(ENOENT, [])

View File

@ -1,6 +1,6 @@
import * as Bluebird from 'bluebird'; import * as Bluebird from 'bluebird';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { fs } from 'mz'; import { promises as fs, watch } from 'fs';
import * as networkCheck from 'network-checker'; import * as networkCheck from 'network-checker';
import * as os from 'os'; import * as os from 'os';
import * as url from 'url'; import * as url from 'url';
@ -74,7 +74,7 @@ export const startConnectivityCheck = _.once(
log.debug('VPN status path exists.'); log.debug('VPN status path exists.');
}) })
.then(() => { .then(() => {
fs.watch(constants.vpnStatusPath, vpnStatusInotifyCallback); watch(constants.vpnStatusPath, vpnStatusInotifyCallback);
}); });
if (enable) { if (enable) {

View File

@ -1,7 +1,10 @@
import * as Promise from 'bluebird'; import * as Promise from 'bluebird';
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as express from 'express'; import * as express from 'express';
import { fs, child_process as childProcess } from 'mz'; import { promises as fs } from 'fs';
import * as path from 'path';
import * as url from 'url';
import * as request from './lib/request'; import * as request from './lib/request';
import * as constants from './lib/constants'; import * as constants from './lib/constants';
import { import {
@ -9,26 +12,20 @@ import {
validStringOrUndefined, validStringOrUndefined,
validObjectOrUndefined, validObjectOrUndefined,
} from './lib/validation'; } from './lib/validation';
import * as path from 'path'; import { log } from './lib/supervisor-console';
import * as mkdirp from 'mkdirp'; import * as dockerUtils from './lib/docker-utils';
import * as bodyParser from 'body-parser'; import { InternalInconsistencyError } from './lib/errors';
import * as url from 'url'; import * as apiHelper from './lib/api-helper';
import { exec, mkdirp } from './lib/fs-utils';
import { normalise } from './compose/images'; import { normalise } from './compose/images';
import { log } from './lib/supervisor-console';
import * as db from './db'; import * as db from './db';
import * as config from './config'; import * as config from './config';
import * as dockerUtils from './lib/docker-utils';
import * as logger from './logger'; import * as logger from './logger';
import { InternalInconsistencyError } from './lib/errors';
import * as apiBinder from './api-binder'; import * as apiBinder from './api-binder';
import * as apiHelper from './lib/api-helper';
import * as dbFormat from './device-state/db-format'; import * as dbFormat from './device-state/db-format';
import * as deviceConfig from './device-config'; import * as deviceConfig from './device-config';
const mkdirpAsync = Promise.promisify(mkdirp);
const isDefined = _.negate(_.isUndefined); const isDefined = _.negate(_.isUndefined);
const parseDeviceFields = function (device) { const parseDeviceFields = function (device) {
@ -52,8 +49,8 @@ const getTarArchive = (source, destination) =>
fs fs
.lstat(destination) .lstat(destination)
.catch(() => .catch(() =>
mkdirpAsync(path.dirname(destination)).then(() => mkdirp(path.dirname(destination)).then(() =>
childProcess.exec(`tar -cvf '${destination}' *`, { cwd: source }), exec(`tar -cvf '${destination}' *`, { cwd: source }),
), ),
); );
@ -92,8 +89,8 @@ const formatCurrentAsState = (device) => ({
const createProxyvisorRouter = function (proxyvisor) { const createProxyvisorRouter = function (proxyvisor) {
const router = express.Router(); const router = express.Router();
router.use(bodyParser.urlencoded({ limit: '10mb', extended: true })); router.use(express.urlencoded({ limit: '10mb', extended: true }));
router.use(bodyParser.json({ limit: '10mb' })); router.use(express.json({ limit: '10mb' }));
router.get('/v1/devices', async (_req, res) => { router.get('/v1/devices', async (_req, res) => {
try { try {
const fields = await db.models('dependentDevice').select(); const fields = await db.models('dependentDevice').select();

View File

@ -4,11 +4,12 @@ import * as _ from 'lodash';
import { Builder } from 'resin-docker-build'; import { Builder } from 'resin-docker-build';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import { child_process } from 'mz';
import * as Path from 'path'; import * as Path from 'path';
import { Duplex, Readable } from 'stream'; import { Duplex, Readable } from 'stream';
import * as tar from 'tar-stream'; import * as tar from 'tar-stream';
import { exec } from '../src/lib/fs-utils';
export function getDocker(deviceAddress: string): Docker { export function getDocker(deviceAddress: string): Docker {
return new Docker({ return new Docker({
host: deviceAddress, host: deviceAddress,
@ -146,7 +147,7 @@ async function tarDirectory(
// Absolutely no escaping in this function, just be careful // Absolutely no escaping in this function, just be careful
async function runSshCommand(address: string, command: string) { async function runSshCommand(address: string, command: string) {
// TODO: Make the port configurable // TODO: Make the port configurable
const [stdout] = await child_process.exec( const { stdout } = await exec(
'ssh -p 22222 -o LogLevel=ERROR ' + 'ssh -p 22222 -o LogLevel=ERROR ' +
'-o StrictHostKeyChecking=no ' + '-o StrictHostKeyChecking=no ' +
'-o UserKnownHostsFile=/dev/null ' + '-o UserKnownHostsFile=/dev/null ' +

View File

@ -1,10 +1,8 @@
import * as packageJson from '../package.json';
import * as livepush from 'livepush'; import * as livepush from 'livepush';
import { promises as fs } from 'fs';
import { fs } from 'mz';
import * as yargs from 'yargs'; import * as yargs from 'yargs';
import * as packageJson from '../package.json';
import * as device from './device'; import * as device from './device';
import * as init from './init'; import * as init from './init';
import { startLivepush } from './livepush'; import { startLivepush } from './livepush';

View File

@ -1,7 +1,4 @@
import ChaiConfig = require('./lib/chai-config'); import { expect } from 'chai';
const { expect } = ChaiConfig;
import constants = require('../src/lib/constants'); import constants = require('../src/lib/constants');
describe('constants', function () { describe('constants', function () {

View File

@ -1,13 +1,10 @@
import * as Bluebird from 'bluebird'; import * as Bluebird from 'bluebird';
import * as Knex from 'knex'; import * as Knex from 'knex';
import { fs } from 'mz';
import ChaiConfig = require('./lib/chai-config'); import { expect } from 'chai';
import prepare = require('./lib/prepare'); import prepare = require('./lib/prepare');
import * as constants from '../src/lib/constants'; import * as constants from '../src/lib/constants';
import { exists } from '../src/lib/fs-utils';
const { expect } = ChaiConfig;
async function createOldDatabase(path: string) { async function createOldDatabase(path: string) {
const knex = Knex({ const knex = Knex({
@ -63,7 +60,7 @@ describe('Database Migrations', () => {
const testDb = await import('../src/db'); const testDb = await import('../src/db');
await testDb.initialized; await testDb.initialized;
await fs.stat(databasePath); expect(await exists(databasePath)).to.be.true;
}); });
it('adds new fields and removes old ones in an old database', async () => { it('adds new fields and removes old ones in an old database', async () => {
@ -109,9 +106,8 @@ describe('Database', () => {
it('initializes correctly, running the migrations', () => { it('initializes correctly, running the migrations', () => {
return expect(db.initialized).to.be.fulfilled; return expect(db.initialized).to.be.fulfilled;
}); });
it('creates a database at the path from an env var', () => { it('creates a database at the path from an env var', async () => {
const promise = fs.stat(process.env.DATABASE_PATH!); expect(await exists(process.env.DATABASE_PATH!)).to.be.true;
return expect(promise).to.be.fulfilled;
}); });
it('creates a deviceConfig table with a single default value', async () => { it('creates a deviceConfig table with a single default value', async () => {
const deviceConfig = await db.models('deviceConfig').select(); const deviceConfig = await db.models('deviceConfig').select();

View File

@ -1,8 +1,8 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { fs } from 'mz'; import { promises as fs } from 'fs';
import { SinonStub, stub } from 'sinon'; import { SinonStub, stub } from 'sinon';
import chai = require('./lib/chai-config'); import * as chai from 'chai';
import prepare = require('./lib/prepare'); import prepare = require('./lib/prepare');
import * as conf from '../src/config'; import * as conf from '../src/config';
@ -20,12 +20,6 @@ describe('Config', () => {
await conf.initialized; await conf.initialized;
}); });
it('uses the correct config.json path', async () => {
expect(await conf.configJsonBackend.path()).to.equal(
'test/data/config.json',
);
});
it('reads and exposes values from the config.json', async () => { it('reads and exposes values from the config.json', async () => {
const id = await conf.get('applicationId'); const id = await conf.get('applicationId');
return expect(id).to.equal(78373); return expect(id).to.equal(78373);

View File

@ -1,7 +1,7 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { stub } from 'sinon'; import { stub } from 'sinon';
import chai = require('./lib/chai-config'); import * as chai from 'chai';
import { StatusCodeError } from '../src/lib/errors'; import { StatusCodeError } from '../src/lib/errors';
import prepare = require('./lib/prepare'); import prepare = require('./lib/prepare');
import * as dockerUtils from '../src/lib/docker-utils'; import * as dockerUtils from '../src/lib/docker-utils';

View File

@ -1,5 +1,5 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { expect } from './lib/chai-config'; import { expect } from 'chai';
import * as validation from '../src/lib/validation'; import * as validation from '../src/lib/validation';

View File

@ -1,5 +1,5 @@
import { fs } from 'mz'; import { promises as fs } from 'fs';
import { expect } from './lib/chai-config'; import { expect } from 'chai';
import blink = require('../src/lib/blink'); import blink = require('../src/lib/blink');
import constants = require('../src/lib/constants'); import constants = require('../src/lib/constants');

View File

@ -1,7 +1,7 @@
import * as mixpanel from 'mixpanel'; import * as mixpanel from 'mixpanel';
import { SinonStub, stub, spy, SinonSpy } from 'sinon'; import { SinonStub, stub, spy, SinonSpy } from 'sinon';
import { expect } from './lib/chai-config'; import { expect } from 'chai';
import log from '../src/lib/supervisor-console'; import log from '../src/lib/supervisor-console';
import supervisorVersion = require('../src/lib/supervisor-version'); import supervisorVersion = require('../src/lib/supervisor-version');

View File

@ -1,8 +1,8 @@
import { fs } from 'mz'; import { promises as fs } from 'fs';
import * as os from 'os'; import * as os from 'os';
import { stub, spy } from 'sinon'; import { stub, spy } from 'sinon';
import { expect } from './lib/chai-config'; import { expect } from 'chai';
import Log from '../src/lib/supervisor-console'; import Log from '../src/lib/supervisor-console';
import * as network from '../src/network'; import * as network from '../src/network';

View File

@ -1,5 +1,5 @@
import { stripIndent } from 'common-tags'; import { stripIndent } from 'common-tags';
import { fs } from 'mz'; import { promises as fs } from 'fs';
import { Server } from 'net'; import { Server } from 'net';
import { SinonSpy, SinonStub, spy, stub } from 'sinon'; import { SinonSpy, SinonStub, spy, stub } from 'sinon';
@ -7,7 +7,7 @@ import prepare = require('./lib/prepare');
import * as config from '../src/config'; import * as config from '../src/config';
import * as deviceState from '../src/device-state'; import * as deviceState from '../src/device-state';
import Log from '../src/lib/supervisor-console'; import Log from '../src/lib/supervisor-console';
import chai = require('./lib/chai-config'); import { expect } from 'chai';
import balenaAPI = require('./lib/mocked-balena-api'); import balenaAPI = require('./lib/mocked-balena-api');
import { schema } from '../src/config/schema'; import { schema } from '../src/config/schema';
import ConfigJsonConfigBackend from '../src/config/configJson'; import ConfigJsonConfigBackend from '../src/config/configJson';
@ -21,7 +21,6 @@ import { DeviceNotFoundError } from '../src/lib/errors';
import { eventTrackSpy } from './lib/mocked-event-tracker'; import { eventTrackSpy } from './lib/mocked-event-tracker';
const { expect } = chai;
let ApiBinder: typeof import('../src/api-binder'); let ApiBinder: typeof import('../src/api-binder');
class ExpectedError extends TypedError {} class ExpectedError extends TypedError {}

View File

@ -2,7 +2,7 @@ import * as https from 'https';
import * as stream from 'stream'; import * as stream from 'stream';
import * as zlib from 'zlib'; import * as zlib from 'zlib';
import * as Promise from 'bluebird'; import * as Promise from 'bluebird';
import { expect } from './lib/chai-config'; import { expect } from 'chai';
import * as sinon from 'sinon'; import * as sinon from 'sinon';
import { ContainerLogs } from '../src/logging/container'; import { ContainerLogs } from '../src/logging/container';

View File

@ -1,8 +1,8 @@
import { stripIndent } from 'common-tags'; import { stripIndent } from 'common-tags';
import { child_process, fs } from 'mz'; import { promises as fs } from 'fs';
import { SinonStub, stub, spy, SinonSpy } from 'sinon'; import { SinonStub, stub, spy, SinonSpy } from 'sinon';
import { expect } from './lib/chai-config'; import { expect } from 'chai';
import * as deviceConfig from '../src/device-config'; import * as deviceConfig from '../src/device-config';
import * as fsUtils from '../src/lib/fs-utils'; import * as fsUtils from '../src/lib/fs-utils';
import * as logger from '../src/logger'; import * as logger from '../src/logger';
@ -131,7 +131,7 @@ describe('Device Backend Config', () => {
it('writes the target config.txt', async () => { it('writes the target config.txt', async () => {
stub(fsUtils, 'writeFileAtomic').resolves(); stub(fsUtils, 'writeFileAtomic').resolves();
stub(child_process, 'exec').resolves(); stub(fsUtils, 'exec').resolves();
const current = { const current = {
HOST_CONFIG_initramfs: 'initramf.gz 0x00800000', HOST_CONFIG_initramfs: 'initramf.gz 0x00800000',
HOST_CONFIG_dtparam: '"i2c=on","audio=on"', HOST_CONFIG_dtparam: '"i2c=on","audio=on"',
@ -154,7 +154,7 @@ describe('Device Backend Config', () => {
// @ts-ignore accessing private value // @ts-ignore accessing private value
await deviceConfig.setBootConfig(configTxtBackend, target); await deviceConfig.setBootConfig(configTxtBackend, target);
expect(child_process.exec).to.be.calledOnce; expect(fsUtils.exec).to.be.calledOnce;
expect(logSpy).to.be.calledTwice; expect(logSpy).to.be.calledTwice;
expect(logSpy.getCall(1).args[2]).to.equal('Apply boot config success'); expect(logSpy.getCall(1).args[2]).to.equal('Apply boot config success');
expect(fsUtils.writeFileAtomic).to.be.calledWith( expect(fsUtils.writeFileAtomic).to.be.calledWith(
@ -171,12 +171,12 @@ describe('Device Backend Config', () => {
// Restore stubs // Restore stubs
(fsUtils.writeFileAtomic as SinonStub).restore(); (fsUtils.writeFileAtomic as SinonStub).restore();
(child_process.exec as SinonStub).restore(); (fsUtils.exec as SinonStub).restore();
}); });
it('ensures required fields are written to config.txt', async () => { it('ensures required fields are written to config.txt', async () => {
stub(fsUtils, 'writeFileAtomic').resolves(); stub(fsUtils, 'writeFileAtomic').resolves();
stub(child_process, 'exec').resolves(); stub(fsUtils, 'exec').resolves();
stub(config, 'get').withArgs('deviceType').resolves('fincm3'); stub(config, 'get').withArgs('deviceType').resolves('fincm3');
const current = { const current = {
HOST_CONFIG_initramfs: 'initramf.gz 0x00800000', HOST_CONFIG_initramfs: 'initramf.gz 0x00800000',
@ -200,7 +200,7 @@ describe('Device Backend Config', () => {
// @ts-ignore accessing private value // @ts-ignore accessing private value
await deviceConfig.setBootConfig(configTxtBackend, target); await deviceConfig.setBootConfig(configTxtBackend, target);
expect(child_process.exec).to.be.calledOnce; expect(fsUtils.exec).to.be.calledOnce;
expect(logSpy).to.be.calledTwice; expect(logSpy).to.be.calledTwice;
expect(logSpy.getCall(1).args[2]).to.equal('Apply boot config success'); expect(logSpy.getCall(1).args[2]).to.equal('Apply boot config success');
expect(fsUtils.writeFileAtomic).to.be.calledWith( expect(fsUtils.writeFileAtomic).to.be.calledWith(
@ -218,7 +218,7 @@ describe('Device Backend Config', () => {
// Restore stubs // Restore stubs
(fsUtils.writeFileAtomic as SinonStub).restore(); (fsUtils.writeFileAtomic as SinonStub).restore();
(child_process.exec as SinonStub).restore(); (fsUtils.exec as SinonStub).restore();
(config.get as SinonStub).restore(); (config.get as SinonStub).restore();
}); });
@ -269,7 +269,7 @@ describe('Device Backend Config', () => {
describe('Extlinux files', () => { describe('Extlinux files', () => {
it('should correctly write to extlinux.conf files', async () => { it('should correctly write to extlinux.conf files', async () => {
stub(fsUtils, 'writeFileAtomic').resolves(); stub(fsUtils, 'writeFileAtomic').resolves();
stub(child_process, 'exec').resolves(); stub(fsUtils, 'exec').resolves();
const current = {}; const current = {};
const target = { const target = {
@ -284,7 +284,7 @@ describe('Device Backend Config', () => {
// @ts-ignore accessing private value // @ts-ignore accessing private value
await deviceConfig.setBootConfig(extlinuxBackend, target); await deviceConfig.setBootConfig(extlinuxBackend, target);
expect(child_process.exec).to.be.calledOnce; expect(fsUtils.exec).to.be.calledOnce;
expect(logSpy).to.be.calledTwice; expect(logSpy).to.be.calledTwice;
expect(logSpy.getCall(1).args[2]).to.equal('Apply boot config success'); expect(logSpy.getCall(1).args[2]).to.equal('Apply boot config success');
expect(fsUtils.writeFileAtomic).to.be.calledWith( expect(fsUtils.writeFileAtomic).to.be.calledWith(
@ -303,7 +303,7 @@ describe('Device Backend Config', () => {
// Restore stubs // Restore stubs
(fsUtils.writeFileAtomic as SinonStub).restore(); (fsUtils.writeFileAtomic as SinonStub).restore();
(child_process.exec as SinonStub).restore(); (fsUtils.exec as SinonStub).restore();
}); });
}); });
@ -395,7 +395,7 @@ describe('Device Backend Config', () => {
describe('ConfigFS files', () => { describe('ConfigFS files', () => {
it('should correctly write to configfs.json files', async () => { it('should correctly write to configfs.json files', async () => {
stub(fsUtils, 'writeFileAtomic').resolves(); stub(fsUtils, 'writeFileAtomic').resolves();
stub(child_process, 'exec').resolves(); stub(fsUtils, 'exec').resolves();
const current = {}; const current = {};
const target = { const target = {
@ -414,7 +414,7 @@ describe('Device Backend Config', () => {
// @ts-ignore accessing private value // @ts-ignore accessing private value
await deviceConfig.setBootConfig(configFsBackend, target); await deviceConfig.setBootConfig(configFsBackend, target);
expect(child_process.exec).to.be.calledOnce; expect(fsUtils.exec).to.be.calledOnce;
expect(logSpy).to.be.calledTwice; expect(logSpy).to.be.calledTwice;
expect(logSpy.getCall(1).args[2]).to.equal('Apply boot config success'); expect(logSpy.getCall(1).args[2]).to.equal('Apply boot config success');
expect(fsUtils.writeFileAtomic).to.be.calledWith( expect(fsUtils.writeFileAtomic).to.be.calledWith(
@ -424,13 +424,13 @@ describe('Device Backend Config', () => {
// Restore stubs // Restore stubs
(fsUtils.writeFileAtomic as SinonStub).restore(); (fsUtils.writeFileAtomic as SinonStub).restore();
(child_process.exec as SinonStub).restore(); (fsUtils.exec as SinonStub).restore();
}); });
it('should correctly load the configfs.json file', async () => { it('should correctly load the configfs.json file', async () => {
stub(child_process, 'exec').resolves(); stub(fsUtils, 'exec').resolves();
stub(fsUtils, 'writeFileAtomic').resolves(); stub(fsUtils, 'writeFileAtomic').resolves();
stub(fs, 'exists').resolves(true); stub(fsUtils, 'exists').resolves(true);
stub(fs, 'mkdir').resolves(); stub(fs, 'mkdir').resolves();
stub(fs, 'readdir').resolves([]); stub(fs, 'readdir').resolves([]);
stub(fs, 'readFile').callsFake((file) => { stub(fs, 'readFile').callsFake((file) => {
@ -445,20 +445,20 @@ describe('Device Backend Config', () => {
}); });
await configFsBackend.initialise(); await configFsBackend.initialise();
expect(child_process.exec).to.be.calledWith('modprobe acpi_configfs'); expect(fsUtils.exec).to.be.calledWith('modprobe acpi_configfs');
expect(child_process.exec).to.be.calledWith( expect(fsUtils.exec).to.be.calledWith(
`mount -t vfat -o remount,rw ${constants.bootBlockDevice} ./test/data/mnt/boot`, `mount -t vfat -o remount,rw ${constants.bootBlockDevice} ./test/data/mnt/boot`,
); );
expect(child_process.exec).to.be.calledWith( expect(fsUtils.exec).to.be.calledWith(
'cat test/data/boot/acpi-tables/spidev1.1.aml > test/data/sys/kernel/config/acpi/table/spidev1.1/aml', 'cat test/data/boot/acpi-tables/spidev1.1.aml > test/data/sys/kernel/config/acpi/table/spidev1.1/aml',
); );
expect((fs.exists as SinonSpy).callCount).to.equal(2); expect((fsUtils.exists as SinonSpy).callCount).to.equal(2);
expect((fs.readFile as SinonSpy).callCount).to.equal(4); expect((fs.readFile as SinonSpy).callCount).to.equal(4);
// Restore stubs // Restore stubs
(fsUtils.writeFileAtomic as SinonStub).restore(); (fsUtils.writeFileAtomic as SinonStub).restore();
(child_process.exec as SinonStub).restore(); (fsUtils.exec as SinonStub).restore();
(fs.exists as SinonStub).restore(); (fsUtils.exists as SinonStub).restore();
(fs.mkdir as SinonStub).restore(); (fs.mkdir as SinonStub).restore();
(fs.readdir as SinonStub).restore(); (fs.readdir as SinonStub).restore();
(fs.readFile as SinonStub).restore(); (fs.readFile as SinonStub).restore();
@ -537,18 +537,18 @@ describe('Device Backend Config', () => {
beforeEach(() => { beforeEach(() => {
// Setup stubs // Setup stubs
stub(fsUtils, 'writeFileAtomic').resolves(); stub(fsUtils, 'writeFileAtomic').resolves();
stub(child_process, 'exec').resolves(); stub(fsUtils, 'exec').resolves();
}); });
afterEach(() => { afterEach(() => {
// Restore stubs // Restore stubs
(fsUtils.writeFileAtomic as SinonStub).restore(); (fsUtils.writeFileAtomic as SinonStub).restore();
(child_process.exec as SinonStub).restore(); (fsUtils.exec as SinonStub).restore();
}); });
it('should correctly write to resin-logo.png', async () => { it('should correctly write to resin-logo.png', async () => {
// Devices with balenaOS < 2.51 use resin-logo.png // Devices with balenaOS < 2.51 use resin-logo.png
stub(fs, 'readdir').resolves(['resin-logo.png']); stub(fs, 'readdir').resolves(['resin-logo.png'] as any);
const current = {}; const current = {};
const target = { const target = {
@ -568,7 +568,7 @@ describe('Device Backend Config', () => {
await deviceConfig.setBootConfig(splashImageBackend, target); await deviceConfig.setBootConfig(splashImageBackend, target);
expect(child_process.exec).to.be.calledOnce; expect(fsUtils.exec).to.be.calledOnce;
expect(logSpy).to.be.calledTwice; expect(logSpy).to.be.calledTwice;
expect(logSpy.getCall(1).args[2]).to.equal('Apply boot config success'); expect(logSpy.getCall(1).args[2]).to.equal('Apply boot config success');
expect(fsUtils.writeFileAtomic).to.be.calledOnceWith( expect(fsUtils.writeFileAtomic).to.be.calledOnceWith(
@ -581,7 +581,7 @@ describe('Device Backend Config', () => {
it('should correctly write to balena-logo.png', async () => { it('should correctly write to balena-logo.png', async () => {
// Devices with balenaOS >= 2.51 use balena-logo.png // Devices with balenaOS >= 2.51 use balena-logo.png
stub(fs, 'readdir').resolves(['balena-logo.png']); stub(fs, 'readdir').resolves(['balena-logo.png'] as any);
const current = {}; const current = {};
const target = { const target = {
@ -601,7 +601,7 @@ describe('Device Backend Config', () => {
await deviceConfig.setBootConfig(splashImageBackend, target); await deviceConfig.setBootConfig(splashImageBackend, target);
expect(child_process.exec).to.be.calledOnce; expect(fsUtils.exec).to.be.calledOnce;
expect(logSpy).to.be.calledTwice; expect(logSpy).to.be.calledTwice;
expect(logSpy.getCall(1).args[2]).to.equal('Apply boot config success'); expect(logSpy.getCall(1).args[2]).to.equal('Apply boot config success');
expect(fsUtils.writeFileAtomic).to.be.calledOnceWith( expect(fsUtils.writeFileAtomic).to.be.calledOnceWith(
@ -634,7 +634,7 @@ describe('Device Backend Config', () => {
await deviceConfig.setBootConfig(splashImageBackend, target); await deviceConfig.setBootConfig(splashImageBackend, target);
expect(child_process.exec).to.be.calledOnce; expect(fsUtils.exec).to.be.calledOnce;
expect(logSpy).to.be.calledTwice; expect(logSpy).to.be.calledTwice;
expect(logSpy.getCall(1).args[2]).to.equal('Apply boot config success'); expect(logSpy.getCall(1).args[2]).to.equal('Apply boot config success');
expect(fsUtils.writeFileAtomic).to.be.calledOnceWith( expect(fsUtils.writeFileAtomic).to.be.calledOnceWith(
@ -646,7 +646,7 @@ describe('Device Backend Config', () => {
}); });
it('should correctly read the splash logo if different from the default', async () => { it('should correctly read the splash logo if different from the default', async () => {
stub(fs, 'readdir').resolves(['balena-logo.png']); stub(fs, 'readdir').resolves(['balena-logo.png'] as any);
const readFileStub: SinonStub = stub(fs, 'readFile').resolves( const readFileStub: SinonStub = stub(fs, 'readFile').resolves(
Buffer.from(png, 'base64') as any, Buffer.from(png, 'base64') as any,

View File

@ -10,7 +10,7 @@ import * as deviceState from '../src/device-state';
import * as dockerUtils from '../src/lib/docker-utils'; import * as dockerUtils from '../src/lib/docker-utils';
import * as images from '../src/compose/images'; import * as images from '../src/compose/images';
import chai = require('./lib/chai-config'); import * as chai from 'chai';
import prepare = require('./lib/prepare'); import prepare = require('./lib/prepare');
import * as db from '../src/db'; import * as db from '../src/db';
import * as dbFormat from '../src/device-state/db-format'; import * as dbFormat from '../src/device-state/db-format';

View File

@ -1,4 +1,4 @@
import { expect } from './lib/chai-config'; import { expect } from 'chai';
import * as conversion from '../src/lib/conversions'; import * as conversion from '../src/lib/conversions';
describe('conversions', function () { describe('conversions', function () {

View File

@ -1,5 +1,5 @@
import { PortMap, PortRange } from '../src/compose/ports'; import { PortMap, PortRange } from '../src/compose/ports';
import { expect } from './lib/chai-config'; import { expect } from 'chai';
// Force cast `PortMap` as a public version so we can test it // Force cast `PortMap` as a public version so we can test it
const PortMapPublic = (PortMap as any) as new ( const PortMapPublic = (PortMap as any) as new (

View File

@ -1,7 +1,7 @@
import { stub } from 'sinon'; import { stub } from 'sinon';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { expect } from './lib/chai-config'; import { expect } from 'chai';
import * as config from '../src/config'; import * as config from '../src/config';
import * as configUtils from '../src/config/utils'; import * as configUtils from '../src/config/utils';
import { ExtraUEnv } from '../src/config/backends/extra-uEnv'; import { ExtraUEnv } from '../src/config/backends/extra-uEnv';

View File

@ -1,5 +1,5 @@
import { SinonStub, stub } from 'sinon'; import { SinonStub, stub } from 'sinon';
import { expect } from './lib/chai-config'; import { expect } from 'chai';
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as apiBinder from '../src/api-binder'; import * as apiBinder from '../src/api-binder';

View File

@ -1,4 +1,4 @@
import { expect } from './lib/chai-config'; import { expect } from 'chai';
import * as ComposeUtils from '../src/compose/utils'; import * as ComposeUtils from '../src/compose/utils';
describe('Composition utilities', () => describe('Composition utilities', () =>

View File

@ -1,7 +1,5 @@
import { assert, expect } from 'chai'; import { assert, expect } from 'chai';
import { SinonStub, stub } from 'sinon'; import { SinonStub, stub } from 'sinon';
import { child_process } from 'mz';
import * as semver from 'semver'; import * as semver from 'semver';
import * as constants from '../src/lib/constants'; import * as constants from '../src/lib/constants';
@ -12,6 +10,7 @@ import {
} from '../src/lib/contracts'; } from '../src/lib/contracts';
import * as osRelease from '../src/lib/os-release'; import * as osRelease from '../src/lib/os-release';
import supervisorVersion = require('../src/lib/supervisor-version'); import supervisorVersion = require('../src/lib/supervisor-version');
import * as fsUtils from '../src/lib/fs-utils';
describe('Container contracts', () => { describe('Container contracts', () => {
before(() => { before(() => {
@ -405,9 +404,10 @@ describe('L4T version detection', () => {
let execStub: SinonStub; let execStub: SinonStub;
const seedExec = (version: string) => { const seedExec = (version: string) => {
execStub = stub(child_process, 'exec').returns( execStub = stub(fsUtils, 'exec').resolves({
Promise.resolve([Buffer.from(version), Buffer.from('')]), stdout: Buffer.from(version),
); stderr: Buffer.from(''),
});
}; };
afterEach(() => { afterEach(() => {

View File

@ -1,7 +1,7 @@
import { SinonStub, stub } from 'sinon'; import { SinonStub, stub } from 'sinon';
import constants = require('../src/lib/constants'); import constants = require('../src/lib/constants');
import { spawnJournalctl } from '../src/lib/journald'; import { spawnJournalctl } from '../src/lib/journald';
import { expect } from './lib/chai-config'; import { expect } from 'chai';
describe('journald', () => { describe('journald', () => {
let spawn: SinonStub; let spawn: SinonStub;

View File

@ -1,8 +1,8 @@
import { child_process, fs } from 'mz'; import { promises as fs } from 'fs';
import { stripIndent } from 'common-tags'; import { stripIndent } from 'common-tags';
import { SinonStub, stub } from 'sinon'; import { SinonStub, stub } from 'sinon';
import { expect } from './lib/chai-config'; import { expect } from 'chai';
import * as fsUtils from '../src/lib/fs-utils'; import * as fsUtils from '../src/lib/fs-utils';
import { Extlinux } from '../src/config/backends/extlinux'; import { Extlinux } from '../src/config/backends/extlinux';
@ -182,7 +182,7 @@ describe('Extlinux Configuration', () => {
it('sets new config values', async () => { it('sets new config values', async () => {
stub(fsUtils, 'writeFileAtomic').resolves(); stub(fsUtils, 'writeFileAtomic').resolves();
stub(child_process, 'exec').resolves(); stub(fsUtils, 'exec').resolves();
await backend.setBootConfig({ await backend.setBootConfig({
fdt: '/boot/mycustomdtb.dtb', fdt: '/boot/mycustomdtb.dtb',
@ -205,7 +205,7 @@ describe('Extlinux Configuration', () => {
// Restore stubs // Restore stubs
(fsUtils.writeFileAtomic as SinonStub).restore(); (fsUtils.writeFileAtomic as SinonStub).restore();
(child_process.exec as SinonStub).restore(); (fsUtils.exec as SinonStub).restore();
}); });
it('only allows supported configuration options', () => { it('only allows supported configuration options', () => {

View File

@ -1,5 +1,5 @@
import { AppendDirective } from '../src/config/backends/extlinux-file'; import { AppendDirective } from '../src/config/backends/extlinux-file';
import { expect } from './lib/chai-config'; import { expect } from 'chai';
describe('APPEND directive', () => { describe('APPEND directive', () => {
const supportedConfigValues = ['isolcpus']; const supportedConfigValues = ['isolcpus'];

View File

@ -1,5 +1,5 @@
import { FDTDirective } from '../src/config/backends/extlinux-file'; import { FDTDirective } from '../src/config/backends/extlinux-file';
import { expect } from './lib/chai-config'; import { expect } from 'chai';
describe('FDT directive', () => { describe('FDT directive', () => {
const directive = new FDTDirective(); const directive = new FDTDirective();

View File

@ -1,8 +1,8 @@
import { child_process, fs } from 'mz'; import { promises as fs } from 'fs';
import { stripIndent } from 'common-tags'; import { stripIndent } from 'common-tags';
import { SinonStub, spy, stub } from 'sinon'; import { SinonStub, spy, stub } from 'sinon';
import { expect } from './lib/chai-config'; import { expect } from 'chai';
import * as fsUtils from '../src/lib/fs-utils'; import * as fsUtils from '../src/lib/fs-utils';
import Log from '../src/lib/supervisor-console'; import Log from '../src/lib/supervisor-console';
import { ExtraUEnv } from '../src/config/backends/extra-uEnv'; import { ExtraUEnv } from '../src/config/backends/extra-uEnv';
@ -67,7 +67,7 @@ describe('extra_uEnv Configuration', () => {
}); });
it('only matches supported devices', async () => { it('only matches supported devices', async () => {
const existsStub = stub(fs, 'exists'); const existsStub = stub(fsUtils, 'exists');
for (const device of MATCH_TESTS) { for (const device of MATCH_TESTS) {
// Test device that has extra_uEnv.txt // Test device that has extra_uEnv.txt
let hasExtraUEnv = true; let hasExtraUEnv = true;
@ -109,7 +109,7 @@ describe('extra_uEnv Configuration', () => {
it('sets new config values', async () => { it('sets new config values', async () => {
stub(fsUtils, 'writeFileAtomic').resolves(); stub(fsUtils, 'writeFileAtomic').resolves();
stub(child_process, 'exec').resolves(); stub(fsUtils, 'exec').resolves();
const logWarningStub = spy(Log, 'warn'); const logWarningStub = spy(Log, 'warn');
// This config contains a value set from something else // This config contains a value set from something else
@ -138,13 +138,13 @@ describe('extra_uEnv Configuration', () => {
// Restore stubs // Restore stubs
(fsUtils.writeFileAtomic as SinonStub).restore(); (fsUtils.writeFileAtomic as SinonStub).restore();
(child_process.exec as SinonStub).restore(); (fsUtils.exec as SinonStub).restore();
logWarningStub.restore(); logWarningStub.restore();
}); });
it('sets new config values containing collections', async () => { it('sets new config values containing collections', async () => {
stub(fsUtils, 'writeFileAtomic').resolves(); stub(fsUtils, 'writeFileAtomic').resolves();
stub(child_process, 'exec').resolves(); stub(fsUtils, 'exec').resolves();
const logWarningStub = spy(Log, 'warn'); const logWarningStub = spy(Log, 'warn');
// @ts-ignore accessing private value // @ts-ignore accessing private value
@ -173,7 +173,7 @@ describe('extra_uEnv Configuration', () => {
// Restore stubs // Restore stubs
(fsUtils.writeFileAtomic as SinonStub).restore(); (fsUtils.writeFileAtomic as SinonStub).restore();
(child_process.exec as SinonStub).restore(); (fsUtils.exec as SinonStub).restore();
logWarningStub.restore(); logWarningStub.restore();
// @ts-ignore accessing private value // @ts-ignore accessing private value
ExtraUEnv.supportedConfigs = previousSupportedConfigs; ExtraUEnv.supportedConfigs = previousSupportedConfigs;

View File

@ -3,7 +3,7 @@ import { promises as fs } from 'fs';
import { resolve } from 'path'; import { resolve } from 'path';
import { expect } from './lib/chai-config'; import { expect } from 'chai';
import Log from '../src/lib/supervisor-console'; import Log from '../src/lib/supervisor-console';
import { Odmdata } from '../src/config/backends/odmdata'; import { Odmdata } from '../src/config/backends/odmdata';

View File

@ -1,7 +1,7 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { stub } from 'sinon'; import { stub } from 'sinon';
import * as systeminformation from 'systeminformation'; import * as systeminformation from 'systeminformation';
import { fs } from 'mz'; import { promises as fs } from 'fs';
import * as sysInfo from '../src/lib/system-info'; import * as sysInfo from '../src/lib/system-info';

View File

@ -1,7 +1,7 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { docker } from '../src/lib/docker-utils'; import { docker } from '../src/lib/docker-utils';
import { expect } from './lib/chai-config'; import { expect } from 'chai';
import * as Images from '../src/compose/images'; import * as Images from '../src/compose/images';
import * as mockedDockerode from './lib/mocked-dockerode'; import * as mockedDockerode from './lib/mocked-dockerode';
import * as mockedDatabase from './lib/mocked-database'; import * as mockedDatabase from './lib/mocked-database';

View File

@ -4,7 +4,7 @@ import { Promise } from 'bluebird';
import * as _ from 'lodash'; import * as _ from 'lodash';
import rewire = require('rewire'); import rewire = require('rewire');
import { expect } from './lib/chai-config'; import { expect } from 'chai';
import { sleep } from './lib/helpers'; import { sleep } from './lib/helpers';
import * as TargetState from '../src/device-state/target-state'; import * as TargetState from '../src/device-state/target-state';
import Log from '../src/lib/supervisor-console'; import Log from '../src/lib/supervisor-console';

View File

@ -13,6 +13,7 @@ import * as supertest from 'supertest';
import * as path from 'path'; import * as path from 'path';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import { exists, unlinkAll } from '../src/lib/fs-utils';
import * as appMock from './lib/application-state-mock'; import * as appMock from './lib/application-state-mock';
import * as mockedDockerode from './lib/mocked-dockerode'; import * as mockedDockerode from './lib/mocked-dockerode';
import mockedAPI = require('./lib/mocked-device-api'); import mockedAPI = require('./lib/mocked-device-api');
@ -826,7 +827,7 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
}); });
it('returns current host config (hostname only)', async () => { it('returns current host config (hostname only)', async () => {
await Promise.all([fs.unlink(redsocksPath), fs.unlink(noProxyPath)]); await unlinkAll(redsocksPath, noProxyPath);
await request await request
.get('/v1/device/host-config') .get('/v1/device/host-config')
@ -839,7 +840,7 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
}); });
it('errors if no hostname file exists', async () => { it('errors if no hostname file exists', async () => {
await fs.unlink(hostnamePath); await unlinkAll(hostnamePath);
await request await request
.get('/v1/device/host-config') .get('/v1/device/host-config')
@ -896,7 +897,7 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
}); });
it('updates the hostname with provided string if string is not empty', async () => { it('updates the hostname with provided string if string is not empty', async () => {
await Promise.all([fs.unlink(redsocksPath), fs.unlink(noProxyPath)]); await unlinkAll(redsocksPath, noProxyPath);
const patchBody = { network: { hostname: 'newdevice' } }; const patchBody = { network: { hostname: 'newdevice' } };
@ -924,8 +925,7 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
}); });
it('updates hostname to first 7 digits of device uuid when sent invalid hostname', async () => { it('updates hostname to first 7 digits of device uuid when sent invalid hostname', async () => {
await Promise.all([fs.unlink(redsocksPath), fs.unlink(noProxyPath)]); await unlinkAll(redsocksPath, noProxyPath);
await request await request
.patch('/v1/device/host-config') .patch('/v1/device/host-config')
.send({ network: { hostname: '' } }) .send({ network: { hostname: '' } })
@ -965,8 +965,8 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
.then(async (response) => { .then(async (response) => {
validatePatchResponse(response); validatePatchResponse(response);
expect(fs.stat(redsocksPath)).to.be.rejected; expect(await exists(redsocksPath)).to.be.false;
expect(fs.stat(noProxyPath)).to.be.rejected; expect(await exists(noProxyPath)).to.be.false;
}); });
expect(restartServiceSpy.callCount).to.equal(2); expect(restartServiceSpy.callCount).to.equal(2);

View File

@ -1,7 +1,7 @@
import { fs, child_process } from 'mz'; import { promises as fs } from 'fs';
import { SinonStub, stub } from 'sinon'; import { SinonStub, stub } from 'sinon';
import { expect } from './lib/chai-config'; import { expect } from 'chai';
import * as fsUtils from '../src/lib/fs-utils'; import * as fsUtils from '../src/lib/fs-utils';
import { SplashImage } from '../src/config/backends/splash-image'; import { SplashImage } from '../src/config/backends/splash-image';
import log from '../src/lib/supervisor-console'; import log from '../src/lib/supervisor-console';
@ -20,27 +20,27 @@ describe('Splash image configuration', () => {
beforeEach(() => { beforeEach(() => {
// Setup stubs // Setup stubs
writeFileAtomicStub = stub(fsUtils, 'writeFileAtomic').resolves(); writeFileAtomicStub = stub(fsUtils, 'writeFileAtomic').resolves();
stub(child_process, 'exec').resolves(); stub(fsUtils, 'exec').resolves();
readFileStub = stub(fs, 'readFile').resolves( readFileStub = stub(fs, 'readFile').resolves(
Buffer.from(logo, 'base64') as any, Buffer.from(logo, 'base64') as any,
); );
readFileStub readFileStub
.withArgs('test/data/mnt/boot/splash/balena-logo-default.png') .withArgs('test/data/mnt/boot/splash/balena-logo-default.png')
.resolves(Buffer.from(defaultLogo, 'base64') as any); .resolves(Buffer.from(defaultLogo, 'base64') as any);
readDirStub = stub(fs, 'readdir').resolves(['balena-logo.png']); readDirStub = stub(fs, 'readdir').resolves(['balena-logo.png'] as any);
}); });
afterEach(() => { afterEach(() => {
// Restore stubs // Restore stubs
writeFileAtomicStub.restore(); writeFileAtomicStub.restore();
(child_process.exec as SinonStub).restore(); (fsUtils.exec as SinonStub).restore();
readFileStub.restore(); readFileStub.restore();
readDirStub.restore(); readDirStub.restore();
}); });
describe('initialise', () => { describe('initialise', () => {
it('should make a copy of the existing boot image on initialise if not yet created', async () => { it('should make a copy of the existing boot image on initialise if not yet created', async () => {
stub(fs, 'exists').resolves(false); stub(fsUtils, 'exists').resolves(false);
// Do the initialization // Do the initialization
await backend.initialise(); await backend.initialise();
@ -55,25 +55,25 @@ describe('Splash image configuration', () => {
Buffer.from(logo, 'base64'), Buffer.from(logo, 'base64'),
); );
(fs.exists as SinonStub).restore(); (fsUtils.exists as SinonStub).restore();
}); });
it('should skip initialization if the default image already exists', async () => { it('should skip initialization if the default image already exists', async () => {
stub(fs, 'exists').resolves(true); stub(fsUtils, 'exists').resolves(true);
// Do the initialization // Do the initialization
await backend.initialise(); await backend.initialise();
expect(fs.exists).to.be.calledOnceWith( expect(fsUtils.exists).to.be.calledOnceWith(
'test/data/mnt/boot/splash/balena-logo-default.png', 'test/data/mnt/boot/splash/balena-logo-default.png',
); );
expect(fs.readFile).to.not.have.been.called; expect(fs.readFile).to.not.have.been.called;
(fs.exists as SinonStub).restore(); (fsUtils.exists as SinonStub).restore();
}); });
it('should fail initialization if there is no default image on the device', async () => { it('should fail initialization if there is no default image on the device', async () => {
stub(fs, 'exists').resolves(false); stub(fsUtils, 'exists').resolves(false);
readDirStub.resolves([]); readDirStub.resolves([]);
readFileStub.rejects(); readFileStub.rejects();
stub(log, 'warn'); stub(log, 'warn');

View File

@ -1,8 +1,8 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { Router } from 'express'; import { Router } from 'express';
import { fs } from 'mz';
import rewire = require('rewire'); import rewire = require('rewire');
import { unlinkAll } from '../../src/lib/fs-utils';
import * as applicationManager from '../../src/compose/application-manager'; import * as applicationManager from '../../src/compose/application-manager';
import * as networkManager from '../../src/compose/network-manager'; import * as networkManager from '../../src/compose/network-manager';
import * as serviceManager from '../../src/compose/service-manager'; import * as serviceManager from '../../src/compose/service-manager';
@ -149,12 +149,8 @@ async function create(): Promise<SupervisorAPI> {
} }
async function cleanUp(): Promise<void> { async function cleanUp(): Promise<void> {
try { // Clean up test data
// clean up test data await unlinkAll(DB_PATH);
await fs.unlink(DB_PATH);
} catch (e) {
/* noop */
}
// Restore created SinonStubs // Restore created SinonStubs
return restoreStubs(); return restoreStubs();
} }

View File

@ -1,7 +1,7 @@
import _ = require('lodash'); import _ = require('lodash');
import { expect } from 'chai'; import { expect } from 'chai';
import { stub } from 'sinon'; import { stub } from 'sinon';
import { child_process } from 'mz'; import * as childProcess from 'child_process';
import * as firewall from '../../src/lib/firewall'; import * as firewall from '../../src/lib/firewall';
import * as iptables from '../../src/lib/iptables'; import * as iptables from '../../src/lib/iptables';
@ -130,7 +130,7 @@ export const whilstMocked = async (
) => { ) => {
const getOriginalDefaultRuleAdaptor = iptables.getDefaultRuleAdaptor; const getOriginalDefaultRuleAdaptor = iptables.getDefaultRuleAdaptor;
const spawnStub = stub(child_process, 'spawn').callsFake(() => { const spawnStub = stub(childProcess, 'spawn').callsFake(() => {
const fakeProc = new EventEmitter(); const fakeProc = new EventEmitter();
(fakeProc as any).stdout = new EventEmitter(); (fakeProc as any).stdout = new EventEmitter();

View File

@ -1,3 +1,3 @@
--exit --exit
--timeout 30000 --timeout 30000
build/test/**/*.js build/test/**/*.spec.js

View File

@ -1,7 +1,6 @@
import ChaiConfig = require('../lib/chai-config'); import { expect } from 'chai';
const { expect } = ChaiConfig;
import { Network } from '../../src/compose/network'; import { Network } from '../../../src/compose/network';
import { NetworkInspectInfo } from 'dockerode'; import { NetworkInspectInfo } from 'dockerode';
describe('compose/network', () => { describe('compose/network', () => {

View File

@ -0,0 +1,157 @@
import { expect } from 'chai';
import * as path from 'path';
import { promises as fs } from 'fs';
import { spy, SinonSpy } from 'sinon';
import mock = require('mock-fs');
import * as fsUtils from '../../../src/lib/fs-utils';
import { rootMountPoint } from '../../../src/lib/constants';
describe('lib/fs-utils', () => {
const testFileName1 = 'file.1';
const testFileName2 = 'file.2';
const testFile1 = path.join(rootMountPoint, testFileName1);
const testFile2 = path.join(rootMountPoint, testFileName2);
const mockFs = () => {
mock({
[testFile1]: 'foo',
[testFile2]: 'bar',
});
};
const unmockFs = () => {
mock.restore();
};
describe('writeAndSyncFile', () => {
before(mockFs);
after(unmockFs);
it('should write and sync string data', async () => {
await fsUtils.writeAndSyncFile(testFile1, 'foo bar');
expect(await fs.readFile(testFile1, 'utf-8')).to.equal('foo bar');
});
it('should write and sync buffers', async () => {
await fsUtils.writeAndSyncFile(testFile1, Buffer.from('bar foo'));
expect(await fs.readFile(testFile1, 'utf-8')).to.equal('bar foo');
});
});
describe('writeFileAtomic', () => {
before(() => {
spy(fs, 'rename');
mockFs();
});
after(() => {
(fs.rename as SinonSpy).restore();
unmockFs();
});
it('should write string data atomically', async () => {
await fsUtils.writeFileAtomic(testFile1, 'foo baz');
expect(await fs.readFile(testFile1, 'utf-8')).to.equal('foo baz');
expect(fs.rename).to.have.been.calledWith(`${testFile1}.new`, testFile1);
});
it('should write buffer data atomically', async () => {
await fsUtils.writeFileAtomic(testFile1, 'baz foo');
expect(await fs.readFile(testFile1, 'utf-8')).to.equal('baz foo');
expect(fs.rename).to.have.been.calledWith(`${testFile1}.new`, testFile1);
});
});
describe('safeRename', () => {
beforeEach(mockFs);
afterEach(unmockFs);
it('should rename a file', async () => {
await fsUtils.safeRename(testFile1, testFile1 + 'rename');
const dirContents = await fs.readdir(rootMountPoint);
expect(dirContents).to.have.length(2);
expect(dirContents).to.not.include(testFileName1);
expect(dirContents).to.include(testFileName1 + 'rename');
});
it('should replace an existing file', async () => {
await fsUtils.safeRename(testFile1, testFile2);
const dirContents = await fs.readdir(rootMountPoint);
expect(dirContents).to.have.length(1);
expect(dirContents).to.include(testFileName2);
expect(dirContents).to.not.include(testFileName1);
});
});
/**
* TODO: Un-skip this test after all fs tests that write to a test file system use
* mock-fs instead. Hypothesis: exists isn't handling the relative directory it's
* being passed well. When all unit tests use mock-fs, we can set process.env.ROOT_MOUNTPOINT
* to `/mnt/root` so we can have an absolute path in all these tests.
*/
describe.skip('exists', () => {
before(mockFs);
after(unmockFs);
it('should return whether a file exists', async () => {
expect(await fsUtils.exists(testFile1)).to.be.true;
await fs.unlink(testFile1).catch(() => {
/* noop */
});
expect(await fsUtils.exists(testFile1)).to.be.false;
});
});
describe('pathExistsOnHost', () => {
before(mockFs);
after(unmockFs);
it('should return whether a file exists in host OS fs', async () => {
expect(await fsUtils.pathExistsOnHost(testFileName1)).to.be.true;
await fs.unlink(testFile1);
expect(await fsUtils.pathExistsOnHost(testFileName1)).to.be.false;
});
});
describe('mkdirp', () => {
before(mockFs);
after(unmockFs);
it('should recursively create directories', async () => {
await fsUtils.mkdirp(
path.join(rootMountPoint, 'test1', 'test2', 'test3'),
);
expect(() =>
fs.readdir(path.join(rootMountPoint, 'test1', 'test2', 'test3')),
).to.not.throw();
});
});
describe('unlinkAll', () => {
beforeEach(mockFs);
afterEach(unmockFs);
it('should unlink a single file', async () => {
await fsUtils.unlinkAll(testFile1);
expect(await fs.readdir(rootMountPoint)).to.not.include(testFileName1);
});
it('should unlink multiple files', async () => {
await fsUtils.unlinkAll(testFile1, testFile2);
expect(await fs.readdir(rootMountPoint)).to.have.length(0);
});
});
describe('getPathOnHost', () => {
before(mockFs);
after(unmockFs);
it("should return the paths of one or more files as they exist on host OS's root", async () => {
expect(fsUtils.getPathOnHost(testFileName1)).to.deep.equal([testFile1]);
expect(
fsUtils.getPathOnHost(...[testFileName1, testFileName2]),
).to.deep.equal([testFile1, testFile2]);
});
});
});