Update @balena/lint to v7

This updates balena lint to the latest version to enable eslint support
and unblock Typescript updates. This is a huge number of changes as the
linting rules are much more strict now, requiring modifiying most files
in the codebase. This commit also bumps the test dependency `rewire` as
that was interfering with the update of balena-lint

Change-type: patch
This commit is contained in:
Felipe Lalanne
2024-02-29 19:00:39 -03:00
parent 8750951521
commit 988a1c9e9a
136 changed files with 7256 additions and 2756 deletions

View File

@ -2,10 +2,4 @@
"*.ts": [ "*.ts": [
"balena-lint --typescript --fix", "balena-lint --typescript --fix",
], ],
"*.js": [
"balena-lint --typescript --fix",
],
"test/**/*.ts": [
"balena-lint --typescript --no-prettier --tests"
],
} }

8896
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@
"scripts": { "scripts": {
"start": "./entry.sh", "start": "./entry.sh",
"build": "npm run clean && npm run release && webpack", "build": "npm run clean && npm run release && webpack",
"lint": "balena-lint -e ts -e js src/ test/ typings/ build-utils/ webpack.config.js", "lint": "balena-lint -e ts -e js src/ test/ typings/ build-utils/",
"test:build": "tsc --noEmit && tsc --noEmit --project tsconfig.js.json", "test:build": "tsc --noEmit && tsc --noEmit --project tsconfig.js.json",
"test:unit": "mocha --config test/unit/.mocharc.js", "test:unit": "mocha --config test/unit/.mocharc.js",
"test:integration": "find test/integration -name *.spec.ts | xargs -n 1 -I {} sh -c 'mocha --config test/integration/.mocharc.js {} || exit 255'", "test:integration": "find test/integration -name *.spec.ts | xargs -n 1 -I {} sh -c 'mocha --config test/integration/.mocharc.js {} || exit 255'",
@ -21,7 +21,7 @@
"test:compose": "(ARCH=$(./build-utils/detect-arch.sh) docker compose -f docker-compose.yml -f docker-compose.test.yml run --build --rm sut || docker compose logs); npm run compose:down", "test:compose": "(ARCH=$(./build-utils/detect-arch.sh) docker compose -f docker-compose.yml -f docker-compose.test.yml run --build --rm sut || docker compose logs); npm run compose:down",
"test": "npm run lint && npm run test:build && npm run test:unit", "test": "npm run lint && npm run test:build && npm run test:unit",
"compose:down": "docker compose -f docker-compose.yml -f docker-compose.test.yml down --volumes", "compose:down": "docker compose -f docker-compose.yml -f docker-compose.test.yml down --volumes",
"prettify": "balena-lint -e ts -e js --fix src/ test/ typings/ build-utils/ webpack.config.js", "prettify": "balena-lint -e ts -e js --fix src/ test/ typings/ build-utils/",
"release": "tsc --project tsconfig.release.json && mv build/src/* build", "release": "tsc --project tsconfig.release.json && mv build/src/* build",
"sync": "ts-node --files sync/sync.ts", "sync": "ts-node --files sync/sync.ts",
"clean": "rimraf build", "clean": "rimraf build",
@ -42,7 +42,7 @@
"@balena/contrato": "^0.6.0", "@balena/contrato": "^0.6.0",
"@balena/es-version": "^1.0.1", "@balena/es-version": "^1.0.1",
"@balena/happy-eyeballs": "0.0.6", "@balena/happy-eyeballs": "0.0.6",
"@balena/lint": "^6.2.0", "@balena/lint": "^7.3.0",
"@types/bluebird": "^3.5.37", "@types/bluebird": "^3.5.37",
"@types/chai": "^4.3.3", "@types/chai": "^4.3.3",
"@types/chai-as-promised": "^7.1.5", "@types/chai-as-promised": "^7.1.5",
@ -53,6 +53,7 @@
"@types/dockerode": "^2.5.34", "@types/dockerode": "^2.5.34",
"@types/event-stream": "^3.3.34", "@types/event-stream": "^3.3.34",
"@types/express": "^4.17.14", "@types/express": "^4.17.14",
"@types/json-mask": "2.0.3",
"@types/lodash": "^4.14.186", "@types/lodash": "^4.14.186",
"@types/memoizee": "^0.4.8", "@types/memoizee": "^0.4.8",
"@types/mocha": "^8.2.3", "@types/mocha": "^8.2.3",
@ -116,7 +117,7 @@
"request": "^2.88.2", "request": "^2.88.2",
"resin-docker-build": "^1.1.6", "resin-docker-build": "^1.1.6",
"resumable-request": "^2.0.1", "resumable-request": "^2.0.1",
"rewire": "^5.0.0", "rewire": "^6.0.0",
"rimraf": "^2.7.1", "rimraf": "^2.7.1",
"rwlock": "^5.0.0", "rwlock": "^5.0.0",
"semver": "7.5.4", "semver": "7.5.4",

View File

@ -147,7 +147,7 @@ export async function start() {
} }
log.debug('Starting current state report'); log.debug('Starting current state report');
await startCurrentStateReport(); void startCurrentStateReport();
// When we've provisioned, try to load the backup. We // When we've provisioned, try to load the backup. We
// must wait for the provisioning because we need a // must wait for the provisioning because we need a
@ -158,7 +158,7 @@ export async function start() {
readyForUpdates = true; readyForUpdates = true;
log.debug('Starting target state poll'); log.debug('Starting target state poll');
TargetState.startPoll(); void TargetState.startPoll();
// Update and apply new target state // Update and apply new target state
TargetState.emitter.on( TargetState.emitter.on(
'target-state-update', 'target-state-update',
@ -235,7 +235,7 @@ export function startCurrentStateReport() {
'Trying to start state reporting without initializing API client', 'Trying to start state reporting without initializing API client',
); );
} }
startReporting(); return startReporting();
} }
export async function fetchDeviceTags(): Promise<DeviceTag[]> { export async function fetchDeviceTags(): Promise<DeviceTag[]> {

View File

@ -1,17 +1,17 @@
import * as url from 'url'; import * as url from 'url';
import * as _ from 'lodash'; import type { CoreOptions } from 'request';
import { CoreOptions } from 'request';
import { performance } from 'perf_hooks'; import { performance } from 'perf_hooks';
import { setTimeout } from 'timers/promises'; import { setTimeout } from 'timers/promises';
import { readFile } from 'fs/promises'; import { readFile } from 'fs/promises';
import { DeviceState } from '../types'; import { DeviceState } from '../types';
import * as config from '../config'; import * as config from '../config';
import { SchemaTypeKey, SchemaReturn } from '../config/schema-type'; import type { SchemaTypeKey, SchemaReturn } from '../config/schema-type';
import * as eventTracker from '../event-tracker'; import * as eventTracker from '../event-tracker';
import * as deviceState from '../device-state'; import * as deviceState from '../device-state';
import { withBackoff, OnFailureInfo } from '../lib/backoff'; import type { OnFailureInfo } from '../lib/backoff';
import { withBackoff } from '../lib/backoff';
import { log } from '../lib/supervisor-console'; import { log } from '../lib/supervisor-console';
import { InternalInconsistencyError, StatusError } from '../lib/errors'; import { InternalInconsistencyError, StatusError } from '../lib/errors';
import { getRequestInstance } from '../lib/request'; import { getRequestInstance } from '../lib/request';

View File

@ -107,7 +107,9 @@ async function mdnsLookup(
// Make NodeJS RFC 3484 compliant for properly handling IPv6 // Make NodeJS RFC 3484 compliant for properly handling IPv6
// See: https://github.com/nodejs/node/pull/14731 // See: https://github.com/nodejs/node/pull/14731
// https://github.com/nodejs/node/pull/17793 // https://github.com/nodejs/node/pull/17793
const dns = require('dns'); // We disable linting for the next line. The require call
// is necesary for monkey-patching the dns module
const dns = require('dns'); // eslint-disable-line
const { lookup } = dns; const { lookup } = dns;
dns.lookup = ( dns.lookup = (

View File

@ -1,18 +1,18 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import { ImageInspectInfo } from 'dockerode'; import type { ImageInspectInfo } from 'dockerode';
import Network from './network'; import Network from './network';
import Volume from './volume'; import Volume from './volume';
import Service from './service'; import Service from './service';
import * as imageManager from './images'; import * as imageManager from './images';
import type { Image } from './images'; import type { Image } from './images';
import { import type {
CompositionStep, CompositionStep,
generateStep,
CompositionStepAction, CompositionStepAction,
} from './composition-steps'; } from './composition-steps';
import * as targetStateCache from '../device-state/target-state-cache'; import { generateStep } from './composition-steps';
import type * as targetStateCache from '../device-state/target-state-cache';
import { getNetworkGateway } from '../lib/docker-utils'; import { getNetworkGateway } from '../lib/docker-utils';
import * as constants from '../lib/constants'; import * as constants from '../lib/constants';
import { import {
@ -22,7 +22,7 @@ import {
import { isNotFoundError } from '../lib/errors'; import { isNotFoundError } from '../lib/errors';
import * as config from '../config'; import * as config from '../config';
import { checkTruthy } from '../lib/validation'; import { checkTruthy } from '../lib/validation';
import { ServiceComposeConfig, DeviceMetadata } from './types/service'; import type { ServiceComposeConfig, DeviceMetadata } from './types/service';
import { pathExistsOnRoot } from '../lib/host-utils'; import { pathExistsOnRoot } from '../lib/host-utils';
import { isSupervisor } from '../lib/supervisor-metadata'; import { isSupervisor } from '../lib/supervisor-metadata';
@ -64,7 +64,10 @@ export class App {
public networks: Network[]; public networks: Network[];
public volumes: Volume[]; public volumes: Volume[];
public constructor(opts: AppConstructOpts, public isTargetState: boolean) { public constructor(
opts: AppConstructOpts,
public isTargetState: boolean,
) {
this.appId = opts.appId; this.appId = opts.appId;
this.appUuid = opts.appUuid; this.appUuid = opts.appUuid;
this.appName = opts.appName; this.appName = opts.appName;
@ -513,12 +516,18 @@ export class App {
return; return;
} }
const needsDownload = !context.availableImages.some( const needsDownload =
target != null &&
!context.availableImages.some(
(image) => (image) =>
image.dockerImageId === target?.config.image || image.dockerImageId === target.config.image ||
imageManager.isSameImage(image, { name: target?.imageName! }), imageManager.isSameImage(image, { name: target.imageName! }),
); );
if (needsDownload && context.downloading.includes(target?.imageName!)) { if (
target != null &&
needsDownload &&
context.downloading.includes(target.imageName!)
) {
// The image needs to be downloaded, and it's currently downloading. // The image needs to be downloaded, and it's currently downloading.
// We simply keep the application loop alive // We simply keep the application loop alive
return generateStep('noop', {}); return generateStep('noop', {});

View File

@ -1,9 +1,10 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import StrictEventEmitter from 'strict-event-emitter-types'; import type StrictEventEmitter from 'strict-event-emitter-types';
import * as config from '../config'; import * as config from '../config';
import { transaction, Transaction } from '../db'; import type { Transaction } from '../db';
import { transaction } from '../db';
import * as logger from '../logger'; import * as logger from '../logger';
import LocalModeManager from '../local-mode'; import LocalModeManager from '../local-mode';
@ -25,9 +26,9 @@ import * as networkManager from './network-manager';
import * as serviceManager from './service-manager'; import * as serviceManager from './service-manager';
import * as imageManager from './images'; import * as imageManager from './images';
import * as commitStore from './commit'; import * as commitStore from './commit';
import Service from './service'; import type Service from './service';
import Network from './network'; import type Network from './network';
import Volume from './volume'; import type Volume from './volume';
import { generateStep, getExecutors } from './composition-steps'; import { generateStep, getExecutors } from './composition-steps';
import type { import type {
@ -45,11 +46,11 @@ type ApplicationManagerEventEmitter = StrictEventEmitter<
{ change: DeviceLegacyReport } { change: DeviceLegacyReport }
>; >;
const events: ApplicationManagerEventEmitter = new EventEmitter(); const events: ApplicationManagerEventEmitter = new EventEmitter();
export const on: typeof events['on'] = events.on.bind(events); export const on: (typeof events)['on'] = events.on.bind(events);
export const once: typeof events['once'] = events.once.bind(events); export const once: (typeof events)['once'] = events.once.bind(events);
export const removeListener: typeof events['removeListener'] = export const removeListener: (typeof events)['removeListener'] =
events.removeListener.bind(events); events.removeListener.bind(events);
export const removeAllListeners: typeof events['removeAllListeners'] = export const removeAllListeners: (typeof events)['removeAllListeners'] =
events.removeAllListeners.bind(events); events.removeAllListeners.bind(events);
const localModeManager = new LocalModeManager(); const localModeManager = new LocalModeManager();
@ -611,7 +612,7 @@ export async function serviceNameFromId(serviceId: number) {
(svcName) => services[svcName].id === serviceId, (svcName) => services[svcName].id === serviceId,
); );
if (!!serviceName) { if (serviceName) {
return serviceName; return serviceName;
} }
} }
@ -771,7 +772,7 @@ function saveAndRemoveImages(
: availableAndUnused.filter((image) => !deltaSources.includes(image.name)); : availableAndUnused.filter((image) => !deltaSources.includes(image.name));
return imagesToSave return imagesToSave
.map((image) => ({ action: 'saveImage', image } as CompositionStep)) .map((image) => ({ action: 'saveImage', image }) as CompositionStep)
.concat(imagesToRemove.map((image) => ({ action: 'removeImage', image }))); .concat(imagesToRemove.map((image) => ({ action: 'removeImage', image })));
} }
@ -948,7 +949,7 @@ export async function getState() {
// We cannot report services that do not have an image as the API // We cannot report services that do not have an image as the API
// requires passing the image name // requires passing the image name
.filter(([, img]) => !!img) .filter(([, img]) => !!img)
.map(([svc, img]) => ({ ...img, ...svc } as ServiceInfo)) .map(([svc, img]) => ({ ...img, ...svc }) as ServiceInfo)
.map((svc, __, serviceList) => { .map((svc, __, serviceList) => {
// If the service is not running it cannot be a handover // If the service is not running it cannot be a handover
if (svc.status !== 'Running') { if (svc.status !== 'Running') {

View File

@ -4,15 +4,15 @@ import * as config from '../config';
import type { Image } from './images'; import type { Image } from './images';
import * as images from './images'; import * as images from './images';
import Network from './network'; import type Network from './network';
import Service from './service'; import type Service from './service';
import * as serviceManager from './service-manager'; import * as serviceManager from './service-manager';
import Volume from './volume'; import type Volume from './volume';
import { checkTruthy } from '../lib/validation'; import { checkTruthy } from '../lib/validation';
import * as networkManager from './network-manager'; import * as networkManager from './network-manager';
import * as volumeManager from './volume-manager'; import * as volumeManager from './volume-manager';
import { DeviceLegacyReport } from '../types/state'; import type { DeviceLegacyReport } from '../types/state';
import * as commitStore from './commit'; import * as commitStore from './commit';
interface BaseCompositionStepArgs { interface BaseCompositionStepArgs {
@ -81,7 +81,7 @@ interface CompositionStepArgs {
saveImage: { saveImage: {
image: Image; image: Image;
}; };
cleanup: {}; cleanup: object;
createNetwork: { createNetwork: {
target: Network; target: Network;
}; };
@ -94,8 +94,8 @@ interface CompositionStepArgs {
removeVolume: { removeVolume: {
current: Volume; current: Volume;
}; };
ensureSupervisorNetwork: {}; ensureSupervisorNetwork: object;
noop: {}; noop: object;
} }
export type CompositionStepAction = keyof CompositionStepArgs; export type CompositionStepAction = keyof CompositionStepArgs;

View File

@ -10,7 +10,10 @@ export class InvalidNetworkNameError extends TypedError {
} }
export class ResourceRecreationAttemptError extends TypedError { export class ResourceRecreationAttemptError extends TypedError {
public constructor(public resource: string, public name: string) { public constructor(
public resource: string,
public name: string,
) {
super( super(
`Trying to create ${resource} with name: ${name}, but a ${resource} ` + `Trying to create ${resource} with name: ${name}, but a ${resource} ` +
'with that name and a different configuration already exists', 'with that name and a different configuration already exists',

View File

@ -1,12 +1,13 @@
import * as Docker from 'dockerode'; import type * as Docker from 'dockerode';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import * as _ from 'lodash'; import * as _ from 'lodash';
import StrictEventEmitter from 'strict-event-emitter-types'; import type StrictEventEmitter from 'strict-event-emitter-types';
import * as config from '../config'; import * as config from '../config';
import * as db from '../db'; import * as db from '../db';
import * as constants from '../lib/constants'; import * as constants from '../lib/constants';
import { DeltaFetchOptions, FetchOptions, docker } from '../lib/docker-utils'; import type { DeltaFetchOptions, FetchOptions } from '../lib/docker-utils';
import { docker } from '../lib/docker-utils';
import * as dockerUtils from '../lib/docker-utils'; import * as dockerUtils from '../lib/docker-utils';
import { import {
DeltaStillProcessingError, DeltaStillProcessingError,
@ -67,11 +68,11 @@ class ImageEventEmitter extends (EventEmitter as new () => StrictEventEmitter<
>) {} >) {}
const events = new ImageEventEmitter(); const events = new ImageEventEmitter();
export const on: typeof events['on'] = events.on.bind(events); export const on: (typeof events)['on'] = events.on.bind(events);
export const once: typeof events['once'] = events.once.bind(events); export const once: (typeof events)['once'] = events.once.bind(events);
export const removeListener: typeof events['removeListener'] = export const removeListener: (typeof events)['removeListener'] =
events.removeListener.bind(events); events.removeListener.bind(events);
export const removeAllListeners: typeof events['removeAllListeners'] = export const removeAllListeners: (typeof events)['removeAllListeners'] =
events.removeAllListeners.bind(events); events.removeAllListeners.bind(events);
const imageFetchFailures: Dictionary<number> = {}; const imageFetchFailures: Dictionary<number> = {};
@ -144,15 +145,17 @@ function reportEvent(event: 'start' | 'update' | 'finish', state: Image) {
switch (event) { switch (event) {
case 'start': case 'start':
return true; // always report change on start return true; // always report change on start
case 'update': case 'update': {
const [updatedTask, changedAfterUpdate] = currentTask.update(state); const [updatedTask, changedAfterUpdate] = currentTask.update(state);
runningTasks[imageName] = updatedTask; runningTasks[imageName] = updatedTask;
return changedAfterUpdate; // report change only if the task context changed return changedAfterUpdate; // report change only if the task context changed
case 'finish': }
case 'finish': {
const [, changedAfterFinish] = currentTask.finish(); const [, changedAfterFinish] = currentTask.finish();
delete runningTasks[imageName]; delete runningTasks[imageName];
return changedAfterFinish; // report change depending on the state of the task return changedAfterFinish; // report change depending on the state of the task
} }
}
})(); })();
if (stateChanged) { if (stateChanged) {
@ -551,7 +554,7 @@ const inspectByReference = async (imageName: string) => {
filters: { reference: [reference] }, filters: { reference: [reference] },
}) })
.then(([img]) => .then(([img]) =>
!!img img
? docker.getImage(img.Id).inspect() ? docker.getImage(img.Id).inspect()
: Promise.reject( : Promise.reject(
new StatusError( new StatusError(
@ -582,7 +585,7 @@ const inspectByDigest = async (imageName: string) => {
// Assume that all db entries will point to the same dockerImageId, so use // Assume that all db entries will point to the same dockerImageId, so use
// the first one. If this assumption is false, there is a bug with cleanup // the first one. If this assumption is false, there is a bug with cleanup
.then(([img]) => .then(([img]) =>
!!img img
? docker.getImage(img.dockerImageId).inspect() ? docker.getImage(img.dockerImageId).inspect()
: Promise.reject( : Promise.reject(
new StatusError( new StatusError(

View File

@ -1,5 +1,5 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as dockerode from 'dockerode'; import type * as dockerode from 'dockerode';
import { docker } from '../lib/docker-utils'; import { docker } from '../lib/docker-utils';
import logTypes = require('../lib/log-types'); import logTypes = require('../lib/log-types');
@ -7,7 +7,7 @@ import * as logger from '../logger';
import log from '../lib/supervisor-console'; import log from '../lib/supervisor-console';
import * as ComposeUtils from './utils'; import * as ComposeUtils from './utils';
import { import type {
ComposeNetworkConfig, ComposeNetworkConfig,
NetworkConfig, NetworkConfig,
NetworkInspectInfo, NetworkInspectInfo,
@ -22,7 +22,9 @@ export class Network {
public name: string; public name: string;
public config: NetworkConfig; public config: NetworkConfig;
private constructor() {} private constructor() {
/* do not allow instances using `new` */
}
private static deconstructDockerName( private static deconstructDockerName(
name: string, name: string,

View File

@ -15,7 +15,7 @@ export interface PortBindings {
} }
export interface DockerPortOptions { export interface DockerPortOptions {
exposedPorts: Dictionary<{}>; exposedPorts: Dictionary<EmptyObject>;
portBindings: PortBindings; portBindings: PortBindings;
} }
@ -49,7 +49,7 @@ export class PortMap {
this.ports.externalEnd, this.ports.externalEnd,
); );
const exposedPorts: { [key: string]: {} } = {}; const exposedPorts: { [key: string]: EmptyObject } = {};
const portBindings: PortBindings = {}; const portBindings: PortBindings = {};
_.zipWith(internalRange, externalRange, (internal, external) => { _.zipWith(internalRange, externalRange, (internal, external) => {

View File

@ -1,6 +1,6 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { ConfigMap, ServiceComposeConfig } from './types/service'; import type { ConfigMap, ServiceComposeConfig } from './types/service';
import log from '../lib/supervisor-console'; import log from '../lib/supervisor-console';

View File

@ -1,10 +1,10 @@
import * as Dockerode from 'dockerode'; import type * as Dockerode from 'dockerode';
import { EventEmitter } from 'events'; 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 { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import StrictEventEmitter from 'strict-event-emitter-types'; import type StrictEventEmitter from 'strict-event-emitter-types';
import * as config from '../config'; import * as config from '../config';
import { docker } from '../lib/docker-utils'; import { docker } from '../lib/docker-utils';
@ -12,15 +12,16 @@ import * as logger from '../logger';
import { PermissiveNumber } from '../config/types'; import { PermissiveNumber } from '../config/types';
import * as constants from '../lib/constants'; import * as constants from '../lib/constants';
import type { StatusCodeError } from '../lib/errors';
import { import {
InternalInconsistencyError, InternalInconsistencyError,
isNotFoundError, isNotFoundError,
StatusCodeError,
isStatusError, isStatusError,
} from '../lib/errors'; } from '../lib/errors';
import * as LogTypes from '../lib/log-types'; import * as LogTypes from '../lib/log-types';
import { checkInt, isValidDeviceName } from '../lib/validation'; import { checkInt, isValidDeviceName } from '../lib/validation';
import { Service, ServiceStatus } from './service'; import type { ServiceStatus } from './service';
import { Service } from './service';
import { serviceNetworksToDockerNetworks } from './utils'; import { serviceNetworksToDockerNetworks } from './utils';
import log from '../lib/supervisor-console'; import log from '../lib/supervisor-console';
@ -41,11 +42,11 @@ interface KillOpts {
wait?: boolean; wait?: boolean;
} }
export const on: typeof events['on'] = events.on.bind(events); export const on: (typeof events)['on'] = events.on.bind(events);
export const once: typeof events['once'] = events.once.bind(events); export const once: (typeof events)['once'] = events.once.bind(events);
export const removeListener: typeof events['removeListener'] = export const removeListener: (typeof events)['removeListener'] =
events.removeListener.bind(events); events.removeListener.bind(events);
export const removeAllListeners: typeof events['removeAllListeners'] = export const removeAllListeners: (typeof events)['removeAllListeners'] =
events.removeAllListeners.bind(events); events.removeAllListeners.bind(events);
// Whether a container has died, indexed by ID // Whether a container has died, indexed by ID
@ -359,7 +360,7 @@ export async function start(service: Service) {
); );
} }
logger.attach(container.id, { serviceId, imageId }); void logger.attach(container.id, { serviceId, imageId });
if (!alreadyStarted) { if (!alreadyStarted) {
logger.logSystemEvent(LogTypes.startServiceSuccess, { service }); logger.logSystemEvent(LogTypes.startServiceSuccess, { service });
@ -421,7 +422,7 @@ export function listenToEvents() {
`serviceId and imageId not defined for service: ${service.serviceName} in ServiceManager.listenToEvents`, `serviceId and imageId not defined for service: ${service.serviceName} in ServiceManager.listenToEvents`,
); );
} }
logger.attach(data.id, { void logger.attach(data.id, {
serviceId, serviceId,
imageId, imageId,
}); });
@ -447,7 +448,7 @@ export function listenToEvents() {
}); });
}; };
(async () => { void (async () => {
try { try {
await listen(); await listen();
} catch (e) { } catch (e) {
@ -479,7 +480,7 @@ export async function attachToRunning() {
`containerId not defined for service: ${service.serviceName} in ServiceManager.attachToRunning`, `containerId not defined for service: ${service.serviceName} in ServiceManager.attachToRunning`,
); );
} }
logger.attach(service.containerId, { void logger.attach(service.containerId, {
serviceId, serviceId,
imageId, imageId,
}); });

View File

@ -1,10 +1,11 @@
import { detailedDiff as diff } from 'deep-object-diff'; import { detailedDiff as diff } from 'deep-object-diff';
import * as Dockerode from 'dockerode'; import type * as Dockerode from 'dockerode';
import Duration = require('duration-js'); import Duration = require('duration-js');
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as path from 'path'; import * as path from 'path';
import { DockerPortOptions, PortMap } from './ports'; import type { DockerPortOptions } from './ports';
import { PortMap } from './ports';
import * as ComposeUtils from './utils'; import * as ComposeUtils from './utils';
import * as updateLock from '../lib/update-lock'; import * as updateLock from '../lib/update-lock';
import { sanitiseComposeConfig } from './sanitise'; import { sanitiseComposeConfig } from './sanitise';
@ -14,14 +15,16 @@ import * as conversions from '../lib/conversions';
import { checkInt } from '../lib/validation'; import { checkInt } from '../lib/validation';
import { InternalInconsistencyError } from '../lib/errors'; import { InternalInconsistencyError } from '../lib/errors';
import { EnvVarObject } from '../types'; import type { EnvVarObject } from '../types';
import { import type {
ServiceConfig, ServiceConfig,
ServiceConfigArrayField, ServiceConfigArrayField,
ServiceComposeConfig, ServiceComposeConfig,
ConfigMap, ConfigMap,
DeviceMetadata, DeviceMetadata,
DockerDevice, DockerDevice,
} from './types/service';
import {
ShortMount, ShortMount,
ShortBind, ShortBind,
ShortAnonymousVolume, ShortAnonymousVolume,
@ -108,7 +111,9 @@ export class Service {
'hostname', 'hostname',
].concat(Service.allConfigArrayFields); ].concat(Service.allConfigArrayFields);
private constructor() {} private constructor() {
/* do not allow instancing a service object with `new` */
}
// The type here is actually ServiceComposeConfig, except that the // The type here is actually ServiceComposeConfig, except that the
// keys must be camelCase'd first // keys must be camelCase'd first
@ -909,11 +914,11 @@ export class Service {
private getBindsMountsAndVolumes(): { private getBindsMountsAndVolumes(): {
binds: string[]; binds: string[];
mounts: Dockerode.MountSettings[]; mounts: Dockerode.MountSettings[];
volumes: { [volName: string]: {} }; volumes: { [volName: string]: EmptyObject };
} { } {
const binds: string[] = []; const binds: string[] = [];
const mounts: Dockerode.MountSettings[] = []; const mounts: Dockerode.MountSettings[] = [];
const volumes: { [volName: string]: {} } = {}; const volumes: { [volName: string]: EmptyObject } = {};
for (const volume of this.config.volumes) { for (const volume of this.config.volumes) {
if (LongDefinition.is(volume)) { if (LongDefinition.is(volume)) {
@ -921,7 +926,11 @@ export class Service {
mounts.push(ComposeUtils.serviceMountToDockerMount(volume)); mounts.push(ComposeUtils.serviceMountToDockerMount(volume));
} else { } else {
// Volumes with the string short syntax are acceptable as Docker configs as-is // Volumes with the string short syntax are acceptable as Docker configs as-is
ShortMount.is(volume) ? binds.push(volume) : (volumes[volume] = {}); if (ShortMount.is(volume)) {
binds.push(volume);
} else {
volumes[volume] = {};
}
} }
} }

View File

@ -1,4 +1,4 @@
import { NetworkInspectInfo as DockerNetworkInspectInfo } from 'dockerode'; import type { NetworkInspectInfo as DockerNetworkInspectInfo } from 'dockerode';
// TODO: ConfigOnly is part of @types/dockerode@v3.2.0, but that version isn't // TODO: ConfigOnly is part of @types/dockerode@v3.2.0, but that version isn't
// compatible with `resin-docker-build` which is used for `npm run sync`. // compatible with `resin-docker-build` which is used for `npm run sync`.

View File

@ -1,8 +1,8 @@
import * as Dockerode from 'dockerode'; import type * as Dockerode from 'dockerode';
import * as t from 'io-ts'; import * as t from 'io-ts';
import { isAbsolute } from 'path'; import { isAbsolute } from 'path';
import { PortMap } from '../ports'; import type { PortMap } from '../ports';
export interface ComposeHealthcheck { export interface ComposeHealthcheck {
test: string | string[]; test: string | string[];

View File

@ -1,6 +1,7 @@
import * as imageManager from './images'; import * as imageManager from './images';
import Service from './service'; import type Service from './service';
import { CompositionStep, generateStep } from './composition-steps'; import type { CompositionStep } from './composition-steps';
import { generateStep } from './composition-steps';
import { InternalInconsistencyError } from '../lib/errors'; import { InternalInconsistencyError } from '../lib/errors';
import { checkString } from '../lib/validation'; import { checkString } from '../lib/validation';

View File

@ -1,12 +1,12 @@
import * as Dockerode from 'dockerode'; import type * as Dockerode from 'dockerode';
import Duration = require('duration-js'); import Duration = require('duration-js');
import * as _ from 'lodash'; import * as _ from 'lodash';
import { parse as parseCommand } from 'shell-quote'; import { parse as parseCommand } from 'shell-quote';
import * as constants from '../lib/constants'; import * as constants from '../lib/constants';
import { checkTruthy } from '../lib/validation'; import { checkTruthy } from '../lib/validation';
import { Service } from './service'; import type { Service } from './service';
import { import type {
ComposeHealthcheck, ComposeHealthcheck,
ConfigMap, ConfigMap,
DeviceMetadata, DeviceMetadata,
@ -209,7 +209,7 @@ function getNanoseconds(timeStr: string): number {
export function composeHealthcheckToServiceHealthcheck( export function composeHealthcheckToServiceHealthcheck(
healthcheck: ComposeHealthcheck | null | undefined, healthcheck: ComposeHealthcheck | null | undefined,
): ServiceHealthcheck | {} { ): ServiceHealthcheck | EmptyObject {
if (healthcheck == null) { if (healthcheck == null) {
return {}; return {};
} }
@ -351,9 +351,8 @@ export async function addFeaturesFromLabels(
} as LongBind); } as LongBind);
if (service.config.environment['DOCKER_HOST'] == null) { if (service.config.environment['DOCKER_HOST'] == null) {
service.config.environment[ service.config.environment['DOCKER_HOST'] =
'DOCKER_HOST' `unix://${constants.containerDockerSocket}`;
] = `unix://${constants.containerDockerSocket}`;
} }
// We keep balena.sock for backwards compatibility // We keep balena.sock for backwards compatibility
if (constants.dockerSocket !== '/var/run/balena.sock') { if (constants.dockerSocket !== '/var/run/balena.sock') {

View File

@ -1,6 +1,6 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as path from 'path'; import * as path from 'path';
import { VolumeInspectInfo } from 'dockerode'; import type { VolumeInspectInfo } from 'dockerode';
import { isNotFoundError, InternalInconsistencyError } from '../lib/errors'; import { isNotFoundError, InternalInconsistencyError } from '../lib/errors';
import { safeRename } from '../lib/fs-utils'; import { safeRename } from '../lib/fs-utils';
@ -10,7 +10,8 @@ import * as LogTypes from '../lib/log-types';
import log from '../lib/supervisor-console'; import log from '../lib/supervisor-console';
import * as logger from '../logger'; import * as logger from '../logger';
import { ResourceRecreationAttemptError } from './errors'; import { ResourceRecreationAttemptError } from './errors';
import Volume, { VolumeConfig } from './volume'; import type { VolumeConfig } from './volume';
import Volume from './volume';
export interface VolumeNameOpts { export interface VolumeNameOpts {
name: string; name: string;

View File

@ -1,4 +1,4 @@
import * as Docker from 'dockerode'; import type * as Docker from 'dockerode';
import isEqual = require('lodash/isEqual'); import isEqual = require('lodash/isEqual');
import omitBy = require('lodash/omitBy'); import omitBy = require('lodash/omitBy');
@ -6,7 +6,7 @@ import * as constants from '../lib/constants';
import { docker } from '../lib/docker-utils'; import { docker } from '../lib/docker-utils';
import { InternalInconsistencyError } from '../lib/errors'; import { InternalInconsistencyError } from '../lib/errors';
import * as LogTypes from '../lib/log-types'; import * as LogTypes from '../lib/log-types';
import { LabelObject } from '../types'; import type { LabelObject } from '../types';
import * as logger from '../logger'; import * as logger from '../logger';
import * as ComposeUtils from './utils'; import * as ComposeUtils from './utils';

View File

@ -1,5 +1,3 @@
import * as _ from 'lodash';
export interface ConfigOptions { export interface ConfigOptions {
[key: string]: string | string[]; [key: string]: string | string[];
} }

View File

@ -2,7 +2,8 @@ import * as _ from 'lodash';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import * as path from 'path'; import * as path from 'path';
import { ConfigOptions, ConfigBackend } from './backend'; import type { ConfigOptions } from './backend';
import { ConfigBackend } from './backend';
import { exec, exists } from '../../lib/fs-utils'; import { exec, exists } from '../../lib/fs-utils';
import * as hostUtils from '../../lib/host-utils'; import * as hostUtils from '../../lib/host-utils';
import * as constants from '../../lib/constants'; import * as constants from '../../lib/constants';
@ -49,10 +50,10 @@ 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 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(
`Missing AML for \'${aml}\'. Unable to load.`, `Missing AML for '${aml}'. Unable to load.`,
{ aml, path: amlSrcPath }, { aml, path: amlSrcPath },
'Load AML error', 'Load AML error',
false, false,

View File

@ -1,6 +1,7 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { ConfigOptions, ConfigBackend } from './backend'; import type { ConfigOptions } from './backend';
import { ConfigBackend } 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'; import { exists } from '../../lib/fs-utils';
@ -14,7 +15,7 @@ const ARRAY_CONFIGS = [
'gpio', 'gpio',
] as const; ] as const;
type ArrayConfig = typeof ARRAY_CONFIGS[number]; type ArrayConfig = (typeof ARRAY_CONFIGS)[number];
// Refinement on the ConfigOptions type // Refinement on the ConfigOptions type
// to indicate what properties are arrays // to indicate what properties are arrays

View File

@ -1,6 +1,6 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { ConfigOptions } from './backend'; import type { ConfigOptions } from './backend';
import { import {
ExtLinuxParseError, ExtLinuxParseError,
AppendDirectiveError, AppendDirectiveError,

View File

@ -1,13 +1,10 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as semver from 'semver'; import * as semver from 'semver';
import { ConfigOptions, ConfigBackend } from './backend'; import type { ConfigOptions } from './backend';
import { import { ConfigBackend } from './backend';
ExtlinuxFile, import type { ExtlinuxFile, Directive } from './extlinux-file';
Directive, import { AppendDirective, FDTDirective } from './extlinux-file';
AppendDirective,
FDTDirective,
} from './extlinux-file';
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 { ExtLinuxEnvError, ExtLinuxParseError } from '../../lib/errors'; import { ExtLinuxEnvError, ExtLinuxParseError } from '../../lib/errors';

View File

@ -1,6 +1,7 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { ConfigOptions, ConfigBackend } from './backend'; import type { ConfigOptions } from './backend';
import { ConfigBackend } 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 { ExtraUEnvError } from '../../lib/errors'; import { ExtraUEnvError } from '../../lib/errors';

View File

@ -1,5 +1,3 @@
import * as _ from 'lodash';
import { Extlinux } from './extlinux'; import { Extlinux } from './extlinux';
import { ExtraUEnv } from './extra-uEnv'; import { ExtraUEnv } from './extra-uEnv';
import { ConfigTxt } from './config-txt'; import { ConfigTxt } from './config-txt';

View File

@ -1,7 +1,8 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import { ConfigOptions, ConfigBackend } from './backend'; import type { ConfigOptions } from './backend';
import { ConfigBackend } 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 { ODMDataError } from '../../lib/errors'; import { ODMDataError } from '../../lib/errors';

View File

@ -6,7 +6,8 @@ import * as constants from '../../lib/constants';
import { exists } from '../../lib/fs-utils'; import { exists } from '../../lib/fs-utils';
import * as hostUtils from '../../lib/host-utils'; import * as hostUtils from '../../lib/host-utils';
import log from '../../lib/supervisor-console'; import log from '../../lib/supervisor-console';
import { ConfigBackend, ConfigOptions } from './backend'; import type { ConfigOptions } from './backend';
import { ConfigBackend } from './backend';
export class SplashImage extends ConfigBackend { export class SplashImage extends ConfigBackend {
private static readonly BASEPATH = hostUtils.pathOnBoot('splash'); private static readonly BASEPATH = hostUtils.pathOnBoot('splash');
@ -155,7 +156,7 @@ export class SplashImage extends ConfigBackend {
return SplashImage.CONFIGS.includes(this.stripPrefix(name).toLowerCase()); return SplashImage.CONFIGS.includes(this.stripPrefix(name).toLowerCase());
} }
public async matches(_deviceType: string): Promise<boolean> { public async matches(): Promise<boolean> {
// all device types // all device types
return true; return true;
} }
@ -191,7 +192,10 @@ export class SplashImage extends ConfigBackend {
: await this.readSplashImage(SplashImage.DEFAULT); : await this.readSplashImage(SplashImage.DEFAULT);
// If it is a data URI get only the data part // If it is a data URI get only the data part
const [, image] = value.startsWith('data:') ? value.split(',') : [, value]; let image = value;
if (value.startsWith('data:')) {
[, image] = value.split(',');
}
// Rewrite the splash image // Rewrite the splash image
await this.writeSplashImage(image); await this.writeSplashImage(image);

View File

@ -5,7 +5,7 @@ import * as constants from '../lib/constants';
import * as hostUtils from '../lib/host-utils'; import * as hostUtils from '../lib/host-utils';
import * as osRelease from '../lib/os-release'; import * as osRelease from '../lib/os-release';
import { readLock, writeLock } from '../lib/update-lock'; import { readLock, writeLock } from '../lib/update-lock';
import * as Schema from './schema'; import type * as Schema from './schema';
export default class ConfigJsonConfigBackend { export default class ConfigJsonConfigBackend {
private readonly readLockConfigJson: () => Bluebird.Disposer<() => void>; private readonly readLockConfigJson: () => Bluebird.Disposer<() => void>;

View File

@ -1,18 +1,20 @@
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import type { Knex } from 'knex'; import type { Knex } from 'knex';
import * as _ from 'lodash'; import * as _ from 'lodash';
import StrictEventEmitter from 'strict-event-emitter-types'; import type StrictEventEmitter from 'strict-event-emitter-types';
import { inspect } from 'util'; import { inspect } from 'util';
import { generateUniqueKey } from '../lib/register-device'; import { generateUniqueKey } from '../lib/register-device';
import { Either, isLeft, isRight, Right } from 'fp-ts/lib/Either'; import type { Either, Right } from 'fp-ts/lib/Either';
import { isLeft, isRight } from 'fp-ts/lib/Either';
import * as t from 'io-ts'; import * as t from 'io-ts';
import ConfigJsonConfigBackend from './configJson'; import ConfigJsonConfigBackend from './configJson';
import * as FnSchema from './functions'; import * as FnSchema from './functions';
import * as Schema from './schema'; import * as Schema from './schema';
import { SchemaReturn, SchemaTypeKey, schemaTypes } from './schema-type'; import type { SchemaReturn, SchemaTypeKey } from './schema-type';
import { schemaTypes } from './schema-type';
import * as db from '../db'; import * as db from '../db';
import { import {
@ -43,9 +45,9 @@ class ConfigEvents extends (EventEmitter as new () => ConfigEventEmitter) {}
const events = new ConfigEvents(); const events = new ConfigEvents();
// Expose methods which make this module act as an EventEmitter // Expose methods which make this module act as an EventEmitter
export const on: typeof events['on'] = events.on.bind(events); export const on: (typeof events)['on'] = events.on.bind(events);
export const once: typeof events['once'] = events.once.bind(events); export const once: (typeof events)['once'] = events.once.bind(events);
export const removeListener: typeof events['removeListener'] = export const removeListener: (typeof events)['removeListener'] =
events.removeListener.bind(events); events.removeListener.bind(events);
export async function get<T extends SchemaTypeKey>( export async function get<T extends SchemaTypeKey>(
@ -54,7 +56,7 @@ export async function get<T extends SchemaTypeKey>(
): Promise<SchemaReturn<T>> { ): Promise<SchemaReturn<T>> {
const $db = trx || db.models; const $db = trx || db.models;
if (Schema.schema.hasOwnProperty(key)) { if (Object.prototype.hasOwnProperty.call(Schema.schema, key)) {
const schemaKey = key as Schema.SchemaKey; const schemaKey = key as Schema.SchemaKey;
return getSchema(schemaKey, $db).then((value) => { return getSchema(schemaKey, $db).then((value) => {
@ -80,7 +82,7 @@ export async function get<T extends SchemaTypeKey>(
// the type system happy // the type system happy
return checkValueDecode(decoded, key, value) && decoded.right; return checkValueDecode(decoded, key, value) && decoded.right;
}); });
} else if (FnSchema.fnSchema.hasOwnProperty(key)) { } else if (Object.prototype.hasOwnProperty.call(FnSchema.fnSchema, key)) {
const fnKey = key as FnSchema.FnSchemaKey; const fnKey = key as FnSchema.FnSchemaKey;
// Cast the promise as something that produces an unknown, and this means that // Cast the promise as something that produces an unknown, and this means that
// we can validate the output of the function as well, ensuring that the type matches // we can validate the output of the function as well, ensuring that the type matches
@ -241,10 +243,12 @@ async function getSchema<T extends Schema.SchemaKey>(
value = await configJsonBackend.get(key); value = await configJsonBackend.get(key);
break; break;
case 'db': case 'db':
{
const [conf] = await $db('config').select('value').where({ key }); const [conf] = await $db('config').select('value').where({ key });
if (conf != null) { if (conf != null) {
return conf.value; return conf.value;
} }
}
break; break;
} }
@ -265,7 +269,7 @@ function validateConfigMap<T extends SchemaTypeKey>(
// throw if any value fails verification // throw if any value fails verification
return _.mapValues(configMap, (value, key) => { return _.mapValues(configMap, (value, key) => {
if ( if (
!Schema.schema.hasOwnProperty(key) || !Object.prototype.hasOwnProperty.call(Schema.schema, key) ||
!Schema.schema[key as Schema.SchemaKey].mutable !Schema.schema[key as Schema.SchemaKey].mutable
) { ) {
throw new Error( throw new Error(

View File

@ -54,12 +54,13 @@ export const PermissiveNumber = new t.Type<number, string | number>(
switch (typeof v) { switch (typeof v) {
case 'number': case 'number':
return t.success(v); return t.success(v);
case 'string': case 'string': {
const i = parseInt(v, 10); const i = parseInt(v, 10);
if (Number.isNaN(i)) { if (Number.isNaN(i)) {
return t.failure(v, c); return t.failure(v, c);
} }
return t.success(i); return t.success(i);
}
default: default:
return t.failure(v, c); return t.failure(v, c);
} }
@ -73,7 +74,7 @@ export const PermissiveNumber = new t.Type<number, string | number>(
// Define this differently, so that we can add a generic to it // Define this differently, so that we can add a generic to it
export class StringJSON<T> extends t.Type<T, string> { export class StringJSON<T> extends t.Type<T, string> {
public readonly _tag: 'StringJSON' = 'StringJSON'; public readonly _tag: 'StringJSON' = 'StringJSON' as const;
constructor(type: t.InterfaceType<any>) { constructor(type: t.InterfaceType<any>) {
super( super(
'StringJSON', 'StringJSON',

View File

@ -4,9 +4,9 @@ import * as Bluebird from 'bluebird';
import * as config from '../config'; import * as config from '../config';
import * as constants from '../lib/constants'; import * as constants from '../lib/constants';
import { getMetaOSRelease } from '../lib/os-release'; import { getMetaOSRelease } from '../lib/os-release';
import { EnvVarObject } from '../types'; import type { EnvVarObject } from '../types';
import { allBackends as Backends } from './backends'; import { allBackends as Backends } from './backends';
import { ConfigOptions, ConfigBackend } from './backends/backend'; import type { ConfigOptions, ConfigBackend } from './backends/backend';
export async function getSupportedBackends(): Promise<ConfigBackend[]> { export async function getSupportedBackends(): Promise<ConfigBackend[]> {
// Get required information to find supported backends // Get required information to find supported backends

View File

@ -1,4 +1,5 @@
import { knex, Knex } from 'knex'; import type { Knex } from 'knex';
import { knex } from 'knex';
import * as path from 'path'; import * as path from 'path';
import * as _ from 'lodash'; import * as _ from 'lodash';

View File

@ -8,10 +8,8 @@ import * as logger from '../logger';
import * as config from '../config'; import * as config from '../config';
import * as hostConfig from '../host-config'; import * as hostConfig from '../host-config';
import * as applicationManager from '../compose/application-manager'; import * as applicationManager from '../compose/application-manager';
import { import type { CompositionStepAction } from '../compose/composition-steps';
CompositionStepAction, import { generateStep } from '../compose/composition-steps';
generateStep,
} from '../compose/composition-steps';
import * as commitStore from '../compose/commit'; import * as commitStore from '../compose/commit';
import { getApp } from '../device-state/db-format'; import { getApp } from '../device-state/db-format';
import * as TargetState from '../device-state/target-state'; import * as TargetState from '../device-state/target-state';

View File

@ -1,5 +1,5 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as express from 'express'; import type * as express from 'express';
import * as memoizee from 'memoizee'; import * as memoizee from 'memoizee';
import { TypedError } from 'typed-error'; import { TypedError } from 'typed-error';
@ -37,7 +37,7 @@ type ScopeCheckCollection = {
* The scopes which a key can cover. * The scopes which a key can cover.
*/ */
type ScopeTypes = { type ScopeTypes = {
global: {}; global: NonNullable<unknown>;
app: { app: {
appId: number; appId: number;
}; };
@ -222,11 +222,11 @@ async function generateKey(
// remove the cached lookup for the key // remove the cached lookup for the key
const [apiKey] = secrets; const [apiKey] = secrets;
if (apiKey != null) { if (apiKey != null) {
getApiKeyByKey.clear(apiKey.key); await getApiKeyByKey.clear(apiKey.key);
} }
// remove the cached value for this lookup // remove the cached value for this lookup
getApiKeyForService.clear(appId, serviceName); await getApiKeyForService.clear(appId, serviceName);
// return a new API key // return a new API key
return await createNewKey(appId, serviceName, scopes); return await createNewKey(appId, serviceName, scopes);

View File

@ -100,9 +100,11 @@ export class SupervisorAPI {
log.error('Failed to stop Supervisor API'); log.error('Failed to stop Supervisor API');
return reject(err); return reject(err);
} }
options?.errored if (options?.errored) {
? log.error('Stopped Supervisor API') log.error('Stopped Supervisor API');
: log.info('Stopped Supervisor API'); } else {
log.info('Stopped Supervisor API');
}
return resolve(); return resolve();
}); });
}); });

View File

@ -1,5 +1,5 @@
import * as morgan from 'morgan'; import * as morgan from 'morgan';
import { Request } from 'express'; import type { Request } from 'express';
import log from '../../lib/supervisor-console'; import log from '../../lib/supervisor-console';

View File

@ -1,11 +1,10 @@
import * as express from 'express'; import * as express from 'express';
import * as _ from 'lodash';
import type { Response } from 'express'; import type { Response } from 'express';
import * as actions from './actions'; import * as actions from './actions';
import { AuthorizedRequest } from './api-keys'; import type { AuthorizedRequest } from './api-keys';
import * as eventTracker from '../event-tracker'; import * as eventTracker from '../event-tracker';
import * as deviceState from '../device-state'; import type * as deviceState from '../device-state';
import * as constants from '../lib/constants'; import * as constants from '../lib/constants';
import { checkInt, checkTruthy } from '../lib/validation'; import { checkInt, checkTruthy } from '../lib/validation';
@ -15,7 +14,7 @@ import {
isBadRequestError, isBadRequestError,
UpdatesLockedError, UpdatesLockedError,
} from '../lib/errors'; } from '../lib/errors';
import { CompositionStepAction } from '../compose/composition-steps'; import type { CompositionStepAction } from '../compose/composition-steps';
const disallowedHostConfigPatchFields = ['local_ip', 'local_port']; const disallowedHostConfigPatchFields = ['local_ip', 'local_port'];

View File

@ -5,8 +5,8 @@ import * as _ from 'lodash';
import * as deviceState from '../device-state'; import * as deviceState from '../device-state';
import * as apiBinder from '../api-binder'; import * as apiBinder from '../api-binder';
import * as applicationManager from '../compose/application-manager'; import * as applicationManager from '../compose/application-manager';
import { CompositionStepAction } from '../compose/composition-steps'; import type { CompositionStepAction } from '../compose/composition-steps';
import { Service } from '../compose/service'; import type { Service } from '../compose/service';
import Volume from '../compose/volume'; import Volume from '../compose/volume';
import * as commitStore from '../compose/commit'; import * as commitStore from '../compose/commit';
import * as config from '../config'; import * as config from '../config';
@ -26,7 +26,7 @@ import {
BadRequestError, BadRequestError,
} from '../lib/errors'; } from '../lib/errors';
import { isVPNActive } from '../network'; import { isVPNActive } from '../network';
import { AuthorizedRequest } from './api-keys'; import type { AuthorizedRequest } from './api-keys';
import { fromV2TargetState } from '../lib/legacy'; import { fromV2TargetState } from '../lib/legacy';
import * as actions from './actions'; import * as actions from './actions';
import { v2ServiceEndpointError } from './messages'; import { v2ServiceEndpointError } from './messages';

View File

@ -6,14 +6,14 @@ import * as config from './config';
import * as db from './db'; import * as db from './db';
import * as logger from './logger'; import * as logger from './logger';
import * as dbus from './lib/dbus'; import * as dbus from './lib/dbus';
import { EnvVarObject } from './types'; import type { EnvVarObject } from './types';
import { UnitNotLoadedError } from './lib/errors'; import { UnitNotLoadedError } from './lib/errors';
import { checkInt, checkTruthy } from './lib/validation'; import { checkInt, checkTruthy } from './lib/validation';
import log from './lib/supervisor-console'; import log from './lib/supervisor-console';
import * as configUtils from './config/utils'; import * as configUtils from './config/utils';
import { SchemaTypeKey } from './config/schema-type'; import type { SchemaTypeKey } from './config/schema-type';
import { matchesAnyBootConfig } from './config/backends'; import { matchesAnyBootConfig } from './config/backends';
import { ConfigBackend } from './config/backends/backend'; import type { ConfigBackend } from './config/backends/backend';
import { Odmdata } from './config/backends/odmdata'; import { Odmdata } from './config/backends/odmdata';
import * as fsUtils from './lib/fs-utils'; import * as fsUtils from './lib/fs-utils';
import { pathOnRoot } from './lib/host-utils'; import { pathOnRoot } from './lib/host-utils';
@ -627,6 +627,7 @@ function changeRequired(
// Set changeRequired to false so we do not get stuck in a loop trying to fix this mismatch // Set changeRequired to false so we do not get stuck in a loop trying to fix this mismatch
aChangeIsRequired = false; aChangeIsRequired = false;
} }
throw e;
default: default:
throw e; throw e;
} }

View File

@ -2,7 +2,7 @@ import * as Bluebird from 'bluebird';
import { stripIndent } from 'common-tags'; import { stripIndent } from 'common-tags';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import * as _ from 'lodash'; import * as _ from 'lodash';
import StrictEventEmitter from 'strict-event-emitter-types'; import type StrictEventEmitter from 'strict-event-emitter-types';
import { isRight } from 'fp-ts/lib/Either'; import { isRight } from 'fp-ts/lib/Either';
import Reporter from 'io-ts-reporters'; import Reporter from 'io-ts-reporters';
import prettyMs = require('pretty-ms'); import prettyMs = require('pretty-ms');
@ -31,14 +31,14 @@ import { loadTargetFromFile } from './device-state/preload';
import * as applicationManager from './compose/application-manager'; import * as applicationManager from './compose/application-manager';
import * as commitStore from './compose/commit'; import * as commitStore from './compose/commit';
import { import type {
DeviceLegacyState, DeviceLegacyState,
InstancedDeviceState, InstancedDeviceState,
TargetState,
DeviceState, DeviceState,
DeviceReport, DeviceReport,
AppState, AppState,
} from './types'; } from './types';
import { TargetState } from './types';
import type { import type {
CompositionStepT, CompositionStepT,
CompositionStepAction, CompositionStepAction,
@ -85,11 +85,11 @@ type DeviceStateEventEmitter = StrictEventEmitter<
DeviceStateEvents DeviceStateEvents
>; >;
const events = new EventEmitter() as DeviceStateEventEmitter; const events = new EventEmitter() as DeviceStateEventEmitter;
export const on: typeof events['on'] = events.on.bind(events); export const on: (typeof events)['on'] = events.on.bind(events);
export const once: typeof events['once'] = events.once.bind(events); export const once: (typeof events)['once'] = events.once.bind(events);
export const removeListener: typeof events['removeListener'] = export const removeListener: (typeof events)['removeListener'] =
events.removeListener.bind(events); events.removeListener.bind(events);
export const removeAllListeners: typeof events['removeAllListeners'] = export const removeAllListeners: (typeof events)['removeAllListeners'] =
events.removeAllListeners.bind(events); events.removeAllListeners.bind(events);
export type DeviceStateStepTarget = 'reboot' | 'shutdown' | 'noop'; export type DeviceStateStepTarget = 'reboot' | 'shutdown' | 'noop';
@ -194,9 +194,13 @@ export async function initNetworkChecks({
apiEndpoint: config.ConfigType<'apiEndpoint'>; apiEndpoint: config.ConfigType<'apiEndpoint'>;
connectivityCheckEnabled: config.ConfigType<'connectivityCheckEnabled'>; connectivityCheckEnabled: config.ConfigType<'connectivityCheckEnabled'>;
}) { }) {
network.startConnectivityCheck(apiEndpoint, connectivityCheckEnabled, (c) => { await network.startConnectivityCheck(
apiEndpoint,
connectivityCheckEnabled,
(c) => {
connected = c; connected = c;
}); },
);
config.on('change', function (changedConfig) { config.on('change', function (changedConfig) {
if (changedConfig.connectivityCheckEnabled != null) { if (changedConfig.connectivityCheckEnabled != null) {
network.enableConnectivityCheck(changedConfig.connectivityCheckEnabled); network.enableConnectivityCheck(changedConfig.connectivityCheckEnabled);
@ -240,7 +244,7 @@ export async function loadInitialState() {
]); ]);
maxPollTime = conf.appUpdatePollInterval; maxPollTime = conf.appUpdatePollInterval;
initNetworkChecks(conf); await initNetworkChecks(conf);
if (!conf.initialConfigSaved) { if (!conf.initialConfigSaved) {
await saveInitialConfig(); await saveInitialConfig();
@ -851,8 +855,8 @@ export function triggerApplyTarget({
} }
applyCancelled = false; applyCancelled = false;
applyInProgress = true; applyInProgress = true;
new Promise((resolve, reject) => { void new Promise((resolve, reject) => {
setTimeout(delay).then(resolve); void setTimeout(delay).then(resolve);
cancelDelay = reject; cancelDelay = reject;
}) })
.catch(() => { .catch(() => {

View File

@ -1,13 +1,13 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as db from '../db'; import type * as db from '../db';
import * as targetStateCache from './target-state-cache'; import * as targetStateCache from './target-state-cache';
import { DatabaseApp, DatabaseService } from './target-state-cache'; import type { DatabaseApp, DatabaseService } from './target-state-cache';
import App from '../compose/app'; import App from '../compose/app';
import * as images from '../compose/images'; import * as images from '../compose/images';
import { import type {
InstancedAppState, InstancedAppState,
TargetApp, TargetApp,
TargetApps, TargetApps,

View File

@ -1,6 +1,7 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { fromV2TargetApps, TargetAppsV2 } from '../lib/legacy'; import type { TargetAppsV2 } from '../lib/legacy';
import { AppsJsonFormat, TargetApp, TargetRelease } from '../types'; import { fromV2TargetApps } from '../lib/legacy';
import type { AppsJsonFormat, TargetApp, TargetRelease } from '../types';
/** /**
* Converts a single app from single container format into * Converts a single app from single container format into

View File

@ -1,7 +1,8 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import { Image, imageFromService } from '../compose/images'; import type { Image } from '../compose/images';
import { imageFromService } from '../compose/images';
import { NumericIdentifier } from '../types'; import { NumericIdentifier } from '../types';
import * as deviceState from '../device-state'; import * as deviceState from '../device-state';
import * as config from '../config'; import * as config from '../config';

View File

@ -2,7 +2,7 @@ import * as _ from 'lodash';
import * as config from '../config'; import * as config from '../config';
import * as db from '../db'; import * as db from '../db';
import { TargetAppClass } from '../types'; import type { TargetAppClass } from '../types';
// We omit the id (which does appear in the db) in this type, as we don't use it // We omit the id (which does appear in the db) in this type, as we don't use it
// at all, and we can use the below type for both insertion and retrieval. // at all, and we can use the below type for both insertion and retrieval.

View File

@ -1,7 +1,6 @@
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import * as url from 'url'; import * as url from 'url';
import { setTimeout } from 'timers/promises'; import { setTimeout } from 'timers/promises';
import * as _ from 'lodash';
import * as Bluebird from 'bluebird'; import * as Bluebird from 'bluebird';
import type StrictEventEmitter from 'strict-event-emitter-types'; import type StrictEventEmitter from 'strict-event-emitter-types';

View File

@ -1,7 +1,7 @@
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import * as _ from 'lodash'; import * as _ from 'lodash';
import StrictEventEmitter from 'strict-event-emitter-types'; import type StrictEventEmitter from 'strict-event-emitter-types';
import { TargetState } from './types/state'; import type { TargetState } from './types/state';
export interface GlobalEvents { export interface GlobalEvents {
deviceProvisioned: void; deviceProvisioned: void;
@ -10,10 +10,6 @@ export interface GlobalEvents {
type GlobalEventEmitter = StrictEventEmitter<EventEmitter, GlobalEvents>; type GlobalEventEmitter = StrictEventEmitter<EventEmitter, GlobalEvents>;
export class GlobalEventBus extends (EventEmitter as new () => GlobalEventEmitter) { export const getInstance = _.once(
public constructor() { () => new EventEmitter() as GlobalEventEmitter,
super(); );
}
}
export const getInstance = _.once(() => new GlobalEventBus());

View File

@ -1,6 +1,4 @@
import mask = require('json-mask'); import mask = require('json-mask');
import * as _ from 'lodash';
import log from './lib/supervisor-console'; import log from './lib/supervisor-console';
export type EventTrackProperties = Dictionary<any>; export type EventTrackProperties = Dictionary<any>;
@ -16,7 +14,7 @@ const mixpanelMask = [
'stateDiff/local(os_version,supervisor_version,ip_address,apps/*/services)', 'stateDiff/local(os_version,supervisor_version,ip_address,apps/*/services)',
].join(','); ].join(',');
export async function track( export function track(
event: string, event: string,
properties: EventTrackProperties | Error = {}, properties: EventTrackProperties | Error = {},
) { ) {
@ -34,6 +32,6 @@ export async function track(
} }
// Don't send potentially sensitive information, by using a whitelist // Don't send potentially sensitive information, by using a whitelist
properties = mask(properties, mixpanelMask); properties = mask(properties, mixpanelMask) || {};
log.event('Event:', event, JSON.stringify(properties)); log.event('Event:', event, JSON.stringify(properties));
} }

View File

@ -1,4 +1,4 @@
import { PinejsClientRequest } from 'pinejs-client-request'; import type { PinejsClientRequest } from 'pinejs-client-request';
import * as Bluebird from 'bluebird'; import * as Bluebird from 'bluebird';
import * as config from '../config'; import * as config from '../config';

View File

@ -10,7 +10,7 @@ export const initialized = _.once(async () => {
config.on('change', (conf) => { config.on('change', (conf) => {
if (conf.hostDiscoverability != null) { if (conf.hostDiscoverability != null) {
switchDiscoverability(conf.hostDiscoverability); void switchDiscoverability(conf.hostDiscoverability);
} }
}); });

View File

@ -7,7 +7,7 @@ import { Blueprint, Contract, ContractObject } from '@balena/contrato';
import { ContractValidationError, InternalInconsistencyError } from './errors'; import { ContractValidationError, InternalInconsistencyError } from './errors';
import { checkTruthy } from './validation'; import { checkTruthy } from './validation';
import { TargetApps } from '../types'; import type { TargetApps } from '../types';
export { ContractObject }; export { ContractObject };

View File

@ -1,6 +1,6 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { EnvVarObject } from '../types'; import type { EnvVarObject } from '../types';
import log from '../lib/supervisor-console'; import log from '../lib/supervisor-console';

View File

@ -1,4 +1,5 @@
import { DockerProgress, ProgressCallback } from 'docker-progress'; import type { ProgressCallback } from 'docker-progress';
import { DockerProgress } from 'docker-progress';
import * as Dockerode from 'dockerode'; import * as Dockerode from 'dockerode';
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as memoizee from 'memoizee'; import * as memoizee from 'memoizee';
@ -6,7 +7,7 @@ import * as memoizee from 'memoizee';
import { applyDelta, OutOfSyncError } from 'docker-delta'; import { applyDelta, OutOfSyncError } from 'docker-delta';
import DockerToolbelt = require('docker-toolbelt'); import DockerToolbelt = require('docker-toolbelt');
import { SchemaReturn } from '../config/schema-type'; import type { SchemaReturn } from '../config/schema-type';
import { envArrayToObject } from './conversions'; import { envArrayToObject } from './conversions';
import { import {
DeltaStillProcessingError, DeltaStillProcessingError,
@ -14,7 +15,7 @@ import {
InvalidNetGatewayError, InvalidNetGatewayError,
} from './errors'; } from './errors';
import * as request from './request'; import * as request from './request';
import { EnvVarObject } from '../types'; import type { EnvVarObject } from '../types';
import log from './supervisor-console'; import log from './supervisor-console';
@ -191,6 +192,8 @@ export async function fetchDeltaWithProgress(
`Got ${res.statusCode} when requesting an image from delta server.`, `Got ${res.statusCode} when requesting an image from delta server.`,
); );
} }
{
// lexical declarations inside a case clause need to be wrapped in a block
const deltaUrl = res.headers['location']; const deltaUrl = res.headers['location'];
const deltaSrc = deltaSourceId; const deltaSrc = deltaSourceId;
const resumeOpts = { const resumeOpts = {
@ -206,6 +209,7 @@ export async function fetchDeltaWithProgress(
onProgress, onProgress,
logFn, logFn,
); );
}
break; break;
case 3: case 3:
if (res.statusCode !== 200) { if (res.statusCode !== 200) {
@ -213,6 +217,8 @@ export async function fetchDeltaWithProgress(
`Got ${res.statusCode} when requesting v3 delta from delta server.`, `Got ${res.statusCode} when requesting v3 delta from delta server.`,
); );
} }
{
// lexical declarations inside a case clause need to be wrapped in a block
let name; let name;
try { try {
name = JSON.parse(data).name; name = JSON.parse(data).name;
@ -222,6 +228,7 @@ export async function fetchDeltaWithProgress(
); );
} }
id = await applyBalenaDelta(name, token, onProgress, logFn); id = await applyBalenaDelta(name, token, onProgress, logFn);
}
break; break;
default: default:
throw new Error(`Unsupported delta version: ${deltaOpts.deltaVersion}`); throw new Error(`Unsupported delta version: ${deltaOpts.deltaVersion}`);
@ -295,7 +302,7 @@ export async function getNetworkGateway(networkName: string): Promise<string> {
); );
} }
function applyRsyncDelta( async function applyRsyncDelta(
imgSrc: string, imgSrc: string,
deltaUrl: string, deltaUrl: string,
applyTimeout: number, applyTimeout: number,
@ -305,8 +312,8 @@ function applyRsyncDelta(
): Promise<string> { ): Promise<string> {
logFn(`Applying rsync delta: ${deltaUrl}`); logFn(`Applying rsync delta: ${deltaUrl}`);
return new Promise(async (resolve, reject) => {
const resumable = await request.getResumableRequest(); const resumable = await request.getResumableRequest();
return new Promise((resolve, reject) => {
const req = resumable(Object.assign({ url: deltaUrl }, opts)); const req = resumable(Object.assign({ url: deltaUrl }, opts));
req req
.on('progress', onProgress) .on('progress', onProgress)

View File

@ -15,7 +15,7 @@ export const initialised = _.once(async () => {
// apply firewall whenever relevant config changes occur... // apply firewall whenever relevant config changes occur...
config.on('change', ({ firewallMode, localMode }) => { config.on('change', ({ firewallMode, localMode }) => {
if (firewallMode || localMode != null) { if (firewallMode || localMode != null) {
applyFirewall({ firewallMode, localMode }); void applyFirewall({ firewallMode, localMode });
} }
}); });
}); });

View File

@ -1,4 +1,3 @@
import * as _ from 'lodash';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import * as path from 'path'; import * as path from 'path';
import { exec as execSync } from 'child_process'; import { exec as execSync } from 'child_process';

View File

@ -5,7 +5,7 @@ import { exec, exists } from './fs-utils';
function withBase(base: string) { function withBase(base: string) {
function withPath(): string; function withPath(): string;
function withPath(path: string): string; function withPath(singlePath: string): string;
function withPath(...paths: string[]): string[]; function withPath(...paths: string[]): string[];
function withPath(...paths: string[]): string[] | string { function withPath(...paths: string[]): string[] | string {
if (arguments.length === 0) { if (arguments.length === 0) {
@ -42,7 +42,10 @@ export const pathExistsOnState = async (p: string) =>
await exists(pathOnState(p)); await exists(pathOnState(p));
class CodedError extends Error { class CodedError extends Error {
constructor(msg: string, readonly code: number | string) { constructor(
msg: string,
readonly code: number | string,
) {
super(msg); super(msg);
} }
} }

View File

@ -7,7 +7,10 @@ import { exec } from './fs-utils';
import log from './supervisor-console'; import log from './supervisor-console';
export class IPTablesRuleError extends TypedError { export class IPTablesRuleError extends TypedError {
public constructor(err: string | Error, public ruleset: string) { public constructor(
err: string | Error,
public ruleset: string,
) {
super(err); super(err);
} }
} }

View File

@ -1,4 +1,5 @@
import { ChildProcess, spawn } from 'child_process'; import type { ChildProcess } from 'child_process';
import { spawn } from 'child_process';
import log from './supervisor-console'; import log from './supervisor-console';

View File

@ -30,7 +30,7 @@ export function equals<T>(value: T, other: T): boolean {
* Returns true if the the object equals `{}` or is an empty * Returns true if the the object equals `{}` or is an empty
* array * array
*/ */
export function empty<T extends {}>(value: T): boolean { export function empty<T extends object>(value: T): boolean {
return (Array.isArray(value) && value.length === 0) || equals(value, {}); return (Array.isArray(value) && value.length === 0) || equals(value, {});
} }

View File

@ -1,5 +1,3 @@
import * as _ from 'lodash';
import * as path from 'path'; import * as path from 'path';
import * as apiBinder from '../api-binder'; import * as apiBinder from '../api-binder';
import * as config from '../config'; import * as config from '../config';
@ -16,14 +14,14 @@ import {
import { docker } from './docker-utils'; import { docker } from './docker-utils';
import { log } from './supervisor-console'; import { log } from './supervisor-console';
import { pathOnData } from './host-utils'; import { pathOnData } from './host-utils';
import Volume from '../compose/volume'; import type Volume from '../compose/volume';
import * as logger from '../logger'; import * as logger from '../logger';
import type { import type {
DatabaseApp, DatabaseApp,
DatabaseService, DatabaseService,
} from '../device-state/target-state-cache'; } from '../device-state/target-state-cache';
import { TargetApp, TargetApps, TargetState } from '../types'; import type { TargetApp, TargetApps, TargetState } from '../types';
const defaultLegacyVolume = () => 'resin-data'; const defaultLegacyVolume = () => 'resin-data';

View File

@ -1,10 +1,9 @@
import * as _ from 'lodash';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as volumeManager from '../compose/volume-manager'; import * as volumeManager from '../compose/volume-manager';
import * as deviceState from '../device-state'; import * as deviceState from '../device-state';
import { TargetState } from '../types'; import type { TargetState } from '../types';
import * as constants from './constants'; import * as constants from './constants';
import { BackupError, isNotFoundError } from './errors'; import { BackupError, isNotFoundError } from './errors';
import { exec, exists, mkdirp, unlinkAll } from './fs-utils'; import { exec, exists, mkdirp, unlinkAll } from './fs-utils';
@ -24,15 +23,18 @@ export async function loadBackupFromMigration(
await deviceState.setTarget(targetState); await deviceState.setTarget(targetState);
// TODO: this code is only single-app compatible // Assume there is only a single device in the target state
const [uuid] = Object.keys(targetState.local?.apps); const [localDevice] = Object.values(targetState);
if (!!uuid) { // TODO: this code is only single-app compatible
const [uuid] = Object.keys(localDevice?.apps);
if (uuid) {
throw new BackupError('No apps in the target state'); throw new BackupError('No apps in the target state');
} }
const { id: appId } = targetState.local?.apps[uuid]; const { id: appId } = localDevice.apps[uuid];
const [release] = Object.values(targetState.local?.apps[uuid].releases); const [release] = Object.values(localDevice.apps[uuid].releases);
const volumes = release?.volumes ?? {}; const volumes = release?.volumes ?? {};

View File

@ -4,7 +4,7 @@ import { getRequestInstance } from './request';
export const { generateUniqueKey, register } = getRegisterDevice({ export const { generateUniqueKey, register } = getRegisterDevice({
request: { request: {
send: async (options: {}) => { send: async (options: object) => {
const request = await getRequestInstance(); const request = await getRequestInstance();
const [response] = await request.postAsync({ ...options, json: true }); const [response] = await request.postAsync({ ...options, json: true });
return response; return response;

View File

@ -1,5 +1,5 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { TransformableInfo } from 'logform'; import type { TransformableInfo } from 'logform';
import * as winston from 'winston'; import * as winston from 'winston';
const levels = { const levels = {

View File

@ -1,5 +1,4 @@
import * as Bluebird from 'bluebird'; import * as Bluebird from 'bluebird';
import * as _ from 'lodash';
import { promises as fs } from 'fs'; 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';
@ -92,7 +91,7 @@ async function dispose(
* TODO: Remove skipLock as it's not a good interface. If lock is called it should try to take the lock * TODO: Remove skipLock as it's not a good interface. If lock is called it should try to take the lock
* without an option to skip. * without an option to skip.
*/ */
export async function lock<T extends unknown>( export async function lock<T>(
appId: number | number[], appId: number | number[],
{ force = false, skipLock = false }: { force: boolean; skipLock?: boolean }, { force = false, skipLock = false }: { force: boolean; skipLock?: boolean },
fn: () => Resolvable<T>, fn: () => Resolvable<T>,

View File

@ -171,13 +171,13 @@ export class LocalModeManager {
e.message, e.message,
); );
return this.collectContainerResources(fallback); return this.collectContainerResources(fallback);
} catch (e: any) { } catch (err: any) {
// Inspect operation fails (using legacy container name?). // Inspect operation fails (using legacy container name?).
const fallback = SUPERVISOR_LEGACY_CONTAINER_NAME_FALLBACK; const fallback = SUPERVISOR_LEGACY_CONTAINER_NAME_FALLBACK;
log.warn( log.warn(
'Supervisor container resources cannot be obtained by container ID. ' + 'Supervisor container resources cannot be obtained by container ID. ' +
`Using '${fallback}' name instead.`, `Using '${fallback}' name instead.`,
e.message, err.message,
); );
return this.collectContainerResources(fallback); return this.collectContainerResources(fallback);
} }

View File

@ -4,15 +4,12 @@ import * as _ from 'lodash';
import * as config from './config'; import * as config from './config';
import * as db from './db'; import * as db from './db';
import * as eventTracker from './event-tracker'; import * as eventTracker from './event-tracker';
import { LogType } from './lib/log-types'; import type { LogType } from './lib/log-types';
import { writeLock } from './lib/update-lock'; import { writeLock } from './lib/update-lock';
import { import type { LogBackend, LogMessage } from './logging';
BalenaLogBackend, import { BalenaLogBackend, LocalLogBackend } from './logging';
LocalLogBackend, import type { MonitorHook } from './logging/monitor';
LogBackend, import logMonitor from './logging/monitor';
LogMessage,
} from './logging';
import logMonitor, { MonitorHook } from './logging/monitor';
import * as globalEventBus from './event-bus'; import * as globalEventBus from './event-bus';
import superConsole from './lib/supervisor-console'; import superConsole from './lib/supervisor-console';

View File

@ -1,11 +1,12 @@
import { ClientRequest } from 'http'; import type { ClientRequest } from 'http';
import * as https from 'https'; import * as https from 'https';
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as stream from 'stream'; import * as stream from 'stream';
import * as url from 'url'; import * as url from 'url';
import * as zlib from 'zlib'; import * as zlib from 'zlib';
import { LogBackend, LogMessage } from './log-backend'; import type { LogMessage } from './log-backend';
import { LogBackend } from './log-backend';
import log from '../lib/supervisor-console'; import log from '../lib/supervisor-console';

View File

@ -2,7 +2,8 @@ import * as _ from 'lodash';
import { Readable } from 'stream'; import { Readable } from 'stream';
import { checkInt } from '../lib/validation'; import { checkInt } from '../lib/validation';
import { LogBackend, LogMessage } from './log-backend'; import type { LogMessage } from './log-backend';
import { LogBackend } from './log-backend';
import log from '../lib/supervisor-console'; import log from '../lib/supervisor-console';

View File

@ -183,7 +183,10 @@ class LogMonitor {
const isStdErr = row.PRIORITY === '3'; const isStdErr = row.PRIORITY === '3';
const timestamp = Math.floor(Number(row.__REALTIME_TIMESTAMP) / 1000); // microseconds to milliseconds const timestamp = Math.floor(Number(row.__REALTIME_TIMESTAMP) / 1000); // microseconds to milliseconds
this.updateContainerSentTimestamp(containerId, timestamp); this.updateContainerSentTimestamp(containerId, timestamp);
this.containers[containerId].hook({ message, isStdErr, timestamp });
// WARNING: this could lead to a memory leak as the hook is not being awaited
// and the journal can be very verbose
void this.containers[containerId].hook({ message, isStdErr, timestamp });
} }
private updateContainerSentTimestamp( private updateContainerSentTimestamp(

View File

@ -1,7 +1,7 @@
const Bluebird = require('bluebird'); import * as Bluebird from 'bluebird';
const _ = require('lodash'); import * as _ from 'lodash';
var tryParse = function (obj) { const tryParse = function (obj) {
try { try {
return JSON.parse(obj); return JSON.parse(obj);
} catch { } catch {
@ -9,14 +9,14 @@ var tryParse = function (obj) {
} }
}; };
var singleToMulticontainerApp = function (app) { const singleToMulticontainerApp = function (app) {
// From *very* old supervisors, env or config may be null // From *very* old supervisors, env or config may be null
// so we ignore errors parsing them // so we ignore errors parsing them
const conf = tryParse(app.config); const conf = tryParse(app.config);
const env = tryParse(app.env); const env = tryParse(app.env);
const environment = {}; const environment = {};
const appId = parseInt(app.appId, 10); const appId = parseInt(app.appId, 10);
for (let key in env) { for (const key in env) {
if (!/^RESIN_/.test(key)) { if (!/^RESIN_/.test(key)) {
environment[key] = env[key]; environment[key] = env[key];
} }
@ -68,7 +68,7 @@ var singleToMulticontainerApp = function (app) {
return newApp; return newApp;
}; };
var jsonifyAppFields = function (app) { const jsonifyAppFields = function (app) {
const newApp = _.clone(app); const newApp = _.clone(app);
newApp.services = JSON.stringify(app.services); newApp.services = JSON.stringify(app.services);
newApp.networks = JSON.stringify(app.networks); newApp.networks = JSON.stringify(app.networks);
@ -76,7 +76,7 @@ var jsonifyAppFields = function (app) {
return newApp; return newApp;
}; };
var imageForApp = function (app) { const imageForApp = function (app) {
const service = app.services[0]; const service = app.services[0];
return { return {
name: service.image, name: service.image,
@ -89,7 +89,7 @@ var imageForApp = function (app) {
}; };
}; };
var imageForDependentApp = function (app) { const imageForDependentApp = function (app) {
return { return {
name: app.image, name: app.image,
appId: app.appId, appId: app.appId,

View File

@ -1,4 +1,4 @@
const fs = require('fs'); import * as fs from 'fs';
const configJsonPath = process.env.CONFIG_MOUNT_POINT; const configJsonPath = process.env.CONFIG_MOUNT_POINT;
exports.up = function (knex) { exports.up = function (knex) {

View File

@ -1,5 +1,5 @@
const Bluebird = require('bluebird'); import * as Bluebird from 'bluebird';
const fs = require('fs'); import * as fs from 'fs';
const configJsonPath = process.env.CONFIG_MOUNT_POINT; const configJsonPath = process.env.CONFIG_MOUNT_POINT;
exports.up = function (knex) { exports.up = function (knex) {

View File

@ -1,4 +1,4 @@
const _ = require('lodash'); import * as _ from 'lodash';
// We take legacy deviceConfig targets and store them without the RESIN_ prefix // We take legacy deviceConfig targets and store them without the RESIN_ prefix
// (we also strip the BALENA_ prefix for completeness, even though no supervisors // (we also strip the BALENA_ prefix for completeness, even though no supervisors

View File

@ -1,7 +1,7 @@
const fs = require('fs'); import * as fs from 'fs';
const configJsonPath = process.env.CONFIG_MOUNT_POINT; const configJsonPath = process.env.CONFIG_MOUNT_POINT;
const { checkTruthy } = require('../lib/validation'); import { checkTruthy } from '../lib/validation';
exports.up = function (knex) { exports.up = function (knex) {
return knex('config') return knex('config')

View File

@ -1,7 +1,7 @@
const fs = require('fs'); import * as fs from 'fs';
const configJsonPath = process.env.CONFIG_MOUNT_POINT; const configJsonPath = process.env.CONFIG_MOUNT_POINT;
const { checkTruthy } = require('../lib/validation'); import { checkTruthy } from '../lib/validation';
exports.up = function (knex) { exports.up = function (knex) {
return new Promise((resolve) => { return new Promise((resolve) => {

View File

@ -80,7 +80,7 @@ export const startConnectivityCheck = _.once(
watch(constants.vpnStatusPath, vpnStatusInotifyCallback); watch(constants.vpnStatusPath, vpnStatusInotifyCallback);
if (enable) { if (enable) {
vpnStatusInotifyCallback(); void vpnStatusInotifyCallback();
} }
const parsedUrl = url.parse(apiEndpoint); const parsedUrl = url.parse(apiEndpoint);

View File

@ -72,8 +72,8 @@ export class Supervisor {
routers: [v1.router, v2.router], routers: [v1.router, v2.router],
healthchecks: [apiBinder.healthcheck, deviceState.healthcheck], healthchecks: [apiBinder.healthcheck, deviceState.healthcheck],
}); });
this.api.listen(conf.listenPort, conf.apiTimeout);
deviceState.on('shutdown', () => this.api.stop()); deviceState.on('shutdown', () => this.api.stop());
return this.api.listen(conf.listenPort, conf.apiTimeout);
})(), })(),
apiBinder.start(), apiBinder.start(),
]); ]);

View File

@ -1,5 +1,6 @@
import * as t from 'io-ts'; import * as t from 'io-ts';
import { chain, fold, isRight, left, right, Either } from 'fp-ts/lib/Either'; import type { Either } from 'fp-ts/lib/Either';
import { chain, fold, isRight, left, right } from 'fp-ts/lib/Either';
import { pipe, flow } from 'fp-ts/lib/function'; import { pipe, flow } from 'fp-ts/lib/function';
/** /**
@ -154,7 +155,7 @@ export type LabelObject = t.TypeOf<typeof LabelObject>;
// Valid docker container and volume name according to // Valid docker container and volume name according to
// https://github.com/moby/moby/blob/04c6f09fbdf60c7765cc4cb78883faaa9d971fa5/daemon/daemon.go#L56 // https://github.com/moby/moby/blob/04c6f09fbdf60c7765cc4cb78883faaa9d971fa5/daemon/daemon.go#L56
// [a-zA-Z0-9][a-zA-Z0-9_.-] // [a-zA-Z0-9][a-zA-Z0-9_.-]
const DOCKER_NAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9_\.\-]*$/; const DOCKER_NAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/;
export const DockerName = shortStringWithRegex( export const DockerName = shortStringWithRegex(
'LabelName', 'LabelName',
DOCKER_NAME_REGEX, DOCKER_NAME_REGEX,

View File

@ -1,8 +1,8 @@
import * as t from 'io-ts'; import * as t from 'io-ts';
// TODO: move all these exported types to ../compose/types // TODO: move all these exported types to ../compose/types
import { ComposeNetworkConfig } from '../compose/types/network'; import type { ComposeNetworkConfig } from '../compose/types/network';
import { ComposeVolumeConfig } from '../compose/volume'; import type { ComposeVolumeConfig } from '../compose/volume';
import { import {
DockerName, DockerName,
@ -15,7 +15,7 @@ import {
nonEmptyRecord, nonEmptyRecord,
} from './basic'; } from './basic';
import App from '../compose/app'; import type App from '../compose/app';
export type DeviceLegacyReport = Partial<{ export type DeviceLegacyReport = Partial<{
api_port: number; api_port: number;

View File

@ -18,7 +18,7 @@ import {
expectSteps, expectSteps,
expectNoStep, expectNoStep,
} from '~/test-lib/state-helper'; } from '~/test-lib/state-helper';
import { InstancedAppState } from '~/src/types'; import type { InstancedAppState } from '~/src/types';
// TODO: application manager inferNextSteps still queries some stuff from // TODO: application manager inferNextSteps still queries some stuff from
// the engine instead of receiving that information as parameter. Refactoring // the engine instead of receiving that information as parameter. Refactoring

View File

@ -1,5 +1,3 @@
import * as _ from 'lodash';
import { expect } from 'chai'; import { expect } from 'chai';
import Service from '~/src/compose/service'; import Service from '~/src/compose/service';

View File

@ -1,5 +1,6 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { SinonStub, stub } from 'sinon'; import type { SinonStub } from 'sinon';
import { stub } from 'sinon';
import Volume from '~/src/compose/volume'; import Volume from '~/src/compose/volume';
import * as logTypes from '~/lib/log-types'; import * as logTypes from '~/lib/log-types';
import * as logger from '~/src/logger'; import * as logger from '~/src/logger';

View File

@ -1,14 +1,15 @@
import * as _ from 'lodash';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import { SinonSpy, spy, stub } from 'sinon'; import type { SinonSpy } from 'sinon';
import { spy, stub } from 'sinon';
import { expect } from 'chai'; import { expect } from 'chai';
import { testfs, TestFs } from 'mocha-pod'; import type { TestFs } from 'mocha-pod';
import { testfs } from 'mocha-pod';
import { fnSchema } from '~/src/config/functions'; import { fnSchema } from '~/src/config/functions';
import * as hostUtils from '~/lib/host-utils'; import * as hostUtils from '~/lib/host-utils';
import { configJsonPath } from '~/lib/constants'; import { configJsonPath } from '~/lib/constants';
// Utility method to use along with `require` // Utility type to use along with `require`
type Config = typeof import('~/src/config'); type Config = typeof import('~/src/config');
describe('config', () => { describe('config', () => {
@ -35,6 +36,7 @@ describe('config', () => {
}); });
it('reads and exposes values from config.json', async () => { it('reads and exposes values from config.json', async () => {
// eslint-disable-next-line
const config = require('~/src/config') as Config; const config = require('~/src/config') as Config;
await config.initialized(); await config.initialized();
const configJson = await readConfigJson(); const configJson = await readConfigJson();
@ -43,6 +45,7 @@ describe('config', () => {
}); });
it('allows reading several values in one getMany call', async () => { it('allows reading several values in one getMany call', async () => {
// eslint-disable-next-line
const config = require('~/src/config') as Config; const config = require('~/src/config') as Config;
await config.initialized(); await config.initialized();
const configJson = await readConfigJson(); const configJson = await readConfigJson();
@ -55,6 +58,7 @@ describe('config', () => {
}); });
it('generates a uuid and stores it in config.json', async () => { it('generates a uuid and stores it in config.json', async () => {
// eslint-disable-next-line
const config = require('~/src/config') as Config; const config = require('~/src/config') as Config;
await config.initialized(); await config.initialized();
const configJson = await readConfigJson(); const configJson = await readConfigJson();
@ -65,6 +69,7 @@ describe('config', () => {
}); });
it('does not allow setting an immutable field', async () => { it('does not allow setting an immutable field', async () => {
// eslint-disable-next-line
const config = require('~/src/config') as Config; const config = require('~/src/config') as Config;
await config.initialized(); await config.initialized();
return expect(config.set({ deviceType: 'a different device type' })).to.be return expect(config.set({ deviceType: 'a different device type' })).to.be
@ -72,6 +77,7 @@ describe('config', () => {
}); });
it('allows setting both config.json and database fields transparently', async () => { it('allows setting both config.json and database fields transparently', async () => {
// eslint-disable-next-line
const config = require('~/src/config') as Config; const config = require('~/src/config') as Config;
await config.initialized(); await config.initialized();
await config.set({ await config.set({
@ -86,6 +92,7 @@ describe('config', () => {
}); });
it('allows deleting a config.json key and returns a default value if none is set', async () => { it('allows deleting a config.json key and returns a default value if none is set', async () => {
// eslint-disable-next-line
const config = require('~/src/config') as Config; const config = require('~/src/config') as Config;
await config.initialized(); await config.initialized();
await config.remove('appUpdatePollInterval'); await config.remove('appUpdatePollInterval');
@ -94,6 +101,7 @@ describe('config', () => {
}); });
it('allows deleting a config.json key if it is null', async () => { it('allows deleting a config.json key if it is null', async () => {
// eslint-disable-next-line
const config = require('~/src/config') as Config; const config = require('~/src/config') as Config;
await config.initialized(); await config.initialized();
await config.set({ apiKey: null }); await config.set({ apiKey: null });
@ -107,6 +115,7 @@ describe('config', () => {
}); });
it('does not allow modifying or removing a function value', async () => { it('does not allow modifying or removing a function value', async () => {
// eslint-disable-next-line
const config = require('~/src/config') as Config; const config = require('~/src/config') as Config;
await config.initialized(); await config.initialized();
// We have to cast to any below, as the type system will // We have to cast to any below, as the type system will
@ -116,19 +125,19 @@ describe('config', () => {
}); });
it('throws when asked for an unknown key', async () => { it('throws when asked for an unknown key', async () => {
// eslint-disable-next-line
const config = require('~/src/config') as Config; const config = require('~/src/config') as Config;
await config.initialized(); await config.initialized();
await expect(config.get('unknownInvalidValue' as any)).to.be.rejected; await expect(config.get('unknownInvalidValue' as any)).to.be.rejected;
}); });
it('emits a change event when values change', async () => { it('emits a change event when values change', async () => {
// eslint-disable-next-line
const config = require('~/src/config') as Config; const config = require('~/src/config') as Config;
await config.initialized(); await config.initialized();
const listener = stub(); const listener = stub();
config.on('change', listener); config.on('change', listener);
config.set({ name: 'someValue' }); await config.set({ name: 'someValue' });
await new Promise((resolve) => setTimeout(resolve, 1000));
expect(listener).to.have.been.calledWith({ name: 'someValue' }); expect(listener).to.have.been.calledWith({ name: 'someValue' });
}); });
@ -149,6 +158,7 @@ describe('config', () => {
), ),
}).enable(); }).enable();
// eslint-disable-next-line
const config = require('~/src/config') as Config; const config = require('~/src/config') as Config;
await config.initialized(); await config.initialized();
await config.set({ developmentMode: false }); await config.set({ developmentMode: false });
@ -160,6 +170,7 @@ describe('config', () => {
}); });
it('reads and exposes MAC addresses', async () => { it('reads and exposes MAC addresses', async () => {
// eslint-disable-next-line
const config = require('~/src/config') as Config; const config = require('~/src/config') as Config;
await config.initialized(); await config.initialized();
const macAddress = await config.get('macAddress'); const macAddress = await config.get('macAddress');
@ -168,12 +179,14 @@ describe('config', () => {
describe('Function config providers', () => { describe('Function config providers', () => {
it('should throw if a non-mutable function provider is set', async () => { it('should throw if a non-mutable function provider is set', async () => {
// eslint-disable-next-line
const config = require('~/src/config') as Config; const config = require('~/src/config') as Config;
await config.initialized(); await config.initialized();
await expect(config.set({ version: 'some-version' })).to.be.rejected; await expect(config.set({ version: 'some-version' })).to.be.rejected;
}); });
it('should throw if a non-mutable function provider is removed', async () => { it('should throw if a non-mutable function provider is removed', async () => {
// eslint-disable-next-line
const config = require('~/src/config') as Config; const config = require('~/src/config') as Config;
await config.initialized(); await config.initialized();
await expect(config.remove('version' as any)).to.be.rejected; await expect(config.remove('version' as any)).to.be.rejected;
@ -181,14 +194,15 @@ describe('config', () => {
}); });
describe('Config data sources', () => { describe('Config data sources', () => {
afterEach(() => { afterEach(async () => {
// Clean up memoized values // Clean up memoized values
fnSchema.deviceArch.clear(); await fnSchema.deviceArch.clear();
fnSchema.deviceType.clear(); await fnSchema.deviceType.clear();
}); });
it('should obtain deviceArch from device-type.json', async () => { it('should obtain deviceArch from device-type.json', async () => {
const dtJson = await readDeviceTypeJson(); const dtJson = await readDeviceTypeJson();
// eslint-disable-next-line
const config = require('~/src/config') as Config; const config = require('~/src/config') as Config;
await config.initialized(); await config.initialized();
@ -198,6 +212,7 @@ describe('config', () => {
it('should obtain deviceType from device-type.json', async () => { it('should obtain deviceType from device-type.json', async () => {
const dtJson = await readDeviceTypeJson(); const dtJson = await readDeviceTypeJson();
// eslint-disable-next-line
const config = require('~/src/config') as Config; const config = require('~/src/config') as Config;
await config.initialized(); await config.initialized();
@ -206,6 +221,7 @@ describe('config', () => {
}); });
it('should memoize values from device-type.json', async () => { it('should memoize values from device-type.json', async () => {
// eslint-disable-next-line
const config = require('~/src/config') as Config; const config = require('~/src/config') as Config;
await config.initialized(); await config.initialized();
const dtJson = await readDeviceTypeJson(); const dtJson = await readDeviceTypeJson();
@ -233,6 +249,7 @@ describe('config', () => {
}); });
it('should not memoize errors when reading deviceArch', async () => { it('should not memoize errors when reading deviceArch', async () => {
// eslint-disable-next-line
const config = require('~/src/config') as Config; const config = require('~/src/config') as Config;
await config.initialized(); await config.initialized();
@ -253,6 +270,7 @@ describe('config', () => {
}); });
it('should not memoize errors when reading deviceType', async () => { it('should not memoize errors when reading deviceType', async () => {
// eslint-disable-next-line
const config = require('~/src/config') as Config; const config = require('~/src/config') as Config;
await config.initialized(); await config.initialized();

View File

@ -1,5 +1,6 @@
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import { testfs, TestFs } from 'mocha-pod'; import type { TestFs } from 'mocha-pod';
import { testfs } from 'mocha-pod';
import { expect } from 'chai'; import { expect } from 'chai';
import * as hostUtils from '~/lib/host-utils'; import * as hostUtils from '~/lib/host-utils';

View File

@ -7,7 +7,7 @@ import { Extlinux } from '~/src/config/backends/extlinux';
import { ConfigTxt } from '~/src/config/backends/config-txt'; import { ConfigTxt } from '~/src/config/backends/config-txt';
import { ConfigFs } from '~/src/config/backends/config-fs'; import { ConfigFs } from '~/src/config/backends/config-fs';
import { SplashImage } from '~/src/config/backends/splash-image'; import { SplashImage } from '~/src/config/backends/splash-image';
import { ConfigBackend } from '~/src/config/backends/backend'; import type { ConfigBackend } from '~/src/config/backends/backend';
import * as hostUtils from '~/lib/host-utils'; import * as hostUtils from '~/lib/host-utils';

View File

@ -1,4 +1,5 @@
import { knex, Knex } from 'knex'; import type { Knex } from 'knex';
import { knex } from 'knex';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import { expect } from 'chai'; import { expect } from 'chai';
@ -56,6 +57,7 @@ describe('db', () => {
}); });
it('creates a database at the path passed on creation', async () => { it('creates a database at the path passed on creation', async () => {
// eslint-disable-next-line
const testDb = require('~/src/db') as Db; const testDb = require('~/src/db') as Db;
await testDb.initialized(); await testDb.initialized();
await expect(fs.access(constants.databasePath)).to.not.be.rejected; await expect(fs.access(constants.databasePath)).to.not.be.rejected;
@ -64,6 +66,7 @@ describe('db', () => {
it('migrations add new fields and removes old ones in an old database', async () => { it('migrations add new fields and removes old ones in an old database', async () => {
// Create a database with an older schema // Create a database with an older schema
const knexForDB = await createOldDatabase(constants.databasePath); const knexForDB = await createOldDatabase(constants.databasePath);
// eslint-disable-next-line
const testDb = require('~/src/db') as Db; const testDb = require('~/src/db') as Db;
await testDb.initialized(); await testDb.initialized();
await Promise.all([ await Promise.all([
@ -87,6 +90,7 @@ describe('db', () => {
}); });
it('creates a deviceConfig table with a single default value', async () => { it('creates a deviceConfig table with a single default value', async () => {
// eslint-disable-next-line
const testDb = require('~/src/db') as Db; const testDb = require('~/src/db') as Db;
await testDb.initialized(); await testDb.initialized();
const deviceConfig = await testDb.models('deviceConfig').select(); const deviceConfig = await testDb.models('deviceConfig').select();
@ -95,6 +99,7 @@ describe('db', () => {
}); });
it('allows performing transactions', async () => { it('allows performing transactions', async () => {
// eslint-disable-next-line
const testDb = require('~/src/db') as Db; const testDb = require('~/src/db') as Db;
await testDb.initialized(); await testDb.initialized();
return testDb.transaction((trx) => expect(trx.commit()).to.be.fulfilled); return testDb.transaction((trx) => expect(trx.commit()).to.be.fulfilled);

View File

@ -1,5 +1,6 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { stub, SinonStub } from 'sinon'; import type { SinonStub } from 'sinon';
import { stub } from 'sinon';
import * as Docker from 'dockerode'; import * as Docker from 'dockerode';
import * as request from 'supertest'; import * as request from 'supertest';
import { setTimeout } from 'timers/promises'; import { setTimeout } from 'timers/promises';
@ -191,17 +192,21 @@ describe('manages application lifecycle', () => {
// This test suite will timeout if anything goes wrong, since // This test suite will timeout if anything goes wrong, since
// we don't have any way of knowing whether Docker has finished // we don't have any way of knowing whether Docker has finished
// setting up containers or not. // setting up containers or not.
while (true) { let containers = await docker.listContainers({ all: true });
const containers = await docker.listContainers({ all: true }); let containerInspects = await Promise.all(
const containerInspects = await Promise.all(
containers.map(({ Id }) => docker.getContainer(Id).inspect()), containers.map(({ Id }) => docker.getContainer(Id).inspect()),
); );
if (expected === containers.length && isWaitComplete(containerInspects)) { while (
return containerInspects; expected !== containers.length ||
} else { !isWaitComplete(containerInspects)
) {
await setTimeout(500); await setTimeout(500);
containers = await docker.listContainers({ all: true });
containerInspects = await Promise.all(
containers.map(({ Id }) => docker.getContainer(Id).inspect()),
);
} }
} return containerInspects;
}; };
// Get NEW container inspects. This function should be passed to waitForSetup // Get NEW container inspects. This function should be passed to waitForSetup
@ -217,12 +222,10 @@ describe('manages application lifecycle', () => {
// Images are ignored in local mode so we need to pull the base image // Images are ignored in local mode so we need to pull the base image
await docker.pull(BASE_IMAGE); await docker.pull(BASE_IMAGE);
// Wait for base image to finish pulling // Wait for base image to finish pulling
while (true) { let images = await docker.listImages();
const images = await docker.listImages(); while (images.length === 0) {
if (images.length > 0) {
break;
}
await setTimeout(500); await setTimeout(500);
images = await docker.listImages();
} }
}); });

View File

@ -6,7 +6,7 @@ import * as config from '~/src/config';
import * as testDb from '~/src/db'; import * as testDb from '~/src/db';
import * as deviceApi from '~/src/device-api'; import * as deviceApi from '~/src/device-api';
import * as middleware from '~/src/device-api/middleware'; import * as middleware from '~/src/device-api/middleware';
import { AuthorizedRequest } from '~/src/device-api/api-keys'; import type { AuthorizedRequest } from '~/src/device-api/api-keys';
describe('device-api/api-keys', () => { describe('device-api/api-keys', () => {
let app: express.Application; let app: express.Application;

View File

@ -1,4 +1,4 @@
import * as express from 'express'; import type * as express from 'express';
import * as request from 'supertest'; import * as request from 'supertest';
import * as deviceApi from '~/src/device-api'; import * as deviceApi from '~/src/device-api';
@ -10,7 +10,7 @@ describe('device-api/index', () => {
api = new deviceApi.SupervisorAPI({ api = new deviceApi.SupervisorAPI({
routers: [], routers: [],
healthchecks: [], healthchecks: [],
// @ts-expect-error // @ts-expect-error extract private variable for testing
}).api; }).api;
// Express app set in SupervisorAPI is private here // Express app set in SupervisorAPI is private here
// but we need to access it for supertest // but we need to access it for supertest

View File

@ -1,12 +1,13 @@
import { expect } from 'chai'; import { expect } from 'chai';
import * as express from 'express'; import type * as express from 'express';
import { SinonStub, stub } from 'sinon'; import type { SinonStub } from 'sinon';
import { stub } from 'sinon';
import * as request from 'supertest'; import * as request from 'supertest';
import * as config from '~/src/config'; import * as config from '~/src/config';
import * as db from '~/src/db'; import * as db from '~/src/db';
import * as hostConfig from '~/src/host-config'; import * as hostConfig from '~/src/host-config';
import Service from '~/src/compose/service'; import type Service from '~/src/compose/service';
import * as deviceApi from '~/src/device-api'; import * as deviceApi from '~/src/device-api';
import * as actions from '~/src/device-api/actions'; import * as actions from '~/src/device-api/actions';
import * as v1 from '~/src/device-api/v1'; import * as v1 from '~/src/device-api/v1';
@ -32,7 +33,7 @@ describe('device-api/v1', () => {
api = new deviceApi.SupervisorAPI({ api = new deviceApi.SupervisorAPI({
routers: [v1.router], routers: [v1.router],
healthchecks: [], healthchecks: [],
// @ts-expect-error // @ts-expect-error extract private variable for testing
}).api; }).api;
}); });
@ -41,7 +42,7 @@ describe('device-api/v1', () => {
api = new deviceApi.SupervisorAPI({ api = new deviceApi.SupervisorAPI({
routers: [v1.router], routers: [v1.router],
healthchecks: [], healthchecks: [],
// @ts-expect-error // @ts-expect-error extract private variable for testing
}).api; }).api;
}); });
@ -49,7 +50,7 @@ describe('device-api/v1', () => {
api = new deviceApi.SupervisorAPI({ api = new deviceApi.SupervisorAPI({
routers: [v1.router], routers: [v1.router],
healthchecks: [stub().resolves(true), stub().resolves(true)], healthchecks: [stub().resolves(true), stub().resolves(true)],
// @ts-expect-error // @ts-expect-error extract private variable for testing
}).api; }).api;
await request(api).get('/v1/healthy').expect(200); await request(api).get('/v1/healthy').expect(200);
}); });
@ -58,7 +59,7 @@ describe('device-api/v1', () => {
api = new deviceApi.SupervisorAPI({ api = new deviceApi.SupervisorAPI({
routers: [v1.router], routers: [v1.router],
healthchecks: [stub().resolves(false), stub().resolves(true)], healthchecks: [stub().resolves(false), stub().resolves(true)],
// @ts-expect-error // @ts-expect-error extract private variable for testing
}).api; }).api;
await request(api).get('/v1/healthy').expect(500); await request(api).get('/v1/healthy').expect(500);
}); });

View File

@ -1,6 +1,7 @@
import { expect } from 'chai'; import { expect } from 'chai';
import * as express from 'express'; import type * as express from 'express';
import { SinonStub, stub } from 'sinon'; import type { SinonStub } from 'sinon';
import { stub } from 'sinon';
import * as request from 'supertest'; import * as request from 'supertest';
import * as config from '~/src/config'; import * as config from '~/src/config';
@ -28,7 +29,7 @@ describe('device-api/v2', () => {
api = new deviceApi.SupervisorAPI({ api = new deviceApi.SupervisorAPI({
routers: [v2.router], routers: [v2.router],
healthchecks: [], healthchecks: [],
// @ts-expect-error // @ts-expect-error extract private variable for testing
}).api; }).api;
}); });

View File

@ -1,7 +1,8 @@
import { stripIndent } from 'common-tags'; import { stripIndent } from 'common-tags';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import * as path from 'path'; import * as path from 'path';
import { SinonStub, stub, spy, SinonSpy } from 'sinon'; import type { SinonStub, SinonSpy } from 'sinon';
import { stub, spy } from 'sinon';
import { expect } from 'chai'; import { expect } from 'chai';
import * as deviceConfig from '~/src/device-config'; import * as deviceConfig from '~/src/device-config';

View File

@ -6,7 +6,7 @@ import * as updateLock from '~/lib/update-lock';
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 { appsJsonBackup, loadTargetFromFile } from '~/src/device-state/preload'; import { appsJsonBackup, loadTargetFromFile } from '~/src/device-state/preload';
import { TargetState } from '~/src/types'; import type { TargetState } from '~/src/types';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import { initializeContractRequirements } from '~/lib/contracts'; import { initializeContractRequirements } from '~/lib/contracts';
@ -196,7 +196,7 @@ describe('device-state', () => {
it('does not allow setting an invalid target state', () => { it('does not allow setting an invalid target state', () => {
// v2 state should be rejected // v2 state should be rejected
expect( return expect(
deviceState.setTarget({ deviceState.setTarget({
local: { local: {
name: 'aDeviceWithDifferentName', name: 'aDeviceWithDifferentName',

View File

@ -1,13 +1,15 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { testfs, TestFs } from 'mocha-pod'; import type { TestFs } from 'mocha-pod';
import { testfs } from 'mocha-pod';
import * as path from 'path'; import * as path from 'path';
import { SinonStub, stub } from 'sinon'; import type { SinonStub } from 'sinon';
import { stub } from 'sinon';
import * as fs from 'fs/promises'; import * as fs from 'fs/promises';
import * as hostConfig from '~/src/host-config'; import * as hostConfig from '~/src/host-config';
import * as config from '~/src/config'; import * as config from '~/src/config';
import * as applicationManager from '~/src/compose/application-manager'; import * as applicationManager from '~/src/compose/application-manager';
import { InstancedAppState } from '~/src/types/state'; import type { InstancedAppState } from '~/src/types/state';
import * as updateLock from '~/lib/update-lock'; import * as updateLock from '~/lib/update-lock';
import { UpdatesLockedError } from '~/lib/errors'; import { UpdatesLockedError } from '~/lib/errors';
import * as dbus from '~/lib/dbus'; import * as dbus from '~/lib/dbus';

View File

@ -10,7 +10,8 @@ import * as dbFormat from '~/src/device-state/db-format';
import * as iptables from '~/lib/iptables'; import * as iptables from '~/lib/iptables';
import * as firewall from '~/lib/firewall'; import * as firewall from '~/lib/firewall';
import * as constants from '~/lib/constants'; import * as constants from '~/lib/constants';
import { RuleAction, Rule } from '~/lib/iptables'; import type { Rule } from '~/lib/iptables';
import { RuleAction } from '~/lib/iptables';
import { log } from '~/lib/supervisor-console'; import { log } from '~/lib/supervisor-console';
describe('lib/firewall', function () { describe('lib/firewall', function () {

View File

@ -1,6 +1,7 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { promises as fs, mkdirSync } from 'fs'; import { promises as fs, mkdirSync } from 'fs';
import { testfs, TestFs } from 'mocha-pod'; import type { TestFs } from 'mocha-pod';
import { testfs } from 'mocha-pod';
import * as os from 'os'; import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import { stub } from 'sinon'; import { stub } from 'sinon';
@ -174,7 +175,7 @@ describe('lib/lockfile', () => {
await expect(lockfile.lock(lockOne)).to.not.be.rejected; await expect(lockfile.lock(lockOne)).to.not.be.rejected;
await expect(lockfile.lock(lockTwo, NOBODY_UID)).to.not.be.rejected; await expect(lockfile.lock(lockTwo, NOBODY_UID)).to.not.be.rejected;
// @ts-expect-error // @ts-expect-error simulate process exit event
process.emit('exit'); process.emit('exit');
// Verify lockfile removal regardless of appId / appUuid // Verify lockfile removal regardless of appId / appUuid

View File

@ -1,5 +1,6 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { testfs, TestFs } from 'mocha-pod'; import type { TestFs } from 'mocha-pod';
import { testfs } from 'mocha-pod';
import * as osRelease from '~/lib/os-release'; import * as osRelease from '~/lib/os-release';

Some files were not shown because too many files have changed in this diff Show More