diff options
Diffstat (limited to 'server/controllers/api/runners')
-rw-r--r-- | server/controllers/api/runners/index.ts | 20 | ||||
-rw-r--r-- | server/controllers/api/runners/jobs-files.ts | 112 | ||||
-rw-r--r-- | server/controllers/api/runners/jobs.ts | 416 | ||||
-rw-r--r-- | server/controllers/api/runners/manage-runners.ts | 112 | ||||
-rw-r--r-- | server/controllers/api/runners/registration-tokens.ts | 91 |
5 files changed, 0 insertions, 751 deletions
diff --git a/server/controllers/api/runners/index.ts b/server/controllers/api/runners/index.ts deleted file mode 100644 index 9998fe4cc..000000000 --- a/server/controllers/api/runners/index.ts +++ /dev/null | |||
@@ -1,20 +0,0 @@ | |||
1 | import express from 'express' | ||
2 | import { runnerJobsRouter } from './jobs' | ||
3 | import { runnerJobFilesRouter } from './jobs-files' | ||
4 | import { manageRunnersRouter } from './manage-runners' | ||
5 | import { runnerRegistrationTokensRouter } from './registration-tokens' | ||
6 | |||
7 | const runnersRouter = express.Router() | ||
8 | |||
9 | // No api route limiter here, they are defined in child routers | ||
10 | |||
11 | runnersRouter.use('/', manageRunnersRouter) | ||
12 | runnersRouter.use('/', runnerJobsRouter) | ||
13 | runnersRouter.use('/', runnerJobFilesRouter) | ||
14 | runnersRouter.use('/', runnerRegistrationTokensRouter) | ||
15 | |||
16 | // --------------------------------------------------------------------------- | ||
17 | |||
18 | export { | ||
19 | runnersRouter | ||
20 | } | ||
diff --git a/server/controllers/api/runners/jobs-files.ts b/server/controllers/api/runners/jobs-files.ts deleted file mode 100644 index d28f43701..000000000 --- a/server/controllers/api/runners/jobs-files.ts +++ /dev/null | |||
@@ -1,112 +0,0 @@ | |||
1 | import express from 'express' | ||
2 | import { logger, loggerTagsFactory } from '@server/helpers/logger' | ||
3 | import { proxifyHLS, proxifyWebVideoFile } from '@server/lib/object-storage' | ||
4 | import { VideoPathManager } from '@server/lib/video-path-manager' | ||
5 | import { getStudioTaskFilePath } from '@server/lib/video-studio' | ||
6 | import { apiRateLimiter, asyncMiddleware } from '@server/middlewares' | ||
7 | import { jobOfRunnerGetValidatorFactory } from '@server/middlewares/validators/runners' | ||
8 | import { | ||
9 | runnerJobGetVideoStudioTaskFileValidator, | ||
10 | runnerJobGetVideoTranscodingFileValidator | ||
11 | } from '@server/middlewares/validators/runners/job-files' | ||
12 | import { RunnerJobState, VideoStorage } from '@shared/models' | ||
13 | |||
14 | const lTags = loggerTagsFactory('api', 'runner') | ||
15 | |||
16 | const runnerJobFilesRouter = express.Router() | ||
17 | |||
18 | runnerJobFilesRouter.post('/jobs/:jobUUID/files/videos/:videoId/max-quality', | ||
19 | apiRateLimiter, | ||
20 | asyncMiddleware(jobOfRunnerGetValidatorFactory([ RunnerJobState.PROCESSING ])), | ||
21 | asyncMiddleware(runnerJobGetVideoTranscodingFileValidator), | ||
22 | asyncMiddleware(getMaxQualityVideoFile) | ||
23 | ) | ||
24 | |||
25 | runnerJobFilesRouter.post('/jobs/:jobUUID/files/videos/:videoId/previews/max-quality', | ||
26 | apiRateLimiter, | ||
27 | asyncMiddleware(jobOfRunnerGetValidatorFactory([ RunnerJobState.PROCESSING ])), | ||
28 | asyncMiddleware(runnerJobGetVideoTranscodingFileValidator), | ||
29 | getMaxQualityVideoPreview | ||
30 | ) | ||
31 | |||
32 | runnerJobFilesRouter.post('/jobs/:jobUUID/files/videos/:videoId/studio/task-files/:filename', | ||
33 | apiRateLimiter, | ||
34 | asyncMiddleware(jobOfRunnerGetValidatorFactory([ RunnerJobState.PROCESSING ])), | ||
35 | asyncMiddleware(runnerJobGetVideoTranscodingFileValidator), | ||
36 | runnerJobGetVideoStudioTaskFileValidator, | ||
37 | getVideoStudioTaskFile | ||
38 | ) | ||
39 | |||
40 | // --------------------------------------------------------------------------- | ||
41 | |||
42 | export { | ||
43 | runnerJobFilesRouter | ||
44 | } | ||
45 | |||
46 | // --------------------------------------------------------------------------- | ||
47 | |||
48 | async function getMaxQualityVideoFile (req: express.Request, res: express.Response) { | ||
49 | const runnerJob = res.locals.runnerJob | ||
50 | const runner = runnerJob.Runner | ||
51 | const video = res.locals.videoAll | ||
52 | |||
53 | logger.info( | ||
54 | 'Get max quality file of video %s of job %s for runner %s', video.uuid, runnerJob.uuid, runner.name, | ||
55 | lTags(runner.name, runnerJob.id, runnerJob.type) | ||
56 | ) | ||
57 | |||
58 | const file = video.getMaxQualityFile() | ||
59 | |||
60 | if (file.storage === VideoStorage.OBJECT_STORAGE) { | ||
61 | if (file.isHLS()) { | ||
62 | return proxifyHLS({ | ||
63 | req, | ||
64 | res, | ||
65 | filename: file.filename, | ||
66 | playlist: video.getHLSPlaylist(), | ||
67 | reinjectVideoFileToken: false, | ||
68 | video | ||
69 | }) | ||
70 | } | ||
71 | |||
72 | // Web video | ||
73 | return proxifyWebVideoFile({ | ||
74 | req, | ||
75 | res, | ||
76 | filename: file.filename | ||
77 | }) | ||
78 | } | ||
79 | |||
80 | return VideoPathManager.Instance.makeAvailableVideoFile(file, videoPath => { | ||
81 | return res.sendFile(videoPath) | ||
82 | }) | ||
83 | } | ||
84 | |||
85 | function getMaxQualityVideoPreview (req: express.Request, res: express.Response) { | ||
86 | const runnerJob = res.locals.runnerJob | ||
87 | const runner = runnerJob.Runner | ||
88 | const video = res.locals.videoAll | ||
89 | |||
90 | logger.info( | ||
91 | 'Get max quality preview file of video %s of job %s for runner %s', video.uuid, runnerJob.uuid, runner.name, | ||
92 | lTags(runner.name, runnerJob.id, runnerJob.type) | ||
93 | ) | ||
94 | |||
95 | const file = video.getPreview() | ||
96 | |||
97 | return res.sendFile(file.getPath()) | ||
98 | } | ||
99 | |||
100 | function getVideoStudioTaskFile (req: express.Request, res: express.Response) { | ||
101 | const runnerJob = res.locals.runnerJob | ||
102 | const runner = runnerJob.Runner | ||
103 | const video = res.locals.videoAll | ||
104 | const filename = req.params.filename | ||
105 | |||
106 | logger.info( | ||
107 | 'Get video studio task file %s of video %s of job %s for runner %s', filename, video.uuid, runnerJob.uuid, runner.name, | ||
108 | lTags(runner.name, runnerJob.id, runnerJob.type) | ||
109 | ) | ||
110 | |||
111 | return res.sendFile(getStudioTaskFilePath(filename)) | ||
112 | } | ||
diff --git a/server/controllers/api/runners/jobs.ts b/server/controllers/api/runners/jobs.ts deleted file mode 100644 index e9e2ddf49..000000000 --- a/server/controllers/api/runners/jobs.ts +++ /dev/null | |||
@@ -1,416 +0,0 @@ | |||
1 | import express, { UploadFiles } from 'express' | ||
2 | import { retryTransactionWrapper } from '@server/helpers/database-utils' | ||
3 | import { createReqFiles } from '@server/helpers/express-utils' | ||
4 | import { logger, loggerTagsFactory } from '@server/helpers/logger' | ||
5 | import { generateRunnerJobToken } from '@server/helpers/token-generator' | ||
6 | import { MIMETYPES } from '@server/initializers/constants' | ||
7 | import { sequelizeTypescript } from '@server/initializers/database' | ||
8 | import { getRunnerJobHandlerClass, runnerJobCanBeCancelled, updateLastRunnerContact } from '@server/lib/runners' | ||
9 | import { | ||
10 | apiRateLimiter, | ||
11 | asyncMiddleware, | ||
12 | authenticate, | ||
13 | ensureUserHasRight, | ||
14 | paginationValidator, | ||
15 | runnerJobsSortValidator, | ||
16 | setDefaultPagination, | ||
17 | setDefaultSort | ||
18 | } from '@server/middlewares' | ||
19 | import { | ||
20 | abortRunnerJobValidator, | ||
21 | acceptRunnerJobValidator, | ||
22 | cancelRunnerJobValidator, | ||
23 | errorRunnerJobValidator, | ||
24 | getRunnerFromTokenValidator, | ||
25 | jobOfRunnerGetValidatorFactory, | ||
26 | listRunnerJobsValidator, | ||
27 | runnerJobGetValidator, | ||
28 | successRunnerJobValidator, | ||
29 | updateRunnerJobValidator | ||
30 | } from '@server/middlewares/validators/runners' | ||
31 | import { RunnerModel } from '@server/models/runner/runner' | ||
32 | import { RunnerJobModel } from '@server/models/runner/runner-job' | ||
33 | import { | ||
34 | AbortRunnerJobBody, | ||
35 | AcceptRunnerJobResult, | ||
36 | ErrorRunnerJobBody, | ||
37 | HttpStatusCode, | ||
38 | ListRunnerJobsQuery, | ||
39 | LiveRTMPHLSTranscodingUpdatePayload, | ||
40 | RequestRunnerJobResult, | ||
41 | RunnerJobState, | ||
42 | RunnerJobSuccessBody, | ||
43 | RunnerJobSuccessPayload, | ||
44 | RunnerJobType, | ||
45 | RunnerJobUpdateBody, | ||
46 | RunnerJobUpdatePayload, | ||
47 | ServerErrorCode, | ||
48 | UserRight, | ||
49 | VideoStudioTranscodingSuccess, | ||
50 | VODAudioMergeTranscodingSuccess, | ||
51 | VODHLSTranscodingSuccess, | ||
52 | VODWebVideoTranscodingSuccess | ||
53 | } from '@shared/models' | ||
54 | |||
55 | const postRunnerJobSuccessVideoFiles = createReqFiles( | ||
56 | [ 'payload[videoFile]', 'payload[resolutionPlaylistFile]' ], | ||
57 | { ...MIMETYPES.VIDEO.MIMETYPE_EXT, ...MIMETYPES.M3U8.MIMETYPE_EXT } | ||
58 | ) | ||
59 | |||
60 | const runnerJobUpdateVideoFiles = createReqFiles( | ||
61 | [ 'payload[videoChunkFile]', 'payload[resolutionPlaylistFile]', 'payload[masterPlaylistFile]' ], | ||
62 | { ...MIMETYPES.VIDEO.MIMETYPE_EXT, ...MIMETYPES.M3U8.MIMETYPE_EXT } | ||
63 | ) | ||
64 | |||
65 | const lTags = loggerTagsFactory('api', 'runner') | ||
66 | |||
67 | const runnerJobsRouter = express.Router() | ||
68 | |||
69 | // --------------------------------------------------------------------------- | ||
70 | // Controllers for runners | ||
71 | // --------------------------------------------------------------------------- | ||
72 | |||
73 | runnerJobsRouter.post('/jobs/request', | ||
74 | apiRateLimiter, | ||
75 | asyncMiddleware(getRunnerFromTokenValidator), | ||
76 | asyncMiddleware(requestRunnerJob) | ||
77 | ) | ||
78 | |||
79 | runnerJobsRouter.post('/jobs/:jobUUID/accept', | ||
80 | apiRateLimiter, | ||
81 | asyncMiddleware(runnerJobGetValidator), | ||
82 | acceptRunnerJobValidator, | ||
83 | asyncMiddleware(getRunnerFromTokenValidator), | ||
84 | asyncMiddleware(acceptRunnerJob) | ||
85 | ) | ||
86 | |||
87 | runnerJobsRouter.post('/jobs/:jobUUID/abort', | ||
88 | apiRateLimiter, | ||
89 | asyncMiddleware(jobOfRunnerGetValidatorFactory([ RunnerJobState.PROCESSING ])), | ||
90 | abortRunnerJobValidator, | ||
91 | asyncMiddleware(abortRunnerJob) | ||
92 | ) | ||
93 | |||
94 | runnerJobsRouter.post('/jobs/:jobUUID/update', | ||
95 | runnerJobUpdateVideoFiles, | ||
96 | apiRateLimiter, // Has to be after multer middleware to parse runner token | ||
97 | asyncMiddleware(jobOfRunnerGetValidatorFactory([ RunnerJobState.PROCESSING, RunnerJobState.COMPLETING, RunnerJobState.COMPLETED ])), | ||
98 | updateRunnerJobValidator, | ||
99 | asyncMiddleware(updateRunnerJobController) | ||
100 | ) | ||
101 | |||
102 | runnerJobsRouter.post('/jobs/:jobUUID/error', | ||
103 | asyncMiddleware(jobOfRunnerGetValidatorFactory([ RunnerJobState.PROCESSING ])), | ||
104 | errorRunnerJobValidator, | ||
105 | asyncMiddleware(errorRunnerJob) | ||
106 | ) | ||
107 | |||
108 | runnerJobsRouter.post('/jobs/:jobUUID/success', | ||
109 | postRunnerJobSuccessVideoFiles, | ||
110 | apiRateLimiter, // Has to be after multer middleware to parse runner token | ||
111 | asyncMiddleware(jobOfRunnerGetValidatorFactory([ RunnerJobState.PROCESSING ])), | ||
112 | successRunnerJobValidator, | ||
113 | asyncMiddleware(postRunnerJobSuccess) | ||
114 | ) | ||
115 | |||
116 | // --------------------------------------------------------------------------- | ||
117 | // Controllers for admins | ||
118 | // --------------------------------------------------------------------------- | ||
119 | |||
120 | runnerJobsRouter.post('/jobs/:jobUUID/cancel', | ||
121 | authenticate, | ||
122 | ensureUserHasRight(UserRight.MANAGE_RUNNERS), | ||
123 | asyncMiddleware(runnerJobGetValidator), | ||
124 | cancelRunnerJobValidator, | ||
125 | asyncMiddleware(cancelRunnerJob) | ||
126 | ) | ||
127 | |||
128 | runnerJobsRouter.get('/jobs', | ||
129 | authenticate, | ||
130 | ensureUserHasRight(UserRight.MANAGE_RUNNERS), | ||
131 | paginationValidator, | ||
132 | runnerJobsSortValidator, | ||
133 | setDefaultSort, | ||
134 | setDefaultPagination, | ||
135 | listRunnerJobsValidator, | ||
136 | asyncMiddleware(listRunnerJobs) | ||
137 | ) | ||
138 | |||
139 | runnerJobsRouter.delete('/jobs/:jobUUID', | ||
140 | authenticate, | ||
141 | ensureUserHasRight(UserRight.MANAGE_RUNNERS), | ||
142 | asyncMiddleware(runnerJobGetValidator), | ||
143 | asyncMiddleware(deleteRunnerJob) | ||
144 | ) | ||
145 | |||
146 | // --------------------------------------------------------------------------- | ||
147 | |||
148 | export { | ||
149 | runnerJobsRouter | ||
150 | } | ||
151 | |||
152 | // --------------------------------------------------------------------------- | ||
153 | |||
154 | // --------------------------------------------------------------------------- | ||
155 | // Controllers for runners | ||
156 | // --------------------------------------------------------------------------- | ||
157 | |||
158 | async function requestRunnerJob (req: express.Request, res: express.Response) { | ||
159 | const runner = res.locals.runner | ||
160 | const availableJobs = await RunnerJobModel.listAvailableJobs() | ||
161 | |||
162 | logger.debug('Runner %s requests for a job.', runner.name, { availableJobs, ...lTags(runner.name) }) | ||
163 | |||
164 | const result: RequestRunnerJobResult = { | ||
165 | availableJobs: availableJobs.map(j => ({ | ||
166 | uuid: j.uuid, | ||
167 | type: j.type, | ||
168 | payload: j.payload | ||
169 | })) | ||
170 | } | ||
171 | |||
172 | updateLastRunnerContact(req, runner) | ||
173 | |||
174 | return res.json(result) | ||
175 | } | ||
176 | |||
177 | async function acceptRunnerJob (req: express.Request, res: express.Response) { | ||
178 | const runner = res.locals.runner | ||
179 | const runnerJob = res.locals.runnerJob | ||
180 | |||
181 | const newRunnerJob = await retryTransactionWrapper(() => { | ||
182 | return sequelizeTypescript.transaction(async transaction => { | ||
183 | await runnerJob.reload({ transaction }) | ||
184 | |||
185 | if (runnerJob.state !== RunnerJobState.PENDING) { | ||
186 | res.fail({ | ||
187 | type: ServerErrorCode.RUNNER_JOB_NOT_IN_PENDING_STATE, | ||
188 | message: 'This job is not in pending state anymore', | ||
189 | status: HttpStatusCode.CONFLICT_409 | ||
190 | }) | ||
191 | |||
192 | return undefined | ||
193 | } | ||
194 | |||
195 | runnerJob.state = RunnerJobState.PROCESSING | ||
196 | runnerJob.processingJobToken = generateRunnerJobToken() | ||
197 | runnerJob.startedAt = new Date() | ||
198 | runnerJob.runnerId = runner.id | ||
199 | |||
200 | return runnerJob.save({ transaction }) | ||
201 | }) | ||
202 | }) | ||
203 | if (!newRunnerJob) return | ||
204 | |||
205 | newRunnerJob.Runner = runner as RunnerModel | ||
206 | |||
207 | const result: AcceptRunnerJobResult = { | ||
208 | job: { | ||
209 | ...newRunnerJob.toFormattedJSON(), | ||
210 | |||
211 | jobToken: newRunnerJob.processingJobToken | ||
212 | } | ||
213 | } | ||
214 | |||
215 | updateLastRunnerContact(req, runner) | ||
216 | |||
217 | logger.info( | ||
218 | 'Remote runner %s has accepted job %s (%s)', runner.name, runnerJob.uuid, runnerJob.type, | ||
219 | lTags(runner.name, runnerJob.uuid, runnerJob.type) | ||
220 | ) | ||
221 | |||
222 | return res.json(result) | ||
223 | } | ||
224 | |||
225 | async function abortRunnerJob (req: express.Request, res: express.Response) { | ||
226 | const runnerJob = res.locals.runnerJob | ||
227 | const runner = runnerJob.Runner | ||
228 | const body: AbortRunnerJobBody = req.body | ||
229 | |||
230 | logger.info( | ||
231 | 'Remote runner %s is aborting job %s (%s)', runner.name, runnerJob.uuid, runnerJob.type, | ||
232 | { reason: body.reason, ...lTags(runner.name, runnerJob.uuid, runnerJob.type) } | ||
233 | ) | ||
234 | |||
235 | const RunnerJobHandler = getRunnerJobHandlerClass(runnerJob) | ||
236 | await new RunnerJobHandler().abort({ runnerJob }) | ||
237 | |||
238 | updateLastRunnerContact(req, runnerJob.Runner) | ||
239 | |||
240 | return res.sendStatus(HttpStatusCode.NO_CONTENT_204) | ||
241 | } | ||
242 | |||
243 | async function errorRunnerJob (req: express.Request, res: express.Response) { | ||
244 | const runnerJob = res.locals.runnerJob | ||
245 | const runner = runnerJob.Runner | ||
246 | const body: ErrorRunnerJobBody = req.body | ||
247 | |||
248 | runnerJob.failures += 1 | ||
249 | |||
250 | logger.error( | ||
251 | 'Remote runner %s had an error with job %s (%s)', runner.name, runnerJob.uuid, runnerJob.type, | ||
252 | { errorMessage: body.message, totalFailures: runnerJob.failures, ...lTags(runner.name, runnerJob.uuid, runnerJob.type) } | ||
253 | ) | ||
254 | |||
255 | const RunnerJobHandler = getRunnerJobHandlerClass(runnerJob) | ||
256 | await new RunnerJobHandler().error({ runnerJob, message: body.message }) | ||
257 | |||
258 | updateLastRunnerContact(req, runnerJob.Runner) | ||
259 | |||
260 | return res.sendStatus(HttpStatusCode.NO_CONTENT_204) | ||
261 | } | ||
262 | |||
263 | // --------------------------------------------------------------------------- | ||
264 | |||
265 | const jobUpdateBuilders: { | ||
266 | [id in RunnerJobType]?: (payload: RunnerJobUpdatePayload, files?: UploadFiles) => RunnerJobUpdatePayload | ||
267 | } = { | ||
268 | 'live-rtmp-hls-transcoding': (payload: LiveRTMPHLSTranscodingUpdatePayload, files) => { | ||
269 | return { | ||
270 | ...payload, | ||
271 | |||
272 | masterPlaylistFile: files['payload[masterPlaylistFile]']?.[0].path, | ||
273 | resolutionPlaylistFile: files['payload[resolutionPlaylistFile]']?.[0].path, | ||
274 | videoChunkFile: files['payload[videoChunkFile]']?.[0].path | ||
275 | } | ||
276 | } | ||
277 | } | ||
278 | |||
279 | async function updateRunnerJobController (req: express.Request, res: express.Response) { | ||
280 | const runnerJob = res.locals.runnerJob | ||
281 | const runner = runnerJob.Runner | ||
282 | const body: RunnerJobUpdateBody = req.body | ||
283 | |||
284 | if (runnerJob.state === RunnerJobState.COMPLETING || runnerJob.state === RunnerJobState.COMPLETED) { | ||
285 | return res.sendStatus(HttpStatusCode.NO_CONTENT_204) | ||
286 | } | ||
287 | |||
288 | const payloadBuilder = jobUpdateBuilders[runnerJob.type] | ||
289 | const updatePayload = payloadBuilder | ||
290 | ? payloadBuilder(body.payload, req.files as UploadFiles) | ||
291 | : undefined | ||
292 | |||
293 | logger.debug( | ||
294 | 'Remote runner %s is updating job %s (%s)', runnerJob.Runner.name, runnerJob.uuid, runnerJob.type, | ||
295 | { body, updatePayload, ...lTags(runner.name, runnerJob.uuid, runnerJob.type) } | ||
296 | ) | ||
297 | |||
298 | const RunnerJobHandler = getRunnerJobHandlerClass(runnerJob) | ||
299 | await new RunnerJobHandler().update({ | ||
300 | runnerJob, | ||
301 | progress: req.body.progress, | ||
302 | updatePayload | ||
303 | }) | ||
304 | |||
305 | updateLastRunnerContact(req, runnerJob.Runner) | ||
306 | |||
307 | return res.sendStatus(HttpStatusCode.NO_CONTENT_204) | ||
308 | } | ||
309 | |||
310 | // --------------------------------------------------------------------------- | ||
311 | |||
312 | const jobSuccessPayloadBuilders: { | ||
313 | [id in RunnerJobType]: (payload: RunnerJobSuccessPayload, files?: UploadFiles) => RunnerJobSuccessPayload | ||
314 | } = { | ||
315 | 'vod-web-video-transcoding': (payload: VODWebVideoTranscodingSuccess, files) => { | ||
316 | return { | ||
317 | ...payload, | ||
318 | |||
319 | videoFile: files['payload[videoFile]'][0].path | ||
320 | } | ||
321 | }, | ||
322 | |||
323 | 'vod-hls-transcoding': (payload: VODHLSTranscodingSuccess, files) => { | ||
324 | return { | ||
325 | ...payload, | ||
326 | |||
327 | videoFile: files['payload[videoFile]'][0].path, | ||
328 | resolutionPlaylistFile: files['payload[resolutionPlaylistFile]'][0].path | ||
329 | } | ||
330 | }, | ||
331 | |||
332 | 'vod-audio-merge-transcoding': (payload: VODAudioMergeTranscodingSuccess, files) => { | ||
333 | return { | ||
334 | ...payload, | ||
335 | |||
336 | videoFile: files['payload[videoFile]'][0].path | ||
337 | } | ||
338 | }, | ||
339 | |||
340 | 'video-studio-transcoding': (payload: VideoStudioTranscodingSuccess, files) => { | ||
341 | return { | ||
342 | ...payload, | ||
343 | |||
344 | videoFile: files['payload[videoFile]'][0].path | ||
345 | } | ||
346 | }, | ||
347 | |||
348 | 'live-rtmp-hls-transcoding': () => ({}) | ||
349 | } | ||
350 | |||
351 | async function postRunnerJobSuccess (req: express.Request, res: express.Response) { | ||
352 | const runnerJob = res.locals.runnerJob | ||
353 | const runner = runnerJob.Runner | ||
354 | const body: RunnerJobSuccessBody = req.body | ||
355 | |||
356 | const resultPayload = jobSuccessPayloadBuilders[runnerJob.type](body.payload, req.files as UploadFiles) | ||
357 | |||
358 | logger.info( | ||
359 | 'Remote runner %s is sending success result for job %s (%s)', runnerJob.Runner.name, runnerJob.uuid, runnerJob.type, | ||
360 | { resultPayload, ...lTags(runner.name, runnerJob.uuid, runnerJob.type) } | ||
361 | ) | ||
362 | |||
363 | const RunnerJobHandler = getRunnerJobHandlerClass(runnerJob) | ||
364 | await new RunnerJobHandler().complete({ runnerJob, resultPayload }) | ||
365 | |||
366 | updateLastRunnerContact(req, runnerJob.Runner) | ||
367 | |||
368 | return res.sendStatus(HttpStatusCode.NO_CONTENT_204) | ||
369 | } | ||
370 | |||
371 | // --------------------------------------------------------------------------- | ||
372 | // Controllers for admins | ||
373 | // --------------------------------------------------------------------------- | ||
374 | |||
375 | async function cancelRunnerJob (req: express.Request, res: express.Response) { | ||
376 | const runnerJob = res.locals.runnerJob | ||
377 | |||
378 | logger.info('Cancelling job %s (%s)', runnerJob.uuid, runnerJob.type, lTags(runnerJob.uuid, runnerJob.type)) | ||
379 | |||
380 | const RunnerJobHandler = getRunnerJobHandlerClass(runnerJob) | ||
381 | await new RunnerJobHandler().cancel({ runnerJob }) | ||
382 | |||
383 | return res.sendStatus(HttpStatusCode.NO_CONTENT_204) | ||
384 | } | ||
385 | |||
386 | async function deleteRunnerJob (req: express.Request, res: express.Response) { | ||
387 | const runnerJob = res.locals.runnerJob | ||
388 | |||
389 | logger.info('Deleting job %s (%s)', runnerJob.uuid, runnerJob.type, lTags(runnerJob.uuid, runnerJob.type)) | ||
390 | |||
391 | if (runnerJobCanBeCancelled(runnerJob)) { | ||
392 | const RunnerJobHandler = getRunnerJobHandlerClass(runnerJob) | ||
393 | await new RunnerJobHandler().cancel({ runnerJob }) | ||
394 | } | ||
395 | |||
396 | await runnerJob.destroy() | ||
397 | |||
398 | return res.sendStatus(HttpStatusCode.NO_CONTENT_204) | ||
399 | } | ||
400 | |||
401 | async function listRunnerJobs (req: express.Request, res: express.Response) { | ||
402 | const query: ListRunnerJobsQuery = req.query | ||
403 | |||
404 | const resultList = await RunnerJobModel.listForApi({ | ||
405 | start: query.start, | ||
406 | count: query.count, | ||
407 | sort: query.sort, | ||
408 | search: query.search, | ||
409 | stateOneOf: query.stateOneOf | ||
410 | }) | ||
411 | |||
412 | return res.json({ | ||
413 | total: resultList.total, | ||
414 | data: resultList.data.map(d => d.toFormattedAdminJSON()) | ||
415 | }) | ||
416 | } | ||
diff --git a/server/controllers/api/runners/manage-runners.ts b/server/controllers/api/runners/manage-runners.ts deleted file mode 100644 index be7ebc0b3..000000000 --- a/server/controllers/api/runners/manage-runners.ts +++ /dev/null | |||
@@ -1,112 +0,0 @@ | |||
1 | import express from 'express' | ||
2 | import { logger, loggerTagsFactory } from '@server/helpers/logger' | ||
3 | import { generateRunnerToken } from '@server/helpers/token-generator' | ||
4 | import { | ||
5 | apiRateLimiter, | ||
6 | asyncMiddleware, | ||
7 | authenticate, | ||
8 | ensureUserHasRight, | ||
9 | paginationValidator, | ||
10 | runnersSortValidator, | ||
11 | setDefaultPagination, | ||
12 | setDefaultSort | ||
13 | } from '@server/middlewares' | ||
14 | import { deleteRunnerValidator, getRunnerFromTokenValidator, registerRunnerValidator } from '@server/middlewares/validators/runners' | ||
15 | import { RunnerModel } from '@server/models/runner/runner' | ||
16 | import { HttpStatusCode, ListRunnersQuery, RegisterRunnerBody, UserRight } from '@shared/models' | ||
17 | |||
18 | const lTags = loggerTagsFactory('api', 'runner') | ||
19 | |||
20 | const manageRunnersRouter = express.Router() | ||
21 | |||
22 | manageRunnersRouter.post('/register', | ||
23 | apiRateLimiter, | ||
24 | asyncMiddleware(registerRunnerValidator), | ||
25 | asyncMiddleware(registerRunner) | ||
26 | ) | ||
27 | manageRunnersRouter.post('/unregister', | ||
28 | apiRateLimiter, | ||
29 | asyncMiddleware(getRunnerFromTokenValidator), | ||
30 | asyncMiddleware(unregisterRunner) | ||
31 | ) | ||
32 | |||
33 | manageRunnersRouter.delete('/:runnerId', | ||
34 | apiRateLimiter, | ||
35 | authenticate, | ||
36 | ensureUserHasRight(UserRight.MANAGE_RUNNERS), | ||
37 | asyncMiddleware(deleteRunnerValidator), | ||
38 | asyncMiddleware(deleteRunner) | ||
39 | ) | ||
40 | |||
41 | manageRunnersRouter.get('/', | ||
42 | apiRateLimiter, | ||
43 | authenticate, | ||
44 | ensureUserHasRight(UserRight.MANAGE_RUNNERS), | ||
45 | paginationValidator, | ||
46 | runnersSortValidator, | ||
47 | setDefaultSort, | ||
48 | setDefaultPagination, | ||
49 | asyncMiddleware(listRunners) | ||
50 | ) | ||
51 | |||
52 | // --------------------------------------------------------------------------- | ||
53 | |||
54 | export { | ||
55 | manageRunnersRouter | ||
56 | } | ||
57 | |||
58 | // --------------------------------------------------------------------------- | ||
59 | |||
60 | async function registerRunner (req: express.Request, res: express.Response) { | ||
61 | const body: RegisterRunnerBody = req.body | ||
62 | |||
63 | const runnerToken = generateRunnerToken() | ||
64 | |||
65 | const runner = new RunnerModel({ | ||
66 | runnerToken, | ||
67 | name: body.name, | ||
68 | description: body.description, | ||
69 | lastContact: new Date(), | ||
70 | ip: req.ip, | ||
71 | runnerRegistrationTokenId: res.locals.runnerRegistrationToken.id | ||
72 | }) | ||
73 | |||
74 | await runner.save() | ||
75 | |||
76 | logger.info('Registered new runner %s', runner.name, { ...lTags(runner.name) }) | ||
77 | |||
78 | return res.json({ id: runner.id, runnerToken }) | ||
79 | } | ||
80 | async function unregisterRunner (req: express.Request, res: express.Response) { | ||
81 | const runner = res.locals.runner | ||
82 | await runner.destroy() | ||
83 | |||
84 | logger.info('Unregistered runner %s', runner.name, { ...lTags(runner.name) }) | ||
85 | |||
86 | return res.sendStatus(HttpStatusCode.NO_CONTENT_204) | ||
87 | } | ||
88 | |||
89 | async function deleteRunner (req: express.Request, res: express.Response) { | ||
90 | const runner = res.locals.runner | ||
91 | |||
92 | await runner.destroy() | ||
93 | |||
94 | logger.info('Deleted runner %s', runner.name, { ...lTags(runner.name) }) | ||
95 | |||
96 | return res.sendStatus(HttpStatusCode.NO_CONTENT_204) | ||
97 | } | ||
98 | |||
99 | async function listRunners (req: express.Request, res: express.Response) { | ||
100 | const query: ListRunnersQuery = req.query | ||
101 | |||
102 | const resultList = await RunnerModel.listForApi({ | ||
103 | start: query.start, | ||
104 | count: query.count, | ||
105 | sort: query.sort | ||
106 | }) | ||
107 | |||
108 | return res.json({ | ||
109 | total: resultList.total, | ||
110 | data: resultList.data.map(d => d.toFormattedJSON()) | ||
111 | }) | ||
112 | } | ||
diff --git a/server/controllers/api/runners/registration-tokens.ts b/server/controllers/api/runners/registration-tokens.ts deleted file mode 100644 index 117ff271b..000000000 --- a/server/controllers/api/runners/registration-tokens.ts +++ /dev/null | |||
@@ -1,91 +0,0 @@ | |||
1 | import express from 'express' | ||
2 | import { logger, loggerTagsFactory } from '@server/helpers/logger' | ||
3 | import { generateRunnerRegistrationToken } from '@server/helpers/token-generator' | ||
4 | import { | ||
5 | apiRateLimiter, | ||
6 | asyncMiddleware, | ||
7 | authenticate, | ||
8 | ensureUserHasRight, | ||
9 | paginationValidator, | ||
10 | runnerRegistrationTokensSortValidator, | ||
11 | setDefaultPagination, | ||
12 | setDefaultSort | ||
13 | } from '@server/middlewares' | ||
14 | import { deleteRegistrationTokenValidator } from '@server/middlewares/validators/runners' | ||
15 | import { RunnerRegistrationTokenModel } from '@server/models/runner/runner-registration-token' | ||
16 | import { HttpStatusCode, ListRunnerRegistrationTokensQuery, UserRight } from '@shared/models' | ||
17 | |||
18 | const lTags = loggerTagsFactory('api', 'runner') | ||
19 | |||
20 | const runnerRegistrationTokensRouter = express.Router() | ||
21 | |||
22 | runnerRegistrationTokensRouter.post('/registration-tokens/generate', | ||
23 | apiRateLimiter, | ||
24 | authenticate, | ||
25 | ensureUserHasRight(UserRight.MANAGE_RUNNERS), | ||
26 | asyncMiddleware(generateRegistrationToken) | ||
27 | ) | ||
28 | |||
29 | runnerRegistrationTokensRouter.delete('/registration-tokens/:id', | ||
30 | apiRateLimiter, | ||
31 | authenticate, | ||
32 | ensureUserHasRight(UserRight.MANAGE_RUNNERS), | ||
33 | asyncMiddleware(deleteRegistrationTokenValidator), | ||
34 | asyncMiddleware(deleteRegistrationToken) | ||
35 | ) | ||
36 | |||
37 | runnerRegistrationTokensRouter.get('/registration-tokens', | ||
38 | apiRateLimiter, | ||
39 | authenticate, | ||
40 | ensureUserHasRight(UserRight.MANAGE_RUNNERS), | ||
41 | paginationValidator, | ||
42 | runnerRegistrationTokensSortValidator, | ||
43 | setDefaultSort, | ||
44 | setDefaultPagination, | ||
45 | asyncMiddleware(listRegistrationTokens) | ||
46 | ) | ||
47 | |||
48 | // --------------------------------------------------------------------------- | ||
49 | |||
50 | export { | ||
51 | runnerRegistrationTokensRouter | ||
52 | } | ||
53 | |||
54 | // --------------------------------------------------------------------------- | ||
55 | |||
56 | async function generateRegistrationToken (req: express.Request, res: express.Response) { | ||
57 | logger.info('Generating new runner registration token.', lTags()) | ||
58 | |||
59 | const registrationToken = new RunnerRegistrationTokenModel({ | ||
60 | registrationToken: generateRunnerRegistrationToken() | ||
61 | }) | ||
62 | |||
63 | await registrationToken.save() | ||
64 | |||
65 | return res.sendStatus(HttpStatusCode.NO_CONTENT_204) | ||
66 | } | ||
67 | |||
68 | async function deleteRegistrationToken (req: express.Request, res: express.Response) { | ||
69 | logger.info('Removing runner registration token.', lTags()) | ||
70 | |||
71 | const runnerRegistrationToken = res.locals.runnerRegistrationToken | ||
72 | |||
73 | await runnerRegistrationToken.destroy() | ||
74 | |||
75 | return res.sendStatus(HttpStatusCode.NO_CONTENT_204) | ||
76 | } | ||
77 | |||
78 | async function listRegistrationTokens (req: express.Request, res: express.Response) { | ||
79 | const query: ListRunnerRegistrationTokensQuery = req.query | ||
80 | |||
81 | const resultList = await RunnerRegistrationTokenModel.listForApi({ | ||
82 | start: query.start, | ||
83 | count: query.count, | ||
84 | sort: query.sort | ||
85 | }) | ||
86 | |||
87 | return res.json({ | ||
88 | total: resultList.total, | ||
89 | data: resultList.data.map(d => d.toFormattedJSON()) | ||
90 | }) | ||
91 | } | ||