mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2024-12-22 06:57:49 +00:00
48e0733c7e
The supervisor uses the following pattern for async module initialization ```typescript // module.ts export const initialised = (async () => { // do some async initialization })(); // somewhere else import * as module from 'module'; async function setup() { await module.initialise; } ``` The above pattern means that whenever the module is imported, the initialisation procedure will be ran, which is an anti-pattern. This converts any instance of this pattern into a function ```typescript export const initialised = _.once(async () => { // do some async initialization }); ``` And anywhere else on the code it replaces the call with a ```typescript await module.initialised(); ``` Change-type: patch
217 lines
5.6 KiB
TypeScript
217 lines
5.6 KiB
TypeScript
import * as _ from 'lodash';
|
|
import { Router } from 'express';
|
|
import rewire = require('rewire');
|
|
|
|
import { unlinkAll } from '~/lib/fs-utils';
|
|
import * as applicationManager from '~/src/compose/application-manager';
|
|
import * as serviceManager from '~/src/compose/service-manager';
|
|
import * as volumeManager from '~/src/compose/volume-manager';
|
|
import * as commitStore from '~/src/compose/commit';
|
|
import * as config from '~/src/config';
|
|
import * as db from '~/src/db';
|
|
import { createV1Api } from '~/src/device-api/v1';
|
|
import { createV2Api } from '~/src/device-api/v2';
|
|
import * as deviceState from '~/src/device-state';
|
|
import SupervisorAPI from '~/src/supervisor-api';
|
|
import { Service } from '~/src/compose/service';
|
|
import { Image } from '~/src/compose/images';
|
|
|
|
const apiBinder = rewire('~/src/api-binder');
|
|
|
|
const DB_PATH = './test/data/supervisor-api.sqlite';
|
|
|
|
// Holds all values used for stubbing
|
|
const STUBBED_VALUES = {
|
|
commits: {
|
|
1: '7fc9c5bea8e361acd49886fe6cc1e1cd',
|
|
2: '4e380136c2cf56cd64197d51a1ab263a',
|
|
},
|
|
services: [
|
|
{
|
|
appId: 1,
|
|
imageId: 1111,
|
|
status: 'Running',
|
|
releaseId: 99999,
|
|
createdAt: new Date('2020-04-25T04:15:23.111Z'),
|
|
serviceName: 'main',
|
|
},
|
|
{
|
|
appId: 1,
|
|
imageId: 2222,
|
|
status: 'Running',
|
|
releaseId: 99999,
|
|
createdAt: new Date('2020-04-25T04:15:23.111Z'),
|
|
serviceName: 'redis',
|
|
},
|
|
{
|
|
appId: 2,
|
|
imageId: 3333,
|
|
containerId: 'abc123',
|
|
status: 'Running',
|
|
releaseId: 77777,
|
|
createdAt: new Date('2020-05-15T19:33:06.088Z'),
|
|
serviceName: 'main',
|
|
},
|
|
],
|
|
images: [],
|
|
networks: [],
|
|
volumes: [],
|
|
};
|
|
|
|
// Useful for creating mock services in the ServiceManager
|
|
const mockService = (overrides?: Partial<Service>) => {
|
|
return {
|
|
...{
|
|
appId: 1658654,
|
|
status: 'Running',
|
|
serviceName: 'main',
|
|
imageId: 2885946,
|
|
serviceId: 640681,
|
|
containerId:
|
|
'f93d386599d1b36e71272d46ad69770cff333842db04e2e4c64dda7b54da07c6',
|
|
createdAt: '2020-11-13T20:29:44.143Z',
|
|
config: {
|
|
labels: {},
|
|
},
|
|
toDockerContainer: () => {
|
|
return;
|
|
},
|
|
extraNetworksToJoin: () => {
|
|
return [];
|
|
},
|
|
isEqualConfig: (service: Service) => {
|
|
return _.isEqual(
|
|
_.pick(mockService, ['imageId', 'containerId', 'serviceId']),
|
|
_.pick(service, ['imageId', 'containerId', 'serviceId']),
|
|
);
|
|
},
|
|
},
|
|
...overrides,
|
|
} as Service;
|
|
};
|
|
|
|
// Useful for creating mock images that are returned from Images.getStatus
|
|
const mockImage = (overrides?: Partial<Image>) => {
|
|
return {
|
|
...{
|
|
name:
|
|
'registry2.balena-cloud.com/v2/e2bf6410ffc30850e96f5071cdd1dca8@sha256:e2e87a8139b8fc14510095b210ad652d7d5badcc64fdc686cbf749d399fba15e',
|
|
appId: 1658654,
|
|
serviceName: 'main',
|
|
imageId: 2885946,
|
|
dockerImageId:
|
|
'sha256:4502983d72e2c72bc292effad1b15b49576da3801356f47fd275ba274d409c1a',
|
|
status: 'Downloaded',
|
|
downloadProgress: null,
|
|
},
|
|
...overrides,
|
|
} as Image;
|
|
};
|
|
|
|
const mockedOptions = {
|
|
listenPort: 54321,
|
|
timeout: 30000,
|
|
};
|
|
|
|
/**
|
|
* THIS MOCKED API CONTAINS STUBS THAT MIGHT CAUSE UNEXPECTED RESULTS
|
|
* IF YOU WANT TO ADD/MODIFY STUBS THAT INVOLVE API OPERATIONS
|
|
* AND MULTIPLE TEST CASES WILL USE THEM THEN ADD THEM HERE
|
|
* OTHERWISE YOU CAN ADD STUBS ON A PER TEST CASE BASIS
|
|
*
|
|
* EXAMPLE: We stub ApplicationManager so there is at least 1 running app
|
|
*
|
|
* You can see all the stubbed values convientiely in STUBBED_VALUES.
|
|
*
|
|
*/
|
|
|
|
async function create(
|
|
healthchecks = [deviceState.healthcheck, apiBinder.healthcheck],
|
|
): Promise<SupervisorAPI> {
|
|
// Get SupervisorAPI construct options
|
|
await createAPIOpts();
|
|
|
|
// Stub functions
|
|
setupStubs();
|
|
|
|
// Create SupervisorAPI
|
|
const api = new SupervisorAPI({
|
|
routers: [deviceState.router, buildRoutes()],
|
|
healthchecks,
|
|
});
|
|
|
|
const macAddress = await config.get('macAddress');
|
|
deviceState.reportCurrentState({
|
|
mac_address: macAddress,
|
|
});
|
|
|
|
// Return SupervisorAPI that is not listening yet
|
|
return api;
|
|
}
|
|
|
|
async function cleanUp(): Promise<void> {
|
|
// Clean up test data
|
|
await unlinkAll(DB_PATH);
|
|
// Restore created SinonStubs
|
|
return restoreStubs();
|
|
}
|
|
|
|
async function createAPIOpts(): Promise<void> {
|
|
await db.initialized();
|
|
await deviceState.initialized();
|
|
|
|
// Initialize and set values for mocked Config
|
|
await initConfig();
|
|
}
|
|
|
|
async function initConfig(): Promise<void> {
|
|
// Initialize this config
|
|
await config.initialized();
|
|
|
|
// Set a currentCommit
|
|
for (const [id, commit] of Object.entries(STUBBED_VALUES.commits)) {
|
|
await commitStore.upsertCommitForApp(parseInt(id, 10), commit);
|
|
}
|
|
}
|
|
|
|
function buildRoutes(): Router {
|
|
// Add to existing apiBinder router (it contains additional middleware and endpoints)
|
|
const router = apiBinder.router;
|
|
// Add V1 routes
|
|
createV1Api(applicationManager.router);
|
|
// Add V2 routes
|
|
createV2Api(applicationManager.router);
|
|
// Return modified Router
|
|
return router;
|
|
}
|
|
|
|
// TO-DO: Create a cleaner way to restore previous values.
|
|
const originalVolGetAll = volumeManager.getAllByAppId;
|
|
const originalSvcGetStatus = serviceManager.getState;
|
|
const originalReadyForUpdates = apiBinder.__get__('readyForUpdates');
|
|
|
|
function setupStubs() {
|
|
apiBinder.__set__('readyForUpdates', true);
|
|
// @ts-expect-error Assigning to a RO property
|
|
volumeManager.getAllByAppId = async () => STUBBED_VALUES.volumes;
|
|
// @ts-expect-error Assigning to a RO property
|
|
serviceManager.getState = async () => STUBBED_VALUES.services;
|
|
}
|
|
|
|
function restoreStubs() {
|
|
apiBinder.__set__('readyForUpdates', originalReadyForUpdates);
|
|
// @ts-expect-error Assigning to a RO property
|
|
volumeManager.getAllByAppId = originalVolGetAll;
|
|
// @ts-expect-error Assigning to a RO property
|
|
serviceManager.getState = originalSvcGetStatus;
|
|
}
|
|
|
|
export = {
|
|
create,
|
|
cleanUp,
|
|
STUBBED_VALUES,
|
|
mockService,
|
|
mockImage,
|
|
mockedOptions,
|
|
};
|