@shipfox/api-runners 12.3.0 → 12.4.1
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 +36 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +9 -2
- 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/errors.d.ts +5 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +9 -2
- package/dist/core/errors.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 +116 -20
- 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/job-executions.d.ts.map +1 -1
- package/dist/db/job-executions.js +35 -4
- package/dist/db/job-executions.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 +246 -51
- package/dist/db/reservations.js.map +1 -1
- package/dist/db/runner-assignments.d.ts +25 -1
- package/dist/db/runner-assignments.d.ts.map +1 -1
- package/dist/db/runner-assignments.js +160 -18
- package/dist/db/runner-assignments.js.map +1 -1
- package/dist/db/runner-instances.d.ts +11 -2
- package/dist/db/runner-instances.d.ts.map +1 -1
- package/dist/db/runner-instances.js +167 -24
- 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 +14 -1
- 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 +72 -2
- package/dist/metrics/instance.d.ts.map +1 -1
- package/dist/metrics/instance.js +161 -1
- package/dist/metrics/instance.js.map +1 -1
- package/dist/metrics/service.d.ts.map +1 -1
- package/dist/metrics/service.js +25 -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 +10 -4
- 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 +6 -6
- package/src/config.test.ts +22 -0
- package/src/config.ts +15 -2
- 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/errors.ts +30 -1
- package/src/core/runner-activation.ts +48 -2
- package/src/core/runner-control-sessions.test.ts +135 -3
- package/src/core/runner-control-sessions.ts +141 -19
- 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/job-executions.test.ts +98 -12
- package/src/db/job-executions.ts +52 -4
- package/src/db/reservation-locks.ts +22 -0
- package/src/db/reservations.test.ts +733 -22
- package/src/db/reservations.ts +408 -144
- package/src/db/runner-assignments.test.ts +105 -1
- package/src/db/runner-assignments.ts +245 -25
- package/src/db/runner-instances.test.ts +357 -2
- package/src/db/runner-instances.ts +289 -55
- package/src/db/runner-sessions.ts +19 -1
- 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 +24 -1
- package/src/metrics/instance.test.ts +118 -0
- package/src/metrics/instance.ts +247 -3
- package/src/metrics/service.test.ts +46 -1
- package/src/metrics/service.ts +55 -8
- 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 +14 -2
- package/src/presentation/routes/late-runner-enrollment.test.ts +219 -2
- package/src/presentation/routes/poll-demand.test.ts +56 -0
- 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/tsconfig.build.tsbuildinfo +1 -1
|
@@ -18,6 +18,9 @@ export function toPollDemandResponseDto(result) {
|
|
|
18
18
|
count: reservation.count,
|
|
19
19
|
expires_at: reservation.expiresAt.toISOString()
|
|
20
20
|
})),
|
|
21
|
+
...result.newlyReservedCount !== undefined ? {
|
|
22
|
+
newly_reserved_count: result.newlyReservedCount
|
|
23
|
+
} : {},
|
|
21
24
|
terminate_provider_runner_ids: result.terminateRunnerInstanceIds
|
|
22
25
|
};
|
|
23
26
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/dto/poll-demand.ts"],"sourcesContent":["import type {PollDemandResponseDto} from '@shipfox/api-runners-dto';\nimport type {PollDemandResult} from '#core/demand.js';\n\nexport function toPollDemandResponseDto(result: PollDemandResult): PollDemandResponseDto {\n return {\n stats: result.stats.map((stat) => ({\n ...(stat.workspaceId ? {workspace_id: stat.workspaceId} : {}),\n labels: stat.labels,\n queued: stat.queued,\n reserved: stat.reserved,\n oldest_queued_at: stat.oldestQueuedAt.toISOString(),\n })),\n reservations: result.reservations.map((reservation) => ({\n reservation_id: reservation.reservationId,\n ...(reservation.workspaceId ? {workspace_id: reservation.workspaceId} : {}),\n labels: reservation.labels,\n count: reservation.count,\n expires_at: reservation.expiresAt.toISOString(),\n })),\n terminate_provider_runner_ids: result.terminateRunnerInstanceIds,\n };\n}\n"],"names":["toPollDemandResponseDto","result","stats","map","stat","workspaceId","workspace_id","labels","queued","reserved","oldest_queued_at","oldestQueuedAt","toISOString","reservations","reservation","reservation_id","reservationId","count","expires_at","expiresAt","terminate_provider_runner_ids","terminateRunnerInstanceIds"],"mappings":"AAGA,OAAO,SAASA,wBAAwBC,MAAwB;IAC9D,OAAO;QACLC,OAAOD,OAAOC,KAAK,CAACC,GAAG,CAAC,CAACC,OAAU,CAAA;gBACjC,GAAIA,KAAKC,WAAW,GAAG;oBAACC,cAAcF,KAAKC,WAAW;gBAAA,IAAI,CAAC,CAAC;gBAC5DE,QAAQH,KAAKG,MAAM;gBACnBC,QAAQJ,KAAKI,MAAM;gBACnBC,UAAUL,KAAKK,QAAQ;gBACvBC,kBAAkBN,KAAKO,cAAc,CAACC,WAAW;YACnD,CAAA;QACAC,cAAcZ,OAAOY,YAAY,CAACV,GAAG,CAAC,CAACW,cAAiB,CAAA;gBACtDC,gBAAgBD,YAAYE,aAAa;gBACzC,GAAIF,YAAYT,WAAW,GAAG;oBAACC,cAAcQ,YAAYT,WAAW;gBAAA,IAAI,CAAC,CAAC;gBAC1EE,QAAQO,YAAYP,MAAM;gBAC1BU,OAAOH,YAAYG,KAAK;gBACxBC,YAAYJ,YAAYK,SAAS,CAACP,WAAW;YAC/C,CAAA;
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/dto/poll-demand.ts"],"sourcesContent":["import type {PollDemandResponseDto} from '@shipfox/api-runners-dto';\nimport type {PollDemandResult} from '#core/demand.js';\n\nexport function toPollDemandResponseDto(result: PollDemandResult): PollDemandResponseDto {\n return {\n stats: result.stats.map((stat) => ({\n ...(stat.workspaceId ? {workspace_id: stat.workspaceId} : {}),\n labels: stat.labels,\n queued: stat.queued,\n reserved: stat.reserved,\n oldest_queued_at: stat.oldestQueuedAt.toISOString(),\n })),\n reservations: result.reservations.map((reservation) => ({\n reservation_id: reservation.reservationId,\n ...(reservation.workspaceId ? {workspace_id: reservation.workspaceId} : {}),\n labels: reservation.labels,\n count: reservation.count,\n expires_at: reservation.expiresAt.toISOString(),\n })),\n ...(result.newlyReservedCount !== undefined\n ? {newly_reserved_count: result.newlyReservedCount}\n : {}),\n terminate_provider_runner_ids: result.terminateRunnerInstanceIds,\n };\n}\n"],"names":["toPollDemandResponseDto","result","stats","map","stat","workspaceId","workspace_id","labels","queued","reserved","oldest_queued_at","oldestQueuedAt","toISOString","reservations","reservation","reservation_id","reservationId","count","expires_at","expiresAt","newlyReservedCount","undefined","newly_reserved_count","terminate_provider_runner_ids","terminateRunnerInstanceIds"],"mappings":"AAGA,OAAO,SAASA,wBAAwBC,MAAwB;IAC9D,OAAO;QACLC,OAAOD,OAAOC,KAAK,CAACC,GAAG,CAAC,CAACC,OAAU,CAAA;gBACjC,GAAIA,KAAKC,WAAW,GAAG;oBAACC,cAAcF,KAAKC,WAAW;gBAAA,IAAI,CAAC,CAAC;gBAC5DE,QAAQH,KAAKG,MAAM;gBACnBC,QAAQJ,KAAKI,MAAM;gBACnBC,UAAUL,KAAKK,QAAQ;gBACvBC,kBAAkBN,KAAKO,cAAc,CAACC,WAAW;YACnD,CAAA;QACAC,cAAcZ,OAAOY,YAAY,CAACV,GAAG,CAAC,CAACW,cAAiB,CAAA;gBACtDC,gBAAgBD,YAAYE,aAAa;gBACzC,GAAIF,YAAYT,WAAW,GAAG;oBAACC,cAAcQ,YAAYT,WAAW;gBAAA,IAAI,CAAC,CAAC;gBAC1EE,QAAQO,YAAYP,MAAM;gBAC1BU,OAAOH,YAAYG,KAAK;gBACxBC,YAAYJ,YAAYK,SAAS,CAACP,WAAW;YAC/C,CAAA;QACA,GAAIX,OAAOmB,kBAAkB,KAAKC,YAC9B;YAACC,sBAAsBrB,OAAOmB,kBAAkB;QAAA,IAChD,CAAC,CAAC;QACNG,+BAA+BtB,OAAOuB,0BAA0B;IAClE;AACF"}
|
|
@@ -3,10 +3,13 @@ export declare function toReportRunnerInstancesResponseDto(result: ReportRunnerI
|
|
|
3
3
|
accepted: number;
|
|
4
4
|
reservations_released: number;
|
|
5
5
|
};
|
|
6
|
-
export declare function toReconcileRunnerInstancesResponseDto(result: ReconcileRunnerInstancesResult
|
|
6
|
+
export declare function toReconcileRunnerInstancesResponseDto(result: ReconcileRunnerInstancesResult, options: {
|
|
7
|
+
includeIntendedReservationId: boolean;
|
|
8
|
+
}): {
|
|
7
9
|
runners: Array<{
|
|
8
10
|
provider_runner_id: string;
|
|
9
11
|
state: ReconcileRunnerInstancesResult['runners'][number]['state'];
|
|
12
|
+
intended_reservation_id?: string | null;
|
|
10
13
|
reservation_id: string | null;
|
|
11
14
|
runner_session_id: string | null;
|
|
12
15
|
bound_job: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner-instances.d.ts","sourceRoot":"","sources":["../../../src/presentation/dto/runner-instances.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,8BAA8B,EAC9B,2BAA2B,EAC5B,MAAM,2BAA2B,CAAC;AAEnC,wBAAgB,kCAAkC,CAAC,MAAM,EAAE,2BAA2B,GAAG;IACvF,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB,EAAE,MAAM,CAAC;CAC/B,CAKA;AAED,wBAAgB,qCAAqC,
|
|
1
|
+
{"version":3,"file":"runner-instances.d.ts","sourceRoot":"","sources":["../../../src/presentation/dto/runner-instances.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,8BAA8B,EAC9B,2BAA2B,EAC5B,MAAM,2BAA2B,CAAC;AAEnC,wBAAgB,kCAAkC,CAAC,MAAM,EAAE,2BAA2B,GAAG;IACvF,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB,EAAE,MAAM,CAAC;CAC/B,CAKA;AAED,wBAAgB,qCAAqC,CACnD,MAAM,EAAE,8BAA8B,EACtC,OAAO,EAAE;IAAC,4BAA4B,EAAE,OAAO,CAAA;CAAC,GAC/C;IACD,OAAO,EAAE,KAAK,CAAC;QACb,kBAAkB,EAAE,MAAM,CAAC;QAC3B,KAAK,EAAE,8BAA8B,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC;QAClE,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACxC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;QACjC,SAAS,EAAE;YACT,MAAM,EAAE,MAAM,CAAC;YACf,uBAAuB,EAAE,MAAM,CAAC;YAChC,iBAAiB,EAAE,MAAM,CAAC;YAC1B,yBAAyB,EAAE,MAAM,GAAG,IAAI,CAAC;SAC1C,GAAG,IAAI,CAAC;QACT,cAAc,EAAE,8BAA8B,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC;KACpF,CAAC,CAAC;IACH,qCAAqC,EAAE,MAAM,EAAE,CAAC;CACjD,CAuBA;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG;IACnE,OAAO,EAAE,KAAK,CAAC;QACb,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;QACjC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;QAClC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC;QAC7B,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;QAC7B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;QACvC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;KAClC,CAAC,CAAC;CACJ,CAiBA"}
|
|
@@ -4,11 +4,14 @@ export function toReportRunnerInstancesResponseDto(result) {
|
|
|
4
4
|
reservations_released: result.reservationsReleased
|
|
5
5
|
};
|
|
6
6
|
}
|
|
7
|
-
export function toReconcileRunnerInstancesResponseDto(result) {
|
|
7
|
+
export function toReconcileRunnerInstancesResponseDto(result, options) {
|
|
8
8
|
return {
|
|
9
9
|
runners: result.runners.map((runner)=>({
|
|
10
10
|
provider_runner_id: runner.providerRunnerId,
|
|
11
11
|
state: runner.state,
|
|
12
|
+
...options.includeIntendedReservationId ? {
|
|
13
|
+
intended_reservation_id: runner.intendedReservationId
|
|
14
|
+
} : {},
|
|
12
15
|
reservation_id: runner.reservationId,
|
|
13
16
|
runner_session_id: runner.runnerSessionId,
|
|
14
17
|
bound_job: runner.boundJobExecution ? {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/dto/runner-instances.ts"],"sourcesContent":["import type {\n ActiveRunner,\n ReconcileRunnerInstancesResult,\n ReportRunnerInstancesResult,\n} from '#core/runner-instances.js';\n\nexport function toReportRunnerInstancesResponseDto(result: ReportRunnerInstancesResult): {\n accepted: number;\n reservations_released: number;\n} {\n return {\n accepted: result.accepted,\n reservations_released: result.reservationsReleased,\n };\n}\n\nexport function toReconcileRunnerInstancesResponseDto(result: ReconcileRunnerInstancesResult): {\n runners: Array<{\n provider_runner_id: string;\n state: ReconcileRunnerInstancesResult['runners'][number]['state'];\n reservation_id: string | null;\n runner_session_id: string | null;\n bound_job: {\n job_id: string;\n workflow_run_attempt_id: string;\n last_heartbeat_at: string;\n cancellation_requested_at: string | null;\n } | null;\n desired_intent: ReconcileRunnerInstancesResult['runners'][number]['desiredIntent'];\n }>;\n terminated_absent_provider_runner_ids: string[];\n} {\n return {\n runners: result.runners.map((runner) => ({\n provider_runner_id: runner.providerRunnerId,\n state: runner.state,\n reservation_id: runner.reservationId,\n runner_session_id: runner.runnerSessionId,\n bound_job: runner.boundJobExecution\n ? {\n job_id: runner.boundJobExecution.jobId,\n workflow_run_attempt_id: runner.boundJobExecution.workflowRunAttemptId,\n last_heartbeat_at: runner.boundJobExecution.lastHeartbeatAt.toISOString(),\n cancellation_requested_at:\n runner.boundJobExecution.cancellationRequestedAt?.toISOString() ?? null,\n }\n : null,\n desired_intent: runner.desiredIntent,\n })),\n terminated_absent_provider_runner_ids: result.terminatedAbsentRunnerInstanceIds,\n };\n}\n\nexport function toActiveRunnersResponseDto(runners: ActiveRunner[]): {\n runners: Array<{\n runner_session_id: string | null;\n provider_runner_id: string | null;\n provisioner_id: string | null;\n state: ActiveRunner['state'];\n labels: string[];\n template_key: string | null;\n provider_kind: string | null;\n job_id: string | null;\n workflow_run_attempt_id: string | null;\n project_id: string | null;\n reported_at: string | null;\n last_heartbeat_at: string | null;\n }>;\n} {\n return {\n runners: runners.map((runner) => ({\n runner_session_id: runner.runnerSessionId,\n provider_runner_id: runner.providerRunnerId,\n provisioner_id: runner.provisionerId,\n state: runner.state,\n labels: runner.labels,\n template_key: runner.templateKey,\n provider_kind: runner.providerKind,\n job_id: runner.jobId,\n workflow_run_attempt_id: runner.workflowRunAttemptId,\n project_id: runner.projectId,\n reported_at: runner.reportedAt?.toISOString() ?? null,\n last_heartbeat_at: runner.lastHeartbeatAt?.toISOString() ?? null,\n })),\n };\n}\n"],"names":["toReportRunnerInstancesResponseDto","result","accepted","reservations_released","reservationsReleased","toReconcileRunnerInstancesResponseDto","runners","map","runner","provider_runner_id","providerRunnerId","state","reservation_id","reservationId","runner_session_id","runnerSessionId","bound_job","boundJobExecution","job_id","jobId","workflow_run_attempt_id","workflowRunAttemptId","last_heartbeat_at","lastHeartbeatAt","toISOString","cancellation_requested_at","cancellationRequestedAt","desired_intent","desiredIntent","terminated_absent_provider_runner_ids","terminatedAbsentRunnerInstanceIds","toActiveRunnersResponseDto","provisioner_id","provisionerId","labels","template_key","templateKey","provider_kind","providerKind","project_id","projectId","reported_at","reportedAt"],"mappings":"AAMA,OAAO,SAASA,mCAAmCC,MAAmC;IAIpF,OAAO;QACLC,UAAUD,OAAOC,QAAQ;QACzBC,uBAAuBF,OAAOG,oBAAoB;IACpD;AACF;AAEA,OAAO,SAASC,
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/dto/runner-instances.ts"],"sourcesContent":["import type {\n ActiveRunner,\n ReconcileRunnerInstancesResult,\n ReportRunnerInstancesResult,\n} from '#core/runner-instances.js';\n\nexport function toReportRunnerInstancesResponseDto(result: ReportRunnerInstancesResult): {\n accepted: number;\n reservations_released: number;\n} {\n return {\n accepted: result.accepted,\n reservations_released: result.reservationsReleased,\n };\n}\n\nexport function toReconcileRunnerInstancesResponseDto(\n result: ReconcileRunnerInstancesResult,\n options: {includeIntendedReservationId: boolean},\n): {\n runners: Array<{\n provider_runner_id: string;\n state: ReconcileRunnerInstancesResult['runners'][number]['state'];\n intended_reservation_id?: string | null;\n reservation_id: string | null;\n runner_session_id: string | null;\n bound_job: {\n job_id: string;\n workflow_run_attempt_id: string;\n last_heartbeat_at: string;\n cancellation_requested_at: string | null;\n } | null;\n desired_intent: ReconcileRunnerInstancesResult['runners'][number]['desiredIntent'];\n }>;\n terminated_absent_provider_runner_ids: string[];\n} {\n return {\n runners: result.runners.map((runner) => ({\n provider_runner_id: runner.providerRunnerId,\n state: runner.state,\n ...(options.includeIntendedReservationId\n ? {intended_reservation_id: runner.intendedReservationId}\n : {}),\n reservation_id: runner.reservationId,\n runner_session_id: runner.runnerSessionId,\n bound_job: runner.boundJobExecution\n ? {\n job_id: runner.boundJobExecution.jobId,\n workflow_run_attempt_id: runner.boundJobExecution.workflowRunAttemptId,\n last_heartbeat_at: runner.boundJobExecution.lastHeartbeatAt.toISOString(),\n cancellation_requested_at:\n runner.boundJobExecution.cancellationRequestedAt?.toISOString() ?? null,\n }\n : null,\n desired_intent: runner.desiredIntent,\n })),\n terminated_absent_provider_runner_ids: result.terminatedAbsentRunnerInstanceIds,\n };\n}\n\nexport function toActiveRunnersResponseDto(runners: ActiveRunner[]): {\n runners: Array<{\n runner_session_id: string | null;\n provider_runner_id: string | null;\n provisioner_id: string | null;\n state: ActiveRunner['state'];\n labels: string[];\n template_key: string | null;\n provider_kind: string | null;\n job_id: string | null;\n workflow_run_attempt_id: string | null;\n project_id: string | null;\n reported_at: string | null;\n last_heartbeat_at: string | null;\n }>;\n} {\n return {\n runners: runners.map((runner) => ({\n runner_session_id: runner.runnerSessionId,\n provider_runner_id: runner.providerRunnerId,\n provisioner_id: runner.provisionerId,\n state: runner.state,\n labels: runner.labels,\n template_key: runner.templateKey,\n provider_kind: runner.providerKind,\n job_id: runner.jobId,\n workflow_run_attempt_id: runner.workflowRunAttemptId,\n project_id: runner.projectId,\n reported_at: runner.reportedAt?.toISOString() ?? null,\n last_heartbeat_at: runner.lastHeartbeatAt?.toISOString() ?? null,\n })),\n };\n}\n"],"names":["toReportRunnerInstancesResponseDto","result","accepted","reservations_released","reservationsReleased","toReconcileRunnerInstancesResponseDto","options","runners","map","runner","provider_runner_id","providerRunnerId","state","includeIntendedReservationId","intended_reservation_id","intendedReservationId","reservation_id","reservationId","runner_session_id","runnerSessionId","bound_job","boundJobExecution","job_id","jobId","workflow_run_attempt_id","workflowRunAttemptId","last_heartbeat_at","lastHeartbeatAt","toISOString","cancellation_requested_at","cancellationRequestedAt","desired_intent","desiredIntent","terminated_absent_provider_runner_ids","terminatedAbsentRunnerInstanceIds","toActiveRunnersResponseDto","provisioner_id","provisionerId","labels","template_key","templateKey","provider_kind","providerKind","project_id","projectId","reported_at","reportedAt"],"mappings":"AAMA,OAAO,SAASA,mCAAmCC,MAAmC;IAIpF,OAAO;QACLC,UAAUD,OAAOC,QAAQ;QACzBC,uBAAuBF,OAAOG,oBAAoB;IACpD;AACF;AAEA,OAAO,SAASC,sCACdJ,MAAsC,EACtCK,OAAgD;IAkBhD,OAAO;QACLC,SAASN,OAAOM,OAAO,CAACC,GAAG,CAAC,CAACC,SAAY,CAAA;gBACvCC,oBAAoBD,OAAOE,gBAAgB;gBAC3CC,OAAOH,OAAOG,KAAK;gBACnB,GAAIN,QAAQO,4BAA4B,GACpC;oBAACC,yBAAyBL,OAAOM,qBAAqB;gBAAA,IACtD,CAAC,CAAC;gBACNC,gBAAgBP,OAAOQ,aAAa;gBACpCC,mBAAmBT,OAAOU,eAAe;gBACzCC,WAAWX,OAAOY,iBAAiB,GAC/B;oBACEC,QAAQb,OAAOY,iBAAiB,CAACE,KAAK;oBACtCC,yBAAyBf,OAAOY,iBAAiB,CAACI,oBAAoB;oBACtEC,mBAAmBjB,OAAOY,iBAAiB,CAACM,eAAe,CAACC,WAAW;oBACvEC,2BACEpB,OAAOY,iBAAiB,CAACS,uBAAuB,EAAEF,iBAAiB;gBACvE,IACA;gBACJG,gBAAgBtB,OAAOuB,aAAa;YACtC,CAAA;QACAC,uCAAuChC,OAAOiC,iCAAiC;IACjF;AACF;AAEA,OAAO,SAASC,2BAA2B5B,OAAuB;IAgBhE,OAAO;QACLA,SAASA,QAAQC,GAAG,CAAC,CAACC,SAAY,CAAA;gBAChCS,mBAAmBT,OAAOU,eAAe;gBACzCT,oBAAoBD,OAAOE,gBAAgB;gBAC3CyB,gBAAgB3B,OAAO4B,aAAa;gBACpCzB,OAAOH,OAAOG,KAAK;gBACnB0B,QAAQ7B,OAAO6B,MAAM;gBACrBC,cAAc9B,OAAO+B,WAAW;gBAChCC,eAAehC,OAAOiC,YAAY;gBAClCpB,QAAQb,OAAOc,KAAK;gBACpBC,yBAAyBf,OAAOgB,oBAAoB;gBACpDkB,YAAYlC,OAAOmC,SAAS;gBAC5BC,aAAapC,OAAOqC,UAAU,EAAElB,iBAAiB;gBACjDF,mBAAmBjB,OAAOkB,eAAe,EAAEC,iBAAiB;YAC9D,CAAA;IACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assign-runner-instances.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/assign-runner-instances.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"assign-runner-instances.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/assign-runner-instances.ts"],"names":[],"mappings":"AAkBA,eAAO,MAAM,0BAA0B,iDA0CrC,CAAC"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { requireProvisionerContext } from '@shipfox/api-auth-context';
|
|
2
|
-
import { assignRunnerInstancesBodySchema, assignRunnerInstancesResponseSchema } from '@shipfox/api-runners-dto';
|
|
2
|
+
import { assignRunnerInstancesBodySchema, assignRunnerInstancesResponseSchema, RESERVATION_EXPIRED_ERROR_CODE, RUNNER_INSTANCE_NOT_ASSIGNABLE_ERROR_CODE } from '@shipfox/api-runners-dto';
|
|
3
3
|
import { ClientError, defineRoute } from '@shipfox/node-fastify';
|
|
4
|
-
import { ReservationExpiredError, ReservationNotFoundError, RunnerInstanceAlreadyAssignedError, RunnerInstanceNotAssignableError } from '#core/errors.js';
|
|
4
|
+
import { getRunnerAssignmentRejectionReason, ReservationExpiredError, ReservationNotFoundError, RunnerInstanceAlreadyAssignedError, RunnerInstanceNotAssignableError } from '#core/errors.js';
|
|
5
5
|
import { assignRunnerInstances } from '#core/runner-assignments.js';
|
|
6
|
+
import { recordProviderRunnerAssignmentRejected } from '#metrics/instance.js';
|
|
6
7
|
export const assignRunnerInstancesRoute = defineRoute({
|
|
7
8
|
method: 'POST',
|
|
8
9
|
path: '/runner-instances/assignments',
|
|
@@ -14,13 +15,18 @@ export const assignRunnerInstancesRoute = defineRoute({
|
|
|
14
15
|
}
|
|
15
16
|
},
|
|
16
17
|
errorHandler: (error)=>{
|
|
18
|
+
const rejectionReason = getRunnerAssignmentRejectionReason(error);
|
|
19
|
+
if (rejectionReason) recordProviderRunnerAssignmentRejected({
|
|
20
|
+
reason: rejectionReason,
|
|
21
|
+
surface: 'provisioner'
|
|
22
|
+
});
|
|
17
23
|
if (error instanceof ReservationNotFoundError) throw new ClientError('Reservation not found', 'reservation-not-found', {
|
|
18
24
|
status: 404
|
|
19
25
|
});
|
|
20
|
-
if (error instanceof ReservationExpiredError) throw new ClientError('Reservation has expired',
|
|
26
|
+
if (error instanceof ReservationExpiredError) throw new ClientError('Reservation has expired', RESERVATION_EXPIRED_ERROR_CODE, {
|
|
21
27
|
status: 409
|
|
22
28
|
});
|
|
23
|
-
if (error instanceof RunnerInstanceAlreadyAssignedError || error instanceof RunnerInstanceNotAssignableError) throw new ClientError('Runner instance cannot be assigned',
|
|
29
|
+
if (error instanceof RunnerInstanceAlreadyAssignedError || error instanceof RunnerInstanceNotAssignableError) throw new ClientError('Runner instance cannot be assigned', RUNNER_INSTANCE_NOT_ASSIGNABLE_ERROR_CODE, {
|
|
24
30
|
status: 409
|
|
25
31
|
});
|
|
26
32
|
throw error;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/routes/assign-runner-instances.ts"],"sourcesContent":["import {requireProvisionerContext} from '@shipfox/api-auth-context';\nimport {\n assignRunnerInstancesBodySchema,\n assignRunnerInstancesResponseSchema,\n} from '@shipfox/api-runners-dto';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {\n ReservationExpiredError,\n ReservationNotFoundError,\n RunnerInstanceAlreadyAssignedError,\n RunnerInstanceNotAssignableError,\n} from '#core/errors.js';\nimport {assignRunnerInstances} from '#core/runner-assignments.js';\n\nexport const assignRunnerInstancesRoute = defineRoute({\n method: 'POST',\n path: '/runner-instances/assignments',\n description: 'Assign enrolled runner instances to an owned demand reservation',\n schema: {\n body: assignRunnerInstancesBodySchema,\n response: {200: assignRunnerInstancesResponseSchema},\n },\n errorHandler: (error) => {\n if (error instanceof ReservationNotFoundError)\n throw new ClientError('Reservation not found', 'reservation-not-found', {status: 404});\n if (error instanceof ReservationExpiredError)\n throw new ClientError('Reservation has expired',
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/routes/assign-runner-instances.ts"],"sourcesContent":["import {requireProvisionerContext} from '@shipfox/api-auth-context';\nimport {\n assignRunnerInstancesBodySchema,\n assignRunnerInstancesResponseSchema,\n RESERVATION_EXPIRED_ERROR_CODE,\n RUNNER_INSTANCE_NOT_ASSIGNABLE_ERROR_CODE,\n} from '@shipfox/api-runners-dto';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {\n getRunnerAssignmentRejectionReason,\n ReservationExpiredError,\n ReservationNotFoundError,\n RunnerInstanceAlreadyAssignedError,\n RunnerInstanceNotAssignableError,\n} from '#core/errors.js';\nimport {assignRunnerInstances} from '#core/runner-assignments.js';\nimport {recordProviderRunnerAssignmentRejected} from '#metrics/instance.js';\n\nexport const assignRunnerInstancesRoute = defineRoute({\n method: 'POST',\n path: '/runner-instances/assignments',\n description: 'Assign enrolled runner instances to an owned demand reservation',\n schema: {\n body: assignRunnerInstancesBodySchema,\n response: {200: assignRunnerInstancesResponseSchema},\n },\n errorHandler: (error) => {\n const rejectionReason = getRunnerAssignmentRejectionReason(error);\n if (rejectionReason)\n recordProviderRunnerAssignmentRejected({\n reason: rejectionReason,\n surface: 'provisioner',\n });\n if (error instanceof ReservationNotFoundError)\n throw new ClientError('Reservation not found', 'reservation-not-found', {status: 404});\n if (error instanceof ReservationExpiredError)\n throw new ClientError('Reservation has expired', RESERVATION_EXPIRED_ERROR_CODE, {\n status: 409,\n });\n if (\n error instanceof RunnerInstanceAlreadyAssignedError ||\n error instanceof RunnerInstanceNotAssignableError\n )\n throw new ClientError(\n 'Runner instance cannot be assigned',\n RUNNER_INSTANCE_NOT_ASSIGNABLE_ERROR_CODE,\n {status: 409},\n );\n throw error;\n },\n handler: async (request) => {\n const {provisionerTokenId} = requireProvisionerContext(request);\n return {\n runner_instance_ids: await assignRunnerInstances({\n provisionerId: provisionerTokenId,\n reservationId: request.body.reservation_id,\n runnerInstanceIds: request.body.runner_instance_ids,\n }),\n };\n },\n});\n"],"names":["requireProvisionerContext","assignRunnerInstancesBodySchema","assignRunnerInstancesResponseSchema","RESERVATION_EXPIRED_ERROR_CODE","RUNNER_INSTANCE_NOT_ASSIGNABLE_ERROR_CODE","ClientError","defineRoute","getRunnerAssignmentRejectionReason","ReservationExpiredError","ReservationNotFoundError","RunnerInstanceAlreadyAssignedError","RunnerInstanceNotAssignableError","assignRunnerInstances","recordProviderRunnerAssignmentRejected","assignRunnerInstancesRoute","method","path","description","schema","body","response","errorHandler","error","rejectionReason","reason","surface","status","handler","request","provisionerTokenId","runner_instance_ids","provisionerId","reservationId","reservation_id","runnerInstanceIds"],"mappings":"AAAA,SAAQA,yBAAyB,QAAO,4BAA4B;AACpE,SACEC,+BAA+B,EAC/BC,mCAAmC,EACnCC,8BAA8B,EAC9BC,yCAAyC,QACpC,2BAA2B;AAClC,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SACEC,kCAAkC,EAClCC,uBAAuB,EACvBC,wBAAwB,EACxBC,kCAAkC,EAClCC,gCAAgC,QAC3B,kBAAkB;AACzB,SAAQC,qBAAqB,QAAO,8BAA8B;AAClE,SAAQC,sCAAsC,QAAO,uBAAuB;AAE5E,OAAO,MAAMC,6BAA6BR,YAAY;IACpDS,QAAQ;IACRC,MAAM;IACNC,aAAa;IACbC,QAAQ;QACNC,MAAMlB;QACNmB,UAAU;YAAC,KAAKlB;QAAmC;IACrD;IACAmB,cAAc,CAACC;QACb,MAAMC,kBAAkBhB,mCAAmCe;QAC3D,IAAIC,iBACFV,uCAAuC;YACrCW,QAAQD;YACRE,SAAS;QACX;QACF,IAAIH,iBAAiBb,0BACnB,MAAM,IAAIJ,YAAY,yBAAyB,yBAAyB;YAACqB,QAAQ;QAAG;QACtF,IAAIJ,iBAAiBd,yBACnB,MAAM,IAAIH,YAAY,2BAA2BF,gCAAgC;YAC/EuB,QAAQ;QACV;QACF,IACEJ,iBAAiBZ,sCACjBY,iBAAiBX,kCAEjB,MAAM,IAAIN,YACR,sCACAD,2CACA;YAACsB,QAAQ;QAAG;QAEhB,MAAMJ;IACR;IACAK,SAAS,OAAOC;QACd,MAAM,EAACC,kBAAkB,EAAC,GAAG7B,0BAA0B4B;QACvD,OAAO;YACLE,qBAAqB,MAAMlB,sBAAsB;gBAC/CmB,eAAeF;gBACfG,eAAeJ,QAAQT,IAAI,CAACc,cAAc;gBAC1CC,mBAAmBN,QAAQT,IAAI,CAACW,mBAAmB;YACrD;QACF;IACF;AACF,GAAG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"poll-demand.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/poll-demand.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAC,0BAA0B,EAAC,MAAM,+BAA+B,CAAC;AAsB9E,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,0BAA+B,
|
|
1
|
+
{"version":3,"file":"poll-demand.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/poll-demand.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAC,0BAA0B,EAAC,MAAM,+BAA+B,CAAC;AAsB9E,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,0BAA+B,mDAiG7E;AAED,eAAO,MAAM,eAAe,iDAA0B,CAAC"}
|
|
@@ -74,6 +74,7 @@ export function createPollDemandRoute(options = {}) {
|
|
|
74
74
|
provisionerId: provisionerContext.provisionerTokenId,
|
|
75
75
|
maxReservations: request.body.max_reservations,
|
|
76
76
|
ttlSeconds,
|
|
77
|
+
activationGraceSeconds: config.RESERVATION_TTL_SECONDS,
|
|
77
78
|
templates,
|
|
78
79
|
capabilityWindowSeconds: config.PROVISIONER_ACTIVE_WINDOW_SECONDS,
|
|
79
80
|
eligibleWorkspaceIds,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/routes/poll-demand.ts"],"sourcesContent":["import {\n requireProvisionerContext,\n requireWorkspaceProvisionerContext,\n} from '@shipfox/api-auth-context';\nimport type {PollDemandTemplateDto} from '@shipfox/api-runners-dto';\nimport {pollDemandBodySchema, pollDemandResponseSchema} from '@shipfox/api-runners-dto';\nimport {reportError} from '@shipfox/node-error-monitoring';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {config} from '#config.js';\nimport {pollDemand, releaseReservationGrants} from '#core/demand.js';\nimport {sanitizeRunnerLabels} from '#core/runner-labels.js';\nimport {publishWorkspaceProvisionerCapabilitySnapshot} from '#db/provisioner-capability-snapshots.js';\nimport {\n listQueuedDemandWorkspaceIds,\n pollInstallationDemandAndReserve,\n type ReservationGrant,\n type ReservationTemplate,\n} from '#db/reservations.js';\nimport type {CreateRunnersModuleOptions} from '#installation-provisioning.js';\nimport {toPollDemandResponseDto} from '#presentation/dto/index.js';\n\nconst TERMINATE_INTENT_LIMIT = 1000;\n\nfunction toReservationTemplates(\n templates: PollDemandTemplateDto[],\n scope: 'installation' | 'workspace',\n): ReservationTemplate[] {\n return templates.map((template) => ({\n templateKey: template.template_key,\n labels: sanitizeRunnerLabels(template.labels, {\n scope,\n logLevel: 'debug',\n source: 'provisioner template advertisement',\n }),\n availableSlots: template.available_slots,\n starting: template.starting,\n running: template.running,\n }));\n}\n\nexport function createPollDemandRoute(options: CreateRunnersModuleOptions = {}) {\n return defineRoute({\n method: 'POST',\n path: '/demand/poll',\n description: 'Poll aggregate runner demand and reserve capacity for a provisioner',\n schema: {\n body: pollDemandBodySchema,\n response: {\n 200: pollDemandResponseSchema,\n },\n },\n handler: async (request, reply) => {\n const abortController = new AbortController();\n let responseFinished = false;\n let responseReservations: ReservationGrant[] = [];\n reply.raw.on('finish', () => {\n responseFinished = true;\n });\n reply.raw.on('close', () => {\n if (!responseFinished) {\n abortController.abort();\n void releaseReservationGrants(responseReservations).catch((error) => {\n logger().error(\n {err: error, reservationCount: responseReservations.length},\n 'Failed to release reservations after provisioner disconnect',\n );\n reportError(error, {\n boundary: 'runners.cleanup',\n operation: 'release-disconnected-reservations',\n extra: {reservationCount: responseReservations.length},\n });\n });\n }\n });\n const provisionerContext = requireProvisionerContext(request);\n const ttlSeconds = Math.min(\n request.body.reservation_ttl_seconds ?? config.RESERVATION_TTL_SECONDS,\n config.RESERVATION_TTL_MAX_SECONDS,\n );\n if (provisionerContext.scope === 'installation') {\n if (!options.installationProvisioning) {\n throw new ClientError(\n 'Installation provisioner credentials are not accepted on this host',\n 'forbidden',\n {\n status: 403,\n },\n );\n }\n const candidateWorkspaceIds = await listQueuedDemandWorkspaceIds();\n const eligibleWorkspaceIds =\n await options.installationProvisioning.policy.filterEligibleWorkspaceIds(\n candidateWorkspaceIds,\n );\n const templates = toReservationTemplates(request.body.templates, 'installation');\n const result = await pollInstallationDemandAndReserve({\n provisionerId: provisionerContext.provisionerTokenId,\n maxReservations: request.body.max_reservations,\n ttlSeconds,\n templates,\n capabilityWindowSeconds: config.PROVISIONER_ACTIVE_WINDOW_SECONDS,\n eligibleWorkspaceIds,\n signal: abortController.signal,\n onReservations: (reservations) => {\n responseReservations.push(...reservations);\n },\n });\n return toPollDemandResponseDto({\n ...result,\n terminateRunnerInstanceIds: [],\n });\n }\n const {provisionerTokenId, workspaceId} = requireWorkspaceProvisionerContext(request);\n\n const templates = toReservationTemplates(request.body.templates, 'workspace');\n await publishWorkspaceProvisionerCapabilitySnapshot({\n workspaceId,\n provisionerId: provisionerTokenId,\n templates,\n });\n\n const result = await pollDemand({\n workspaceId,\n provisionerId: provisionerTokenId,\n maxReservations: request.body.max_reservations,\n waitSeconds: request.body.wait_seconds,\n ttlSeconds,\n terminateIntentLimit: TERMINATE_INTENT_LIMIT,\n templates,\n signal: abortController.signal,\n });\n responseReservations = result.reservations;\n\n return toPollDemandResponseDto(result);\n },\n });\n}\n\nexport const pollDemandRoute = createPollDemandRoute();\n"],"names":["requireProvisionerContext","requireWorkspaceProvisionerContext","pollDemandBodySchema","pollDemandResponseSchema","reportError","ClientError","defineRoute","logger","config","pollDemand","releaseReservationGrants","sanitizeRunnerLabels","publishWorkspaceProvisionerCapabilitySnapshot","listQueuedDemandWorkspaceIds","pollInstallationDemandAndReserve","toPollDemandResponseDto","TERMINATE_INTENT_LIMIT","toReservationTemplates","templates","scope","map","template","templateKey","template_key","labels","logLevel","source","availableSlots","available_slots","starting","running","createPollDemandRoute","options","method","path","description","schema","body","response","handler","request","reply","abortController","AbortController","responseFinished","responseReservations","raw","on","abort","catch","error","err","reservationCount","length","boundary","operation","extra","provisionerContext","ttlSeconds","Math","min","reservation_ttl_seconds","RESERVATION_TTL_SECONDS","RESERVATION_TTL_MAX_SECONDS","installationProvisioning","status","candidateWorkspaceIds","eligibleWorkspaceIds","policy","filterEligibleWorkspaceIds","result","provisionerId","provisionerTokenId","maxReservations","max_reservations","capabilityWindowSeconds","PROVISIONER_ACTIVE_WINDOW_SECONDS","signal","onReservations","reservations","push","terminateRunnerInstanceIds","workspaceId","waitSeconds","wait_seconds","terminateIntentLimit","pollDemandRoute"],"mappings":"AAAA,SACEA,yBAAyB,EACzBC,kCAAkC,QAC7B,4BAA4B;AAEnC,SAAQC,oBAAoB,EAAEC,wBAAwB,QAAO,2BAA2B;AACxF,SAAQC,WAAW,QAAO,iCAAiC;AAC3D,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SAAQC,MAAM,QAAO,8BAA8B;AACnD,SAAQC,MAAM,QAAO,aAAa;AAClC,SAAQC,UAAU,EAAEC,wBAAwB,QAAO,kBAAkB;AACrE,SAAQC,oBAAoB,QAAO,yBAAyB;AAC5D,SAAQC,6CAA6C,QAAO,0CAA0C;AACtG,SACEC,4BAA4B,EAC5BC,gCAAgC,QAG3B,sBAAsB;AAE7B,SAAQC,uBAAuB,QAAO,6BAA6B;AAEnE,MAAMC,yBAAyB;AAE/B,SAASC,uBACPC,SAAkC,EAClCC,KAAmC;IAEnC,OAAOD,UAAUE,GAAG,CAAC,CAACC,WAAc,CAAA;YAClCC,aAAaD,SAASE,YAAY;YAClCC,QAAQb,qBAAqBU,SAASG,MAAM,EAAE;gBAC5CL;gBACAM,UAAU;gBACVC,QAAQ;YACV;YACAC,gBAAgBN,SAASO,eAAe;YACxCC,UAAUR,SAASQ,QAAQ;YAC3BC,SAAST,SAASS,OAAO;QAC3B,CAAA;AACF;AAEA,OAAO,SAASC,sBAAsBC,UAAsC,CAAC,CAAC;IAC5E,OAAO1B,YAAY;QACjB2B,QAAQ;QACRC,MAAM;QACNC,aAAa;QACbC,QAAQ;YACNC,MAAMnC;YACNoC,UAAU;gBACR,KAAKnC;YACP;QACF;QACAoC,SAAS,OAAOC,SAASC;YACvB,MAAMC,kBAAkB,IAAIC;YAC5B,IAAIC,mBAAmB;YACvB,IAAIC,uBAA2C,EAAE;YACjDJ,MAAMK,GAAG,CAACC,EAAE,CAAC,UAAU;gBACrBH,mBAAmB;YACrB;YACAH,MAAMK,GAAG,CAACC,EAAE,CAAC,SAAS;gBACpB,IAAI,CAACH,kBAAkB;oBACrBF,gBAAgBM,KAAK;oBACrB,KAAKtC,yBAAyBmC,sBAAsBI,KAAK,CAAC,CAACC;wBACzD3C,SAAS2C,KAAK,CACZ;4BAACC,KAAKD;4BAAOE,kBAAkBP,qBAAqBQ,MAAM;wBAAA,GAC1D;wBAEFjD,YAAY8C,OAAO;4BACjBI,UAAU;4BACVC,WAAW;4BACXC,OAAO;gCAACJ,kBAAkBP,qBAAqBQ,MAAM;4BAAA;wBACvD;oBACF;gBACF;YACF;YACA,MAAMI,qBAAqBzD,0BAA0BwC;YACrD,MAAMkB,aAAaC,KAAKC,GAAG,CACzBpB,QAAQH,IAAI,CAACwB,uBAAuB,IAAIrD,OAAOsD,uBAAuB,EACtEtD,OAAOuD,2BAA2B;YAEpC,IAAIN,mBAAmBtC,KAAK,KAAK,gBAAgB;gBAC/C,IAAI,CAACa,QAAQgC,wBAAwB,EAAE;oBACrC,MAAM,IAAI3D,YACR,sEACA,aACA;wBACE4D,QAAQ;oBACV;gBAEJ;gBACA,MAAMC,wBAAwB,MAAMrD;gBACpC,MAAMsD,uBACJ,MAAMnC,QAAQgC,wBAAwB,CAACI,MAAM,CAACC,0BAA0B,CACtEH;gBAEJ,MAAMhD,YAAYD,uBAAuBuB,QAAQH,IAAI,CAACnB,SAAS,EAAE;gBACjE,MAAMoD,SAAS,MAAMxD,iCAAiC;oBACpDyD,eAAed,mBAAmBe,kBAAkB;oBACpDC,iBAAiBjC,QAAQH,IAAI,CAACqC,gBAAgB;oBAC9ChB;
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/routes/poll-demand.ts"],"sourcesContent":["import {\n requireProvisionerContext,\n requireWorkspaceProvisionerContext,\n} from '@shipfox/api-auth-context';\nimport type {PollDemandTemplateDto} from '@shipfox/api-runners-dto';\nimport {pollDemandBodySchema, pollDemandResponseSchema} from '@shipfox/api-runners-dto';\nimport {reportError} from '@shipfox/node-error-monitoring';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {config} from '#config.js';\nimport {pollDemand, releaseReservationGrants} from '#core/demand.js';\nimport {sanitizeRunnerLabels} from '#core/runner-labels.js';\nimport {publishWorkspaceProvisionerCapabilitySnapshot} from '#db/provisioner-capability-snapshots.js';\nimport {\n listQueuedDemandWorkspaceIds,\n pollInstallationDemandAndReserve,\n type ReservationGrant,\n type ReservationTemplate,\n} from '#db/reservations.js';\nimport type {CreateRunnersModuleOptions} from '#installation-provisioning.js';\nimport {toPollDemandResponseDto} from '#presentation/dto/index.js';\n\nconst TERMINATE_INTENT_LIMIT = 1000;\n\nfunction toReservationTemplates(\n templates: PollDemandTemplateDto[],\n scope: 'installation' | 'workspace',\n): ReservationTemplate[] {\n return templates.map((template) => ({\n templateKey: template.template_key,\n labels: sanitizeRunnerLabels(template.labels, {\n scope,\n logLevel: 'debug',\n source: 'provisioner template advertisement',\n }),\n availableSlots: template.available_slots,\n starting: template.starting,\n running: template.running,\n }));\n}\n\nexport function createPollDemandRoute(options: CreateRunnersModuleOptions = {}) {\n return defineRoute({\n method: 'POST',\n path: '/demand/poll',\n description: 'Poll aggregate runner demand and reserve capacity for a provisioner',\n schema: {\n body: pollDemandBodySchema,\n response: {\n 200: pollDemandResponseSchema,\n },\n },\n handler: async (request, reply) => {\n const abortController = new AbortController();\n let responseFinished = false;\n let responseReservations: ReservationGrant[] = [];\n reply.raw.on('finish', () => {\n responseFinished = true;\n });\n reply.raw.on('close', () => {\n if (!responseFinished) {\n abortController.abort();\n void releaseReservationGrants(responseReservations).catch((error) => {\n logger().error(\n {err: error, reservationCount: responseReservations.length},\n 'Failed to release reservations after provisioner disconnect',\n );\n reportError(error, {\n boundary: 'runners.cleanup',\n operation: 'release-disconnected-reservations',\n extra: {reservationCount: responseReservations.length},\n });\n });\n }\n });\n const provisionerContext = requireProvisionerContext(request);\n const ttlSeconds = Math.min(\n request.body.reservation_ttl_seconds ?? config.RESERVATION_TTL_SECONDS,\n config.RESERVATION_TTL_MAX_SECONDS,\n );\n if (provisionerContext.scope === 'installation') {\n if (!options.installationProvisioning) {\n throw new ClientError(\n 'Installation provisioner credentials are not accepted on this host',\n 'forbidden',\n {\n status: 403,\n },\n );\n }\n const candidateWorkspaceIds = await listQueuedDemandWorkspaceIds();\n const eligibleWorkspaceIds =\n await options.installationProvisioning.policy.filterEligibleWorkspaceIds(\n candidateWorkspaceIds,\n );\n const templates = toReservationTemplates(request.body.templates, 'installation');\n const result = await pollInstallationDemandAndReserve({\n provisionerId: provisionerContext.provisionerTokenId,\n maxReservations: request.body.max_reservations,\n ttlSeconds,\n activationGraceSeconds: config.RESERVATION_TTL_SECONDS,\n templates,\n capabilityWindowSeconds: config.PROVISIONER_ACTIVE_WINDOW_SECONDS,\n eligibleWorkspaceIds,\n signal: abortController.signal,\n onReservations: (reservations) => {\n responseReservations.push(...reservations);\n },\n });\n return toPollDemandResponseDto({\n ...result,\n terminateRunnerInstanceIds: [],\n });\n }\n const {provisionerTokenId, workspaceId} = requireWorkspaceProvisionerContext(request);\n\n const templates = toReservationTemplates(request.body.templates, 'workspace');\n await publishWorkspaceProvisionerCapabilitySnapshot({\n workspaceId,\n provisionerId: provisionerTokenId,\n templates,\n });\n\n const result = await pollDemand({\n workspaceId,\n provisionerId: provisionerTokenId,\n maxReservations: request.body.max_reservations,\n waitSeconds: request.body.wait_seconds,\n ttlSeconds,\n terminateIntentLimit: TERMINATE_INTENT_LIMIT,\n templates,\n signal: abortController.signal,\n });\n responseReservations = result.reservations;\n\n return toPollDemandResponseDto(result);\n },\n });\n}\n\nexport const pollDemandRoute = createPollDemandRoute();\n"],"names":["requireProvisionerContext","requireWorkspaceProvisionerContext","pollDemandBodySchema","pollDemandResponseSchema","reportError","ClientError","defineRoute","logger","config","pollDemand","releaseReservationGrants","sanitizeRunnerLabels","publishWorkspaceProvisionerCapabilitySnapshot","listQueuedDemandWorkspaceIds","pollInstallationDemandAndReserve","toPollDemandResponseDto","TERMINATE_INTENT_LIMIT","toReservationTemplates","templates","scope","map","template","templateKey","template_key","labels","logLevel","source","availableSlots","available_slots","starting","running","createPollDemandRoute","options","method","path","description","schema","body","response","handler","request","reply","abortController","AbortController","responseFinished","responseReservations","raw","on","abort","catch","error","err","reservationCount","length","boundary","operation","extra","provisionerContext","ttlSeconds","Math","min","reservation_ttl_seconds","RESERVATION_TTL_SECONDS","RESERVATION_TTL_MAX_SECONDS","installationProvisioning","status","candidateWorkspaceIds","eligibleWorkspaceIds","policy","filterEligibleWorkspaceIds","result","provisionerId","provisionerTokenId","maxReservations","max_reservations","activationGraceSeconds","capabilityWindowSeconds","PROVISIONER_ACTIVE_WINDOW_SECONDS","signal","onReservations","reservations","push","terminateRunnerInstanceIds","workspaceId","waitSeconds","wait_seconds","terminateIntentLimit","pollDemandRoute"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconcile-runner-instances.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/reconcile-runner-instances.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"reconcile-runner-instances.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/reconcile-runner-instances.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,6BAA6B,iDAwBxC,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { requireProvisionerContext } from '@shipfox/api-auth-context';
|
|
2
|
-
import { reconcileRunnerInstancesBodySchema, reconcileRunnerInstancesResponseSchema } from '@shipfox/api-runners-dto';
|
|
2
|
+
import { RECONCILE_RUNNER_INSTANCES_INTENDED_RESERVATION_HEADER, RECONCILE_RUNNER_INSTANCES_INTENDED_RESERVATION_HEADER_VALUE, reconcileRunnerInstancesBodySchema, reconcileRunnerInstancesResponseSchema } from '@shipfox/api-runners-dto';
|
|
3
3
|
import { defineRoute } from '@shipfox/node-fastify';
|
|
4
4
|
import { reconcileRunnerInstances } from '#core/index.js';
|
|
5
5
|
import { toReconcileRunnerInstancesResponseDto } from '#presentation/dto/index.js';
|
|
@@ -20,7 +20,9 @@ export const reconcileRunnerInstancesRoute = defineRoute({
|
|
|
20
20
|
provisionerId: context.provisionerTokenId,
|
|
21
21
|
observedRunnerInstanceIds: request.body.observed_provider_runner_ids
|
|
22
22
|
});
|
|
23
|
-
return toReconcileRunnerInstancesResponseDto(result
|
|
23
|
+
return toReconcileRunnerInstancesResponseDto(result, {
|
|
24
|
+
includeIntendedReservationId: request.headers[RECONCILE_RUNNER_INSTANCES_INTENDED_RESERVATION_HEADER] === RECONCILE_RUNNER_INSTANCES_INTENDED_RESERVATION_HEADER_VALUE
|
|
25
|
+
});
|
|
24
26
|
}
|
|
25
27
|
});
|
|
26
28
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/routes/reconcile-runner-instances.ts"],"sourcesContent":["import {requireProvisionerContext} from '@shipfox/api-auth-context';\nimport {\n reconcileRunnerInstancesBodySchema,\n reconcileRunnerInstancesResponseSchema,\n} from '@shipfox/api-runners-dto';\nimport {defineRoute} from '@shipfox/node-fastify';\nimport {reconcileRunnerInstances} from '#core/index.js';\nimport {toReconcileRunnerInstancesResponseDto} from '#presentation/dto/index.js';\n\nexport const reconcileRunnerInstancesRoute = defineRoute({\n method: 'POST',\n path: '/runner-instances/reconcile',\n description: 'Reconcile provisioned runner state for a provisioner',\n schema: {\n body: reconcileRunnerInstancesBodySchema,\n response: {\n 200: reconcileRunnerInstancesResponseSchema,\n },\n },\n handler: async (request) => {\n const context = requireProvisionerContext(request);\n const result = await reconcileRunnerInstances({\n workspaceId: context.scope === 'workspace' ? context.workspaceId : null,\n provisionerId: context.provisionerTokenId,\n observedRunnerInstanceIds: request.body.observed_provider_runner_ids,\n });\n\n return toReconcileRunnerInstancesResponseDto(result);\n },\n});\n"],"names":["requireProvisionerContext","reconcileRunnerInstancesBodySchema","reconcileRunnerInstancesResponseSchema","defineRoute","reconcileRunnerInstances","toReconcileRunnerInstancesResponseDto","reconcileRunnerInstancesRoute","method","path","description","schema","body","response","handler","request","context","result","workspaceId","scope","provisionerId","provisionerTokenId","observedRunnerInstanceIds","observed_provider_runner_ids"],"mappings":"AAAA,SAAQA,yBAAyB,QAAO,4BAA4B;AACpE,SACEC,kCAAkC,EAClCC,sCAAsC,QACjC,2BAA2B;AAClC,SAAQC,WAAW,QAAO,wBAAwB;AAClD,SAAQC,wBAAwB,QAAO,iBAAiB;AACxD,SAAQC,qCAAqC,QAAO,6BAA6B;AAEjF,OAAO,MAAMC,gCAAgCH,YAAY;IACvDI,QAAQ;IACRC,MAAM;IACNC,aAAa;IACbC,QAAQ;QACNC,MAAMV;QACNW,UAAU;YACR,KAAKV;QACP;IACF;IACAW,SAAS,OAAOC;QACd,MAAMC,
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/routes/reconcile-runner-instances.ts"],"sourcesContent":["import {requireProvisionerContext} from '@shipfox/api-auth-context';\nimport {\n RECONCILE_RUNNER_INSTANCES_INTENDED_RESERVATION_HEADER,\n RECONCILE_RUNNER_INSTANCES_INTENDED_RESERVATION_HEADER_VALUE,\n reconcileRunnerInstancesBodySchema,\n reconcileRunnerInstancesResponseSchema,\n} from '@shipfox/api-runners-dto';\nimport {defineRoute} from '@shipfox/node-fastify';\nimport {reconcileRunnerInstances} from '#core/index.js';\nimport {toReconcileRunnerInstancesResponseDto} from '#presentation/dto/index.js';\n\nexport const reconcileRunnerInstancesRoute = defineRoute({\n method: 'POST',\n path: '/runner-instances/reconcile',\n description: 'Reconcile provisioned runner state for a provisioner',\n schema: {\n body: reconcileRunnerInstancesBodySchema,\n response: {\n 200: reconcileRunnerInstancesResponseSchema,\n },\n },\n handler: async (request) => {\n const context = requireProvisionerContext(request);\n const result = await reconcileRunnerInstances({\n workspaceId: context.scope === 'workspace' ? context.workspaceId : null,\n provisionerId: context.provisionerTokenId,\n observedRunnerInstanceIds: request.body.observed_provider_runner_ids,\n });\n\n return toReconcileRunnerInstancesResponseDto(result, {\n includeIntendedReservationId:\n request.headers[RECONCILE_RUNNER_INSTANCES_INTENDED_RESERVATION_HEADER] ===\n RECONCILE_RUNNER_INSTANCES_INTENDED_RESERVATION_HEADER_VALUE,\n });\n },\n});\n"],"names":["requireProvisionerContext","RECONCILE_RUNNER_INSTANCES_INTENDED_RESERVATION_HEADER","RECONCILE_RUNNER_INSTANCES_INTENDED_RESERVATION_HEADER_VALUE","reconcileRunnerInstancesBodySchema","reconcileRunnerInstancesResponseSchema","defineRoute","reconcileRunnerInstances","toReconcileRunnerInstancesResponseDto","reconcileRunnerInstancesRoute","method","path","description","schema","body","response","handler","request","context","result","workspaceId","scope","provisionerId","provisionerTokenId","observedRunnerInstanceIds","observed_provider_runner_ids","includeIntendedReservationId","headers"],"mappings":"AAAA,SAAQA,yBAAyB,QAAO,4BAA4B;AACpE,SACEC,sDAAsD,EACtDC,4DAA4D,EAC5DC,kCAAkC,EAClCC,sCAAsC,QACjC,2BAA2B;AAClC,SAAQC,WAAW,QAAO,wBAAwB;AAClD,SAAQC,wBAAwB,QAAO,iBAAiB;AACxD,SAAQC,qCAAqC,QAAO,6BAA6B;AAEjF,OAAO,MAAMC,gCAAgCH,YAAY;IACvDI,QAAQ;IACRC,MAAM;IACNC,aAAa;IACbC,QAAQ;QACNC,MAAMV;QACNW,UAAU;YACR,KAAKV;QACP;IACF;IACAW,SAAS,OAAOC;QACd,MAAMC,UAAUjB,0BAA0BgB;QAC1C,MAAME,SAAS,MAAMZ,yBAAyB;YAC5Ca,aAAaF,QAAQG,KAAK,KAAK,cAAcH,QAAQE,WAAW,GAAG;YACnEE,eAAeJ,QAAQK,kBAAkB;YACzCC,2BAA2BP,QAAQH,IAAI,CAACW,4BAA4B;QACtE;QAEA,OAAOjB,sCAAsCW,QAAQ;YACnDO,8BACET,QAAQU,OAAO,CAACzB,uDAAuD,KACvEC;QACJ;IACF;AACF,GAAG"}
|
|
@@ -8,7 +8,10 @@ type RunnerAssignmentPollParams = {
|
|
|
8
8
|
deadline: number;
|
|
9
9
|
intervalMs: number;
|
|
10
10
|
signal: AbortSignal;
|
|
11
|
-
getAssignment: () => Promise<
|
|
11
|
+
getAssignment: () => Promise<{
|
|
12
|
+
workspaceId: string | null;
|
|
13
|
+
runnerSessionId: string | null;
|
|
14
|
+
} | null>;
|
|
12
15
|
issueActivationToken: () => Promise<string | null>;
|
|
13
16
|
};
|
|
14
17
|
export declare function resolveRunnerAssignmentPollWaitSeconds(requestedWaitSeconds: number | undefined): number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner-enrollment.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/runner-enrollment.ts"],"names":[],"mappings":"AAiCA,eAAO,MAAM,0BAA0B,
|
|
1
|
+
{"version":3,"file":"runner-enrollment.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/runner-enrollment.ts"],"names":[],"mappings":"AAiCA,eAAO,MAAM,0BAA0B,iDA6BrC,CAAC;AAEH,eAAO,MAAM,mCAAmC,iDAmB9C,CAAC;AAEH,eAAO,MAAM,4BAA4B,iDA2BvC,CAAC;AAEH,eAAO,MAAM,iBAAiB,iDAuB5B,CAAC;AAEH,eAAO,MAAM,kCAAkC,iDAkB7C,CAAC;AAEH,eAAO,MAAM,2BAA2B,iDAYtC,CAAC;AAEH,KAAK,0BAA0B,GAAG;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,WAAW,CAAC;IACpB,aAAa,EAAE,MAAM,OAAO,CAAC;QAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;KAAC,GAAG,IAAI,CAAC,CAAC;IAClG,oBAAoB,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACpD,CAAC;AAEF,wBAAgB,sCAAsC,CACpD,oBAAoB,EAAE,MAAM,GAAG,SAAS,GACvC,MAAM,CAQR;AAED,wBAAsB,4BAA4B,CAChD,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAaxB;AACD,eAAO,MAAM,yBAAyB,iDA6BpC,CAAC"}
|
|
@@ -20,7 +20,7 @@ export const createRunnerInstancesRoute = defineRoute({
|
|
|
20
20
|
},
|
|
21
21
|
handler: async (request)=>{
|
|
22
22
|
const { provisionerTokenId } = requireProvisionerContext(request);
|
|
23
|
-
const
|
|
23
|
+
const result = await createRunnerInstancesWithBootstrapTokens({
|
|
24
24
|
provisionerId: provisionerTokenId,
|
|
25
25
|
...request.body.provider_kind ? {
|
|
26
26
|
providerKind: request.body.provider_kind
|
|
@@ -36,10 +36,14 @@ export const createRunnerInstancesRoute = defineRoute({
|
|
|
36
36
|
ttlSeconds: config.RUNNER_BOOTSTRAP_TOKEN_TTL_SECONDS
|
|
37
37
|
});
|
|
38
38
|
return {
|
|
39
|
-
runner_instances:
|
|
40
|
-
runner_instance_id:
|
|
41
|
-
bootstrap_token:
|
|
42
|
-
|
|
39
|
+
runner_instances: result.runnerInstances.map((runnerInstance)=>({
|
|
40
|
+
runner_instance_id: runnerInstance.runnerInstanceId,
|
|
41
|
+
bootstrap_token: runnerInstance.bootstrapToken,
|
|
42
|
+
request_index: runnerInstance.requestIndex
|
|
43
|
+
})),
|
|
44
|
+
...result.reservationUnavailable ? {
|
|
45
|
+
reservation_unavailable: true
|
|
46
|
+
} : {}
|
|
43
47
|
};
|
|
44
48
|
}
|
|
45
49
|
});
|
|
@@ -220,7 +224,8 @@ export const runnerAssignmentPollRoute = defineRoute({
|
|
|
220
224
|
getAssignment: ()=>getRunnerAssignment(session),
|
|
221
225
|
issueActivationToken: ()=>issueRunnerActivationToken({
|
|
222
226
|
...session,
|
|
223
|
-
ttlSeconds: config.RUNNER_ACTIVATION_TOKEN_TTL_SECONDS
|
|
227
|
+
ttlSeconds: config.RUNNER_ACTIVATION_TOKEN_TTL_SECONDS,
|
|
228
|
+
surface: 'poll'
|
|
224
229
|
})
|
|
225
230
|
});
|
|
226
231
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/routes/runner-enrollment.ts"],"sourcesContent":["import {\n requireProvisionerContext,\n requireRunnerControlSessionContext,\n} from '@shipfox/api-auth-context';\nimport {\n attachRunnerControlProviderIdBodySchema,\n createRunnerInstancesBodySchema,\n createRunnerInstancesResponseSchema,\n runnerAssignmentPollQuerySchema,\n runnerAssignmentPollResponseSchema,\n runnerBootstrapExchangeBodySchema,\n runnerBootstrapExchangeResponseSchema,\n runnerControlHeartbeatResponseSchema,\n runnerEnrollmentBodySchema,\n runnerEnrollmentResponseSchema,\n} from '@shipfox/api-runners-dto';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {z} from 'zod';\nimport {config} from '#config.js';\nimport {getRunnerAssignment, issueRunnerActivationToken} from '#core/runner-activation.js';\nimport {\n attachRunnerControlProviderId,\n createRunnerInstancesWithBootstrapTokens,\n enrollRunnerControlSession,\n exchangeRunnerBootstrapToken,\n RunnerBootstrapTokenInvalidError,\n RunnerControlSessionInvalidError,\n touchRunnerControlSession,\n} from '#core/runner-control-sessions.js';\nimport {attachRunnerInstanceProviderId} from '#core/runner-instances.js';\nimport {runnerBootstrapExchangeCount, runnerControlHeartbeatCount} from '#metrics/instance.js';\nimport {authenticateRunnerControlSession} from '#presentation/auth/index.js';\n\nexport const createRunnerInstancesRoute = defineRoute({\n method: 'POST',\n path: '/runner-instances/batch',\n description:\n 'Create provisioner-owned runner instances and one-use bootstrap tokens before launch',\n schema: {\n body: createRunnerInstancesBodySchema,\n response: {200: createRunnerInstancesResponseSchema},\n },\n handler: async (request) => {\n const {provisionerTokenId} = requireProvisionerContext(request);\n const results = await createRunnerInstancesWithBootstrapTokens({\n provisionerId: provisionerTokenId,\n ...(request.body.provider_kind ? {providerKind: request.body.provider_kind} : {}),\n runnerInstances: request.body.runner_instances.map((runner) => ({\n ...(runner.template_key ? {templateKey: runner.template_key} : {}),\n ...(runner.reservation_id ? {reservationId: runner.reservation_id} : {}),\n })),\n ttlSeconds: config.RUNNER_BOOTSTRAP_TOKEN_TTL_SECONDS,\n });\n return {\n runner_instances: results.map((result) => ({\n runner_instance_id: result.runnerInstanceId,\n bootstrap_token: result.bootstrapToken,\n })),\n };\n },\n});\n\nexport const attachRunnerInstanceProviderIdRoute = defineRoute({\n method: 'POST',\n path: '/runner-instances/:runnerInstanceId/provider-runner',\n description: 'Attach the provider identity after the provisioner launches a runner instance',\n schema: {\n params: z.object({runnerInstanceId: z.string().uuid()}),\n body: attachRunnerControlProviderIdBodySchema,\n response: {200: z.object({attached: z.boolean()})},\n },\n handler: async (request) => {\n const {provisionerTokenId} = requireProvisionerContext(request);\n return {\n attached: await attachRunnerInstanceProviderId({\n runnerInstanceId: request.params.runnerInstanceId,\n provisionerId: provisionerTokenId,\n providerRunnerId: request.body.provider_runner_id,\n }),\n };\n },\n});\n\nexport const exchangeRunnerBootstrapRoute = defineRoute({\n method: 'POST',\n path: '/exchange',\n description: 'Exchange a one-use runner bootstrap token for a workspace-neutral control session',\n schema: {\n body: runnerBootstrapExchangeBodySchema,\n response: {200: runnerBootstrapExchangeResponseSchema},\n },\n errorHandler: (error) => {\n if (error instanceof RunnerBootstrapTokenInvalidError)\n runnerBootstrapExchangeCount.add(1, {outcome: 'rejected'});\n if (error instanceof RunnerBootstrapTokenInvalidError)\n throw new ClientError(error.message, 'runner-bootstrap-token-invalid', {status: 401});\n throw error;\n },\n handler: async (request) => {\n const result = await exchangeRunnerBootstrapToken({\n rawToken: request.body.bootstrap_token,\n ttlSeconds: config.RUNNER_CONTROL_SESSION_TTL_SECONDS,\n });\n runnerBootstrapExchangeCount.add(1, {outcome: 'accepted'});\n return {\n runner_instance_id: result.runnerInstanceId,\n control_session_token: result.controlSessionToken,\n expires_at: result.expiresAt.toISOString(),\n };\n },\n});\n\nexport const enrollRunnerRoute = defineRoute({\n method: 'POST',\n path: '/enrollment',\n description: 'Declare the authenticated runner instance labels and protocol capabilities',\n schema: {body: runnerEnrollmentBodySchema, response: {200: runnerEnrollmentResponseSchema}},\n preHandler: authenticateRunnerControlSession,\n errorHandler: (error) => {\n if (error instanceof RunnerControlSessionInvalidError)\n throw new ClientError(error.message, 'runner-control-session-invalid', {status: 409});\n throw error;\n },\n handler: async (request) => {\n const session = requireRunnerControlSessionContext(request);\n const activationToken = await enrollRunnerControlSession({\n runnerInstanceId: session.runnerInstanceId,\n provisionerId: session.provisionerId,\n labels: request.body.labels,\n ...(request.body.capabilities ? {capabilities: request.body.capabilities} : {}),\n providerKind: request.body.provider_kind,\n protocolVersion: request.body.protocol_version,\n });\n return {activation_token: activationToken};\n },\n});\n\nexport const attachRunnerControlProviderIdRoute = defineRoute({\n method: 'POST',\n path: '/provider-runner',\n description: 'Attach the authenticated runner instance provider identity once',\n schema: {\n body: attachRunnerControlProviderIdBodySchema,\n response: {200: z.object({attached: z.boolean()})},\n },\n preHandler: authenticateRunnerControlSession,\n handler: async (request) => {\n const session = requireRunnerControlSessionContext(request);\n return {\n attached: await attachRunnerControlProviderId({\n ...session,\n providerRunnerId: request.body.provider_runner_id,\n }),\n };\n },\n});\n\nexport const runnerControlHeartbeatRoute = defineRoute({\n method: 'POST',\n path: '/heartbeat',\n description: 'Record liveness for only the authenticated runner instance',\n schema: {response: {200: runnerControlHeartbeatResponseSchema}},\n preHandler: authenticateRunnerControlSession,\n handler: async (request) => {\n const session = requireRunnerControlSessionContext(request);\n await touchRunnerControlSession(session.runnerInstanceId, session.provisionerId);\n runnerControlHeartbeatCount.add(1);\n return {ok: true};\n },\n});\n\ntype RunnerAssignmentPollParams = {\n deadline: number;\n intervalMs: number;\n signal: AbortSignal;\n getAssignment: () => Promise<unknown | null>;\n issueActivationToken: () => Promise<string | null>;\n};\n\nexport function resolveRunnerAssignmentPollWaitSeconds(\n requestedWaitSeconds: number | undefined,\n): number {\n return Math.max(\n 1,\n Math.min(\n requestedWaitSeconds ?? config.RUNNER_ASSIGNMENT_POLL_MAX_WAIT_SECONDS,\n config.RUNNER_ASSIGNMENT_POLL_MAX_WAIT_SECONDS,\n ),\n );\n}\n\nexport async function pollForRunnerActivationToken(\n params: RunnerAssignmentPollParams,\n): Promise<string | null> {\n while (Date.now() < params.deadline) {\n if (params.signal.aborted) return null;\n const assignment = await params.getAssignment();\n if (assignment) {\n const activationToken = await params.issueActivationToken();\n if (activationToken) return activationToken;\n }\n const remainingMs = params.deadline - Date.now();\n if (remainingMs <= 0 || params.signal.aborted) return null;\n await new Promise((resolve) => setTimeout(resolve, Math.min(params.intervalMs, remainingMs)));\n }\n return null;\n}\nexport const runnerAssignmentPollRoute = defineRoute({\n method: 'GET',\n path: '/assignment',\n description: 'Wait for only the authenticated runner instance assignment',\n schema: {\n querystring: runnerAssignmentPollQuerySchema,\n response: {200: runnerAssignmentPollResponseSchema},\n },\n preHandler: authenticateRunnerControlSession,\n handler: async (request, reply) => {\n const session = requireRunnerControlSessionContext(request);\n const waitSeconds = resolveRunnerAssignmentPollWaitSeconds(request.query.wait_seconds);\n const deadline = Date.now() + waitSeconds * 1000;\n const abortController = new AbortController();\n reply.raw.on('close', () => abortController.abort());\n const activationToken = await pollForRunnerActivationToken({\n deadline,\n intervalMs: config.RUNNER_ASSIGNMENT_POLL_INTERVAL_MS,\n signal: abortController.signal,\n getAssignment: () => getRunnerAssignment(session),\n issueActivationToken: () =>\n issueRunnerActivationToken({\n ...session,\n ttlSeconds: config.RUNNER_ACTIVATION_TOKEN_TTL_SECONDS,\n }),\n });\n return {activation_token: activationToken};\n },\n});\n"],"names":["requireProvisionerContext","requireRunnerControlSessionContext","attachRunnerControlProviderIdBodySchema","createRunnerInstancesBodySchema","createRunnerInstancesResponseSchema","runnerAssignmentPollQuerySchema","runnerAssignmentPollResponseSchema","runnerBootstrapExchangeBodySchema","runnerBootstrapExchangeResponseSchema","runnerControlHeartbeatResponseSchema","runnerEnrollmentBodySchema","runnerEnrollmentResponseSchema","ClientError","defineRoute","z","config","getRunnerAssignment","issueRunnerActivationToken","attachRunnerControlProviderId","createRunnerInstancesWithBootstrapTokens","enrollRunnerControlSession","exchangeRunnerBootstrapToken","RunnerBootstrapTokenInvalidError","RunnerControlSessionInvalidError","touchRunnerControlSession","attachRunnerInstanceProviderId","runnerBootstrapExchangeCount","runnerControlHeartbeatCount","authenticateRunnerControlSession","createRunnerInstancesRoute","method","path","description","schema","body","response","handler","request","provisionerTokenId","results","provisionerId","provider_kind","providerKind","runnerInstances","runner_instances","map","runner","template_key","templateKey","reservation_id","reservationId","ttlSeconds","RUNNER_BOOTSTRAP_TOKEN_TTL_SECONDS","result","runner_instance_id","runnerInstanceId","bootstrap_token","bootstrapToken","attachRunnerInstanceProviderIdRoute","params","object","string","uuid","attached","boolean","providerRunnerId","provider_runner_id","exchangeRunnerBootstrapRoute","errorHandler","error","add","outcome","message","status","rawToken","RUNNER_CONTROL_SESSION_TTL_SECONDS","control_session_token","controlSessionToken","expires_at","expiresAt","toISOString","enrollRunnerRoute","preHandler","session","activationToken","labels","capabilities","protocolVersion","protocol_version","activation_token","attachRunnerControlProviderIdRoute","runnerControlHeartbeatRoute","ok","resolveRunnerAssignmentPollWaitSeconds","requestedWaitSeconds","Math","max","min","RUNNER_ASSIGNMENT_POLL_MAX_WAIT_SECONDS","pollForRunnerActivationToken","Date","now","deadline","signal","aborted","assignment","getAssignment","issueActivationToken","remainingMs","Promise","resolve","setTimeout","intervalMs","runnerAssignmentPollRoute","querystring","reply","waitSeconds","query","wait_seconds","abortController","AbortController","raw","on","abort","RUNNER_ASSIGNMENT_POLL_INTERVAL_MS","RUNNER_ACTIVATION_TOKEN_TTL_SECONDS"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/routes/runner-enrollment.ts"],"sourcesContent":["import {\n requireProvisionerContext,\n requireRunnerControlSessionContext,\n} from '@shipfox/api-auth-context';\nimport {\n attachRunnerControlProviderIdBodySchema,\n createRunnerInstancesBodySchema,\n createRunnerInstancesResponseSchema,\n runnerAssignmentPollQuerySchema,\n runnerAssignmentPollResponseSchema,\n runnerBootstrapExchangeBodySchema,\n runnerBootstrapExchangeResponseSchema,\n runnerControlHeartbeatResponseSchema,\n runnerEnrollmentBodySchema,\n runnerEnrollmentResponseSchema,\n} from '@shipfox/api-runners-dto';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {z} from 'zod';\nimport {config} from '#config.js';\nimport {getRunnerAssignment, issueRunnerActivationToken} from '#core/runner-activation.js';\nimport {\n attachRunnerControlProviderId,\n createRunnerInstancesWithBootstrapTokens,\n enrollRunnerControlSession,\n exchangeRunnerBootstrapToken,\n RunnerBootstrapTokenInvalidError,\n RunnerControlSessionInvalidError,\n touchRunnerControlSession,\n} from '#core/runner-control-sessions.js';\nimport {attachRunnerInstanceProviderId} from '#core/runner-instances.js';\nimport {runnerBootstrapExchangeCount, runnerControlHeartbeatCount} from '#metrics/instance.js';\nimport {authenticateRunnerControlSession} from '#presentation/auth/index.js';\n\nexport const createRunnerInstancesRoute = defineRoute({\n method: 'POST',\n path: '/runner-instances/batch',\n description:\n 'Create provisioner-owned runner instances and one-use bootstrap tokens before launch',\n schema: {\n body: createRunnerInstancesBodySchema,\n response: {200: createRunnerInstancesResponseSchema},\n },\n handler: async (request) => {\n const {provisionerTokenId} = requireProvisionerContext(request);\n const result = await createRunnerInstancesWithBootstrapTokens({\n provisionerId: provisionerTokenId,\n ...(request.body.provider_kind ? {providerKind: request.body.provider_kind} : {}),\n runnerInstances: request.body.runner_instances.map((runner) => ({\n ...(runner.template_key ? {templateKey: runner.template_key} : {}),\n ...(runner.reservation_id ? {reservationId: runner.reservation_id} : {}),\n })),\n ttlSeconds: config.RUNNER_BOOTSTRAP_TOKEN_TTL_SECONDS,\n });\n return {\n runner_instances: result.runnerInstances.map((runnerInstance) => ({\n runner_instance_id: runnerInstance.runnerInstanceId,\n bootstrap_token: runnerInstance.bootstrapToken,\n request_index: runnerInstance.requestIndex,\n })),\n ...(result.reservationUnavailable ? {reservation_unavailable: true as const} : {}),\n };\n },\n});\n\nexport const attachRunnerInstanceProviderIdRoute = defineRoute({\n method: 'POST',\n path: '/runner-instances/:runnerInstanceId/provider-runner',\n description: 'Attach the provider identity after the provisioner launches a runner instance',\n schema: {\n params: z.object({runnerInstanceId: z.string().uuid()}),\n body: attachRunnerControlProviderIdBodySchema,\n response: {200: z.object({attached: z.boolean()})},\n },\n handler: async (request) => {\n const {provisionerTokenId} = requireProvisionerContext(request);\n return {\n attached: await attachRunnerInstanceProviderId({\n runnerInstanceId: request.params.runnerInstanceId,\n provisionerId: provisionerTokenId,\n providerRunnerId: request.body.provider_runner_id,\n }),\n };\n },\n});\n\nexport const exchangeRunnerBootstrapRoute = defineRoute({\n method: 'POST',\n path: '/exchange',\n description: 'Exchange a one-use runner bootstrap token for a workspace-neutral control session',\n schema: {\n body: runnerBootstrapExchangeBodySchema,\n response: {200: runnerBootstrapExchangeResponseSchema},\n },\n errorHandler: (error) => {\n if (error instanceof RunnerBootstrapTokenInvalidError)\n runnerBootstrapExchangeCount.add(1, {outcome: 'rejected'});\n if (error instanceof RunnerBootstrapTokenInvalidError)\n throw new ClientError(error.message, 'runner-bootstrap-token-invalid', {status: 401});\n throw error;\n },\n handler: async (request) => {\n const result = await exchangeRunnerBootstrapToken({\n rawToken: request.body.bootstrap_token,\n ttlSeconds: config.RUNNER_CONTROL_SESSION_TTL_SECONDS,\n });\n runnerBootstrapExchangeCount.add(1, {outcome: 'accepted'});\n return {\n runner_instance_id: result.runnerInstanceId,\n control_session_token: result.controlSessionToken,\n expires_at: result.expiresAt.toISOString(),\n };\n },\n});\n\nexport const enrollRunnerRoute = defineRoute({\n method: 'POST',\n path: '/enrollment',\n description: 'Declare the authenticated runner instance labels and protocol capabilities',\n schema: {body: runnerEnrollmentBodySchema, response: {200: runnerEnrollmentResponseSchema}},\n preHandler: authenticateRunnerControlSession,\n errorHandler: (error) => {\n if (error instanceof RunnerControlSessionInvalidError)\n throw new ClientError(error.message, 'runner-control-session-invalid', {status: 409});\n throw error;\n },\n handler: async (request) => {\n const session = requireRunnerControlSessionContext(request);\n const activationToken = await enrollRunnerControlSession({\n runnerInstanceId: session.runnerInstanceId,\n provisionerId: session.provisionerId,\n labels: request.body.labels,\n ...(request.body.capabilities ? {capabilities: request.body.capabilities} : {}),\n providerKind: request.body.provider_kind,\n protocolVersion: request.body.protocol_version,\n });\n return {activation_token: activationToken};\n },\n});\n\nexport const attachRunnerControlProviderIdRoute = defineRoute({\n method: 'POST',\n path: '/provider-runner',\n description: 'Attach the authenticated runner instance provider identity once',\n schema: {\n body: attachRunnerControlProviderIdBodySchema,\n response: {200: z.object({attached: z.boolean()})},\n },\n preHandler: authenticateRunnerControlSession,\n handler: async (request) => {\n const session = requireRunnerControlSessionContext(request);\n return {\n attached: await attachRunnerControlProviderId({\n ...session,\n providerRunnerId: request.body.provider_runner_id,\n }),\n };\n },\n});\n\nexport const runnerControlHeartbeatRoute = defineRoute({\n method: 'POST',\n path: '/heartbeat',\n description: 'Record liveness for only the authenticated runner instance',\n schema: {response: {200: runnerControlHeartbeatResponseSchema}},\n preHandler: authenticateRunnerControlSession,\n handler: async (request) => {\n const session = requireRunnerControlSessionContext(request);\n await touchRunnerControlSession(session.runnerInstanceId, session.provisionerId);\n runnerControlHeartbeatCount.add(1);\n return {ok: true};\n },\n});\n\ntype RunnerAssignmentPollParams = {\n deadline: number;\n intervalMs: number;\n signal: AbortSignal;\n getAssignment: () => Promise<{workspaceId: string | null; runnerSessionId: string | null} | null>;\n issueActivationToken: () => Promise<string | null>;\n};\n\nexport function resolveRunnerAssignmentPollWaitSeconds(\n requestedWaitSeconds: number | undefined,\n): number {\n return Math.max(\n 1,\n Math.min(\n requestedWaitSeconds ?? config.RUNNER_ASSIGNMENT_POLL_MAX_WAIT_SECONDS,\n config.RUNNER_ASSIGNMENT_POLL_MAX_WAIT_SECONDS,\n ),\n );\n}\n\nexport async function pollForRunnerActivationToken(\n params: RunnerAssignmentPollParams,\n): Promise<string | null> {\n while (Date.now() < params.deadline) {\n if (params.signal.aborted) return null;\n const assignment = await params.getAssignment();\n if (assignment) {\n const activationToken = await params.issueActivationToken();\n if (activationToken) return activationToken;\n }\n const remainingMs = params.deadline - Date.now();\n if (remainingMs <= 0 || params.signal.aborted) return null;\n await new Promise((resolve) => setTimeout(resolve, Math.min(params.intervalMs, remainingMs)));\n }\n return null;\n}\nexport const runnerAssignmentPollRoute = defineRoute({\n method: 'GET',\n path: '/assignment',\n description: 'Wait for only the authenticated runner instance assignment',\n schema: {\n querystring: runnerAssignmentPollQuerySchema,\n response: {200: runnerAssignmentPollResponseSchema},\n },\n preHandler: authenticateRunnerControlSession,\n handler: async (request, reply) => {\n const session = requireRunnerControlSessionContext(request);\n const waitSeconds = resolveRunnerAssignmentPollWaitSeconds(request.query.wait_seconds);\n const deadline = Date.now() + waitSeconds * 1000;\n const abortController = new AbortController();\n reply.raw.on('close', () => abortController.abort());\n const activationToken = await pollForRunnerActivationToken({\n deadline,\n intervalMs: config.RUNNER_ASSIGNMENT_POLL_INTERVAL_MS,\n signal: abortController.signal,\n getAssignment: () => getRunnerAssignment(session),\n issueActivationToken: () =>\n issueRunnerActivationToken({\n ...session,\n ttlSeconds: config.RUNNER_ACTIVATION_TOKEN_TTL_SECONDS,\n surface: 'poll',\n }),\n });\n return {activation_token: activationToken};\n },\n});\n"],"names":["requireProvisionerContext","requireRunnerControlSessionContext","attachRunnerControlProviderIdBodySchema","createRunnerInstancesBodySchema","createRunnerInstancesResponseSchema","runnerAssignmentPollQuerySchema","runnerAssignmentPollResponseSchema","runnerBootstrapExchangeBodySchema","runnerBootstrapExchangeResponseSchema","runnerControlHeartbeatResponseSchema","runnerEnrollmentBodySchema","runnerEnrollmentResponseSchema","ClientError","defineRoute","z","config","getRunnerAssignment","issueRunnerActivationToken","attachRunnerControlProviderId","createRunnerInstancesWithBootstrapTokens","enrollRunnerControlSession","exchangeRunnerBootstrapToken","RunnerBootstrapTokenInvalidError","RunnerControlSessionInvalidError","touchRunnerControlSession","attachRunnerInstanceProviderId","runnerBootstrapExchangeCount","runnerControlHeartbeatCount","authenticateRunnerControlSession","createRunnerInstancesRoute","method","path","description","schema","body","response","handler","request","provisionerTokenId","result","provisionerId","provider_kind","providerKind","runnerInstances","runner_instances","map","runner","template_key","templateKey","reservation_id","reservationId","ttlSeconds","RUNNER_BOOTSTRAP_TOKEN_TTL_SECONDS","runnerInstance","runner_instance_id","runnerInstanceId","bootstrap_token","bootstrapToken","request_index","requestIndex","reservationUnavailable","reservation_unavailable","attachRunnerInstanceProviderIdRoute","params","object","string","uuid","attached","boolean","providerRunnerId","provider_runner_id","exchangeRunnerBootstrapRoute","errorHandler","error","add","outcome","message","status","rawToken","RUNNER_CONTROL_SESSION_TTL_SECONDS","control_session_token","controlSessionToken","expires_at","expiresAt","toISOString","enrollRunnerRoute","preHandler","session","activationToken","labels","capabilities","protocolVersion","protocol_version","activation_token","attachRunnerControlProviderIdRoute","runnerControlHeartbeatRoute","ok","resolveRunnerAssignmentPollWaitSeconds","requestedWaitSeconds","Math","max","min","RUNNER_ASSIGNMENT_POLL_MAX_WAIT_SECONDS","pollForRunnerActivationToken","Date","now","deadline","signal","aborted","assignment","getAssignment","issueActivationToken","remainingMs","Promise","resolve","setTimeout","intervalMs","runnerAssignmentPollRoute","querystring","reply","waitSeconds","query","wait_seconds","abortController","AbortController","raw","on","abort","RUNNER_ASSIGNMENT_POLL_INTERVAL_MS","RUNNER_ACTIVATION_TOKEN_TTL_SECONDS","surface"],"mappings":"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"}
|