@shipfox/api-runners 6.0.0 → 7.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +26 -0
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/core/entities/runner-instance.d.ts +1 -0
- package/dist/core/entities/runner-instance.d.ts.map +1 -1
- package/dist/core/entities/runner-instance.js.map +1 -1
- package/dist/core/errors.d.ts +6 -6
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +8 -8
- package/dist/core/errors.js.map +1 -1
- package/dist/core/index.d.ts +2 -4
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/runner-assignments.d.ts +6 -0
- package/dist/core/runner-assignments.d.ts.map +1 -0
- package/dist/core/runner-assignments.js +6 -0
- package/dist/core/runner-assignments.js.map +1 -0
- package/dist/core/runner-instances.d.ts +2 -9
- package/dist/core/runner-instances.d.ts.map +1 -1
- package/dist/core/runner-instances.js +3 -6
- package/dist/core/runner-instances.js.map +1 -1
- package/dist/db/db.d.ts +34 -218
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/db.js +0 -2
- package/dist/db/db.js.map +1 -1
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +2 -2
- package/dist/db/index.js.map +1 -1
- package/dist/db/runner-assignments.d.ts +7 -0
- package/dist/db/runner-assignments.d.ts.map +1 -0
- package/dist/db/runner-assignments.js +55 -0
- package/dist/db/runner-assignments.js.map +1 -0
- package/dist/db/runner-instances.d.ts +2 -9
- package/dist/db/runner-instances.d.ts.map +1 -1
- package/dist/db/runner-instances.js +2 -16
- package/dist/db/runner-instances.js.map +1 -1
- package/dist/db/schema/runner-instances.d.ts +17 -0
- package/dist/db/schema/runner-instances.d.ts.map +1 -1
- package/dist/db/schema/runner-instances.js +5 -1
- package/dist/db/schema/runner-instances.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/presentation/dto/index.d.ts +0 -1
- package/dist/presentation/dto/index.d.ts.map +1 -1
- package/dist/presentation/dto/index.js +0 -1
- package/dist/presentation/dto/index.js.map +1 -1
- package/dist/presentation/routes/assign-runner-instances.d.ts +2 -0
- package/dist/presentation/routes/assign-runner-instances.d.ts.map +1 -0
- package/dist/presentation/routes/assign-runner-instances.js +40 -0
- package/dist/presentation/routes/assign-runner-instances.js.map +1 -0
- package/dist/presentation/routes/index.d.ts.map +1 -1
- package/dist/presentation/routes/index.js +4 -8
- package/dist/presentation/routes/index.js.map +1 -1
- package/dist/presentation/routes/runner-enrollment.d.ts +1 -0
- package/dist/presentation/routes/runner-enrollment.d.ts.map +1 -1
- package/dist/presentation/routes/runner-enrollment.js +27 -0
- package/dist/presentation/routes/runner-enrollment.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0000_initial.sql +1 -12
- package/drizzle/meta/0000_snapshot.json +6 -97
- package/package.json +8 -29
- package/src/config.ts +1 -1
- package/src/core/entities/runner-instance.ts +1 -0
- package/src/core/errors.ts +8 -8
- package/src/core/index.ts +2 -9
- package/src/core/runner-assignments.ts +9 -0
- package/src/core/runner-instances.ts +4 -13
- package/src/db/db.ts +0 -2
- package/src/db/index.ts +2 -3
- package/src/db/runner-assignments.test.ts +163 -0
- package/src/db/runner-assignments.ts +115 -0
- package/src/db/runner-instances.test.ts +36 -23
- package/src/db/runner-instances.ts +3 -23
- package/src/db/schema/runner-instances.ts +3 -1
- package/src/index.ts +0 -9
- package/src/presentation/dto/index.ts +0 -4
- package/src/presentation/routes/assign-runner-instances.ts +49 -0
- package/src/presentation/routes/index.ts +4 -7
- package/src/presentation/routes/runner-enrollment.test.ts +60 -0
- package/src/presentation/routes/runner-enrollment.ts +22 -0
- package/test/globalSetup.ts +0 -1
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/core/capacity-assignments.d.ts +0 -7
- package/dist/core/capacity-assignments.d.ts.map +0 -1
- package/dist/core/capacity-assignments.js +0 -6
- package/dist/core/capacity-assignments.js.map +0 -1
- package/dist/core/entities/capacity-assignment.d.ts +0 -9
- package/dist/core/entities/capacity-assignment.d.ts.map +0 -1
- package/dist/core/entities/capacity-assignment.js +0 -3
- package/dist/core/entities/capacity-assignment.js.map +0 -1
- package/dist/db/capacity-assignments.d.ts +0 -7
- package/dist/db/capacity-assignments.d.ts.map +0 -1
- package/dist/db/capacity-assignments.js +0 -72
- package/dist/db/capacity-assignments.js.map +0 -1
- package/dist/db/schema/capacity-assignments.d.ts +0 -113
- package/dist/db/schema/capacity-assignments.d.ts.map +0 -1
- package/dist/db/schema/capacity-assignments.js +0 -29
- package/dist/db/schema/capacity-assignments.js.map +0 -1
- package/dist/presentation/dto/mint-registration-tokens.d.ts +0 -7
- package/dist/presentation/dto/mint-registration-tokens.d.ts.map +0 -1
- package/dist/presentation/dto/mint-registration-tokens.js +0 -19
- package/dist/presentation/dto/mint-registration-tokens.js.map +0 -1
- package/dist/presentation/routes/assign-capacity.d.ts +0 -2
- package/dist/presentation/routes/assign-capacity.d.ts.map +0 -1
- package/dist/presentation/routes/assign-capacity.js +0 -44
- package/dist/presentation/routes/assign-capacity.js.map +0 -1
- package/dist/presentation/routes/mint-registration-tokens.d.ts +0 -2
- package/dist/presentation/routes/mint-registration-tokens.d.ts.map +0 -1
- package/dist/presentation/routes/mint-registration-tokens.js +0 -74
- package/dist/presentation/routes/mint-registration-tokens.js.map +0 -1
- package/dist/presentation/routes/planned-capacity.d.ts +0 -3
- package/dist/presentation/routes/planned-capacity.d.ts.map +0 -1
- package/dist/presentation/routes/planned-capacity.js +0 -54
- package/dist/presentation/routes/planned-capacity.js.map +0 -1
- package/src/core/capacity-assignments.ts +0 -10
- package/src/core/entities/capacity-assignment.ts +0 -8
- package/src/db/capacity-assignments.test.ts +0 -159
- package/src/db/capacity-assignments.ts +0 -116
- package/src/db/schema/capacity-assignments.ts +0 -34
- package/src/presentation/dto/mint-registration-tokens.ts +0 -28
- package/src/presentation/routes/assign-capacity.ts +0 -48
- package/src/presentation/routes/mint-registration-tokens.test.ts +0 -453
- package/src/presentation/routes/mint-registration-tokens.ts +0 -80
- package/src/presentation/routes/planned-capacity.ts +0 -49
|
@@ -3,8 +3,7 @@ import {and, desc, eq, inArray, or, sql} from 'drizzle-orm';
|
|
|
3
3
|
import {db} from '#db/db.js';
|
|
4
4
|
import {createRunnerSessionConsumingEphemeralToken} from '#db/ephemeral-registration-tokens.js';
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
createPlannedProvisionedCapacity,
|
|
6
|
+
attachRunnerInstanceProviderId,
|
|
8
7
|
listActiveRunnerInstanceCountsByTemplateTx,
|
|
9
8
|
listActiveRunnerInstances,
|
|
10
9
|
listProvisionerTerminateIntentRowsTx,
|
|
@@ -1014,26 +1013,33 @@ describe('reapStaleRunnerInstances', () => {
|
|
|
1014
1013
|
expect(reservation?.count).toBe(1);
|
|
1015
1014
|
});
|
|
1016
1015
|
|
|
1017
|
-
it('fails stale
|
|
1016
|
+
it('fails a stale installation runner instance before provider attachment', async () => {
|
|
1018
1017
|
const installationProvisioner = await provisionerTokenFactory.create({
|
|
1019
1018
|
scope: 'installation',
|
|
1020
1019
|
workspaceId: null,
|
|
1021
1020
|
});
|
|
1022
|
-
const capacity = await createPlannedProvisionedCapacity({
|
|
1023
|
-
provisionerId: installationProvisioner.id,
|
|
1024
|
-
providerKind: 'docker',
|
|
1025
|
-
templateKey: null,
|
|
1026
|
-
});
|
|
1027
1021
|
await db()
|
|
1028
|
-
.update(
|
|
1029
|
-
.set({
|
|
1030
|
-
.where(eq(
|
|
1022
|
+
.update(provisionerTokens)
|
|
1023
|
+
.set({lastSeenAt: null})
|
|
1024
|
+
.where(eq(provisionerTokens.id, installationProvisioner.id));
|
|
1025
|
+
const [instance] = await db()
|
|
1026
|
+
.insert(providerRunners)
|
|
1027
|
+
.values({
|
|
1028
|
+
provisionerId: installationProvisioner.id,
|
|
1029
|
+
providerKind: 'docker',
|
|
1030
|
+
labels: [],
|
|
1031
|
+
state: 'starting',
|
|
1032
|
+
reportedAt: staleAt(),
|
|
1033
|
+
updatedAt: staleAt(),
|
|
1034
|
+
})
|
|
1035
|
+
.returning({id: providerRunners.id});
|
|
1036
|
+
if (!instance) throw new Error('Runner instance insert returned no row');
|
|
1031
1037
|
|
|
1032
1038
|
const result = await reapStaleRunnerInstances({thresholdSeconds: 60, limit: 100});
|
|
1033
1039
|
const [row] = await db()
|
|
1034
1040
|
.select()
|
|
1035
1041
|
.from(providerRunners)
|
|
1036
|
-
.where(eq(providerRunners.id,
|
|
1042
|
+
.where(eq(providerRunners.id, instance.id));
|
|
1037
1043
|
|
|
1038
1044
|
expect(result.reaped).toBe(1);
|
|
1039
1045
|
expect(row).toMatchObject({workspaceId: null, providerRunnerId: null, state: 'failed'});
|
|
@@ -1815,29 +1821,36 @@ describe('reconcileRunnerInstances', () => {
|
|
|
1815
1821
|
}
|
|
1816
1822
|
});
|
|
1817
1823
|
|
|
1818
|
-
describe('
|
|
1824
|
+
describe('runner instance provider attachment', () => {
|
|
1819
1825
|
it('belongs to its provisioner until it receives one provider runner identity', async () => {
|
|
1820
1826
|
const provisionerId = crypto.randomUUID();
|
|
1821
|
-
const
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1827
|
+
const [instance] = await db()
|
|
1828
|
+
.insert(providerRunners)
|
|
1829
|
+
.values({
|
|
1830
|
+
provisionerId,
|
|
1831
|
+
providerKind: 'docker',
|
|
1832
|
+
templateKey: 'linux',
|
|
1833
|
+
labels: [],
|
|
1834
|
+
state: 'starting',
|
|
1835
|
+
reportedAt: new Date(),
|
|
1836
|
+
})
|
|
1837
|
+
.returning({id: providerRunners.id});
|
|
1838
|
+
if (!instance) throw new Error('Runner instance insert returned no row');
|
|
1826
1839
|
|
|
1827
|
-
const attached = await
|
|
1828
|
-
|
|
1840
|
+
const attached = await attachRunnerInstanceProviderId({
|
|
1841
|
+
runnerInstanceId: instance.id,
|
|
1829
1842
|
provisionerId,
|
|
1830
1843
|
providerRunnerId: 'container-1',
|
|
1831
1844
|
});
|
|
1832
|
-
const rebound = await
|
|
1833
|
-
|
|
1845
|
+
const rebound = await attachRunnerInstanceProviderId({
|
|
1846
|
+
runnerInstanceId: instance.id,
|
|
1834
1847
|
provisionerId,
|
|
1835
1848
|
providerRunnerId: 'container-2',
|
|
1836
1849
|
});
|
|
1837
1850
|
const [row] = await db()
|
|
1838
1851
|
.select()
|
|
1839
1852
|
.from(providerRunners)
|
|
1840
|
-
.where(eq(providerRunners.id,
|
|
1853
|
+
.where(eq(providerRunners.id, instance.id));
|
|
1841
1854
|
|
|
1842
1855
|
expect(attached).toBe(true);
|
|
1843
1856
|
expect(rebound).toBe(false);
|
|
@@ -97,28 +97,8 @@ export interface ReapStaleRunnerInstancesResult {
|
|
|
97
97
|
reservationsReleased: number;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
export async function
|
|
101
|
-
|
|
102
|
-
providerKind: string | null;
|
|
103
|
-
templateKey: string | null;
|
|
104
|
-
}): Promise<{capacityId: string}> {
|
|
105
|
-
const [row] = await db()
|
|
106
|
-
.insert(providerRunners)
|
|
107
|
-
.values({
|
|
108
|
-
provisionerId: params.provisionerId,
|
|
109
|
-
providerKind: params.providerKind,
|
|
110
|
-
templateKey: params.templateKey,
|
|
111
|
-
state: 'starting',
|
|
112
|
-
labels: [],
|
|
113
|
-
reportedAt: new Date(),
|
|
114
|
-
})
|
|
115
|
-
.returning({capacityId: providerRunners.id});
|
|
116
|
-
if (!row) throw new Error('Planned capacity insert returned no row');
|
|
117
|
-
return row;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export async function attachProviderRunnerId(params: {
|
|
121
|
-
capacityId: string;
|
|
100
|
+
export async function attachRunnerInstanceProviderId(params: {
|
|
101
|
+
runnerInstanceId: string;
|
|
122
102
|
provisionerId: string;
|
|
123
103
|
providerRunnerId: string;
|
|
124
104
|
}): Promise<boolean> {
|
|
@@ -127,7 +107,7 @@ export async function attachProviderRunnerId(params: {
|
|
|
127
107
|
.set({providerRunnerId: params.providerRunnerId, updatedAt: sql`now()`})
|
|
128
108
|
.where(
|
|
129
109
|
and(
|
|
130
|
-
eq(providerRunners.id, params.
|
|
110
|
+
eq(providerRunners.id, params.runnerInstanceId),
|
|
131
111
|
eq(providerRunners.provisionerId, params.provisionerId),
|
|
132
112
|
isNull(providerRunners.providerRunnerId),
|
|
133
113
|
notInArray(providerRunners.state, [...terminalStates]),
|
|
@@ -24,6 +24,7 @@ export const providerRunners = pgTable(
|
|
|
24
24
|
provisionerId: uuid('provisioner_id').notNull(),
|
|
25
25
|
providerRunnerId: text('provider_runner_id'),
|
|
26
26
|
reservationId: uuid('reservation_id'),
|
|
27
|
+
assignedAt: timestamp('assigned_at', {withTimezone: true}),
|
|
27
28
|
templateKey: text('template_key'),
|
|
28
29
|
labels: text('labels').array().notNull().default([]),
|
|
29
30
|
state: providerRunnerStateEnum('state').notNull(),
|
|
@@ -62,13 +63,14 @@ export type RunnerInstanceDb = typeof providerRunners.$inferSelect;
|
|
|
62
63
|
export type RunnerInstanceInsertDb = typeof providerRunners.$inferInsert;
|
|
63
64
|
|
|
64
65
|
export function toRunnerInstance(row: RunnerInstanceDb): RunnerInstance {
|
|
65
|
-
if (!row.providerRunnerId) throw new Error('
|
|
66
|
+
if (!row.providerRunnerId) throw new Error('Runner instance has no provider runner identity');
|
|
66
67
|
return {
|
|
67
68
|
id: row.id,
|
|
68
69
|
workspaceId: row.workspaceId,
|
|
69
70
|
provisionerId: row.provisionerId,
|
|
70
71
|
providerRunnerId: row.providerRunnerId,
|
|
71
72
|
reservationId: row.reservationId,
|
|
73
|
+
assignedAt: row.assignedAt,
|
|
72
74
|
templateKey: row.templateKey,
|
|
73
75
|
labels: row.labels,
|
|
74
76
|
state: row.state,
|
package/src/index.ts
CHANGED
|
@@ -21,15 +21,6 @@ import {createRunnersInterModulePresentation} from '#presentation/inter-module.j
|
|
|
21
21
|
import {createRunnersMaintenanceActivities} from '#temporal/activities/index.js';
|
|
22
22
|
import {RUNNERS_MAINTENANCE_TASK_QUEUE} from '#temporal/constants.js';
|
|
23
23
|
|
|
24
|
-
export {
|
|
25
|
-
type MintEphemeralRegistrationTokenParams,
|
|
26
|
-
type MintEphemeralRegistrationTokenResult,
|
|
27
|
-
type MintEphemeralRegistrationTokensBatchParams,
|
|
28
|
-
type MintEphemeralRegistrationTokensBatchResult,
|
|
29
|
-
type MintEphemeralRegistrationTokensBatchRunnerInstance,
|
|
30
|
-
mintEphemeralRegistrationToken,
|
|
31
|
-
mintEphemeralRegistrationTokensBatch,
|
|
32
|
-
} from '#core/ephemeral-registration-tokens.js';
|
|
33
24
|
export {
|
|
34
25
|
type EffectiveRunnerToolCapabilitiesResult,
|
|
35
26
|
getEffectiveRunnerToolCapabilities,
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
export {toActiveProvisionerDto} from './active-provisioner.js';
|
|
2
2
|
export {toManualRegistrationTokenDto} from './manual-registration-token.js';
|
|
3
|
-
export {
|
|
4
|
-
toMintRegistrationTokensResponseDto,
|
|
5
|
-
toMintRegistrationTokensRunnerInstances,
|
|
6
|
-
} from './mint-registration-tokens.js';
|
|
7
3
|
export {toPollDemandResponseDto} from './poll-demand.js';
|
|
8
4
|
export {toProvisionerTokenDto} from './provisioner-token.js';
|
|
9
5
|
export {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {requireProvisionerContext} from '@shipfox/api-auth-context';
|
|
2
|
+
import {
|
|
3
|
+
assignRunnerInstancesBodySchema,
|
|
4
|
+
assignRunnerInstancesResponseSchema,
|
|
5
|
+
} from '@shipfox/api-runners-dto';
|
|
6
|
+
import {ClientError, defineRoute} from '@shipfox/node-fastify';
|
|
7
|
+
import {
|
|
8
|
+
ReservationExpiredError,
|
|
9
|
+
ReservationNotFoundError,
|
|
10
|
+
RunnerInstanceAlreadyAssignedError,
|
|
11
|
+
RunnerInstanceNotAssignableError,
|
|
12
|
+
} from '#core/errors.js';
|
|
13
|
+
import {assignRunnerInstances} from '#core/runner-assignments.js';
|
|
14
|
+
|
|
15
|
+
export const assignRunnerInstancesRoute = defineRoute({
|
|
16
|
+
method: 'POST',
|
|
17
|
+
path: '/runner-instances/assignments',
|
|
18
|
+
description: 'Assign enrolled runner instances to an owned demand reservation',
|
|
19
|
+
schema: {
|
|
20
|
+
body: assignRunnerInstancesBodySchema,
|
|
21
|
+
response: {200: assignRunnerInstancesResponseSchema},
|
|
22
|
+
},
|
|
23
|
+
errorHandler: (error) => {
|
|
24
|
+
if (error instanceof ReservationNotFoundError)
|
|
25
|
+
throw new ClientError('Reservation not found', 'reservation-not-found', {status: 404});
|
|
26
|
+
if (error instanceof ReservationExpiredError)
|
|
27
|
+
throw new ClientError('Reservation has expired', 'reservation-expired', {status: 409});
|
|
28
|
+
if (
|
|
29
|
+
error instanceof RunnerInstanceAlreadyAssignedError ||
|
|
30
|
+
error instanceof RunnerInstanceNotAssignableError
|
|
31
|
+
)
|
|
32
|
+
throw new ClientError(
|
|
33
|
+
'Runner instance cannot be assigned',
|
|
34
|
+
'runner-instance-not-assignable',
|
|
35
|
+
{status: 409},
|
|
36
|
+
);
|
|
37
|
+
throw error;
|
|
38
|
+
},
|
|
39
|
+
handler: async (request) => {
|
|
40
|
+
const {provisionerTokenId} = requireProvisionerContext(request);
|
|
41
|
+
return {
|
|
42
|
+
runner_instance_ids: await assignRunnerInstances({
|
|
43
|
+
provisionerId: provisionerTokenId,
|
|
44
|
+
reservationId: request.body.reservation_id,
|
|
45
|
+
runnerInstanceIds: request.body.runner_instance_ids,
|
|
46
|
+
}),
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
});
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import type {AuthInterModuleClient} from '@shipfox/api-auth-dto/inter-module';
|
|
9
9
|
import type {RouteGroup} from '@shipfox/node-fastify';
|
|
10
10
|
import type {CreateRunnersModuleOptions} from '#installation-provisioning.js';
|
|
11
|
-
import {
|
|
11
|
+
import {assignRunnerInstancesRoute} from './assign-runner-instances.js';
|
|
12
12
|
import {createManualRegistrationTokenRoute} from './create-manual-registration-token.js';
|
|
13
13
|
import {createProvisionerTokenRoute} from './create-provisioner-token.js';
|
|
14
14
|
import {getProvisionerMeRoute} from './get-provisioner-me.js';
|
|
@@ -17,8 +17,6 @@ import {listActiveProvisionersRoute} from './list-active-provisioners.js';
|
|
|
17
17
|
import {listActiveRunnersRoute} from './list-active-runners.js';
|
|
18
18
|
import {listManualRegistrationTokensRoute} from './list-manual-registration-tokens.js';
|
|
19
19
|
import {listProvisionerTokensRoute} from './list-provisioner-tokens.js';
|
|
20
|
-
import {mintRegistrationTokensRoute} from './mint-registration-tokens.js';
|
|
21
|
-
import {attachProviderRunnerRoute, createPlannedCapacityRoute} from './planned-capacity.js';
|
|
22
20
|
import {createPollDemandRoute, pollDemandRoute} from './poll-demand.js';
|
|
23
21
|
import {reconcileRunnerInstancesRoute} from './reconcile-runner-instances.js';
|
|
24
22
|
import {createRegisterRoute} from './register.js';
|
|
@@ -28,6 +26,7 @@ import {revokeManualRegistrationTokenRoute} from './revoke-manual-registration-t
|
|
|
28
26
|
import {revokeProvisionerTokenRoute} from './revoke-provisioner-token.js';
|
|
29
27
|
import {
|
|
30
28
|
attachRunnerControlProviderIdRoute,
|
|
29
|
+
attachRunnerInstanceProviderIdRoute,
|
|
31
30
|
createRunnerInstancesRoute,
|
|
32
31
|
enrollRunnerRoute,
|
|
33
32
|
exchangeRunnerBootstrapRoute,
|
|
@@ -72,10 +71,8 @@ function createRunnerOnlyRoutes(auth: AuthInterModuleClient): RouteGroup[] {
|
|
|
72
71
|
routes: [
|
|
73
72
|
pollDemandRoute,
|
|
74
73
|
createRunnerInstancesRoute,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
assignCapacityRoute,
|
|
78
|
-
mintRegistrationTokensRoute,
|
|
74
|
+
attachRunnerInstanceProviderIdRoute,
|
|
75
|
+
assignRunnerInstancesRoute,
|
|
79
76
|
reportRunnerInstancesRoute,
|
|
80
77
|
reconcileRunnerInstancesRoute,
|
|
81
78
|
],
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import {eq} from 'drizzle-orm';
|
|
11
11
|
import type {FastifyInstance, FastifyRequest} from 'fastify';
|
|
12
12
|
import {db} from '#db/db.js';
|
|
13
|
+
import {reservations} from '#db/schema/reservations.js';
|
|
13
14
|
import {runnerActivationTokens} from '#db/schema/runner-activation-tokens.js';
|
|
14
15
|
import {runnerBootstrapTokens, runnerControlSessions} from '#db/schema/runner-control-sessions.js';
|
|
15
16
|
import {providerRunners} from '#db/schema/runner-instances.js';
|
|
@@ -162,6 +163,65 @@ describe('runner enrollment control plane', () => {
|
|
|
162
163
|
expect(enrolled.json()).toMatchObject({code: 'runner-control-session-invalid'});
|
|
163
164
|
});
|
|
164
165
|
|
|
166
|
+
it('assigns an enrolled runner instance only through the owned reservation', async () => {
|
|
167
|
+
const workspaceId = crypto.randomUUID();
|
|
168
|
+
const created = await app.inject({
|
|
169
|
+
method: 'POST',
|
|
170
|
+
url: '/provisioners/runner-instances/batch',
|
|
171
|
+
headers: {authorization: `Bearer ${token}`},
|
|
172
|
+
payload: {runner_instances: [{template_key: 'linux'}]},
|
|
173
|
+
});
|
|
174
|
+
const runner = created.json().runner_instances[0];
|
|
175
|
+
const exchanged = await app.inject({
|
|
176
|
+
method: 'POST',
|
|
177
|
+
url: '/runner-enrollment/exchange',
|
|
178
|
+
payload: {bootstrap_token: runner.bootstrap_token},
|
|
179
|
+
});
|
|
180
|
+
const controlToken = exchanged.json().control_session_token;
|
|
181
|
+
await app.inject({
|
|
182
|
+
method: 'POST',
|
|
183
|
+
url: '/runner-control/enrollment',
|
|
184
|
+
headers: {authorization: `Bearer ${controlToken}`},
|
|
185
|
+
payload: {labels: ['linux'], provider_kind: 'docker', protocol_version: '1'},
|
|
186
|
+
});
|
|
187
|
+
await app.inject({
|
|
188
|
+
method: 'POST',
|
|
189
|
+
url: '/runner-control/provider-runner',
|
|
190
|
+
headers: {authorization: `Bearer ${controlToken}`},
|
|
191
|
+
payload: {provider_runner_id: 'container-assignment-test'},
|
|
192
|
+
});
|
|
193
|
+
const [reservation] = await db()
|
|
194
|
+
.insert(reservations)
|
|
195
|
+
.values({
|
|
196
|
+
workspaceId,
|
|
197
|
+
provisionerId,
|
|
198
|
+
requiredLabels: ['linux'],
|
|
199
|
+
count: 1,
|
|
200
|
+
expiresAt: new Date(Date.now() + 60_000),
|
|
201
|
+
})
|
|
202
|
+
.returning();
|
|
203
|
+
if (!reservation) throw new Error('Reservation insert returned no row');
|
|
204
|
+
|
|
205
|
+
const assigned = await app.inject({
|
|
206
|
+
method: 'POST',
|
|
207
|
+
url: '/provisioners/runner-instances/assignments',
|
|
208
|
+
headers: {authorization: `Bearer ${token}`},
|
|
209
|
+
payload: {reservation_id: reservation.id, runner_instance_ids: [runner.runner_instance_id]},
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
expect(assigned.statusCode).toBe(200);
|
|
213
|
+
expect(assigned.json()).toEqual({runner_instance_ids: [runner.runner_instance_id]});
|
|
214
|
+
const [instance] = await db()
|
|
215
|
+
.select()
|
|
216
|
+
.from(providerRunners)
|
|
217
|
+
.where(eq(providerRunners.id, runner.runner_instance_id));
|
|
218
|
+
expect(instance).toMatchObject({
|
|
219
|
+
workspaceId,
|
|
220
|
+
reservationId: reservation.id,
|
|
221
|
+
assignedAt: expect.any(Date),
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
165
225
|
it('returns an activation token only for its assigned runner and closes control access after registration', async () => {
|
|
166
226
|
const created = await app.inject({
|
|
167
227
|
method: 'POST',
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
RunnerControlSessionInvalidError,
|
|
26
26
|
touchRunnerControlSession,
|
|
27
27
|
} from '#core/runner-control-sessions.js';
|
|
28
|
+
import {attachRunnerInstanceProviderId} from '#core/runner-instances.js';
|
|
28
29
|
import {runnerBootstrapExchangeCount, runnerControlHeartbeatCount} from '#metrics/instance.js';
|
|
29
30
|
import {authenticateRunnerControlSession} from '#presentation/auth/index.js';
|
|
30
31
|
|
|
@@ -56,6 +57,27 @@ export const createRunnerInstancesRoute = defineRoute({
|
|
|
56
57
|
},
|
|
57
58
|
});
|
|
58
59
|
|
|
60
|
+
export const attachRunnerInstanceProviderIdRoute = defineRoute({
|
|
61
|
+
method: 'POST',
|
|
62
|
+
path: '/runner-instances/:runnerInstanceId/provider-runner',
|
|
63
|
+
description: 'Attach the provider identity after the provisioner launches a runner instance',
|
|
64
|
+
schema: {
|
|
65
|
+
params: z.object({runnerInstanceId: z.string().uuid()}),
|
|
66
|
+
body: attachRunnerControlProviderIdBodySchema,
|
|
67
|
+
response: {200: z.object({attached: z.boolean()})},
|
|
68
|
+
},
|
|
69
|
+
handler: async (request) => {
|
|
70
|
+
const {provisionerTokenId} = requireProvisionerContext(request);
|
|
71
|
+
return {
|
|
72
|
+
attached: await attachRunnerInstanceProviderId({
|
|
73
|
+
runnerInstanceId: request.params.runnerInstanceId,
|
|
74
|
+
provisionerId: provisionerTokenId,
|
|
75
|
+
providerRunnerId: request.body.provider_runner_id,
|
|
76
|
+
}),
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
59
81
|
export const exchangeRunnerBootstrapRoute = defineRoute({
|
|
60
82
|
method: 'POST',
|
|
61
83
|
path: '/exchange',
|
package/test/globalSetup.ts
CHANGED
|
@@ -9,7 +9,6 @@ export async function setup() {
|
|
|
9
9
|
|
|
10
10
|
await runMigrations(db(), migrationsPath, '__drizzle_migrations_runners');
|
|
11
11
|
await db().execute(sql`TRUNCATE runners_provisioner_capability_snapshots CASCADE`);
|
|
12
|
-
await db().execute(sql`TRUNCATE runners_capacity_assignments CASCADE`);
|
|
13
12
|
await db().execute(sql`TRUNCATE runners_runner_instances, runners_reservations CASCADE`);
|
|
14
13
|
await db().execute(sql`TRUNCATE runners_provisioner_tokens CASCADE`);
|
|
15
14
|
await db().execute(sql`TRUNCATE runners_ephemeral_registration_tokens CASCADE`);
|