@shipfox/api-runners 12.1.0 → 12.3.0
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 +35 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +24 -2
- package/dist/config.js.map +1 -1
- package/dist/core/errors.d.ts +4 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +6 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/rate-limit.d.ts.map +1 -1
- package/dist/core/runner-control-sessions.d.ts.map +1 -1
- package/dist/core/runner-control-sessions.js +37 -11
- package/dist/core/runner-control-sessions.js.map +1 -1
- package/dist/core/runner-instances.d.ts +1 -0
- package/dist/core/runner-instances.d.ts.map +1 -1
- package/dist/core/runner-instances.js.map +1 -1
- package/dist/core/runner-labels.d.ts +11 -0
- package/dist/core/runner-labels.d.ts.map +1 -0
- package/dist/core/runner-labels.js +41 -0
- package/dist/core/runner-labels.js.map +1 -0
- package/dist/core/runner-sessions.d.ts.map +1 -1
- package/dist/core/runner-sessions.js +5 -1
- package/dist/core/runner-sessions.js.map +1 -1
- package/dist/db/db.d.ts +64 -64
- package/dist/db/ephemeral-registration-tokens.d.ts.map +1 -1
- package/dist/db/ephemeral-registration-tokens.js +12 -2
- package/dist/db/ephemeral-registration-tokens.js.map +1 -1
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/reservations.d.ts.map +1 -1
- package/dist/db/reservations.js +36 -10
- package/dist/db/reservations.js.map +1 -1
- package/dist/db/runner-instances.d.ts +4 -0
- package/dist/db/runner-instances.d.ts.map +1 -1
- package/dist/db/runner-instances.js +16 -5
- package/dist/db/runner-instances.js.map +1 -1
- package/dist/db/runner-sessions.d.ts.map +1 -1
- package/dist/db/runner-sessions.js +33 -15
- package/dist/db/runner-sessions.js.map +1 -1
- package/dist/db/schema/admin-command-results.d.ts +1 -1
- package/dist/db/schema/ephemeral-registration-tokens.d.ts +1 -1
- package/dist/db/schema/manual-registration-tokens.d.ts +1 -1
- package/dist/db/schema/outbox.d.ts +1 -1
- package/dist/db/schema/pending-job-executions.d.ts +3 -3
- package/dist/db/schema/provisioner-capability-snapshots.d.ts +3 -3
- package/dist/db/schema/provisioner-tokens.d.ts +2 -2
- package/dist/db/schema/rate-limits.d.ts +1 -1
- package/dist/db/schema/reservations.d.ts +3 -3
- package/dist/db/schema/runner-activation-tokens.d.ts +1 -1
- package/dist/db/schema/runner-control-sessions.d.ts +2 -2
- package/dist/db/schema/runner-instances.d.ts +4 -4
- package/dist/db/schema/runner-instances.d.ts.map +1 -1
- package/dist/db/schema/runner-instances.js +1 -0
- package/dist/db/schema/runner-instances.js.map +1 -1
- package/dist/db/schema/runner-sessions.d.ts +4 -4
- package/dist/db/schema/running-job-executions.d.ts +5 -5
- package/dist/metrics/index.d.ts +2 -1
- package/dist/metrics/index.d.ts.map +1 -1
- package/dist/metrics/index.js +1 -1
- package/dist/metrics/index.js.map +1 -1
- package/dist/metrics/instance.d.ts +13 -8
- package/dist/metrics/instance.d.ts.map +1 -1
- package/dist/metrics/instance.js +8 -0
- package/dist/metrics/instance.js.map +1 -1
- package/dist/metrics/service.d.ts.map +1 -1
- package/dist/metrics/service.js +20 -4
- package/dist/metrics/service.js.map +1 -1
- package/dist/presentation/dto/admin-provisioner-token.d.ts +1 -1
- package/dist/presentation/dto/admin-provisioner-token.d.ts.map +1 -1
- package/dist/presentation/dto/admin-runner-instances.d.ts +2 -2
- package/dist/presentation/dto/admin-runner-instances.d.ts.map +1 -1
- package/dist/presentation/routes/poll-demand.d.ts.map +1 -1
- package/dist/presentation/routes/poll-demand.js +12 -6
- package/dist/presentation/routes/poll-demand.js.map +1 -1
- package/dist/presentation/routes/rate-limit.d.ts.map +1 -1
- package/dist/presentation/routes/register.d.ts.map +1 -1
- package/dist/presentation/routes/register.js +9 -1
- package/dist/presentation/routes/register.js.map +1 -1
- package/dist/presentation/routes/report-runner-instances.d.ts.map +1 -1
- package/dist/presentation/routes/report-runner-instances.js +1 -0
- package/dist/presentation/routes/report-runner-instances.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0004_huge_skreet.sql +1 -0
- package/drizzle/meta/0004_snapshot.json +2194 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +9 -9
- package/src/config.test.ts +81 -0
- package/src/config.ts +39 -2
- package/src/core/errors.ts +9 -0
- package/src/core/index.ts +1 -0
- package/src/core/runner-control-sessions.test.ts +404 -0
- package/src/core/runner-control-sessions.ts +51 -11
- package/src/core/runner-instances.ts +1 -0
- package/src/core/runner-labels.test.ts +53 -0
- package/src/core/runner-labels.ts +51 -0
- package/src/core/runner-sessions.test.ts +34 -1
- package/src/core/runner-sessions.ts +8 -1
- package/src/db/ephemeral-registration-tokens.ts +15 -1
- package/src/db/index.ts +1 -0
- package/src/db/reservations.test.ts +537 -0
- package/src/db/reservations.ts +116 -17
- package/src/db/runner-instances.test.ts +276 -1
- package/src/db/runner-instances.ts +39 -3
- package/src/db/runner-sessions.ts +39 -15
- package/src/db/schema/runner-instances.ts +3 -0
- package/src/metrics/index.ts +2 -0
- package/src/metrics/instance.ts +18 -0
- package/src/metrics/service.test.ts +99 -0
- package/src/metrics/service.ts +24 -4
- package/src/presentation/routes/late-runner-enrollment.test.ts +230 -0
- package/src/presentation/routes/poll-demand.test.ts +116 -3
- package/src/presentation/routes/poll-demand.ts +18 -6
- package/src/presentation/routes/register.test.ts +116 -2
- package/src/presentation/routes/register.ts +7 -0
- package/src/presentation/routes/report-runner-instances.test.ts +33 -0
- package/src/presentation/routes/report-runner-instances.ts +1 -0
- package/src/presentation/routes/runner-enrollment.test.ts +65 -5
- package/src/temporal/activities/maintenance-activities.test.ts +24 -31
- package/test/env.ts +1 -0
- package/test/fixtures/late-runner-enrollment.ts +103 -0
- package/test/index.ts +5 -0
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
deleteExpiredEphemeralRegistrationTokens,
|
|
3
|
-
deleteExpiredRunnerReservations,
|
|
4
|
-
deleteExpiredRunnerSessions,
|
|
5
|
-
detectAndExpireStuckJobs,
|
|
6
|
-
reapStaleRunnerInstances,
|
|
7
|
-
} from '#core/maintenance.js';
|
|
8
|
-
import {
|
|
9
|
-
deleteExpiredEphemeralRegistrationTokensActivity,
|
|
10
|
-
deleteExpiredReservationsActivity,
|
|
11
|
-
deleteExpiredRunnerSessionsActivity,
|
|
12
|
-
detectAndExpireStuckJobsActivity,
|
|
13
|
-
reapStaleRunnerInstancesActivity,
|
|
14
|
-
} from './maintenance-activities.js';
|
|
15
|
-
|
|
16
1
|
vi.mock('#core/maintenance.js', () => ({
|
|
17
2
|
deleteExpiredEphemeralRegistrationTokens: vi.fn(),
|
|
18
3
|
deleteExpiredRunnerReservations: vi.fn(),
|
|
@@ -21,46 +6,52 @@ vi.mock('#core/maintenance.js', () => ({
|
|
|
21
6
|
reapStaleRunnerInstances: vi.fn(),
|
|
22
7
|
}));
|
|
23
8
|
|
|
24
|
-
|
|
9
|
+
let maintenance: typeof import('#core/maintenance.js');
|
|
10
|
+
let activities: typeof import('./maintenance-activities.js');
|
|
11
|
+
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
vi.resetModules();
|
|
14
|
+
maintenance = await import('#core/maintenance.js');
|
|
15
|
+
activities = await import('./maintenance-activities.js');
|
|
25
16
|
vi.clearAllMocks();
|
|
26
17
|
});
|
|
27
18
|
|
|
28
19
|
describe('detectAndExpireStuckJobsActivity', () => {
|
|
29
20
|
it('delegates to core maintenance', async () => {
|
|
30
|
-
vi.mocked(detectAndExpireStuckJobs).mockResolvedValueOnce({expired: 2});
|
|
21
|
+
vi.mocked(maintenance.detectAndExpireStuckJobs).mockResolvedValueOnce({expired: 2});
|
|
31
22
|
|
|
32
|
-
const result = await detectAndExpireStuckJobsActivity({thresholdSeconds: 180});
|
|
23
|
+
const result = await activities.detectAndExpireStuckJobsActivity({thresholdSeconds: 180});
|
|
33
24
|
|
|
34
25
|
expect(result).toEqual({expired: 2});
|
|
35
|
-
expect(detectAndExpireStuckJobs).toHaveBeenCalledWith({thresholdSeconds: 180});
|
|
26
|
+
expect(maintenance.detectAndExpireStuckJobs).toHaveBeenCalledWith({thresholdSeconds: 180});
|
|
36
27
|
});
|
|
37
28
|
});
|
|
38
29
|
|
|
39
30
|
describe('deleteExpiredReservationsActivity', () => {
|
|
40
31
|
it('delegates to core maintenance', async () => {
|
|
41
|
-
vi.mocked(deleteExpiredRunnerReservations).mockResolvedValueOnce({deleted: 3});
|
|
32
|
+
vi.mocked(maintenance.deleteExpiredRunnerReservations).mockResolvedValueOnce({deleted: 3});
|
|
42
33
|
|
|
43
|
-
const result = await deleteExpiredReservationsActivity({limit: 50});
|
|
34
|
+
const result = await activities.deleteExpiredReservationsActivity({limit: 50});
|
|
44
35
|
|
|
45
36
|
expect(result).toEqual({deleted: 3});
|
|
46
|
-
expect(deleteExpiredRunnerReservations).toHaveBeenCalledWith({limit: 50});
|
|
37
|
+
expect(maintenance.deleteExpiredRunnerReservations).toHaveBeenCalledWith({limit: 50});
|
|
47
38
|
});
|
|
48
39
|
});
|
|
49
40
|
|
|
50
41
|
describe('reapStaleRunnerInstancesActivity', () => {
|
|
51
42
|
it('delegates to core maintenance', async () => {
|
|
52
|
-
vi.mocked(reapStaleRunnerInstances).mockResolvedValueOnce({
|
|
43
|
+
vi.mocked(maintenance.reapStaleRunnerInstances).mockResolvedValueOnce({
|
|
53
44
|
reaped: 4,
|
|
54
45
|
reservationsReleased: 2,
|
|
55
46
|
});
|
|
56
47
|
|
|
57
|
-
const result = await reapStaleRunnerInstancesActivity({
|
|
48
|
+
const result = await activities.reapStaleRunnerInstancesActivity({
|
|
58
49
|
thresholdSeconds: 300,
|
|
59
50
|
limit: 100,
|
|
60
51
|
});
|
|
61
52
|
|
|
62
53
|
expect(result).toEqual({reaped: 4, reservationsReleased: 2});
|
|
63
|
-
expect(reapStaleRunnerInstances).toHaveBeenCalledWith({
|
|
54
|
+
expect(maintenance.reapStaleRunnerInstances).toHaveBeenCalledWith({
|
|
64
55
|
thresholdSeconds: 300,
|
|
65
56
|
limit: 100,
|
|
66
57
|
});
|
|
@@ -69,16 +60,16 @@ describe('reapStaleRunnerInstancesActivity', () => {
|
|
|
69
60
|
|
|
70
61
|
describe('deleteExpiredRunnerSessionsActivity', () => {
|
|
71
62
|
it('delegates to core maintenance', async () => {
|
|
72
|
-
vi.mocked(deleteExpiredRunnerSessions).mockResolvedValueOnce({deleted: 4});
|
|
63
|
+
vi.mocked(maintenance.deleteExpiredRunnerSessions).mockResolvedValueOnce({deleted: 4});
|
|
73
64
|
|
|
74
|
-
const result = await deleteExpiredRunnerSessionsActivity({
|
|
65
|
+
const result = await activities.deleteExpiredRunnerSessionsActivity({
|
|
75
66
|
manualRetentionDays: 30,
|
|
76
67
|
ephemeralRetentionDays: 7,
|
|
77
68
|
limit: 25,
|
|
78
69
|
});
|
|
79
70
|
|
|
80
71
|
expect(result).toEqual({deleted: 4});
|
|
81
|
-
expect(deleteExpiredRunnerSessions).toHaveBeenCalledWith({
|
|
72
|
+
expect(maintenance.deleteExpiredRunnerSessions).toHaveBeenCalledWith({
|
|
82
73
|
manualRetentionDays: 30,
|
|
83
74
|
ephemeralRetentionDays: 7,
|
|
84
75
|
limit: 25,
|
|
@@ -88,15 +79,17 @@ describe('deleteExpiredRunnerSessionsActivity', () => {
|
|
|
88
79
|
|
|
89
80
|
describe('deleteExpiredEphemeralRegistrationTokensActivity', () => {
|
|
90
81
|
it('delegates to core maintenance', async () => {
|
|
91
|
-
vi.mocked(deleteExpiredEphemeralRegistrationTokens).mockResolvedValueOnce({
|
|
82
|
+
vi.mocked(maintenance.deleteExpiredEphemeralRegistrationTokens).mockResolvedValueOnce({
|
|
83
|
+
deleted: 6,
|
|
84
|
+
});
|
|
92
85
|
|
|
93
|
-
const result = await deleteExpiredEphemeralRegistrationTokensActivity({
|
|
86
|
+
const result = await activities.deleteExpiredEphemeralRegistrationTokensActivity({
|
|
94
87
|
retentionDays: 7,
|
|
95
88
|
limit: 25,
|
|
96
89
|
});
|
|
97
90
|
|
|
98
91
|
expect(result).toEqual({deleted: 6});
|
|
99
|
-
expect(deleteExpiredEphemeralRegistrationTokens).toHaveBeenCalledWith({
|
|
92
|
+
expect(maintenance.deleteExpiredEphemeralRegistrationTokens).toHaveBeenCalledWith({
|
|
100
93
|
retentionDays: 7,
|
|
101
94
|
limit: 25,
|
|
102
95
|
});
|
package/test/env.ts
CHANGED
|
@@ -4,6 +4,7 @@ process.env.POSTGRES_USERNAME ??= 'shipfox';
|
|
|
4
4
|
process.env.POSTGRES_PASSWORD ??= 'password';
|
|
5
5
|
process.env.POSTGRES_DATABASE = 'api_test';
|
|
6
6
|
process.env.POSTGRES_MAX_CONNECTIONS ??= '5';
|
|
7
|
+
process.env.RUNNER_RESERVED_LABELS ??= 'shipfox-managed';
|
|
7
8
|
process.env.WORKSPACE_JWT_SECRET = 'test-secret';
|
|
8
9
|
process.env.AUTH_ROOT_KEY = 'MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=';
|
|
9
10
|
process.env.TZ = 'UTC';
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import {extractDisplayPrefix, generateOpaqueToken, hashOpaqueToken} from '@shipfox/node-tokens';
|
|
2
|
+
import {db} from '#db/db.js';
|
|
3
|
+
import {deleteReservationsByIds} from '#db/reservations.js';
|
|
4
|
+
import {reservations} from '#db/schema/reservations.js';
|
|
5
|
+
import {runnerControlSessions} from '#db/schema/runner-control-sessions.js';
|
|
6
|
+
import {providerRunners} from '#db/schema/runner-instances.js';
|
|
7
|
+
|
|
8
|
+
export interface LateRunnerEnrollmentArrangement {
|
|
9
|
+
controlSessionToken: string;
|
|
10
|
+
provisionerId: string;
|
|
11
|
+
reservationId: string;
|
|
12
|
+
runnerInstanceId: string;
|
|
13
|
+
workspaceId: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function arrangeExpiredRunnerEnrollment(params: {
|
|
17
|
+
provisionerId: string;
|
|
18
|
+
workspaceId: string;
|
|
19
|
+
}): Promise<LateRunnerEnrollmentArrangement> {
|
|
20
|
+
const reservationId = await insertReservation({
|
|
21
|
+
...params,
|
|
22
|
+
expiresAt: new Date(Date.now() - 1_000),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return await createRunnerEnrollmentArrangement({...params, reservationId});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function arrangeDeletedRunnerEnrollment(params: {
|
|
29
|
+
provisionerId: string;
|
|
30
|
+
workspaceId: string;
|
|
31
|
+
}): Promise<LateRunnerEnrollmentArrangement> {
|
|
32
|
+
const reservationId = await insertReservation({
|
|
33
|
+
...params,
|
|
34
|
+
expiresAt: new Date(Date.now() + 60_000),
|
|
35
|
+
});
|
|
36
|
+
const deleted = await deleteReservationsByIds([reservationId]);
|
|
37
|
+
if (deleted !== 1) throw new Error('Expected reservation cleanup to delete one row');
|
|
38
|
+
|
|
39
|
+
// The provider can still enroll from a launch payload carrying the deleted reservation id.
|
|
40
|
+
return await createRunnerEnrollmentArrangement({...params, reservationId});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function insertReservation(params: {
|
|
44
|
+
expiresAt: Date;
|
|
45
|
+
provisionerId: string;
|
|
46
|
+
workspaceId: string;
|
|
47
|
+
}): Promise<string> {
|
|
48
|
+
const [reservation] = await db()
|
|
49
|
+
.insert(reservations)
|
|
50
|
+
.values({
|
|
51
|
+
workspaceId: params.workspaceId,
|
|
52
|
+
provisionerId: params.provisionerId,
|
|
53
|
+
requiredLabels: ['linux'],
|
|
54
|
+
count: 1,
|
|
55
|
+
expiresAt: params.expiresAt,
|
|
56
|
+
})
|
|
57
|
+
.returning({id: reservations.id});
|
|
58
|
+
if (!reservation) throw new Error('Expected reservation');
|
|
59
|
+
|
|
60
|
+
return reservation.id;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function createRunnerEnrollmentArrangement(params: {
|
|
64
|
+
provisionerId: string;
|
|
65
|
+
reservationId: string;
|
|
66
|
+
workspaceId: string;
|
|
67
|
+
}): Promise<LateRunnerEnrollmentArrangement> {
|
|
68
|
+
const providerRunnerId = `late-runner-${crypto.randomUUID()}`;
|
|
69
|
+
const controlSessionToken = generateOpaqueToken('runnerControlSession');
|
|
70
|
+
const [runner] = await db()
|
|
71
|
+
.insert(providerRunners)
|
|
72
|
+
.values({
|
|
73
|
+
provisionerId: params.provisionerId,
|
|
74
|
+
providerRunnerId,
|
|
75
|
+
intendedReservationId: params.reservationId,
|
|
76
|
+
templateKey: 'linux',
|
|
77
|
+
labels: ['linux'],
|
|
78
|
+
state: 'starting',
|
|
79
|
+
providerKind: 'docker',
|
|
80
|
+
protocolVersion: '1',
|
|
81
|
+
reportedAt: new Date(),
|
|
82
|
+
})
|
|
83
|
+
.returning({id: providerRunners.id});
|
|
84
|
+
if (!runner) throw new Error('Expected runner instance');
|
|
85
|
+
|
|
86
|
+
await db()
|
|
87
|
+
.insert(runnerControlSessions)
|
|
88
|
+
.values({
|
|
89
|
+
runnerInstanceId: runner.id,
|
|
90
|
+
provisionerId: params.provisionerId,
|
|
91
|
+
hashedToken: hashOpaqueToken(controlSessionToken),
|
|
92
|
+
prefix: extractDisplayPrefix(controlSessionToken),
|
|
93
|
+
expiresAt: new Date(Date.now() + 60_000),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
controlSessionToken,
|
|
98
|
+
provisionerId: params.provisionerId,
|
|
99
|
+
reservationId: params.reservationId,
|
|
100
|
+
runnerInstanceId: runner.id,
|
|
101
|
+
workspaceId: params.workspaceId,
|
|
102
|
+
};
|
|
103
|
+
}
|
package/test/index.ts
CHANGED
|
@@ -23,3 +23,8 @@ export {
|
|
|
23
23
|
mintRunnerSessionToken,
|
|
24
24
|
} from './fixtures/auth.js';
|
|
25
25
|
export {runnersTestAuthClient} from './fixtures/auth-inter-module.js';
|
|
26
|
+
export {
|
|
27
|
+
arrangeDeletedRunnerEnrollment,
|
|
28
|
+
arrangeExpiredRunnerEnrollment,
|
|
29
|
+
type LateRunnerEnrollmentArrangement,
|
|
30
|
+
} from './fixtures/late-runner-enrollment.js';
|