From 5af146ec4e1f1e8e12fab1a7b065f60a192a2387 Mon Sep 17 00:00:00 2001
From: Christina Ying Wang <christina@balena.io>
Date: Thu, 15 Sep 2022 18:51:17 -0700
Subject: [PATCH] Move supervisor-api.ts to device-api/index.ts

Signed-off-by: Christina Ying Wang <christina@balena.io>
---
 src/device-api/index.ts               | 175 ++++++++++++++++++++++++++
 src/supervisor-api.ts                 | 175 --------------------------
 src/supervisor.ts                     |   2 +-
 test/legacy/21-supervisor-api.spec.ts |   2 +-
 test/legacy/41-device-api-v1.spec.ts  |   2 +-
 test/legacy/42-device-api-v2.spec.ts  |   2 +-
 test/lib/mocked-device-api.ts         |   2 +-
 7 files changed, 180 insertions(+), 180 deletions(-)
 delete mode 100644 src/supervisor-api.ts

diff --git a/src/device-api/index.ts b/src/device-api/index.ts
index e69de29b..4aaa138d 100644
--- a/src/device-api/index.ts
+++ b/src/device-api/index.ts
@@ -0,0 +1,175 @@
+import * as express from 'express';
+import * as _ from 'lodash';
+import * as morgan from 'morgan';
+
+import * as eventTracker from '../event-tracker';
+import * as deviceState from '../device-state';
+import blink = require('../lib/blink');
+import log from '../lib/supervisor-console';
+import * as apiKeys from '../lib/api-keys';
+import { UpdatesLockedError } from '../lib/errors';
+
+import type { Server } from 'http';
+import type { NextFunction, Request, Response } from 'express';
+
+const expressLogger = morgan(
+	(tokens, req, res) =>
+		[
+			tokens.method(req, res),
+			req.path,
+			tokens.status(req, res),
+			'-',
+			tokens['response-time'](req, res),
+			'ms',
+		].join(' '),
+	{
+		stream: { write: (d) => log.api(d.toString().trimRight()) },
+	},
+);
+
+interface SupervisorAPIConstructOpts {
+	routers: express.Router[];
+	healthchecks: Array<() => Promise<boolean>>;
+}
+
+interface SupervisorAPIStopOpts {
+	errored: boolean;
+}
+
+export class SupervisorAPI {
+	private routers: express.Router[];
+	private healthchecks: Array<() => Promise<boolean>>;
+
+	private api = express();
+	private server: Server | null = null;
+
+	public constructor({ routers, healthchecks }: SupervisorAPIConstructOpts) {
+		this.routers = routers;
+		this.healthchecks = healthchecks;
+
+		this.api.disable('x-powered-by');
+		this.api.use(expressLogger);
+
+		this.api.get('/v1/healthy', async (_req, res) => {
+			try {
+				const healths = await Promise.all(this.healthchecks.map((fn) => fn()));
+				if (!_.every(healths)) {
+					log.error('Healthcheck failed');
+					return res.status(500).send('Unhealthy');
+				}
+				return res.sendStatus(200);
+			} catch {
+				log.error('Healthcheck failed');
+				return res.status(500).send('Unhealthy');
+			}
+		});
+
+		this.api.get('/ping', (_req, res) => res.send('OK'));
+
+		this.api.use(apiKeys.authMiddleware);
+
+		this.api.post('/v1/blink', (_req, res) => {
+			eventTracker.track('Device blink');
+			blink.pattern.start();
+			setTimeout(blink.pattern.stop, 15000);
+			return res.sendStatus(200);
+		});
+
+		// Expires the supervisor's API key and generates a new one.
+		// It also communicates the new key to the balena API.
+		this.api.post(
+			'/v1/regenerate-api-key',
+			async (req: apiKeys.AuthorizedRequest, res) => {
+				await deviceState.initialized();
+				await apiKeys.initialized();
+
+				// check if we're updating the cloud API key
+				const updateCloudKey = req.auth.apiKey === apiKeys.cloudApiKey;
+
+				// regenerate the key...
+				const newKey = await apiKeys.refreshKey(req.auth.apiKey);
+
+				// if we need to update the cloud API with our new key
+				if (updateCloudKey) {
+					// report the new key to the cloud API
+					deviceState.reportCurrentState({
+						api_secret: apiKeys.cloudApiKey,
+					});
+				}
+
+				// return the value of the new key to the caller
+				res.status(200).send(newKey);
+			},
+		);
+
+		// And assign all external routers
+		for (const router of this.routers) {
+			this.api.use(router);
+		}
+
+		// Error handling.
+		const messageFromError = (err?: Error | string | null): string => {
+			let message = 'Unknown error';
+			if (err != null) {
+				if (_.isError(err) && err.message != null) {
+					message = err.message;
+				} else {
+					message = err as string;
+				}
+			}
+			return message;
+		};
+
+		this.api.use(
+			(err: Error, req: Request, res: Response, next: NextFunction) => {
+				if (res.headersSent) {
+					// Error happens while we are writing the response - default handler closes the connection.
+					next(err);
+					return;
+				}
+
+				// Return 423 Locked when locks as set
+				const code = err instanceof UpdatesLockedError ? 423 : 503;
+				if (code !== 423) {
+					log.error(`Error on ${req.method} ${req.path}: `, err);
+				}
+
+				res.status(code).send({
+					status: 'failed',
+					message: messageFromError(err),
+				});
+			},
+		);
+	}
+
+	public async listen(port: number, apiTimeout: number): Promise<void> {
+		return new Promise((resolve) => {
+			this.server = this.api.listen(port, () => {
+				log.info(`Supervisor API successfully started on port ${port}`);
+				if (this.server) {
+					this.server.timeout = apiTimeout;
+				}
+				return resolve();
+			});
+		});
+	}
+
+	public async stop(options?: SupervisorAPIStopOpts): Promise<void> {
+		if (this.server != null) {
+			return new Promise((resolve, reject) => {
+				this.server?.close((err: Error) => {
+					if (err) {
+						log.error('Failed to stop Supervisor API');
+						return reject(err);
+					}
+					options?.errored
+						? log.error('Stopped Supervisor API')
+						: log.info('Stopped Supervisor API');
+					return resolve();
+				});
+			});
+		}
+	}
+}
+
+export default SupervisorAPI;
diff --git a/src/supervisor-api.ts b/src/supervisor-api.ts
deleted file mode 100644
index ea0b549a..00000000
--- a/src/supervisor-api.ts
+++ /dev/null
@@ -1,175 +0,0 @@
-import { NextFunction, Request, Response } from 'express';
-import * as express from 'express';
-import { Server } from 'http';
-import * as _ from 'lodash';
-import * as morgan from 'morgan';
-
-import * as eventTracker from './event-tracker';
-import blink = require('./lib/blink');
-
-import log from './lib/supervisor-console';
-import * as apiKeys from './lib/api-keys';
-import * as deviceState from './device-state';
-import { UpdatesLockedError } from './lib/errors';
-
-interface SupervisorAPIConstructOpts {
-	routers: express.Router[];
-	healthchecks: Array<() => Promise<boolean>>;
-}
-
-interface SupervisorAPIStopOpts {
-	errored: boolean;
-}
-
-export class SupervisorAPI {
-	private routers: express.Router[];
-	private healthchecks: Array<() => Promise<boolean>>;
-
-	private api = express();
-	private server: Server | null = null;
-
-	public constructor({ routers, healthchecks }: SupervisorAPIConstructOpts) {
-		this.routers = routers;
-		this.healthchecks = healthchecks;
-
-		this.api.disable('x-powered-by');
-		this.api.use(
-			morgan(
-				(tokens, req, res) =>
-					[
-						tokens.method(req, res),
-						req.path,
-						tokens.status(req, res),
-						'-',
-						tokens['response-time'](req, res),
-						'ms',
-					].join(' '),
-				{
-					stream: { write: (d) => log.api(d.toString().trimRight()) },
-				},
-			),
-		);
-
-		this.api.get('/v1/healthy', async (_req, res) => {
-			try {
-				const healths = await Promise.all(this.healthchecks.map((fn) => fn()));
-				if (!_.every(healths)) {
-					log.error('Healthcheck failed');
-					return res.status(500).send('Unhealthy');
-				}
-				return res.sendStatus(200);
-			} catch {
-				log.error('Healthcheck failed');
-				return res.status(500).send('Unhealthy');
-			}
-		});
-
-		this.api.get('/ping', (_req, res) => res.send('OK'));
-
-		this.api.use(apiKeys.authMiddleware);
-
-		this.api.post('/v1/blink', (_req, res) => {
-			eventTracker.track('Device blink');
-			blink.pattern.start();
-			setTimeout(blink.pattern.stop, 15000);
-			return res.sendStatus(200);
-		});
-
-		// Expires the supervisor's API key and generates a new one.
-		// It also communicates the new key to the balena API.
-		this.api.post(
-			'/v1/regenerate-api-key',
-			async (req: apiKeys.AuthorizedRequest, res) => {
-				await deviceState.initialized();
-				await apiKeys.initialized();
-
-				// check if we're updating the cloud API key
-				const updateCloudKey = req.auth.apiKey === apiKeys.cloudApiKey;
-
-				// regenerate the key...
-				const newKey = await apiKeys.refreshKey(req.auth.apiKey);
-
-				// if we need to update the cloud API with our new key
-				if (updateCloudKey) {
-					// report the new key to the cloud API
-					deviceState.reportCurrentState({
-						api_secret: apiKeys.cloudApiKey,
-					});
-				}
-
-				// return the value of the new key to the caller
-				res.status(200).send(newKey);
-			},
-		);
-
-		// And assign all external routers
-		for (const router of this.routers) {
-			this.api.use(router);
-		}
-
-		// Error handling.
-		const messageFromError = (err?: Error | string | null): string => {
-			let message = 'Unknown error';
-			if (err != null) {
-				if (_.isError(err) && err.message != null) {
-					message = err.message;
-				} else {
-					message = err as string;
-				}
-			}
-			return message;
-		};
-
-		this.api.use(
-			(err: Error, req: Request, res: Response, next: NextFunction) => {
-				if (res.headersSent) {
-					// Error happens while we are writing the response - default handler closes the connection.
-					next(err);
-					return;
-				}
-
-				// Return 423 Locked when locks as set
-				const code = err instanceof UpdatesLockedError ? 423 : 503;
-				if (code !== 423) {
-					log.error(`Error on ${req.method} ${req.path}: `, err);
-				}
-
-				res.status(code).send({
-					status: 'failed',
-					message: messageFromError(err),
-				});
-			},
-		);
-	}
-
-	public async listen(port: number, apiTimeout: number): Promise<void> {
-		return new Promise((resolve) => {
-			this.server = this.api.listen(port, () => {
-				log.info(`Supervisor API successfully started on port ${port}`);
-				if (this.server) {
-					this.server.timeout = apiTimeout;
-				}
-				return resolve();
-			});
-		});
-	}
-
-	public async stop(options?: SupervisorAPIStopOpts): Promise<void> {
-		if (this.server != null) {
-			return new Promise((resolve, reject) => {
-				this.server?.close((err: Error) => {
-					if (err) {
-						log.error('Failed to stop Supervisor API');
-						return reject(err);
-					}
-					options?.errored
-						? log.error('Stopped Supervisor API')
-						: log.info('Stopped Supervisor API');
-					return resolve();
-				});
-			});
-		}
-	}
-}
-
-export default SupervisorAPI;
diff --git a/src/supervisor.ts b/src/supervisor.ts
index b98020ef..03d4c0ea 100644
--- a/src/supervisor.ts
+++ b/src/supervisor.ts
@@ -6,7 +6,7 @@ import { intialiseContractRequirements } from './lib/contracts';
 import { normaliseLegacyDatabase } from './lib/legacy';
 import * as osRelease from './lib/os-release';
 import * as logger from './logger';
-import SupervisorAPI from './supervisor-api';
+import SupervisorAPI from './device-api';
 import log from './lib/supervisor-console';
 import version = require('./lib/supervisor-version');
 import * as avahi from './lib/avahi';
diff --git a/test/legacy/21-supervisor-api.spec.ts b/test/legacy/21-supervisor-api.spec.ts
index 340af82a..37450db5 100644
--- a/test/legacy/21-supervisor-api.spec.ts
+++ b/test/legacy/21-supervisor-api.spec.ts
@@ -6,7 +6,7 @@ import mockedAPI = require('~/test-lib/mocked-device-api');
 import * as apiBinder from '~/src/api-binder';
 import * as deviceState from '~/src/device-state';
 import Log from '~/lib/supervisor-console';
-import SupervisorAPI from '~/src/supervisor-api';
+import SupervisorAPI from '~/src/device-api';
 import * as apiKeys from '~/lib/api-keys';
 import * as db from '~/src/db';
 import { cloudApiKey } from '~/lib/api-keys';
diff --git a/test/legacy/41-device-api-v1.spec.ts b/test/legacy/41-device-api-v1.spec.ts
index 4e789389..0bc1ce78 100644
--- a/test/legacy/41-device-api-v1.spec.ts
+++ b/test/legacy/41-device-api-v1.spec.ts
@@ -20,7 +20,7 @@ import mockedAPI = require('~/test-lib/mocked-device-api');
 import sampleResponses = require('~/test-data/device-api-responses.json');
 import * as config from '~/src/config';
 import * as logger from '~/src/logger';
-import SupervisorAPI from '~/src/supervisor-api';
+import SupervisorAPI from '~/src/device-api';
 import * as apiBinder from '~/src/api-binder';
 import * as deviceState from '~/src/device-state';
 import * as apiKeys from '~/lib/api-keys';
diff --git a/test/legacy/42-device-api-v2.spec.ts b/test/legacy/42-device-api-v2.spec.ts
index df8fd318..96905dde 100644
--- a/test/legacy/42-device-api-v2.spec.ts
+++ b/test/legacy/42-device-api-v2.spec.ts
@@ -7,7 +7,7 @@ import sampleResponses = require('~/test-data/device-api-responses.json');
 import mockedAPI = require('~/test-lib/mocked-device-api');
 import * as apiBinder from '~/src/api-binder';
 import * as deviceState from '~/src/device-state';
-import SupervisorAPI from '~/src/supervisor-api';
+import SupervisorAPI from '~/src/device-api';
 import * as serviceManager from '~/src/compose/service-manager';
 import * as images from '~/src/compose/images';
 import * as apiKeys from '~/lib/api-keys';
diff --git a/test/lib/mocked-device-api.ts b/test/lib/mocked-device-api.ts
index 4b5dba08..46e3b7c4 100644
--- a/test/lib/mocked-device-api.ts
+++ b/test/lib/mocked-device-api.ts
@@ -12,7 +12,7 @@ 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 SupervisorAPI from '~/src/device-api';
 import { Service } from '~/src/compose/service';
 import { Image } from '~/src/compose/images';