Merge pull request #1490 from balena-io/1472-device-type

Change source of deviceType to device-type.json
This commit is contained in:
bulldozer-balena[bot] 2020-10-27 13:26:56 +00:00 committed by GitHub
commit 22402dbb93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 194 additions and 24 deletions

View File

@ -1,5 +1,6 @@
import * as Bluebird from 'bluebird'; import * as Bluebird from 'bluebird';
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as memoizee from 'memoizee';
import { fs } from 'mz'; import { fs } from 'mz';
import { URL } from 'url'; import { URL } from 'url';
@ -38,22 +39,44 @@ export const fnSchema = {
macAddress: () => { macAddress: () => {
return macAddress.getAll(constants.macAddressPath); return macAddress.getAll(constants.macAddressPath);
}, },
deviceArch: async () => { deviceArch: memoizee(
try { async () => {
// FIXME: We should be mounting the following file into the supervisor from the try {
// start-resin-supervisor script, changed in meta-resin - but until then, hardcode it // FIXME: We should be mounting the following file into the supervisor from the
const data = await fs.readFile( // start-resin-supervisor script, changed in meta-resin - but until then, hardcode it
`${constants.rootMountPoint}/resin-boot/device-type.json`, const data = await fs.readFile(
'utf8', `${constants.bootMountPoint}/device-type.json`,
); 'utf8',
const deviceInfo = JSON.parse(data); );
const deviceInfo = JSON.parse(data);
return deviceInfo.arch; return deviceInfo.arch;
} catch (e) { } catch (e) {
log.error(`Unable to get architecture: ${e}`); log.error(`Unable to get architecture: ${e}`);
return 'unknown'; 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: () => { provisioningOptions: () => {
return config return config
.getMany([ .getMany([

View File

@ -87,11 +87,29 @@ export async function get<T extends SchemaTypeKey>(
// 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
const promiseValue = FnSchema.fnSchema[fnKey](); const promiseValue = FnSchema.fnSchema[fnKey]();
return promiseValue.then((value: unknown) => { return promiseValue
const decoded = schemaTypes[key].type.decode(value); .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<any>).decode(undefined);
return (
checkValueDecode(maybeDecoded, key, undefined) && maybeDecoded.right
);
}
return defaultValue as SchemaReturn<T>;
});
} else { } else {
throw new Error(`Unknown config value ${key}`); throw new Error(`Unknown config value ${key}`);
} }

View File

@ -34,11 +34,6 @@ export const schema = {
mutable: true, mutable: true,
removeIfNull: false, removeIfNull: false,
}, },
deviceType: {
source: 'config.json',
mutable: false,
removeIfNull: false,
},
deviceId: { deviceId: {
source: 'config.json', source: 'config.json',
mutable: true, mutable: true,

View File

@ -1,5 +1,6 @@
import * as _ from 'lodash'; import * as _ from 'lodash';
import { fs } from 'mz'; import { fs } from 'mz';
import { SinonStub, stub } from 'sinon';
import chai = require('./lib/chai-config'); import chai = require('./lib/chai-config');
import prepare = require('./lib/prepare'); import prepare = require('./lib/prepare');
@ -7,6 +8,7 @@ import * as conf from '../src/config';
import constants = require('../src/lib/constants'); import constants = require('../src/lib/constants');
import { SchemaTypeKey } from '../src/config/schema-type'; import { SchemaTypeKey } from '../src/config/schema-type';
import { fnSchema } from '../src/config/functions';
// tslint:disable-next-line // tslint:disable-next-line
chai.use(require('chai-events')); chai.use(require('chai-events'));
@ -130,4 +132,136 @@ describe('Config', () => {
expect(conf.remove('version' as any)).to.be.rejected; 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();
});
});
});
}); });