@shipfox/api-runners 12.2.0 → 12.4.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 +34 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -3
- package/dist/config.js.map +1 -1
- package/dist/core/demand.d.ts +4 -2
- package/dist/core/demand.d.ts.map +1 -1
- package/dist/core/demand.js +22 -6
- package/dist/core/demand.js.map +1 -1
- package/dist/core/entities/reservation.d.ts +1 -0
- package/dist/core/entities/reservation.d.ts.map +1 -1
- package/dist/core/entities/reservation.js.map +1 -1
- package/dist/core/entities/runner-instance.d.ts +3 -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/runner-activation.d.ts +4 -1
- package/dist/core/runner-activation.d.ts.map +1 -1
- package/dist/core/runner-activation.js +32 -3
- package/dist/core/runner-activation.js.map +1 -1
- package/dist/core/runner-control-sessions.d.ts +8 -4
- package/dist/core/runner-control-sessions.d.ts.map +1 -1
- package/dist/core/runner-control-sessions.js +69 -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 +1 -0
- package/dist/core/runner-instances.js.map +1 -1
- package/dist/db/db.d.ts +68 -0
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/ephemeral-registration-tokens.d.ts.map +1 -1
- package/dist/db/ephemeral-registration-tokens.js +1 -1
- 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/reservation-locks.d.ts +6 -0
- package/dist/db/reservation-locks.d.ts.map +1 -0
- package/dist/db/reservation-locks.js +15 -0
- package/dist/db/reservation-locks.js.map +1 -0
- package/dist/db/reservations.d.ts +17 -3
- package/dist/db/reservations.d.ts.map +1 -1
- package/dist/db/reservations.js +256 -45
- package/dist/db/reservations.js.map +1 -1
- package/dist/db/runner-assignments.d.ts +17 -0
- package/dist/db/runner-assignments.d.ts.map +1 -1
- package/dist/db/runner-assignments.js +103 -8
- package/dist/db/runner-assignments.js.map +1 -1
- package/dist/db/runner-instances.d.ts +5 -2
- package/dist/db/runner-instances.d.ts.map +1 -1
- package/dist/db/runner-instances.js +134 -25
- 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 +22 -15
- package/dist/db/runner-sessions.js.map +1 -1
- package/dist/db/runner-states.d.ts +2 -0
- package/dist/db/runner-states.d.ts.map +1 -0
- package/dist/db/runner-states.js +7 -0
- package/dist/db/runner-states.js.map +1 -0
- package/dist/db/schema/reservations.d.ts +18 -0
- package/dist/db/schema/reservations.d.ts.map +1 -1
- package/dist/db/schema/reservations.js +7 -1
- package/dist/db/schema/reservations.js.map +1 -1
- package/dist/db/schema/runner-instances.d.ts +18 -0
- package/dist/db/schema/runner-instances.d.ts.map +1 -1
- package/dist/db/schema/runner-instances.js +8 -0
- package/dist/db/schema/runner-instances.js.map +1 -1
- package/dist/metrics/index.d.ts +2 -2
- 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 +24 -2
- package/dist/metrics/instance.d.ts.map +1 -1
- package/dist/metrics/instance.js +23 -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/poll-demand.d.ts.map +1 -1
- package/dist/presentation/dto/poll-demand.js +3 -0
- package/dist/presentation/dto/poll-demand.js.map +1 -1
- package/dist/presentation/dto/runner-instances.d.ts +4 -1
- package/dist/presentation/dto/runner-instances.d.ts.map +1 -1
- package/dist/presentation/dto/runner-instances.js +4 -1
- package/dist/presentation/dto/runner-instances.js.map +1 -1
- package/dist/presentation/routes/assign-runner-instances.d.ts.map +1 -1
- package/dist/presentation/routes/assign-runner-instances.js +3 -3
- package/dist/presentation/routes/assign-runner-instances.js.map +1 -1
- package/dist/presentation/routes/poll-demand.d.ts.map +1 -1
- package/dist/presentation/routes/poll-demand.js +1 -0
- package/dist/presentation/routes/poll-demand.js.map +1 -1
- package/dist/presentation/routes/reconcile-runner-instances.d.ts.map +1 -1
- package/dist/presentation/routes/reconcile-runner-instances.js +4 -2
- package/dist/presentation/routes/reconcile-runner-instances.js.map +1 -1
- package/dist/presentation/routes/runner-enrollment.d.ts +4 -1
- package/dist/presentation/routes/runner-enrollment.d.ts.map +1 -1
- package/dist/presentation/routes/runner-enrollment.js +11 -6
- package/dist/presentation/routes/runner-enrollment.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0005_previous_lenny_balinger.sql +5 -0
- package/drizzle/meta/0005_snapshot.json +2220 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +4 -4
- package/src/config.test.ts +24 -2
- package/src/config.ts +16 -3
- package/src/core/demand.test.ts +192 -3
- package/src/core/demand.ts +44 -4
- package/src/core/entities/reservation.ts +1 -0
- package/src/core/entities/runner-instance.ts +4 -0
- package/src/core/runner-activation.ts +48 -2
- package/src/core/runner-control-sessions.test.ts +230 -4
- package/src/core/runner-control-sessions.ts +84 -10
- package/src/core/runner-instances.test.ts +19 -0
- package/src/core/runner-instances.ts +2 -0
- package/src/core/runner-sessions.test.ts +121 -1
- package/src/db/ephemeral-registration-tokens.test.ts +20 -1
- package/src/db/ephemeral-registration-tokens.ts +1 -0
- package/src/db/index.ts +1 -0
- package/src/db/reservation-locks.ts +22 -0
- package/src/db/reservations.test.ts +1110 -31
- package/src/db/reservations.ts +434 -105
- package/src/db/runner-assignments.test.ts +105 -1
- package/src/db/runner-assignments.ts +178 -9
- package/src/db/runner-instances.test.ts +496 -2
- package/src/db/runner-instances.ts +247 -56
- package/src/db/runner-sessions.ts +26 -15
- package/src/db/runner-states.ts +7 -0
- package/src/db/schema/reservations.ts +5 -1
- package/src/db/schema/runner-instances.ts +9 -0
- package/src/metrics/index.ts +10 -1
- package/src/metrics/instance.ts +59 -2
- package/src/metrics/service.test.ts +106 -0
- package/src/metrics/service.ts +24 -4
- package/src/presentation/dto/poll-demand.ts +3 -0
- package/src/presentation/dto/runner-instances.ts +8 -1
- package/src/presentation/routes/assign-runner-instances.ts +6 -2
- package/src/presentation/routes/late-runner-enrollment.test.ts +447 -0
- package/src/presentation/routes/poll-demand.test.ts +79 -6
- package/src/presentation/routes/poll-demand.ts +1 -0
- package/src/presentation/routes/reconcile-runner-instances.test.ts +34 -2
- package/src/presentation/routes/reconcile-runner-instances.ts +7 -1
- package/src/presentation/routes/runner-enrollment.test.ts +316 -3
- package/src/presentation/routes/runner-enrollment.ts +8 -5
- package/test/factories/reservation.ts +2 -0
- package/test/factories/runner-instance.ts +6 -0
- package/test/fixtures/late-runner-enrollment.ts +103 -0
- package/test/index.ts +5 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/src/metrics/instance.ts
CHANGED
|
@@ -31,6 +31,21 @@ export const runnerBootstrapExchangeCount = meter.createCounter<{
|
|
|
31
31
|
description: 'Runner bootstrap-token exchanges by outcome',
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
+
export type RunnerActivationTokenNotIssuedReason =
|
|
35
|
+
| 'runner-not-found'
|
|
36
|
+
| 'missing-workspace'
|
|
37
|
+
| 'existing-session'
|
|
38
|
+
| 'not-running';
|
|
39
|
+
|
|
40
|
+
export type RunnerActivationTokenNotIssuedSurface = 'enrollment' | 'poll';
|
|
41
|
+
|
|
42
|
+
export const runnerActivationTokenNotIssuedCount = meter.createCounter<{
|
|
43
|
+
reason: RunnerActivationTokenNotIssuedReason;
|
|
44
|
+
surface: RunnerActivationTokenNotIssuedSurface;
|
|
45
|
+
}>('runners_runner_activation_token_not_issued', {
|
|
46
|
+
description: 'Runner activation token issuance skips by reason and surface',
|
|
47
|
+
});
|
|
48
|
+
|
|
34
49
|
export const runnerControlHeartbeatCount = meter.createCounter<Record<string, never>>(
|
|
35
50
|
'runners_runner_control_heartbeat',
|
|
36
51
|
{description: 'Pre-workspace runner-control heartbeats accepted'},
|
|
@@ -67,17 +82,23 @@ export const providerRunnerAbsentTerminatedCount = meter.createCounter<Record<st
|
|
|
67
82
|
|
|
68
83
|
export const providerRunnerTerminateIntentIssuedCount = meter.createCounter<{
|
|
69
84
|
surface: 'poll-demand' | 'reconcile';
|
|
70
|
-
reason: 'job-cancelled' | 'terminal-state';
|
|
85
|
+
reason: 'activation-timeout' | 'job-cancelled' | 'terminal-state';
|
|
71
86
|
}>('runners_provider_runner_terminate_intent_issued', {
|
|
72
87
|
description: 'Provisioned runner terminate intents returned to provisioners',
|
|
73
88
|
});
|
|
74
89
|
|
|
75
90
|
export const providerRunnerTerminateIntentHonoredCount = meter.createCounter<{
|
|
76
|
-
reason: 'job-cancelled';
|
|
91
|
+
reason: 'activation-timeout' | 'job-cancelled';
|
|
77
92
|
}>('runners_provider_runner_terminate_intent_honored', {
|
|
78
93
|
description: 'Provisioned runner terminate intents honored by first transition to terminated',
|
|
79
94
|
});
|
|
80
95
|
|
|
96
|
+
export const providerRunnerActivationOutcomeCount = meter.createCounter<{
|
|
97
|
+
outcome: 'reaped' | 'rebound';
|
|
98
|
+
}>('runners_provider_runner_activation_outcome', {
|
|
99
|
+
description: 'Demand-backed runner activation outcomes by recovery action',
|
|
100
|
+
});
|
|
101
|
+
|
|
81
102
|
export const reservationReleasedCount = meter.createCounter<Record<string, never>>(
|
|
82
103
|
'runners_reservation_released',
|
|
83
104
|
{description: 'Reservation units released from terminal provisioned runner reports'},
|
|
@@ -95,6 +116,18 @@ export const runnerReservationPromotionFailureCount = meter.createCounter<{
|
|
|
95
116
|
description: 'Runner reservation promotion failures during enrollment by reason',
|
|
96
117
|
});
|
|
97
118
|
|
|
119
|
+
export type RunnerReservationCapacityFailureReason =
|
|
120
|
+
| 'reservation-not-found'
|
|
121
|
+
| 'reservation-kind-mismatch'
|
|
122
|
+
| 'reservation-expired'
|
|
123
|
+
| 'capacity-exhausted';
|
|
124
|
+
|
|
125
|
+
export const runnerReservationCapacityFailureCount = meter.createCounter<{
|
|
126
|
+
reason: RunnerReservationCapacityFailureReason;
|
|
127
|
+
}>('runners_reservation_capacity_failures', {
|
|
128
|
+
description: 'Runner reservation admission shortfalls by reason',
|
|
129
|
+
});
|
|
130
|
+
|
|
98
131
|
export type RunnersRateLimitAction = 'provisioner-mint' | 'ephemeral-register';
|
|
99
132
|
export type RunnersRateLimitScope = 'provisioner' | 'ephemeral-token';
|
|
100
133
|
export type RunnersRateLimitOutcome = 'allowed' | 'blocked' | 'unavailable';
|
|
@@ -125,6 +158,21 @@ export function recordRunnerReservationPromotionFailure(
|
|
|
125
158
|
recordMetric(() => runnerReservationPromotionFailureCount.add(1, {reason}));
|
|
126
159
|
}
|
|
127
160
|
|
|
161
|
+
export function recordRunnerActivationTokenNotIssued(params: {
|
|
162
|
+
reason: RunnerActivationTokenNotIssuedReason;
|
|
163
|
+
surface: RunnerActivationTokenNotIssuedSurface;
|
|
164
|
+
}): void {
|
|
165
|
+
recordMetric(() => runnerActivationTokenNotIssuedCount.add(1, params));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function recordRunnerReservationCapacityFailure(
|
|
169
|
+
reason: RunnerReservationCapacityFailureReason,
|
|
170
|
+
count: number,
|
|
171
|
+
): void {
|
|
172
|
+
if (count <= 0) return;
|
|
173
|
+
recordMetric(() => runnerReservationCapacityFailureCount.add(count, {reason}));
|
|
174
|
+
}
|
|
175
|
+
|
|
128
176
|
export function recordRunnersRateLimitCheck(params: {
|
|
129
177
|
action: RunnersRateLimitAction;
|
|
130
178
|
scope: RunnersRateLimitScope;
|
|
@@ -142,3 +190,12 @@ export function recordRunnersRateLimitCheck(params: {
|
|
|
142
190
|
export function recordRunnersRateLimitPruneFailure(): void {
|
|
143
191
|
recordMetric(() => rateLimitPruneFailureCount.add(1));
|
|
144
192
|
}
|
|
193
|
+
|
|
194
|
+
export function recordProviderRunnerActivationOutcome(params: {
|
|
195
|
+
outcome: 'reaped' | 'rebound';
|
|
196
|
+
count?: number;
|
|
197
|
+
}): void {
|
|
198
|
+
recordMetric(() =>
|
|
199
|
+
providerRunnerActivationOutcomeCount.add(params.count ?? 1, {outcome: params.outcome}),
|
|
200
|
+
);
|
|
201
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const mocks = vi.hoisted(() => {
|
|
2
|
+
const gauges = {
|
|
3
|
+
enrolledRunnersWithoutRecentReport: {},
|
|
4
|
+
pendingJobExecutions: {},
|
|
5
|
+
runningJobExecutions: {},
|
|
6
|
+
};
|
|
7
|
+
const gaugeByName = {
|
|
8
|
+
runners_enrolled_without_recent_report: gauges.enrolledRunnersWithoutRecentReport,
|
|
9
|
+
runners_pending_job_executions: gauges.pendingJobExecutions,
|
|
10
|
+
runners_running_job_executions: gauges.runningJobExecutions,
|
|
11
|
+
};
|
|
12
|
+
return {
|
|
13
|
+
addBatchObservableCallback: vi.fn(),
|
|
14
|
+
countStaleEnrolledRunnerInstances: vi.fn(),
|
|
15
|
+
createObservableGauge: vi.fn((name: string) => gaugeByName[name as keyof typeof gaugeByName]),
|
|
16
|
+
gauges,
|
|
17
|
+
getMeter: vi.fn(),
|
|
18
|
+
getJobExecutionQueueDepth: vi.fn(),
|
|
19
|
+
getServiceMetricsProvider: vi.fn(),
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
vi.mock('@shipfox/node-opentelemetry', () => ({
|
|
24
|
+
getServiceMetricsProvider: mocks.getServiceMetricsProvider,
|
|
25
|
+
}));
|
|
26
|
+
vi.mock('#config.js', () => ({
|
|
27
|
+
config: {RUNNER_STALE_PROVISIONED_RUNNER_THRESHOLD_SECONDS: 300},
|
|
28
|
+
}));
|
|
29
|
+
vi.mock('#db/job-executions.js', () => ({
|
|
30
|
+
getJobExecutionQueueDepth: mocks.getJobExecutionQueueDepth,
|
|
31
|
+
}));
|
|
32
|
+
vi.mock('#db/runner-instances.js', () => ({
|
|
33
|
+
countStaleEnrolledRunnerInstances: mocks.countStaleEnrolledRunnerInstances,
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
let registerRunnersServiceMetrics: typeof import('./service.js').registerRunnersServiceMetrics;
|
|
37
|
+
|
|
38
|
+
beforeAll(async () => {
|
|
39
|
+
// This package intentionally runs with isolate: false. Reset the shared module cache before
|
|
40
|
+
// importing the service so a previous file's real OpenTelemetry module cannot bypass this mock.
|
|
41
|
+
vi.resetModules();
|
|
42
|
+
({registerRunnersServiceMetrics} = await import('./service.js'));
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('registerRunnersServiceMetrics', () => {
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
mocks.addBatchObservableCallback.mockReset();
|
|
48
|
+
mocks.countStaleEnrolledRunnerInstances.mockReset();
|
|
49
|
+
mocks.createObservableGauge.mockClear();
|
|
50
|
+
mocks.getJobExecutionQueueDepth.mockReset();
|
|
51
|
+
mocks.getMeter.mockReset();
|
|
52
|
+
mocks.getServiceMetricsProvider.mockReset();
|
|
53
|
+
mocks.getJobExecutionQueueDepth.mockResolvedValue({
|
|
54
|
+
pendingJobExecutions: 0,
|
|
55
|
+
runningJobExecutions: 0,
|
|
56
|
+
});
|
|
57
|
+
mocks.getMeter.mockReturnValue({
|
|
58
|
+
createObservableGauge: mocks.createObservableGauge,
|
|
59
|
+
addBatchObservableCallback: mocks.addBatchObservableCallback,
|
|
60
|
+
});
|
|
61
|
+
mocks.getServiceMetricsProvider.mockReturnValue({getMeter: mocks.getMeter});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('observes enrolled runners without recent reports after the grace window', async () => {
|
|
65
|
+
mocks.countStaleEnrolledRunnerInstances.mockResolvedValue(2);
|
|
66
|
+
|
|
67
|
+
registerRunnersServiceMetrics();
|
|
68
|
+
const callback = mocks.addBatchObservableCallback.mock.calls[0]?.[0];
|
|
69
|
+
if (typeof callback !== 'function') throw new Error('Expected metrics callback');
|
|
70
|
+
const observer = {observe: vi.fn()};
|
|
71
|
+
|
|
72
|
+
await callback(observer);
|
|
73
|
+
|
|
74
|
+
expect(mocks.createObservableGauge).toHaveBeenCalledWith(
|
|
75
|
+
'runners_enrolled_without_recent_report',
|
|
76
|
+
{
|
|
77
|
+
description:
|
|
78
|
+
'Running enrolled runners with a live control session, no workspace or runner session, and no recent provisioner report after the stale-runner grace window',
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
expect(mocks.countStaleEnrolledRunnerInstances).toHaveBeenCalledWith({graceSeconds: 300});
|
|
82
|
+
expect(observer.observe).toHaveBeenCalledWith(
|
|
83
|
+
mocks.gauges.enrolledRunnersWithoutRecentReport,
|
|
84
|
+
2,
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('keeps queue gauges observable when the enrolled-runner query fails', async () => {
|
|
89
|
+
mocks.getJobExecutionQueueDepth.mockResolvedValue({
|
|
90
|
+
pendingJobExecutions: 3,
|
|
91
|
+
runningJobExecutions: 4,
|
|
92
|
+
});
|
|
93
|
+
mocks.countStaleEnrolledRunnerInstances.mockRejectedValue(new Error('database unavailable'));
|
|
94
|
+
|
|
95
|
+
registerRunnersServiceMetrics();
|
|
96
|
+
const callback = mocks.addBatchObservableCallback.mock.calls[0]?.[0];
|
|
97
|
+
if (typeof callback !== 'function') throw new Error('Expected metrics callback');
|
|
98
|
+
const observer = {observe: vi.fn()};
|
|
99
|
+
|
|
100
|
+
await callback(observer);
|
|
101
|
+
|
|
102
|
+
expect(observer.observe).toHaveBeenCalledWith(mocks.gauges.pendingJobExecutions, 3);
|
|
103
|
+
expect(observer.observe).toHaveBeenCalledWith(mocks.gauges.runningJobExecutions, 4);
|
|
104
|
+
expect(observer.observe).toHaveBeenCalledTimes(2);
|
|
105
|
+
});
|
|
106
|
+
});
|
package/src/metrics/service.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {getServiceMetricsProvider} from '@shipfox/node-opentelemetry';
|
|
2
|
+
import {config} from '#config.js';
|
|
2
3
|
import {getJobExecutionQueueDepth} from '#db/job-executions.js';
|
|
4
|
+
import {countStaleEnrolledRunnerInstances} from '#db/runner-instances.js';
|
|
3
5
|
|
|
4
6
|
export function registerRunnersServiceMetrics(): void {
|
|
5
7
|
const meter = getServiceMetricsProvider().getMeter('runners');
|
|
@@ -10,13 +12,31 @@ export function registerRunnersServiceMetrics(): void {
|
|
|
10
12
|
const runningJobExecutions = meter.createObservableGauge('runners_running_job_executions', {
|
|
11
13
|
description: 'Job executions currently claimed by a runner and in progress',
|
|
12
14
|
});
|
|
15
|
+
const enrolledRunnersWithoutRecentReport = meter.createObservableGauge(
|
|
16
|
+
'runners_enrolled_without_recent_report',
|
|
17
|
+
{
|
|
18
|
+
description:
|
|
19
|
+
'Running enrolled runners with a live control session, no workspace or runner session, and no recent provisioner report after the stale-runner grace window',
|
|
20
|
+
},
|
|
21
|
+
);
|
|
13
22
|
|
|
14
23
|
meter.addBatchObservableCallback(
|
|
15
24
|
async (observer) => {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
25
|
+
const [depthResult, staleEnrolledRunnerCountResult] = await Promise.allSettled([
|
|
26
|
+
getJobExecutionQueueDepth(),
|
|
27
|
+
countStaleEnrolledRunnerInstances({
|
|
28
|
+
graceSeconds: config.RUNNER_STALE_PROVISIONED_RUNNER_THRESHOLD_SECONDS,
|
|
29
|
+
}),
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
if (depthResult.status === 'fulfilled') {
|
|
33
|
+
observer.observe(pendingJobExecutions, depthResult.value.pendingJobExecutions);
|
|
34
|
+
observer.observe(runningJobExecutions, depthResult.value.runningJobExecutions);
|
|
35
|
+
}
|
|
36
|
+
if (staleEnrolledRunnerCountResult.status === 'fulfilled') {
|
|
37
|
+
observer.observe(enrolledRunnersWithoutRecentReport, staleEnrolledRunnerCountResult.value);
|
|
38
|
+
}
|
|
19
39
|
},
|
|
20
|
-
[pendingJobExecutions, runningJobExecutions],
|
|
40
|
+
[pendingJobExecutions, runningJobExecutions, enrolledRunnersWithoutRecentReport],
|
|
21
41
|
);
|
|
22
42
|
}
|
|
@@ -17,6 +17,9 @@ export function toPollDemandResponseDto(result: PollDemandResult): PollDemandRes
|
|
|
17
17
|
count: reservation.count,
|
|
18
18
|
expires_at: reservation.expiresAt.toISOString(),
|
|
19
19
|
})),
|
|
20
|
+
...(result.newlyReservedCount !== undefined
|
|
21
|
+
? {newly_reserved_count: result.newlyReservedCount}
|
|
22
|
+
: {}),
|
|
20
23
|
terminate_provider_runner_ids: result.terminateRunnerInstanceIds,
|
|
21
24
|
};
|
|
22
25
|
}
|
|
@@ -14,10 +14,14 @@ export function toReportRunnerInstancesResponseDto(result: ReportRunnerInstances
|
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export function toReconcileRunnerInstancesResponseDto(
|
|
17
|
+
export function toReconcileRunnerInstancesResponseDto(
|
|
18
|
+
result: ReconcileRunnerInstancesResult,
|
|
19
|
+
options: {includeIntendedReservationId: boolean},
|
|
20
|
+
): {
|
|
18
21
|
runners: Array<{
|
|
19
22
|
provider_runner_id: string;
|
|
20
23
|
state: ReconcileRunnerInstancesResult['runners'][number]['state'];
|
|
24
|
+
intended_reservation_id?: string | null;
|
|
21
25
|
reservation_id: string | null;
|
|
22
26
|
runner_session_id: string | null;
|
|
23
27
|
bound_job: {
|
|
@@ -34,6 +38,9 @@ export function toReconcileRunnerInstancesResponseDto(result: ReconcileRunnerIns
|
|
|
34
38
|
runners: result.runners.map((runner) => ({
|
|
35
39
|
provider_runner_id: runner.providerRunnerId,
|
|
36
40
|
state: runner.state,
|
|
41
|
+
...(options.includeIntendedReservationId
|
|
42
|
+
? {intended_reservation_id: runner.intendedReservationId}
|
|
43
|
+
: {}),
|
|
37
44
|
reservation_id: runner.reservationId,
|
|
38
45
|
runner_session_id: runner.runnerSessionId,
|
|
39
46
|
bound_job: runner.boundJobExecution
|
|
@@ -2,6 +2,8 @@ import {requireProvisionerContext} from '@shipfox/api-auth-context';
|
|
|
2
2
|
import {
|
|
3
3
|
assignRunnerInstancesBodySchema,
|
|
4
4
|
assignRunnerInstancesResponseSchema,
|
|
5
|
+
RESERVATION_EXPIRED_ERROR_CODE,
|
|
6
|
+
RUNNER_INSTANCE_NOT_ASSIGNABLE_ERROR_CODE,
|
|
5
7
|
} from '@shipfox/api-runners-dto';
|
|
6
8
|
import {ClientError, defineRoute} from '@shipfox/node-fastify';
|
|
7
9
|
import {
|
|
@@ -24,14 +26,16 @@ export const assignRunnerInstancesRoute = defineRoute({
|
|
|
24
26
|
if (error instanceof ReservationNotFoundError)
|
|
25
27
|
throw new ClientError('Reservation not found', 'reservation-not-found', {status: 404});
|
|
26
28
|
if (error instanceof ReservationExpiredError)
|
|
27
|
-
throw new ClientError('Reservation has expired',
|
|
29
|
+
throw new ClientError('Reservation has expired', RESERVATION_EXPIRED_ERROR_CODE, {
|
|
30
|
+
status: 409,
|
|
31
|
+
});
|
|
28
32
|
if (
|
|
29
33
|
error instanceof RunnerInstanceAlreadyAssignedError ||
|
|
30
34
|
error instanceof RunnerInstanceNotAssignableError
|
|
31
35
|
)
|
|
32
36
|
throw new ClientError(
|
|
33
37
|
'Runner instance cannot be assigned',
|
|
34
|
-
|
|
38
|
+
RUNNER_INSTANCE_NOT_ASSIGNABLE_ERROR_CODE,
|
|
35
39
|
{status: 409},
|
|
36
40
|
);
|
|
37
41
|
throw error;
|