はじまりの大地
このコミットが含まれているのは:
@@ -0,0 +1,20 @@
|
||||
import express from 'express'
|
||||
import { runnerJobsRouter } from './jobs.js'
|
||||
import { runnerJobFilesRouter } from './jobs-files.js'
|
||||
import { manageRunnersRouter } from './manage-runners.js'
|
||||
import { runnerRegistrationTokensRouter } from './registration-tokens.js'
|
||||
|
||||
const runnersRouter = express.Router()
|
||||
|
||||
// No api route limiter here, they are defined in child routers
|
||||
|
||||
runnersRouter.use('/', manageRunnersRouter)
|
||||
runnersRouter.use('/', runnerJobsRouter)
|
||||
runnersRouter.use('/', runnerJobFilesRouter)
|
||||
runnersRouter.use('/', runnerRegistrationTokensRouter)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
runnersRouter
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import express from 'express'
|
||||
import { logger, loggerTagsFactory } from '@server/helpers/logger.js'
|
||||
import { proxifyHLS, proxifyWebVideoFile } from '@server/lib/object-storage/index.js'
|
||||
import { VideoPathManager } from '@server/lib/video-path-manager.js'
|
||||
import { getStudioTaskFilePath } from '@server/lib/video-studio.js'
|
||||
import { apiRateLimiter, asyncMiddleware } from '@server/middlewares/index.js'
|
||||
import { jobOfRunnerGetValidatorFactory } from '@server/middlewares/validators/runners/index.js'
|
||||
import {
|
||||
runnerJobGetVideoStudioTaskFileValidator,
|
||||
runnerJobGetVideoTranscodingFileValidator
|
||||
} from '@server/middlewares/validators/runners/job-files.js'
|
||||
import { RunnerJobState, FileStorage } from '@peertube/peertube-models'
|
||||
|
||||
const lTags = loggerTagsFactory('api', 'runner')
|
||||
|
||||
const runnerJobFilesRouter = express.Router()
|
||||
|
||||
runnerJobFilesRouter.post('/jobs/:jobUUID/files/videos/:videoId/max-quality',
|
||||
apiRateLimiter,
|
||||
asyncMiddleware(jobOfRunnerGetValidatorFactory([ RunnerJobState.PROCESSING ])),
|
||||
asyncMiddleware(runnerJobGetVideoTranscodingFileValidator),
|
||||
asyncMiddleware(getMaxQualityVideoFile)
|
||||
)
|
||||
|
||||
runnerJobFilesRouter.post('/jobs/:jobUUID/files/videos/:videoId/previews/max-quality',
|
||||
apiRateLimiter,
|
||||
asyncMiddleware(jobOfRunnerGetValidatorFactory([ RunnerJobState.PROCESSING ])),
|
||||
asyncMiddleware(runnerJobGetVideoTranscodingFileValidator),
|
||||
getMaxQualityVideoPreview
|
||||
)
|
||||
|
||||
runnerJobFilesRouter.post('/jobs/:jobUUID/files/videos/:videoId/studio/task-files/:filename',
|
||||
apiRateLimiter,
|
||||
asyncMiddleware(jobOfRunnerGetValidatorFactory([ RunnerJobState.PROCESSING ])),
|
||||
asyncMiddleware(runnerJobGetVideoTranscodingFileValidator),
|
||||
runnerJobGetVideoStudioTaskFileValidator,
|
||||
getVideoStudioTaskFile
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
runnerJobFilesRouter
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function getMaxQualityVideoFile (req: express.Request, res: express.Response) {
|
||||
const runnerJob = res.locals.runnerJob
|
||||
const runner = runnerJob.Runner
|
||||
const video = res.locals.videoAll
|
||||
|
||||
logger.info(
|
||||
'Get max quality file of video %s of job %s for runner %s', video.uuid, runnerJob.uuid, runner.name,
|
||||
lTags(runner.name, runnerJob.id, runnerJob.type)
|
||||
)
|
||||
|
||||
const file = video.getMaxQualityFile()
|
||||
|
||||
if (file.storage === FileStorage.OBJECT_STORAGE) {
|
||||
if (file.isHLS()) {
|
||||
return proxifyHLS({
|
||||
req,
|
||||
res,
|
||||
filename: file.filename,
|
||||
playlist: video.getHLSPlaylist(),
|
||||
reinjectVideoFileToken: false,
|
||||
video
|
||||
})
|
||||
}
|
||||
|
||||
// Web video
|
||||
return proxifyWebVideoFile({
|
||||
req,
|
||||
res,
|
||||
filename: file.filename
|
||||
})
|
||||
}
|
||||
|
||||
return VideoPathManager.Instance.makeAvailableVideoFile(file, videoPath => {
|
||||
return res.sendFile(videoPath)
|
||||
})
|
||||
}
|
||||
|
||||
function getMaxQualityVideoPreview (req: express.Request, res: express.Response) {
|
||||
const runnerJob = res.locals.runnerJob
|
||||
const runner = runnerJob.Runner
|
||||
const video = res.locals.videoAll
|
||||
|
||||
logger.info(
|
||||
'Get max quality preview file of video %s of job %s for runner %s', video.uuid, runnerJob.uuid, runner.name,
|
||||
lTags(runner.name, runnerJob.id, runnerJob.type)
|
||||
)
|
||||
|
||||
const file = video.getPreview()
|
||||
|
||||
return res.sendFile(file.getPath())
|
||||
}
|
||||
|
||||
function getVideoStudioTaskFile (req: express.Request, res: express.Response) {
|
||||
const runnerJob = res.locals.runnerJob
|
||||
const runner = runnerJob.Runner
|
||||
const video = res.locals.videoAll
|
||||
const filename = req.params.filename
|
||||
|
||||
logger.info(
|
||||
'Get video studio task file %s of video %s of job %s for runner %s', filename, video.uuid, runnerJob.uuid, runner.name,
|
||||
lTags(runner.name, runnerJob.id, runnerJob.type)
|
||||
)
|
||||
|
||||
return res.sendFile(getStudioTaskFilePath(filename))
|
||||
}
|
||||
@@ -0,0 +1,425 @@
|
||||
import {
|
||||
AbortRunnerJobBody,
|
||||
AcceptRunnerJobResult,
|
||||
ErrorRunnerJobBody,
|
||||
HttpStatusCode,
|
||||
ListRunnerJobsQuery,
|
||||
LiveRTMPHLSTranscodingUpdatePayload,
|
||||
RequestRunnerJobResult,
|
||||
RunnerJobState,
|
||||
RunnerJobSuccessBody,
|
||||
RunnerJobSuccessPayload,
|
||||
RunnerJobType,
|
||||
RunnerJobUpdateBody,
|
||||
RunnerJobUpdatePayload,
|
||||
ServerErrorCode,
|
||||
TranscriptionSuccess,
|
||||
UserRight,
|
||||
VODAudioMergeTranscodingSuccess,
|
||||
VODHLSTranscodingSuccess,
|
||||
VODWebVideoTranscodingSuccess,
|
||||
VideoStudioTranscodingSuccess
|
||||
} from '@peertube/peertube-models'
|
||||
import { retryTransactionWrapper } from '@server/helpers/database-utils.js'
|
||||
import { createReqFiles } from '@server/helpers/express-utils.js'
|
||||
import { logger, loggerTagsFactory } from '@server/helpers/logger.js'
|
||||
import { generateRunnerJobToken } from '@server/helpers/token-generator.js'
|
||||
import { MIMETYPES } from '@server/initializers/constants.js'
|
||||
import { sequelizeTypescript } from '@server/initializers/database.js'
|
||||
import { getRunnerJobHandlerClass, runnerJobCanBeCancelled, updateLastRunnerContact } from '@server/lib/runners/index.js'
|
||||
import {
|
||||
apiRateLimiter,
|
||||
asyncMiddleware,
|
||||
authenticate,
|
||||
ensureUserHasRight,
|
||||
paginationValidator,
|
||||
runnerJobsSortValidator,
|
||||
setDefaultPagination,
|
||||
setDefaultSort
|
||||
} from '@server/middlewares/index.js'
|
||||
import {
|
||||
abortRunnerJobValidator,
|
||||
acceptRunnerJobValidator,
|
||||
cancelRunnerJobValidator,
|
||||
errorRunnerJobValidator,
|
||||
getRunnerFromTokenValidator,
|
||||
jobOfRunnerGetValidatorFactory,
|
||||
listRunnerJobsValidator,
|
||||
runnerJobGetValidator,
|
||||
successRunnerJobValidator,
|
||||
updateRunnerJobValidator
|
||||
} from '@server/middlewares/validators/runners/index.js'
|
||||
import { RunnerJobModel } from '@server/models/runner/runner-job.js'
|
||||
import { RunnerModel } from '@server/models/runner/runner.js'
|
||||
import express, { UploadFiles } from 'express'
|
||||
|
||||
const postRunnerJobSuccessVideoFiles = createReqFiles(
|
||||
[ 'payload[videoFile]', 'payload[resolutionPlaylistFile]', 'payload[vttFile]' ],
|
||||
{ ...MIMETYPES.VIDEO.MIMETYPE_EXT, ...MIMETYPES.M3U8.MIMETYPE_EXT, ...MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT }
|
||||
)
|
||||
|
||||
const runnerJobUpdateVideoFiles = createReqFiles(
|
||||
[ 'payload[videoChunkFile]', 'payload[resolutionPlaylistFile]', 'payload[masterPlaylistFile]' ],
|
||||
{ ...MIMETYPES.VIDEO.MIMETYPE_EXT, ...MIMETYPES.M3U8.MIMETYPE_EXT }
|
||||
)
|
||||
|
||||
const lTags = loggerTagsFactory('api', 'runner')
|
||||
|
||||
const runnerJobsRouter = express.Router()
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Controllers for runners
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
runnerJobsRouter.post('/jobs/request',
|
||||
apiRateLimiter,
|
||||
asyncMiddleware(getRunnerFromTokenValidator),
|
||||
asyncMiddleware(requestRunnerJob)
|
||||
)
|
||||
|
||||
runnerJobsRouter.post('/jobs/:jobUUID/accept',
|
||||
apiRateLimiter,
|
||||
asyncMiddleware(runnerJobGetValidator),
|
||||
acceptRunnerJobValidator,
|
||||
asyncMiddleware(getRunnerFromTokenValidator),
|
||||
asyncMiddleware(acceptRunnerJob)
|
||||
)
|
||||
|
||||
runnerJobsRouter.post('/jobs/:jobUUID/abort',
|
||||
apiRateLimiter,
|
||||
asyncMiddleware(jobOfRunnerGetValidatorFactory([ RunnerJobState.PROCESSING ])),
|
||||
abortRunnerJobValidator,
|
||||
asyncMiddleware(abortRunnerJob)
|
||||
)
|
||||
|
||||
runnerJobsRouter.post('/jobs/:jobUUID/update',
|
||||
runnerJobUpdateVideoFiles,
|
||||
apiRateLimiter, // Has to be after multer middleware to parse runner token
|
||||
asyncMiddleware(jobOfRunnerGetValidatorFactory([ RunnerJobState.PROCESSING, RunnerJobState.COMPLETING, RunnerJobState.COMPLETED ])),
|
||||
updateRunnerJobValidator,
|
||||
asyncMiddleware(updateRunnerJobController)
|
||||
)
|
||||
|
||||
runnerJobsRouter.post('/jobs/:jobUUID/error',
|
||||
asyncMiddleware(jobOfRunnerGetValidatorFactory([ RunnerJobState.PROCESSING ])),
|
||||
errorRunnerJobValidator,
|
||||
asyncMiddleware(errorRunnerJob)
|
||||
)
|
||||
|
||||
runnerJobsRouter.post('/jobs/:jobUUID/success',
|
||||
postRunnerJobSuccessVideoFiles,
|
||||
apiRateLimiter, // Has to be after multer middleware to parse runner token
|
||||
asyncMiddleware(jobOfRunnerGetValidatorFactory([ RunnerJobState.PROCESSING ])),
|
||||
successRunnerJobValidator,
|
||||
asyncMiddleware(postRunnerJobSuccess)
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Controllers for admins
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
runnerJobsRouter.post('/jobs/:jobUUID/cancel',
|
||||
authenticate,
|
||||
ensureUserHasRight(UserRight.MANAGE_RUNNERS),
|
||||
asyncMiddleware(runnerJobGetValidator),
|
||||
cancelRunnerJobValidator,
|
||||
asyncMiddleware(cancelRunnerJob)
|
||||
)
|
||||
|
||||
runnerJobsRouter.get('/jobs',
|
||||
authenticate,
|
||||
ensureUserHasRight(UserRight.MANAGE_RUNNERS),
|
||||
paginationValidator,
|
||||
runnerJobsSortValidator,
|
||||
setDefaultSort,
|
||||
setDefaultPagination,
|
||||
listRunnerJobsValidator,
|
||||
asyncMiddleware(listRunnerJobs)
|
||||
)
|
||||
|
||||
runnerJobsRouter.delete('/jobs/:jobUUID',
|
||||
authenticate,
|
||||
ensureUserHasRight(UserRight.MANAGE_RUNNERS),
|
||||
asyncMiddleware(runnerJobGetValidator),
|
||||
asyncMiddleware(deleteRunnerJob)
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
runnerJobsRouter
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Controllers for runners
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function requestRunnerJob (req: express.Request, res: express.Response) {
|
||||
const runner = res.locals.runner
|
||||
const availableJobs = await RunnerJobModel.listAvailableJobs()
|
||||
|
||||
logger.debug('Runner %s requests for a job.', runner.name, { availableJobs, ...lTags(runner.name) })
|
||||
|
||||
const result: RequestRunnerJobResult = {
|
||||
availableJobs: availableJobs.map(j => ({
|
||||
uuid: j.uuid,
|
||||
type: j.type,
|
||||
payload: j.payload
|
||||
}))
|
||||
}
|
||||
|
||||
updateLastRunnerContact(req, runner)
|
||||
|
||||
return res.json(result)
|
||||
}
|
||||
|
||||
async function acceptRunnerJob (req: express.Request, res: express.Response) {
|
||||
const runner = res.locals.runner
|
||||
const runnerJob = res.locals.runnerJob
|
||||
|
||||
const newRunnerJob = await retryTransactionWrapper(() => {
|
||||
return sequelizeTypescript.transaction(async transaction => {
|
||||
await runnerJob.reload({ transaction })
|
||||
|
||||
if (runnerJob.state !== RunnerJobState.PENDING) {
|
||||
res.fail({
|
||||
type: ServerErrorCode.RUNNER_JOB_NOT_IN_PENDING_STATE,
|
||||
message: 'This job is not in pending state anymore',
|
||||
status: HttpStatusCode.CONFLICT_409
|
||||
})
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
runnerJob.state = RunnerJobState.PROCESSING
|
||||
runnerJob.processingJobToken = generateRunnerJobToken()
|
||||
runnerJob.startedAt = new Date()
|
||||
runnerJob.runnerId = runner.id
|
||||
|
||||
return runnerJob.save({ transaction })
|
||||
})
|
||||
})
|
||||
if (!newRunnerJob) return
|
||||
|
||||
newRunnerJob.Runner = runner as RunnerModel
|
||||
|
||||
const result: AcceptRunnerJobResult = {
|
||||
job: {
|
||||
...newRunnerJob.toFormattedJSON(),
|
||||
|
||||
jobToken: newRunnerJob.processingJobToken
|
||||
}
|
||||
}
|
||||
|
||||
updateLastRunnerContact(req, runner)
|
||||
|
||||
logger.info(
|
||||
'Remote runner %s has accepted job %s (%s)', runner.name, runnerJob.uuid, runnerJob.type,
|
||||
lTags(runner.name, runnerJob.uuid, runnerJob.type)
|
||||
)
|
||||
|
||||
return res.json(result)
|
||||
}
|
||||
|
||||
async function abortRunnerJob (req: express.Request, res: express.Response) {
|
||||
const runnerJob = res.locals.runnerJob
|
||||
const runner = runnerJob.Runner
|
||||
const body: AbortRunnerJobBody = req.body
|
||||
|
||||
logger.info(
|
||||
'Remote runner %s is aborting job %s (%s)', runner.name, runnerJob.uuid, runnerJob.type,
|
||||
{ reason: body.reason, ...lTags(runner.name, runnerJob.uuid, runnerJob.type) }
|
||||
)
|
||||
|
||||
const RunnerJobHandler = getRunnerJobHandlerClass(runnerJob)
|
||||
await new RunnerJobHandler().abort({ runnerJob })
|
||||
|
||||
updateLastRunnerContact(req, runnerJob.Runner)
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
}
|
||||
|
||||
async function errorRunnerJob (req: express.Request, res: express.Response) {
|
||||
const runnerJob = res.locals.runnerJob
|
||||
const runner = runnerJob.Runner
|
||||
const body: ErrorRunnerJobBody = req.body
|
||||
|
||||
runnerJob.failures += 1
|
||||
|
||||
logger.error(
|
||||
'Remote runner %s had an error with job %s (%s)', runner.name, runnerJob.uuid, runnerJob.type,
|
||||
{ errorMessage: body.message, totalFailures: runnerJob.failures, ...lTags(runner.name, runnerJob.uuid, runnerJob.type) }
|
||||
)
|
||||
|
||||
const RunnerJobHandler = getRunnerJobHandlerClass(runnerJob)
|
||||
await new RunnerJobHandler().error({ runnerJob, message: body.message })
|
||||
|
||||
updateLastRunnerContact(req, runnerJob.Runner)
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const jobUpdateBuilders: {
|
||||
[id in RunnerJobType]?: (payload: RunnerJobUpdatePayload, files?: UploadFiles) => RunnerJobUpdatePayload
|
||||
} = {
|
||||
'live-rtmp-hls-transcoding': (payload: LiveRTMPHLSTranscodingUpdatePayload, files) => {
|
||||
return {
|
||||
...payload,
|
||||
|
||||
masterPlaylistFile: files['payload[masterPlaylistFile]']?.[0].path,
|
||||
resolutionPlaylistFile: files['payload[resolutionPlaylistFile]']?.[0].path,
|
||||
videoChunkFile: files['payload[videoChunkFile]']?.[0].path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function updateRunnerJobController (req: express.Request, res: express.Response) {
|
||||
const runnerJob = res.locals.runnerJob
|
||||
const runner = runnerJob.Runner
|
||||
const body: RunnerJobUpdateBody = req.body
|
||||
|
||||
if (runnerJob.state === RunnerJobState.COMPLETING || runnerJob.state === RunnerJobState.COMPLETED) {
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
}
|
||||
|
||||
const payloadBuilder = jobUpdateBuilders[runnerJob.type]
|
||||
const updatePayload = payloadBuilder
|
||||
? payloadBuilder(body.payload, req.files as UploadFiles)
|
||||
: undefined
|
||||
|
||||
logger.debug(
|
||||
'Remote runner %s is updating job %s (%s)', runnerJob.Runner.name, runnerJob.uuid, runnerJob.type,
|
||||
{ body, updatePayload, ...lTags(runner.name, runnerJob.uuid, runnerJob.type) }
|
||||
)
|
||||
|
||||
const RunnerJobHandler = getRunnerJobHandlerClass(runnerJob)
|
||||
await new RunnerJobHandler().update({
|
||||
runnerJob,
|
||||
progress: req.body.progress,
|
||||
updatePayload
|
||||
})
|
||||
|
||||
updateLastRunnerContact(req, runnerJob.Runner)
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const jobSuccessPayloadBuilders: {
|
||||
[id in RunnerJobType]: (payload: RunnerJobSuccessPayload, files?: UploadFiles) => RunnerJobSuccessPayload
|
||||
} = {
|
||||
'vod-web-video-transcoding': (payload: VODWebVideoTranscodingSuccess, files) => {
|
||||
return {
|
||||
...payload,
|
||||
|
||||
videoFile: files['payload[videoFile]'][0].path
|
||||
}
|
||||
},
|
||||
|
||||
'vod-hls-transcoding': (payload: VODHLSTranscodingSuccess, files) => {
|
||||
return {
|
||||
...payload,
|
||||
|
||||
videoFile: files['payload[videoFile]'][0].path,
|
||||
resolutionPlaylistFile: files['payload[resolutionPlaylistFile]'][0].path
|
||||
}
|
||||
},
|
||||
|
||||
'vod-audio-merge-transcoding': (payload: VODAudioMergeTranscodingSuccess, files) => {
|
||||
return {
|
||||
...payload,
|
||||
|
||||
videoFile: files['payload[videoFile]'][0].path
|
||||
}
|
||||
},
|
||||
|
||||
'video-studio-transcoding': (payload: VideoStudioTranscodingSuccess, files) => {
|
||||
return {
|
||||
...payload,
|
||||
|
||||
videoFile: files['payload[videoFile]'][0].path
|
||||
}
|
||||
},
|
||||
|
||||
'live-rtmp-hls-transcoding': () => ({}),
|
||||
|
||||
'video-transcription': (payload: TranscriptionSuccess, files) => {
|
||||
return {
|
||||
...payload,
|
||||
|
||||
vttFile: files['payload[vttFile]'][0].path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function postRunnerJobSuccess (req: express.Request, res: express.Response) {
|
||||
const runnerJob = res.locals.runnerJob
|
||||
const runner = runnerJob.Runner
|
||||
const body: RunnerJobSuccessBody = req.body
|
||||
|
||||
const resultPayload = jobSuccessPayloadBuilders[runnerJob.type](body.payload, req.files as UploadFiles)
|
||||
|
||||
logger.info(
|
||||
'Remote runner %s is sending success result for job %s (%s)', runnerJob.Runner.name, runnerJob.uuid, runnerJob.type,
|
||||
{ resultPayload, ...lTags(runner.name, runnerJob.uuid, runnerJob.type) }
|
||||
)
|
||||
|
||||
const RunnerJobHandler = getRunnerJobHandlerClass(runnerJob)
|
||||
await new RunnerJobHandler().complete({ runnerJob, resultPayload })
|
||||
|
||||
updateLastRunnerContact(req, runnerJob.Runner)
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Controllers for admins
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function cancelRunnerJob (req: express.Request, res: express.Response) {
|
||||
const runnerJob = res.locals.runnerJob
|
||||
|
||||
logger.info('Cancelling job %s (%s)', runnerJob.uuid, runnerJob.type, lTags(runnerJob.uuid, runnerJob.type))
|
||||
|
||||
const RunnerJobHandler = getRunnerJobHandlerClass(runnerJob)
|
||||
await new RunnerJobHandler().cancel({ runnerJob })
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
}
|
||||
|
||||
async function deleteRunnerJob (req: express.Request, res: express.Response) {
|
||||
const runnerJob = res.locals.runnerJob
|
||||
|
||||
logger.info('Deleting job %s (%s)', runnerJob.uuid, runnerJob.type, lTags(runnerJob.uuid, runnerJob.type))
|
||||
|
||||
if (runnerJobCanBeCancelled(runnerJob)) {
|
||||
const RunnerJobHandler = getRunnerJobHandlerClass(runnerJob)
|
||||
await new RunnerJobHandler().cancel({ runnerJob })
|
||||
}
|
||||
|
||||
await runnerJob.destroy()
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
}
|
||||
|
||||
async function listRunnerJobs (req: express.Request, res: express.Response) {
|
||||
const query: ListRunnerJobsQuery = req.query
|
||||
|
||||
const resultList = await RunnerJobModel.listForApi({
|
||||
start: query.start,
|
||||
count: query.count,
|
||||
sort: query.sort,
|
||||
search: query.search,
|
||||
stateOneOf: query.stateOneOf
|
||||
})
|
||||
|
||||
return res.json({
|
||||
total: resultList.total,
|
||||
data: resultList.data.map(d => d.toFormattedAdminJSON())
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import express from 'express'
|
||||
import { HttpStatusCode, ListRunnersQuery, RegisterRunnerBody, UserRight } from '@peertube/peertube-models'
|
||||
import { logger, loggerTagsFactory } from '@server/helpers/logger.js'
|
||||
import { generateRunnerToken } from '@server/helpers/token-generator.js'
|
||||
import {
|
||||
apiRateLimiter,
|
||||
asyncMiddleware,
|
||||
authenticate,
|
||||
ensureUserHasRight,
|
||||
paginationValidator,
|
||||
runnersSortValidator,
|
||||
setDefaultPagination,
|
||||
setDefaultSort
|
||||
} from '@server/middlewares/index.js'
|
||||
import {
|
||||
deleteRunnerValidator,
|
||||
getRunnerFromTokenValidator,
|
||||
registerRunnerValidator
|
||||
} from '@server/middlewares/validators/runners/index.js'
|
||||
import { RunnerModel } from '@server/models/runner/runner.js'
|
||||
|
||||
const lTags = loggerTagsFactory('api', 'runner')
|
||||
|
||||
const manageRunnersRouter = express.Router()
|
||||
|
||||
manageRunnersRouter.post('/register',
|
||||
apiRateLimiter,
|
||||
asyncMiddleware(registerRunnerValidator),
|
||||
asyncMiddleware(registerRunner)
|
||||
)
|
||||
manageRunnersRouter.post('/unregister',
|
||||
apiRateLimiter,
|
||||
asyncMiddleware(getRunnerFromTokenValidator),
|
||||
asyncMiddleware(unregisterRunner)
|
||||
)
|
||||
|
||||
manageRunnersRouter.delete('/:runnerId',
|
||||
apiRateLimiter,
|
||||
authenticate,
|
||||
ensureUserHasRight(UserRight.MANAGE_RUNNERS),
|
||||
asyncMiddleware(deleteRunnerValidator),
|
||||
asyncMiddleware(deleteRunner)
|
||||
)
|
||||
|
||||
manageRunnersRouter.get('/',
|
||||
apiRateLimiter,
|
||||
authenticate,
|
||||
ensureUserHasRight(UserRight.MANAGE_RUNNERS),
|
||||
paginationValidator,
|
||||
runnersSortValidator,
|
||||
setDefaultSort,
|
||||
setDefaultPagination,
|
||||
asyncMiddleware(listRunners)
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
manageRunnersRouter
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function registerRunner (req: express.Request, res: express.Response) {
|
||||
const body: RegisterRunnerBody = req.body
|
||||
|
||||
const runnerToken = generateRunnerToken()
|
||||
|
||||
const runner = new RunnerModel({
|
||||
runnerToken,
|
||||
name: body.name,
|
||||
description: body.description,
|
||||
lastContact: new Date(),
|
||||
ip: req.ip,
|
||||
runnerRegistrationTokenId: res.locals.runnerRegistrationToken.id
|
||||
})
|
||||
|
||||
await runner.save()
|
||||
|
||||
logger.info('Registered new runner %s', runner.name, { ...lTags(runner.name) })
|
||||
|
||||
return res.json({ id: runner.id, runnerToken })
|
||||
}
|
||||
async function unregisterRunner (req: express.Request, res: express.Response) {
|
||||
const runner = res.locals.runner
|
||||
await runner.destroy()
|
||||
|
||||
logger.info('Unregistered runner %s', runner.name, { ...lTags(runner.name) })
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
}
|
||||
|
||||
async function deleteRunner (req: express.Request, res: express.Response) {
|
||||
const runner = res.locals.runner
|
||||
|
||||
await runner.destroy()
|
||||
|
||||
logger.info('Deleted runner %s', runner.name, { ...lTags(runner.name) })
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
}
|
||||
|
||||
async function listRunners (req: express.Request, res: express.Response) {
|
||||
const query: ListRunnersQuery = req.query
|
||||
|
||||
const resultList = await RunnerModel.listForApi({
|
||||
start: query.start,
|
||||
count: query.count,
|
||||
sort: query.sort
|
||||
})
|
||||
|
||||
return res.json({
|
||||
total: resultList.total,
|
||||
data: resultList.data.map(d => d.toFormattedJSON())
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import express from 'express'
|
||||
import { logger, loggerTagsFactory } from '@server/helpers/logger.js'
|
||||
import { generateRunnerRegistrationToken } from '@server/helpers/token-generator.js'
|
||||
import {
|
||||
apiRateLimiter,
|
||||
asyncMiddleware,
|
||||
authenticate,
|
||||
ensureUserHasRight,
|
||||
paginationValidator,
|
||||
runnerRegistrationTokensSortValidator,
|
||||
setDefaultPagination,
|
||||
setDefaultSort
|
||||
} from '@server/middlewares/index.js'
|
||||
import { deleteRegistrationTokenValidator } from '@server/middlewares/validators/runners/index.js'
|
||||
import { RunnerRegistrationTokenModel } from '@server/models/runner/runner-registration-token.js'
|
||||
import { HttpStatusCode, ListRunnerRegistrationTokensQuery, UserRight } from '@peertube/peertube-models'
|
||||
|
||||
const lTags = loggerTagsFactory('api', 'runner')
|
||||
|
||||
const runnerRegistrationTokensRouter = express.Router()
|
||||
|
||||
runnerRegistrationTokensRouter.post('/registration-tokens/generate',
|
||||
apiRateLimiter,
|
||||
authenticate,
|
||||
ensureUserHasRight(UserRight.MANAGE_RUNNERS),
|
||||
asyncMiddleware(generateRegistrationToken)
|
||||
)
|
||||
|
||||
runnerRegistrationTokensRouter.delete('/registration-tokens/:id',
|
||||
apiRateLimiter,
|
||||
authenticate,
|
||||
ensureUserHasRight(UserRight.MANAGE_RUNNERS),
|
||||
asyncMiddleware(deleteRegistrationTokenValidator),
|
||||
asyncMiddleware(deleteRegistrationToken)
|
||||
)
|
||||
|
||||
runnerRegistrationTokensRouter.get('/registration-tokens',
|
||||
apiRateLimiter,
|
||||
authenticate,
|
||||
ensureUserHasRight(UserRight.MANAGE_RUNNERS),
|
||||
paginationValidator,
|
||||
runnerRegistrationTokensSortValidator,
|
||||
setDefaultSort,
|
||||
setDefaultPagination,
|
||||
asyncMiddleware(listRegistrationTokens)
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
runnerRegistrationTokensRouter
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function generateRegistrationToken (req: express.Request, res: express.Response) {
|
||||
logger.info('Generating new runner registration token.', lTags())
|
||||
|
||||
const registrationToken = new RunnerRegistrationTokenModel({
|
||||
registrationToken: generateRunnerRegistrationToken()
|
||||
})
|
||||
|
||||
await registrationToken.save()
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
}
|
||||
|
||||
async function deleteRegistrationToken (req: express.Request, res: express.Response) {
|
||||
logger.info('Removing runner registration token.', lTags())
|
||||
|
||||
const runnerRegistrationToken = res.locals.runnerRegistrationToken
|
||||
|
||||
await runnerRegistrationToken.destroy()
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
}
|
||||
|
||||
async function listRegistrationTokens (req: express.Request, res: express.Response) {
|
||||
const query: ListRunnerRegistrationTokensQuery = req.query
|
||||
|
||||
const resultList = await RunnerRegistrationTokenModel.listForApi({
|
||||
start: query.start,
|
||||
count: query.count,
|
||||
sort: query.sort
|
||||
})
|
||||
|
||||
return res.json({
|
||||
total: resultList.total,
|
||||
data: resultList.data.map(d => d.toFormattedJSON())
|
||||
})
|
||||
}
|
||||
新しい課題から参照
ユーザをブロックする