Skip to content

Commit

Permalink
Drop the /device-types/v1/:deviceType/images route in favor of hostApps
Browse files Browse the repository at this point in the history
  • Loading branch information
thgreasi committed Apr 18, 2024
1 parent 26f392d commit a315f9e
Show file tree
Hide file tree
Showing 6 changed files with 27 additions and 209 deletions.
4 changes: 2 additions & 2 deletions src/features/device-config/download.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import type { Application, DeviceType } from '../../balena-model.js';

import { generateConfig } from './device-config.js';
import { findBySlug } from '../device-types/device-types.js';
import { findDeviceTypeJsonBySlug } from '../device-types/device-types.js';
import { checkInt } from '../../lib/utils.js';

const { UnauthorizedError, NotFoundError } = errors;
Expand Down Expand Up @@ -63,7 +63,7 @@ export const downloadImageConfig: RequestHandler = async (req, res) => {
const resinApi = api.resin.clone({ passthrough: { req } });

const app = await getApp(appId, req);
const deviceTypeJson = await findBySlug(
const deviceTypeJson = await findDeviceTypeJsonBySlug(
resinApi,
deviceTypeSlug || app.is_for__device_type[0].slug,
);
Expand Down
97 changes: 21 additions & 76 deletions src/features/device-types/device-types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import _ from 'lodash';

import type { DeviceTypeJson } from './device-type-json.js';
import type { sbvrUtils } from '@balena/pinejs';
import { errors } from '@balena/pinejs';
const { InternalRequestError } = errors;

import { captureException } from '../../infra/error-handling/index.js';

Expand Down Expand Up @@ -61,6 +58,13 @@ export const getDeviceTypeBySlug = async (
return dt;
};

export const validateSlug = (slug?: string) => {
if (slug == null || !/^[\w-]+$/.test(slug)) {
throw new BadRequestError('Invalid device type');
}
return slug;
};

const findDeviceTypeInfoBySlug = async (
resinApi: sbvrUtils.PinejsClient,
slug: string,
Expand All @@ -74,28 +78,19 @@ const findDeviceTypeInfoBySlug = async (
return deviceTypeInfo;
};

export const validateSlug = (slug?: string) => {
if (slug == null || !/^[\w-]+$/.test(slug)) {
throw new BadRequestError('Invalid device type');
}
return slug;
};

/** @deprecated */
const getAllDeviceTypes = async () => {
const dtInfo = await getDeviceTypes();
return _.uniqBy(
Object.values(dtInfo).map((dtEntry) => dtEntry.latest),
(dt) => dt.slug,
);
};
/** @deprecated Use the getDeviceTypeBySlug unless you need the device-type.json contents. */
export const findDeviceTypeJsonBySlug = async (
resinApi: sbvrUtils.PinejsClient,
slug: string,
): Promise<DeviceTypeJson> =>
(await findDeviceTypeInfoBySlug(resinApi, slug)).latest;

/** @deprecated */
export const getAccessibleDeviceTypes = async (
resinApi: sbvrUtils.PinejsClient,
): Promise<DeviceTypeJson[]> => {
const [deviceTypes, accessibleDeviceTypes] = await Promise.all([
getAllDeviceTypes(),
const [deviceTypeInfosBySlug, accessibleDeviceTypes] = await Promise.all([
getDeviceTypes(),
resinApi.get({
resource: 'device_type',
options: {
Expand All @@ -104,30 +99,22 @@ export const getAccessibleDeviceTypes = async (
}) as Promise<Array<{ slug: string }>>,
]);

const accessSet = new Set(accessibleDeviceTypes.map((dt) => dt.slug));
return deviceTypes.filter((deviceType) => {
return accessSet.has(deviceType.slug);
});
return accessibleDeviceTypes
.map((dt) => deviceTypeInfosBySlug[dt.slug]?.latest)
.filter((dtJson) => dtJson != null);
};

/** @deprecated Use the getDeviceTypeBySlug unless you need the device-type.json contents. */
export const findBySlug = async (
resinApi: sbvrUtils.PinejsClient,
slug: string,
): Promise<DeviceTypeJson> =>
(await findDeviceTypeInfoBySlug(resinApi, slug)).latest;

export const getImageSize = async (
resinApi: sbvrUtils.PinejsClient,
slug: string,
buildId: string,
): Promise<number> => {
const deviceTypeInfo = await findDeviceTypeInfoBySlug(resinApi, slug);
const deviceType = deviceTypeInfo.latest;
const normalizedSlug = deviceType.slug;
const deviceTypeJson = deviceTypeInfo.latest;
const normalizedSlug = deviceTypeJson.slug;

if (buildId === 'latest') {
buildId = deviceType.buildId;
buildId = deviceTypeJson.buildId;
}

if (!deviceTypeInfo.versions.includes(buildId)) {
Expand All @@ -150,45 +137,3 @@ export const getImageSize = async (
throw err;
}
};

export interface ImageVersions {
versions: string[];
latest: string;
}

export const getImageVersions = async (
resinApi: sbvrUtils.PinejsClient,
slug: string,
): Promise<ImageVersions> => {
const deviceTypeInfo = await findDeviceTypeInfoBySlug(resinApi, slug);
const deviceType = deviceTypeInfo.latest;
const normalizedSlug = deviceType.slug;

const versionInfo = await Promise.all(
deviceTypeInfo.versions.map(async (buildId) => {
try {
return {
buildId,
hasDeviceTypeJson: await getDeviceTypeJson(normalizedSlug, buildId),
};
} catch {
return;
}
}),
);
const filteredInfo = versionInfo.filter(
(buildInfo): buildInfo is NonNullable<typeof buildInfo> =>
buildInfo != null && !!buildInfo.hasDeviceTypeJson,
);
if (_.isEmpty(filteredInfo) && !_.isEmpty(deviceTypeInfo.versions)) {
throw new InternalRequestError(
`Could not retrieve any image version for device type ${slug}`,
);
}

const buildIds = filteredInfo.map(({ buildId }) => buildId);
return {
versions: buildIds,
latest: buildIds[0],
};
};
12 changes: 1 addition & 11 deletions src/features/device-types/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import type { Application } from 'express';
import { middleware } from '../../infra/auth/index.js';
import {
downloadImageSize,
getDeviceType,
getDeviceTypes,
listAvailableImageVersions,
} from './routes.js';
import { downloadImageSize, getDeviceType, getDeviceTypes } from './routes.js';

export const setup = (app: Application) => {
app.get(
Expand All @@ -18,11 +13,6 @@ export const setup = (app: Application) => {
middleware.resolveCredentialsAndUser,
getDeviceType,
);
app.get(
'/device-types/v1/:deviceType/images',
middleware.resolveCredentialsAndUser,
listAvailableImageVersions,
);
app.get(
'/device-types/v1/:deviceType/images/:version/download-size',
middleware.resolveCredentialsAndUser,
Expand Down
17 changes: 1 addition & 16 deletions src/features/device-types/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const getDeviceType: RequestHandler = async (req, res) => {
try {
const resinApi = api.resin.clone({ passthrough: { req } });
const slug = deviceTypesLib.validateSlug(req.params.deviceType);
const data = await deviceTypesLib.findBySlug(resinApi, slug);
const data = await deviceTypesLib.findDeviceTypeJsonBySlug(resinApi, slug);
res.json(data);
} catch (err) {
captureException(err, 'Error getting device type', {
Expand All @@ -44,21 +44,6 @@ export const getDeviceType: RequestHandler = async (req, res) => {
}
};

export const listAvailableImageVersions: RequestHandler = async (req, res) => {
try {
const resinApi = api.resin.clone({ passthrough: { req } });
const slug = deviceTypesLib.validateSlug(req.params.deviceType);
const data = await deviceTypesLib.getImageVersions(resinApi, slug);
res.json(data);
} catch (err) {
captureException(err, 'Error getting image versions', { req });
if (handleHttpErrors(req, res, err)) {
return;
}
res.status(500).send(translateError(err));
}
};

const DOWNLOAD_TIMEOUT = 30000; // we must respond within this time

export const downloadImageSize: RequestHandler = async (req, res) => {
Expand Down
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ import {
} from './infra/rate-limiting/index.js';
import {
getAccessibleDeviceTypes,
findBySlug,
findDeviceTypeJsonBySlug,
getDeviceTypeBySlug,
} from './features/device-types/device-types.js';
import { proxy as supervisorProxy } from './features/device-proxy/device-proxy.js';
Expand Down Expand Up @@ -242,7 +242,7 @@ export const release = {
};
export const deviceTypes = {
getAccessibleDeviceTypes,
findBySlug,
findBySlug: findDeviceTypeJsonBySlug,
getDeviceTypeBySlug,
};
export * as contracts from './exports/contracts.js';
Expand Down
102 changes: 0 additions & 102 deletions test/02_device-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,107 +277,5 @@ export default () => {
expect(rpiConfig).to.not.have.property('logoUrl');
});
});

describe('/device-types/v1/:deviceType/images', () => {
it('should return a proper result', async () => {
const res = await supertest()
.get('/device-types/v1/raspberrypi3-64/images')
.expect(200);
expect(res.body).to.deep.equal({
versions: [
'2.0.2+rev2',
'2.0.2+rev2.dev',
'2.0.2+rev1',
'2.0.2+rev1.dev',
],
latest: '2.0.2+rev2',
});
});

it('should return a proper result for an alias', async () => {
const res = await supertest()
.get('/device-types/v1/raspberrypi364/images')
.expect(200);
expect(res.body).to.deep.equal({
versions: [
'2.0.2+rev2',
'2.0.2+rev2.dev',
'2.0.2+rev1',
'2.0.2+rev1.dev',
],
latest: '2.0.2+rev2',
});
});

it('should omit releases that have an IGNORE file', async () => {
const res = await supertest()
.get('/device-types/v1/dt-with-ignored-release/images')
.expect(200);
expect(res.body).to.deep.equal({
versions: ['2.0.1+rev1.prod', '2.0.0+rev1.prod'],
latest: '2.0.1+rev1.prod',
});
});

it('should omit releases that have an IGNORE file that gives an unauthorized error', async () => {
const res = await supertest()
.get('/device-types/v1/dt-with-403-ignore-file-release/images')
.expect(200);
expect(res.body).to.deep.equal({
versions: ['2.0.1+rev1.prod', '2.0.0+rev1.prod'],
latest: '2.0.1+rev1.prod',
});
});

it('should omit releases that have an empty device-type.json', async () => {
const res = await supertest()
.get('/device-types/v1/dt-with-empty-device-type-json-release/images')
.expect(200);
expect(res.body).to.deep.equal({
versions: ['2.0.1+rev1.prod', '2.0.0+rev1.prod'],
latest: '2.0.1+rev1.prod',
});
});

it('should omit releases that do not have a device-type.json', async () => {
const res = await supertest()
.get('/device-types/v1/dt-with-404-device-type-json-release/images')
.expect(200);
expect(res.body).to.deep.equal({
versions: ['2.0.1+rev1.prod', '2.0.0+rev1.prod'],
latest: '2.0.1+rev1.prod',
});
});

it('should succeed and omit releases that the retrieval of the IGNORE file fails', async () => {
const res = await supertest()
.get('/device-types/v1/dt-with-500-ignore-file-release/images')
.expect(200);
expect(res.body).to.deep.equal({
versions: ['2.0.1+rev1.prod', '2.0.0+rev1.prod'],
latest: '2.0.1+rev1.prod',
});
});

it('should succeed and omit releases that the retrieval of device-type.json fails', async () => {
const res = await supertest()
.get('/device-types/v1/dt-with-500-device-type-json-release/images')
.expect(200);
expect(res.body).to.deep.equal({
versions: ['2.0.1+rev1.prod', '2.0.0+rev1.prod'],
latest: '2.0.1+rev1.prod',
});
});

it('should succeed and omit device types whose details fail to be retrieved', async () => {
const res = await supertest()
.get('/device-types/v1/dt-with-500-device-type-json-release/images')
.expect(200);
expect(res.body).to.deep.equal({
versions: ['2.0.1+rev1.prod', '2.0.0+rev1.prod'],
latest: '2.0.1+rev1.prod',
});
});
});
});
};

0 comments on commit a315f9e

Please sign in to comment.