diff --git a/src/features/vars-schema/schema.ts b/src/features/vars-schema/schema.ts
index 421f3b6537..fac58eb4c0 100644
--- a/src/features/vars-schema/schema.ts
+++ b/src/features/vars-schema/schema.ts
@@ -1,6 +1,6 @@
import type { RequestHandler } from 'express';
import type { JSONSchema6 } from 'json-schema';
-
+import { sbvrUtils } from '@balena/pinejs';
import {
BLOCKED_NAMES,
DEVICE_TYPE_SPECIFIC_CONFIG_VAR_PROPERTIES,
@@ -13,19 +13,54 @@ import {
ALLOWED_NAMESPACES,
} from './env-vars';
+const { api } = sbvrUtils;
+
// Return config variable constants for use by external components.
// A query string parameter of 'deviceType' is accepted, which should
// be a device type slug.
-export const schema: RequestHandler = (req, res) => {
+export const schema: RequestHandler = async (req, res) => {
+ const deviceTypeSlug = await (async () => {
+ if (typeof req.query.deviceType !== 'string') {
+ return;
+ }
+
+ const resinApi = api.resin.clone({ passthrough: { req } });
+ // Ensure that the user has access to the provided device type.
+ const [dt] = (await resinApi.get({
+ resource: 'device_type',
+ options: {
+ $top: 1,
+ $select: 'slug',
+ $filter: {
+ device_type_alias: {
+ $any: {
+ $alias: 'dta',
+ $expr: {
+ dta: {
+ is_referenced_by__alias: req.query.deviceType,
+ },
+ },
+ },
+ },
+ },
+ },
+ })) as Array<{ slug: string }>;
+
+ // We do not throw when the DT is not found for backwards compatibility reasons.
+ return dt?.slug;
+ })();
+
const configVarSchema: JSONSchema6 = {
type: 'object',
$schema: 'http://json-schema.org/draft-06/schema#',
properties: Object.assign(
{},
SUPERVISOR_CONFIG_VAR_PROPERTIES,
- ...DEVICE_TYPE_SPECIFIC_CONFIG_VAR_PROPERTIES.filter((config) =>
- config.capableDeviceTypes.includes(req.query.deviceType as string),
- ).map((config) => config.properties),
+ ...(deviceTypeSlug != null
+ ? DEVICE_TYPE_SPECIFIC_CONFIG_VAR_PROPERTIES.filter((config) =>
+ config.capableDeviceTypes.includes(deviceTypeSlug),
+ ).map((config) => config.properties)
+ : []),
),
};
diff --git a/test/01_basic.ts b/test/01_basic.ts
index 789a75f67c..bff12e9d99 100644
--- a/test/01_basic.ts
+++ b/test/01_basic.ts
@@ -109,6 +109,14 @@ describe('Basic', () => {
checkBaseVarsResult(vars);
});
+ it(`should return the base vars when device type is not found`, async () => {
+ const { body: vars } = await supertest()
+ .get(`/config/vars?deviceType=wrong-device-type`)
+ .expect(200);
+
+ checkBaseVarsResult(vars);
+ });
+
[
{ deviceType: 'beaglebone-black' },
{
diff --git a/test/02_device-types.ts b/test/02_device-types.ts
index 5c8105768c..de2940878a 100644
--- a/test/02_device-types.ts
+++ b/test/02_device-types.ts
@@ -90,7 +90,7 @@ describe('device type resource', () => {
expect(deviceType).to.have.property('name').that.is.a('string');
});
- expect(res.body.d).to.have.property('length', 14);
+ expect(res.body.d).to.have.property('length', 16);
});
});
@@ -217,7 +217,7 @@ describe('device type endpoints', () => {
it('should return a proper result', async () => {
const res = await supertest().get('/device-types/v1').expect(200);
expect(res.body).to.be.an('array');
- expect(res.body).to.have.property('length', 15);
+ expect(res.body).to.have.property('length', 17);
const rpi3config = _.find(res.body, { slug: 'raspberrypi3' });
expect(rpi3config).to.be.an('object');
expect(rpi3config).to.have.property('buildId', '2.19.0+rev1.prod');
diff --git a/test/09_contracts.ts b/test/09_contracts.ts
index 63491f8cf9..c5cd8bc5d8 100644
--- a/test/09_contracts.ts
+++ b/test/09_contracts.ts
@@ -96,7 +96,7 @@ describe('contracts', () => {
]);
const contracts = await getContracts('hw.device-type');
- expect(contracts).to.have.length(15);
+ expect(contracts).to.have.length(17);
expect(
contracts.find((contract) => contract.slug === 'other-contract-dt'),
).to.not.be.undefined;
@@ -163,7 +163,7 @@ describe('contracts', () => {
(dbDeviceType) => dbDeviceType.slug === 'fincm3',
);
- expect(contracts).to.have.length(15);
+ expect(contracts).to.have.length(17);
expect(newDt).to.not.be.undefined;
expect(finDt).to.have.property('name', 'Fin');
});
@@ -232,7 +232,7 @@ describe('contracts', () => {
(dbDeviceType) => dbDeviceType.slug === 'raspberry-pi',
);
- expect(dbDeviceTypes).to.have.length(15);
+ expect(dbDeviceTypes).to.have.length(17);
expect(newDt).to.not.be.undefined;
expect(finDt).to.have.property('name', 'Fin');
expect(finDt).to.have.deep.property(
diff --git a/test/fixtures/contracts/base-contracts/contracts/hw.device-type/jetson-tx2/contract.json b/test/fixtures/contracts/base-contracts/contracts/hw.device-type/jetson-tx2/contract.json
new file mode 100644
index 0000000000..a48d1c24d2
--- /dev/null
+++ b/test/fixtures/contracts/base-contracts/contracts/hw.device-type/jetson-tx2/contract.json
@@ -0,0 +1,37 @@
+{
+ "slug": "jetson-tx2",
+ "version": "1",
+ "type": "hw.device-type",
+ "aliases": [],
+ "name": "Nvidia Jetson TX2",
+ "assets": {
+ "logo": {
+ "url": "./jetson-tx2.svg",
+ "name": "logo"
+ }
+ },
+ "data": {
+ "arch": "aarch64",
+ "hdmi": true,
+ "led": false,
+ "connectivity": {
+ "bluetooth": true,
+ "wifi": true
+ },
+ "storage": {
+ "internal": true
+ },
+ "media": {
+ "defaultBoot": "internal",
+ "altBoot": ["sdcard"]
+ },
+ "is_private": false
+ },
+ "partials": {
+ "bootDeviceExternal": [
+ "Connect power to the {{name}} and press and hold the POWER push button for 1 second"
+ ],
+ "flashIndicator": ["all LEDs are off"],
+ "bootDevice": ["Remove and re-connect power to the {{name}}"]
+ }
+}
diff --git a/test/fixtures/contracts/base-contracts/contracts/hw.device-type/jetson-tx2/jetson-tx2.svg b/test/fixtures/contracts/base-contracts/contracts/hw.device-type/jetson-tx2/jetson-tx2.svg
new file mode 100644
index 0000000000..8b77d60d47
--- /dev/null
+++ b/test/fixtures/contracts/base-contracts/contracts/hw.device-type/jetson-tx2/jetson-tx2.svg
@@ -0,0 +1,27 @@
+
+
+
diff --git a/test/fixtures/contracts/base-contracts/contracts/hw.device-type/up-board/contract.json b/test/fixtures/contracts/base-contracts/contracts/hw.device-type/up-board/contract.json
new file mode 100644
index 0000000000..b56e6e9302
--- /dev/null
+++ b/test/fixtures/contracts/base-contracts/contracts/hw.device-type/up-board/contract.json
@@ -0,0 +1,40 @@
+{
+ "slug": "up-board",
+ "version": "1",
+ "type": "hw.device-type",
+ "aliases": [],
+ "name": "UP Board",
+ "assets": {
+ "logo": {
+ "url": "./up-board.svg",
+ "name": "logo"
+ }
+ },
+ "data": {
+ "arch": "amd64",
+ "family": "family-upboard",
+ "hdmi": true,
+ "led": true,
+ "connectivity": {
+ "bluetooth": false,
+ "wifi": false
+ },
+ "storage": {
+ "internal": true
+ },
+ "media": {
+ "defaultBoot": "internal",
+ "altBoot": ["usb_mass_storage"]
+ },
+ "is_private": false
+ },
+ "partials": {
+ "bootDeviceExternal": [
+ "Power on the {{name}} with a keyboard connected.",
+ "Press the F7 key while BIOS is loading to enter the boot menu.",
+ "Select the \"UEFI:\" option from the boot menu."
+ ],
+ "flashIndicator": ["all LEDs are off"],
+ "bootDevice": ["Power up the {{name}}"]
+ }
+}
diff --git a/test/fixtures/contracts/base-contracts/contracts/hw.device-type/up-board/up-board.svg b/test/fixtures/contracts/base-contracts/contracts/hw.device-type/up-board/up-board.svg
new file mode 100644
index 0000000000..86d814c549
--- /dev/null
+++ b/test/fixtures/contracts/base-contracts/contracts/hw.device-type/up-board/up-board.svg
@@ -0,0 +1,12 @@
+
+
+
+