@shipfox/api-runners 12.7.0 → 13.0.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.
Files changed (55) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/CHANGELOG.md +12 -0
  3. package/dist/db/index.d.ts +1 -1
  4. package/dist/db/index.d.ts.map +1 -1
  5. package/dist/db/index.js +1 -1
  6. package/dist/db/index.js.map +1 -1
  7. package/dist/db/job-executions.d.ts +8 -24
  8. package/dist/db/job-executions.d.ts.map +1 -1
  9. package/dist/db/job-executions.js +62 -81
  10. package/dist/db/job-executions.js.map +1 -1
  11. package/dist/db/reservations.d.ts.map +1 -1
  12. package/dist/db/reservations.js +6 -4
  13. package/dist/db/reservations.js.map +1 -1
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +5 -4
  17. package/dist/index.js.map +1 -1
  18. package/dist/presentation/index.d.ts +2 -1
  19. package/dist/presentation/index.d.ts.map +1 -1
  20. package/dist/presentation/index.js +2 -1
  21. package/dist/presentation/index.js.map +1 -1
  22. package/dist/presentation/inter-module.d.ts +0 -1
  23. package/dist/presentation/inter-module.d.ts.map +1 -1
  24. package/dist/presentation/inter-module.js +2 -25
  25. package/dist/presentation/inter-module.js.map +1 -1
  26. package/dist/presentation/subscribers/on-workflows-job-execution-queued.d.ts +3 -0
  27. package/dist/presentation/subscribers/on-workflows-job-execution-queued.d.ts.map +1 -0
  28. package/dist/presentation/subscribers/on-workflows-job-execution-queued.js +21 -0
  29. package/dist/presentation/subscribers/on-workflows-job-execution-queued.js.map +1 -0
  30. package/dist/presentation/subscribers/on-workflows-job-execution-terminated.d.ts +3 -0
  31. package/dist/presentation/subscribers/on-workflows-job-execution-terminated.d.ts.map +1 -0
  32. package/dist/presentation/subscribers/{on-workflows-job-execution-timed-out.js → on-workflows-job-execution-terminated.js} +5 -4
  33. package/dist/presentation/subscribers/on-workflows-job-execution-terminated.js.map +1 -0
  34. package/dist/tsconfig.test.tsbuildinfo +1 -1
  35. package/package.json +3 -3
  36. package/src/db/index.ts +0 -2
  37. package/src/db/job-executions.test.ts +105 -335
  38. package/src/db/job-executions.ts +70 -91
  39. package/src/db/reservations.ts +6 -3
  40. package/src/db/runner-instances.test.ts +100 -0
  41. package/src/index.test.ts +1 -0
  42. package/src/index.ts +8 -7
  43. package/src/presentation/index.ts +2 -1
  44. package/src/presentation/inter-module.ts +2 -40
  45. package/src/presentation/subscribers/on-workflows-job-execution-queued.test.ts +57 -0
  46. package/src/presentation/subscribers/on-workflows-job-execution-queued.ts +26 -0
  47. package/src/presentation/subscribers/on-workflows-job-execution-terminated.test.ts +142 -0
  48. package/src/presentation/subscribers/{on-workflows-job-execution-timed-out.ts → on-workflows-job-execution-terminated.ts} +5 -4
  49. package/test/factories/pending-job.ts +3 -0
  50. package/tsconfig.build.tsbuildinfo +1 -1
  51. package/dist/presentation/subscribers/on-workflows-job-execution-timed-out.d.ts +0 -3
  52. package/dist/presentation/subscribers/on-workflows-job-execution-timed-out.d.ts.map +0 -1
  53. package/dist/presentation/subscribers/on-workflows-job-execution-timed-out.js.map +0 -1
  54. package/src/presentation/inter-module.test.ts +0 -15
  55. package/src/presentation/subscribers/on-workflows-job-execution-timed-out.test.ts +0 -163
@@ -0,0 +1,142 @@
1
+ import type {WorkflowsJobExecutionTerminatedEventDto} from '@shipfox/api-workflows-dto';
2
+ import {eq} from 'drizzle-orm';
3
+ import {db} from '#db/db.js';
4
+ import {reservations} from '#db/schema/reservations.js';
5
+ import {providerRunners} from '#db/schema/runner-instances.js';
6
+ import {runningJobExecutions} from '#db/schema/running-job-executions.js';
7
+ import {providerRunnerFactory, runnerSessionFactory} from '#test/index.js';
8
+ import {onWorkflowsJobExecutionTerminated} from './on-workflows-job-execution-terminated.js';
9
+
10
+ describe('onWorkflowsJobExecutionTerminated', () => {
11
+ it('releases a terminal runner reservation once no uncancelled lease remains', async () => {
12
+ const workspaceId = crypto.randomUUID();
13
+ const provisionerId = crypto.randomUUID();
14
+ const providerRunnerId = crypto.randomUUID();
15
+ const workflowRunId = crypto.randomUUID();
16
+ const workflowRunAttemptId = crypto.randomUUID();
17
+ const jobId = crypto.randomUUID();
18
+ const jobExecutionId = crypto.randomUUID();
19
+ const projectId = crypto.randomUUID();
20
+ const runnerSession = await runnerSessionFactory.create({workspaceId});
21
+ const [reservation] = await db()
22
+ .insert(reservations)
23
+ .values({
24
+ workspaceId,
25
+ provisionerId,
26
+ requiredLabels: ['linux'],
27
+ count: 1,
28
+ expiresAt: new Date(Date.now() + 60_000),
29
+ })
30
+ .returning();
31
+ if (!reservation) throw new Error('Expected reservation');
32
+ await providerRunnerFactory.create({
33
+ workspaceId,
34
+ provisionerId,
35
+ providerRunnerId,
36
+ reservationId: reservation.id,
37
+ runnerSessionId: runnerSession.id,
38
+ state: 'terminated',
39
+ terminatedAt: new Date(),
40
+ });
41
+ await db()
42
+ .insert(runningJobExecutions)
43
+ .values({
44
+ workspaceId,
45
+ workflowRunId,
46
+ workflowRunAttemptId,
47
+ jobId,
48
+ jobExecutionId,
49
+ projectId,
50
+ runnerSessionId: runnerSession.id,
51
+ provisionerId,
52
+ providerRunnerId,
53
+ requiredLabels: ['linux'],
54
+ runnerLabels: ['linux'],
55
+ });
56
+ const payload: WorkflowsJobExecutionTerminatedEventDto = {
57
+ jobId,
58
+ jobExecutionId,
59
+ workflowRunId,
60
+ workflowRunAttemptId,
61
+ status: 'cancelled',
62
+ statusReason: 'run_cancelled',
63
+ statusReasonMessage: null,
64
+ };
65
+
66
+ await onWorkflowsJobExecutionTerminated(payload);
67
+ await onWorkflowsJobExecutionTerminated(payload);
68
+
69
+ expect(
70
+ await db().select().from(reservations).where(eq(reservations.id, reservation.id)),
71
+ ).toHaveLength(0);
72
+ const [runner] = await db()
73
+ .select()
74
+ .from(providerRunners)
75
+ .where(eq(providerRunners.providerRunnerId, providerRunnerId));
76
+ expect(runner?.reservationReleasedAt).toBeInstanceOf(Date);
77
+ const [lease] = await db()
78
+ .select()
79
+ .from(runningJobExecutions)
80
+ .where(eq(runningJobExecutions.jobExecutionId, jobExecutionId));
81
+ expect(lease?.cancellationRequestedAt).toBeInstanceOf(Date);
82
+ });
83
+
84
+ it('keeps the reservation while the provider runner is non-terminal', async () => {
85
+ const workspaceId = crypto.randomUUID();
86
+ const provisionerId = crypto.randomUUID();
87
+ const providerRunnerId = crypto.randomUUID();
88
+ const runnerSession = await runnerSessionFactory.create({workspaceId});
89
+ const [reservation] = await db()
90
+ .insert(reservations)
91
+ .values({
92
+ workspaceId,
93
+ provisionerId,
94
+ requiredLabels: ['linux'],
95
+ count: 1,
96
+ expiresAt: new Date(Date.now() + 60_000),
97
+ })
98
+ .returning();
99
+ if (!reservation) throw new Error('Expected reservation');
100
+ await providerRunnerFactory.create({
101
+ workspaceId,
102
+ provisionerId,
103
+ providerRunnerId,
104
+ reservationId: reservation.id,
105
+ runnerSessionId: runnerSession.id,
106
+ state: 'running',
107
+ });
108
+ const workflowRunId = crypto.randomUUID();
109
+ const workflowRunAttemptId = crypto.randomUUID();
110
+ const jobId = crypto.randomUUID();
111
+ const jobExecutionId = crypto.randomUUID();
112
+ await db()
113
+ .insert(runningJobExecutions)
114
+ .values({
115
+ workspaceId,
116
+ workflowRunId,
117
+ workflowRunAttemptId,
118
+ jobId,
119
+ jobExecutionId,
120
+ projectId: crypto.randomUUID(),
121
+ runnerSessionId: runnerSession.id,
122
+ provisionerId,
123
+ providerRunnerId,
124
+ requiredLabels: ['linux'],
125
+ runnerLabels: ['linux'],
126
+ });
127
+
128
+ await onWorkflowsJobExecutionTerminated({
129
+ jobId,
130
+ jobExecutionId,
131
+ workflowRunId,
132
+ workflowRunAttemptId,
133
+ status: 'failed',
134
+ statusReason: 'step_failed',
135
+ statusReasonMessage: null,
136
+ });
137
+
138
+ expect(
139
+ await db().select().from(reservations).where(eq(reservations.id, reservation.id)),
140
+ ).toHaveLength(1);
141
+ });
142
+ });
@@ -1,17 +1,18 @@
1
- import type {WorkflowsJobExecutionTimedOutEventDto} from '@shipfox/api-workflows-dto';
1
+ import type {WorkflowsJobExecutionTerminatedEventDto} from '@shipfox/api-workflows-dto';
2
2
  import {logger} from '@shipfox/node-opentelemetry';
3
3
  import {reconcileTerminalJobExecution} from '#db/job-executions.js';
4
4
 
5
- export async function onWorkflowsJobExecutionTimedOut(
6
- payload: WorkflowsJobExecutionTimedOutEventDto,
5
+ export async function onWorkflowsJobExecutionTerminated(
6
+ payload: WorkflowsJobExecutionTerminatedEventDto,
7
7
  ): Promise<void> {
8
8
  logger().info(
9
9
  {
10
10
  jobId: payload.jobId,
11
11
  jobExecutionId: payload.jobExecutionId,
12
12
  workflowRunAttemptId: payload.workflowRunAttemptId,
13
+ status: payload.status,
13
14
  },
14
- 'Reconciling runner state for timed-out job execution',
15
+ 'Reconciling runner state for terminal job execution',
15
16
  );
16
17
  await reconcileTerminalJobExecution({jobExecutionId: payload.jobExecutionId});
17
18
  }
@@ -9,6 +9,7 @@ interface PendingJobAttrs {
9
9
  jobExecutionId: string;
10
10
  projectId: string;
11
11
  requiredLabels: string[];
12
+ queuedAt: Date;
12
13
  }
13
14
 
14
15
  export const pendingJobFactory = Factory.define<PendingJobAttrs>(({onCreate}) => {
@@ -28,6 +29,7 @@ export const pendingJobFactory = Factory.define<PendingJobAttrs>(({onCreate}) =>
28
29
  jobExecutionId: attrs.jobExecutionId,
29
30
  projectId: attrs.projectId,
30
31
  requiredLabels: attrs.requiredLabels,
32
+ queuedAt: attrs.queuedAt,
31
33
  });
32
34
  return attrs;
33
35
  });
@@ -40,5 +42,6 @@ export const pendingJobFactory = Factory.define<PendingJobAttrs>(({onCreate}) =>
40
42
  jobExecutionId,
41
43
  projectId,
42
44
  requiredLabels: ['linux'],
45
+ queuedAt: new Date(),
43
46
  };
44
47
  });