diff --git a/src/features/service-install/hooks/backfill-device-service-environment-variable.ts b/src/features/service-install/hooks/backfill-device-service-environment-variable.ts deleted file mode 100644 index ba5e9cf5d..000000000 --- a/src/features/service-install/hooks/backfill-device-service-environment-variable.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { hooks, errors, type sbvrUtils } from '@balena/pinejs'; - -async function backfillDeviceAndService({ - request, - api, -}: sbvrUtils.HookArgs<'resin'>) { - const { service_install: siId } = request.values; - - if (siId == null) { - return; - } - - const si = await api.get({ - resource: 'service_install', - id: siId, - options: { - $select: ['device', 'service'], - }, - }); - - if (si == null) { - throw new errors.UnauthorizedError(); - } - - request.values.device = si.device.__id; - request.values.service = si.service.__id; -} - -hooks.addPureHook('POST', 'resin', 'device_service_environment_variable', { - POSTPARSE: backfillDeviceAndService, -}); - -hooks.addPureHook('PATCH', 'resin', 'device_service_environment_variable', { - POSTPARSE: backfillDeviceAndService, -}); diff --git a/src/features/service-install/hooks/index.ts b/src/features/service-install/hooks/index.ts index d089cbd91..926929752 100644 --- a/src/features/service-install/hooks/index.ts +++ b/src/features/service-install/hooks/index.ts @@ -1 +1 @@ -import './backfill-device-service-environment-variable.js'; +import './nullify-si-on-device-service-environment-variable.js'; diff --git a/src/features/service-install/hooks/nullify-si-on-device-service-environment-variable.ts b/src/features/service-install/hooks/nullify-si-on-device-service-environment-variable.ts new file mode 100644 index 000000000..e4fa4a07f --- /dev/null +++ b/src/features/service-install/hooks/nullify-si-on-device-service-environment-variable.ts @@ -0,0 +1,13 @@ +import { hooks, type sbvrUtils } from '@balena/pinejs'; + +function backfillDeviceAndService({ request }: sbvrUtils.HookArgs<'resin'>) { + delete request.values.service_install; +} + +hooks.addPureHook('POST', 'resin', 'device_service_environment_variable', { + POSTPARSE: backfillDeviceAndService, +}); + +hooks.addPureHook('PATCH', 'resin', 'device_service_environment_variable', { + POSTPARSE: backfillDeviceAndService, +}); diff --git a/src/translations/v7/hooks.ts b/src/translations/v7/hooks.ts index b3856ca9f..c3078ec7f 100644 --- a/src/translations/v7/hooks.ts +++ b/src/translations/v7/hooks.ts @@ -1,6 +1,52 @@ import { addDeleteHookForDependents } from '../../infra/cascade-delete/index.js'; +import { hooks, sbvrUtils, errors } from '@balena/pinejs'; + +const addReadOnlyHook = ( + methods: Array[0]>, + resource: string, + hook: sbvrUtils.Hooks<'v7'>, +) => { + methods.map((method) => { + hooks.addHook(method, 'v7', resource, { + ...hook, + sideEffects: false, + readOnlyTx: true, + }); + }); +}; // Service install resource should only be used for <= v7 translations addDeleteHookForDependents('v7', 'service_install', { device_service_environment_variable: 'service_install', }); + +addReadOnlyHook( + ['POST', 'PATCH', 'PUT'], + 'device_service_environment_variable', + { + POSTPARSE: async ({ request, api }) => { + const { service_install: siId } = request.values; + + if (siId == null) { + return; + } + + const si = await sbvrUtils.api.resin.get({ + resource: 'service_install', + passthrough: api.passthrough, + id: siId, + options: { + $select: ['device', 'service'], + }, + }); + + if (si == null) { + throw new errors.UnauthorizedError(); + } + + request.values.device = si.device.__id; + request.values.service = si.service.__id; + delete request.values.service_install; + }, + }, +); diff --git a/test/25_service-installs.ts b/test/25_service-installs.ts index d5300dd9f..3c9a630d3 100644 --- a/test/25_service-installs.ts +++ b/test/25_service-installs.ts @@ -317,7 +317,9 @@ export default () => { .post({ resource: 'device_service_environment_variable', body: { - service_install: serviceInstall.id, + ...(versions.lte(version, 'v7') + ? { service_install: serviceInstall.id } + : { device: ctx.device.id, service: ctx.app2Service1.id }), name: 'test', value: '123', }, @@ -341,68 +343,70 @@ export default () => { ); }); - it('should be able to update device_service_environment_variable service_install', async () => { - const { body: serviceInstallService1 } = await pineUser - .get({ - resource: 'service_install', - id: { - device: ctx.device.id, - installs__service: ctx.app2Service1.id, - }, - }) - .expect(200); - assertExists(serviceInstallService1); + if (versions.lte(version, 'v7')) { + it('should be able to update device_service_environment_variable service_install', async () => { + const { body: serviceInstallService1 } = await pineUser + .get({ + resource: 'service_install', + id: { + device: ctx.device.id, + installs__service: ctx.app2Service1.id, + }, + }) + .expect(200); + assertExists(serviceInstallService1); - const { body: serviceInstallService2 } = await pineUser - .get({ - resource: 'service_install', - id: { - device: ctx.device.id, - installs__service: ctx.app2Service2.id, - }, - }) - .expect(200); - assertExists(serviceInstallService2); + const { body: serviceInstallService2 } = await pineUser + .get({ + resource: 'service_install', + id: { + device: ctx.device.id, + installs__service: ctx.app2Service2.id, + }, + }) + .expect(200); + assertExists(serviceInstallService2); - const { - body: [deviceServiceEnvVar], - } = await pineUser - .get({ - resource: 'device_service_environment_variable', - options: { - $filter: { - service_install: serviceInstallService1.id, + const { + body: [deviceServiceEnvVar], + } = await pineUser + .get({ + resource: 'device_service_environment_variable', + options: { + $filter: { + service_install: serviceInstallService1.id, + }, }, - }, - }) - .expect(200); - assertExists(deviceServiceEnvVar); + }) + .expect(200); + assertExists(deviceServiceEnvVar); + + await pineUser + .patch({ + resource: 'device_service_environment_variable', + id: deviceServiceEnvVar.id, + body: { + service_install: serviceInstallService2.id, + }, + }) + .expect(200); - await pineUser - .patch({ - resource: 'device_service_environment_variable', - id: deviceServiceEnvVar.id, + const { body: { - service_install: serviceInstallService2.id, + d: [dbDeviceServiceEnvVar], }, - }) - .expect(200); - - const { - body: { - d: [dbDeviceServiceEnvVar], - }, - } = await supertest(ctx.admin) - .get( - `/resin/device_service_environment_variable(${deviceServiceEnvVar.id})?$select=device,service`, - ) - .expect(200); + } = await supertest(ctx.admin) + .get( + `/resin/device_service_environment_variable(${deviceServiceEnvVar.id})?$select=device,service`, + ) + .expect(200); - expect(dbDeviceServiceEnvVar.device.__id).to.equal(ctx.device.id); - expect(dbDeviceServiceEnvVar.service.__id).to.equal( - ctx.app2Service2.id, - ); - }); + expect(dbDeviceServiceEnvVar.device.__id).to.equal(ctx.device.id); + expect(dbDeviceServiceEnvVar.service.__id).to.equal( + ctx.app2Service2.id, + ); + }); + } }); }); }); diff --git a/test/test-lib/fixtures.ts b/test/test-lib/fixtures.ts index 0ed0ca3ae..e5843dc5e 100644 --- a/test/test-lib/fixtures.ts +++ b/test/test-lib/fixtures.ts @@ -508,8 +508,8 @@ const loaders: types.Dictionary = { logErrorAndThrow(`Could not find service: ${jsonData.service}`); } - const si = await expectToEventually(async () => { - const $si = await api.resin.get({ + await expectToEventually(async () => { + const si = await api.resin.get({ resource: 'service_install', passthrough: { req: permissions.rootRead }, id: { @@ -517,14 +517,14 @@ const loaders: types.Dictionary = { installs__service: service.id, }, }); - assertExists($si); - return $si; + assertExists(si); }); return await createResource({ resource: 'device_service_environment_variable', body: { - service_install: si.id, + device: device.id, + service: service.id, name: jsonData.name, value: jsonData.value, },