aboutsummaryrefslogtreecommitdiffhomepage
path: root/shared/server-commands/runners
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2023-07-31 14:34:36 +0200
committerChocobozzz <me@florianbigard.com>2023-08-11 15:02:33 +0200
commit3a4992633ee62d5edfbb484d9c6bcb3cf158489d (patch)
treee4510b39bdac9c318fdb4b47018d08f15368b8f0 /shared/server-commands/runners
parent04d1da5621d25d59bd5fa1543b725c497bf5d9a8 (diff)
downloadPeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.tar.gz
PeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.tar.zst
PeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.zip
Migrate server to ESM
Sorry for the very big commit that may lead to git log issues and merge conflicts, but it's a major step forward: * Server can be faster at startup because imports() are async and we can easily lazy import big modules * Angular doesn't seem to support ES import (with .js extension), so we had to correctly organize peertube into a monorepo: * Use yarn workspace feature * Use typescript reference projects for dependencies * Shared projects have been moved into "packages", each one is now a node module (with a dedicated package.json/tsconfig.json) * server/tools have been moved into apps/ and is now a dedicated app bundled and published on NPM so users don't have to build peertube cli tools manually * server/tests have been moved into packages/ so we don't compile them every time we want to run the server * Use isolatedModule option: * Had to move from const enum to const (https://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums) * Had to explictely specify "type" imports when used in decorators * Prefer tsx (that uses esbuild under the hood) instead of ts-node to load typescript files (tests with mocha or scripts): * To reduce test complexity as esbuild doesn't support decorator metadata, we only test server files that do not import server models * We still build tests files into js files for a faster CI * Remove unmaintained peertube CLI import script * Removed some barrels to speed up execution (less imports)
Diffstat (limited to 'shared/server-commands/runners')
-rw-r--r--shared/server-commands/runners/index.ts3
-rw-r--r--shared/server-commands/runners/runner-jobs-command.ts294
-rw-r--r--shared/server-commands/runners/runner-registration-tokens-command.ts55
-rw-r--r--shared/server-commands/runners/runners-command.ts78
4 files changed, 0 insertions, 430 deletions
diff --git a/shared/server-commands/runners/index.ts b/shared/server-commands/runners/index.ts
deleted file mode 100644
index 9e8e1baf2..000000000
--- a/shared/server-commands/runners/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
1export * from './runner-jobs-command'
2export * from './runner-registration-tokens-command'
3export * from './runners-command'
diff --git a/shared/server-commands/runners/runner-jobs-command.ts b/shared/server-commands/runners/runner-jobs-command.ts
deleted file mode 100644
index 0a0ffb5d3..000000000
--- a/shared/server-commands/runners/runner-jobs-command.ts
+++ /dev/null
@@ -1,294 +0,0 @@
1import { omit, pick, wait } from '@shared/core-utils'
2import {
3 AbortRunnerJobBody,
4 AcceptRunnerJobBody,
5 AcceptRunnerJobResult,
6 ErrorRunnerJobBody,
7 HttpStatusCode,
8 isHLSTranscodingPayloadSuccess,
9 isLiveRTMPHLSTranscodingUpdatePayload,
10 isWebVideoOrAudioMergeTranscodingPayloadSuccess,
11 ListRunnerJobsQuery,
12 RequestRunnerJobBody,
13 RequestRunnerJobResult,
14 ResultList,
15 RunnerJobAdmin,
16 RunnerJobLiveRTMPHLSTranscodingPayload,
17 RunnerJobPayload,
18 RunnerJobState,
19 RunnerJobSuccessBody,
20 RunnerJobSuccessPayload,
21 RunnerJobType,
22 RunnerJobUpdateBody,
23 RunnerJobVODPayload
24} from '@shared/models'
25import { unwrapBody } from '../requests'
26import { waitJobs } from '../server'
27import { AbstractCommand, OverrideCommandOptions } from '../shared'
28
29export class RunnerJobsCommand extends AbstractCommand {
30
31 list (options: OverrideCommandOptions & ListRunnerJobsQuery = {}) {
32 const path = '/api/v1/runners/jobs'
33
34 return this.getRequestBody<ResultList<RunnerJobAdmin>>({
35 ...options,
36
37 path,
38 query: pick(options, [ 'start', 'count', 'sort', 'search', 'stateOneOf' ]),
39 implicitToken: true,
40 defaultExpectedStatus: HttpStatusCode.OK_200
41 })
42 }
43
44 cancelByAdmin (options: OverrideCommandOptions & { jobUUID: string }) {
45 const path = '/api/v1/runners/jobs/' + options.jobUUID + '/cancel'
46
47 return this.postBodyRequest({
48 ...options,
49
50 path,
51 implicitToken: true,
52 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
53 })
54 }
55
56 deleteByAdmin (options: OverrideCommandOptions & { jobUUID: string }) {
57 const path = '/api/v1/runners/jobs/' + options.jobUUID
58
59 return this.deleteRequest({
60 ...options,
61
62 path,
63 implicitToken: true,
64 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
65 })
66 }
67
68 // ---------------------------------------------------------------------------
69
70 request (options: OverrideCommandOptions & RequestRunnerJobBody) {
71 const path = '/api/v1/runners/jobs/request'
72
73 return unwrapBody<RequestRunnerJobResult>(this.postBodyRequest({
74 ...options,
75
76 path,
77 fields: pick(options, [ 'runnerToken' ]),
78 implicitToken: false,
79 defaultExpectedStatus: HttpStatusCode.OK_200
80 }))
81 }
82
83 async requestVOD (options: OverrideCommandOptions & RequestRunnerJobBody) {
84 const vodTypes = new Set<RunnerJobType>([ 'vod-audio-merge-transcoding', 'vod-hls-transcoding', 'vod-web-video-transcoding' ])
85
86 const { availableJobs } = await this.request(options)
87
88 return {
89 availableJobs: availableJobs.filter(j => vodTypes.has(j.type))
90 } as RequestRunnerJobResult<RunnerJobVODPayload>
91 }
92
93 async requestLive (options: OverrideCommandOptions & RequestRunnerJobBody) {
94 const vodTypes = new Set<RunnerJobType>([ 'live-rtmp-hls-transcoding' ])
95
96 const { availableJobs } = await this.request(options)
97
98 return {
99 availableJobs: availableJobs.filter(j => vodTypes.has(j.type))
100 } as RequestRunnerJobResult<RunnerJobLiveRTMPHLSTranscodingPayload>
101 }
102
103 // ---------------------------------------------------------------------------
104
105 accept <T extends RunnerJobPayload = RunnerJobPayload> (options: OverrideCommandOptions & AcceptRunnerJobBody & { jobUUID: string }) {
106 const path = '/api/v1/runners/jobs/' + options.jobUUID + '/accept'
107
108 return unwrapBody<AcceptRunnerJobResult<T>>(this.postBodyRequest({
109 ...options,
110
111 path,
112 fields: pick(options, [ 'runnerToken' ]),
113 implicitToken: false,
114 defaultExpectedStatus: HttpStatusCode.OK_200
115 }))
116 }
117
118 abort (options: OverrideCommandOptions & AbortRunnerJobBody & { jobUUID: string }) {
119 const path = '/api/v1/runners/jobs/' + options.jobUUID + '/abort'
120
121 return this.postBodyRequest({
122 ...options,
123
124 path,
125 fields: pick(options, [ 'reason', 'jobToken', 'runnerToken' ]),
126 implicitToken: false,
127 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
128 })
129 }
130
131 update (options: OverrideCommandOptions & RunnerJobUpdateBody & { jobUUID: string }) {
132 const path = '/api/v1/runners/jobs/' + options.jobUUID + '/update'
133
134 const { payload } = options
135 const attaches: { [id: string]: any } = {}
136 let payloadWithoutFiles = payload
137
138 if (isLiveRTMPHLSTranscodingUpdatePayload(payload)) {
139 if (payload.masterPlaylistFile) {
140 attaches[`payload[masterPlaylistFile]`] = payload.masterPlaylistFile
141 }
142
143 attaches[`payload[resolutionPlaylistFile]`] = payload.resolutionPlaylistFile
144 attaches[`payload[videoChunkFile]`] = payload.videoChunkFile
145
146 payloadWithoutFiles = omit(payloadWithoutFiles as any, [ 'masterPlaylistFile', 'resolutionPlaylistFile', 'videoChunkFile' ])
147 }
148
149 return this.postUploadRequest({
150 ...options,
151
152 path,
153 fields: {
154 ...pick(options, [ 'progress', 'jobToken', 'runnerToken' ]),
155
156 payload: payloadWithoutFiles
157 },
158 attaches,
159 implicitToken: false,
160 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
161 })
162 }
163
164 error (options: OverrideCommandOptions & ErrorRunnerJobBody & { jobUUID: string }) {
165 const path = '/api/v1/runners/jobs/' + options.jobUUID + '/error'
166
167 return this.postBodyRequest({
168 ...options,
169
170 path,
171 fields: pick(options, [ 'message', 'jobToken', 'runnerToken' ]),
172 implicitToken: false,
173 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
174 })
175 }
176
177 success (options: OverrideCommandOptions & RunnerJobSuccessBody & { jobUUID: string }) {
178 const { payload } = options
179
180 const path = '/api/v1/runners/jobs/' + options.jobUUID + '/success'
181 const attaches: { [id: string]: any } = {}
182 let payloadWithoutFiles = payload
183
184 if ((isWebVideoOrAudioMergeTranscodingPayloadSuccess(payload) || isHLSTranscodingPayloadSuccess(payload)) && payload.videoFile) {
185 attaches[`payload[videoFile]`] = payload.videoFile
186
187 payloadWithoutFiles = omit(payloadWithoutFiles as any, [ 'videoFile' ])
188 }
189
190 if (isHLSTranscodingPayloadSuccess(payload) && payload.resolutionPlaylistFile) {
191 attaches[`payload[resolutionPlaylistFile]`] = payload.resolutionPlaylistFile
192
193 payloadWithoutFiles = omit(payloadWithoutFiles as any, [ 'resolutionPlaylistFile' ])
194 }
195
196 return this.postUploadRequest({
197 ...options,
198
199 path,
200 attaches,
201 fields: {
202 ...pick(options, [ 'jobToken', 'runnerToken' ]),
203
204 payload: payloadWithoutFiles
205 },
206 implicitToken: false,
207 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
208 })
209 }
210
211 getJobFile (options: OverrideCommandOptions & { url: string, jobToken: string, runnerToken: string }) {
212 const { host, protocol, pathname } = new URL(options.url)
213
214 return this.postBodyRequest({
215 url: `${protocol}//${host}`,
216 path: pathname,
217
218 fields: pick(options, [ 'jobToken', 'runnerToken' ]),
219 implicitToken: false,
220 defaultExpectedStatus: HttpStatusCode.OK_200
221 })
222 }
223
224 // ---------------------------------------------------------------------------
225
226 async autoAccept (options: OverrideCommandOptions & RequestRunnerJobBody & { type?: RunnerJobType }) {
227 const { availableJobs } = await this.request(options)
228
229 const job = options.type
230 ? availableJobs.find(j => j.type === options.type)
231 : availableJobs[0]
232
233 return this.accept({ ...options, jobUUID: job.uuid })
234 }
235
236 async autoProcessWebVideoJob (runnerToken: string, jobUUIDToProcess?: string) {
237 let jobUUID = jobUUIDToProcess
238
239 if (!jobUUID) {
240 const { availableJobs } = await this.request({ runnerToken })
241 jobUUID = availableJobs[0].uuid
242 }
243
244 const { job } = await this.accept({ runnerToken, jobUUID })
245 const jobToken = job.jobToken
246
247 const payload: RunnerJobSuccessPayload = { videoFile: 'video_short.mp4' }
248 await this.success({ runnerToken, jobUUID, jobToken, payload })
249
250 await waitJobs([ this.server ])
251
252 return job
253 }
254
255 async cancelAllJobs (options: { state?: RunnerJobState } = {}) {
256 const { state } = options
257
258 const { data } = await this.list({ count: 100 })
259
260 const allowedStates = new Set<RunnerJobState>([
261 RunnerJobState.PENDING,
262 RunnerJobState.PROCESSING,
263 RunnerJobState.WAITING_FOR_PARENT_JOB
264 ])
265
266 for (const job of data) {
267 if (state && job.state.id !== state) continue
268 else if (allowedStates.has(job.state.id) !== true) continue
269
270 await this.cancelByAdmin({ jobUUID: job.uuid })
271 }
272 }
273
274 async getJob (options: OverrideCommandOptions & { uuid: string }) {
275 const { data } = await this.list({ ...options, count: 100, sort: '-updatedAt' })
276
277 return data.find(j => j.uuid === options.uuid)
278 }
279
280 async requestLiveJob (runnerToken: string) {
281 let availableJobs: RequestRunnerJobResult<RunnerJobLiveRTMPHLSTranscodingPayload>['availableJobs'] = []
282
283 while (availableJobs.length === 0) {
284 const result = await this.requestLive({ runnerToken })
285 availableJobs = result.availableJobs
286
287 if (availableJobs.length === 1) break
288
289 await wait(150)
290 }
291
292 return availableJobs[0]
293 }
294}
diff --git a/shared/server-commands/runners/runner-registration-tokens-command.ts b/shared/server-commands/runners/runner-registration-tokens-command.ts
deleted file mode 100644
index e4f2e3d95..000000000
--- a/shared/server-commands/runners/runner-registration-tokens-command.ts
+++ /dev/null
@@ -1,55 +0,0 @@
1import { pick } from '@shared/core-utils'
2import { HttpStatusCode, ResultList, RunnerRegistrationToken } from '@shared/models'
3import { AbstractCommand, OverrideCommandOptions } from '../shared'
4
5export class RunnerRegistrationTokensCommand extends AbstractCommand {
6
7 list (options: OverrideCommandOptions & {
8 start?: number
9 count?: number
10 sort?: string
11 } = {}) {
12 const path = '/api/v1/runners/registration-tokens'
13
14 return this.getRequestBody<ResultList<RunnerRegistrationToken>>({
15 ...options,
16
17 path,
18 query: pick(options, [ 'start', 'count', 'sort' ]),
19 implicitToken: true,
20 defaultExpectedStatus: HttpStatusCode.OK_200
21 })
22 }
23
24 generate (options: OverrideCommandOptions = {}) {
25 const path = '/api/v1/runners/registration-tokens/generate'
26
27 return this.postBodyRequest({
28 ...options,
29
30 path,
31 implicitToken: true,
32 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
33 })
34 }
35
36 delete (options: OverrideCommandOptions & {
37 id: number
38 }) {
39 const path = '/api/v1/runners/registration-tokens/' + options.id
40
41 return this.deleteRequest({
42 ...options,
43
44 path,
45 implicitToken: true,
46 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
47 })
48 }
49
50 async getFirstRegistrationToken (options: OverrideCommandOptions = {}) {
51 const { data } = await this.list(options)
52
53 return data[0].registrationToken
54 }
55}
diff --git a/shared/server-commands/runners/runners-command.ts b/shared/server-commands/runners/runners-command.ts
deleted file mode 100644
index b0083e841..000000000
--- a/shared/server-commands/runners/runners-command.ts
+++ /dev/null
@@ -1,78 +0,0 @@
1import { pick } from '@shared/core-utils'
2import { buildUUID } from '@shared/extra-utils'
3import { HttpStatusCode, RegisterRunnerBody, RegisterRunnerResult, ResultList, Runner, UnregisterRunnerBody } from '@shared/models'
4import { unwrapBody } from '../requests'
5import { AbstractCommand, OverrideCommandOptions } from '../shared'
6
7export class RunnersCommand extends AbstractCommand {
8
9 list (options: OverrideCommandOptions & {
10 start?: number
11 count?: number
12 sort?: string
13 } = {}) {
14 const path = '/api/v1/runners'
15
16 return this.getRequestBody<ResultList<Runner>>({
17 ...options,
18
19 path,
20 query: pick(options, [ 'start', 'count', 'sort' ]),
21 implicitToken: true,
22 defaultExpectedStatus: HttpStatusCode.OK_200
23 })
24 }
25
26 register (options: OverrideCommandOptions & RegisterRunnerBody) {
27 const path = '/api/v1/runners/register'
28
29 return unwrapBody<RegisterRunnerResult>(this.postBodyRequest({
30 ...options,
31
32 path,
33 fields: pick(options, [ 'name', 'registrationToken', 'description' ]),
34 implicitToken: true,
35 defaultExpectedStatus: HttpStatusCode.OK_200
36 }))
37 }
38
39 unregister (options: OverrideCommandOptions & UnregisterRunnerBody) {
40 const path = '/api/v1/runners/unregister'
41
42 return this.postBodyRequest({
43 ...options,
44
45 path,
46 fields: pick(options, [ 'runnerToken' ]),
47 implicitToken: false,
48 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
49 })
50 }
51
52 delete (options: OverrideCommandOptions & {
53 id: number
54 }) {
55 const path = '/api/v1/runners/' + options.id
56
57 return this.deleteRequest({
58 ...options,
59
60 path,
61 implicitToken: true,
62 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
63 })
64 }
65
66 // ---------------------------------------------------------------------------
67
68 async autoRegisterRunner () {
69 const { data } = await this.server.runnerRegistrationTokens.list({ sort: 'createdAt' })
70
71 const { runnerToken } = await this.register({
72 name: 'runner ' + buildUUID(),
73 registrationToken: data[0].registrationToken
74 })
75
76 return runnerToken
77 }
78}