@shipfox/api-runners 12.1.0 → 12.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +35 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +24 -2
- package/dist/config.js.map +1 -1
- package/dist/core/errors.d.ts +4 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +6 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/rate-limit.d.ts.map +1 -1
- package/dist/core/runner-control-sessions.d.ts.map +1 -1
- package/dist/core/runner-control-sessions.js +37 -11
- package/dist/core/runner-control-sessions.js.map +1 -1
- package/dist/core/runner-instances.d.ts +1 -0
- package/dist/core/runner-instances.d.ts.map +1 -1
- package/dist/core/runner-instances.js.map +1 -1
- package/dist/core/runner-labels.d.ts +11 -0
- package/dist/core/runner-labels.d.ts.map +1 -0
- package/dist/core/runner-labels.js +41 -0
- package/dist/core/runner-labels.js.map +1 -0
- package/dist/core/runner-sessions.d.ts.map +1 -1
- package/dist/core/runner-sessions.js +5 -1
- package/dist/core/runner-sessions.js.map +1 -1
- package/dist/db/db.d.ts +64 -64
- package/dist/db/ephemeral-registration-tokens.d.ts.map +1 -1
- package/dist/db/ephemeral-registration-tokens.js +12 -2
- package/dist/db/ephemeral-registration-tokens.js.map +1 -1
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/reservations.d.ts.map +1 -1
- package/dist/db/reservations.js +36 -10
- package/dist/db/reservations.js.map +1 -1
- package/dist/db/runner-instances.d.ts +4 -0
- package/dist/db/runner-instances.d.ts.map +1 -1
- package/dist/db/runner-instances.js +16 -5
- package/dist/db/runner-instances.js.map +1 -1
- package/dist/db/runner-sessions.d.ts.map +1 -1
- package/dist/db/runner-sessions.js +33 -15
- package/dist/db/runner-sessions.js.map +1 -1
- package/dist/db/schema/admin-command-results.d.ts +1 -1
- package/dist/db/schema/ephemeral-registration-tokens.d.ts +1 -1
- package/dist/db/schema/manual-registration-tokens.d.ts +1 -1
- package/dist/db/schema/outbox.d.ts +1 -1
- package/dist/db/schema/pending-job-executions.d.ts +3 -3
- package/dist/db/schema/provisioner-capability-snapshots.d.ts +3 -3
- package/dist/db/schema/provisioner-tokens.d.ts +2 -2
- package/dist/db/schema/rate-limits.d.ts +1 -1
- package/dist/db/schema/reservations.d.ts +3 -3
- package/dist/db/schema/runner-activation-tokens.d.ts +1 -1
- package/dist/db/schema/runner-control-sessions.d.ts +2 -2
- package/dist/db/schema/runner-instances.d.ts +4 -4
- package/dist/db/schema/runner-instances.d.ts.map +1 -1
- package/dist/db/schema/runner-instances.js +1 -0
- package/dist/db/schema/runner-instances.js.map +1 -1
- package/dist/db/schema/runner-sessions.d.ts +4 -4
- package/dist/db/schema/running-job-executions.d.ts +5 -5
- package/dist/metrics/index.d.ts +2 -1
- package/dist/metrics/index.d.ts.map +1 -1
- package/dist/metrics/index.js +1 -1
- package/dist/metrics/index.js.map +1 -1
- package/dist/metrics/instance.d.ts +13 -8
- package/dist/metrics/instance.d.ts.map +1 -1
- package/dist/metrics/instance.js +8 -0
- package/dist/metrics/instance.js.map +1 -1
- package/dist/metrics/service.d.ts.map +1 -1
- package/dist/metrics/service.js +20 -4
- package/dist/metrics/service.js.map +1 -1
- package/dist/presentation/dto/admin-provisioner-token.d.ts +1 -1
- package/dist/presentation/dto/admin-provisioner-token.d.ts.map +1 -1
- package/dist/presentation/dto/admin-runner-instances.d.ts +2 -2
- package/dist/presentation/dto/admin-runner-instances.d.ts.map +1 -1
- package/dist/presentation/routes/poll-demand.d.ts.map +1 -1
- package/dist/presentation/routes/poll-demand.js +12 -6
- package/dist/presentation/routes/poll-demand.js.map +1 -1
- package/dist/presentation/routes/rate-limit.d.ts.map +1 -1
- package/dist/presentation/routes/register.d.ts.map +1 -1
- package/dist/presentation/routes/register.js +9 -1
- package/dist/presentation/routes/register.js.map +1 -1
- package/dist/presentation/routes/report-runner-instances.d.ts.map +1 -1
- package/dist/presentation/routes/report-runner-instances.js +1 -0
- package/dist/presentation/routes/report-runner-instances.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0004_huge_skreet.sql +1 -0
- package/drizzle/meta/0004_snapshot.json +2194 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +9 -9
- package/src/config.test.ts +81 -0
- package/src/config.ts +39 -2
- package/src/core/errors.ts +9 -0
- package/src/core/index.ts +1 -0
- package/src/core/runner-control-sessions.test.ts +404 -0
- package/src/core/runner-control-sessions.ts +51 -11
- package/src/core/runner-instances.ts +1 -0
- package/src/core/runner-labels.test.ts +53 -0
- package/src/core/runner-labels.ts +51 -0
- package/src/core/runner-sessions.test.ts +34 -1
- package/src/core/runner-sessions.ts +8 -1
- package/src/db/ephemeral-registration-tokens.ts +15 -1
- package/src/db/index.ts +1 -0
- package/src/db/reservations.test.ts +537 -0
- package/src/db/reservations.ts +116 -17
- package/src/db/runner-instances.test.ts +276 -1
- package/src/db/runner-instances.ts +39 -3
- package/src/db/runner-sessions.ts +39 -15
- package/src/db/schema/runner-instances.ts +3 -0
- package/src/metrics/index.ts +2 -0
- package/src/metrics/instance.ts +18 -0
- package/src/metrics/service.test.ts +99 -0
- package/src/metrics/service.ts +24 -4
- package/src/presentation/routes/late-runner-enrollment.test.ts +230 -0
- package/src/presentation/routes/poll-demand.test.ts +116 -3
- package/src/presentation/routes/poll-demand.ts +18 -6
- package/src/presentation/routes/register.test.ts +116 -2
- package/src/presentation/routes/register.ts +7 -0
- package/src/presentation/routes/report-runner-instances.test.ts +33 -0
- package/src/presentation/routes/report-runner-instances.ts +1 -0
- package/src/presentation/routes/runner-enrollment.test.ts +65 -5
- package/src/temporal/activities/maintenance-activities.test.ts +24 -31
- package/test/env.ts +1 -0
- package/test/fixtures/late-runner-enrollment.ts +103 -0
- package/test/index.ts +5 -0
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -2,7 +2,6 @@ import type {RunnerToolCapabilitiesDto} from '@shipfox/api-runners-dto';
|
|
|
2
2
|
import type {NodePgDatabase} from '@shipfox/node-drizzle';
|
|
3
3
|
import {logger} from '@shipfox/node-opentelemetry';
|
|
4
4
|
import {extractDisplayPrefix, generateOpaqueToken, hashOpaqueToken} from '@shipfox/node-tokens';
|
|
5
|
-
import {canonicalizeLabels} from '@shipfox/runner-labels';
|
|
6
5
|
import {and, eq, gt, isNull, notInArray, or, sql} from 'drizzle-orm';
|
|
7
6
|
import {config} from '#config.js';
|
|
8
7
|
import {
|
|
@@ -11,12 +10,17 @@ import {
|
|
|
11
10
|
RunnerInstanceAlreadyAssignedError,
|
|
12
11
|
RunnerInstanceNotAssignableError,
|
|
13
12
|
} from '#core/errors.js';
|
|
13
|
+
import {sanitizeRunnerLabels} from '#core/runner-labels.js';
|
|
14
14
|
import {db, type schema, type Tx} from '#db/db.js';
|
|
15
15
|
import {assignRunnerInstancesTx} from '#db/runner-assignments.js';
|
|
16
16
|
import {terminalStates} from '#db/runner-instances.js';
|
|
17
17
|
import {provisionerTokens} from '#db/schema/provisioner-tokens.js';
|
|
18
18
|
import {runnerBootstrapTokens, runnerControlSessions} from '#db/schema/runner-control-sessions.js';
|
|
19
19
|
import {providerRunners} from '#db/schema/runner-instances.js';
|
|
20
|
+
import {
|
|
21
|
+
type RunnerReservationPromotionFailureReason,
|
|
22
|
+
recordRunnerReservationPromotionFailure,
|
|
23
|
+
} from '#metrics/index.js';
|
|
20
24
|
import {issueRunnerActivationTokenTx} from './runner-activation.js';
|
|
21
25
|
|
|
22
26
|
export class RunnerBootstrapTokenInvalidError extends Error {
|
|
@@ -141,8 +145,12 @@ export async function enrollRunnerControlSession(params: {
|
|
|
141
145
|
}): Promise<string | null> {
|
|
142
146
|
return await db().transaction(async (tx) => {
|
|
143
147
|
const [current] = await tx
|
|
144
|
-
.select({
|
|
148
|
+
.select({
|
|
149
|
+
intendedReservationId: providerRunners.intendedReservationId,
|
|
150
|
+
provisionerScope: provisionerTokens.scope,
|
|
151
|
+
})
|
|
145
152
|
.from(providerRunners)
|
|
153
|
+
.innerJoin(provisionerTokens, eq(provisionerTokens.id, providerRunners.provisionerId))
|
|
146
154
|
.where(
|
|
147
155
|
and(
|
|
148
156
|
eq(providerRunners.id, params.runnerInstanceId),
|
|
@@ -155,13 +163,37 @@ export async function enrollRunnerControlSession(params: {
|
|
|
155
163
|
sql`select pg_advisory_xact_lock(hashtext(${`runners_assignment:${params.provisionerId}:${current.intendedReservationId}`}))`,
|
|
156
164
|
);
|
|
157
165
|
|
|
158
|
-
|
|
166
|
+
// Provider reports may populate reservationId before the assignment commits. assignedAt is
|
|
167
|
+
// written by the assignment transaction, so keep the guard on that write boundary.
|
|
168
|
+
const [metadataUpdated] = await tx
|
|
159
169
|
.update(providerRunners)
|
|
160
170
|
.set({
|
|
161
|
-
labels:
|
|
171
|
+
labels: sanitizeRunnerLabels(params.labels, {
|
|
172
|
+
scope: current.provisionerScope,
|
|
173
|
+
source: 'runner control enrollment',
|
|
174
|
+
}),
|
|
162
175
|
providerKind: params.providerKind,
|
|
163
176
|
protocolVersion: params.protocolVersion,
|
|
164
177
|
capabilities: params.capabilities ?? null,
|
|
178
|
+
})
|
|
179
|
+
.where(
|
|
180
|
+
and(
|
|
181
|
+
eq(providerRunners.id, params.runnerInstanceId),
|
|
182
|
+
eq(providerRunners.provisionerId, params.provisionerId),
|
|
183
|
+
isNull(providerRunners.assignedAt),
|
|
184
|
+
notInArray(providerRunners.state, [...terminalStates]),
|
|
185
|
+
),
|
|
186
|
+
)
|
|
187
|
+
.returning({id: providerRunners.id});
|
|
188
|
+
if (!metadataUpdated)
|
|
189
|
+
logger().debug(
|
|
190
|
+
{runnerInstanceId: params.runnerInstanceId, provisionerId: params.provisionerId},
|
|
191
|
+
'Runner enrollment metadata update skipped for assigned or terminal runner',
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const [updated] = await tx
|
|
195
|
+
.update(providerRunners)
|
|
196
|
+
.set({
|
|
165
197
|
state: 'running',
|
|
166
198
|
reportedAt: sql`now()`,
|
|
167
199
|
updatedAt: sql`now()`,
|
|
@@ -198,29 +230,37 @@ export async function enrollRunnerControlSession(params: {
|
|
|
198
230
|
});
|
|
199
231
|
});
|
|
200
232
|
} catch (error) {
|
|
201
|
-
const
|
|
202
|
-
error instanceof ReservationExpiredError ||
|
|
203
|
-
error instanceof ReservationNotFoundError ||
|
|
204
|
-
error instanceof RunnerInstanceAlreadyAssignedError ||
|
|
205
|
-
error instanceof RunnerInstanceNotAssignableError;
|
|
233
|
+
const expectedFailureReason = getRunnerReservationPromotionFailureReason(error);
|
|
206
234
|
const details = {
|
|
207
235
|
err: error,
|
|
208
236
|
runnerInstanceId: params.runnerInstanceId,
|
|
209
237
|
provisionerId: params.provisionerId,
|
|
210
238
|
reservationId: intendedReservationId,
|
|
211
239
|
};
|
|
212
|
-
if (
|
|
240
|
+
if (expectedFailureReason) {
|
|
241
|
+
recordRunnerReservationPromotionFailure(expectedFailureReason);
|
|
213
242
|
logger().debug(details, 'Runner reservation could not be promoted during enrollment');
|
|
214
|
-
else
|
|
243
|
+
} else {
|
|
215
244
|
logger().error(
|
|
216
245
|
details,
|
|
217
246
|
'Unexpected failure promoting runner reservation during enrollment',
|
|
218
247
|
);
|
|
248
|
+
}
|
|
219
249
|
return null;
|
|
220
250
|
}
|
|
221
251
|
});
|
|
222
252
|
}
|
|
223
253
|
|
|
254
|
+
function getRunnerReservationPromotionFailureReason(
|
|
255
|
+
error: unknown,
|
|
256
|
+
): RunnerReservationPromotionFailureReason | null {
|
|
257
|
+
if (error instanceof ReservationExpiredError) return 'reservation-expired';
|
|
258
|
+
if (error instanceof ReservationNotFoundError) return 'reservation-not-found';
|
|
259
|
+
if (error instanceof RunnerInstanceAlreadyAssignedError) return 'already-assigned';
|
|
260
|
+
if (error instanceof RunnerInstanceNotAssignableError) return 'not-assignable';
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
|
|
224
264
|
export async function attachRunnerControlProviderId(params: {
|
|
225
265
|
runnerInstanceId: string;
|
|
226
266
|
provisionerId: string;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {RunnerLabelsReservedError} from './errors.js';
|
|
2
|
+
import {sanitizeRunnerLabels, sanitizeRunnerLabelsOrThrow} from './runner-labels.js';
|
|
3
|
+
|
|
4
|
+
describe('sanitizeRunnerLabels', () => {
|
|
5
|
+
it('strips reserved labels from non-installation sources', () => {
|
|
6
|
+
const labels = sanitizeRunnerLabels([' Linux ', 'shipfox-managed', 'linux'], {
|
|
7
|
+
scope: 'workspace',
|
|
8
|
+
source: 'test',
|
|
9
|
+
reservedLabels: ['Shipfox-Managed'],
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
expect(labels).toEqual(['linux']);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('preserves reserved labels for installation sources', () => {
|
|
16
|
+
const labels = sanitizeRunnerLabels([' Linux ', 'shipfox-managed'], {
|
|
17
|
+
scope: 'installation',
|
|
18
|
+
source: 'test',
|
|
19
|
+
reservedLabels: ['shipfox-managed'],
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
expect(labels).toEqual(['linux', 'shipfox-managed']);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('does not change labels when no reserved labels are configured', () => {
|
|
26
|
+
const labels = sanitizeRunnerLabels([' Linux ', 'linux'], {
|
|
27
|
+
scope: 'manual',
|
|
28
|
+
source: 'test',
|
|
29
|
+
reservedLabels: [],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(labels).toEqual(['linux']);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('uses the configured reserved labels when none are provided explicitly', () => {
|
|
36
|
+
const labels = sanitizeRunnerLabels(['linux', 'shipfox-managed'], {
|
|
37
|
+
scope: 'workspace',
|
|
38
|
+
source: 'test',
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
expect(labels).toEqual(['linux']);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('throws when sanitization removes every canonical label', () => {
|
|
45
|
+
expect(() =>
|
|
46
|
+
sanitizeRunnerLabelsOrThrow([' Shipfox-Managed '], {
|
|
47
|
+
scope: 'workspace',
|
|
48
|
+
source: 'test',
|
|
49
|
+
reservedLabels: ['shipfox-managed'],
|
|
50
|
+
}),
|
|
51
|
+
).toThrow(RunnerLabelsReservedError);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {logger} from '@shipfox/node-opentelemetry';
|
|
2
|
+
import {canonicalizeLabels} from '@shipfox/runner-labels';
|
|
3
|
+
import {runnerReservedLabels} from '#config.js';
|
|
4
|
+
import {RunnerLabelsReservedError} from './errors.js';
|
|
5
|
+
|
|
6
|
+
export type RunnerLabelScope = 'installation' | 'workspace' | 'manual';
|
|
7
|
+
|
|
8
|
+
type SanitizeRunnerLabelsParams = {
|
|
9
|
+
scope: RunnerLabelScope;
|
|
10
|
+
source: string;
|
|
11
|
+
logLevel?: 'debug' | 'warn';
|
|
12
|
+
reservedLabels?: readonly string[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function sanitizeCanonicalRunnerLabels(
|
|
16
|
+
canonicalLabels: readonly string[],
|
|
17
|
+
params: SanitizeRunnerLabelsParams,
|
|
18
|
+
): string[] {
|
|
19
|
+
if (params.scope === 'installation') return [...canonicalLabels];
|
|
20
|
+
|
|
21
|
+
const reservedLabels = new Set(canonicalizeLabels(params.reservedLabels ?? runnerReservedLabels));
|
|
22
|
+
if (reservedLabels.size === 0) return [...canonicalLabels];
|
|
23
|
+
|
|
24
|
+
const removedLabels = canonicalLabels.filter((label) => reservedLabels.has(label));
|
|
25
|
+
if (removedLabels.length === 0) return [...canonicalLabels];
|
|
26
|
+
|
|
27
|
+
const details = {source: params.source, removedLabels};
|
|
28
|
+
if (params.logLevel === 'debug')
|
|
29
|
+
logger().debug(details, 'Removed reserved runner labels from a non-installation source');
|
|
30
|
+
else logger().warn(details, 'Removed reserved runner labels from a non-installation source');
|
|
31
|
+
return canonicalLabels.filter((label) => !reservedLabels.has(label));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function sanitizeRunnerLabels(
|
|
35
|
+
labels: readonly string[],
|
|
36
|
+
params: SanitizeRunnerLabelsParams,
|
|
37
|
+
): string[] {
|
|
38
|
+
return sanitizeCanonicalRunnerLabels([...canonicalizeLabels(labels)], params);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function sanitizeRunnerLabelsOrThrow(
|
|
42
|
+
labels: readonly string[],
|
|
43
|
+
params: SanitizeRunnerLabelsParams,
|
|
44
|
+
): string[] {
|
|
45
|
+
const canonicalLabels = [...canonicalizeLabels(labels)];
|
|
46
|
+
const sanitizedLabels = sanitizeCanonicalRunnerLabels(canonicalLabels, params);
|
|
47
|
+
if (sanitizedLabels.length === 0 && canonicalLabels.length > 0) {
|
|
48
|
+
throw new RunnerLabelsReservedError(canonicalLabels);
|
|
49
|
+
}
|
|
50
|
+
return sanitizedLabels;
|
|
51
|
+
}
|
|
@@ -9,12 +9,14 @@ import {
|
|
|
9
9
|
getRunnerSessionTokenClaims,
|
|
10
10
|
manualRegistrationTokenFactory,
|
|
11
11
|
providerRunnerFactory,
|
|
12
|
+
provisionerTokenFactory,
|
|
12
13
|
runnersTestAuthClient,
|
|
13
14
|
} from '#test/index.js';
|
|
14
15
|
import {
|
|
15
16
|
EmptyRunnerLabelsError,
|
|
16
17
|
RegistrationTokenConsumedError,
|
|
17
18
|
RegistrationTokenWorkspaceMismatchError,
|
|
19
|
+
RunnerLabelsReservedError,
|
|
18
20
|
} from './errors.js';
|
|
19
21
|
import {issueRunnerActivationToken} from './runner-activation.js';
|
|
20
22
|
import {registerRunnerSession} from './runner-sessions.js';
|
|
@@ -66,6 +68,15 @@ describe('registerRunnerSession', () => {
|
|
|
66
68
|
).rejects.toBeInstanceOf(EmptyRunnerLabelsError);
|
|
67
69
|
});
|
|
68
70
|
|
|
71
|
+
it('names labels removed by the reserved-label policy', () => {
|
|
72
|
+
const error = new RunnerLabelsReservedError(['shipfox-managed']);
|
|
73
|
+
|
|
74
|
+
expect(error.message).toBe(
|
|
75
|
+
'All supplied runner labels are reserved for installation-scope provisioners: shipfox-managed',
|
|
76
|
+
);
|
|
77
|
+
expect(error.labels).toEqual(['shipfox-managed']);
|
|
78
|
+
});
|
|
79
|
+
|
|
69
80
|
it('consumes an ephemeral token and creates a one-claim session', async () => {
|
|
70
81
|
const token = await ephemeralRegistrationTokenFactory.create({workspaceId});
|
|
71
82
|
|
|
@@ -223,7 +234,8 @@ describe('activation runner sessions', () => {
|
|
|
223
234
|
|
|
224
235
|
beforeEach(async () => {
|
|
225
236
|
workspaceId = crypto.randomUUID();
|
|
226
|
-
|
|
237
|
+
const provisioner = await provisionerTokenFactory.create({workspaceId});
|
|
238
|
+
provisionerId = provisioner.id;
|
|
227
239
|
const runner = await providerRunnerFactory.create({workspaceId, provisionerId});
|
|
228
240
|
runnerInstanceId = runner.id;
|
|
229
241
|
});
|
|
@@ -296,4 +308,25 @@ describe('activation runner sessions', () => {
|
|
|
296
308
|
expect(storedToken?.consumedAt).toBeInstanceOf(Date);
|
|
297
309
|
expect(sessions).toHaveLength(1);
|
|
298
310
|
});
|
|
311
|
+
|
|
312
|
+
it('strips reserved labels from workspace activation registration', async () => {
|
|
313
|
+
const rawToken = await issueRunnerActivationToken({
|
|
314
|
+
runnerInstanceId,
|
|
315
|
+
provisionerId,
|
|
316
|
+
ttlSeconds: 60,
|
|
317
|
+
});
|
|
318
|
+
const [activationToken] = await db()
|
|
319
|
+
.select()
|
|
320
|
+
.from(runnerActivationTokens)
|
|
321
|
+
.where(eq(runnerActivationTokens.runnerInstanceId, runnerInstanceId));
|
|
322
|
+
if (!rawToken || !activationToken) throw new Error('Activation token was not created');
|
|
323
|
+
|
|
324
|
+
const result = await registerRunnerSession({
|
|
325
|
+
auth: runnersTestAuthClient,
|
|
326
|
+
credential: {kind: 'activation', activationTokenId: activationToken.id, workspaceId},
|
|
327
|
+
labels: ['linux', 'shipfox-managed'],
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
expect(result.session.labels).toEqual(['linux']);
|
|
331
|
+
});
|
|
299
332
|
});
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from '#db/runner-sessions.js';
|
|
9
9
|
import type {RunnerSession} from './entities/runner-session.js';
|
|
10
10
|
import {EmptyRunnerLabelsError} from './errors.js';
|
|
11
|
+
import {sanitizeRunnerLabelsOrThrow} from './runner-labels.js';
|
|
11
12
|
|
|
12
13
|
export interface RegisterRunnerSessionResult {
|
|
13
14
|
session: RunnerSession;
|
|
@@ -38,7 +39,13 @@ export async function registerRunnerSession(params: {
|
|
|
38
39
|
labels: string[];
|
|
39
40
|
toolCapabilities?: RunnerToolCapabilitiesDto | null;
|
|
40
41
|
}): Promise<RegisterRunnerSessionResult> {
|
|
41
|
-
const labels =
|
|
42
|
+
const labels =
|
|
43
|
+
params.credential.kind === 'manual'
|
|
44
|
+
? sanitizeRunnerLabelsOrThrow(params.labels, {
|
|
45
|
+
scope: 'manual',
|
|
46
|
+
source: 'manual runner registration',
|
|
47
|
+
})
|
|
48
|
+
: [...canonicalizeLabels(params.labels)];
|
|
42
49
|
if (labels.length === 0) throw new EmptyRunnerLabelsError();
|
|
43
50
|
|
|
44
51
|
const mode = params.credential.kind;
|
|
@@ -4,6 +4,7 @@ import type {EphemeralRegistrationToken} from '#core/entities/ephemeral-registra
|
|
|
4
4
|
import {
|
|
5
5
|
ActiveEphemeralRegistrationTokenExistsError,
|
|
6
6
|
ActiveEphemeralRegistrationTokensExistError,
|
|
7
|
+
EmptyRunnerLabelsError,
|
|
7
8
|
RegistrationTokenBatchExceedsReservationError,
|
|
8
9
|
RegistrationTokenConsumedError,
|
|
9
10
|
RegistrationTokenExpiredError,
|
|
@@ -11,11 +12,13 @@ import {
|
|
|
11
12
|
ReservationExpiredError,
|
|
12
13
|
ReservationNotFoundError,
|
|
13
14
|
} from '#core/errors.js';
|
|
15
|
+
import {sanitizeRunnerLabelsOrThrow} from '#core/runner-labels.js';
|
|
14
16
|
import {db} from './db.js';
|
|
15
17
|
import {
|
|
16
18
|
ephemeralRegistrationTokens,
|
|
17
19
|
toEphemeralRegistrationToken,
|
|
18
20
|
} from './schema/ephemeral-registration-tokens.js';
|
|
21
|
+
import {provisionerTokens} from './schema/provisioner-tokens.js';
|
|
19
22
|
import {reservations} from './schema/reservations.js';
|
|
20
23
|
import {providerRunners} from './schema/runner-instances.js';
|
|
21
24
|
import {runnerSessions, toRunnerSession} from './schema/runner-sessions.js';
|
|
@@ -307,6 +310,17 @@ export async function createRunnerSessionConsumingEphemeralToken(params: {
|
|
|
307
310
|
);
|
|
308
311
|
}
|
|
309
312
|
|
|
313
|
+
const [provisioner] = await tx
|
|
314
|
+
.select({scope: provisionerTokens.scope})
|
|
315
|
+
.from(provisionerTokens)
|
|
316
|
+
.where(eq(provisionerTokens.id, consumed[0].provisionerId))
|
|
317
|
+
.limit(1);
|
|
318
|
+
const labels = sanitizeRunnerLabelsOrThrow(params.labels, {
|
|
319
|
+
scope: provisioner?.scope ?? 'workspace',
|
|
320
|
+
source: 'ephemeral runner registration',
|
|
321
|
+
});
|
|
322
|
+
if (labels.length === 0) throw new EmptyRunnerLabelsError();
|
|
323
|
+
|
|
310
324
|
const [session] = await tx
|
|
311
325
|
.insert(runnerSessions)
|
|
312
326
|
.values({
|
|
@@ -316,7 +330,7 @@ export async function createRunnerSessionConsumingEphemeralToken(params: {
|
|
|
316
330
|
registrationTokenKind: 'ephemeral',
|
|
317
331
|
provisionerId: consumed[0].provisionerId,
|
|
318
332
|
providerRunnerId: consumed[0].providerRunnerId,
|
|
319
|
-
labels
|
|
333
|
+
labels,
|
|
320
334
|
toolCapabilities: params.toolCapabilities ?? null,
|
|
321
335
|
toolCapabilitiesReportedAt: params.toolCapabilities ? sql`now()` : null,
|
|
322
336
|
maxClaims: params.maxClaims,
|
package/src/db/index.ts
CHANGED