diff --git a/package-lock.json b/package-lock.json index d13d52bd..d7f06702 100644 --- a/package-lock.json +++ b/package-lock.json @@ -352,9 +352,9 @@ "dev": true }, "@types/lodash": { - "version": "4.14.122", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.122.tgz", - "integrity": "sha512-9IdED8wU93ty8gP06ninox+42SBSJHp2IAamsSYMUY76mshRTeUsid/gtbl8ovnOwy8im41ib4cxTiIYMXGKew==", + "version": "4.14.149", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz", + "integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==", "dev": true }, "@types/lodash.memoize": { diff --git a/package.json b/package.json index e37554a9..bde1b9c9 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "@types/express": "^4.17.2", "@types/knex": "^0.14.14", "@types/lockfile": "^1.0.1", - "@types/lodash": "4.14.122", + "@types/lodash": "^4.14.149", "@types/memoizee": "^0.4.3", "@types/mkdirp": "^0.5.2", "@types/mocha": "^5.2.7", diff --git a/src/compose/images.ts b/src/compose/images.ts index ba67d836..b5c05c15 100644 --- a/src/compose/images.ts +++ b/src/compose/images.ts @@ -53,10 +53,6 @@ export interface Image { downloadProgress: Nullable; } -// TODO: This is necessary for the format() method, but I'm not sure -// why, and it seems like a bad idea as it is. Fix the need for this. -type MaybeImage = { [key in keyof Image]: Image[key] | null }; - // TODO: Remove the need for this type... type NormalisedDockerImage = Docker.ImageInfo & { NormalisedRepoTags: string[]; @@ -336,11 +332,11 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) { } public async update(image: Image): Promise { - image = this.format(image); + const formattedImage = this.format(image); await this.db .models('image') - .update(image) - .where({ name: image.name }); + .update(formattedImage) + .where({ name: formattedImage.name }); } public async save(image: Image): Promise { @@ -577,17 +573,17 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) { } private async markAsSupervised(image: Image): Promise { - image = this.format(image); - // TODO: Get rid of this janky cast once the database is - // more strongly typed + const formattedImage = this.format(image); await this.db.upsertModel( 'image', - image, - (image as unknown) as Dictionary, + formattedImage, + // TODO: Upsert to new values only when they already match? This is likely a bug + // and currently acts like an "insert if not exists" + formattedImage, ); } - private format(image: MaybeImage): Image { + private format(image: Image): Omit { return _(image) .defaults({ serviceId: null, @@ -598,7 +594,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) { dockerImageId: null, }) .omit('id') - .value() as Image; + .value(); } private async fetchDelta( diff --git a/src/lib/validation.ts b/src/lib/validation.ts index 9cd11725..b3a183c7 100644 --- a/src/lib/validation.ts +++ b/src/lib/validation.ts @@ -1,6 +1,7 @@ import * as _ from 'lodash'; import { inspect } from 'util'; +import { TargetState } from '../types/state'; import { EnvVarObject, LabelObject } from './types'; import log from './supervisor-console'; @@ -206,7 +207,7 @@ function undefinedOrValidEnv(val: EnvVarObject): boolean { * * TODO: Type the input */ -export function isValidDependentAppsObject(apps: any): boolean { +export function isValidDependentAppsObject(apps: unknown): boolean { if (!_.isObject(apps)) { log.debug( 'Non-object passed to validation.isValidDependentAppsObject\nApps:', @@ -215,8 +216,8 @@ export function isValidDependentAppsObject(apps: any): boolean { return false; } - return _.every(apps, (val, appId) => { - val = _.defaults(_.clone(val), { + return _.every(apps, (v, appId) => { + const val: TargetState['dependent']['apps'][any] = _.defaults(_.clone(v), { config: undefined, environment: undefined, commit: undefined, @@ -366,7 +367,7 @@ export function isValidAppsObject(obj: any): boolean { return false; } - return _.every(obj, (val, appId) => { + return _.every(obj, (v, appId) => { if (!isValidShortText(appId) || !checkInt(appId)) { log.debug( 'Invalid appId passed to validation.isValidAppsObject\nApp ID:', @@ -375,7 +376,13 @@ export function isValidAppsObject(obj: any): boolean { return false; } - return _.conformsTo(_.defaults(_.clone(val), { releaseId: undefined }), { + // TODO: Remove this partial and validate the extra fields + const val: Partial = _.defaults( + _.clone(v), + { releaseId: undefined }, + ); + + return _.conformsTo(val, { name: (n: any) => { if (!isValidShortText(n)) { log.debug( @@ -443,7 +450,7 @@ export function isValidDependentDevicesObject(devices: any): boolean { return false; } - return _.conformsTo(val, { + return _.conformsTo(val as TargetState['dependent']['devices'][any], { name: (n: any) => { if (!isValidShortText(n)) { log.debug( @@ -470,34 +477,37 @@ export function isValidDependentDevicesObject(devices: any): boolean { return false; } - return _.every(a, app => { - app = _.defaults(_.clone(app), { - config: undefined, - environment: undefined, - }); - return _.conformsTo(app, { - config: (c: any) => { - if (!undefinedOrValidEnv(c)) { - log.debug( - 'Invalid config passed to validation.isValidDependentDevicesObject\nConfig:', - inspect(c), - ); - return false; - } - return true; - }, - environment: (e: any) => { - if (!undefinedOrValidEnv(e)) { - log.debug( - 'Invalid environment passed to validation.isValidDependentDevicesObject\nConfig:', - inspect(e), - ); - return false; - } - return true; - }, - }); - }); + return _.every( + a as TargetState['dependent']['devices'][any]['apps'], + app => { + app = _.defaults(_.clone(app), { + config: undefined, + environment: undefined, + }); + return _.conformsTo(app, { + config: (c: any) => { + if (!undefinedOrValidEnv(c)) { + log.debug( + 'Invalid config passed to validation.isValidDependentDevicesObject\nConfig:', + inspect(c), + ); + return false; + } + return true; + }, + environment: (e: any) => { + if (!undefinedOrValidEnv(e)) { + log.debug( + 'Invalid environment passed to validation.isValidDependentDevicesObject\nConfig:', + inspect(e), + ); + return false; + } + return true; + }, + }); + }, + ); }, }); }); diff --git a/src/types/state.ts b/src/types/state.ts index bcb2978e..afd8f1ce 100644 --- a/src/types/state.ts +++ b/src/types/state.ts @@ -1,6 +1,7 @@ import { ComposeNetworkConfig } from '../compose/types/network'; import { ServiceComposeConfig } from '../compose/types/service'; import { ComposeVolumeConfig } from '../compose/volume'; +import { EnvVarObject, LabelObject } from '../lib/types'; export interface DeviceApplicationState { local?: { @@ -27,7 +28,7 @@ export interface DeviceApplicationState { export interface TargetState { local: { name: string; - config: Dictionary; + config: EnvVarObject; apps: { [appId: string]: { name: string; @@ -35,12 +36,12 @@ export interface TargetState { releaseId: number; services: { [serviceId: string]: { - labels: Dictionary; + labels: LabelObject; imageId: number; serviceName: string; image: string; running: boolean; - environment: Dictionary; + environment: EnvVarObject; } & ServiceComposeConfig; }; volumes: Dictionary>; @@ -50,7 +51,22 @@ export interface TargetState { }; // TODO: Correctly type this once dependent devices are // actually properly supported - dependent: Dictionary; + dependent: { + apps: Dictionary<{ + name?: string; + image?: string; + commit?: string; + config?: EnvVarObject; + environment?: EnvVarObject; + }>; + devices: Dictionary<{ + name?: string; + apps?: Dictionary<{ + config?: EnvVarObject; + environment?: EnvVarObject; + }>; + }>; + }; } export type LocalTargetState = TargetState['local'];