From f5183df35677f091f1374c3f6e86706b141471f3 Mon Sep 17 00:00:00 2001 From: Felipe Lalanne Date: Thu, 15 Oct 2020 12:23:49 -0300 Subject: [PATCH] Change source of deviceType to device-type.json The source of truth for the device-type should be device-type.json instead of config.json Change-type: patch Signed-off-by: Felipe Lalanne Connects-to: #1472 --- src/config/functions.ts | 53 +++++++++++----- src/config/index.ts | 26 ++++++-- src/config/schema.ts | 5 -- test/03-config.spec.ts | 134 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 194 insertions(+), 24 deletions(-) diff --git a/src/config/functions.ts b/src/config/functions.ts index 4ab0109c..b777fe05 100644 --- a/src/config/functions.ts +++ b/src/config/functions.ts @@ -1,5 +1,6 @@ import * as Bluebird from 'bluebird'; import * as _ from 'lodash'; +import * as memoizee from 'memoizee'; import { fs } from 'mz'; import { URL } from 'url'; @@ -38,22 +39,44 @@ export const fnSchema = { macAddress: () => { return macAddress.getAll(constants.macAddressPath); }, - deviceArch: async () => { - try { - // FIXME: We should be mounting the following file into the supervisor from the - // start-resin-supervisor script, changed in meta-resin - but until then, hardcode it - const data = await fs.readFile( - `${constants.rootMountPoint}/resin-boot/device-type.json`, - 'utf8', - ); - const deviceInfo = JSON.parse(data); + deviceArch: memoizee( + async () => { + try { + // FIXME: We should be mounting the following file into the supervisor from the + // start-resin-supervisor script, changed in meta-resin - but until then, hardcode it + const data = await fs.readFile( + `${constants.bootMountPoint}/device-type.json`, + 'utf8', + ); + const deviceInfo = JSON.parse(data); - return deviceInfo.arch; - } catch (e) { - log.error(`Unable to get architecture: ${e}`); - return 'unknown'; - } - }, + return deviceInfo.arch; + } catch (e) { + log.error(`Unable to get architecture: ${e}`); + throw e; + } + }, + { promise: true }, + ), + deviceType: memoizee( + async () => { + try { + // FIXME: We should be mounting the following file into the supervisor from the + // start-resin-supervisor script, changed in meta-resin - but until then, hardcode it + const data = await fs.readFile( + `${constants.bootMountPoint}/device-type.json`, + 'utf8', + ); + const deviceInfo = JSON.parse(data); + + return deviceInfo.slug; + } catch (e) { + log.error(`Unable to get device type: ${e}`); + throw e; + } + }, + { promise: true }, + ), provisioningOptions: () => { return config .getMany([ diff --git a/src/config/index.ts b/src/config/index.ts index 18ec710f..9b80fa02 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -87,11 +87,29 @@ export async function get( // 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 const promiseValue = FnSchema.fnSchema[fnKey](); - return promiseValue.then((value: unknown) => { - const decoded = schemaTypes[key].type.decode(value); + return promiseValue + .then((value: unknown) => { + const decoded = schemaTypes[key].type.decode(value); - return checkValueDecode(decoded, key, value) && decoded.right; - }); + return checkValueDecode(decoded, key, value) && decoded.right; + }) + .catch(() => { + const defaultValue = schemaTypes[key].default; + if (defaultValue instanceof t.Type) { + // For functions, this can happen if t.never is used as default + // value. In that case decoding and the value check below will throw + // (which is what is expected) + // if future functions use NullOrUndefined as with values above + // this branch will return undefined. In any case this should never + // happen + const maybeDecoded = (defaultValue as t.Type).decode(undefined); + + return ( + checkValueDecode(maybeDecoded, key, undefined) && maybeDecoded.right + ); + } + return defaultValue as SchemaReturn; + }); } else { throw new Error(`Unknown config value ${key}`); } diff --git a/src/config/schema.ts b/src/config/schema.ts index af6fa61a..5581013e 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -34,11 +34,6 @@ export const schema = { mutable: true, removeIfNull: false, }, - deviceType: { - source: 'config.json', - mutable: false, - removeIfNull: false, - }, deviceId: { source: 'config.json', mutable: true, diff --git a/test/03-config.spec.ts b/test/03-config.spec.ts index 9ffbc91d..ab9aec46 100644 --- a/test/03-config.spec.ts +++ b/test/03-config.spec.ts @@ -1,5 +1,6 @@ import * as _ from 'lodash'; import { fs } from 'mz'; +import { SinonStub, stub } from 'sinon'; import chai = require('./lib/chai-config'); import prepare = require('./lib/prepare'); @@ -7,6 +8,7 @@ import * as conf from '../src/config'; import constants = require('../src/lib/constants'); import { SchemaTypeKey } from '../src/config/schema-type'; +import { fnSchema } from '../src/config/functions'; // tslint:disable-next-line chai.use(require('chai-events')); @@ -130,4 +132,136 @@ describe('Config', () => { expect(conf.remove('version' as any)).to.be.rejected; }); }); + + describe('Config data sources', () => { + after(() => { + // Clean up memoized values + }); + + it('should obtain deviceArch from device-type.json', async () => { + const [slug, arch] = ['raspberrypi3', 'armv7hf']; + stub(fs, 'readFile').resolves( + JSON.stringify({ + slug, + arch, + }), + ); + + const deviceArch = await conf.get('deviceArch'); + expect(deviceArch).to.equal(arch); + expect(fs.readFile).to.be.calledOnce; + expect(fs.readFile).to.be.calledWith( + `${constants.bootMountPoint}/device-type.json`, + 'utf8', + ); + + (fs.readFile as SinonStub).restore(); + }); + + it('should obtain deviceType from device-type.json', async () => { + const [slug, arch] = ['raspberrypi3', 'armv7hf']; + stub(fs, 'readFile').resolves( + JSON.stringify({ + slug, + arch, + }), + ); + + const deviceType = await conf.get('deviceType'); + expect(deviceType).to.equal(slug); + expect(fs.readFile).to.be.calledOnce; + expect(fs.readFile).to.be.calledWith( + `${constants.bootMountPoint}/device-type.json`, + 'utf8', + ); + + (fs.readFile as SinonStub).restore(); + }); + + it('should memoize values from device-type.json', async () => { + const [slug, arch] = ['raspberrypi3', 'armv7hf']; + stub(fs, 'readFile').resolves( + JSON.stringify({ + slug, + arch, + }), + ); + + const deviceArch = await conf.get('deviceArch'); + expect(deviceArch).to.equal(arch); + + // The result should still be memoized from the + // call on the previous test + expect(fs.readFile).to.not.be.called; + + const deviceType = await conf.get('deviceType'); + expect(deviceType).to.equal(slug); + + // The result should still be memoized from the + // call on the previous test + expect(fs.readFile).to.not.be.called; + + (fs.readFile as SinonStub).restore(); + }); + + it('should not memoize errors when reading deviceArch', (done) => { + // Clean up memoized value + fnSchema.deviceArch.clear(); + + // File not found + stub(fs, 'readFile').throws('File not found'); + + expect(conf.get('deviceArch')).to.eventually.equal('unknown'); + expect(fs.readFile).to.be.calledOnce; + (fs.readFile as SinonStub).restore(); + + // Next call should not throw + const [slug, arch] = ['raspberrypi3', 'armv7hf']; + stub(fs, 'readFile').resolves( + JSON.stringify({ + slug, + arch, + }), + ); + + // We need to let rejection be discovered + // https://github.com/medikoo/memoizee/issues/93 + setTimeout(() => { + expect(conf.get('deviceArch')).to.eventually.equal(arch); + expect(fs.readFile).to.be.calledOnce; + (fs.readFile as SinonStub).restore(); + done(); + }); + }); + + it('should not memoize errors when reading deviceType', (done) => { + // Clean up memoized value + fnSchema.deviceType.clear(); + + // File not found + stub(fs, 'readFile').throws('File not found'); + + expect(conf.get('deviceType')).to.eventually.equal('unknown'); + expect(fs.readFile).to.be.calledOnce; + (fs.readFile as SinonStub).restore(); + + // Next call should not throw + const [slug, arch] = ['raspberrypi3', 'armv7hf']; + stub(fs, 'readFile').resolves( + JSON.stringify({ + slug, + arch, + }), + ); + + // We need to let rejection be discovered + // https://github.com/medikoo/memoizee/issues/93 + setTimeout(() => { + expect(conf.get('deviceType')).to.eventually.equal(slug); + expect(fs.readFile).to.be.calledOnce; + (fs.readFile as SinonStub).restore(); + done(); + }); + }); + }); });