diff --git a/api/package.json b/api/package.json index 75cb08c..819a5c5 100644 --- a/api/package.json +++ b/api/package.json @@ -6,7 +6,6 @@ "dependencies": { "body-parser": "^1.19.0", "express": "^4.17.1", - "express-validator": "^6.10.0", "is-docker": "^2.1.1", "js-yaml": "^4.0.0", "logplease": "^1.2.15", diff --git a/api/src/api/v1.js b/api/src/api/v1.js new file mode 100644 index 0000000..72db74e --- /dev/null +++ b/api/src/api/v1.js @@ -0,0 +1,184 @@ +const express = require('express'); +const router = express.Router(); + +const runtime = require('../runtime'); +const {Job} = require("../job"); +const package = require('../package') +const logger = require('logplease').create('api/v1'); + +router.post('/execute', async function(req, res){ + const {language, version, files, stdin, args, run_timeout, compile_timeout} = req.body; + + if(!language || typeof language !== "string") + { + return res + .status(400) + .send({ + message: "language is required as a string" + }); + } + + if(!version || typeof version !== "string") + { + return res + .status(400) + .send({ + message: "version is required as a string" + }); + } + + if(!files || !Array.isArray(files)) + { + return res + .status(400) + .send({ + message: "files is required as an array" + }); + } + + for (const [i,file] of files.entries()) { + if(!file.content || typeof file.content !== "string"){ + return res + .status(400) + .send({ + message: `files[${i}].content is required as a string` + }); + } + } + + + + + const rt = runtime.get_latest_runtime_matching_language_version(language, version); + + if (rt === undefined) { + return res + .status(400) + .send({ + message: `${language}-${version} runtime is unknown` + }); + } + + const job = new Job({ + runtime: rt, + alias: language, + files: files, + args: args || [], + stdin: stdin || "", + timeouts: { + run: run_timeout || 3000, + compile: compile_timeout || 10000 + } + }); + + await job.prime(); + + const result = await job.execute(); + + await job.cleanup(); + + return res + .status(200) + .send(result); +}); + +router.get('/runtimes', function(req, res){ + const runtimes = runtime.map(rt => ({ + language: rt.language, + version: rt.version.raw, + aliases: rt.aliases, + runtime: rt.runtime + })); + + return res + .status(200) + .send(runtimes); +}); + +router.get('/packages', async function(req, res){ + logger.debug('Request to list packages'); + let packages = await package.get_package_list(); + + packages = packages + .map(pkg => { + return { + language: pkg.language, + language_version: pkg.version.raw, + installed: pkg.installed + }; + }); + + return res + .status(200) + .send(packages); +}); + +router.post('/packages/:language/:version', async function(req, res){ + logger.debug('Request to install package'); + + const {language, version} = req.params; + + const pkg = await package.get_package(language, version); + + if (pkg == null) { + return res + .status(404) + .send({ + message: `Requested package ${language}-${version} does not exist` + }); + } + + try { + const response = await pkg.install(); + + return res + .status(200) + .send(response); + } catch(e) { + logger.error(`Error while installing package ${pkg.language}-${pkg.version}:`, e.message); + + return res + .status(500) + .send({ + message: e.message + }); + } +}); + +router.delete('/packages/:language/:version', async function(req, res){ + logger.debug('Request to uninstall package'); + + const {language, version} = req.params; + + const pkg = await package.get_package(language, version); + + if (pkg == null) { + return res + .status(404) + .send({ + message: `Requested package ${language}-${version} does not exist` + }); + } + + try { + const response = await pkg.uninstall(); + + return res + .status(200) + .send(response); + } catch(e) { + logger.error(`Error while uninstalling package ${pkg.language}-${pkg.version}:`, e.message); + + return res + .status(500) + .send({ + message: e.message + }); + } +}); + + + + + +module.exports = router; diff --git a/api/src/executor/routes.js b/api/src/executor/routes.js deleted file mode 100644 index d7cabff..0000000 --- a/api/src/executor/routes.js +++ /dev/null @@ -1,57 +0,0 @@ -// {"language":"python","version":"3.9.1","files":{"code.py":"print('hello world')"},"args":[],"stdin":"","compile_timeout":10, "run_timeout":3} -// {"success":true, "run":{"stdout":"hello world", "stderr":"", "error_code":0},"compile":{"stdout":"","stderr":"","error_code":0}} - -const { get_latest_runtime_matching_language_version } = require('../runtime'); -const { Job } = require('./job'); -const { body } = require('express-validator'); - -module.exports = { - - run_job_validators: [ - body('language') - .isString(), - body('version') - .isString(), - // isSemVer requires it to be a version, not a selector - body('files') - .isArray(), - body('files.*.content') - .isString(), - ], - - // POST /execute - async run_job(req, res) { - const runtime = get_latest_runtime_matching_language_version(req.body.language, req.body.version); - - if (runtime === undefined) { - return res - .status(400) - .send({ - message: `${req.body.language}-${req.body.version} runtime is unknown` - }); - } - - const job = new Job({ - runtime, - alias: req.body.language, - files: req.body.files, - args: req.body.args || [], - stdin: req.body.stdin || "", - timeouts: { - run: req.body.run_timeout || 3000, - compile: req.body.compile_timeout || 10000 - } - }); - - await job.prime(); - - const result = await job.execute(); - - await job.cleanup(); - - return res - .status(200) - .send(result); - } - -}; diff --git a/api/src/index.js b/api/src/index.js index fcf82d6..8a3fbb4 100644 --- a/api/src/index.js +++ b/api/src/index.js @@ -9,7 +9,6 @@ const fs = require('fs/promises'); const fss = require('fs'); const body_parser = require('body-parser'); const runtime = require('./runtime'); -const { validationResult } = require('express-validator'); const logger = Logger.create('index'); const app = express(); @@ -67,48 +66,10 @@ const app = express(); }) }) - const validate = (req, res, next) => { - const errors = validationResult(req); - - if (!errors.isEmpty()) { - return res - .status(400) - .send({ - message: errors.array() - }); - } - - next(); - }; - logger.debug('Registering Routes'); - const ppman_routes = require('./ppman/routes'); - const executor_routes = require('./executor/routes'); - - app.get('/api/v1/packages', ppman_routes.package_list); - app.post('/api/v1/packages/:language/:version', ppman_routes.package_install); - app.delete('/api/v1/packages/:language/:version', ppman_routes.package_uninstall); - app.post('/api/v1/execute', - executor_routes.run_job_validators, - validate, - executor_routes.run_job - ); - app.get('/api/v1/runtimes', (req, res) => { - const runtimes = runtime - .map(rt => { - return { - language: rt.language, - version: rt.version.raw, - aliases: rt.aliases, - runtime: rt.runtime - }; - }); - - return res - .status(200) - .send(runtimes); - }); + const api_v1 = require('./api/v1') + app.use('/api/v1', api_v1); app.use(function (req,res,next){ return res.status(404).send({message: 'Not Found'}); diff --git a/api/src/executor/job.js b/api/src/job.js similarity index 98% rename from api/src/executor/job.js rename to api/src/job.js index 63122d0..b815c11 100644 --- a/api/src/executor/job.js +++ b/api/src/job.js @@ -2,8 +2,8 @@ const logger = require('logplease').create('executor/job'); const {v4: uuidv4} = require('uuid'); const cp = require('child_process'); const path = require('path'); -const config = require('../config'); -const globals = require('../globals'); +const config = require('./config'); +const globals = require('./globals'); const fs = require('fs/promises'); const job_states = { diff --git a/api/src/ppman/package.js b/api/src/package.js similarity index 82% rename from api/src/ppman/package.js rename to api/src/package.js index 0e3c994..6efa33d 100644 --- a/api/src/ppman/package.js +++ b/api/src/package.js @@ -1,14 +1,14 @@ const logger = require('logplease').create('ppman/package'); const semver = require('semver'); -const config = require('../config'); -const globals = require('../globals'); +const config = require('./config'); +const globals = require('./globals'); const fetch = require('node-fetch'); const path = require('path'); const fs = require('fs/promises'); const fss = require('fs'); const cp = require('child_process'); const crypto = require('crypto'); -const runtime = require('../runtime'); +const runtime = require('./runtime'); class Package { @@ -158,8 +158,38 @@ class Package { } + static async get_package_list() { + const repo_content = await fetch(config.repo_url).then(x => x.text()); + + const entries = repo_content + .split('\n') + .filter(x => x.length > 0); + + return entries.map(line => { + const [ language, version, checksum, download ] = line.split(',', 4); + + return new Package({ + language, + version, + checksum, + download + }); + }); + } + + static async get_package (lang, version) { + const packages = await Package.get_package_list(); + + const candidates = packages + .filter(pkg => { + return pkg.language == lang && semver.satisfies(pkg.version, version) + }); + + candidates.sort((a, b) => semver.rcompare(a.version, b.version)); + + return candidates[0] || null; + } + } -module.exports = { - Package -}; +module.exports = Package; diff --git a/api/src/ppman/routes.js b/api/src/ppman/routes.js deleted file mode 100644 index 89f472c..0000000 --- a/api/src/ppman/routes.js +++ /dev/null @@ -1,123 +0,0 @@ -const logger = require('logplease').create('ppman/routes'); -const semver = require('semver'); -const fetch = require('node-fetch'); -const config = require('../config'); -const { Package } = require('./package'); - -const get_package_list = async () => { - const repo_content = await fetch(config.repo_url).then(x => x.text()); - - const entries = repo_content - .split('\n') - .filter(x => x.length > 0); - - return entries.map(line => { - const [ language, version, checksum, download ] = line.split(',', 4); - - return new Package({ - language, - version, - checksum, - download - }); - }); -}; - -const get_package = async (lang, version) => { - const packages = await get_package_list(); - - const candidates = packages - .filter(pkg => { - return pkg.language == lang && semver.satisfies(pkg.version, version) - }); - - candidates.sort((a, b) => semver.rcompare(a.version, b.version)); - - return candidates[0] || null; -}; - -module.exports = { - - // GET /packages - async package_list(req, res) { - logger.debug('Request to list packages'); - - let packages = await get_package_list(); - - packages = packages - .map(pkg => { - return { - language: pkg.language, - language_version: pkg.version.raw, - installed: pkg.installed - }; - }); - - return res - .status(200) - .send(packages); - }, - - // POST /packages/:language/:version - async package_install(req, res) { - logger.debug('Request to install package'); - - const pkg = await get_package(req.params.language, req.params.version); - - if (pkg == null) { - return res - .status(404) - .send({ - message: `Requested package ${req.params.language}-${req.params.version} does not exist` - }); - } - - try { - const response = await pkg.install(); - - return res - .status(200) - .send(response); - } catch(e) { - logger.error(`Error while installing package ${pkg.language}-${pkg.version}:`, e.message); - - return res - .status(500) - .send({ - message: e.message - }); - } - }, - - // DELETE /packages/:language/:version - async package_uninstall(req, res) { - logger.debug('Request to uninstall package'); - - const pkg = await get_package(req.params.language, req.params.version); - - if (pkg == null) { - return res - .status(404) - .send({ - message: `Requested package ${req.params.language}-${req.params.version} does not exist` - }); - } - - try { - const response = await pkg.uninstall(); - - return res - .status(200) - .send(response); - } catch(e) { - logger.error(`Error while uninstalling package ${pkg.language}-${pkg.version}:`, e.message); - - return res - .status(500) - .send({ - message: e.message - }); - } - } - -};