diff --git a/src/balena-init.sql b/src/balena-init.sql index 89a40d507..27a2088a6 100644 --- a/src/balena-init.sql +++ b/src/balena-init.sql @@ -63,15 +63,20 @@ CREATE INDEX IF NOT EXISTS "device_device_type_idx" ON "device" ("is of-device type"); CREATE INDEX IF NOT EXISTS "device_is_running_release_idx" ON "device" ("is running-release"); --- Also optimizes should be running succesful release rule +-- Also optimizes should be running successful release rule CREATE INDEX IF NOT EXISTS "device_should_be_running_release_application_idx" ON "device" ("should be running-release", "belongs to-application"); +-- Also optimizes is pinned on successful release rule +-- TODO: remove as soon as the is_pinned_on__release migration is completed +CREATE INDEX IF NOT EXISTS "device_is_pinned_on_release_idx" +ON "device" ("is pinned on-release"); CREATE INDEX IF NOT EXISTS "device_should_be_operated_by_release_device_type_idx" ON "device" ("should be operated by-release", "is of-device type"); -- Also optimizes the supervisor cpu arch should match device cpu arch rule CREATE INDEX IF NOT EXISTS "device_should_be_managed_by__release_device_type_idx" ON "device" ("should be managed by-release", "is of-device type"); + -- "device config variable"."device" is the first part of an automated unique index -- "device environment variable"."device" is the first part of an automated unique index diff --git a/src/balena-model.ts b/src/balena-model.ts index 5f81b31de..9408f1e23 100644 --- a/src/balena-model.ts +++ b/src/balena-model.ts @@ -398,6 +398,7 @@ export interface Device { | [ServiceInstance?] | null; should_be_running__release: { __id: Release['id'] } | [Release?] | null; + is_pinned_on__release: { __id: Release['id'] } | [Release?] | null; should_be_operated_by__release: { __id: Release['id'] } | [Release?] | null; should_be_managed_by__release: { __id: Release['id'] } | [Release?] | null; is_web_accessible: boolean | null; @@ -519,6 +520,7 @@ export interface Release { should_be_running_on__application?: Application[]; should_be_running_on__device?: Device[]; is_running_on__device?: Device[]; + is_pinned_on__device?: Device[]; should_operate__device?: Device[]; should_manage__device?: Device[]; provides__device__installs__image?: ImageInstall[]; diff --git a/src/balena.sbvr b/src/balena.sbvr index 9508c5271..40336d986 100644 --- a/src/balena.sbvr +++ b/src/balena.sbvr @@ -579,6 +579,9 @@ Fact type: device is managed by service instance Fact type: device should be running release Synonymous Form: release should be running on device Necessity: each device should be running at most one release +Fact type: device is pinned on release + Synonymous Form: release is pinned to device + Necessity: each device is pinned on at most one release Fact type: device should be operated by release Synonymous Form: release should operate device Necessity: each device should be operated by at most one release @@ -860,6 +863,7 @@ Rule: It is necessary that each image that has a status that is equal to "succes Rule: It is necessary that each application that owns a release1 that has a status that is equal to "success" and has a commit1, owns at most one release2 that has a status that is equal to "success" and has a commit2 that is equal to the commit1. Rule: It is necessary that each application that owns a release1 that has a revision, owns at most one release2 that has a semver major that is of the release1 and has a semver minor that is of the release1 and has a semver patch that is of the release1 and has a semver prerelease that is of the release1 and has a variant that is of the release1 and has a revision that is of the release1. Rule: It is necessary that each release that should be running on a device, has a status that is equal to "success" and belongs to an application1 that the device belongs to. +Rule: It is necessary that each release that is pinned on a device, has a status that is equal to "success" and belongs to an application1 that the device belongs to. Rule: It is necessary that each release that should be running on an application, has a status that is equal to "success" and belongs to the application. Rule: It is necessary that each application that owns a release that contains at least 2 images, has an application type that supports multicontainer. Rule: It is necessary that each release that should operate a device, has a status that is equal to "success". diff --git a/src/features/devices/hooks/defaults-validation.ts b/src/features/devices/hooks/defaults-validation.ts index 4ac90b708..c79f7f505 100644 --- a/src/features/devices/hooks/defaults-validation.ts +++ b/src/features/devices/hooks/defaults-validation.ts @@ -29,11 +29,20 @@ hooks.addPureHook('POST', 'resin', 'device', { 'Device UUID must be a 32 or 62 character long lower case hex string.', ); } + + // TODO[device management next step]: Drop this after re-migrating all data on step 2: + if (request.values.should_be_running__release !== undefined) { + // Add an async boundary so that value updates, + // and doesn't remove the properties that we add. + await null; + request.values.is_pinned_on__release = + request.values.should_be_running__release; + } }, }); hooks.addPureHook('PATCH', 'resin', 'device', { - POSTPARSE: ({ request }) => { + POSTPARSE: async ({ request }) => { // Check for extra whitespace characters if ( request.values.device_name != null && @@ -73,5 +82,14 @@ hooks.addPureHook('PATCH', 'resin', 'device', { if (request.values.is_online != null) { request.values.last_connectivity_event = new Date(); } + + // TODO[device management next step]: Drop this after re-migrating all data on step 2: + if (request.values.should_be_running__release !== undefined) { + // Add an async boundary so that value updates, + // and doesn't remove the properties that we add. + await null; + request.values.is_pinned_on__release = + request.values.should_be_running__release; + } }, }); diff --git a/src/migrations/00087-add-device-pinned-on-release.sql b/src/migrations/00087-add-device-pinned-on-release.sql new file mode 100644 index 000000000..8b5f31ba6 --- /dev/null +++ b/src/migrations/00087-add-device-pinned-on-release.sql @@ -0,0 +1,25 @@ +-- Add the column if it does not exist +ALTER TABLE "device" +ADD COLUMN IF NOT EXISTS "is pinned on-release" INTEGER NULL; + +-- Add an index for optimization if it does not exist +CREATE INDEX IF NOT EXISTS "device_is_pinned_on_release_idx" +ON "device" ("is pinned on-release"); + +-- Check and add the foreign key constraint conditionally +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM information_schema.table_constraints AS tc + JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name + WHERE tc.constraint_type = 'FOREIGN KEY' + AND tc.table_schema = CURRENT_SCHEMA() + AND tc.table_name = 'device' + AND kcu.column_name = 'is pinned on-release' + ) THEN + ALTER TABLE "device" + ADD CONSTRAINT "device_is_pinned_on_release_fkey" FOREIGN KEY ("is pinned on-release") REFERENCES "release" ("id"); + END IF; +END; +$$;