diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 81e800b059..b5d8c1f527 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -118,7 +118,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - node: [8, 10] + node: [10] os: [ubuntu-16.04, ubuntu-latest] steps: @@ -131,7 +131,6 @@ jobs: - name: yarn install and test run: | cd src/rest-server - yarn config set ignore-engines true yarn install --frozen-lockfiles yarn test diff --git a/.travis.yml b/.travis.yml index 44b8d844cd..01567cf08c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -87,7 +87,7 @@ matrix: - mvn clean test jacoco:report - language: node_js - node_js: lts/carbon + node_js: lts/dubnium env: NODE_ENV=test before_install: - cd src/rest-server @@ -97,16 +97,6 @@ matrix: - npm test - npm run coveralls - - language: node_js - node_js: lts/dubnium - env: NODE_ENV=test - before_install: - - cd src/rest-server - install: - - yarn install --ignore-engines - script: - - npm test - - language: node_js node_js: lts/carbon before_install: diff --git a/src/rest-server/.eslintrc.js b/src/rest-server/.eslintrc.js index cd1899305e..429ce6e13f 100644 --- a/src/rest-server/.eslintrc.js +++ b/src/rest-server/.eslintrc.js @@ -1,19 +1,29 @@ module.exports = { - "parserOptions": { - "ecmaVersion": 2017, - "ecmaFeatures": { - "experimentalObjectRestSpread": true - } + plugins: ['eslint-plugin-prettier'], + env: { + es6: true, + node: true, + mocha: true, + browser: false, }, - "env": { - "es6": true, - "node": true, + extends: ['standard', 'plugin:prettier/recommended', 'prettier'], + parserOptions: { + ecmaFeatures: { + ecmaVersion: 8, + experimentalObjectRestSpread: true, + }, + sourceType: 'module', }, - "extends": ["eslint:recommended", "google"], - "rules": { - "max-len": [0, 80], - "require-jsdoc": 0, - "valid-jsdoc": 0, - "linebreak-style": 0, + rules: { + 'prettier/prettier': ['error'], + 'max-len': [ + 'error', + { + code: 120, + ignoreComments: true, + ignoreStrings: false, + ignoreTemplateLiterals: true, + }, + ], }, }; diff --git a/src/rest-server/build/rest-server.common.dockerfile b/src/rest-server/build/rest-server.common.dockerfile index 4ce0ddf084..44efebba11 100644 --- a/src/rest-server/build/rest-server.common.dockerfile +++ b/src/rest-server/build/rest-server.common.dockerfile @@ -15,7 +15,7 @@ # DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -FROM node:carbon +FROM node:dubnium WORKDIR /usr/src/app diff --git a/src/rest-server/package.json b/src/rest-server/package.json index 0a331798eb..73d56f7fad 100644 --- a/src/rest-server/package.json +++ b/src/rest-server/package.json @@ -21,9 +21,10 @@ "url": "https://github.com/Microsoft/pai/issues" }, "engines": { - "node": "^8.9.0" + "node": "^10" }, "dependencies": { + "openpaidbsdk": "file:./openpaidbsdk", "@kubernetes/client-node": "^0.11.0", "ajv": "^6.10.0", "ajv-merge-patch": "~4.1.0", @@ -37,8 +38,6 @@ "cors": "~2.8.4", "coveralls": "~3.0.0", "dotenv": "~4.0.0", - "eslint": "~4.18.2", - "eslint-config-google": "~0.9.1", "express": "~4.16.2", "express-rate-limit": "^5.1.1", "fs-extra": "~7.0.1", @@ -54,7 +53,6 @@ "nock": "~9.1.6", "node-cache": "~4.2.0", "nyc": "^14.1.1", - "openpaidbsdk": "file:./openpaidbsdk", "pg": "^7.17.1", "querystring": "~0.2.0", "sequelize": "^5.21.3", @@ -70,11 +68,23 @@ }, "scripts": { "coveralls": "nyc report --reporter=text-lcov | coveralls ..", - "lint": "eslint .", + "lint": "eslint --ext .js ./src", "mocha": "mocha --file ./test/setup --ui bdd --recursive --timeout 1000 --exit", "preinstall": "npx ncp ../database-controller/sdk openpaidbsdk || echo skip copying openpaidbsdk", - "postinstall": "rm -rf openpaidbsdk", + "postinstall": "rimraf openpaidbsdk", "start": "node index.js", "test": "npm run lint && nyc npm run mocha" + }, + "devDependencies": { + "eslint": "^7.7.0", + "eslint-config-prettier": "~6.11.0", + "eslint-config-standard": "~14.1.1", + "eslint-plugin-import": "^2.22.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "~3.1.4", + "eslint-plugin-promise": "^4.2.1", + "eslint-plugin-standard": "^4.0.1", + "prettier": "~2.0.5", + "rimraf": "^3.0.2" } } diff --git a/src/rest-server/prettier.config.js b/src/rest-server/prettier.config.js new file mode 100644 index 0000000000..b3b694afd9 --- /dev/null +++ b/src/rest-server/prettier.config.js @@ -0,0 +1,7 @@ +module.exports = { + semi: true, + // Trailing commas help with git merging and conflict resolution + trailingComma: 'all', + // Use single quote in all files. https://github.com/prettier/prettier/issues/1080#issuecomment-390363232 + singleQuote: true, +}; diff --git a/src/rest-server/src/config/authn.js b/src/rest-server/src/config/authn.js index c4b0710bfb..c58c9a6c5f 100644 --- a/src/rest-server/src/config/authn.js +++ b/src/rest-server/src/config/authn.js @@ -32,7 +32,8 @@ if (authnConfig.authnMethod === 'OIDC') { const initOIDCEndpointAndGroupUrl = async () => { try { const response = await axios.get(authnConfig.OIDCConfig.wellKnownURL); - authnConfig.OIDCConfig.authorization_endpoint = response.data.authorization_endpoint; + authnConfig.OIDCConfig.authorization_endpoint = + response.data.authorization_endpoint; authnConfig.OIDCConfig.token_endpoint = response.data.token_endpoint; authnConfig.OIDCConfig.msgraph_host = response.data.msgraph_host; } catch (error) { @@ -46,8 +47,10 @@ if (authnConfig.authnMethod === 'OIDC') { if (process.env.OIDC_CONFIG_PATH) { odicConfigPath = process.env.OIDC_CONFIG_PATH; } - authnConfig.OIDCConfig = yaml.safeLoad(fs.readFileSync(odicConfigPath, 'utf8')); - (async function() { + authnConfig.OIDCConfig = yaml.safeLoad( + fs.readFileSync(odicConfigPath, 'utf8'), + ); + (async function () { await initOIDCEndpointAndGroupUrl(); })(); } @@ -57,22 +60,24 @@ try { if (process.env.GROUP_CONFIG_PATH) { groupConfigPath = process.env.GROUP_CONFIG_PATH; } - authnConfig.groupConfig = yaml.safeLoad(fs.readFileSync(groupConfigPath, 'utf8')); + authnConfig.groupConfig = yaml.safeLoad( + fs.readFileSync(groupConfigPath, 'utf8'), + ); } catch (error) { logger.error('Failed to load group config from configmap file.'); throw error; } // define the schema for authn -const authnSchema = Joi.object().keys({ - authnMethod: Joi.string().empty('') - .valid('OIDC', 'basic'), - OIDCConfig: Joi.object().pattern(/\w+/, Joi.required()), - groupConfig: Joi.object().pattern(/\w+/, Joi.required()), -}).required(); +const authnSchema = Joi.object() + .keys({ + authnMethod: Joi.string().empty('').valid('OIDC', 'basic'), + OIDCConfig: Joi.object().pattern(/\w+/, Joi.required()), + groupConfig: Joi.object().pattern(/\w+/, Joi.required()), + }) + .required(); - -const {error, value} = Joi.validate(authnConfig, authnSchema); +const { error, value } = Joi.validate(authnConfig, authnSchema); if (error) { throw new Error(`config error\n${error}`); } diff --git a/src/rest-server/src/config/express.js b/src/rest-server/src/config/express.js index 54377c8cf2..c7aebff9ef 100644 --- a/src/rest-server/src/config/express.js +++ b/src/rest-server/src/config/express.js @@ -15,7 +15,6 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // module dependencies const fs = require('fs'); const cors = require('cors'); @@ -44,14 +43,14 @@ app.set('json spaces', config.env === 'development' ? 4 : 0); app.use(cors()); app.use(compress()); -app.use(bodyParser.urlencoded({extended: true})); +app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); -app.use(bodyParser.text({type: 'text/*'})); +app.use(bodyParser.text({ type: 'text/*' })); app.use(cookieParser()); app.use(limiter.api); // setup the logger for requests -app.use(morgan('dev', {'stream': logger.stream})); +app.use(morgan('dev', { stream: logger.stream })); // mount all v1 APIs to /api/v1 app.use('/api/v1', routers.v1); @@ -70,9 +69,9 @@ app.use((req, res, next) => { if (authnConfig.authnMethod === 'OIDC') { // error handler for /api/v1/authn/oidc/return - app.use('/api/v1/authn/oidc/return', function(err, req, res, next) { + app.use('/api/v1/authn/oidc/return', function (err, req, res, next) { logger.warn(err); - let qsData = { + const qsData = { errorMessage: err.message, }; let redirectURI = err.targetURI ? err.targetURI : process.env.WEBPORTAL_URL; @@ -87,7 +86,7 @@ app.use((err, req, res, next) => { res.status(err.status || 500).json({ code: err.code, message: err.message, - stack: config.env === 'development' ? err.stack.split('\n') : void 0, + stack: config.env === 'development' ? err.stack.split('\n') : undefined, }); }); diff --git a/src/rest-server/src/config/index.js b/src/rest-server/src/config/index.js index 69e329fc4c..ba3649b7b1 100644 --- a/src/rest-server/src/config/index.js +++ b/src/rest-server/src/config/index.js @@ -15,14 +15,13 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // module dependencies const fse = require('fs-extra'); const Joi = require('joi'); const dotenv = require('dotenv'); const mustache = require('mustache'); - +/* eslint-disable-next-line node/no-deprecated-api */ require.extensions['.mustache'] = (module, filename) => { module.exports = fse.readFileSync(filename, 'utf8'); }; @@ -31,20 +30,29 @@ dotenv.config(); mustache.escape = (string) => { // https://stackoverflow.com/questions/15783701/which-characters-need-to-be-escaped-when-using-bash/27817504#27817504 - return String(string) - /* eslint-disable no-control-regex */ - .replace(/[\x20-\x24\x26-\x2A\x2C\x3B\x3C\x3E\x3F\x5B-\x5E\x60\x7B-\x7E]/g, '\\$&') - .replace(/[\x00-\x1F\x7F]/g, (c) => ({ - /* eslint-enable no-control-regex */ - 0x07: '\\a', - 0x08: '\\b', - 0x09: '\\t', - 0x0A: '\\n', - 0x0B: '\\v', - 0x0C: '\\f', - 0x0D: '\\r', - 0x1B: '\\E', - }[c.charCodeAt(0)] || '')); + return ( + String(string) + /* eslint-disable no-control-regex */ + .replace( + /[\x20-\x24\x26-\x2A\x2C\x3B\x3C\x3E\x3F\x5B-\x5E\x60\x7B-\x7E]/g, + '\\$&', + ) + .replace( + /[\x00-\x1F\x7F]/g, + (c) => + ({ + /* eslint-enable no-control-regex */ + 0x07: '\\a', + 0x08: '\\b', + 0x09: '\\t', + 0x0a: '\\n', + 0x0b: '\\v', + 0x0c: '\\f', + 0x0d: '\\r', + 0x1b: '\\E', + }[c.charCodeAt(0)] || ''), + ) + ); }; // get config from environment variables @@ -56,24 +64,22 @@ let config = { }; // define config schema -const configSchema = Joi.object().keys({ - env: Joi.string() - .allow(['test', 'development', 'production']) - .default('development'), - logLevel: Joi.string() - .allow(['error', 'warn', 'info', 'verbose', 'debug', 'silly']) - .default('debug'), - serverPort: Joi.number() - .integer() - .min(8000) - .max(65535) - .default(9186), - jwtSecret: Joi.string() - .required() - .description('JWT Secret required to sign'), -}).required(); +const configSchema = Joi.object() + .keys({ + env: Joi.string() + .allow(['test', 'development', 'production']) + .default('development'), + logLevel: Joi.string() + .allow(['error', 'warn', 'info', 'verbose', 'debug', 'silly']) + .default('debug'), + serverPort: Joi.number().integer().min(8000).max(65535).default(9186), + jwtSecret: Joi.string() + .required() + .description('JWT Secret required to sign'), + }) + .required(); -const {error, value} = Joi.validate(config, configSchema); +const { error, value } = Joi.validate(config, configSchema); if (error) { throw new Error(`config error\n${error}`); } diff --git a/src/rest-server/src/config/kubernetes.js b/src/rest-server/src/config/kubernetes.js index 288932158d..f93ef1ef0f 100644 --- a/src/rest-server/src/config/kubernetes.js +++ b/src/rest-server/src/config/kubernetes.js @@ -15,9 +15,8 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - const assert = require('assert'); -const {readFileSync} = require('fs'); +const { readFileSync } = require('fs'); const k8s = require('@kubernetes/client-node'); const logger = require('@pai/config/logger'); @@ -61,22 +60,29 @@ if (RBAC_IN_CLUSTER === 'false') { } // https://github.com/kubernetes-client/javascript/blob/da9f3d872bdebaebf37fe22f089b2a1c655fe591/src/config.ts#L373 - const httpsOptions = {headers: {}}; + const httpsOptions = { headers: {} }; const cluster = kc.getCurrentCluster(); apiserverConfig.uri = cluster.server; - initPromise = kc.applytoHTTPSOptions(httpsOptions).then(() => { - apiserverConfig.headers = httpsOptions.headers; - apiserverConfig.ca = httpsOptions.ca; - apiserverConfig.key = httpsOptions.key; - apiserverConfig.cert = httpsOptions.cert; - }).catch((e) => { - logger.error('failed to init rbac config. Please check your clusters\' config'); - logger.error(e.stack); - // hard rejection - process.exit(1); - }); + initPromise = kc + .applytoHTTPSOptions(httpsOptions) + .then(() => { + apiserverConfig.headers = httpsOptions.headers; + apiserverConfig.ca = httpsOptions.ca; + apiserverConfig.key = httpsOptions.key; + apiserverConfig.cert = httpsOptions.cert; + }) + .catch((e) => { + logger.error( + "failed to init rbac config. Please check your clusters' config", + ); + logger.error(e.stack); + // hard rejection + process.exit(1); + }); } catch (error) { - logger.error('failed to init rbac config. Please check your clusters\' config'); + logger.error( + "failed to init rbac config. Please check your clusters' config", + ); throw error; } } diff --git a/src/rest-server/src/config/launcher.js b/src/rest-server/src/config/launcher.js index 92cc01b014..bf2835a2a6 100644 --- a/src/rest-server/src/config/launcher.js +++ b/src/rest-server/src/config/launcher.js @@ -15,68 +15,38 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // module dependencies const Joi = require('joi'); - // define k8s launcher config schema -const k8sLauncherConfigSchema = Joi.object().keys({ - hivedWebserviceUri: Joi.string() - .uri() - .required(), - enabledPriorityClass: Joi.boolean() - .required(), - apiVersion: Joi.string() - .required(), - podGracefulDeletionTimeoutSec: Joi.number() - .integer() - .default(30 * 60), - scheduler: Joi.string() - .required(), - enabledHived: Joi.boolean() - .required(), - hivedSpecPath: Joi.string() - .required(), - runtimeImage: Joi.string() - .required(), - runtimeImagePullSecrets: Joi.string() - .required(), - requestHeaders: Joi.object(), - sqlConnectionString: Joi.string() - .required(), - sqlMaxConnection: Joi.number() - .integer() - .required(), - enabledJobHistory: Joi.boolean() - .required(), - writeMergerUrl: Joi.string() - .required(), - healthCheckPath: Joi.func() - .arity(0) - .required(), - frameworksPath: Joi.func() - .arity(0) - .required(), - frameworkPath: Joi.func() - .arity(1) - .required(), - priorityClassesPath: Joi.func() - .arity(0) - .required(), - priorityClassPath: Joi.func() - .arity(1) - .required(), - secretsPath: Joi.func() - .arity(0) - .required(), - secretPath: Joi.func() - .arity(1) - .required(), - podPath: Joi.func() - .arity(1) - .required(), -}).required(); +const k8sLauncherConfigSchema = Joi.object() + .keys({ + hivedWebserviceUri: Joi.string().uri().required(), + enabledPriorityClass: Joi.boolean().required(), + apiVersion: Joi.string().required(), + podGracefulDeletionTimeoutSec: Joi.number() + .integer() + .default(30 * 60), + scheduler: Joi.string().required(), + enabledHived: Joi.boolean().required(), + hivedSpecPath: Joi.string().required(), + runtimeImage: Joi.string().required(), + runtimeImagePullSecrets: Joi.string().required(), + requestHeaders: Joi.object(), + sqlConnectionString: Joi.string().required(), + sqlMaxConnection: Joi.number().integer().required(), + enabledJobHistory: Joi.boolean().required(), + writeMergerUrl: Joi.string().required(), + healthCheckPath: Joi.func().arity(0).required(), + frameworksPath: Joi.func().arity(0).required(), + frameworkPath: Joi.func().arity(1).required(), + priorityClassesPath: Joi.func().arity(0).required(), + priorityClassPath: Joi.func().arity(1).required(), + secretsPath: Joi.func().arity(0).required(), + secretPath: Joi.func().arity(1).required(), + podPath: Joi.func().arity(1).required(), + }) + .required(); let launcherConfig; const launcherType = process.env.LAUNCHER_TYPE; @@ -90,9 +60,10 @@ if (launcherType === 'k8s') { runtimeImage: process.env.LAUNCHER_RUNTIME_IMAGE, runtimeImagePullSecrets: process.env.LAUNCHER_RUNTIME_IMAGE_PULL_SECRETS, enabledHived: process.env.LAUNCHER_SCHEDULER === 'hivedscheduler', - hivedSpecPath: process.env.HIVED_SPEC_PATH || '/hived-spec/hivedscheduler.yaml', + hivedSpecPath: + process.env.HIVED_SPEC_PATH || '/hived-spec/hivedscheduler.yaml', requestHeaders: { - 'Accept': 'application/json', + Accept: 'application/json', 'Content-Type': 'application/json', }, sqlConnectionString: process.env.SQL_CONNECTION_STR || 'unset', @@ -102,10 +73,10 @@ if (launcherType === 'k8s') { healthCheckPath: () => { return `/apis/${launcherConfig.apiVersion}`; }, - frameworksPath: (namespace='default') => { + frameworksPath: (namespace = 'default') => { return `/apis/${launcherConfig.apiVersion}/namespaces/${namespace}/frameworks`; }, - frameworkPath: (frameworkName, namespace='default') => { + frameworkPath: (frameworkName, namespace = 'default') => { return `/apis/${launcherConfig.apiVersion}/namespaces/${namespace}/frameworks/${frameworkName}`; }, priorityClassesPath: () => { @@ -114,18 +85,21 @@ if (launcherType === 'k8s') { priorityClassPath: (priorityClassName) => { return `/apis/scheduling.k8s.io/v1/priorityclasses/${priorityClassName}`; }, - secretsPath: (namespace='default') => { + secretsPath: (namespace = 'default') => { return `/api/v1/namespaces/${namespace}/secrets`; }, - secretPath: (secretName, namespace='default') => { + secretPath: (secretName, namespace = 'default') => { return `/api/v1/namespaces/${namespace}/secrets/${secretName}`; }, - podPath: (podName, namespace='default') => { + podPath: (podName, namespace = 'default') => { return `/api/v1/namespaces/${namespace}/pods/${podName}`; }, }; - const {error, value} = Joi.validate(launcherConfig, k8sLauncherConfigSchema); + const { error, value } = Joi.validate( + launcherConfig, + k8sLauncherConfigSchema, + ); if (error) { throw new Error(`launcher config error\n${error}`); } diff --git a/src/rest-server/src/config/logger.js b/src/rest-server/src/config/logger.js index 3b58baf490..442ee60fd8 100644 --- a/src/rest-server/src/config/logger.js +++ b/src/rest-server/src/config/logger.js @@ -15,13 +15,11 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // module dependencies const util = require('util'); const winston = require('winston'); const config = require('@pai/config'); - const logTransports = { console: new winston.transports.Console({ json: false, @@ -30,12 +28,14 @@ const logTransports = { formatter: (options) => { const timestamp = options.timestamp(); const level = winston.config.colorize( - options.level, - options.level.toUpperCase() + options.level, + options.level.toUpperCase(), ); const message = options.message ? options.message : ''; - const meta = options.meta && Object.keys(options.meta).length ? - '\nmeta = ' + JSON.stringify(options.meta, null, 2) : ''; + const meta = + options.meta && Object.keys(options.meta).length + ? '\nmeta = ' + JSON.stringify(options.meta, null, 2) + : ''; return util.format(timestamp, '[' + level + ']', message, meta); }, }), @@ -50,10 +50,7 @@ const logTransports = { // create logger const logger = new winston.Logger({ level: config.logLevel, - transports: [ - logTransports.console, - logTransports.file, - ], + transports: [logTransports.console, logTransports.file], exitOnError: false, }); diff --git a/src/rest-server/src/config/paiConfig.js b/src/rest-server/src/config/paiConfig.js index 7411731e06..8f699bee94 100644 --- a/src/rest-server/src/config/paiConfig.js +++ b/src/rest-server/src/config/paiConfig.js @@ -18,62 +18,64 @@ // module dependencies const Joi = require('joi'); const yaml = require('js-yaml'); -const {get} = require('lodash'); +const { get } = require('lodash'); const fs = require('fs'); const logger = require('@pai/config/logger'); const k8sModel = require('@pai/models/kubernetes/kubernetes'); let paiMachineList = []; try { - paiMachineList = yaml.safeLoad(fs.readFileSync('/pai-cluster-config/layout.yaml', 'utf8'))['machine-list']; + paiMachineList = yaml.safeLoad( + fs.readFileSync('/pai-cluster-config/layout.yaml', 'utf8'), + )['machine-list']; } catch (err) { - paiMachineList = []; - logger.warn('Unable to load machine list from cluster-configuration.'); - logger.warn('The machine list will be initialized as an empty list.'); + paiMachineList = []; + logger.warn('Unable to load machine list from cluster-configuration.'); + logger.warn('The machine list will be initialized as an empty list.'); } let paiConfigData = { - machineList: paiMachineList, - version: null, - debuggingReservationSeconds: Number(process.env.DEBUGGING_RESERVATION_SECONDS || '604800'), + machineList: paiMachineList, + version: null, + debuggingReservationSeconds: Number( + process.env.DEBUGGING_RESERVATION_SECONDS || '604800', + ), }; - // define the schema for pai configuration -const paiConfigSchema = Joi.object().keys({ +const paiConfigSchema = Joi.object() + .keys({ machineList: Joi.array(), version: Joi.string().allow(null), debuggingReservationSeconds: Joi.number().integer().positive(), -}).required(); - + }) + .required(); -const {error, value} = Joi.validate(paiConfigData, paiConfigSchema); +const { error, value } = Joi.validate(paiConfigData, paiConfigSchema); if (error) { - throw new Error(`config error\n${error}`); + throw new Error(`config error\n${error}`); } paiConfigData = value; const fetchPAIVersion = async () => { - try { - const client = k8sModel.getClient(); - const res = await client.get('/api/v1/namespaces/default/configmaps/pai-version'); - const version = get(res.data, 'data["PAI.VERSION"]'); - if (version) { - return version.trim(); - } else { - return null; - } - } catch (err) { - throw err; - } + const client = k8sModel.getClient(); + const res = await client.get( + '/api/v1/namespaces/default/configmaps/pai-version', + ); + const version = get(res.data, 'data["PAI.VERSION"]'); + if (version) { + return version.trim(); + } else { + return null; + } }; -fetchPAIVersion().then( - (res) => { - paiConfigData.version = res; - } -).catch(() => { +fetchPAIVersion() + .then((res) => { + paiConfigData.version = res; + }) + .catch(() => { logger.warn('Unable to load pai version from config map.'); -}); + }); module.exports = paiConfigData; diff --git a/src/rest-server/src/config/rate-limit.js b/src/rest-server/src/config/rate-limit.js index 84546a122d..1ab03776ae 100644 --- a/src/rest-server/src/config/rate-limit.js +++ b/src/rest-server/src/config/rate-limit.js @@ -19,26 +19,21 @@ const Joi = require('joi'); const rateLimit = require('express-rate-limit'); - let limiterConfig = { apiPerMin: process.env.RATE_LIMIT_API_PER_MIN, listJobPerMin: process.env.RATE_LIMIT_LIST_JOB_PER_MIN, submitJobPerHour: process.env.RATE_LIMIT_SUBMIT_JOB_PER_HOUR, }; -const limiterConfigSchema = Joi.object().keys({ - apiPerMin: Joi.number() - .integer() - .default(600), - listJobPerMin: Joi.number() - .integer() - .default(60), - submitJobPerHour: Joi.number() - .integer() - .default(60), -}).required(); +const limiterConfigSchema = Joi.object() + .keys({ + apiPerMin: Joi.number().integer().default(600), + listJobPerMin: Joi.number().integer().default(60), + submitJobPerHour: Joi.number().integer().default(60), + }) + .required(); -const {error, value} = Joi.validate(limiterConfig, limiterConfigSchema); +const { error, value } = Joi.validate(limiterConfig, limiterConfigSchema); if (error) { throw new Error(`rate limit config error\n${error}`); } diff --git a/src/rest-server/src/config/schedule-port.js b/src/rest-server/src/config/schedule-port.js index e2a1dc7905..b897148f0c 100644 --- a/src/rest-server/src/config/schedule-port.js +++ b/src/rest-server/src/config/schedule-port.js @@ -18,22 +18,22 @@ // module dependencies const Joi = require('joi'); - let schedulePortConfig = { start: process.env.SCHEDULE_PORT_START, end: process.env.SCHEDULE_PORT_END, }; -const schedulePortConfigSchema = Joi.object().keys({ - start: Joi.number() - .integer() - .default(15000), - end: Joi.number() - .integer() - .default(40000), -}).required(); +const schedulePortConfigSchema = Joi.object() + .keys({ + start: Joi.number().integer().default(15000), + end: Joi.number().integer().default(40000), + }) + .required(); -const {error, value} = Joi.validate(schedulePortConfig, schedulePortConfigSchema); +const { error, value } = Joi.validate( + schedulePortConfig, + schedulePortConfigSchema, +); if (error) { throw new Error(`schedule port config error\n${error}`); } diff --git a/src/rest-server/src/config/secret.js b/src/rest-server/src/config/secret.js index e285d5de07..b756154c34 100644 --- a/src/rest-server/src/config/secret.js +++ b/src/rest-server/src/config/secret.js @@ -15,7 +15,6 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // module dependencies const Joi = require('joi'); const authnConfig = require('@pai/config/authn'); @@ -36,24 +35,24 @@ if (authnConfig.authnMethod !== 'OIDC') { let userSecretConfigSchema = {}; if (authnConfig.authnMethod !== 'OIDC') { - userSecretConfigSchema = Joi.object().keys({ - paiUserNameSpace: Joi.string() - .default('pai-user'), - adminName: Joi.string() - .regex(/^[\w.-]+$/, 'username') - .required(), - adminPass: Joi.string() - .min(6) - .required(), - }).required(); + userSecretConfigSchema = Joi.object() + .keys({ + paiUserNameSpace: Joi.string().default('pai-user'), + adminName: Joi.string() + .regex(/^[\w.-]+$/, 'username') + .required(), + adminPass: Joi.string().min(6).required(), + }) + .required(); } else { - userSecretConfigSchema = Joi.object().keys({ - paiUserNameSpace: Joi.string() - .default('pai-user'), - }).required(); + userSecretConfigSchema = Joi.object() + .keys({ + paiUserNameSpace: Joi.string().default('pai-user'), + }) + .required(); } -const {error, value} = Joi.validate(userSecretConfig, userSecretConfigSchema); +const { error, value } = Joi.validate(userSecretConfig, userSecretConfigSchema); if (error) { throw new Error(`config error\n${error}`); } diff --git a/src/rest-server/src/config/token.js b/src/rest-server/src/config/token.js index ee49e5bbda..46e5497b77 100644 --- a/src/rest-server/src/config/token.js +++ b/src/rest-server/src/config/token.js @@ -20,21 +20,23 @@ const Joi = require('joi'); const config = require('@pai/config'); // define input schema -const tokenPostInputSchema = Joi.object().keys({ - username: Joi.string() - .regex(/^[\w.-]+$/, 'username') - .required(), - password: Joi.string() - .min(6) - .required(), - expiration: Joi.number() - .integer() - .min(60) - .max(7 * 24 * 60 * 60) - .default(24 * 60 * 60), -}).required(); +const tokenPostInputSchema = Joi.object() + .keys({ + username: Joi.string() + .regex(/^[\w.-]+$/, 'username') + .required(), + password: Joi.string().min(6).required(), + expiration: Joi.number() + .integer() + .min(60) + .max(7 * 24 * 60 * 60) + .default(24 * 60 * 60), + }) + .required(); -const tokenExpireTime = process.env.JWT_TOKEN_EXPIRE_TIME ? process.env.JWT_TOKEN_EXPIRE_TIME : '7d'; +const tokenExpireTime = process.env.JWT_TOKEN_EXPIRE_TIME + ? process.env.JWT_TOKEN_EXPIRE_TIME + : '7d'; // module exports module.exports = { diff --git a/src/rest-server/src/config/v2/group.js b/src/rest-server/src/config/v2/group.js index ff9cc8ded9..ebb9f9e227 100644 --- a/src/rest-server/src/config/v2/group.js +++ b/src/rest-server/src/config/v2/group.js @@ -19,9 +19,11 @@ const Joi = require('joi'); // define the input schema for the 'update group extension' api -const groupExtensionUpdateInputSchema = Joi.object().keys({ - extension: Joi.object().pattern(/\w+/, Joi.required()), -}).required(); +const groupExtensionUpdateInputSchema = Joi.object() + .keys({ + extension: Joi.object().pattern(/\w+/, Joi.required()), + }) + .required(); // define the input schema for the 'update group description' api const groupDescriptionUpdateInputSchema = Joi.object().keys({ @@ -43,42 +45,36 @@ const groupCreateInputSchema = Joi.object().keys({ groupname: Joi.string() .regex(/^[A-Za-z0-9_]+$/, 'groupname') .required(), - description: Joi.string() - .empty(''), - externalName: Joi.string() - .empty(''), - extension: Joi.object() - .pattern(/\w+/, Joi.required()) - .default(), + description: Joi.string().empty(''), + externalName: Joi.string().empty(''), + extension: Joi.object().pattern(/\w+/, Joi.required()).default(), }); // define the input schema for the 'Update group' api const groupUpdateInputSchema = Joi.object().keys({ - patch: Joi.boolean() - .default(false), - data: Joi.object().keys({ - groupname: Joi.string() - .regex(/^[A-Za-z0-9_]+$/, 'groupname') - .required(), - description: Joi.string(), - externalName: Joi.string(), - extension: Joi.object() - .pattern(/\w+/, Joi.required()), - }) + patch: Joi.boolean().default(false), + data: Joi.object() + .keys({ + groupname: Joi.string() + .regex(/^[A-Za-z0-9_]+$/, 'groupname') + .required(), + description: Joi.string(), + externalName: Joi.string(), + extension: Joi.object().pattern(/\w+/, Joi.required()), + }) .when('patch', { - is: true, - then: Joi.object({ - description: Joi.empty(null), - externalName: Joi.empty(null), - extension: Joi.empty(null), - }), - otherwise: Joi.object({ - description: Joi.required(), - externalName: Joi.required(), - extension: Joi.required(), - }), - } - ), + is: true, + then: Joi.object({ + description: Joi.empty(null), + externalName: Joi.empty(null), + extension: Joi.empty(null), + }), + otherwise: Joi.object({ + description: Joi.required(), + externalName: Joi.required(), + extension: Joi.required(), + }), + }), }); // module exports diff --git a/src/rest-server/src/config/v2/hived.js b/src/rest-server/src/config/v2/hived.js index a8246ea8ee..908ef1440c 100644 --- a/src/rest-server/src/config/v2/hived.js +++ b/src/rest-server/src/config/v2/hived.js @@ -15,10 +15,9 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // module dependencies const Ajv = require('ajv'); -const ajv = new Ajv({allErrors: true, useDefaults: true}); +const ajv = new Ajv({ allErrors: true, useDefaults: true }); // hived schema const hivedSchema = { @@ -68,7 +67,6 @@ const hivedSchema = { const hivedValidate = ajv.compile(hivedSchema); - // module exports module.exports = { validate: hivedValidate, diff --git a/src/rest-server/src/config/v2/protocol.js b/src/rest-server/src/config/v2/protocol.js index bd6460a671..8bc601af3c 100644 --- a/src/rest-server/src/config/v2/protocol.js +++ b/src/rest-server/src/config/v2/protocol.js @@ -15,12 +15,11 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // module dependencies const Ajv = require('ajv'); const ajvMerge = require('ajv-merge-patch/keywords/merge'); -const ajv = new Ajv({allErrors: true}); +const ajv = new Ajv({ allErrors: true }); ajvMerge(ajv); // base schema @@ -243,11 +242,7 @@ const protocolSchema = { additionalProperties: false, }, }, - required: [ - 'cpu', - 'memoryMB', - 'gpu', - ], + required: ['cpu', 'memoryMB', 'gpu'], additionalProperties: false, }, commands: { @@ -258,11 +253,7 @@ const protocolSchema = { minItems: 1, }, }, - required: [ - 'dockerImage', - 'resourcePerInstance', - 'commands', - ], + required: ['dockerImage', 'resourcePerInstance', 'commands'], additionalProperties: false, }, }, @@ -307,10 +298,7 @@ const protocolSchema = { additionalProperties: false, }, }, - required: [ - 'name', - 'taskRoles', - ], + required: ['name', 'taskRoles'], }, minItems: 1, }, @@ -326,18 +314,12 @@ const protocolSchema = { type: 'object', }, }, - required: [ - 'protocolVersion', - 'name', - 'type', - 'taskRoles', - ], + required: ['protocolVersion', 'name', 'type', 'taskRoles'], additionalProperties: false, }; const protocolValidate = ajv.addSchema(baseSchema).compile(protocolSchema); - // module exports module.exports = { validate: protocolValidate, diff --git a/src/rest-server/src/config/v2/user.js b/src/rest-server/src/config/v2/user.js index f24eb64cda..64a7dfdfea 100644 --- a/src/rest-server/src/config/v2/user.js +++ b/src/rest-server/src/config/v2/user.js @@ -19,9 +19,11 @@ const Joi = require('joi'); // define the input schema for the 'update user extension' api -const userExtensionUpdateInputSchema = Joi.object().keys({ - extension: Joi.object().pattern(/\w+/, Joi.required()), -}).required(); +const userExtensionUpdateInputSchema = Joi.object() + .keys({ + extension: Joi.object().pattern(/\w+/, Joi.required()), + }) + .required(); // define the input schema for the 'update user virtualCluster' api const userVirtualClusterUpdateInputSchema = Joi.object().keys({ @@ -61,91 +63,72 @@ const userCreateInputSchema = Joi.object().keys({ username: Joi.string() .regex(/^[\w.-]+$/, 'username') .required(), - email: Joi.string() - .email() - .empty(''), - virtualCluster: Joi.array() - .items(Joi.string()) - .default([]), - admin: Joi.boolean() - .default(false), - password: Joi.string() - .min(6) - .required(), - extension: Joi.object() - .pattern(/\w+/, Joi.required()) - .default(), + email: Joi.string().email().empty(''), + virtualCluster: Joi.array().items(Joi.string()).default([]), + admin: Joi.boolean().default(false), + password: Joi.string().min(6).required(), + extension: Joi.object().pattern(/\w+/, Joi.required()).default(), }); // define the input schema for the 'update user' api in basic mode for admin user const basicAdminUserUpdateInputSchema = Joi.object().keys({ - patch: Joi.boolean() - .default(false), - data: Joi.object().keys({ - username: Joi.string() - .regex(/^[\w.-]+$/, 'username') - .required(), - email: Joi.string() - .email(), - virtualCluster: Joi.array() - .items(Joi.string()), - admin: Joi.boolean(), - password: Joi.string() - .min(6), - extension: Joi.object() - .pattern(/\w+/, Joi.required()), - }) + patch: Joi.boolean().default(false), + data: Joi.object() + .keys({ + username: Joi.string() + .regex(/^[\w.-]+$/, 'username') + .required(), + email: Joi.string().email(), + virtualCluster: Joi.array().items(Joi.string()), + admin: Joi.boolean(), + password: Joi.string().min(6), + extension: Joi.object().pattern(/\w+/, Joi.required()), + }) .when('patch', { - is: true, - then: Joi.object({ - email: Joi.empty(null), - virtualCluster: Joi.empty(null), - admin: Joi.empty(null), - password: Joi.empty(null), - extension: Joi.empty(null), - }), - otherwise: Joi.object({ - email: Joi.required(), - virtualCluster: Joi.required(), - admin: Joi.required(), - password: Joi.required(), - extension: Joi.required(), - }), - } - ), + is: true, + then: Joi.object({ + email: Joi.empty(null), + virtualCluster: Joi.empty(null), + admin: Joi.empty(null), + password: Joi.empty(null), + extension: Joi.empty(null), + }), + otherwise: Joi.object({ + email: Joi.required(), + virtualCluster: Joi.required(), + admin: Joi.required(), + password: Joi.required(), + extension: Joi.required(), + }), + }), }); // define the input schema for the 'update user' api in basic mode for all user const basicUserUpdateInputSchema = Joi.object().keys({ - patch: Joi.boolean() - .default(false), - data: Joi.object().keys({ - username: Joi.string() - .regex(/^[\w.-]+$/, 'username') - .required(), - email: Joi.string() - .email(), - newPassword: Joi.string() - .min(6), - oldPassword: Joi.string() - .min(6) - .when('newPassword', { + patch: Joi.boolean().default(false), + data: Joi.object() + .keys({ + username: Joi.string() + .regex(/^[\w.-]+$/, 'username') + .required(), + email: Joi.string().email(), + newPassword: Joi.string().min(6), + oldPassword: Joi.string().min(6).when('newPassword', { is: Joi.exist(), then: Joi.required(), }), - }) + }) .when('patch', { - is: true, - then: Joi.object({ - email: Joi.empty(null), - newPassword: Joi.empty(null), - }), - otherwise: Joi.object({ - email: Joi.required(), - newPassword: Joi.required(), - }), - } - ), + is: true, + then: Joi.object({ + email: Joi.empty(null), + newPassword: Joi.empty(null), + }), + otherwise: Joi.object({ + email: Joi.required(), + newPassword: Joi.required(), + }), + }), }); // define the input schema for the 'update user' api in oidc mode @@ -154,12 +137,9 @@ const oidcAdminUserUpdateInputSchema = Joi.object().keys({ username: Joi.string() .regex(/^[\w.-]+$/, 'username') .required(), - extension: Joi.object() - .pattern(/\w+/, Joi.required()) - .default(), + extension: Joi.object().pattern(/\w+/, Joi.required()).default(), }), - patch: Joi.boolean() - .default(false), + patch: Joi.boolean().default(false), }); // module exports diff --git a/src/rest-server/src/config/vc.js b/src/rest-server/src/config/vc.js index c2d925f833..3a9e63991f 100644 --- a/src/rest-server/src/config/vc.js +++ b/src/rest-server/src/config/vc.js @@ -20,43 +20,43 @@ const fs = require('fs'); const Joi = require('joi'); const yaml = require('js-yaml'); const k8s = require('@pai/utils/k8sUtils'); -const {enabledHived, hivedSpecPath} = require('@pai/config/launcher'); +const { enabledHived, hivedSpecPath } = require('@pai/config/launcher'); // define the input schema for the 'create vc' api -const vcCreateInputSchema = Joi.object().keys({ - vcCapacity: Joi.number() - .min(0) - .max(100) - .required(), - vcMaxCapacity: Joi.number() - .min(Joi.ref('vcCapacity')) - .max(100) - .optional(), - description: Joi.string() - .empty(''), - externalName: Joi.string() - .empty(''), -}).required(); +const vcCreateInputSchema = Joi.object() + .keys({ + vcCapacity: Joi.number().min(0).max(100).required(), + vcMaxCapacity: Joi.number().min(Joi.ref('vcCapacity')).max(100).optional(), + description: Joi.string().empty(''), + externalName: Joi.string().empty(''), + }) + .required(); // define the input schema for the 'put vc status' api -const vcStatusPutInputSchema = Joi.object().keys({ - vcStatus: Joi.string() - .valid(['stopped', 'running']) - .required(), -}).required(); +const vcStatusPutInputSchema = Joi.object() + .keys({ + vcStatus: Joi.string().valid(['stopped', 'running']).required(), + }) + .required(); const resourceUnits = {}; if (enabledHived) { const hivedConfig = yaml.safeLoad(fs.readFileSync(hivedSpecPath)); - if (!('physicalCluster' in hivedConfig && + if ( + !( + 'physicalCluster' in hivedConfig && !!hivedConfig.physicalCluster.skuTypes && hivedConfig.physicalCluster.skuTypes.constructor === Object && - Object.keys(hivedConfig.physicalCluster.skuTypes).length > 0)) { + Object.keys(hivedConfig.physicalCluster.skuTypes).length > 0 + ) + ) { throw new Error('Cannot find skuTypes in hivedscheduler config.'); } - for (let [key, val] of Object.entries(hivedConfig.physicalCluster.skuTypes)) { + for (const [key, val] of Object.entries( + hivedConfig.physicalCluster.skuTypes, + )) { resourceUnits[key] = { cpu: k8s.atoi(val.cpu), memory: k8s.convertMemoryMb(val.memory), diff --git a/src/rest-server/src/controllers/index.js b/src/rest-server/src/controllers/index.js index 87f61b053b..c082dae527 100644 --- a/src/rest-server/src/controllers/index.js +++ b/src/rest-server/src/controllers/index.js @@ -29,4 +29,4 @@ const index = (req, res) => { }; // module exports -module.exports = {index}; +module.exports = { index }; diff --git a/src/rest-server/src/controllers/v2/azureAD.js b/src/rest-server/src/controllers/v2/azureAD.js index 17493ba3b4..7207b78bea 100644 --- a/src/rest-server/src/controllers/v2/azureAD.js +++ b/src/rest-server/src/controllers/v2/azureAD.js @@ -32,7 +32,7 @@ const requestAuthCode = async (req, res, next) => { if (authnConfig.groupConfig.groupDataSource === 'ms-graph') { scope = `${scope} https://${authnConfig.OIDCConfig.msgraph_host}/directory.read.all`; } - let state = {}; + const state = {}; state.redirect = process.env.WEBPORTAL_URL + '/index.html'; if (req.query.redirect_uri) { state.redirect = req.query.redirect_uri; @@ -41,14 +41,17 @@ const requestAuthCode = async (req, res, next) => { state.from = req.query.from; } const requestURL = authnConfig.OIDCConfig.authorization_endpoint; - return res.redirect(`${requestURL}?`+ querystring.stringify({ - client_id: clientId, - response_type: responseType, - redirect_uri: redirectUri, - response_mode: responseMode, - scope: scope, - state: JSON.stringify(state), - })); + return res.redirect( + `${requestURL}?` + + querystring.stringify({ + client_id: clientId, + response_type: responseType, + redirect_uri: redirectUri, + response_mode: responseMode, + scope: scope, + state: JSON.stringify(state), + }), + ); }; const requestTokenWithCode = async (req, res, next) => { @@ -89,7 +92,11 @@ const requestTokenWithCode = async (req, res, next) => { const parseTokenData = async (req, res, next) => { try { - const email = req.accessToken.upn ? req.accessToken.upn : (req.accessToken.email ? req.accessToken.email: req.accessToken.unique_name); + const email = req.accessToken.upn + ? req.accessToken.upn + : req.accessToken.email + ? req.accessToken.email + : req.accessToken.unique_name; const userBasicInfo = { email: email, username: email.substring(0, email.lastIndexOf('@')), diff --git a/src/rest-server/src/controllers/v2/cluster.js b/src/rest-server/src/controllers/v2/cluster.js index 20e7a876d2..dd772ba9e7 100644 --- a/src/rest-server/src/controllers/v2/cluster.js +++ b/src/rest-server/src/controllers/v2/cluster.js @@ -15,13 +15,12 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // module dependencies const status = require('statuses'); -const {resourceUnits} = require('@pai/config/vc'); +const { resourceUnits } = require('@pai/config/vc'); const getSkuTypes = (req, res) => { res.status(status('OK')).json(resourceUnits); }; -module.exports = {getSkuTypes}; +module.exports = { getSkuTypes }; diff --git a/src/rest-server/src/controllers/v2/group.js b/src/rest-server/src/controllers/v2/group.js index 866641214f..81acfe7f71 100644 --- a/src/rest-server/src/controllers/v2/group.js +++ b/src/rest-server/src/controllers/v2/group.js @@ -28,7 +28,13 @@ const getGroup = async (req, res, next) => { return res.status(200).json(groupInfo); } catch (error) { if (error.status === 404) { - return next(createError('Bad Request', 'NoGroupError', `Group ${req.params.groupname} is not found.`)); + return next( + createError( + 'Bad Request', + 'NoGroupError', + `Group ${req.params.groupname} is not found.`, + ), + ); } return next(createError.unknown(error)); } @@ -46,11 +52,17 @@ const getAllGroup = async (req, res, next) => { const getGroupUserList = async (req, res, next) => { try { if (!req.user.admin) { - next(createError('Forbidden', 'ForbiddenUserError', `Non-admin is not allow to do this operation.`)); + next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `Non-admin is not allow to do this operation.`, + ), + ); } const groupname = req.params.groupname; const allUserInfoList = await userModel.getAllUser(); - let userlist = []; + const userlist = []; for (const userInfo of allUserInfoList) { if (userInfo.grouplist.includes(groupname)) { userlist.push({ @@ -68,7 +80,13 @@ const getGroupUserList = async (req, res, next) => { const createGroup = async (req, res, next) => { try { if (!req.user.admin) { - next(createError('Forbidden', 'ForbiddenUserError', `Non-admin is not allow to do this operation.`)); + next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `Non-admin is not allow to do this operation.`, + ), + ); } const groupname = req.body.groupname; const groupValue = { @@ -90,36 +108,50 @@ const updateGroup = async (req, res, next) => { const groupname = req.body.data.groupname; try { if (req.user.admin) { - let groupInfo = await groupModel.getGroup(groupname); + const groupInfo = await groupModel.getGroup(groupname); if (req.body.patch) { if ('description' in req.body.data) { - groupInfo['description'] = req.body.data.description; + groupInfo.description = req.body.data.description; } if ('externalName' in req.body.data) { - groupInfo['externalName'] = req.body.data.externalName; + groupInfo.externalName = req.body.data.externalName; } if ('extension' in req.body.data) { if (Object.keys(req.body.data.extension).length > 0) { - for (let [key, value] of Object.entries(req.body.data.extension)) { - groupInfo['extension'][key] = value; + for (const [key, value] of Object.entries( + req.body.data.extension, + )) { + groupInfo.extension[key] = value; } } } } else { - groupInfo['description'] = req.body.data.description; - groupInfo['externalName'] = req.body.data.externalName; - groupInfo['extension'] = req.body.data.extension; + groupInfo.description = req.body.data.description; + groupInfo.externalName = req.body.data.externalName; + groupInfo.extension = req.body.data.extension; } await groupModel.updateGroup(groupname, groupInfo); return res.status(201).json({ message: `update group ${groupname} successfully.`, }); } else { - next(createError('Forbidden', 'ForbiddenUserError', `Non-admin is not allow to do this operation.`)); + next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `Non-admin is not allow to do this operation.`, + ), + ); } } catch (error) { if (error.status === 404) { - return next(createError('Bad Request', 'NoGroupError', `Group ${groupname} is not found.`)); + return next( + createError( + 'Bad Request', + 'NoGroupError', + `Group ${groupname} is not found.`, + ), + ); } return next(createError.unknown(error)); } @@ -134,10 +166,16 @@ const deleteGroup = async (req, res, next) => { message: 'group is removed successfully', }); } else { - next(createError('Forbidden', 'ForbiddenUserError', `Non-admin is not allow to do this operation.`)); + next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `Non-admin is not allow to do this operation.`, + ), + ); } } catch (error) { - return next(createError.unknown((error))); + return next(createError.unknown(error)); } }; @@ -147,19 +185,25 @@ const updateGroupExtension = async (req, res, next) => { const groupname = req.params.groupname; const extensionData = req.body.extension; if (req.user.admin) { - let groupInfo = await groupModel.getGroup(groupname); - for (let [key, value] of Object.entries(extensionData)) { - groupInfo['extension'][key] = value; + const groupInfo = await groupModel.getGroup(groupname); + for (const [key, value] of Object.entries(extensionData)) { + groupInfo.extension[key] = value; } await groupModel.updateGroup(groupname, groupInfo); return res.status(201).json({ message: 'update group extension data successfully.', }); } else { - next(createError('Forbidden', 'ForbiddenUserError', `Non-admin is not allow to do this operation.`)); + next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `Non-admin is not allow to do this operation.`, + ), + ); } } catch (error) { - return next(createError.unknown((error))); + return next(createError.unknown(error)); } }; @@ -171,19 +215,35 @@ const updateGroupExtensionAttr = async (req, res, next) => { const updateData = req.body.data; if (req.user.admin) { const groupInfo = await groupModel.getGroup(groupname); - groupInfo.extension = common.assignValueByKeyarray(groupInfo.extension, attrs, updateData); + groupInfo.extension = common.assignValueByKeyarray( + groupInfo.extension, + attrs, + updateData, + ); await groupModel.updateGroup(groupname, groupInfo); return res.status(201).json({ message: 'Update group extension data successfully.', }); } else { - return next(createError('Forbidden', 'ForbiddenUserError', `Non-admin is not allow to do this operation.`)); + return next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `Non-admin is not allow to do this operation.`, + ), + ); } } catch (error) { if (error.status === 404) { - return next(createError('Bad Request', 'NoGroupError', `Group ${req.params.groupname} is not found.`)); + return next( + createError( + 'Bad Request', + 'NoGroupError', + `Group ${req.params.groupname} is not found.`, + ), + ); } - return next(createError.unknown((error))); + return next(createError.unknown(error)); } }; @@ -193,17 +253,23 @@ const updateGroupDescription = async (req, res, next) => { const groupname = req.params.groupname; const descriptionData = req.body.description; if (req.user.admin) { - let groupInfo = await groupModel.getGroup(groupname); - groupInfo['description'] = descriptionData; + const groupInfo = await groupModel.getGroup(groupname); + groupInfo.description = descriptionData; await groupModel.updateGroup(groupname, groupInfo); return res.status(201).json({ message: 'update group description data successfully.', }); } else { - next(createError('Forbidden', 'ForbiddenUserError', `Non-admin is not allow to do this operation.`)); + next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `Non-admin is not allow to do this operation.`, + ), + ); } } catch (error) { - return next(createError.unknown((error))); + return next(createError.unknown(error)); } }; @@ -213,17 +279,23 @@ const updateGroupExternalName = async (req, res, next) => { const groupname = req.params.groupname; const externalNameData = req.body.externalName; if (req.user.admin) { - let groupInfo = await groupModel.getGroup(groupname); - groupInfo['externalName'] = externalNameData; + const groupInfo = await groupModel.getGroup(groupname); + groupInfo.externalName = externalNameData; await groupModel.updateGroup(groupname, groupInfo); return res.status(201).json({ message: 'update group externalNameData data successfully.', }); } else { - next(createError('Forbidden', 'ForbiddenUserError', `Non-admin is not allow to do this operation.`)); + next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `Non-admin is not allow to do this operation.`, + ), + ); } } catch (error) { - return next(createError.unknown((error))); + return next(createError.unknown(error)); } }; diff --git a/src/rest-server/src/controllers/v2/index.js b/src/rest-server/src/controllers/v2/index.js index cda26cbd55..1a02e588bd 100644 --- a/src/rest-server/src/controllers/v2/index.js +++ b/src/rest-server/src/controllers/v2/index.js @@ -15,14 +15,14 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // module dependencies const status = require('statuses'); const index = (req, res) => { - return res.status(status('OK')).type('text').send( - 'PAI RESTful API v2 (beta version)' - ); + return res + .status(status('OK')) + .type('text') + .send('PAI RESTful API v2 (beta version)'); }; // module exports diff --git a/src/rest-server/src/controllers/v2/info.js b/src/rest-server/src/controllers/v2/info.js index 7b92f4608e..603b2aa58d 100644 --- a/src/rest-server/src/controllers/v2/info.js +++ b/src/rest-server/src/controllers/v2/info.js @@ -16,4 +16,4 @@ const info = (req, res) => { }; // module exports -module.exports = {info}; +module.exports = { info }; diff --git a/src/rest-server/src/controllers/v2/job-attempt.js b/src/rest-server/src/controllers/v2/job-attempt.js index fc20cfc43d..22197c6398 100644 --- a/src/rest-server/src/controllers/v2/job-attempt.js +++ b/src/rest-server/src/controllers/v2/job-attempt.js @@ -34,7 +34,10 @@ const list = asyncHandler(async (req, res) => { }); const get = asyncHandler(async (req, res) => { - const result = await jobAttempt.get(req.params.frameworkName, Number(req.params.jobAttemptIndex)); + const result = await jobAttempt.get( + req.params.frameworkName, + Number(req.params.jobAttemptIndex), + ); res.status(result.status).json(result.data); }); diff --git a/src/rest-server/src/controllers/v2/job.js b/src/rest-server/src/controllers/v2/job.js index 2d930247c0..f3bb7043ca 100644 --- a/src/rest-server/src/controllers/v2/job.js +++ b/src/rest-server/src/controllers/v2/job.js @@ -15,14 +15,13 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // module dependencies const status = require('statuses'); const asyncHandler = require('@pai/middlewares/v2/asyncHandler'); const createError = require('@pai/utils/error'); const job = require('@pai/models/v2/job'); -const {Op} = require('sequelize'); +const { Op } = require('sequelize'); const list = asyncHandler(async (req, res) => { // ?keyword=&username=,&vc=, @@ -32,7 +31,7 @@ const list = asyncHandler(async (req, res) => { let offset = 0; let limit; let withTotalCount = false; - let order = []; + const order = []; // limit has a max number and a default number const maxLimit = 50000; const defaultLimit = 5000; @@ -52,7 +51,11 @@ const list = asyncHandler(async (req, res) => { if ('limit' in req.query) { limit = parseInt(req.query.limit); if (limit > maxLimit) { - throw createError('Bad Request', 'InvalidParametersError', `Limit exceeds max number ${maxLimit}.`); + throw createError( + 'Bad Request', + 'InvalidParametersError', + `Limit exceeds max number ${maxLimit}.`, + ); } } else { limit = defaultLimit; @@ -63,15 +66,25 @@ const list = asyncHandler(async (req, res) => { if ('keyword' in req.query) { // match text in username, jobname, or vc filters[Op.or] = [ - {'userName': {[Op.substring]: req.query.keyword}}, - {'jobName': {[Op.substring]: req.query.keyword}}, - {'virtualCluster': {[Op.substring]: req.query.keyword}}, + { userName: { [Op.substring]: req.query.keyword } }, + { jobName: { [Op.substring]: req.query.keyword } }, + { virtualCluster: { [Op.substring]: req.query.keyword } }, ]; } if ('order' in req.query) { - const {field, ordering} = req.query.order.split(','); - if (['jobName', 'submissionTime', 'username', 'vc', 'retries', 'totalTaskNumber', - 'totalGpuNumber', 'state'].includes(field)) { + const { field, ordering } = req.query.order.split(','); + if ( + [ + 'jobName', + 'submissionTime', + 'username', + 'vc', + 'retries', + 'totalTaskNumber', + 'totalGpuNumber', + 'state', + ].includes(field) + ) { if (ordering === 'ASC' || ordering === 'DESC') { // different cases for username if (field !== 'username') { @@ -87,10 +100,35 @@ const list = asyncHandler(async (req, res) => { order.push(['submissionTime', 'DESC']); } } - const attributes = ['name', 'jobName', 'userName', 'executionType', 'submissionTime', 'creationTime', 'virtualCluster', - 'totalGpuNumber', 'totalTaskNumber', 'totalTaskRoleNumber', 'retries', 'retryDelayTime', 'platformRetries', - 'resourceRetries', 'userRetries', 'completionTime', 'appExitCode', 'subState', 'state']; - const data = await job.list(attributes, filters, order, offset, limit, withTotalCount); + const attributes = [ + 'name', + 'jobName', + 'userName', + 'executionType', + 'submissionTime', + 'creationTime', + 'virtualCluster', + 'totalGpuNumber', + 'totalTaskNumber', + 'totalTaskRoleNumber', + 'retries', + 'retryDelayTime', + 'platformRetries', + 'resourceRetries', + 'userRetries', + 'completionTime', + 'appExitCode', + 'subState', + 'state', + ]; + const data = await job.list( + attributes, + filters, + order, + offset, + limit, + withTotalCount, + ); res.json(data); }); @@ -108,7 +146,11 @@ const update = asyncHandler(async (req, res) => { try { const data = await job.get(frameworkName); if (data != null) { - throw createError('Conflict', 'ConflictJobError', `Job ${frameworkName} already exists.`); + throw createError( + 'Conflict', + 'ConflictJobError', + `Job ${frameworkName} already exists.`, + ); } } catch (error) { if (error.code !== 'NoJobError') { @@ -126,7 +168,7 @@ const execute = asyncHandler(async (req, res) => { const userName = req.user.username; const admin = req.user.admin; const data = await job.get(req.params.frameworkName); - if ((data.jobStatus.username === userName) || admin) { + if (data.jobStatus.username === userName || admin) { await job.execute(req.params.frameworkName, req.body.value); res.status(status('Accepted')).json({ status: status('Accepted'), @@ -136,7 +178,7 @@ const execute = asyncHandler(async (req, res) => { throw createError( 'Forbidden', 'ForbiddenUserError', - `User ${userName} is not allowed to execute job ${req.params.frameworkName}.` + `User ${userName} is not allowed to execute job ${req.params.frameworkName}.`, ); } }); @@ -147,7 +189,11 @@ const getConfig = asyncHandler(async (req, res) => { return res.status(200).type('text/yaml').send(data); } catch (error) { if (error.message.startsWith('[WebHDFS] 404')) { - throw createError('Not Found', 'NoJobConfigError', `Config of job ${req.params.frameworkName} is not found.`); + throw createError( + 'Not Found', + 'NoJobConfigError', + `Config of job ${req.params.frameworkName} is not found.`, + ); } else { throw createError.unknown(error); } diff --git a/src/rest-server/src/controllers/v2/storage-deprecated.js b/src/rest-server/src/controllers/v2/storage-deprecated.js index 84feef70ed..dd9c395c54 100644 --- a/src/rest-server/src/controllers/v2/storage-deprecated.js +++ b/src/rest-server/src/controllers/v2/storage-deprecated.js @@ -18,21 +18,22 @@ // module dependencies const asyncHandler = require('@pai/middlewares/v2/asyncHandler'); const createError = require('@pai/utils/error'); -const {get, list} = require('@pai/models/v2/storage'); -const {getUserStorages} = require('@pai/models/v2/user'); - +const { get, list } = require('@pai/models/v2/storage'); +const { getUserStorages } = require('@pai/models/v2/user'); const convertConfig = (storage, userDefaultStorages) => { const config = { name: storage.name, - default: (userDefaultStorages.includes(storage.name)), + default: userDefaultStorages.includes(storage.name), servers: [storage.volumeName], - mountInfos: [{ - mountPoint: `/mnt/${storage.name}`, - path: storage.share === false ? '${PAI_USER_NAME}' : '', - server: storage.volumeName, - permission: 'rw', - }], + mountInfos: [ + { + mountPoint: `/mnt/${storage.name}`, + path: storage.share === false ? '${PAI_USER_NAME}' : '', // eslint-disable-line no-template-curly-in-string + server: storage.volumeName, + permission: 'rw', + }, + ], }; return config; }; @@ -91,7 +92,7 @@ const getConfig = asyncHandler(async (req, res) => { const userName = req.user.username; const userDefaultStorages = await getUserStorages(userName, true); const storages = (await list(userName)).storages - .filter((item) => names ? names.includes(item.name) : true) + .filter((item) => (names ? names.includes(item.name) : true)) .map((item) => convertConfig(item, userDefaultStorages)); if (req.params.name) { @@ -119,8 +120,9 @@ const getServer = asyncHandler(async (req, res) => { const userName = req.user.username; const storages = await Promise.all( (await list(userName)).storages - .filter((item) => names ? names.includes(item.volumeName) : true) - .map(convertServer)); + .filter((item) => (names ? names.includes(item.volumeName) : true)) + .map(convertServer), + ); if (req.params.name) { if (storages.length === 1) { diff --git a/src/rest-server/src/controllers/v2/storage.js b/src/rest-server/src/controllers/v2/storage.js index 39af8f7915..bc4b002015 100644 --- a/src/rest-server/src/controllers/v2/storage.js +++ b/src/rest-server/src/controllers/v2/storage.js @@ -5,10 +5,9 @@ const asyncHandler = require('@pai/middlewares/v2/asyncHandler'); const storage = require('@pai/models/v2/storage'); - const list = asyncHandler(async (req, res) => { const userName = req.user.username; - const filterDefault = (req.query.default === 'true'); + const filterDefault = req.query.default === 'true'; const data = await storage.list(userName, filterDefault); res.json(data); }); diff --git a/src/rest-server/src/controllers/v2/token.js b/src/rest-server/src/controllers/v2/token.js index 12a78f36e3..9ad1fb5de5 100644 --- a/src/rest-server/src/controllers/v2/token.js +++ b/src/rest-server/src/controllers/v2/token.js @@ -21,7 +21,7 @@ const userModel = require('@pai/models/v2/user'); const groupModel = require('@pai/models/v2/group'); const tokenModel = require('@pai/models/token'); const createError = require('@pai/utils/error'); -const {encrypt} = require('@pai/utils/manager/user/user'); +const { encrypt } = require('@pai/utils/manager/user/user'); /** * Get the token. @@ -35,11 +35,23 @@ const get = async (req, res, next) => { userItem = await userModel.getUser(username); } catch (error) { if (error.status && error.status === 404) { - return next(createError('Bad Request', 'NoUserError', `User ${req.body.username} is not found.`)); + return next( + createError( + 'Bad Request', + 'NoUserError', + `User ${req.body.username} is not found.`, + ), + ); } } - if (hash !== userItem['password']) { - return next(createError('Bad Request', 'IncorrectPasswordError', 'Password is incorrect.')); + if (hash !== userItem.password) { + return next( + createError( + 'Bad Request', + 'IncorrectPasswordError', + 'Password is incorrect.', + ), + ); } try { const admin = await groupModel.getGroupsAdmin(userItem.grouplist); @@ -65,13 +77,17 @@ const getAAD = async (req, res, next) => { const admin = await userModel.checkAdmin(username); const token = await tokenModel.create(username); const fromURI = req.fromURI; - return res.redirect(req.returnBackURI + '?'+ querystring.stringify({ - user: userInfo.username, - token: token, - admin: admin, - hasGitHubPAT: userInfo.extension.githubPAT, - from: fromURI, - })); + return res.redirect( + req.returnBackURI + + '?' + + querystring.stringify({ + user: userInfo.username, + token: token, + admin: admin, + hasGitHubPAT: userInfo.extension.githubPAT, + from: fromURI, + }), + ); } catch (error) { return next(createError.unknown(error)); } diff --git a/src/rest-server/src/controllers/v2/user.js b/src/rest-server/src/controllers/v2/user.js index 481b39532b..621785425d 100644 --- a/src/rest-server/src/controllers/v2/user.js +++ b/src/rest-server/src/controllers/v2/user.js @@ -40,14 +40,22 @@ const getUser = async (req, res, next) => { const username = req.params.username; const userInfo = await userModel.getUser(username); const groupItems = await groupModel.getListGroup(userInfo.grouplist); - userInfo['admin'] = groupModel.getAdminWithGroupInfo(groupItems); - userInfo['virtualCluster'] = await groupModel.getVCsWithGroupInfo(groupItems); - userInfo['storageConfig'] = await groupModel.getStorageConfigsWithGroupInfo(groupItems); - delete userInfo['password']; + userInfo.admin = groupModel.getAdminWithGroupInfo(groupItems); + userInfo.virtualCluster = await groupModel.getVCsWithGroupInfo(groupItems); + userInfo.storageConfig = await groupModel.getStorageConfigsWithGroupInfo( + groupItems, + ); + delete userInfo.password; return res.status(200).json(userInfo); } catch (error) { if (error.status === 404) { - return next(createError('Bad Request', 'NoUserError', `User ${req.params.username} is not found.`)); + return next( + createError( + 'Bad Request', + 'NoUserError', + `User ${req.params.username} is not found.`, + ), + ); } return next(createError.unknown(error)); } @@ -63,15 +71,24 @@ const getAllUser = async (req, res, next) => { groupMap[groupItem.groupname] = groupItem; } - const retUserList = await Promise.all(userList.map(async (userItem) => { - const groupItems = Array.from(userItem.grouplist, (groupname) => groupMap[groupname]); - const admin = groupModel.getAdminWithGroupInfo(groupItems); - userItem.admin = admin; - userItem.virtualCluster = admin ? allVClist : await groupModel.getVCsWithGroupInfo(groupItems); - userItem.storageConfig = await groupModel.getStorageConfigsWithGroupInfo(groupItems); - delete userItem.password; - return userItem; - })); + const retUserList = await Promise.all( + userList.map(async (userItem) => { + const groupItems = Array.from( + userItem.grouplist, + (groupname) => groupMap[groupname], + ); + const admin = groupModel.getAdminWithGroupInfo(groupItems); + userItem.admin = admin; + userItem.virtualCluster = admin + ? allVClist + : await groupModel.getVCsWithGroupInfo(groupItems); + userItem.storageConfig = await groupModel.getStorageConfigsWithGroupInfo( + groupItems, + ); + delete userItem.password; + return userItem; + }), + ); return res.status(200).json(retUserList); } catch (error) { return next(createError.unknown(error)); @@ -85,19 +102,25 @@ const createUserIfUserNotExist = async (req, res, next) => { const username = userData.username; let grouplist = []; if (authConfig.groupConfig.groupDataSource !== 'basic') { - let data = {}; + const data = {}; if (authConfig.groupConfig.groupDataSource === 'ms-graph') { - data['accessToken'] = req.undecodedAccessToken; - data['graphUrl'] = `https://${authConfig.OIDCConfig.msgraph_host}/`; + data.accessToken = req.undecodedAccessToken; + data.graphUrl = `https://${authConfig.OIDCConfig.msgraph_host}/`; } grouplist = await groupModel.getUserGrouplistFromExternal(username, data); req.grouplist = grouplist; if (grouplist && grouplist.length === 0) { let forbiddenMessage = `User ${userData.username} is not in configured groups.`; if (authConfig.groupConfig.groupDataSource === 'ms-graph') { - forbiddenMessage = forbiddenMessage + `Please contact your admin, and join the AAD group named [ ${authConfig.groupConfig.defaultGroup.externalName} ].`; + forbiddenMessage = + forbiddenMessage + + `Please contact your admin, and join the AAD group named [ ${authConfig.groupConfig.defaultGroup.externalName} ].`; } - let forbiddenError = createError('Forbidden', 'ForbiddenUserError', forbiddenMessage); + const forbiddenError = createError( + 'Forbidden', + 'ForbiddenUserError', + forbiddenMessage, + ); forbiddenError.targetURI = req.returnBackURI; return next(forbiddenError); } @@ -126,31 +149,51 @@ const updateUserGroupListFromExternal = async (req, res, next) => { try { if (!req.updateResult) { const username = req.userData.username; - let userInfo = await userModel.getUser(username); - userInfo['grouplist'] = req.grouplist; + const userInfo = await userModel.getUser(username); + userInfo.grouplist = req.grouplist; await userModel.updateUser(username, userInfo); } next(); } catch (error) { - return next(createError.unknown((error))); + return next(createError.unknown(error)); } }; const createUser = async (req, res, next) => { if (!req.user.admin) { - next(createError('Forbidden', 'ForbiddenUserError', `Non-admin is not allow to do this operation.`)); + next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `Non-admin is not allow to do this operation.`, + ), + ); } let grouplist; try { - grouplist = await groupModel.virtualCluster2GroupList(req.body.virtualCluster); + grouplist = await groupModel.virtualCluster2GroupList( + req.body.virtualCluster, + ); } catch (error) { if (error.status === 404) { - return next(createError('Not Found', 'NoGroupError', `No groups for vc: ${req.body.virtualCluster}`)); + return next( + createError( + 'Not Found', + 'NoGroupError', + `No groups for vc: ${req.body.virtualCluster}`, + ), + ); } return next(createError.unknown(error)); } if (grouplist.length !== req.body.virtualCluster.length) { - next(createError('Bad Request', 'NoVirtualClusterError', `Try to update: ${req.body.virtualCluster}, but found ${grouplist}`)); + next( + createError( + 'Bad Request', + 'NoVirtualClusterError', + `Try to update: ${req.body.virtualCluster}, but found ${grouplist}`, + ), + ); } if (!grouplist.includes(authConfig.groupConfig.defaultGroup.groupname)) { grouplist.push(authConfig.groupConfig.defaultGroup.groupname); @@ -172,7 +215,13 @@ const createUser = async (req, res, next) => { await userModel.createUser(username, userValue); } catch (error) { if (error.status === 409) { - return next(createError('Conflict', 'ConflictUserError', `User name ${req.body.username} already exists.`)); + return next( + createError( + 'Conflict', + 'ConflictUserError', + `User name ${req.body.username} already exists.`, + ), + ); } return next(createError.unknown(error)); } @@ -182,9 +231,9 @@ const createUser = async (req, res, next) => { }; const updateExtensionInternal = async (oldExtension, newExtension) => { - let retExtension = JSON.parse(JSON.stringify(oldExtension)); - for (let [key, value] of Object.entries(newExtension)) { - retExtension[key] = value; + const retExtension = JSON.parse(JSON.stringify(oldExtension)); + for (const [key, value] of Object.entries(newExtension)) { + retExtension[key] = value; } return retExtension; }; @@ -194,20 +243,35 @@ const updateUserExtension = async (req, res, next) => { const username = req.params.username; const extensionData = req.body.extension; if (req.user.admin || req.user.username === username) { - let userInfo = await userModel.getUser(username); - userInfo['extension'] = await updateExtensionInternal(userInfo['extension'], extensionData); + const userInfo = await userModel.getUser(username); + userInfo.extension = await updateExtensionInternal( + userInfo.extension, + extensionData, + ); await userModel.updateUser(username, userInfo); return res.status(201).json({ message: 'Update user extension data successfully.', }); } else { - next(createError('Forbidden', 'ForbiddenUserError', `Non-admin is not allow to do this operation.`)); + next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `Non-admin is not allow to do this operation.`, + ), + ); } } catch (error) { if (error.status === 404) { - return next(createError('Not Found', 'NoUserError', `User ${req.params.username} not found.`)); + return next( + createError( + 'Not Found', + 'NoUserError', + `User ${req.params.username} not found.`, + ), + ); } - return next(createError.unknown((error))); + return next(createError.unknown(error)); } }; @@ -216,10 +280,18 @@ const updateVirtualClusterInternal = async (newVc) => { try { groupList = await groupModel.virtualCluster2GroupList(newVc); } catch (error) { - throw createError('Bad Request', 'NoVirtualClusterError', `Try to update nonexist: ${newVc}`); + throw createError( + 'Bad Request', + 'NoVirtualClusterError', + `Try to update nonexist: ${newVc}`, + ); } if (groupList.length !== newVc.length) { - throw createError('Bad Request', 'NoVirtualClusterError', `Try to update: ${newVc}, but found: ${groupList}`); + throw createError( + 'Bad Request', + 'NoVirtualClusterError', + `Try to update: ${newVc}, but found: ${groupList}`, + ); } if (!groupList.includes(authConfig.groupConfig.defaultGroup.groupname)) { groupList.push(authConfig.groupConfig.defaultGroup.groupname); @@ -231,47 +303,79 @@ const updateUserVirtualCluster = async (req, res, next) => { try { const username = req.params.username; if (req.user.admin) { - let newGroupList = await updateVirtualClusterInternal(req.body.virtualCluster); + const newGroupList = await updateVirtualClusterInternal( + req.body.virtualCluster, + ); let userInfo; try { - userInfo = await userModel.getUser(username); + userInfo = await userModel.getUser(username); } catch (error) { if (error.status === 404) { - return next(createError('Not Found', 'NoUserError', `User ${req.params.username} not found.`)); + return next( + createError( + 'Not Found', + 'NoUserError', + `User ${req.params.username} not found.`, + ), + ); } - return next(createError.unknown((error))); + return next(createError.unknown(error)); } if (await userModel.checkAdmin(username)) { - return next(createError('Forbidden', 'ForbiddenUserError', 'Admin\'s virtual clusters cannot be updated.')); + return next( + createError( + 'Forbidden', + 'ForbiddenUserError', + "Admin's virtual clusters cannot be updated.", + ), + ); } - userInfo['grouplist'] = newGroupList; + userInfo.grouplist = newGroupList; await userModel.updateUser(username, userInfo); return res.status(201).json({ message: 'Update user virtualCluster data successfully.', }); } else { - return next(createError('Forbidden', 'ForbiddenUserError', `Non-admin is not allow to do this operation.`)); + return next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `Non-admin is not allow to do this operation.`, + ), + ); } } catch (error) { if (error.code === 'NoVirtualClusterError') { return next(error); } - return next(createError.unknown((error))); + return next(createError.unknown(error)); } }; const updateGroupListInternal = async (groupList) => { - let retGroupList = await groupModel.filterExistGroups(groupList); - if (retGroupList.length !== groupList.length) { - const nonExistGrouplist = groupList.filter((groupname) => !retGroupList.includes(groupname)); - throw createError('Not Found', 'NoGroupError', `Updated nonexistent grouplist: ${nonExistGrouplist}`); - } - return retGroupList; + const retGroupList = await groupModel.filterExistGroups(groupList); + if (retGroupList.length !== groupList.length) { + const nonExistGrouplist = groupList.filter( + (groupname) => !retGroupList.includes(groupname), + ); + throw createError( + 'Not Found', + 'NoGroupError', + `Updated nonexistent grouplist: ${nonExistGrouplist}`, + ); + } + return retGroupList; }; const updateUserGroupList = async (req, res, next) => { if (!req.user.admin) { - next(createError('Forbidden', 'ForbiddenUserError', `Non-admin is not allow to do this operation.`)); + next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `Non-admin is not allow to do this operation.`, + ), + ); } const username = req.params.username; let userValue; @@ -283,7 +387,13 @@ const updateUserGroupList = async (req, res, next) => { return next(error); } if (error.status === 404) { - return next(createError('Not Found', 'NoUserError', `User ${req.params.username} not found.`)); + return next( + createError( + 'Not Found', + 'NoUserError', + `User ${req.params.username} not found.`, + ), + ); } return next(createError.unknown(error)); } @@ -295,11 +405,25 @@ const updateUserGroupList = async (req, res, next) => { const addGroupIntoUserGrouplist = async (req, res, next) => { if (!req.user.admin) { - next(createError('Forbidden', 'ForbiddenUserError', `Non-admin is not allow to do this operation.`)); - } - const existGrouplist = await groupModel.filterExistGroups([req.body.groupname]); + next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `Non-admin is not allow to do this operation.`, + ), + ); + } + const existGrouplist = await groupModel.filterExistGroups([ + req.body.groupname, + ]); if (existGrouplist.length === 0) { - return next(createError('Not Found', 'NoGroupError', `Updated nonexistent group: ${req.body.groupname}`)); + return next( + createError( + 'Not Found', + 'NoGroupError', + `Updated nonexistent group: ${req.body.groupname}`, + ), + ); } const username = req.params.username; const groupname = req.body.groupname; @@ -308,7 +432,13 @@ const addGroupIntoUserGrouplist = async (req, res, next) => { userInfo = await userModel.getUser(username); } catch (error) { if (error.status === 404) { - return next(createError('Not Found', 'NoUserError', `User ${req.params.username} not found.`)); + return next( + createError( + 'Not Found', + 'NoUserError', + `User ${req.params.username} not found.`, + ), + ); } return next(createError.unknown(error)); } @@ -324,11 +454,17 @@ const addGroupIntoUserGrouplist = async (req, res, next) => { const removeGroupFromUserGrouplist = async (req, res, next) => { try { if (!req.user.admin) { - next(createError('Forbidden', 'ForbiddenUserError', `Non-admin is not allow to do this operation.`)); + next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `Non-admin is not allow to do this operation.`, + ), + ); } const username = req.params.username; const groupname = req.body.groupname; - let userInfo = await userModel.getUser(username); + const userInfo = await userModel.getUser(username); if (userInfo.grouplist.includes(groupname)) { userInfo.grouplist.splice(userInfo.grouplist.indexOf(groupname), 1); } @@ -338,7 +474,13 @@ const removeGroupFromUserGrouplist = async (req, res, next) => { }); } catch (error) { if (error.status === 404) { - return next(createError('Not Found', 'NoUserError', `User ${req.params.username} not found.`)); + return next( + createError( + 'Not Found', + 'NoUserError', + `User ${req.params.username} not found.`, + ), + ); } return next(createError.unknown(error)); } @@ -354,15 +496,21 @@ const updateUserPassword = async (req, res, next) => { userValue = await userModel.getUser(username); } catch (error) { if (error.status === 404) { - return next(createError('Not Found', 'NoUserError', `User ${req.params.username} not found.`)); + return next( + createError( + 'Not Found', + 'NoUserError', + `User ${req.params.username} not found.`, + ), + ); } - return next(createError.unknown((error))); + return next(createError.unknown(error)); } let newUserValue = JSON.parse(JSON.stringify(userValue)); - newUserValue['password'] = oldPassword; + newUserValue.password = oldPassword; newUserValue = await userModel.getEncryptPassword(newUserValue); - if (req.user.admin || newUserValue['password'] === userValue['password']) { - newUserValue['password'] = newPassword; + if (req.user.admin || newUserValue.password === userValue.password) { + newUserValue.password = newPassword; await userModel.updateUser(username, newUserValue, true); // try to revoke browser tokens try { @@ -378,10 +526,16 @@ const updateUserPassword = async (req, res, next) => { message: 'update user password successfully.', }); } else { - next(createError('Forbidden', 'ForbiddenUserError', `Pls input the correct password.`)); + next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `Pls input the correct password.`, + ), + ); } } catch (error) { - return next(createError.unknown((error))); + return next(createError.unknown(error)); } }; @@ -390,20 +544,32 @@ const updateUserEmail = async (req, res, next) => { const username = req.params.username; const email = req.body.email; if (req.user.admin || req.user.username === username) { - let userInfo = await userModel.getUser(username); - userInfo['email'] = email; + const userInfo = await userModel.getUser(username); + userInfo.email = email; await userModel.updateUser(username, userInfo); return res.status(201).json({ message: 'Update user email data successfully.', }); } else { - next(createError('Forbidden', 'ForbiddenUserError', `Pls input the correct password.`)); + next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `Pls input the correct password.`, + ), + ); } } catch (error) { if (error.status === 404) { - return next(createError('Not Found', 'NoUserError', `User ${req.params.username} not found.`)); + return next( + createError( + 'Not Found', + 'NoUserError', + `User ${req.params.username} not found.`, + ), + ); } - return next(createError.unknown((error))); + return next(createError.unknown(error)); } }; @@ -413,7 +579,10 @@ const updateAdminPermissionInternal = async (user, admin) => { let newGroupList = []; if (!existed && admin) { // non-admin -> admin, add into adminGroup - newGroupList = [...user.grouplist, authConfig.groupConfig.adminGroup.groupname]; + newGroupList = [ + ...user.grouplist, + authConfig.groupConfig.adminGroup.groupname, + ]; } else if (existed && !admin) { // admin -> non-admin, remove from all adminGroup for (const groupItem of groupInfo) { @@ -432,16 +601,28 @@ const updateUserAdminPermission = async (req, res, next) => { const username = req.params.username; const admin = req.body.admin; if (!req.user.admin) { - next(createError('Forbidden', 'ForbiddenUserError', `Non-admin is not allow to do this operation.`)); + next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `Non-admin is not allow to do this operation.`, + ), + ); } else { let userInfo; try { userInfo = await userModel.getUser(username); } catch (error) { if (error.status === 404) { - return next(createError('Not Found', 'NoUserError', `User ${req.params.username} not found.`)); + return next( + createError( + 'Not Found', + 'NoUserError', + `User ${req.params.username} not found.`, + ), + ); } - return next(createError.unknown((error))); + return next(createError.unknown(error)); } userInfo.grouplist = await updateAdminPermissionInternal(userInfo, admin); await userModel.updateUser(username, userInfo); @@ -450,48 +631,64 @@ const updateUserAdminPermission = async (req, res, next) => { }); } } catch (error) { - return next(createError.unknown((error))); + return next(createError.unknown(error)); } }; const basicAdminUserUpdate = async (req, res, next) => { const username = req.body.data.username; if (!req.user.admin) { - next(createError('Forbidden', 'ForbiddenUserError', `Non-admin is not allow to do this operation.`)); + next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `Non-admin is not allow to do this operation.`, + ), + ); } let userInfo; try { userInfo = await userModel.getUser(username); } catch (error) { if (error.status === 404) { - return next(createError('Not Found', 'NoUserError', `User ${username} not found.`)); + return next( + createError('Not Found', 'NoUserError', `User ${username} not found.`), + ); } - return next(createError.unknown((error))); + return next(createError.unknown(error)); } try { let updatePassword = false; if ('email' in req.body.data) { - userInfo['email'] = req.body.data.email; + userInfo.email = req.body.data.email; } if ('virtualCluster' in req.body.data) { try { - userInfo['grouplist'] = await updateVirtualClusterInternal(req.body.data.virtualCluster); + userInfo.grouplist = await updateVirtualClusterInternal( + req.body.data.virtualCluster, + ); } catch (error) { if (error.code === 'NoVirtualClusterError') { return next(error); } - return next(createError.unknown((error))); + return next(createError.unknown(error)); } } if ('admin' in req.body.data) { - userInfo['grouplist'] = await updateAdminPermissionInternal(userInfo, req.body.data.admin); + userInfo.grouplist = await updateAdminPermissionInternal( + userInfo, + req.body.data.admin, + ); } if ('password' in req.body.data) { updatePassword = true; - userInfo['password'] = req.body.data.password; + userInfo.password = req.body.data.password; } if ('extension' in req.body.data) { - userInfo['extension'] = await updateExtensionInternal(userInfo['extension'], req.body.data.extension); + userInfo.extension = await updateExtensionInternal( + userInfo.extension, + req.body.data.extension, + ); } await userModel.updateUser(username, userInfo, updatePassword); if (updatePassword) { @@ -507,39 +704,53 @@ const basicAdminUserUpdate = async (req, res, next) => { } } return res.status(201).json({ - message: 'Update user ${username} successfully', + message: `Update user ${username} successfully`, }); } catch (error) { - return next(createError.unknown((error))); + return next(createError.unknown(error)); } }; const basicUserUpdate = async (req, res, next) => { const username = req.user.username; if (username !== req.body.data.username) { - return next(createError('Forbidden', 'ForbiddenUserError', `Can't update other user's data`)); + return next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `Can't update other user's data`, + ), + ); } let userInfo; try { userInfo = await userModel.getUser(username); } catch (error) { if (error.status === 404) { - return next(createError('Not Found', 'NoUserError', `User ${username} not found.`)); + return next( + createError('Not Found', 'NoUserError', `User ${username} not found.`), + ); } - return next(createError.unknown((error))); + return next(createError.unknown(error)); } try { let updatePassword = false; if ('email' in req.body.data) { - userInfo['email'] = req.body.data.email; + userInfo.email = req.body.data.email; } if ('password' in req.body.data) { let newUserValue = JSON.parse(JSON.stringify(userInfo)); newUserValue = await userModel.getEncryptPassword(newUserValue); - if (newUserValue['password'] !== userInfo['password']) { - return next(createError('Forbidden', 'ForbiddenUserError', `Pls input the correct password.`)); + if (newUserValue.password !== userInfo.password) { + return next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `Pls input the correct password.`, + ), + ); } - userInfo['password'] = req.body.data.password; + userInfo.password = req.body.data.password; updatePassword = true; } await userModel.updateUser(username, userInfo, updatePassword); @@ -559,34 +770,45 @@ const basicUserUpdate = async (req, res, next) => { message: `Update user ${username} successfully`, }); } catch (error) { - return next(createError.unknown((error))); + return next(createError.unknown(error)); } }; const oidcUserUpdate = async (req, res, next) => { const username = req.body.data.username; if (!req.user.admin) { - next(createError('Forbidden', 'ForbiddenUserError', `Non-admin is not allow to do this operation.`)); + next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `Non-admin is not allow to do this operation.`, + ), + ); } let userInfo; try { userInfo = await userModel.getUser(username); } catch (error) { if (error.status === 404) { - return next(createError('Not Found', 'NoUserError', `User ${username} not found.`)); + return next( + createError('Not Found', 'NoUserError', `User ${username} not found.`), + ); } - return next(createError.unknown((error))); + return next(createError.unknown(error)); } try { if ('extension' in req.body.data) { - userInfo['extension'] = await updateExtensionInternal(userInfo['extension'], req.body.data.extension); + userInfo.extension = await updateExtensionInternal( + userInfo.extension, + req.body.data.extension, + ); } await userModel.updateUser(username, userInfo); return res.status(201).json({ - message: 'Update user ${username} successfully', + message: `Update user ${username} successfully`, }); } catch (error) { - return next(createError.unknown((error))); + return next(createError.unknown(error)); } }; @@ -595,20 +817,38 @@ const deleteUser = async (req, res, next) => { const username = req.params.username; if (req.user.admin) { if (await userModel.checkAdmin(username)) { - return next(createError('Forbidden', 'RemoveAdminError', `Admin ${username} is not allowed to remove.`)); + return next( + createError( + 'Forbidden', + 'RemoveAdminError', + `Admin ${username} is not allowed to remove.`, + ), + ); } await userModel.deleteUser(username); return res.status(200).json({ message: 'user is removed successfully', }); } else { - next(createError('Forbidden', 'ForbiddenUserError', `Non-admin is not allow to do this operation.`)); + next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `Non-admin is not allow to do this operation.`, + ), + ); } } catch (error) { if (error.status === 404) { - return next(createError('Not Found', 'NoUserError', `User ${req.params.username} not found.`)); + return next( + createError( + 'Not Found', + 'NoUserError', + `User ${req.params.username} not found.`, + ), + ); } - return next(createError.unknown((error))); + return next(createError.unknown(error)); } }; diff --git a/src/rest-server/src/controllers/v2/virtual-cluster.js b/src/rest-server/src/controllers/v2/virtual-cluster.js index 11000ba176..216c57264b 100644 --- a/src/rest-server/src/controllers/v2/virtual-cluster.js +++ b/src/rest-server/src/controllers/v2/virtual-cluster.js @@ -23,12 +23,19 @@ const createError = require('@pai/utils/error'); const groupModel = require('@pai/models/v2/group'); const authConfig = require('@pai/config/authn'); - const validate = (req, res, next, virtualClusterName) => { - if (! /^[A-Za-z0-9_]+$/.test(virtualClusterName)) { - throw createError('Bad Request', 'InvalidParametersError', 'VC name should only contain alpha-numeric and underscore characters'); + if (!/^[A-Za-z0-9_]+$/.test(virtualClusterName)) { + throw createError( + 'Bad Request', + 'InvalidParametersError', + 'VC name should only contain alpha-numeric and underscore characters', + ); } else if (virtualClusterName === 'default' && req.method !== 'GET') { - throw createError('Forbidden', 'ForbiddenUserError', 'Update operation to default vc isn\'t allowed'); + throw createError( + 'Forbidden', + 'ForbiddenUserError', + "Update operation to default vc isn't allowed", + ); } else { return next(); } @@ -52,27 +59,45 @@ const get = asyncHandler(async (req, res) => { const update = asyncHandler(async (req, res) => { const virtualClusterName = req.params.virtualClusterName; if (!req.user.admin) { - throw createError('Forbidden', 'ForbiddenUserError', `Non-admin is not allowed to do this operation.`); + throw createError( + 'Forbidden', + 'ForbiddenUserError', + `Non-admin is not allowed to do this operation.`, + ); } if (virtualClusterName === authConfig.groupConfig.defaultGroup.groupname) { - throw createError('Forbidden', 'ForbiddenUserError', `Update operation to default vc isn't allowed`); + throw createError( + 'Forbidden', + 'ForbiddenUserError', + `Update operation to default vc isn't allowed`, + ); } let operationType = 'update'; try { const groupInfo = await groupModel.getGroup(virtualClusterName); - if (!groupInfo.extension.acls || groupInfo.extension.acls.virtualClusters.length !== 1 || !groupInfo.extension.acls.virtualClusters.includes(virtualClusterName)) { - throw createError('Conflict', 'ConflictVcError', `Group name ${virtualClusterName} already exists.`); + if ( + !groupInfo.extension.acls || + groupInfo.extension.acls.virtualClusters.length !== 1 || + !groupInfo.extension.acls.virtualClusters.includes(virtualClusterName) + ) { + throw createError( + 'Conflict', + 'ConflictVcError', + `Group name ${virtualClusterName} already exists.`, + ); } } catch (error) { if (error.status !== 404) { - throw createError.unknown((error)); + throw createError.unknown(error); } else { operationType = 'create'; } } const capacity = parseInt(req.body.vcCapacity); - const maxCapacity = req.body.vcMaxCapacity ? parseInt(req.body.vcMaxCapacity) : capacity; + const maxCapacity = req.body.vcMaxCapacity + ? parseInt(req.body.vcMaxCapacity) + : capacity; await virtualCluster.update(virtualClusterName, capacity, maxCapacity); if (operationType === 'update') { return res.status(status('Created')).json({ @@ -119,23 +144,43 @@ const updateStatus = asyncHandler(async (req, res) => { message: `Activate virtual cluster ${virtualClusterName} successfully.`, }); } else { - throw createError('Bad Request', 'BadConfigurationError', `Unknown vc status: ${vcStatus}`); + throw createError( + 'Bad Request', + 'BadConfigurationError', + `Unknown vc status: ${vcStatus}`, + ); } } else { - throw createError('Forbidden', 'ForbiddenUserError', 'Non-admin is not allowed to do this operation.'); + throw createError( + 'Forbidden', + 'ForbiddenUserError', + 'Non-admin is not allowed to do this operation.', + ); } }); const remove = asyncHandler(async (req, res) => { const virtualClusterName = req.params.virtualClusterName; if (!req.user.admin) { - throw createError('Forbidden', 'ForbiddenUserError', `Non-admin is not allowed to do this operation.`); + throw createError( + 'Forbidden', + 'ForbiddenUserError', + `Non-admin is not allowed to do this operation.`, + ); } if (virtualClusterName === authConfig.groupConfig.adminGroup.groupname) { - throw createError('Forbidden', 'ForbiddenUserError', `The name '${virtualClusterName}' is occupied by admin group.`); + throw createError( + 'Forbidden', + 'ForbiddenUserError', + `The name '${virtualClusterName}' is occupied by admin group.`, + ); } if (virtualClusterName === authConfig.groupConfig.defaultGroup.groupname) { - throw createError('Forbidden', 'ForbiddenUserError', `Update operation to default vc isn't allowed`); + throw createError( + 'Forbidden', + 'ForbiddenUserError', + `Update operation to default vc isn't allowed`, + ); } await virtualCluster.remove(virtualClusterName); await groupModel.deleteGroup(virtualClusterName); diff --git a/src/rest-server/src/index.js b/src/rest-server/src/index.js index 294b6907f1..6b17b37296 100644 --- a/src/rest-server/src/index.js +++ b/src/rest-server/src/index.js @@ -15,5 +15,4 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - module.exports = require('./server'); diff --git a/src/rest-server/src/middlewares/parameter.js b/src/rest-server/src/middlewares/parameter.js index 6e38266ecb..821c9b2eee 100644 --- a/src/rest-server/src/middlewares/parameter.js +++ b/src/rest-server/src/middlewares/parameter.js @@ -15,7 +15,6 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // module dependencies const Joi = require('joi'); const createError = require('@pai/utils/error'); @@ -38,4 +37,4 @@ const validate = (schema) => { }; // module exports -module.exports = {validate}; +module.exports = { validate }; diff --git a/src/rest-server/src/middlewares/token.js b/src/rest-server/src/middlewares/token.js index e6cf74e05f..53b0971a8a 100644 --- a/src/rest-server/src/middlewares/token.js +++ b/src/rest-server/src/middlewares/token.js @@ -15,7 +15,7 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -const {userProperty} = require('@pai/config/token'); +const { userProperty } = require('@pai/config/token'); const userModel = require('@pai/models/v2/user'); const tokenModel = require('@pai/models/token'); const createError = require('@pai/utils/error'); @@ -42,15 +42,29 @@ const getToken = async (req) => { const check = async (req, _, next) => { if (!req.headers.authorization) { - return next(createError('Unauthorized', 'UnauthorizedUserError', 'Guest is not allowed to do this operation.')); + return next( + createError( + 'Unauthorized', + 'UnauthorizedUserError', + 'Guest is not allowed to do this operation.', + ), + ); } try { req[userProperty] = await getToken(req); - req[userProperty].admin = await userModel.checkAdmin(req[userProperty].username); + req[userProperty].admin = await userModel.checkAdmin( + req[userProperty].username, + ); next(); } catch (error) { logger.debug(error); - return next(createError('Unauthorized', 'UnauthorizedUserError', 'Your token is invalid.')); + return next( + createError( + 'Unauthorized', + 'UnauthorizedUserError', + 'Your token is invalid.', + ), + ); } }; @@ -59,7 +73,13 @@ const notApplication = async (req, _, next) => { if (!token.application) { next(); } else { - return next(createError('Forbidden', 'ForbiddenUserError', 'Applications are not allowed to do this operation.')); + return next( + createError( + 'Forbidden', + 'ForbiddenUserError', + 'Applications are not allowed to do this operation.', + ), + ); } }; diff --git a/src/rest-server/src/middlewares/v2/asyncHandler.js b/src/rest-server/src/middlewares/v2/asyncHandler.js index 9acd0cf04a..50dd6ca824 100644 --- a/src/rest-server/src/middlewares/v2/asyncHandler.js +++ b/src/rest-server/src/middlewares/v2/asyncHandler.js @@ -15,12 +15,9 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - const asyncHandler = (middleware) => { return (req, res, next) => { - Promise - .resolve(middleware(req, res, next)) - .catch(next); + Promise.resolve(middleware(req, res, next)).catch(next); }; }; diff --git a/src/rest-server/src/middlewares/v2/hived.js b/src/rest-server/src/middlewares/v2/hived.js index c11136ebc6..93433a5db6 100644 --- a/src/rest-server/src/middlewares/v2/hived.js +++ b/src/rest-server/src/middlewares/v2/hived.js @@ -17,15 +17,14 @@ // module dependencies const axios = require('axios'); -const {get, pickBy} = require('lodash'); +const { get, pickBy } = require('lodash'); const createError = require('@pai/utils/error'); const logger = require('@pai/config/logger'); const hivedSchema = require('@pai/config/v2/hived'); -const {resourceUnits} = require('@pai/config/vc'); -const {hivedWebserviceUri} = require('@pai/config/launcher'); +const { resourceUnits } = require('@pai/config/vc'); +const { hivedWebserviceUri } = require('@pai/config/launcher'); - -const convertPriority = (priorityClass='test') => { +const convertPriority = (priorityClass = 'test') => { // TODO: make it a cluster-wise config // allowed range: [-1, 126], default priority 0 const priorityMap = { @@ -40,19 +39,26 @@ const convertPriority = (priorityClass='test') => { const getCellStatus = async (virtualCluster) => { let vcStatus; try { - vcStatus = (await axios.get(`${hivedWebserviceUri}/v1/inspect/clusterstatus/virtualclusters/${virtualCluster}`)).data; + vcStatus = ( + await axios.get( + `${hivedWebserviceUri}/v1/inspect/clusterstatus/virtualclusters/${virtualCluster}`, + ) + ).data; } catch (error) { - logger.warn('Failed to inspect vc from hived scheduler: ', error.response ? error.response.data : error); + logger.warn( + 'Failed to inspect vc from hived scheduler: ', + error.response ? error.response.data : error, + ); return { cellQuota: Number.MAX_SAFE_INTEGER, - cellUnits: {...resourceUnits}, + cellUnits: { ...resourceUnits }, }; } let cellQuota = 0; const cellUnits = [...new Set(vcStatus.map((cell) => cell.leafCellType))] .filter((key) => key in resourceUnits) - .reduce((dict, key) => ({...dict, [key]: resourceUnits[key]}), {}); + .reduce((dict, key) => ({ ...dict, [key]: resourceUnits[key] }), {}); const cellQueue = [...vcStatus]; while (cellQueue.length > 0) { const curr = cellQueue.shift(); @@ -65,35 +71,48 @@ const getCellStatus = async (virtualCluster) => { cellQuota += 1; } } - return {cellQuota, cellUnits}; + return { cellQuota, cellUnits }; }; const hivedValidate = async (protocolObj, username) => { if (!hivedSchema.validate(protocolObj)) { - throw createError('Bad Request', 'InvalidProtocolError', hivedSchema.validate.errors); + throw createError( + 'Bad Request', + 'InvalidProtocolError', + hivedSchema.validate.errors, + ); } const hivedConfig = get(protocolObj, 'extras.hivedScheduler', null); const opportunistic = !!(get(hivedConfig, 'jobPriorityClass') === 'oppo'); - const gangAllocation = !!(get(protocolObj, 'extras.gangAllocation', true) === true); + const gangAllocation = !!( + get(protocolObj, 'extras.gangAllocation', true) === true + ); const virtualCluster = get(protocolObj, 'defaults.virtualCluster', 'default'); const affinityGroups = {}; - const {cellQuota, cellUnits} = await getCellStatus(virtualCluster); + const { cellQuota, cellUnits } = await getCellStatus(virtualCluster); // generate podSpec for every taskRole - for (let taskRole of Object.keys(protocolObj.taskRoles)) { - const podSpec = pickBy({ - virtualCluster, - priority: convertPriority(get(hivedConfig, 'jobPriorityClass')), - pinnedCellId: get(hivedConfig, `taskRoles.${taskRole}.pinnedCellId`, null), - leafCellType: get(hivedConfig, `taskRoles.${taskRole}.skuType`, null), - leafCellNumber: get(hivedConfig, `taskRoles.${taskRole}.skuNumber`, 0), - gangReleaseEnable: get(hivedConfig, 'gangReleaseEnable'), - lazyPreemptionEnable: get(hivedConfig, 'lazyPreemptionEnable'), - ignoreK8sSuggestedNodes: get(hivedConfig, 'ignoreK8sSuggestedNodes'), - affinityGroup: null, - }, (v) => v !== undefined); + for (const taskRole of Object.keys(protocolObj.taskRoles)) { + const podSpec = pickBy( + { + virtualCluster, + priority: convertPriority(get(hivedConfig, 'jobPriorityClass')), + pinnedCellId: get( + hivedConfig, + `taskRoles.${taskRole}.pinnedCellId`, + null, + ), + leafCellType: get(hivedConfig, `taskRoles.${taskRole}.skuType`, null), + leafCellNumber: get(hivedConfig, `taskRoles.${taskRole}.skuNumber`, 0), + gangReleaseEnable: get(hivedConfig, 'gangReleaseEnable'), + lazyPreemptionEnable: get(hivedConfig, 'lazyPreemptionEnable'), + ignoreK8sSuggestedNodes: get(hivedConfig, 'ignoreK8sSuggestedNodes'), + affinityGroup: null, + }, + (v) => v !== undefined, + ); // calculate sku number const resourcePerCell = {}; @@ -102,10 +121,16 @@ const hivedValidate = async (protocolObj, username) => { resourcePerCell[t] = resourceUnits[podSpec.leafCellType][t]; } else { resourcePerCell[t] = Math.min( - ...Array.from(Object.values(opportunistic ? resourceUnits : cellUnits), (v) => v[t])); + ...Array.from( + Object.values(opportunistic ? resourceUnits : cellUnits), + (v) => v[t], + ), + ); } } - const {gpu = 0, cpu, memoryMB} = protocolObj.taskRoles[taskRole].resourcePerInstance; + const { gpu = 0, cpu, memoryMB } = protocolObj.taskRoles[ + taskRole + ].resourcePerInstance; let requestedResource = ''; let emptyResource = ''; if (resourcePerCell.gpu === 0 && gpu > 0) { @@ -123,7 +148,7 @@ const hivedValidate = async (protocolObj, username) => { 'Bad Request', 'InvalidProtocolError', `Taskrole ${taskRole} requests ${requestedResource} ${emptyResource}, but SKU does not ` + - `configure ${emptyResource}. Please contact admin if the taskrole needs ${emptyResource} resources.` + `configure ${emptyResource}. Please contact admin if the taskrole needs ${emptyResource} resources.`, ); } podSpec.leafCellNumber = Math.max( @@ -136,24 +161,25 @@ const hivedValidate = async (protocolObj, username) => { } if (hivedConfig != null) { - for (let taskRole of Object.keys(hivedConfig.taskRoles || {})) { + for (const taskRole of Object.keys(hivedConfig.taskRoles || {})) { // must be a valid taskRole if (!(taskRole in protocolObj.taskRoles)) { throw createError( 'Bad Request', 'InvalidProtocolError', - `Taskrole ${taskRole} does not exist.` + `Taskrole ${taskRole} does not exist.`, ); } const skuType = protocolObj.taskRoles[taskRole].hivedPodSpec.leafCellType; - const pinnedCellId = protocolObj.taskRoles[taskRole].hivedPodSpec.pinnedCellId; + const pinnedCellId = + protocolObj.taskRoles[taskRole].hivedPodSpec.pinnedCellId; // only allow one of {skuType, pinnedCellId} if (skuType != null && pinnedCellId != null) { throw createError( 'Bad Request', 'InvalidProtocolError', - `Taskrole ${taskRole} has both skuType and pinnedCellId, only one is allowed.` + `Taskrole ${taskRole} has both skuType and pinnedCellId, only one is allowed.`, ); } // check whether skuType is valid @@ -162,28 +188,35 @@ const hivedValidate = async (protocolObj, username) => { throw createError( 'Bad Request', 'InvalidProtocolError', - `Taskrole ${taskRole} has unknown skuType ${skuType}, allow ${Object.keys(resourceUnits)}.` + `Taskrole ${taskRole} has unknown skuType ${skuType}, allow ${Object.keys( + resourceUnits, + )}.`, ); } if (!opportunistic && !(skuType in cellUnits)) { throw createError( 'Bad Request', 'InvalidProtocolError', - `Taskrole ${taskRole} has skuType ${skuType}, VC ${virtualCluster} only allows ${Object.keys(cellUnits)}.` + `Taskrole ${taskRole} has skuType ${skuType}, VC ${virtualCluster} only allows ${Object.keys( + cellUnits, + )}.`, ); } } - const affinityGroupName = hivedConfig.taskRoles[taskRole].affinityGroupName; + const affinityGroupName = + hivedConfig.taskRoles[taskRole].affinityGroupName; // affinityGroup should have united skuType or pinnedCellId if (affinityGroupName != null) { if (affinityGroupName in affinityGroups) { - if (skuType !== affinityGroups[affinityGroupName].skuType || - pinnedCellId !== affinityGroups[affinityGroupName].pinnedCellId) { + if ( + skuType !== affinityGroups[affinityGroupName].skuType || + pinnedCellId !== affinityGroups[affinityGroupName].pinnedCellId + ) { throw createError( 'Bad Request', 'InvalidProtocolError', - `AffinityGroup ${affinityGroupName} has inconsistent skuType or pinnedCellId.` + `AffinityGroup ${affinityGroupName} has inconsistent skuType or pinnedCellId.`, ); } } else { @@ -195,7 +228,8 @@ const hivedValidate = async (protocolObj, username) => { } affinityGroups[affinityGroupName].affinityTaskList.push({ podNumber: protocolObj.taskRoles[taskRole].instances, - leafCellNumber: protocolObj.taskRoles[taskRole].hivedPodSpec.leafCellNumber, + leafCellNumber: + protocolObj.taskRoles[taskRole].hivedPodSpec.leafCellNumber, }); } } @@ -208,15 +242,19 @@ const hivedValidate = async (protocolObj, username) => { affinityTaskList: Object.keys(protocolObj.taskRoles).map((taskRole) => { return { podNumber: protocolObj.taskRoles[taskRole].instances, - leafCellNumber: protocolObj.taskRoles[taskRole].hivedPodSpec.leafCellNumber, + leafCellNumber: + protocolObj.taskRoles[taskRole].hivedPodSpec.leafCellNumber, }; }), }; } let requestCellNumber = 0; - for (let taskRole of Object.keys(protocolObj.taskRoles)) { - const affinityGroupName = get(hivedConfig, `taskRoles.${taskRole}.affinityGroupName`); + for (const taskRole of Object.keys(protocolObj.taskRoles)) { + const affinityGroupName = get( + hivedConfig, + `taskRoles.${taskRole}.affinityGroupName`, + ); if (affinityGroupName != null) { protocolObj.taskRoles[taskRole].hivedPodSpec.affinityGroup = { name: `${username}~${protocolObj.name}/${affinityGroupName}`, @@ -229,7 +267,8 @@ const hivedValidate = async (protocolObj, username) => { members: defaultAffinityGroup.affinityTaskList, }; } - requestCellNumber += protocolObj.taskRoles[taskRole].instances * + requestCellNumber += + protocolObj.taskRoles[taskRole].instances * protocolObj.taskRoles[taskRole].hivedPodSpec.leafCellNumber; } // best effort check cell quota @@ -237,7 +276,7 @@ const hivedValidate = async (protocolObj, username) => { throw createError( 'Bad Request', 'InvalidProtocolError', - `Job requests ${requestCellNumber} SKUs, exceeds maximum ${cellQuota} SKUs in VC ${virtualCluster}.` + `Job requests ${requestCellNumber} SKUs, exceeds maximum ${cellQuota} SKUs in VC ${virtualCluster}.`, ); } diff --git a/src/rest-server/src/middlewares/v2/protocol.js b/src/rest-server/src/middlewares/v2/protocol.js index 87c9e72f52..6f5dad3d78 100644 --- a/src/rest-server/src/middlewares/v2/protocol.js +++ b/src/rest-server/src/middlewares/v2/protocol.js @@ -15,39 +15,27 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // module dependencies const yaml = require('js-yaml'); const mustache = require('mustache'); const createError = require('@pai/utils/error'); const hived = require('@pai/middlewares/v2/hived'); -const {enabledHived} = require('@pai/config/launcher'); +const { enabledHived } = require('@pai/config/launcher'); const protocolSchema = require('@pai/config/v2/protocol'); const asyncHandler = require('@pai/middlewares/v2/asyncHandler'); const mustacheWriter = new mustache.Writer(); -const prerequisiteTypes = [ - 'script', - 'output', - 'data', - 'dockerimage', -]; +const prerequisiteTypes = ['script', 'output', 'data', 'dockerimage']; -const prerequisiteFields = [ - 'script', - 'output', - 'data', - 'dockerImage', -]; +const prerequisiteFields = ['script', 'output', 'data', 'dockerImage']; - -const render = (template, dict, tags=['<%', '%>']) => { +const render = (template, dict, tags = ['<%', '%>']) => { const tokens = mustacheWriter.parse(template, tags); const context = new mustache.Context(dict); let result = ''; - for (let token of tokens) { + for (const token of tokens) { const symbol = token[0]; let tokenStr = token[1]; if (symbol === 'text') { @@ -68,20 +56,29 @@ const render = (template, dict, tags=['<%', '%>']) => { const protocolValidate = (protocolYAML) => { const protocolObj = yaml.safeLoad(protocolYAML); if (!protocolSchema.validate(protocolObj)) { - throw createError('Bad Request', 'InvalidProtocolError', protocolSchema.validate.errors); + throw createError( + 'Bad Request', + 'InvalidProtocolError', + protocolSchema.validate.errors, + ); } // convert prerequisites list to dict const prerequisites = {}; - for (let type of prerequisiteTypes) { + for (const type of prerequisiteTypes) { prerequisites[type] = {}; } if ('prerequisites' in protocolObj) { - for (let item of protocolObj.prerequisites) { - if (prerequisites[item.type].hasOwnProperty(item.name)) { + for (const item of protocolObj.prerequisites) { + if ( + Object.prototype.hasOwnProperty.call( + prerequisites[item.type], + item.name, + ) + ) { throw createError( 'Bad Request', 'InvalidProtocolError', - `Duplicate ${item.type} prerequisites ${item.name}.` + `Duplicate ${item.type} prerequisites ${item.name}.`, ); } else { prerequisites[item.type][item.name] = item; @@ -92,12 +89,12 @@ const protocolValidate = (protocolYAML) => { // convert deployments list to dict const deployments = {}; if ('deployments' in protocolObj) { - for (let item of protocolObj.deployments) { - if (deployments.hasOwnProperty(item.name)) { + for (const item of protocolObj.deployments) { + if (Object.prototype.hasOwnProperty.call(deployments, item.name)) { throw createError( 'Bad Request', 'InvalidProtocolError', - `Duplicate deployments ${item.name}.` + `Duplicate deployments ${item.name}.`, ); } else { deployments[item.name] = item; @@ -106,27 +103,34 @@ const protocolValidate = (protocolYAML) => { } protocolObj.deployments = deployments; // check prerequisites in taskRoles - for (let taskRole of Object.keys(protocolObj.taskRoles)) { - for (let field of prerequisiteFields) { - if (field in protocolObj.taskRoles[taskRole] && - !(protocolObj.taskRoles[taskRole][field] in prerequisites[field.toLowerCase()])) { + for (const taskRole of Object.keys(protocolObj.taskRoles)) { + for (const field of prerequisiteFields) { + if ( + field in protocolObj.taskRoles[taskRole] && + !( + protocolObj.taskRoles[taskRole][field] in + prerequisites[field.toLowerCase()] + ) + ) { throw createError( 'Bad Request', 'InvalidProtocolError', - `Prerequisite ${protocolObj.taskRoles[taskRole][field]} does not exist.` + `Prerequisite ${protocolObj.taskRoles[taskRole][field]} does not exist.`, ); } } } // check deployment in defaults if ('defaults' in protocolObj) { - if ('deployment' in protocolObj.defaults && - !(protocolObj.defaults.deployment in deployments)) { - throw createError( - 'Bad Request', - 'InvalidProtocolError', - `Default deployment ${protocolObj.defaults.deployment} does not exist.` - ); + if ( + 'deployment' in protocolObj.defaults && + !(protocolObj.defaults.deployment in deployments) + ) { + throw createError( + 'Bad Request', + 'InvalidProtocolError', + `Default deployment ${protocolObj.defaults.deployment} does not exist.`, + ); } } return protocolObj; @@ -134,12 +138,16 @@ const protocolValidate = (protocolYAML) => { const protocolRender = (protocolObj) => { // render auth for Docker image - for (let name of Object.keys(protocolObj.prerequisites.dockerimage)) { + for (const name of Object.keys(protocolObj.prerequisites.dockerimage)) { if ('auth' in protocolObj.prerequisites.dockerimage[name]) { - for (let prop of Object.keys(protocolObj.prerequisites.dockerimage[name].auth)) { + for (const prop of Object.keys( + protocolObj.prerequisites.dockerimage[name].auth, + )) { protocolObj.prerequisites.dockerimage[name].auth[prop] = render( protocolObj.prerequisites.dockerimage[name].auth[prop], - {'$secrets': protocolObj.secrets}, + { + $secrets: protocolObj.secrets, + }, ); } } @@ -149,7 +157,7 @@ const protocolRender = (protocolObj) => { if ('defaults' in protocolObj && 'deployment' in protocolObj.defaults) { deployment = protocolObj.deployments[protocolObj.defaults.deployment]; } - for (let taskRole of Object.keys(protocolObj.taskRoles)) { + for (const taskRole of Object.keys(protocolObj.taskRoles)) { let commands = protocolObj.taskRoles[taskRole].commands; if (deployment != null && taskRole in deployment.taskRoles) { if ('preCommands' in deployment.taskRoles[taskRole]) { @@ -162,17 +170,23 @@ const protocolRender = (protocolObj) => { commands = commands.map((command) => command.trim()).join('\n'); // Will not render secret here for security issue const entrypoint = render(commands, { - '$parameters': protocolObj.parameters, - '$script': protocolObj.prerequisites['script'][protocolObj.taskRoles[taskRole].script], - '$output': protocolObj.prerequisites['output'][protocolObj.taskRoles[taskRole].output], - '$data': protocolObj.prerequisites['data'][protocolObj.taskRoles[taskRole].data], + $parameters: protocolObj.parameters, + $script: + protocolObj.prerequisites.script[ + protocolObj.taskRoles[taskRole].script + ], + $output: + protocolObj.prerequisites.output[ + protocolObj.taskRoles[taskRole].output + ], + $data: + protocolObj.prerequisites.data[protocolObj.taskRoles[taskRole].data], }); protocolObj.taskRoles[taskRole].entrypoint = entrypoint; } return protocolObj; }; - const protocolSubmitMiddleware = [ (req, res, next) => { res.locals.protocol = req.body; @@ -188,7 +202,10 @@ const protocolSubmitMiddleware = [ }, asyncHandler(async (req, res, next) => { if (enabledHived) { - res.locals.protocol = await hived.validate(res.locals.protocol, req.user.username); + res.locals.protocol = await hived.validate( + res.locals.protocol, + req.user.username, + ); } next(); }), diff --git a/src/rest-server/src/models/kubernetes/k8s-secret.js b/src/rest-server/src/models/kubernetes/k8s-secret.js index 54a2ac926a..cdc4f43187 100644 --- a/src/rest-server/src/models/kubernetes/k8s-secret.js +++ b/src/rest-server/src/models/kubernetes/k8s-secret.js @@ -16,7 +16,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. const _ = require('lodash'); -const {encodeSelector, getClient, patchOption} = require('@pai/models/kubernetes/kubernetes'); +const { + encodeSelector, + getClient, + patchOption, +} = require('@pai/models/kubernetes/kubernetes'); const initClient = (namespace) => { if (!namespace) { @@ -25,7 +29,6 @@ const initClient = (namespace) => { return getClient(`/api/v1/namespaces/${namespace}/secrets`); }; - /** * Serialize key-value object to k8s secret's base64 encoded data structure * @param {Object} object - key-value dictionary, value should be string @@ -65,7 +68,7 @@ const get = async (namespace, name, options = {}) => { return list(namespace); } const client = initClient(namespace); - const {encode} = options; + const { encode } = options; let secretName = name; if (encode != null) { // k8s resource's name must consisst of only digits (0-9), lower case letters (a-z), -, and .. @@ -101,7 +104,7 @@ const list = async (namespace, labelSelector) => { params.continue = continueValue; } // request - response = await client.get('/', {params}); + response = await client.get('/', { params }); } catch (err) { if (err.response && err.response.status === 410) { // restart @@ -126,7 +129,7 @@ const list = async (namespace, labelSelector) => { }; const create = async (namespace, name, data, options = {}) => { - const {labels, encode, type} = options; + const { labels, encode, type } = options; const client = initClient(namespace); let secretName = name; @@ -147,7 +150,7 @@ const create = async (namespace, name, data, options = {}) => { }; const replace = async (namespace, name, data, options = {}) => { - const {encode, labels} = options; + const { encode, labels } = options; let secretName = name; if (encode != null) { secretName = Buffer.from(secretName).toString(encode); @@ -165,7 +168,7 @@ const replace = async (namespace, name, data, options = {}) => { }; const remove = async (namespace, name, options = {}) => { - const {encode} = options; + const { encode } = options; let secretName = name; if (encode != null) { secretName = Buffer.from(name).toString(encode); @@ -175,13 +178,13 @@ const remove = async (namespace, name, options = {}) => { }; const patchMetadata = async (namespace, name, metadata, options = {}) => { - const {encode} = options; + const { encode } = options; let secretName = name; if (encode != null) { secretName = Buffer.from(name).toString(encode); } const client = initClient(namespace); - await client.patch(`/${secretName}`, {metadata: metadata}, patchOption); + await client.patch(`/${secretName}`, { metadata: metadata }, patchOption); }; module.exports = { diff --git a/src/rest-server/src/models/kubernetes/kubernetes.js b/src/rest-server/src/models/kubernetes/kubernetes.js index 49678995cb..0bfcfa21ee 100644 --- a/src/rest-server/src/models/kubernetes/kubernetes.js +++ b/src/rest-server/src/models/kubernetes/kubernetes.js @@ -2,9 +2,9 @@ // Licensed under the MIT license. const axios = require('axios'); -const {Agent} = require('https'); -const {URL} = require('url'); -const {apiserver} = require('@pai/config/kubernetes'); +const { Agent } = require('https'); +const { URL } = require('url'); +const { apiserver } = require('@pai/config/kubernetes'); const status = require('statuses'); const logger = require('@pai/config/logger'); @@ -33,7 +33,7 @@ const getClient = (baseURL = '') => { baseURL: new URL(baseURL, apiserver.uri).toString(), maxRedirects: 0, headers: { - 'Accept': 'application/json', + Accept: 'application/json', }, }; if (apiserver.ca || apiserver.cert || apiserver.key) { @@ -94,15 +94,15 @@ const getNodes = async () => { return res.data; }; -const getPods = async (options = {}, headers={}) => { - const {namespace, ...params} = options; +const getPods = async (options = {}, headers = {}) => { + const { namespace, ...params } = options; const client = getClient(); let url = '/api/v1/pods'; if (namespace) { url = `/api/v1/namespaces/${namespace}/pods`; } - const res = await client.get(url, {params, headers}); + const res = await client.get(url, { params, headers }); return res.data; }; diff --git a/src/rest-server/src/models/token.js b/src/rest-server/src/models/token.js index 5352012fd6..6a57306e1d 100644 --- a/src/rest-server/src/models/token.js +++ b/src/rest-server/src/models/token.js @@ -17,7 +17,7 @@ const jwt = require('jsonwebtoken'); const uuid = require('uuid'); -const {secret, tokenExpireTime} = require('@pai/config/token'); +const { secret, tokenExpireTime } = require('@pai/config/token'); const k8sSecret = require('@pai/models/kubernetes/k8s-secret'); const k8sModel = require('@pai/models/kubernetes/kubernetes'); @@ -29,22 +29,21 @@ if (process.env.NODE_ENV !== 'test') { } const sign = async (username, application, expiration) => { - return new Promise((res, rej) => { + return new Promise((resolve, reject) => { jwt.sign( { username, application, }, secret, - expiration != null ? {expiresIn: expiration} : {}, + expiration != null ? { expiresIn: expiration } : {}, (signError, token) => { - signError ? rej(signError) : res(token); - } + signError ? reject(signError) : resolve(token); + }, ); }); }; - /** * Remove invalid (expired/malformed) tokens from given token list object. * @param {Object} data - id -> token format data object (data stored in k8s secret) @@ -64,14 +63,14 @@ const purge = (data) => { }; const list = async (username) => { - const item = await k8sSecret.get(namespace, username, {encode: 'hex'}); + const item = await k8sSecret.get(namespace, username, { encode: 'hex' }); if (item === null) { return {}; } const purged = purge(item); if (Object.keys(item).length !== Object.keys(purged).length) { - await k8sSecret.replace(namespace, username, purged, {encode: 'hex'}); + await k8sSecret.replace(namespace, username, purged, { encode: 'hex' }); } return Object.values(purged); }; @@ -83,15 +82,20 @@ const create = async (username, application = false, expiration) => { expiration = expiration || tokenExpireTime; } const token = await sign(username, application, expiration); - const item = await k8sSecret.get(namespace, username, {encode: 'hex'}); + const item = await k8sSecret.get(namespace, username, { encode: 'hex' }); if (item === null) { - await k8sSecret.create(namespace, username, { - [uuid()]: token, - }, {encode: 'hex'}); + await k8sSecret.create( + namespace, + username, + { + [uuid()]: token, + }, + { encode: 'hex' }, + ); } else { const result = purge(item); result[uuid()] = token; - await k8sSecret.replace(namespace, username, result, {encode: 'hex'}); + await k8sSecret.replace(namespace, username, result, { encode: 'hex' }); } return token; }; @@ -102,7 +106,7 @@ const revoke = async (token) => { if (!username) { throw new Error('Token is invalid'); } - const item = await k8sSecret.get(namespace, username, {encode: 'hex'}); + const item = await k8sSecret.get(namespace, username, { encode: 'hex' }); if (item === null) { throw new Error('Token is invalid'); } @@ -112,18 +116,18 @@ const revoke = async (token) => { delete result[key]; } } - await k8sSecret.replace(namespace, username, result, {encode: 'hex'}); + await k8sSecret.replace(namespace, username, result, { encode: 'hex' }); }; const batchRevoke = async (username, filter) => { - const item = await k8sSecret.get(namespace, username, {encode: 'hex'}); + const item = await k8sSecret.get(namespace, username, { encode: 'hex' }); const result = purge(item || {}); for (const [key, val] of Object.entries(result)) { if (filter(val)) { delete result[key]; } } - await k8sSecret.replace(namespace, username, result, {encode: 'hex'}); + await k8sSecret.replace(namespace, username, result, { encode: 'hex' }); }; const verify = async (token) => { @@ -132,7 +136,7 @@ const verify = async (token) => { if (!username) { throw new Error('Token is invalid'); } - const item = await k8sSecret.get(namespace, username, {encode: 'hex'}); + const item = await k8sSecret.get(namespace, username, { encode: 'hex' }); if (item === null) { throw new Error('Token is invalid'); } @@ -143,7 +147,7 @@ const verify = async (token) => { } } if (Object.keys(item).length !== Object.keys(purged).length) { - await k8sSecret.replace(namespace, username, purged, {encode: 'hex'}); + await k8sSecret.replace(namespace, username, purged, { encode: 'hex' }); } throw new Error('Token has been revoked'); }; diff --git a/src/rest-server/src/models/v2/group.js b/src/rest-server/src/models/v2/group.js index 6cece3ffe6..2cab070846 100644 --- a/src/rest-server/src/models/v2/group.js +++ b/src/rest-server/src/models/v2/group.js @@ -51,11 +51,11 @@ const deleteGroup = async (groupname) => { const userModel = require('@pai/models/v2/user'); const ret = await crudGroup.remove(groupname); // delete group from all user info - let userList = await userModel.getAllUser(); - let updateUserList = []; + const userList = await userModel.getAllUser(); + const updateUserList = []; for (const userItem of userList) { - if (userItem['grouplist'].includes(groupname)) { - userItem['grouplist'].splice(userItem['grouplist'].indexOf(groupname), 1); + if (userItem.grouplist.includes(groupname)) { + userItem.grouplist.splice(userItem.grouplist.indexOf(groupname), 1); updateUserList.push(userItem); } } @@ -71,9 +71,11 @@ const getListGroup = async (grouplist) => { }; const batchUpdateGroups = async (groupItems) => { - return await Promise.all(groupItems.map(async (groupItem) => { - await updateGroup(groupItem.groupname, groupItem); - })); + return await Promise.all( + groupItems.map(async (groupItem) => { + await updateGroup(groupItem.groupname, groupItem); + }), + ); }; const getVCsWithGroupInfo = async (groupItems) => { @@ -83,7 +85,10 @@ const getVCsWithGroupInfo = async (groupItems) => { if (groupItem.extension.acls.admin) { return Object.keys(await vcModel.list()); } else if (groupItem.extension.acls.virtualClusters) { - virtualClusters = new Set([...virtualClusters, ...groupItem.extension.acls.virtualClusters]); + virtualClusters = new Set([ + ...virtualClusters, + ...groupItem.extension.acls.virtualClusters, + ]); } } } @@ -100,7 +105,10 @@ const getGroupsVCs = async (grouplist) => { return getVCsWithGroupInfo(groupItems); }; -const getStorageConfigsWithGroupInfo = async (groupItems, filterDefault=false) => { +const getStorageConfigsWithGroupInfo = async ( + groupItems, + filterDefault = false, +) => { const storageConfigs = new Set(); for (const groupItem of groupItems) { if (groupItem.extension && groupItem.extension.acls) { @@ -108,7 +116,10 @@ const getStorageConfigsWithGroupInfo = async (groupItems, filterDefault=false) = if (filterDefault) { storageConfigs.add(groupItem.extension.acls.storageConfigs[0]); } else { - groupItem.extension.acls.storageConfigs.forEach(storageConfigs.add, storageConfigs); + groupItem.extension.acls.storageConfigs.forEach( + storageConfigs.add, + storageConfigs, + ); } } } @@ -116,14 +127,18 @@ const getStorageConfigsWithGroupInfo = async (groupItems, filterDefault=false) = return [...storageConfigs]; }; -const getGroupsStorages = async (grouplist, filterDefault=false) => { +const getGroupsStorages = async (grouplist, filterDefault = false) => { const groupItems = await getListGroup(grouplist); return getStorageConfigsWithGroupInfo(groupItems, filterDefault); }; const getAdminWithGroupInfo = (groupItems) => { for (const groupItem of groupItems) { - if (groupItem.extension && groupItem.extension.acls && groupItem.extension.acls.admin) { + if ( + groupItem.extension && + groupItem.extension.acls && + groupItem.extension.acls.admin + ) { return true; } } @@ -144,8 +159,12 @@ const addVCintoAdminGroup = async (vcname) => { const allGroups = await getAllGroup(); const updateGroups = []; for (const groupItem of allGroups) { - if (groupItem.extension && groupItem.extension.acls - && groupItem.extension.acls.admin && !groupItem.extension.acls.virtualClusters.includes(vcname)) { + if ( + groupItem.extension && + groupItem.extension.acls && + groupItem.extension.acls.admin && + !groupItem.extension.acls.virtualClusters.includes(vcname) + ) { groupItem.extension.acls.virtualClusters.push(vcname); updateGroups.push(groupItem); } @@ -157,8 +176,15 @@ const deleteVCfromAllGroup = async (vcname) => { const allGroups = await getAllGroup(); const updateGroups = []; for (const groupItem of allGroups) { - if (groupItem.extension && groupItem.extension.acls && groupItem.extension.acls.virtualClusters.includes(vcname)) { - groupItem.extension.acls.virtualClusters.splice(groupItem.extension.acls.virtualClusters.indexOf(vcname), 1); + if ( + groupItem.extension && + groupItem.extension.acls && + groupItem.extension.acls.virtualClusters.includes(vcname) + ) { + groupItem.extension.acls.virtualClusters.splice( + groupItem.extension.acls.virtualClusters.indexOf(vcname), + 1, + ); updateGroups.push(groupItem); } } @@ -175,7 +201,10 @@ const getUserGrouplistFromExternal = async (username, data = {}) => { } else if (adapterType === 'ms-graph') { config = groupAdapter.initConfig(data.graphUrl, data.accessToken); } - const externalGrouplist = await groupAdapter.getUserGroupList(username, config); + const externalGrouplist = await groupAdapter.getUserGroupList( + username, + config, + ); for (const externalGroupname of externalGrouplist) { if (externalGroupname in externalName2Groupname) { response.push(externalName2Groupname[externalGroupname]); @@ -210,7 +239,9 @@ const createGroupIfNonExistent = async (groupname, groupValue) => { const filterExistGroups = async (groupList) => { const allGroupItems = await getAllGroup(); - const allGroupSet = new Set(Array.from(allGroupItems, (groupItem) => groupItem.groupname)); + const allGroupSet = new Set( + Array.from(allGroupItems, (groupItem) => groupItem.groupname), + ); const existGroupList = groupList.filter((groupname) => { return allGroupSet.has(groupname); }); @@ -221,25 +252,32 @@ const filterExistGroups = async (groupList) => { const virtualCluster2GroupList = async (virtualCluster) => { const groupList = await getListGroup(virtualCluster); const filterGroups = groupList.filter((groupItem) => { - return groupItem.extension.acls && groupItem.extension.acls.virtualClusters - && groupItem.extension.acls.virtualClusters.length === 1 - && groupItem.extension.acls.virtualClusters[0] === groupItem.groupname; + return ( + groupItem.extension.acls && + groupItem.extension.acls.virtualClusters && + groupItem.extension.acls.virtualClusters.length === 1 && + groupItem.extension.acls.virtualClusters[0] === groupItem.groupname + ); }); - const vcGroups = new Set(Array.from(filterGroups, (groupItem) => groupItem.groupname)); + const vcGroups = new Set( + Array.from(filterGroups, (groupItem) => groupItem.groupname), + ); return virtualCluster.filter((vcname) => vcGroups.has(vcname)); }; - const updateGroup2ExnternalMapper = async () => { try { logger.info('Begin to update group info.'); const groupList = await getAllGroup(); - let newExternalName2Groupname = {}; + const newExternalName2Groupname = {}; let update = false; for (const groupItem of groupList) { newExternalName2Groupname[groupItem.externalName] = groupItem.groupname; } - if (Object.keys(newExternalName2Groupname).length !== Object.keys(externalName2Groupname).length) { + if ( + Object.keys(newExternalName2Groupname).length !== + Object.keys(externalName2Groupname).length + ) { update = true; } for (const [key, val] of Object.entries(newExternalName2Groupname)) { @@ -266,19 +304,19 @@ const initGrouplistInCfg = async () => { try { logger.info('Create admin group configured in configuration.'); const adminGroup = { - 'groupname': authConfig.groupConfig.adminGroup.groupname, - 'description': authConfig.groupConfig.adminGroup.description, - 'externalName': authConfig.groupConfig.adminGroup.externalName, - 'extension': authConfig.groupConfig.adminGroup.extension, + groupname: authConfig.groupConfig.adminGroup.groupname, + description: authConfig.groupConfig.adminGroup.description, + externalName: authConfig.groupConfig.adminGroup.externalName, + extension: authConfig.groupConfig.adminGroup.extension, }; await createGroupIfNonExistent(adminGroup.groupname, adminGroup); logger.info('Create admin group successfully.'); - logger.info('create default vc\'s group.'); + logger.info("create default vc's group."); const defaultVCGroup = { - 'groupname': authConfig.groupConfig.defaultGroup.groupname, - 'description': authConfig.groupConfig.defaultGroup.description, - 'externalName': authConfig.groupConfig.defaultGroup.externalName, - 'extension': authConfig.groupConfig.defaultGroup.extension, + groupname: authConfig.groupConfig.defaultGroup.groupname, + description: authConfig.groupConfig.defaultGroup.description, + externalName: authConfig.groupConfig.defaultGroup.externalName, + extension: authConfig.groupConfig.defaultGroup.extension, }; await createGroupIfNonExistent(defaultVCGroup.groupname, defaultVCGroup); logger.info('Create default group successfully.'); @@ -298,7 +336,10 @@ const createDefaultAdminUser = async () => { const userModel = require('@pai/models/v2/user'); try { logger.info('Create admin user account configured in configuration.'); - const groupnameList = Array.from(authConfig.groupConfig.grouplist, (groupItem) => groupItem.groupname); + const groupnameList = Array.from( + authConfig.groupConfig.grouplist, + (groupItem) => groupItem.groupname, + ); groupnameList.push(authConfig.groupConfig.defaultGroup.groupname); groupnameList.push(authConfig.groupConfig.adminGroup.groupname); const userValue = { @@ -311,7 +352,9 @@ const createDefaultAdminUser = async () => { await userModel.createUserIfNonExistent(userValue.username, userValue); logger.info('Create admin user account successfully.'); } catch (error) { - logger.error('Failed to create admin user account configured in configuration.'); + logger.error( + 'Failed to create admin user account configured in configuration.', + ); // eslint-disable-next-line no-console console.log(error); } @@ -367,12 +410,22 @@ const deleteNonexistVCs = async () => { const groupInfoList = await getAllGroup(); const vcList = await vcModel.list(); const vcSet = new Set(Object.keys(vcList)); - for (let groupItem of groupInfoList) { - if (groupItem.extension && groupItem.extension.acls && groupItem.extension.acls.virtualClusters) { - const checkedVCs = groupItem.extension.acls.virtualClusters.filter((vcname) => vcSet.has(vcname)); - if (checkedVCs.length !== groupItem.extension.acls.virtualClusters.length) { - logger.info(`Update group: ${groupItem.groupname} vc list, ` + - `old: ${groupItem.extension.acls.virtualClusters}, new: ${checkedVCs}.`); + for (const groupItem of groupInfoList) { + if ( + groupItem.extension && + groupItem.extension.acls && + groupItem.extension.acls.virtualClusters + ) { + const checkedVCs = groupItem.extension.acls.virtualClusters.filter( + (vcname) => vcSet.has(vcname), + ); + if ( + checkedVCs.length !== groupItem.extension.acls.virtualClusters.length + ) { + logger.info( + `Update group: ${groupItem.groupname} vc list, ` + + `old: ${groupItem.extension.acls.virtualClusters}, new: ${checkedVCs}.`, + ); groupItem.extension.acls.virtualClusters = checkedVCs; await updateGroup(groupItem.groupname, groupItem); } @@ -399,7 +452,10 @@ const syncGroupsWithVCs = async () => { }, }, }; - const created = await createGroupIfNonExistent(groupItem.groupname, groupItem); + const created = await createGroupIfNonExistent( + groupItem.groupname, + groupItem, + ); if (created) { logger.info(`Created group for vc ${vcName}`); } @@ -408,22 +464,29 @@ const syncGroupsWithVCs = async () => { // 2. delete meaningless group const groupItems = await getAllGroup(); const filterGroups = groupItems.filter((groupItem) => { - return groupItem.extension.acls && groupItem.extension.acls.virtualClusters - && groupItem.extension.acls.virtualClusters.length === 0 - && groupItem.extension.acls.admin === false; + return ( + groupItem.extension.acls && + groupItem.extension.acls.virtualClusters && + groupItem.extension.acls.virtualClusters.length === 0 && + groupItem.extension.acls.admin === false + ); }); - await Promise.all(filterGroups.map(async (groupItem) => { - await deleteGroup(groupItem.groupname); - logger.info(`Deleted group ${groupItem.groupname}`); - })); + await Promise.all( + filterGroups.map(async (groupItem) => { + await deleteGroup(groupItem.groupname); + logger.info(`Deleted group ${groupItem.groupname}`); + }), + ); }; const deleteInexistGroupFromUserlist = async () => { const userModel = require('@pai/models/v2/user'); const allGroupItems = await getAllGroup(); - const allGroupSet = new Set(Array.from(allGroupItems, (groupItem) => groupItem.groupname)); - let userList = await userModel.getAllUser(); - let updateUserList = []; + const allGroupSet = new Set( + Array.from(allGroupItems, (groupItem) => groupItem.groupname), + ); + const userList = await userModel.getAllUser(); + const updateUserList = []; for (const userItem of userList) { const originGrouplist = userItem.grouplist; const newGrouplist = originGrouplist.filter((groupname) => { @@ -465,7 +528,7 @@ if (config.env !== 'test') { process.exit(1); }); if (authConfig.authnMethod === 'OIDC') { - setInterval(async function() { + setInterval(async function () { await updateGroup2ExnternalMapper(); }, 600 * 1000); } diff --git a/src/rest-server/src/models/v2/job-attempt.js b/src/rest-server/src/models/v2/job-attempt.js index f11ee5ea97..f367aced9d 100644 --- a/src/rest-server/src/models/v2/job-attempt.js +++ b/src/rest-server/src/models/v2/job-attempt.js @@ -18,7 +18,7 @@ // module dependencies const crypto = require('crypto'); -const {convertToJobAttempt} = require('@pai/utils/frameworkConverter'); +const { convertToJobAttempt } = require('@pai/utils/frameworkConverter'); const launcherConfig = require('@pai/config/launcher'); const logger = require('@pai/config/logger'); const databaseModel = require('@pai/utils/dbUtils'); @@ -50,7 +50,7 @@ if (launcherConfig.enabledJobHistory) { }; const list = async (frameworkName) => { - let attemptData = []; + const attemptData = []; const encodedFrameworkName = encodeName(frameworkName); // get latest framework from k8s API @@ -58,10 +58,12 @@ if (launcherConfig.enabledJobHistory) { try { framework = await databaseModel.Framework.findOne({ attributes: ['snapshot'], - where: {name: encodedFrameworkName}} - ); + where: { name: encodedFrameworkName }, + }); } catch (error) { - logger.error(`error when getting framework from database: ${error.message}`); + logger.error( + `error when getting framework from database: ${error.message}`, + ); throw error; } @@ -71,16 +73,17 @@ if (launcherConfig.enabledJobHistory) { isLatest: true, }); } else { - logger.warn(`could not get framework ${encodedFrameworkName} from database.`); - return {status: 404, data: null}; + logger.warn( + `could not get framework ${encodedFrameworkName} from database.`, + ); + return { status: 404, data: null }; } const historyFrameworks = await databaseModel.FrameworkHistory.findAll({ - attributes: ['snapshot'], - where: {frameworkName: encodedFrameworkName}, - order: [['attemptIndex', 'ASC']], - } - ); + attributes: ['snapshot'], + where: { frameworkName: encodedFrameworkName }, + order: [['attemptIndex', 'ASC']], + }); const jobRetries = await Promise.all( historyFrameworks.map((row) => { @@ -89,10 +92,10 @@ if (launcherConfig.enabledJobHistory) { ); attemptData.push( ...jobRetries.map((jobRetry) => { - return {...jobRetry, isLatest: false}; + return { ...jobRetry, isLatest: false }; }), ); - return {status: 200, data: attemptData}; + return { status: 200, data: attemptData }; }; const get = async (frameworkName, jobAttemptIndex) => { @@ -103,40 +106,47 @@ if (launcherConfig.enabledJobHistory) { try { framework = await databaseModel.Framework.findOne({ attributes: ['snapshot'], - where: {name: encodedFrameworkName}} - ); + where: { name: encodedFrameworkName }, + }); } catch (error) { - logger.error(`error when getting framework from database: ${error.message}`); + logger.error( + `error when getting framework from database: ${error.message}`, + ); throw error; } if (framework) { attemptFramework = JSON.parse(framework.snapshot); } else { - logger.warn(`could not get framework ${encodedFrameworkName} from database.`); - return {status: 404, data: null}; + logger.warn( + `could not get framework ${encodedFrameworkName} from database.`, + ); + return { status: 404, data: null }; } if (jobAttemptIndex < attemptFramework.spec.retryPolicy.maxRetryCount) { const historyFramework = await databaseModel.FrameworkHistory.findOne({ attributes: ['snapshot'], - where: {frameworkName: encodedFrameworkName, attemptIndex: jobAttemptIndex}, + where: { + frameworkName: encodedFrameworkName, + attemptIndex: jobAttemptIndex, + }, }); if (!historyFramework) { - return {status: 404, data: null}; + return { status: 404, data: null }; } else { attemptFramework = JSON.parse(historyFramework.snapshot); const attemptDetail = await convertToJobAttempt(attemptFramework); - return {status: 200, data: {...attemptDetail, isLatest: false}}; + return { status: 200, data: { ...attemptDetail, isLatest: false } }; } } else if ( jobAttemptIndex === attemptFramework.spec.retryPolicy.maxRetryCount ) { const attemptDetail = await convertToJobAttempt(attemptFramework); - return {status: 200, data: {...attemptDetail, isLatest: true}}; + return { status: 200, data: { ...attemptDetail, isLatest: true } }; } else { - return {status: 404, data: null}; + return { status: 404, data: null }; } }; @@ -149,10 +159,10 @@ if (launcherConfig.enabledJobHistory) { module.exports = { healthCheck: () => false, list: () => { - throw Error('Unexpected Call'); -}, + throw Error('Unexpected Call'); + }, get: () => { - throw Error('Unexpected Call'); -}, + throw Error('Unexpected Call'); + }, }; } diff --git a/src/rest-server/src/models/v2/job/index.js b/src/rest-server/src/models/v2/job/index.js index e91c79d977..1e5e3db7e0 100644 --- a/src/rest-server/src/models/v2/job/index.js +++ b/src/rest-server/src/models/v2/job/index.js @@ -15,7 +15,6 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // module dependencies const status = require('statuses'); const config = require('@pai/config/index'); @@ -26,12 +25,11 @@ const k8sModel = require('@pai/models/kubernetes/kubernetes'); if (config.env !== 'test') { // framework controller health check (async () => { - const response = await k8sModel.getClient().get( - launcherConfig.healthCheckPath(), - { + const response = await k8sModel + .getClient() + .get(launcherConfig.healthCheckPath(), { headers: launcherConfig.requestHeaders, - } - ); + }); if (response.status === status('OK')) { logger.info('connected to framework controller successfully'); } else { diff --git a/src/rest-server/src/models/v2/job/k8s.js b/src/rest-server/src/models/v2/job/k8s.js index fdb51def09..7e68e3ee03 100644 --- a/src/rest-server/src/models/v2/job/k8s.js +++ b/src/rest-server/src/models/v2/job/k8s.js @@ -15,7 +15,6 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // module dependencies const axios = require('axios'); const zlib = require('zlib'); @@ -33,7 +32,7 @@ const path = require('path'); const fs = require('fs'); const _ = require('lodash'); const logger = require('@pai/config/logger'); -const {apiserver} = require('@pai/config/kubernetes'); +const { apiserver } = require('@pai/config/kubernetes'); const schedulePort = require('@pai/config/schedule-port'); const databaseModel = require('@pai/utils/dbUtils'); @@ -146,7 +145,9 @@ const convertFrameworkSummary = (framework) => { createdTime: new Date(framework.creationTime).getTime() || null, completedTime: new Date(framework.completionTime).getTime() || null, appExitCode: framework.appExitCode, - virtualCluster: framework.virtualCluster ? framework.virtualCluster : 'unknown', + virtualCluster: framework.virtualCluster + ? framework.virtualCluster + : 'unknown', totalGpuNumber: framework.totalGpuNumber, totalTaskNumber: framework.totalTaskNumber, totalTaskRoleNumber: framework.totalTaskRoleNumber, @@ -162,23 +163,33 @@ const convertTaskDetail = async (taskStatus, ports, logPathPrefix) => { const containerPorts = {}; const hashFunc = (str) => { const hexStr = crypto.createHash('md5').update(str).digest('hex'); - return parseInt(hexStr.substring(0, 12), 16) + - parseInt(hexStr.substring(12, 24), 16) + parseInt(hexStr.substring(24), 16); + return ( + parseInt(hexStr.substring(0, 12), 16) + + parseInt(hexStr.substring(12, 24), 16) + + parseInt(hexStr.substring(24), 16) + ); }; if (ports && taskStatus.attemptStatus.podUID) { const randomPorts = JSON.parse(ports); if (randomPorts.ports) { - for (let port of Object.keys(randomPorts.ports)) { - const portNums = [...Array(randomPorts.ports[port].count).keys()].map((index) => { - const rawString = `[${taskStatus.attemptStatus.podUID}][${port}][${index}]`; - return hashFunc(rawString) % (randomPorts.schedulePortEnd - randomPorts.schedulePortStart) + randomPorts.schedulePortStart; - }); + for (const port of Object.keys(randomPorts.ports)) { + const portNums = [...Array(randomPorts.ports[port].count).keys()].map( + (index) => { + const rawString = `[${taskStatus.attemptStatus.podUID}][${port}][${index}]`; + return ( + (hashFunc(rawString) % + (randomPorts.schedulePortEnd - randomPorts.schedulePortStart)) + + randomPorts.schedulePortStart + ); + }, + ); containerPorts[port] = portNums.join(); } } else { // for backward compatibility - for (let port of Object.keys(randomPorts)) { - containerPorts[port] = randomPorts[port].start + taskStatus.index * randomPorts[port].count; + for (const port of Object.keys(randomPorts)) { + containerPorts[port] = + randomPorts[port].start + taskStatus.index * randomPorts[port].count; } } } @@ -204,21 +215,29 @@ const convertTaskDetail = async (taskStatus, ports, logPathPrefix) => { containerGpus, containerLog: `http://${taskStatus.attemptStatus.podHostIP}:${process.env.LOG_MANAGER_PORT}/log-manager/tail/${logPathPrefix}/${taskStatus.attemptStatus.podUID}/`, containerExitCode: completionStatus ? completionStatus.code : null, - containerExitSpec: completionStatus ? generateExitSpec(completionStatus.code) : generateExitSpec(null), - containerExitDiagnostics: exitDiagnostics ? exitDiagnostics.diagnosticsSummary : null, + containerExitSpec: completionStatus + ? generateExitSpec(completionStatus.code) + : generateExitSpec(null), + containerExitDiagnostics: exitDiagnostics + ? exitDiagnostics.diagnosticsSummary + : null, retries: taskStatus.retryPolicyStatus.totalRetriedCount, accountableRetries: taskStatus.retryPolicyStatus.accountableRetriedCount, createdTime: new Date(taskStatus.startTime).getTime() || null, completedTime: new Date(taskStatus.completionTime).getTime() || null, - currentAttemptLaunchedTime: new Date(taskStatus.attemptStatus.runTime || taskStatus.attemptStatus.startTime).getTime() || null, - currentAttemptCompletedTime: new Date(taskStatus.attemptStatus.completionTime).getTime() || null, - ...launcherConfig.enabledHived && { + currentAttemptLaunchedTime: + new Date( + taskStatus.attemptStatus.runTime || taskStatus.attemptStatus.startTime, + ).getTime() || null, + currentAttemptCompletedTime: + new Date(taskStatus.attemptStatus.completionTime).getTime() || null, + ...(launcherConfig.enabledHived && { hived: { affinityGroupName, lazyPreempted: null, lazyPreemptionStatus: null, }, - }, + }), }; }; @@ -226,13 +245,24 @@ const convertFrameworkDetail = async (framework) => { const attemptStatus = framework.status.attemptStatus; // check fields which may be compressed if (attemptStatus.taskRoleStatuses == null) { - attemptStatus.taskRoleStatuses = decompressField(attemptStatus.taskRoleStatusesCompressed); + attemptStatus.taskRoleStatuses = decompressField( + attemptStatus.taskRoleStatusesCompressed, + ); } - const jobName = decodeName(framework.metadata.name, framework.metadata.annotations); - const userName = framework.metadata.labels ? framework.metadata.labels.userName : 'unknown'; - const virtualCluster = framework.metadata.labels ? framework.metadata.labels.virtualCluster : 'unknown'; - const logPathInfix = framework.metadata.annotations ? framework.metadata.annotations.logPathInfix : null; + const jobName = decodeName( + framework.metadata.name, + framework.metadata.annotations, + ); + const userName = framework.metadata.labels + ? framework.metadata.labels.userName + : 'unknown'; + const virtualCluster = framework.metadata.labels + ? framework.metadata.labels.virtualCluster + : 'unknown'; + const logPathInfix = framework.metadata.annotations + ? framework.metadata.annotations.logPathInfix + : null; const completionStatus = attemptStatus.completionStatus; const diagnostics = completionStatus ? completionStatus.diagnostics : null; @@ -252,46 +282,75 @@ const convertFrameworkDetail = async (framework) => { retries: framework.status.retryPolicyStatus.totalRetriedCount, retryDetails: { user: framework.status.retryPolicyStatus.accountableRetriedCount, - platform: framework.status.retryPolicyStatus.totalRetriedCount - framework.status.retryPolicyStatus.accountableRetriedCount, + platform: + framework.status.retryPolicyStatus.totalRetriedCount - + framework.status.retryPolicyStatus.accountableRetriedCount, resource: 0, }, retryDelayTime: framework.status.retryPolicyStatus.retryDelaySec, createdTime: new Date(framework.metadata.creationTimestamp).getTime(), - completedTime: new Date(framework.status.completionTime).getTime() || null, + completedTime: + new Date(framework.status.completionTime).getTime() || null, appId: attemptStatus.instanceUID, appProgress: completionStatus ? 1 : 0, appTrackingUrl: '', - appLaunchedTime: new Date(attemptStatus.runTime || attemptStatus.completionTime).getTime() || null, - appCompletedTime: new Date(attemptStatus.completionTime).getTime() || null, + appLaunchedTime: + new Date( + attemptStatus.runTime || attemptStatus.completionTime, + ).getTime() || null, + appCompletedTime: + new Date(attemptStatus.completionTime).getTime() || null, appExitCode: completionStatus ? completionStatus.code : null, - appExitSpec: completionStatus ? generateExitSpec(completionStatus.code) : generateExitSpec(null), - appExitDiagnostics: exitDiagnostics ? exitDiagnostics.diagnosticsSummary : null, - appExitMessages: exitDiagnostics ? { - container: null, - runtime: exitDiagnostics.runtime, - launcher: exitDiagnostics.launcher, - } : null, - appExitTriggerMessage: completionStatus && completionStatus.trigger ? completionStatus.trigger.message : null, - appExitTriggerTaskRoleName: completionStatus && completionStatus.trigger ? completionStatus.trigger.taskRoleName : null, - appExitTriggerTaskIndex: completionStatus && completionStatus.trigger ? completionStatus.trigger.taskIndex : null, + appExitSpec: completionStatus + ? generateExitSpec(completionStatus.code) + : generateExitSpec(null), + appExitDiagnostics: exitDiagnostics + ? exitDiagnostics.diagnosticsSummary + : null, + appExitMessages: exitDiagnostics + ? { + container: null, + runtime: exitDiagnostics.runtime, + launcher: exitDiagnostics.launcher, + } + : null, + appExitTriggerMessage: + completionStatus && completionStatus.trigger + ? completionStatus.trigger.message + : null, + appExitTriggerTaskRoleName: + completionStatus && completionStatus.trigger + ? completionStatus.trigger.taskRoleName + : null, + appExitTriggerTaskIndex: + completionStatus && completionStatus.trigger + ? completionStatus.trigger.taskIndex + : null, appExitType: completionStatus ? completionStatus.type.name : null, virtualCluster, }, taskRoles: {}, }; const ports = {}; - for (let taskRoleSpec of framework.spec.taskRoles) { - ports[taskRoleSpec.name] = taskRoleSpec.task.pod.metadata.annotations['rest-server/port-scheduling-spec']; + for (const taskRoleSpec of framework.spec.taskRoles) { + ports[taskRoleSpec.name] = + taskRoleSpec.task.pod.metadata.annotations[ + 'rest-server/port-scheduling-spec' + ]; } - for (let taskRoleStatus of framework.status.attemptStatus.taskRoleStatuses) { - const taskStatuses = await Promise.all(taskRoleStatus.taskStatuses.map( - async (status) => await convertTaskDetail( - status, - ports[taskRoleStatus.name], - `${userName}/${logPathInfix || jobName}/${taskRoleStatus.name}`, - ) - )); + for (const taskRoleStatus of framework.status.attemptStatus + .taskRoleStatuses) { + const taskStatuses = await Promise.all( + taskRoleStatus.taskStatuses.map( + async (status) => + await convertTaskDetail( + status, + ports[taskRoleStatus.name], + `${userName}/${logPathInfix || jobName}/${taskRoleStatus.name}`, + ), + ), + ); detail.taskRoles[taskRoleStatus.name] = { taskRoleStatus: { name: taskRoleStatus.name, @@ -303,7 +362,9 @@ const convertFrameworkDetail = async (framework) => { if (launcherConfig.enabledHived) { const affinityGroups = {}; try { - const res = await axios.get(`${launcherConfig.hivedWebserviceUri}/v1/inspect/affinitygroups/`); + const res = await axios.get( + `${launcherConfig.hivedWebserviceUri}/v1/inspect/affinitygroups/`, + ); if (res.data.items) { res.data.items.forEach((affinityGroup) => { affinityGroups[affinityGroup.metadata.name] = affinityGroup; @@ -312,13 +373,18 @@ const convertFrameworkDetail = async (framework) => { } catch (err) { logger.warn('Fail to inspect affinity groups', err); } - for (let taskRoleName of Object.keys(detail.taskRoles)) { + for (const taskRoleName of Object.keys(detail.taskRoles)) { detail.taskRoles[taskRoleName].taskStatuses.forEach((status, idx) => { const name = status.hived.affinityGroupName; if (name in affinityGroups) { - detail.taskRoles[taskRoleName].taskStatuses[idx].hived.lazyPreempted = - Boolean(affinityGroups[name].status.lazyPreemptionStatus); - detail.taskRoles[taskRoleName].taskStatuses[idx].hived.lazyPreemptionStatus = + detail.taskRoles[taskRoleName].taskStatuses[ + idx + ].hived.lazyPreempted = Boolean( + affinityGroups[name].status.lazyPreemptionStatus, + ); + detail.taskRoles[taskRoleName].taskStatuses[ + idx + ].hived.lazyPreemptionStatus = affinityGroups[name].status.lazyPreemptionStatus; } }); @@ -328,16 +394,26 @@ const convertFrameworkDetail = async (framework) => { return detail; }; -const generateTaskRole = (frameworkName, taskRole, jobInfo, frameworkEnvList, config) => { +const generateTaskRole = ( + frameworkName, + taskRole, + jobInfo, + frameworkEnvList, + config, +) => { const ports = config.taskRoles[taskRole].resourcePerInstance.ports || {}; - for (let port of ['ssh', 'http']) { + for (const port of ['ssh', 'http']) { if (!(port in ports)) { ports[port] = 1; } } - const randomPorts = {schedulePortStart: schedulePort.start, schedulePortEnd: schedulePort.end, ports: {}}; - for (let port of Object.keys(ports)) { + const randomPorts = { + schedulePortStart: schedulePort.start, + schedulePortEnd: schedulePort.end, + ports: {}, + }; + for (const port of Object.keys(ports)) { randomPorts.ports[port] = { count: ports[port], }; @@ -348,8 +424,10 @@ const generateTaskRole = (frameworkName, taskRole, jobInfo, frameworkEnvList, co shmMB = config.taskRoles[taskRole].extraContainerOptions.shmMB || 512; } // check InfiniBand device - const infinibandDevice = Boolean('extraContainerOptions' in config.taskRoles[taskRole] && - config.taskRoles[taskRole].extraContainerOptions.infiniband); + const infinibandDevice = Boolean( + 'extraContainerOptions' in config.taskRoles[taskRole] && + config.taskRoles[taskRole].extraContainerOptions.infiniband, + ); // enable gang scheduling or not let gangAllocation = 'true'; const retryPolicy = { @@ -382,7 +460,8 @@ const generateTaskRole = (frameworkName, taskRole, jobInfo, frameworkEnvList, co taskNumber: config.taskRoles[taskRole].instances || 1, task: { retryPolicy, - podGracefulDeletionTimeoutSec: launcherConfig.podGracefulDeletionTimeoutSec, + podGracefulDeletionTimeoutSec: + launcherConfig.podGracefulDeletionTimeoutSec, pod: { metadata: { labels: { @@ -427,7 +506,9 @@ const generateTaskRole = (frameworkName, taskRole, jobInfo, frameworkEnvList, co }, { name: 'host-log', - subPath: `${jobInfo.userName}/${jobInfo.logPathInfix}/${convertName(taskRole)}`, + subPath: `${jobInfo.userName}/${ + jobInfo.logPathInfix + }/${convertName(taskRole)}`, mountPath: '/usr/local/pai/logs', }, { @@ -441,15 +522,19 @@ const generateTaskRole = (frameworkName, taskRole, jobInfo, frameworkEnvList, co { name: 'app', imagePullPolicy: 'Always', - image: config.prerequisites.dockerimage[config.taskRoles[taskRole].dockerImage].uri, + image: + config.prerequisites.dockerimage[ + config.taskRoles[taskRole].dockerImage + ].uri, command: ['/usr/local/pai/runtime'], resources: { limits: { - 'cpu': config.taskRoles[taskRole].resourcePerInstance.cpu, - 'memory': `${config.taskRoles[taskRole].resourcePerInstance.memoryMB}Mi`, + cpu: config.taskRoles[taskRole].resourcePerInstance.cpu, + memory: `${config.taskRoles[taskRole].resourcePerInstance.memoryMB}Mi`, 'github.com/fuse': 1, - 'nvidia.com/gpu': config.taskRoles[taskRole].resourcePerInstance.gpu, - ...infinibandDevice && {'rdma/hca': 1}, + 'nvidia.com/gpu': + config.taskRoles[taskRole].resourcePerInstance.gpu, + ...(infinibandDevice && { 'rdma/hca': 1 }), }, }, env: [ @@ -483,7 +568,9 @@ const generateTaskRole = (frameworkName, taskRole, jobInfo, frameworkEnvList, co }, { name: 'host-log', - subPath: `${jobInfo.userName}/${jobInfo.logPathInfix}/${convertName(taskRole)}`, + subPath: `${jobInfo.userName}/${ + jobInfo.logPathInfix + }/${convertName(taskRole)}`, mountPath: '/usr/local/pai/logs', }, { @@ -553,27 +640,30 @@ const generateTaskRole = (frameworkName, taskRole, jobInfo, frameworkEnvList, co }, }; // add image pull secret - if (config.prerequisites.dockerimage[config.taskRoles[taskRole].dockerImage].auth) { + if ( + config.prerequisites.dockerimage[config.taskRoles[taskRole].dockerImage] + .auth + ) { frameworkTaskRole.task.pod.spec.imagePullSecrets.push({ name: `${encodeName(frameworkName)}-regcred`, }); } // add storages if ('extras' in config && config.extras.storages) { - for (let storage of config.extras.storages) { + for (const storage of config.extras.storages) { if (!storage.name) { continue; } frameworkTaskRole.task.pod.spec.containers[0].volumeMounts.push({ name: `${storage.name}-volume`, mountPath: storage.mountPath || `/mnt/${storage.name}`, - ...(storage.share === false) && {subPath: jobInfo.userName}, + ...(storage.share === false && { subPath: jobInfo.userName }), }); frameworkTaskRole.task.pod.spec.volumes.push({ name: `${storage.name}-volume`, persistentVolumeClaim: { claimName: `${storage.name}`, - ...(storage.readOnly === true) && {readOnly: true}, + ...(storage.readOnly === true && { readOnly: true }), }, }); } @@ -582,28 +672,40 @@ const generateTaskRole = (frameworkName, taskRole, jobInfo, frameworkEnvList, co const completion = config.taskRoles[taskRole].completion; frameworkTaskRole.frameworkAttemptCompletionPolicy = { minFailedTaskCount: - (completion && 'minFailedInstances' in completion && completion.minFailedInstances) ? - completion.minFailedInstances : 1, + completion && + 'minFailedInstances' in completion && + completion.minFailedInstances + ? completion.minFailedInstances + : 1, minSucceededTaskCount: - (completion && 'minSucceededInstances' in completion && completion.minSucceededInstances) ? - completion.minSucceededInstances : frameworkTaskRole.taskNumber, + completion && + 'minSucceededInstances' in completion && + completion.minSucceededInstances + ? completion.minSucceededInstances + : frameworkTaskRole.taskNumber, }; // check cpu job - if (!launcherConfig.enabledHived && config.taskRoles[taskRole].resourcePerInstance.gpu === 0) { - frameworkTaskRole.task.pod.spec.containers[0].env.push( - { - name: 'NVIDIA_VISIBLE_DEVICES', - value: 'none', - }, - ); + if ( + !launcherConfig.enabledHived && + config.taskRoles[taskRole].resourcePerInstance.gpu === 0 + ) { + frameworkTaskRole.task.pod.spec.containers[0].env.push({ + name: 'NVIDIA_VISIBLE_DEVICES', + value: 'none', + }); } // hived spec if (launcherConfig.enabledHived) { frameworkTaskRole.task.pod.spec.schedulerName = `${launcherConfig.scheduler}-ds-${config.taskRoles[taskRole].hivedPodSpec.virtualCluster}`; - delete frameworkTaskRole.task.pod.spec.containers[0].resources.limits['nvidia.com/gpu']; - frameworkTaskRole.task.pod.spec.containers[0] - .resources.limits['hivedscheduler.microsoft.com/pod-scheduling-enable'] = 1; - frameworkTaskRole.task.pod.metadata.annotations['hivedscheduler.microsoft.com/pod-scheduling-spec'] = yaml.safeDump(config.taskRoles[taskRole].hivedPodSpec); + delete frameworkTaskRole.task.pod.spec.containers[0].resources.limits[ + 'nvidia.com/gpu' + ]; + frameworkTaskRole.task.pod.spec.containers[0].resources.limits[ + 'hivedscheduler.microsoft.com/pod-scheduling-enable' + ] = 1; + frameworkTaskRole.task.pod.metadata.annotations[ + 'hivedscheduler.microsoft.com/pod-scheduling-spec' + ] = yaml.safeDump(config.taskRoles[taskRole].hivedPodSpec); frameworkTaskRole.task.pod.spec.containers[0].env.push( { name: 'NVIDIA_VISIBLE_DEVICES', @@ -627,7 +729,12 @@ const generateTaskRole = (frameworkName, taskRole, jobInfo, frameworkEnvList, co return frameworkTaskRole; }; -const generateFrameworkDescription = (frameworkName, virtualCluster, config, rawConfig) => { +const generateFrameworkDescription = ( + frameworkName, + virtualCluster, + config, + rawConfig, +) => { const [userName, jobName] = frameworkName.split(/~(.+)/); const jobInfo = { jobName, @@ -653,7 +760,7 @@ const generateFrameworkDescription = (frameworkName, virtualCluster, config, raw spec: { executionType: 'Start', retryPolicy: { - fancyRetryPolicy: (config.jobRetryCount !== -2), + fancyRetryPolicy: config.jobRetryCount !== -2, maxRetryCount: config.jobRetryCount || 0, }, taskRoles: [], @@ -663,18 +770,29 @@ const generateFrameworkDescription = (frameworkName, virtualCluster, config, raw // generate framework env const frameworkEnv = runtimeEnv.generateFrameworkEnv(frameworkName, config); const frameworkEnvList = Object.keys(frameworkEnv).map((name) => { - return {name, value: `${frameworkEnv[name]}`}; + return { name, value: `${frameworkEnv[name]}` }; }); // fill in task roles let totalGpuNumber = 0; - for (let taskRole of Object.keys(config.taskRoles)) { - totalGpuNumber += config.taskRoles[taskRole].resourcePerInstance.gpu * config.taskRoles[taskRole].instances; - const taskRoleDescription = generateTaskRole(frameworkName, taskRole, jobInfo, frameworkEnvList, config); + for (const taskRole of Object.keys(config.taskRoles)) { + totalGpuNumber += + config.taskRoles[taskRole].resourcePerInstance.gpu * + config.taskRoles[taskRole].instances; + const taskRoleDescription = generateTaskRole( + frameworkName, + taskRole, + jobInfo, + frameworkEnvList, + config, + ); if (launcherConfig.enabledPriorityClass) { - taskRoleDescription.task.pod.spec.priorityClassName = `${encodeName(frameworkName)}-priority`; + taskRoleDescription.task.pod.spec.priorityClassName = `${encodeName( + frameworkName, + )}-priority`; } else { - taskRoleDescription.task.pod.spec.priorityClassName = 'pai-job-minimal-priority'; + taskRoleDescription.task.pod.spec.priorityClassName = + 'pai-job-minimal-priority'; } if (config.secrets) { taskRoleDescription.task.pod.spec.volumes.push({ @@ -717,7 +835,7 @@ const getDockerSecretDef = (frameworkName, auths) => { const cred = { auths: {}, }; - for (let auth of auths) { + for (const auth of auths) { const { username = '', password = '', @@ -734,12 +852,13 @@ const getDockerSecretDef = (frameworkName, auths) => { name: `${encodeName(frameworkName)}-regcred`, namespace: 'default', }, - data: {'.dockerconfigjson': Buffer.from(JSON.stringify(cred)).toString('base64')}, + data: { + '.dockerconfigjson': Buffer.from(JSON.stringify(cred)).toString('base64'), + }, type: 'kubernetes.io/dockerconfigjson', }; }; - const getConfigSecretDef = (frameworkName, secrets) => { const data = { 'secrets.yaml': Buffer.from(yaml.safeDump(secrets)).toString('base64'), @@ -756,22 +875,25 @@ const getConfigSecretDef = (frameworkName, secrets) => { }; }; -const list = async (attributes, filters, order, offset, limit, withTotalCount) => { +const list = async ( + attributes, + filters, + order, + offset, + limit, + withTotalCount, +) => { let frameworks; let totalCount; - try { - frameworks = await databaseModel.Framework.findAll({ - attributes: attributes, - where: filters, - offset: offset, - limit: limit, - order: order, - }); - if (withTotalCount) { - totalCount = await databaseModel.Framework.count({where: filters}); - } - } catch (error) { - throw error; + frameworks = await databaseModel.Framework.findAll({ + attributes: attributes, + where: filters, + offset: offset, + limit: limit, + order: order, + }); + if (withTotalCount) { + totalCount = await databaseModel.Framework.count({ where: filters }); } frameworks = frameworks .filter((item) => checkName(item.name)) @@ -787,32 +909,41 @@ const list = async (attributes, filters, order, offset, limit, withTotalCount) = }; const get = async (frameworkName) => { - let framework; - try { - framework = await databaseModel.Framework.findOne({ - attributes: ['submissionTime', 'snapshot'], - where: {name: encodeName(frameworkName)}, - }); - } catch (error) { - throw error; - } + const framework = await databaseModel.Framework.findOne({ + attributes: ['submissionTime', 'snapshot'], + where: { name: encodeName(frameworkName) }, + }); if (framework) { - const frameworkDetail = await convertFrameworkDetail(JSON.parse(framework.snapshot)); - frameworkDetail.jobStatus.submissionTime = new Date(framework.submissionTime).getTime(); + const frameworkDetail = await convertFrameworkDetail( + JSON.parse(framework.snapshot), + ); + frameworkDetail.jobStatus.submissionTime = new Date( + framework.submissionTime, + ).getTime(); return frameworkDetail; } else { - throw createError('Not Found', 'NoJobError', `Job ${frameworkName} is not found.`); + throw createError( + 'Not Found', + 'NoJobError', + `Job ${frameworkName} is not found.`, + ); } }; const put = async (frameworkName, config, rawConfig) => { const [userName] = frameworkName.split(/~(.+)/); - const virtualCluster = ('defaults' in config && config.defaults.virtualCluster != null) ? - config.defaults.virtualCluster : 'default'; + const virtualCluster = + 'defaults' in config && config.defaults.virtualCluster != null + ? config.defaults.virtualCluster + : 'default'; const flag = await userModel.checkUserVC(userName, virtualCluster); if (flag === false) { - throw createError('Forbidden', 'ForbiddenUserError', `User ${userName} is not allowed to do operation in ${virtualCluster}`); + throw createError( + 'Forbidden', + 'ForbiddenUserError', + `User ${userName} is not allowed to do operation in ${virtualCluster}`, + ); } // check deprecated storages config @@ -821,13 +952,14 @@ const put = async (frameworkName, config, rawConfig) => { !config.extras.storages && 'com.microsoft.pai.runtimeplugin' in config.extras ) { - for (let plugin of config.extras['com.microsoft.pai.runtimeplugin']) { + for (const plugin of config.extras['com.microsoft.pai.runtimeplugin']) { if (plugin.plugin === 'teamwise_storage') { if ('parameters' in plugin && plugin.parameters.storageConfigNames) { - config.extras.storages = - plugin.parameters.storageConfigNames.map((name) => { - return {name}; - }); + config.extras.storages = plugin.parameters.storageConfigNames.map( + (name) => { + return { name }; + }, + ); } else { config.extras.storages = []; } @@ -838,29 +970,35 @@ const put = async (frameworkName, config, rawConfig) => { if ('extras' in config && config.extras.storages) { // add default storages if config is empty if (config.extras.storages.length === 0) { - (await storageModel.list(userName, true)).storages - .forEach((userStorage) => { + (await storageModel.list(userName, true)).storages.forEach( + (userStorage) => { config.extras.storages.push({ name: userStorage.name, share: userStorage.share, }); - }); + }, + ); } else { const userStorages = {}; - (await storageModel.list(userName)).storages - .forEach((userStorage) => userStorages[userStorage.name] = userStorage); - for (let storage of config.extras.storages) { + (await storageModel.list(userName)).storages.forEach( + (userStorage) => (userStorages[userStorage.name] = userStorage), + ); + for (const storage of config.extras.storages) { if (!storage.name) { continue; } if (!(storage.name in userStorages)) { - throw createError('Not Found', 'NoStorageError', `Storage ${storage.name} is not found.`); + throw createError( + 'Not Found', + 'NoStorageError', + `Storage ${storage.name} is not found.`, + ); } else { storage.share = userStorages[storage.name].share; } } } - for (let storage of config.extras.storages) { + for (const storage of config.extras.storages) { if (!storage.name) { continue; } @@ -868,15 +1006,24 @@ const put = async (frameworkName, config, rawConfig) => { } } - const frameworkDescription = generateFrameworkDescription(frameworkName, virtualCluster, config, rawConfig); + const frameworkDescription = generateFrameworkDescription( + frameworkName, + virtualCluster, + config, + rawConfig, + ); // generate image pull secret const auths = Object.values(config.prerequisites.dockerimage) .filter((dockerimage) => dockerimage.auth != null) .map((dockerimage) => dockerimage.auth); - const dockerSecretDef = auths.length ? getDockerSecretDef(frameworkName, auths) : null; + const dockerSecretDef = auths.length + ? getDockerSecretDef(frameworkName, auths) + : null; // generate job config secret - const configSecretDef = config.secrets ? getConfigSecretDef(frameworkName, config.secrets) : null; + const configSecretDef = config.secrets + ? getConfigSecretDef(frameworkName, config.secrets) + : null; // calculate pod priority // reference: https://github.com/microsoft/pai/issues/3704 @@ -885,11 +1032,14 @@ const put = async (frameworkName, config, rawConfig) => { if (launcherConfig.enabledPriorityClass) { let jobPriority = 0; if (launcherConfig.enabledHived) { - jobPriority = parseInt(Object.values(config.taskRoles)[0].hivedPodSpec.priority); + jobPriority = parseInt( + Object.values(config.taskRoles)[0].hivedPodSpec.priority, + ); jobPriority = Math.min(Math.max(jobPriority, -1), 126); } - const jobCreationTime = Math.floor(submissionTime / 1000) & (Math.pow(2, 23) - 1); - const podPriority = - (((126 - jobPriority) << 23) + jobCreationTime); + const jobCreationTime = + Math.floor(submissionTime / 1000) & (Math.pow(2, 23) - 1); + const podPriority = -(((126 - jobPriority) << 23) + jobCreationTime); // create priority class priorityClassDef = getPriorityClassDef(frameworkName, podPriority); } @@ -899,7 +1049,10 @@ const put = async (frameworkName, config, rawConfig) => { try { response = await axios({ method: 'put', - url: launcherConfig.writeMergerUrl + '/api/v1/frameworkRequest/' + encodeName(frameworkName), + url: + launcherConfig.writeMergerUrl + + '/api/v1/frameworkRequest/' + + encodeName(frameworkName), data: { frameworkRequest: frameworkDescription, submissionTime: submissionTime, @@ -928,12 +1081,17 @@ const execute = async (frameworkName, executionType) => { try { const patchData = { spec: { - executionType: `${executionType.charAt(0)}${executionType.slice(1).toLowerCase()}`, + executionType: `${executionType.charAt(0)}${executionType + .slice(1) + .toLowerCase()}`, }, }; response = await axios({ method: 'PATCH', - url: launcherConfig.writeMergerUrl + '/api/v1/frameworkRequest/' + encodeName(frameworkName), + url: + launcherConfig.writeMergerUrl + + '/api/v1/frameworkRequest/' + + encodeName(frameworkName), data: patchData, headers: { 'Content-Type': 'application/merge-patch+json', @@ -952,29 +1110,36 @@ const execute = async (frameworkName, executionType) => { }; const getConfig = async (frameworkName) => { - let framework; - try { - framework = await databaseModel.Framework.findOne({ - attributes: ['jobConfig'], - where: {name: encodeName(frameworkName)}, - }); - } catch (error) { - throw error; - } + const framework = await databaseModel.Framework.findOne({ + attributes: ['jobConfig'], + where: { name: encodeName(frameworkName) }, + }); if (framework) { if (framework.jobConfig) { return yaml.safeLoad(framework.jobConfig); } else { - throw createError('Not Found', 'NoJobConfigError', `Config of job ${frameworkName} is not found.`); + throw createError( + 'Not Found', + 'NoJobConfigError', + `Config of job ${frameworkName} is not found.`, + ); } } else { - throw createError('Not Found', 'NoJobError', `Job ${frameworkName} is not found.`); + throw createError( + 'Not Found', + 'NoJobError', + `Job ${frameworkName} is not found.`, + ); } }; const getSshInfo = async (frameworkName) => { - throw createError('Not Found', 'NoJobSshInfoError', `SSH info of job ${frameworkName} is not found.`); + throw createError( + 'Not Found', + 'NoJobSshInfoError', + `SSH info of job ${frameworkName} is not found.`, + ); }; const generateExitDiagnostics = (diag) => { @@ -1004,7 +1169,8 @@ const generateExitDiagnostics = (diag) => { } const summmaryInfo = diag.substring(0, matches.index + 'matched:'.length); - exitDiagnostics.diagnosticsSummary = summmaryInfo + '\n' + yaml.safeDump(podCompletionStatus); + exitDiagnostics.diagnosticsSummary = + summmaryInfo + '\n' + yaml.safeDump(podCompletionStatus); exitDiagnostics.launcher = exitDiagnostics.diagnosticsSummary; // Get runtime output, set launcher output to null. Otherwise, treat all message as launcher output diff --git a/src/rest-server/src/models/v2/job/runtime-env.js b/src/rest-server/src/models/v2/job/runtime-env.js index 3a8bd15c31..34646487dc 100644 --- a/src/rest-server/src/models/v2/job/runtime-env.js +++ b/src/rest-server/src/models/v2/job/runtime-env.js @@ -15,7 +15,6 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - const generateFrameworkEnv = (frameworkName, config) => { const [userName] = frameworkName.split('~'); const env = { @@ -27,22 +26,30 @@ const generateFrameworkEnv = (frameworkName, config) => { PAI_TASK_ROLE_LIST: Object.keys(config.taskRoles).join(','), }; let tasksNum = 0; - for (let taskRole of Object.keys(config.taskRoles)) { + for (const taskRole of Object.keys(config.taskRoles)) { const tasks = config.taskRoles[taskRole]; - tasksNum += (tasks.instances || 1); - env[`PAI_TASK_ROLE_TASK_COUNT_${taskRole}`] = (tasks.instances || 1); + tasksNum += tasks.instances || 1; + env[`PAI_TASK_ROLE_TASK_COUNT_${taskRole}`] = tasks.instances || 1; env[`PAI_RESOURCE_${taskRole}`] = [ tasks.resourcePerInstance.gpu, tasks.resourcePerInstance.cpu, tasks.resourcePerInstance.memoryMB, - (tasks.extraContainerOptions && 'shmMB' in tasks.extraContainerOptions) ? tasks.extraContainerOptions.shmMB : 0, + tasks.extraContainerOptions && 'shmMB' in tasks.extraContainerOptions + ? tasks.extraContainerOptions.shmMB + : 0, ].join(','); env[`PAI_MIN_FAILED_TASK_COUNT_${taskRole}`] = - (tasks.completion && 'minFailedInstances' in tasks.completion && tasks.completion.minFailedInstances) ? - tasks.completion.minFailedInstances : 1; + tasks.completion && + 'minFailedInstances' in tasks.completion && + tasks.completion.minFailedInstances + ? tasks.completion.minFailedInstances + : 1; env[`PAI_MIN_SUCCEEDED_TASK_COUNT_${taskRole}`] = - (tasks.completion && 'minSucceededInstances' in tasks.completion && tasks.completion.minSucceededInstances) ? - tasks.completion.minSucceededInstances : (tasks.instances || 1); + tasks.completion && + 'minSucceededInstances' in tasks.completion && + tasks.completion.minSucceededInstances + ? tasks.completion.minSucceededInstances + : tasks.instances || 1; } return { ...env, diff --git a/src/rest-server/src/models/v2/storage.js b/src/rest-server/src/models/v2/storage.js index 7b8fc47ef7..541c18d271 100644 --- a/src/rest-server/src/models/v2/storage.js +++ b/src/rest-server/src/models/v2/storage.js @@ -22,11 +22,10 @@ const user = require('@pai/models/v2/user'); const secret = require('@pai/models/kubernetes/k8s-secret'); const kubernetes = require('@pai/models/kubernetes/kubernetes'); - const convertVolumeSummary = (pvc) => { return { name: pvc.metadata.name, - share: (pvc.metadata.labels && pvc.metadata.labels.share === 'false') ? false : true, + share: pvc.metadata.labels && pvc.metadata.labels.share !== 'false', volumeName: pvc.spec.volumeName, }; }; @@ -39,9 +38,9 @@ const convertVolumeDetail = async (pvc) => { let response; try { - response = await kubernetes.getClient().get( - `/api/v1/persistentvolumes/${storage.volumeName}`, - ); + response = await kubernetes + .getClient() + .get(`/api/v1/persistentvolumes/${storage.volumeName}`); } catch (error) { if (error.response != null) { response = error.response; @@ -60,14 +59,14 @@ const convertVolumeDetail = async (pvc) => { server: pv.spec.nfs.server, path: pv.spec.nfs.path, }; - storage.readOnly = (pv.spec.nfs.readOnly === true); + storage.readOnly = pv.spec.nfs.readOnly === true; storage.mountOptions = pv.spec.mountOptions; } else if (pv.spec.azureFile) { storage.type = 'azureFile'; storage.data = { shareName: pv.spec.azureFile.shareName, }; - storage.readOnly = (pv.spec.azureFile.readOnly === true); + storage.readOnly = pv.spec.azureFile.readOnly === true; storage.secretName = pv.spec.azureFile.secretName; } else if (pv.spec.flexVolume) { if (pv.spec.flexVolume.driver === 'azure/blobfuse') { @@ -84,7 +83,7 @@ const convertVolumeDetail = async (pvc) => { storage.type = 'other'; storage.data = {}; } - storage.readOnly = (pv.spec.flexVolume.readOnly === true); + storage.readOnly = pv.spec.flexVolume.readOnly === true; if (pv.spec.flexVolume.secretRef) { storage.secretName = pv.spec.flexVolume.secretRef.name; } @@ -118,12 +117,12 @@ const convertVolumeDetail = async (pvc) => { return storage; }; -const list = async (userName, filterDefault=false) => { +const list = async (userName, filterDefault = false) => { let response; try { - response = await kubernetes.getClient().get( - '/api/v1/namespaces/default/persistentvolumeclaims', - ); + response = await kubernetes + .getClient() + .get('/api/v1/namespaces/default/persistentvolumeclaims'); } catch (error) { if (error.response != null) { response = error.response; @@ -135,26 +134,35 @@ const list = async (userName, filterDefault=false) => { throw createError(response.status, 'UnknownError', response.data.message); } - const userStorages = userName ? await user.getUserStorages(userName, filterDefault) : undefined; + const userStorages = userName + ? await user.getUserStorages(userName, filterDefault) + : undefined; const storages = response.data.items .filter((item) => item.status.phase === 'Bound') - .filter((item) => userStorages === undefined || userStorages.includes(item.metadata.name)) + .filter( + (item) => + userStorages === undefined || userStorages.includes(item.metadata.name), + ) .map(convertVolumeSummary); if (filterDefault) { - storages.forEach((item) => item.default = true); + storages.forEach((item) => (item.default = true)); } else { - const defaultStorages = userName ? await user.getUserStorages(userName, true) : []; - storages.forEach((item) => item.default = (defaultStorages.includes(item.name))); + const defaultStorages = userName + ? await user.getUserStorages(userName, true) + : []; + storages.forEach( + (item) => (item.default = defaultStorages.includes(item.name)), + ); } - return {storages}; + return { storages }; }; const get = async (storageName, userName) => { let response; try { - response = await kubernetes.getClient().get( - `/api/v1/namespaces/default/persistentvolumeclaims/${storageName}`, - ); + response = await kubernetes + .getClient() + .get(`/api/v1/namespaces/default/persistentvolumeclaims/${storageName}`); } catch (error) { if (error.response != null) { response = error.response; @@ -165,14 +173,25 @@ const get = async (storageName, userName) => { if (response.status === status('OK')) { const pvc = response.data; - if (!userName || (await user.checkUserStorage(userName, pvc.metadata.name))) { + if ( + !userName || + (await user.checkUserStorage(userName, pvc.metadata.name)) + ) { return convertVolumeDetail(pvc); } else { - throw createError('Forbidden', 'ForbiddenUserError', `User ${userName} is not allowed to access ${storageName}.`); + throw createError( + 'Forbidden', + 'ForbiddenUserError', + `User ${userName} is not allowed to access ${storageName}.`, + ); } } if (response.status === status('Not Found')) { - throw createError('Not Found', 'NoStorageError', `Storage ${storageName} is not found.`); + throw createError( + 'Not Found', + 'NoStorageError', + `Storage ${storageName} is not found.`, + ); } else { throw createError(response.status, 'UnknownError', response.data.message); } diff --git a/src/rest-server/src/models/v2/user.js b/src/rest-server/src/models/v2/user.js index bd5fb6504b..37d16a7a6a 100644 --- a/src/rest-server/src/models/v2/user.js +++ b/src/rest-server/src/models/v2/user.js @@ -15,7 +15,6 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // module dependencies const crudUtil = require('@pai/utils/manager/user/crudUtil'); const user = require('@pai/utils/manager/user/user'); @@ -47,8 +46,8 @@ const deleteUser = async (username) => { // it's an inplace encrypt! const getEncryptPassword = async (userValue) => { - await user.encryptUserPassword(userValue); - return userValue; + await user.encryptUserPassword(userValue); + return userValue; }; const createUserIfNonExistent = async (username, userValue) => { @@ -64,9 +63,11 @@ const createUserIfNonExistent = async (username, userValue) => { }; const batchUpdateUsers = async (userItems) => { - return await Promise.all(userItems.map(async (userItem) => { - await updateUser(userItem.username, userItem); - })); + return await Promise.all( + userItems.map(async (userItem) => { + await updateUser(userItem.username, userItem); + }), + ); }; const getUserVCs = async (username) => { @@ -84,7 +85,7 @@ const checkUserVC = async (username, vcname) => { return userVCs.includes(vcname); }; -const getUserStorages = async (username, filterDefault=false) => { +const getUserStorages = async (username, filterDefault = false) => { const userItem = await getUser(username); return groupModel.getGroupsStorages(userItem.grouplist, filterDefault); }; diff --git a/src/rest-server/src/models/v2/virtual-cluster/k8s.js b/src/rest-server/src/models/v2/virtual-cluster/k8s.js index b8d77e4e99..35a032ad58 100644 --- a/src/rest-server/src/models/v2/virtual-cluster/k8s.js +++ b/src/rest-server/src/models/v2/virtual-cluster/k8s.js @@ -19,8 +19,8 @@ const axios = require('axios'); const yaml = require('js-yaml'); const createError = require('@pai/utils/error'); -const {resourceUnits} = require('@pai/config/vc'); -const {enabledHived, hivedWebserviceUri} = require('@pai/config/launcher'); +const { resourceUnits } = require('@pai/config/vc'); +const { enabledHived, hivedWebserviceUri } = require('@pai/config/launcher'); const kubernetes = require('@pai/models/kubernetes/kubernetes'); const k8s = require('@pai/utils/k8sUtils'); @@ -33,12 +33,12 @@ const resourcesEmpty = { const add = (x, y) => x + y; const mergeDict = (d1, d2, op) => { - for (let k of [...Object.keys(d1).filter((x) => x in d2)]) { + for (const k of [...Object.keys(d1).filter((x) => x in d2)]) { d1[k] = op(d1[k], d2[k]); } }; -const fetchNodes = async (readiness=true) => { +const fetchNodes = async (readiness = true) => { const nodes = await kubernetes.getNodes(); return nodes.items.filter((node) => { if (node.metadata.labels['pai-worker'] !== 'true') { @@ -46,7 +46,9 @@ const fetchNodes = async (readiness=true) => { } // check node readiness - const readyCondition = node.status.conditions.find((x) => x.type === 'Ready'); + const readyCondition = node.status.conditions.find( + (x) => x.type === 'Ready', + ); if (readyCondition && readyCondition.status !== 'Unknown') { return readiness; } else { @@ -60,7 +62,10 @@ const fetchPods = async () => { labelSelector: 'type=kube-launcher-task', }); return pods.items.filter((pod) => { - return (pod.spec.nodeName && !(pod.status.phase === 'Succeeded' || pod.status.phase === 'Failed')); + return ( + pod.spec.nodeName && + !(pod.status.phase === 'Succeeded' || pod.status.phase === 'Failed') + ); }); }; @@ -78,14 +83,20 @@ const getPodsInfo = async () => { virtualCluster: labels.virtualCluster, taskRoleName: labels.FC_TASKROLE_NAME, nodeName: pod.spec.nodeName, - resourcesUsed: {...resourcesEmpty}, + resourcesUsed: { ...resourcesEmpty }, }; - const bindingInfo = annotations['hivedscheduler.microsoft.com/pod-bind-info']; + const bindingInfo = + annotations['hivedscheduler.microsoft.com/pod-bind-info']; const resourceRequest = pod.spec.containers[0].resources.requests; podInfo.resourcesUsed.cpu = k8s.atoi(resourceRequest.cpu); podInfo.resourcesUsed.memory = k8s.convertMemoryMb(resourceRequest.memory); - if (resourceRequest.hasOwnProperty('hivedscheduler.microsoft.com/pod-scheduling-enable')) { + if ( + Object.prototype.hasOwnProperty.call( + resourceRequest, + 'hivedscheduler.microsoft.com/pod-scheduling-enable', + ) + ) { if (bindingInfo != null) { // scheduled by hived const info = yaml.safeLoad(bindingInfo); @@ -108,13 +119,18 @@ const getNodeResource = async () => { if (enabledHived) { let pcStatus; try { - pcStatus = (await axios.get(`${hivedWebserviceUri}/v1/inspect/clusterstatus/physicalcluster`)).data; + pcStatus = ( + await axios.get( + `${hivedWebserviceUri}/v1/inspect/clusterstatus/physicalcluster`, + ) + ).data; } catch (error) { if (error.response != null) { throw createError( error.response.status, 'UnknownError', - error.response.data || 'Hived scheduler cannot inspect physical cluster.', + error.response.data || + 'Hived scheduler cannot inspect physical cluster.', ); } else { throw error; @@ -155,7 +171,7 @@ const getNodeResource = async () => { } } else { const nodes = await fetchNodes(true); - for (let node of nodes) { + for (const node of nodes) { const nodeName = node.metadata.name; const gpuNumber = k8s.atoi(node.status.capacity['nvidia.com/gpu']) + @@ -167,7 +183,7 @@ const getNodeResource = async () => { }; } const pods = await getPodsInfo(); - for (let pod of pods) { + for (const pod of pods) { if (pod.nodeName in nodeResource) { nodeResource[pod.nodeName].gpuUsed += pod.resourcesUsed.gpu; nodeResource[pod.nodeName].gpuAvailable -= pod.resourcesUsed.gpu; @@ -183,30 +199,35 @@ const getVcList = async () => { capacity: 0, usedCapacity: 0, dedicated: false, - resourcesUsed: {...resourcesEmpty}, - resourcesGuaranteed: {...resourcesEmpty}, - resourcesTotal: {...resourcesEmpty}, + resourcesUsed: { ...resourcesEmpty }, + resourcesGuaranteed: { ...resourcesEmpty }, + resourcesTotal: { ...resourcesEmpty }, }; - const vcInfos = {'default': JSON.parse(JSON.stringify(vcEmpty))}; + const vcInfos = { default: JSON.parse(JSON.stringify(vcEmpty)) }; // set resources if (enabledHived) { let vcStatus; try { - vcStatus = (await axios.get(`${hivedWebserviceUri}/v1/inspect/clusterstatus/virtualclusters/`)).data; + vcStatus = ( + await axios.get( + `${hivedWebserviceUri}/v1/inspect/clusterstatus/virtualclusters/`, + ) + ).data; } catch (error) { if (error.response != null) { throw createError( error.response.status, 'UnknownError', - error.response.data || 'Hived scheduler cannot inspect virtual clusters.', + error.response.data || + 'Hived scheduler cannot inspect virtual clusters.', ); } else { throw error; } } // used, guaranteed, total resources - for (let vc of Object.keys(vcStatus)) { + for (const vc of Object.keys(vcStatus)) { if (!(vc in vcInfos)) { vcInfos[vc] = JSON.parse(JSON.stringify(vcEmpty)); } @@ -238,44 +259,85 @@ const getVcList = async () => { } else { // used resources const pods = await getPodsInfo(); - for (let pod of pods) { + for (const pod of pods) { if (pod.virtualCluster in vcInfos) { - mergeDict(vcInfos[pod.virtualCluster].resourcesUsed, pod.resourcesUsed, add); + mergeDict( + vcInfos[pod.virtualCluster].resourcesUsed, + pod.resourcesUsed, + add, + ); } } // guaranteed resources const nodes = await fetchNodes(true); - vcInfos['default'].resourcesGuaranteed = { - cpu: nodes.reduce((sum, node) => sum + k8s.atoi(node.status.capacity.cpu), 0), - memory: nodes.reduce((sum, node) => sum + k8s.convertMemoryMb(node.status.capacity.memory), 0), - gpu: nodes.reduce((sum, node) => - sum + k8s.atoi(node.status.capacity['nvidia.com/gpu']) + k8s.atoi(node.status.capacity['amd.com/gpu']), 0), + vcInfos.default.resourcesGuaranteed = { + cpu: nodes.reduce( + (sum, node) => sum + k8s.atoi(node.status.capacity.cpu), + 0, + ), + memory: nodes.reduce( + (sum, node) => sum + k8s.convertMemoryMb(node.status.capacity.memory), + 0, + ), + gpu: nodes.reduce( + (sum, node) => + sum + + k8s.atoi(node.status.capacity['nvidia.com/gpu']) + + k8s.atoi(node.status.capacity['amd.com/gpu']), + 0, + ), }; // total resources const preemptedNodes = await fetchNodes(false); - vcInfos['default'].resourcesTotal = { - cpu: preemptedNodes.reduce((sum, node) => sum + k8s.atoi(node.status.capacity.cpu), 0), - memory: preemptedNodes.reduce((sum, node) => sum + k8s.convertMemoryMb(node.status.capacity.memory), 0), - gpu: preemptedNodes.reduce((sum, node) => - sum + k8s.atoi(node.status.capacity['nvidia.com/gpu']) + k8s.atoi(node.status.capacity['amd.com/gpu']), 0), + vcInfos.default.resourcesTotal = { + cpu: preemptedNodes.reduce( + (sum, node) => sum + k8s.atoi(node.status.capacity.cpu), + 0, + ), + memory: preemptedNodes.reduce( + (sum, node) => sum + k8s.convertMemoryMb(node.status.capacity.memory), + 0, + ), + gpu: preemptedNodes.reduce( + (sum, node) => + sum + + k8s.atoi(node.status.capacity['nvidia.com/gpu']) + + k8s.atoi(node.status.capacity['amd.com/gpu']), + 0, + ), }; - mergeDict(vcInfos['default'].resourcesTotal, vcInfos['default'].resourcesGuaranteed, add); + mergeDict( + vcInfos.default.resourcesTotal, + vcInfos.default.resourcesGuaranteed, + add, + ); } // add capacity, maxCapacity, usedCapacity for compatibility - const gpuTotal = Object.values(vcInfos).reduce((sum, vcInfo) => sum + vcInfo.resourcesTotal.gpu, 0); + const gpuTotal = Object.values(vcInfos).reduce( + (sum, vcInfo) => sum + vcInfo.resourcesTotal.gpu, + 0, + ); if (gpuTotal > 0) { - for (let vc of Object.keys(vcInfos)) { - vcInfos[vc].capacity = vcInfos[vc].resourcesTotal.gpu / gpuTotal * 100; + for (const vc of Object.keys(vcInfos)) { + vcInfos[vc].capacity = (vcInfos[vc].resourcesTotal.gpu / gpuTotal) * 100; vcInfos[vc].maxCapacity = vcInfos[vc].capacity; - vcInfos[vc].usedCapacity = vcInfos[vc].resourcesUsed.gpu / gpuTotal * 100; + vcInfos[vc].usedCapacity = + (vcInfos[vc].resourcesUsed.gpu / gpuTotal) * 100; } } // add GPUs, vCores for compatibility - for (let vc of Object.keys(vcInfos)) { - for (let resource of ['resourcesUsed', 'resourcesGuaranteed', 'resourcesTotal']) { - for (let [k, v] of [['vCores', 'cpu'], ['GPUs', 'gpu']]) { + for (const vc of Object.keys(vcInfos)) { + for (const resource of [ + 'resourcesUsed', + 'resourcesGuaranteed', + 'resourcesTotal', + ]) { + for (const [k, v] of [ + ['vCores', 'cpu'], + ['GPUs', 'gpu'], + ]) { vcInfos[vc][resource][k] = vcInfos[vc][resource][v]; } } @@ -285,26 +347,46 @@ const getVcList = async () => { const getVc = async (vcName) => { const vcInfos = await getVcList(); - if (!vcInfos.hasOwnProperty(vcName)) { - throw createError('Not Found', 'NoVirtualClusterError', `Vc ${vcName} not found`); + if (!Object.prototype.hasOwnProperty.call(vcInfos, vcName)) { + throw createError( + 'Not Found', + 'NoVirtualClusterError', + `Vc ${vcName} not found`, + ); } return vcInfos[vcName]; }; const updateVc = () => { - throw createError('Bad Request', 'NotImplementedError', 'updateVc not implemented in k8s'); + throw createError( + 'Bad Request', + 'NotImplementedError', + 'updateVc not implemented in k8s', + ); }; const stopVc = () => { - throw createError('Bad Request', 'NotImplementedError', 'stopVc not implemented in k8s'); + throw createError( + 'Bad Request', + 'NotImplementedError', + 'stopVc not implemented in k8s', + ); }; const activeVc = () => { - throw createError('Bad Request', 'NotImplementedError', 'activeVc not implemented in k8s'); + throw createError( + 'Bad Request', + 'NotImplementedError', + 'activeVc not implemented in k8s', + ); }; const removeVc = () => { - throw createError('Bad Request', 'NotImplementedError', 'removeVc not implemented in k8s'); + throw createError( + 'Bad Request', + 'NotImplementedError', + 'removeVc not implemented in k8s', + ); }; // module exports diff --git a/src/rest-server/src/routes/authn.js b/src/rest-server/src/routes/authn.js index 4995b7603d..fb1440ece9 100644 --- a/src/rest-server/src/routes/authn.js +++ b/src/rest-server/src/routes/authn.js @@ -30,42 +30,40 @@ const tokenMiddleware = require('@pai/middlewares/token'); const router = new express.Router(); -router.route('/info') - .get( - async function(req, res, next) { - const authnMode = authnConfig.authnMethod; - const loginURI = authnConfig.authnMethod === 'OIDC' ? '/api/v1/authn/oidc/login' : '/api/v1/authn/basic/login'; - const loginURIMethod = authnConfig.authnMethod === 'OIDC' ? 'get' : 'post'; - return res.status(200).json({ - authn_type: authnMode, - loginURI: loginURI, - loginURIMethod: loginURIMethod, - }); - } - ); - +router.route('/info').get(async function (req, res, next) { + const authnMode = authnConfig.authnMethod; + const loginURI = + authnConfig.authnMethod === 'OIDC' + ? '/api/v1/authn/oidc/login' + : '/api/v1/authn/basic/login'; + const loginURIMethod = authnConfig.authnMethod === 'OIDC' ? 'get' : 'post'; + return res.status(200).json({ + authn_type: authnMode, + loginURI: loginURI, + loginURIMethod: loginURIMethod, + }); +}); if (authnConfig.authnMethod === 'OIDC') { - router.route('/oidc/login') - /** POST /api/v1/authn/oidc/login - Return a token OIDC authn is passed and the user has the access to OpenPAI */ - .get( - azureADController.requestAuthCode - ); + router + .route('/oidc/login') + /** POST /api/v1/authn/oidc/login - Return a token OIDC authn is passed and the user has the access to OpenPAI */ + .get(azureADController.requestAuthCode); - router.route('/oidc/logout') - /** POST /api/v1/authn/oidc/logout */ - .get( - azureADController.signoutAzureAD - ); + router + .route('/oidc/logout') + /** POST /api/v1/authn/oidc/logout */ + .get(azureADController.signoutAzureAD); - router.route('/oidc/return') - /** GET /api/v1/authn/oidc/return - AAD AUTH RETURN */ + router + .route('/oidc/return') + /** GET /api/v1/authn/oidc/return - AAD AUTH RETURN */ .get( azureADController.requestTokenWithCode, azureADController.parseTokenData, userController.createUserIfUserNotExist, userController.updateUserGroupListFromExternal, - tokenController.getAAD + tokenController.getAAD, ) /** POST /api/v1/authn/oidc/return - AAD AUTH RETURN */ .post( @@ -73,32 +71,42 @@ if (authnConfig.authnMethod === 'OIDC') { azureADController.parseTokenData, userController.createUserIfUserNotExist, userController.updateUserGroupListFromExternal, - tokenController.getAAD + tokenController.getAAD, ); } else { - router.route('/basic/login') - /** POST /api/v1/authn/basic/login - Return a token if username and password is correct */ + router + .route('/basic/login') + /** POST /api/v1/authn/basic/login - Return a token if username and password is correct */ .post( - param.validate(tokenConfig.tokenPostInputSchema), tokenController.get); + param.validate(tokenConfig.tokenPostInputSchema), + tokenController.get, + ); - router.route('/basic/logout') - /** POST /api/v1/authn/basic/logout - logout */ + router + .route('/basic/logout') + /** POST /api/v1/authn/basic/logout - logout */ .delete(tokenMiddleware.checkNotApplication, async (req, res, next) => { - const token = req.headers.authorization.split(' ')[1]; - try { - const {username} = jwt.decode(token); - if (username === req.user.username) { - await tokenModel.revoke(token); - res.status(200).json({ - message: 'Logout successfully', - }); - } else { - next(createError('Forbidden', 'ForbiddenUserError', `User ${req.user.username} is not allowed to do this operation.`)); - } - } catch (err) { - next(createError.unknown(err)); - } -}); + const token = req.headers.authorization.split(' ')[1]; + try { + const { username } = jwt.decode(token); + if (username === req.user.username) { + await tokenModel.revoke(token); + res.status(200).json({ + message: 'Logout successfully', + }); + } else { + next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `User ${req.user.username} is not allowed to do this operation.`, + ), + ); + } + } catch (err) { + next(createError.unknown(err)); + } + }); } // module exports diff --git a/src/rest-server/src/routes/index.js b/src/rest-server/src/routes/index.js index 1859b45e26..b929a72dfe 100644 --- a/src/rest-server/src/routes/index.js +++ b/src/rest-server/src/routes/index.js @@ -15,7 +15,6 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // module dependencies const express = require('express'); const controller = require('@pai/controllers/index'); @@ -26,14 +25,12 @@ const k8sRouter = require('@pai/routes/kubernetes'); const router = new express.Router(); -router.route('/') - .all(controller.index); +router.route('/').all(controller.index); router.use('/token', tokenRouter); router.use('/user', userRouter); router.use('/kubernetes', k8sRouter); router.use('/authn', authnRouter); - // module exports module.exports = router; diff --git a/src/rest-server/src/routes/kubernetes.js b/src/rest-server/src/routes/kubernetes.js index a31b6b298f..d9686ece67 100644 --- a/src/rest-server/src/routes/kubernetes.js +++ b/src/rest-server/src/routes/kubernetes.js @@ -8,11 +8,18 @@ const createError = require('@pai/utils/error'); const router = new express.Router(); -router.route('/nodes') +router + .route('/nodes') /** GET /api/v1/kubernetes/nodes - Return k8s nodes info */ .get(token.check, async (req, res, next) => { if (!req.user.admin) { - return next(createError('Forbidden', 'ForbiddenUserError', `Non-admin is not allow to do this operation.`)); + return next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `Non-admin is not allow to do this operation.`, + ), + ); } try { const nodes = await kubernetes.getNodes(); @@ -22,11 +29,18 @@ router.route('/nodes') } }); -router.route('/pods') +router + .route('/pods') /** GET /api/v1/kubernetes/pods - Return k8s pods info */ .get(token.check, async (req, res, next) => { if (!req.user.admin) { - return next(createError('Forbidden', 'ForbiddenUserError', `Non-admin is not allow to do this operation.`)); + return next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `Non-admin is not allow to do this operation.`, + ), + ); } try { const pods = await kubernetes.getPods(req.query); diff --git a/src/rest-server/src/routes/token.js b/src/rest-server/src/routes/token.js index 521255fbfa..4a4ea9772b 100644 --- a/src/rest-server/src/routes/token.js +++ b/src/rest-server/src/routes/token.js @@ -1,4 +1,3 @@ - // Copyright (c) Microsoft Corporation // All rights reserved. // @@ -29,7 +28,6 @@ const createError = require('@pai/utils/error'); const router = new express.Router(); - /** GET /api/v1/token - Get list of tokens */ router.get('/', tokenMiddleware.checkNotApplication, async (req, res, next) => { try { @@ -44,41 +42,56 @@ router.get('/', tokenMiddleware.checkNotApplication, async (req, res, next) => { if (authnConfig.authnMethod !== 'OIDC') { /** POST /api/v1/token - Generate a token */ - router.post('/', + router.post( + '/', param.validate(tokenConfig.tokenPostInputSchema), - tokenController.get + tokenController.get, ); } /** POST /api/v1/token/application - Generate an application token */ -router.post('/application', tokenMiddleware.checkNotApplication, async (req, res, next) => { - try { - const token = await tokenModel.create(req.user.username, true); - res.status(200).json({ - token, - application: true, - }); - } catch (err) { - next(createError.unknown(err)); - } -}); - -/** DELETE /api/v1/token/:token - Revoke a token */ -router.delete('/:token', tokenMiddleware.checkNotApplication, async (req, res, next) => { - const token = req.params.token; - try { - const {username} = jwt.decode(token); - if (username === req.user.username) { - await tokenModel.revoke(token); +router.post( + '/application', + tokenMiddleware.checkNotApplication, + async (req, res, next) => { + try { + const token = await tokenModel.create(req.user.username, true); res.status(200).json({ - message: 'revoke successfully', + token, + application: true, }); - } else { - next(createError('Forbidden', 'ForbiddenUserError', `User ${req.user.username} is not allowed to do this operation.`)); + } catch (err) { + next(createError.unknown(err)); } - } catch (err) { - next(createError.unknown(err)); - } -}); + }, +); + +/** DELETE /api/v1/token/:token - Revoke a token */ +router.delete( + '/:token', + tokenMiddleware.checkNotApplication, + async (req, res, next) => { + const token = req.params.token; + try { + const { username } = jwt.decode(token); + if (username === req.user.username) { + await tokenModel.revoke(token); + res.status(200).json({ + message: 'revoke successfully', + }); + } else { + next( + createError( + 'Forbidden', + 'ForbiddenUserError', + `User ${req.user.username} is not allowed to do this operation.`, + ), + ); + } + } catch (err) { + next(createError.unknown(err)); + } + }, +); // module exports module.exports = router; diff --git a/src/rest-server/src/routes/user.js b/src/rest-server/src/routes/user.js index 4cd4cb932e..36fcdae618 100644 --- a/src/rest-server/src/routes/user.js +++ b/src/rest-server/src/routes/user.js @@ -20,11 +20,11 @@ const express = require('express'); const token = require('@pai/middlewares/token'); const userController = require('@pai/controllers/v2/user'); - const router = new express.Router(); -router.route('/:username/') -/** Get /api/v1/user/:username */ +router + .route('/:username/') + /** Get /api/v1/user/:username */ .get(token.check, userController.getUser); module.exports = router; diff --git a/src/rest-server/src/routes/v2/cluster.js b/src/rest-server/src/routes/v2/cluster.js index 9104017534..a00239196b 100644 --- a/src/rest-server/src/routes/v2/cluster.js +++ b/src/rest-server/src/routes/v2/cluster.js @@ -22,8 +22,7 @@ const controller = require('@pai/controllers/v2/cluster'); const router = new express.Router(); -router.route('/sku-types') - .get(token.check, controller.getSkuTypes); +router.route('/sku-types').get(token.check, controller.getSkuTypes); // module exports module.exports = router; diff --git a/src/rest-server/src/routes/v2/group.js b/src/rest-server/src/routes/v2/group.js index 4fc54e2ab6..817ce20aa9 100644 --- a/src/rest-server/src/routes/v2/group.js +++ b/src/rest-server/src/routes/v2/group.js @@ -24,47 +24,78 @@ const groupInputSchema = require('@pai/config/v2/group'); const router = new express.Router(); -router.route('/:groupname/') -/** Get /api/v2/group/:groupname */ +router + .route('/:groupname/') + /** Get /api/v2/group/:groupname */ .get(token.check, groupController.getGroup); -router.route('/') -/** Get /api/v2/group */ +router + .route('/') + /** Get /api/v2/group */ .get(token.check, groupController.getAllGroup); -router.route('/:groupname') -/** Post /api/v2/group/:groupname */ +router + .route('/:groupname') + /** Post /api/v2/group/:groupname */ .delete(token.checkNotApplication, groupController.deleteGroup); -router.route('/') -/** Create /api/v2/group */ - .post(token.checkNotApplication, param.validate(groupInputSchema.groupCreateInputSchema), groupController.createGroup); - -router.route('/') -/** put /api/v2/group/ */ - .put(token.checkNotApplication, param.validate(groupInputSchema.groupUpdateInputSchema), groupController.updateGroup); +router + .route('/') + /** Create /api/v2/group */ + .post( + token.checkNotApplication, + param.validate(groupInputSchema.groupCreateInputSchema), + groupController.createGroup, + ); + +router + .route('/') + /** put /api/v2/group/ */ + .put( + token.checkNotApplication, + param.validate(groupInputSchema.groupUpdateInputSchema), + groupController.updateGroup, + ); /** Internal API */ -router.route('/:groupname/extension/*') - .put(token.checkNotApplication, param.validate(groupInputSchema.groupExtensionAttrUpdateInputSchema), groupController.updateGroupExtensionAttr); - -router.route('/:groupname/userlist') -/** get /api/v2/group/:groupname/userlist */ +router + .route('/:groupname/extension/*') + .put( + token.checkNotApplication, + param.validate(groupInputSchema.groupExtensionAttrUpdateInputSchema), + groupController.updateGroupExtensionAttr, + ); + +router + .route('/:groupname/userlist') + /** get /api/v2/group/:groupname/userlist */ .get(token.check, groupController.getGroupUserList); /** Legacy API and will be deprecated in the future. Please use put /api/v2/group */ -router.route('/:groupname/extension') - .put(token.checkNotApplication, param.validate(groupInputSchema.groupExtensionUpdateInputSchema), groupController.updateGroupExtension); +router + .route('/:groupname/extension') + .put( + token.checkNotApplication, + param.validate(groupInputSchema.groupExtensionUpdateInputSchema), + groupController.updateGroupExtension, + ); /** Legacy API and will be deprecated in the future. Please use put /api/v2/group */ -router.route('/:groupname/description') - .put(token.checkNotApplication, param.validate(groupInputSchema.groupDescriptionUpdateInputSchema), groupController.updateGroupDescription); +router + .route('/:groupname/description') + .put( + token.checkNotApplication, + param.validate(groupInputSchema.groupDescriptionUpdateInputSchema), + groupController.updateGroupDescription, + ); /** Legacy API and will be deprecated in the future. Please use put /api/v2/group */ -router.route('/:groupname/externalname') - .put(token.checkNotApplication, param.validate(groupInputSchema.groupExternalNameUpdateInputSchema), groupController.updateGroupExternalName); - +router + .route('/:groupname/externalname') + .put( + token.checkNotApplication, + param.validate(groupInputSchema.groupExternalNameUpdateInputSchema), + groupController.updateGroupExternalName, + ); module.exports = router; - - diff --git a/src/rest-server/src/routes/v2/index.js b/src/rest-server/src/routes/v2/index.js index 3900b046f3..28cc9d11b0 100644 --- a/src/rest-server/src/routes/v2/index.js +++ b/src/rest-server/src/routes/v2/index.js @@ -18,10 +18,8 @@ const k8sRouter = require('@pai/routes/kubernetes'); const router = new express.Router(); -router.route('/') - .all(controller.index); -router.route('/info') - .all(infoController.info); +router.route('/').all(controller.index); +router.route('/info').all(infoController.info); router.use('/jobs', jobRouter); router.use('/cluster', clusterRouter); diff --git a/src/rest-server/src/routes/v2/job-attempt.js b/src/rest-server/src/routes/v2/job-attempt.js index d09e2d8022..f92dc1b5a4 100644 --- a/src/rest-server/src/routes/v2/job-attempt.js +++ b/src/rest-server/src/routes/v2/job-attempt.js @@ -15,25 +15,20 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // module dependencies const express = require('express'); const controller = require('@pai/controllers/v2/job-attempt'); const token = require('@pai/middlewares/token'); +const router = new express.Router({ mergeParams: true }); -const router = new express.Router({mergeParams: true}); - -/** GET /api/v2/jobs/:frameworkName/job-attempts/healthz - health check of job retry endpoint*/ -router.route('/healthz') - .get(token.check, controller.healthCheck); +/** GET /api/v2/jobs/:frameworkName/job-attempts/healthz - health check of job retry endpoint */ +router.route('/healthz').get(token.check, controller.healthCheck); /** GET /api/v2/jobs/:frameworkName/job-attempts - list job retries by job frameworkName */ -router.route('/') - .get(token.check, controller.list); +router.route('/').get(token.check, controller.list); /** GET /api/v2/jobs/:frameworkName/job-attempts/:jobAttemptIndex - get certain job retry by retry index */ -router.route('/:jobAttemptIndex') - .get(token.check, controller.get); +router.route('/:jobAttemptIndex').get(token.check, controller.get); module.exports = router; diff --git a/src/rest-server/src/routes/v2/job.js b/src/rest-server/src/routes/v2/job.js index 581ef9e131..17f11dde32 100644 --- a/src/rest-server/src/routes/v2/job.js +++ b/src/rest-server/src/routes/v2/job.js @@ -15,7 +15,6 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // module dependencies const express = require('express'); const limiter = require('@pai/config/rate-limit'); @@ -24,33 +23,32 @@ const controller = require('@pai/controllers/v2/job'); const protocol = require('@pai/middlewares/v2/protocol'); const jobAttemptRouter = require('@pai/routes/v2/job-attempt.js'); - const router = new express.Router(); -router.route('/') +router + .route('/') /** GET /api/v2/jobs - List job */ .get(token.check, limiter.listJob, controller.list) /** POST /api/v2/jobs - Update job */ - .post( - token.check, - limiter.submitJob, - protocol.submit, - controller.update - ); - -router.route('/:frameworkName') + .post(token.check, limiter.submitJob, protocol.submit, controller.update); + +router + .route('/:frameworkName') /** GET /api/v2/jobs/:frameworkName - Get job */ .get(token.check, controller.get); -router.route('/:frameworkName/executionType') +router + .route('/:frameworkName/executionType') /** PUT /api/v2/jobs/:frameworkName/executionType - Start or stop job */ .put(token.check, controller.execute); -router.route('/:frameworkName/config') +router + .route('/:frameworkName/config') /** GET /api/v2/jobs/:frameworkName/config - Get job config */ .get(token.check, controller.getConfig); -router.route('/:frameworkName/ssh') +router + .route('/:frameworkName/ssh') /** GET /api/v2/jobs/:frameworkName/ssh - Get job ssh info */ .get(token.check, controller.getSshInfo); diff --git a/src/rest-server/src/routes/v2/storage-deprecated.js b/src/rest-server/src/routes/v2/storage-deprecated.js index 2cd2c9d941..2818de1462 100644 --- a/src/rest-server/src/routes/v2/storage-deprecated.js +++ b/src/rest-server/src/routes/v2/storage-deprecated.js @@ -22,16 +22,12 @@ const controller = require('@pai/controllers/v2/storage-deprecated'); const router = new express.Router(); -router.route('/config') - .get(token.check, controller.getConfig); +router.route('/config').get(token.check, controller.getConfig); -router.route('/config/:name') - .get(token.check, controller.getConfig); +router.route('/config/:name').get(token.check, controller.getConfig); -router.route('/server') - .get(token.check, controller.getServer); +router.route('/server').get(token.check, controller.getServer); -router.route('/server/:name') - .get(token.check, controller.getServer); +router.route('/server/:name').get(token.check, controller.getServer); module.exports = router; diff --git a/src/rest-server/src/routes/v2/storage.js b/src/rest-server/src/routes/v2/storage.js index 628015170f..0e879b2a73 100644 --- a/src/rest-server/src/routes/v2/storage.js +++ b/src/rest-server/src/routes/v2/storage.js @@ -22,10 +22,8 @@ const controller = require('@pai/controllers/v2/storage'); const router = new express.Router(); -router.route('/') - .get(token.check, controller.list); +router.route('/').get(token.check, controller.list); -router.route('/:storageName') - .get(token.check, controller.get); +router.route('/:storageName').get(token.check, controller.get); module.exports = router; diff --git a/src/rest-server/src/routes/v2/user.js b/src/rest-server/src/routes/v2/user.js index 7b787e1c98..db32fbace8 100644 --- a/src/rest-server/src/routes/v2/user.js +++ b/src/rest-server/src/routes/v2/user.js @@ -25,72 +25,134 @@ const authnConfig = require('@pai/config/authn'); const router = new express.Router(); -router.route('/:username/') +router + .route('/:username/') /** Get /api/v2/users/:username */ .get(token.check, userController.getUser); -router.route('/') +router + .route('/') /** Get /api/v2/users */ .get(token.check, userController.getAllUser); /** Legacy API and will be deprecated in the future. Please use put /api/v2/users */ -router.route('/:username/extension') +router + .route('/:username/extension') /** Put /api/v2/users/:username/extension */ - .put(token.checkNotApplication, param.validate(userInputSchema.userExtensionUpdateInputSchema), userController.updateUserExtension); - + .put( + token.checkNotApplication, + param.validate(userInputSchema.userExtensionUpdateInputSchema), + userController.updateUserExtension, + ); if (authnConfig.authnMethod === 'basic') { - router.route('/:username') - /** Delete /api/v2/users/:username */ + router + .route('/:username') + /** Delete /api/v2/users/:username */ .delete(token.checkNotApplication, userController.deleteUser); - router.route('/') - /** Create /api/v2/users */ - .post(token.checkNotApplication, param.validate(userInputSchema.userCreateInputSchema), userController.createUser); - - router.route('/') - /** Put /api/v2/users */ - .put(token.checkNotApplication, param.validate(userInputSchema.basicAdminUserUpdateInputSchema), userController.basicAdminUserUpdate); - - router.route('/me') - /** Put /api/v2/users */ - .put(token.checkNotApplication, param.validate(userInputSchema.basicUserUpdateInputSchema), userController.basicUserUpdate); - - router.route('/:username/grouplist') - /** put /api/v2/users/:username/grouplist */ - .put(token.checkNotApplication, param.validate(userInputSchema.userGrouplistUpdateInputSchema), userController.updateUserGroupList); - - router.route('/:username/group') - /** put /api/v2/users/:username/group */ - .put(token.checkNotApplication, param.validate(userInputSchema.addOrRemoveGroupInputSchema), userController.addGroupIntoUserGrouplist); - - router.route('/:username/group') - /** delete /api/v2/users/:username/group */ - .delete(token.checkNotApplication, param.validate(userInputSchema.addOrRemoveGroupInputSchema), userController.removeGroupFromUserGrouplist); + router + .route('/') + /** Create /api/v2/users */ + .post( + token.checkNotApplication, + param.validate(userInputSchema.userCreateInputSchema), + userController.createUser, + ); + + router + .route('/') + /** Put /api/v2/users */ + .put( + token.checkNotApplication, + param.validate(userInputSchema.basicAdminUserUpdateInputSchema), + userController.basicAdminUserUpdate, + ); + + router + .route('/me') + /** Put /api/v2/users */ + .put( + token.checkNotApplication, + param.validate(userInputSchema.basicUserUpdateInputSchema), + userController.basicUserUpdate, + ); + + router + .route('/:username/grouplist') + /** put /api/v2/users/:username/grouplist */ + .put( + token.checkNotApplication, + param.validate(userInputSchema.userGrouplistUpdateInputSchema), + userController.updateUserGroupList, + ); + + router + .route('/:username/group') + /** put /api/v2/users/:username/group */ + .put( + token.checkNotApplication, + param.validate(userInputSchema.addOrRemoveGroupInputSchema), + userController.addGroupIntoUserGrouplist, + ); + + router + .route('/:username/group') + /** delete /api/v2/users/:username/group */ + .delete( + token.checkNotApplication, + param.validate(userInputSchema.addOrRemoveGroupInputSchema), + userController.removeGroupFromUserGrouplist, + ); /** Legacy API and will be deprecated in the future. Please use put /api/v2/users */ - router.route('/:username/virtualcluster') - /** Update /api/v2/users/:username/virtualcluster */ - .put(token.checkNotApplication, param.validate(userInputSchema.userVirtualClusterUpdateInputSchema), userController.updateUserVirtualCluster); + router + .route('/:username/virtualcluster') + /** Update /api/v2/users/:username/virtualcluster */ + .put( + token.checkNotApplication, + param.validate(userInputSchema.userVirtualClusterUpdateInputSchema), + userController.updateUserVirtualCluster, + ); /** Legacy API and will be deprecated in the future. Please use put /api/v2/users */ - router.route('/:username/password') - /** Update /api/v2/users/:username/password */ - .put(token.checkNotApplication, param.validate(userInputSchema.userPasswordUpdateInputSchema), userController.updateUserPassword); + router + .route('/:username/password') + /** Update /api/v2/users/:username/password */ + .put( + token.checkNotApplication, + param.validate(userInputSchema.userPasswordUpdateInputSchema), + userController.updateUserPassword, + ); /** Legacy API and will be deprecated in the future. Please use put /api/v2/users */ - router.route('/:username/email') - /** Update /api/v2/users/:username/email */ - .put(token.checkNotApplication, param.validate(userInputSchema.userEmailUpdateInputSchema), userController.updateUserEmail); + router + .route('/:username/email') + /** Update /api/v2/users/:username/email */ + .put( + token.checkNotApplication, + param.validate(userInputSchema.userEmailUpdateInputSchema), + userController.updateUserEmail, + ); /** Legacy API and will be deprecated in the future. Please use put /api/v2/users */ - router.route('/:username/admin') - /** Update /api/v2/users/:username/admin */ - .put(token.checkNotApplication, param.validate(userInputSchema.userAdminPermissionUpdateInputSchema), userController.updateUserAdminPermission); + router + .route('/:username/admin') + /** Update /api/v2/users/:username/admin */ + .put( + token.checkNotApplication, + param.validate(userInputSchema.userAdminPermissionUpdateInputSchema), + userController.updateUserAdminPermission, + ); } else { - router.route('/') - /** Put /api/v2/users */ - .put(token.checkNotApplication, param.validate(userInputSchema.oidcAdminUserUpdateInputSchema), userController.oidcUserUpdate); + router + .route('/') + /** Put /api/v2/users */ + .put( + token.checkNotApplication, + param.validate(userInputSchema.oidcAdminUserUpdateInputSchema), + userController.oidcUserUpdate, + ); } module.exports = router; diff --git a/src/rest-server/src/routes/v2/virtual-cluster.js b/src/rest-server/src/routes/v2/virtual-cluster.js index 4ddf1b826e..56745dee9b 100644 --- a/src/rest-server/src/routes/v2/virtual-cluster.js +++ b/src/rest-server/src/routes/v2/virtual-cluster.js @@ -24,21 +24,32 @@ const vcConfig = require('@pai/config/vc'); const router = new express.Router(); -router.route('/') +router + .route('/') /** GET /api/v2/virtual-clusters - Return cluster virtual cluster info */ .get(token.check, controller.list); -router.route('/:virtualClusterName') +router + .route('/:virtualClusterName') /** GET /api/v2/virtual-clusters/:virtualClusterName - Get virtual cluster */ .get(token.check, controller.get) /** PUT /api/v2/virtual-clusters/:virtualClusterName - Create a virtual cluster */ - .put(token.checkNotApplication, param.validate(vcConfig.vcCreateInputSchema), controller.update) + .put( + token.checkNotApplication, + param.validate(vcConfig.vcCreateInputSchema), + controller.update, + ) /** DELETE /api/v2/virtual-clusters/:virtualClusterName - Remove a virtual cluster */ .delete(token.checkNotApplication, controller.remove); -router.route('/:virtualClusterName/status') +router + .route('/:virtualClusterName/status') /** PUT /api/v2/virtual-clusters/:virtualClusterName/status - Change virtual cluster status (running or stopped) */ - .put(token.checkNotApplication, param.validate(vcConfig.vcStatusPutInputSchema), controller.updateStatus); + .put( + token.checkNotApplication, + param.validate(vcConfig.vcStatusPutInputSchema), + controller.updateStatus, + ); router.param('virtualClusterName', controller.validate); diff --git a/src/rest-server/src/server.js b/src/rest-server/src/server.js index 526ad6a57c..30ac41506f 100644 --- a/src/rest-server/src/server.js +++ b/src/rest-server/src/server.js @@ -15,7 +15,6 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - /** * Implementation of RESTful API server. * Init and start server instance. @@ -25,7 +24,7 @@ require('module-alias/register'); const config = require('@pai/config'); const logger = require('@pai/config/logger'); -const {initPromise} = require('@pai/config/kubernetes'); +const { initPromise } = require('@pai/config/kubernetes'); module.exports = initPromise.then(() => { const app = require('@pai/config/express'); diff --git a/src/rest-server/src/utils/env.js b/src/rest-server/src/utils/env.js index df47dbe132..c80d280507 100644 --- a/src/rest-server/src/utils/env.js +++ b/src/rest-server/src/utils/env.js @@ -15,7 +15,6 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - module.exports = { exitSpecPath: 'PAI_REST_SERVER_EXIT_SPEC_PATH', }; diff --git a/src/rest-server/src/utils/error.js b/src/rest-server/src/utils/error.js index 5ca4b301bb..4b5e556a11 100644 --- a/src/rest-server/src/utils/error.js +++ b/src/rest-server/src/utils/error.js @@ -18,29 +18,29 @@ const httpErrors = require('http-errors'); const statuses = require('statuses'); -const createError = exports = module.exports = (status, code, message) => { - if (typeof message === 'object') { - if ('message' in message) { - message = message.message; - } else { - message = JSON.stringify(message); - } - } - const error = httpErrors(statuses(status), message, {code}); - Error.captureStackTrace(error, createError); - return error; -}; - -const createUnknownError = exports.unknown = (cause) => { - if (cause instanceof httpErrors.HttpError) { - return cause; - } - const message = cause instanceof Error ? cause.message : String(cause); - const error = createError('Internal Server Error', 'UnknownError', message); - if (cause instanceof Error) { - error.stack = cause.stack; +const createError = (exports = module.exports = (status, code, message) => { + if (typeof message === 'object') { + if ('message' in message) { + message = message.message; } else { - Error.captureStackTrace(error, createUnknownError); + message = JSON.stringify(message); } - return error; -}; + } + const error = httpErrors(statuses(status), message, { code }); + Error.captureStackTrace(error, createError); + return error; +}); + +const createUnknownError = (exports.unknown = (cause) => { + if (cause instanceof httpErrors.HttpError) { + return cause; + } + const message = cause instanceof Error ? cause.message : String(cause); + const error = createError('Internal Server Error', 'UnknownError', message); + if (cause instanceof Error) { + error.stack = cause.stack; + } else { + Error.captureStackTrace(error, createUnknownError); + } + return error; +}); diff --git a/src/rest-server/src/utils/frameworkConverter.js b/src/rest-server/src/utils/frameworkConverter.js index c92935a7b9..ef464e2f35 100644 --- a/src/rest-server/src/utils/frameworkConverter.js +++ b/src/rest-server/src/utils/frameworkConverter.js @@ -24,7 +24,7 @@ const generateSpecMap = () => { exitSpecPath = '/k8s-job-exit-spec-configuration/k8s-job-exit-spec.yaml'; } const exitSpecList = yaml.safeLoad(fs.readFileSync(exitSpecPath)); - let exitSpecMap = {}; + const exitSpecMap = {}; exitSpecList.forEach((val) => { exitSpecMap[val.code] = val; }); @@ -158,7 +158,6 @@ const convertState = (state, exitCode) => { } }; - const generateExitSpec = (code) => { const exitSpecMap = generateSpecMap(); if (!_.isNil(code)) { @@ -205,7 +204,9 @@ const convertToJobAttempt = async (framework) => { framework.metadata.annotations, ); const frameworkName = framework.metadata.name; - const logPathInfix = framework.metadata.annotations.logPathInfix ? framework.metadata.annotations.logPathInfix : jobName; + const logPathInfix = framework.metadata.annotations.logPathInfix + ? framework.metadata.annotations.logPathInfix + : jobName; const uid = framework.metadata.uid; const userName = framework.metadata.labels ? framework.metadata.labels.userName @@ -218,9 +219,13 @@ const convertToJobAttempt = async (framework) => { const originState = framework.status.state; const maxAttemptCount = framework.spec.retryPolicy.maxRetryCount + 1; const attemptIndex = framework.status.attemptStatus.id; - const jobStartedTime = new Date(framework.metadata.creationTimestamp).getTime(); - const attemptStartedTime = new Date(framework.status.attemptStatus.startTime).getTime() || null; - const attemptCompletedTime = new Date(framework.status.attemptStatus.completionTime).getTime() || null; + const jobStartedTime = new Date( + framework.metadata.creationTimestamp, + ).getTime(); + const attemptStartedTime = + new Date(framework.status.attemptStatus.startTime).getTime() || null; + const attemptCompletedTime = + new Date(framework.status.attemptStatus.completionTime).getTime() || null; const totalGpuNumber = framework.metadata.annotations ? parseInt(framework.metadata.annotations.totalGpuNumber) : 0; @@ -265,12 +270,13 @@ const convertToJobAttempt = async (framework) => { ); } - let taskRoles = {}; + const taskRoles = {}; const exitCode = completionStatus ? completionStatus.code : null; const exitPhrase = completionStatus ? completionStatus.phrase : null; const exitType = completionStatus ? completionStatus.type.name : null; - for (let taskRoleStatus of framework.status.attemptStatus.taskRoleStatuses) { + for (const taskRoleStatus of framework.status.attemptStatus + .taskRoleStatuses) { taskRoles[taskRoleStatus.name] = { taskRoleStatus: { name: taskRoleStatus.name, @@ -327,12 +333,11 @@ const convertTaskDetail = async ( // get container gpus let containerGpus = null; try { - const response = await k8sModel.getClient().get( - launcherConfig.podPath(taskStatus.attemptStatus.podName), - { + const response = await k8sModel + .getClient() + .get(launcherConfig.podPath(taskStatus.attemptStatus.podName), { headers: launcherConfig.requestHeaders, - } - ); + }); const pod = response.data; if (launcherConfig.enabledHived) { const isolation = diff --git a/src/rest-server/src/utils/k8sUtils.js b/src/rest-server/src/utils/k8sUtils.js index e17c2000e0..d662529755 100644 --- a/src/rest-server/src/utils/k8sUtils.js +++ b/src/rest-server/src/utils/k8sUtils.js @@ -24,18 +24,18 @@ const convertMemoryMb = (memoryStr) => { let memoryMb = atoi(memoryStr); switch (memoryStr.replace(/[0-9]/g, '')) { case 'Ti': - memoryMb *= (1 << 20); + memoryMb *= 1 << 20; break; case 'Gi': - memoryMb *= (1 << 10); + memoryMb *= 1 << 10; break; case 'Mi': break; case 'Ki': - memoryMb /= (1 << 10); + memoryMb /= 1 << 10; break; default: - memoryMb /= (1 << 20); + memoryMb /= 1 << 20; } return atoi(memoryMb); }; diff --git a/src/rest-server/src/utils/manager/group/adapter/externalUtil.js b/src/rest-server/src/utils/manager/group/adapter/externalUtil.js index 9f3abdbcb9..a217abdc89 100644 --- a/src/rest-server/src/utils/manager/group/adapter/externalUtil.js +++ b/src/rest-server/src/utils/manager/group/adapter/externalUtil.js @@ -27,4 +27,4 @@ const getStorageObject = (type) => { } }; -module.exports = {getStorageObject}; +module.exports = { getStorageObject }; diff --git a/src/rest-server/src/utils/manager/group/adapter/msGraphAdapter.js b/src/rest-server/src/utils/manager/group/adapter/msGraphAdapter.js index 4189dab597..247810eb70 100644 --- a/src/rest-server/src/utils/manager/group/adapter/msGraphAdapter.js +++ b/src/rest-server/src/utils/manager/group/adapter/msGraphAdapter.js @@ -20,42 +20,38 @@ const axios = require('axios'); function initConfig(msGraphUrl, accessToken) { return { - 'msGraphAPI': `${msGraphUrl}v1.0/me/transitiveMemberOf`, - 'Authorization': `Bearer ${accessToken}`, + msGraphAPI: `${msGraphUrl}v1.0/me/transitiveMemberOf`, + Authorization: `Bearer ${accessToken}`, }; } async function getUserGroupList(username, config) { - try { - let responseData = []; - let requestUrl = config.msGraphAPI; - // eslint-disable-next-line no-constant-condition - while (true) { - let response = await axios.get(requestUrl, { - headers: { - 'Accept': 'application/json', - 'Authorization': config.Authorization, - }, - }); - responseData.push(response['data']['value']); - if ('@odata.nextLink' in response['data']) { - requestUrl = response['data']['@odata.nextLink']; - } else { - break; - } + const responseData = []; + let requestUrl = config.msGraphAPI; + // eslint-disable-next-line no-constant-condition + while (true) { + const response = await axios.get(requestUrl, { + headers: { + Accept: 'application/json', + Authorization: config.Authorization, + }, + }); + responseData.push(response.data.value); + if ('@odata.nextLink' in response.data) { + requestUrl = response.data['@odata.nextLink']; + } else { + break; } - let groupList = []; - for (const dataBlock of responseData) { - for (const groupItem of dataBlock) { - if (groupItem.mailNickname) { - groupList.push(groupItem.mailNickname); - } + } + const groupList = []; + for (const dataBlock of responseData) { + for (const groupItem of dataBlock) { + if (groupItem.mailNickname) { + groupList.push(groupItem.mailNickname); } } - return groupList; - } catch (error) { - throw error; } + return groupList; } module.exports = { diff --git a/src/rest-server/src/utils/manager/group/adapter/winbindAdapter.js b/src/rest-server/src/utils/manager/group/adapter/winbindAdapter.js index 55edba3c8d..236dfc90e3 100644 --- a/src/rest-server/src/utils/manager/group/adapter/winbindAdapter.js +++ b/src/rest-server/src/utils/manager/group/adapter/winbindAdapter.js @@ -20,25 +20,21 @@ const axios = require('axios'); function initConfig(winbindServerUrl) { return { - 'requestConfig': { - 'baseURL': `${winbindServerUrl}/`, - 'maxRedirects': 0, + requestConfig: { + baseURL: `${winbindServerUrl}/`, + maxRedirects: 0, }, }; } async function getUserGroupList(username, config) { - try { - const request = axios.create(config.requestConfig); - const response = await request.get(`GetUserId?userName=${username}`, { - headers: { - 'Accept': 'application/json', - }, - }); - return response['data']['groups']; - } catch (error) { - throw error; - } + const request = axios.create(config.requestConfig); + const response = await request.get(`GetUserId?userName=${username}`, { + headers: { + Accept: 'application/json', + }, + }); + return response.data.groups; } module.exports = { diff --git a/src/rest-server/src/utils/manager/group/crudK8sSecret.js b/src/rest-server/src/utils/manager/group/crudK8sSecret.js index c1582ff75f..ec2a11e1c9 100644 --- a/src/rest-server/src/utils/manager/group/crudK8sSecret.js +++ b/src/rest-server/src/utils/manager/group/crudK8sSecret.js @@ -41,15 +41,20 @@ async function read(key) { const hexKey = Buffer.from(key).toString('hex'); const response = await request.get(`${GROUP_NAMESPACE}/secrets/${hexKey}`, { headers: { - 'Accept': 'application/json', + Accept: 'application/json', }, }); - let groupData = response['data']; - let groupInstance = Group.createGroup({ - 'groupname': Buffer.from(groupData['data']['groupname'], 'base64').toString(), - 'description': Buffer.from(groupData['data']['description'], 'base64').toString(), - 'externalName': Buffer.from(groupData['data']['externalName'], 'base64').toString(), - 'extension': JSON.parse(Buffer.from(groupData['data']['extension'], 'base64').toString()), + const groupData = response.data; + const groupInstance = Group.createGroup({ + groupname: Buffer.from(groupData.data.groupname, 'base64').toString(), + description: Buffer.from(groupData.data.description, 'base64').toString(), + externalName: Buffer.from( + groupData.data.externalName, + 'base64', + ).toString(), + extension: JSON.parse( + Buffer.from(groupData.data.extension, 'base64').toString(), + ), }); return groupInstance; } catch (error) { @@ -71,22 +76,29 @@ async function readAll() { const request = k8sModel.getClient('/api/v1/namespaces'); const response = await request.get(`${GROUP_NAMESPACE}/secrets`, { headers: { - 'Accept': 'application/json', + Accept: 'application/json', }, }); - let allGroupInstance = []; - let groupData = response['data']; - for (const item of groupData['items']) { + const allGroupInstance = []; + const groupData = response.data; + for (const item of groupData.items) { try { - let groupInstance = Group.createGroup({ - 'groupname': Buffer.from(item['data']['groupname'], 'base64').toString(), - 'description': Buffer.from(item['data']['description'], 'base64').toString(), - 'externalName': Buffer.from(item['data']['externalName'], 'base64').toString(), - 'extension': JSON.parse(Buffer.from(item['data']['extension'], 'base64').toString()), + const groupInstance = Group.createGroup({ + groupname: Buffer.from(item.data.groupname, 'base64').toString(), + description: Buffer.from(item.data.description, 'base64').toString(), + externalName: Buffer.from( + item.data.externalName, + 'base64', + ).toString(), + extension: JSON.parse( + Buffer.from(item.data.extension, 'base64').toString(), + ), }); allGroupInstance.push(groupInstance); } catch (error) { - logger.debug(`secret ${item['metadata']['name']} is filtered in ${GROUP_NAMESPACE} due to group schema`); + logger.debug( + `secret ${item.metadata.name} is filtered in ${GROUP_NAMESPACE} due to group schema`, + ); } } return allGroupInstance; @@ -110,19 +122,23 @@ async function create(key, value) { try { const request = k8sModel.getClient('/api/v1/namespaces'); const hexKey = key ? Buffer.from(key).toString('hex') : ''; - let groupInstance = Group.createGroup({ - 'groupname': value['groupname'], - 'description': value['description'], - 'externalName': value['externalName'], - 'extension': value['extension'], + const groupInstance = Group.createGroup({ + groupname: value.groupname, + description: value.description, + externalName: value.externalName, + extension: value.extension, }); - let groupData = { - 'metadata': {'name': hexKey}, - 'data': { - 'groupname': Buffer.from(groupInstance['groupname']).toString('base64'), - 'description': Buffer.from(groupInstance['description']).toString('base64'), - 'externalName': Buffer.from(groupInstance['externalName']).toString('base64'), - 'extension': Buffer.from(JSON.stringify(groupInstance['extension'])).toString('base64'), + const groupData = { + metadata: { name: hexKey }, + data: { + groupname: Buffer.from(groupInstance.groupname).toString('base64'), + description: Buffer.from(groupInstance.description).toString('base64'), + externalName: Buffer.from(groupInstance.externalName).toString( + 'base64', + ), + extension: Buffer.from( + JSON.stringify(groupInstance.extension), + ).toString('base64'), }, }; return await request.post(`${GROUP_NAMESPACE}/secrets`, groupData); @@ -146,19 +162,23 @@ async function update(key, value) { try { const request = k8sModel.getClient('/api/v1/namespaces'); const hexKey = Buffer.from(key).toString('hex'); - let groupInstance = Group.createGroup({ - 'groupname': value['groupname'], - 'description': value['description'], - 'externalName': value['externalName'], - 'extension': value['extension'], + const groupInstance = Group.createGroup({ + groupname: value.groupname, + description: value.description, + externalName: value.externalName, + extension: value.extension, }); - let groupData = { - 'metadata': {'name': hexKey}, - 'data': { - 'groupname': Buffer.from(groupInstance['groupname']).toString('base64'), - 'description': Buffer.from(groupInstance['description']).toString('base64'), - 'externalName': Buffer.from(groupInstance['externalName']).toString('base64'), - 'extension': Buffer.from(JSON.stringify(groupInstance['extension'])).toString('base64'), + const groupData = { + metadata: { name: hexKey }, + data: { + groupname: Buffer.from(groupInstance.groupname).toString('base64'), + description: Buffer.from(groupInstance.description).toString('base64'), + externalName: Buffer.from(groupInstance.externalName).toString( + 'base64', + ), + extension: Buffer.from( + JSON.stringify(groupInstance.extension), + ).toString('base64'), }, }; return await request.put(`${GROUP_NAMESPACE}/secrets/${hexKey}`, groupData); @@ -184,7 +204,7 @@ async function remove(key) { return await request.delete(`${GROUP_NAMESPACE}/secrets/${hexKey}`, { headers: { 'Content-Type': 'application/json', - 'Accept': 'application/json', + Accept: 'application/json', }, }); } catch (error) { @@ -196,4 +216,4 @@ async function remove(key) { } } -module.exports = {create, read, readAll, update, remove}; +module.exports = { create, read, readAll, update, remove }; diff --git a/src/rest-server/src/utils/manager/group/crudUtil.js b/src/rest-server/src/utils/manager/group/crudUtil.js index 4e0fa7139f..1b01c5848b 100644 --- a/src/rest-server/src/utils/manager/group/crudUtil.js +++ b/src/rest-server/src/utils/manager/group/crudUtil.js @@ -24,4 +24,4 @@ const getStorageObject = (type) => { } }; -module.exports = {getStorageObject}; +module.exports = { getStorageObject }; diff --git a/src/rest-server/src/utils/manager/group/group.js b/src/rest-server/src/utils/manager/group/group.js index 200deb86f4..deba44c2ce 100644 --- a/src/rest-server/src/utils/manager/group/group.js +++ b/src/rest-server/src/utils/manager/group/group.js @@ -17,37 +17,35 @@ const Joi = require('joi'); -const groupSchema = Joi.object().keys({ - groupname: Joi.string() - .regex(/^[A-Za-z0-9_]+$/, 'groupname') - .required(), - description: Joi.string() - .empty('') - .default(''), - externalName: Joi.string() - .empty('') - .default(''), - extension: Joi.object() - .pattern(/\w+/, Joi.required()) - .keys({ - acls: Joi.object() - .pattern(/\w+/, Joi.required()) - .keys({ - admin: Joi.boolean().default(false), - virtualClusters: Joi.array().items(Joi.string()).default([]), - }) - .default(), - }) - .pattern(/\w+/, Joi.required()) - .required(), -}).required(); +const groupSchema = Joi.object() + .keys({ + groupname: Joi.string() + .regex(/^[A-Za-z0-9_]+$/, 'groupname') + .required(), + description: Joi.string().empty('').default(''), + externalName: Joi.string().empty('').default(''), + extension: Joi.object() + .pattern(/\w+/, Joi.required()) + .keys({ + acls: Joi.object() + .pattern(/\w+/, Joi.required()) + .keys({ + admin: Joi.boolean().default(false), + virtualClusters: Joi.array().items(Joi.string()).default([]), + }) + .default(), + }) + .pattern(/\w+/, Joi.required()) + .required(), + }) + .required(); function createGroup(value) { const res = groupSchema.validate(value); - if (res['error']) { - throw new Error(`Group schema error\n${res['error']}`); + if (res.error) { + throw new Error(`Group schema error\n${res.error}`); } - return res['value']; + return res.value; } -module.exports = {createGroup}; +module.exports = { createGroup }; diff --git a/src/rest-server/src/utils/manager/user/crudK8sSecret.js b/src/rest-server/src/utils/manager/user/crudK8sSecret.js index 94ea5e8300..ac626f3080 100644 --- a/src/rest-server/src/utils/manager/user/crudK8sSecret.js +++ b/src/rest-server/src/utils/manager/user/crudK8sSecret.js @@ -40,18 +40,25 @@ async function read(key) { try { const request = k8sModel.getClient('/api/v1/namespaces/'); const hexKey = Buffer.from(key).toString('hex'); - const response = await request.get(`${USER_NAMESPACE}/secrets/${hexKey}`.toString(), { - headers: { - 'Accept': 'application/json', + const response = await request.get( + `${USER_NAMESPACE}/secrets/${hexKey}`.toString(), + { + headers: { + Accept: 'application/json', + }, }, - }); - let userData = response['data']; - let userInstance = User.createUser({ - 'username': Buffer.from(userData['data']['username'], 'base64').toString(), - 'password': Buffer.from(userData['data']['password'], 'base64').toString(), - 'grouplist': JSON.parse(Buffer.from(userData['data']['grouplist'], 'base64').toString()), - 'email': Buffer.from(userData['data']['email'], 'base64').toString(), - 'extension': JSON.parse(Buffer.from(userData['data']['extension'], 'base64').toString()), + ); + const userData = response.data; + const userInstance = User.createUser({ + username: Buffer.from(userData.data.username, 'base64').toString(), + password: Buffer.from(userData.data.password, 'base64').toString(), + grouplist: JSON.parse( + Buffer.from(userData.data.grouplist, 'base64').toString(), + ), + email: Buffer.from(userData.data.email, 'base64').toString(), + extension: JSON.parse( + Buffer.from(userData.data.extension, 'base64').toString(), + ), }); return userInstance; } catch (error) { @@ -73,23 +80,29 @@ async function readAll() { const request = k8sModel.getClient('/api/v1/namespaces/'); const response = await request.get(`${USER_NAMESPACE}/secrets`.toString(), { headers: { - 'Accept': 'application/json', + Accept: 'application/json', }, }); - let allUserInstance = []; - let userData = response['data']; - for (const item of userData['items']) { + const allUserInstance = []; + const userData = response.data; + for (const item of userData.items) { try { - let userInstance = User.createUser({ - 'username': Buffer.from(item['data']['username'], 'base64').toString(), - 'password': Buffer.from(item['data']['password'], 'base64').toString(), - 'grouplist': JSON.parse(Buffer.from(item['data']['grouplist'], 'base64').toString()), - 'email': Buffer.from(item['data']['email'], 'base64').toString(), - 'extension': JSON.parse(Buffer.from(item['data']['extension'], 'base64').toString()), + const userInstance = User.createUser({ + username: Buffer.from(item.data.username, 'base64').toString(), + password: Buffer.from(item.data.password, 'base64').toString(), + grouplist: JSON.parse( + Buffer.from(item.data.grouplist, 'base64').toString(), + ), + email: Buffer.from(item.data.email, 'base64').toString(), + extension: JSON.parse( + Buffer.from(item.data.extension, 'base64').toString(), + ), }); allUserInstance.push(userInstance); } catch (error) { - logger.debug(`secret ${item['metadata']['name']} is filtered in ${USER_NAMESPACE} due to user schema`); + logger.debug( + `secret ${item.metadata.name} is filtered in ${USER_NAMESPACE} due to user schema`, + ); } } return allUserInstance; @@ -113,27 +126,29 @@ async function create(key, value) { try { const request = k8sModel.getClient('/api/v1/namespaces/'); const hexKey = Buffer.from(key).toString('hex'); - let userInstance = User.createUser( - { - 'username': value['username'], - 'password': value['password'], - 'grouplist': value['grouplist'], - 'email': value['email'], - 'extension': value['extension'], - } - ); + const userInstance = User.createUser({ + username: value.username, + password: value.password, + grouplist: value.grouplist, + email: value.email, + extension: value.extension, + }); await User.encryptUserPassword(userInstance); - let userData = { - 'metadata': {'name': hexKey}, - 'data': { - 'username': Buffer.from(userInstance['username']).toString('base64'), - 'password': Buffer.from(userInstance['password']).toString('base64'), - 'grouplist': Buffer.from(JSON.stringify(userInstance['grouplist'])).toString('base64'), - 'email': Buffer.from(userInstance['email']).toString('base64'), - 'extension': Buffer.from(JSON.stringify(userInstance['extension'])).toString('base64'), + const userData = { + metadata: { name: hexKey }, + data: { + username: Buffer.from(userInstance.username).toString('base64'), + password: Buffer.from(userInstance.password).toString('base64'), + grouplist: Buffer.from(JSON.stringify(userInstance.grouplist)).toString( + 'base64', + ), + email: Buffer.from(userInstance.email).toString('base64'), + extension: Buffer.from(JSON.stringify(userInstance.extension)).toString( + 'base64', + ), }, }; - let response = await request.post(`${USER_NAMESPACE}/secrets`, userData); + const response = await request.post(`${USER_NAMESPACE}/secrets`, userData); return response; } catch (error) { if (error.response) { @@ -156,29 +171,34 @@ async function update(key, value, updatePassword = false) { try { const request = k8sModel.getClient('/api/v1/namespaces/'); const hexKey = Buffer.from(key).toString('hex'); - let userInstance = User.createUser( - { - 'username': value['username'], - 'password': value['password'], - 'grouplist': value['grouplist'], - 'email': value['email'], - 'extension': value['extension'], - } - ); + const userInstance = User.createUser({ + username: value.username, + password: value.password, + grouplist: value.grouplist, + email: value.email, + extension: value.extension, + }); if (updatePassword) { await User.encryptUserPassword(userInstance); } - let userData = { - 'metadata': {'name': hexKey}, - 'data': { - 'username': Buffer.from(userInstance['username']).toString('base64'), - 'password': Buffer.from(userInstance['password']).toString('base64'), - 'grouplist': Buffer.from(JSON.stringify(userInstance['grouplist'])).toString('base64'), - 'email': Buffer.from(userInstance['email']).toString('base64'), - 'extension': Buffer.from(JSON.stringify(userInstance['extension'])).toString('base64'), + const userData = { + metadata: { name: hexKey }, + data: { + username: Buffer.from(userInstance.username).toString('base64'), + password: Buffer.from(userInstance.password).toString('base64'), + grouplist: Buffer.from(JSON.stringify(userInstance.grouplist)).toString( + 'base64', + ), + email: Buffer.from(userInstance.email).toString('base64'), + extension: Buffer.from(JSON.stringify(userInstance.extension)).toString( + 'base64', + ), }, }; - let response = await request.put(`${USER_NAMESPACE}/secrets/${hexKey}`, userData); + const response = await request.put( + `${USER_NAMESPACE}/secrets/${hexKey}`, + userData, + ); return response; } catch (error) { if (error.response) { @@ -202,7 +222,7 @@ async function remove(key) { return await request.delete(`${USER_NAMESPACE}/secrets/${hexKey}`, { headers: { 'Content-Type': 'application/json', - 'Accept': 'application/json', + Accept: 'application/json', }, }); } catch (error) { @@ -214,4 +234,4 @@ async function remove(key) { } } -module.exports = {create, read, readAll, update, remove}; +module.exports = { create, read, readAll, update, remove }; diff --git a/src/rest-server/src/utils/manager/user/crudUtil.js b/src/rest-server/src/utils/manager/user/crudUtil.js index 4e0fa7139f..1b01c5848b 100644 --- a/src/rest-server/src/utils/manager/user/crudUtil.js +++ b/src/rest-server/src/utils/manager/user/crudUtil.js @@ -24,4 +24,4 @@ const getStorageObject = (type) => { } }; -module.exports = {getStorageObject}; +module.exports = { getStorageObject }; diff --git a/src/rest-server/src/utils/manager/user/user.js b/src/rest-server/src/utils/manager/user/user.js index a8ddf3693f..86c6c60913 100644 --- a/src/rest-server/src/utils/manager/user/user.js +++ b/src/rest-server/src/utils/manager/user/user.js @@ -18,49 +18,43 @@ const Joi = require('joi'); const crypto = require('crypto'); -const userSchema = Joi.object().keys({ - username: Joi.string() - .regex(/^[\w.-]+$/, 'username') - .required(), - email: Joi.string() - .email() - .empty('') - .default(''), - grouplist: Joi - .array() - .items(Joi.string()) - .required(), - password: Joi.string() - .empty('') - .default(''), - extension: Joi.object().pattern(/\w+/, Joi.required()).required(), -}).required(); +const userSchema = Joi.object() + .keys({ + username: Joi.string() + .regex(/^[\w.-]+$/, 'username') + .required(), + email: Joi.string().email().empty('').default(''), + grouplist: Joi.array().items(Joi.string()).required(), + password: Joi.string().empty('').default(''), + extension: Joi.object().pattern(/\w+/, Joi.required()).required(), + }) + .required(); function userValidate(userValue) { const res = userSchema.validate(userValue); - if (res['error']) { - throw new Error(`User schema error\n${res['error']}`); + if (res.error) { + throw new Error(`User schema error\n${res.error}`); } - return res['value']; + return res.value; } function encrypt(username, password) { const iterations = 10000; const keylen = 64; const salt = crypto.createHash('md5').update(username).digest('hex'); - return new Promise( (res, rej) => { + return new Promise((resolve, reject) => { crypto.pbkdf2(password, salt, iterations, keylen, 'sha512', (err, key) => { - err ? rej(err) : res(key.toString('hex')); + err ? reject(err) : resolve(key.toString('hex')); }); }); } async function encryptPassword(userValue) { - userValue['password'] = await encrypt(userValue['username'], userValue['password']); + userValue.password = await encrypt(userValue.username, userValue.password); } async function encryptUserPassword(userValue) { await encryptPassword(userValue); } -module.exports = {encrypt, encryptUserPassword, createUser: userValidate}; +module.exports = { encrypt, encryptUserPassword, createUser: userValidate }; diff --git a/src/rest-server/src/utils/protocolSecret.js b/src/rest-server/src/utils/protocolSecret.js index 2c92715012..3cf769b905 100644 --- a/src/rest-server/src/utils/protocolSecret.js +++ b/src/rest-server/src/utils/protocolSecret.js @@ -15,7 +15,6 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - const mask = (protocolYAML) => { let maskYAML = protocolYAML + '\nZ'; maskYAML = maskYAML.replace(/(^secrets:)[^]*?(^\w)/m, '$1 "******"\n$2'); diff --git a/src/rest-server/yarn.lock b/src/rest-server/yarn.lock index 2d30a85ceb..95ac44c626 100644 --- a/src/rest-server/yarn.lock +++ b/src/rest-server/yarn.lock @@ -126,6 +126,11 @@ resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.5.tgz#f8da153ebbe30babb0adc9a528b9ad32be3175a2" integrity sha512-YvbLiIc0DbbhiANrfVObdkLEHJksQZVq0Uvfg550SRAKVYaEJy+V70j65BVe2WNp6E3HtKsUczeijHFCjba3og== +"@types/color-name@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" + integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== + "@types/cookiejar@*": version "2.1.1" resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.1.tgz#90b68446364baf9efd8e8349bb36bd3852b75b80" @@ -136,6 +141,11 @@ resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.1.tgz#5c6f4a1eabca84792fbd916f0cb40847f123c656" integrity sha512-SGGAhXLHDx+PK4YLNcNGa6goPf9XRWQNAUUbffkwVGGXIxmDKWyGGL4inzq2sPmExu431Ekb9aEMn9BkPqEYFA== +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + "@types/node@*": version "12.12.14" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.14.tgz#1c1d6e3c75dba466e0326948d56e8bd72a1903d2" @@ -188,19 +198,15 @@ accepts@~1.3.5: mime-types "~2.1.18" negotiator "0.6.1" -acorn-jsx@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" - dependencies: - acorn "^3.0.4" - -acorn@^3.0.4: - version "3.3.0" - resolved "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" +acorn-jsx@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe" + integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ== -acorn@^5.5.0: - version "5.7.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" +acorn@^7.3.1: + version "7.4.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c" + integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w== aggregate-error@^1.0.0: version "1.0.0" @@ -210,11 +216,6 @@ aggregate-error@^1.0.0: clean-stack "^1.0.0" indent-string "^3.0.0" -ajv-keywords@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" - integrity sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I= - ajv-merge-patch@~4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/ajv-merge-patch/-/ajv-merge-patch-4.1.0.tgz#cd580e5860ac53431d6aa901fa3d5e2eb2b74a6c" @@ -223,7 +224,7 @@ ajv-merge-patch@~4.1.0: fast-json-patch "^2.0.6" json-merge-patch "^0.2.3" -ajv@^5.2.3, ajv@^5.3.0: +ajv@^5.3.0: version "5.5.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" dependencies: @@ -242,26 +243,30 @@ ajv@^6.10.0: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ansi-escapes@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30" - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" +ajv@^6.10.2: + version "6.12.4" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.4.tgz#0614facc4522127fa713445c6bfd3ebd376e2234" + integrity sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" +ansi-colors@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== ansi-regex@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" @@ -269,6 +274,14 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" +ansi-styles@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" + integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + dependencies: + "@types/color-name" "^1.1.1" + color-convert "^2.0.1" + any-promise@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" @@ -295,19 +308,22 @@ array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" -array-union@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" +array-includes@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348" + integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ== dependencies: - array-uniq "^1.0.1" + define-properties "^1.1.3" + es-abstract "^1.17.0" + is-string "^1.0.5" -array-uniq@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" - -arrify@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" +array.prototype.flat@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b" + integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" asn1@~0.2.3: version "0.2.4" @@ -323,6 +339,11 @@ assertion-error@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + async-limiter@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" @@ -362,14 +383,6 @@ axios@^0.18.1: follow-redirects "1.5.10" is-buffer "^2.0.2" -babel-code-frame@^6.22.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" - dependencies: - chalk "^1.1.3" - esutils "^2.0.2" - js-tokens "^3.0.2" - balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -426,10 +439,6 @@ buffer-equal-constant-time@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" -buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - buffer-writer@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" @@ -471,15 +480,10 @@ caching-transform@^3.0.2: package-hash "^3.0.0" write-file-atomic "^2.4.2" -caller-path@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" - dependencies: - callsites "^0.2.0" - -callsites@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== camelcase@^5.0.0: version "5.3.1" @@ -529,17 +533,7 @@ chai@~4.1.2: pathval "^1.0.0" type-detect "^4.0.0" -chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - -chalk@^2.0.0, chalk@^2.1.0: +chalk@^2.0.0: version "2.4.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" dependencies: @@ -547,33 +541,23 @@ chalk@^2.0.0, chalk@^2.1.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chardet@^0.4.0: - version "0.4.2" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" +chalk@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" check-error@^1.0.1, check-error@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" -circular-json@^0.3.1: - version "0.3.3" - resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" - clean-stack@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-1.3.0.tgz#9e821501ae979986c46b1d66d2d432db2fd4ae31" integrity sha1-noIVAa6XmYbEax1m0tQy2y/UrjE= -cli-cursor@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" - dependencies: - restore-cursor "^2.0.0" - -cli-width@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" - cliui@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" @@ -612,10 +596,22 @@ color-convert@^1.9.0: dependencies: color-name "1.1.3" +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + colors@1.0.x: version "1.0.3" resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" @@ -684,14 +680,10 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" -concat-stream@^1.6.0: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" +contains-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" + integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= content-disposition@0.5.2: version "0.5.2" @@ -768,14 +760,6 @@ cross-spawn@^4: lru-cache "^4.0.1" which "^1.2.9" -cross-spawn@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" - dependencies: - lru-cache "^4.0.1" - shebang-command "^1.2.0" - which "^1.2.9" - cross-spawn@^6.0.0: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -787,6 +771,15 @@ cross-spawn@^6.0.0: shebang-command "^1.2.0" which "^1.2.9" +cross-spawn@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + cycle@1.0.x: version "1.0.3" resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2" @@ -797,7 +790,7 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -debug@2.6.9, debug@^2.2.0: +debug@2.6.9, debug@^2.2.0, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" dependencies: @@ -816,7 +809,7 @@ debug@^3.1.0: dependencies: ms "^2.1.1" -debug@^4.1.0, debug@^4.1.1: +debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== @@ -856,9 +849,10 @@ deep-equal@^1.0.0, deep-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" -deep-is@~0.1.3: +deep-is@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= default-require-extensions@^2.0.0: version "2.0.0" @@ -867,17 +861,12 @@ default-require-extensions@^2.0.0: dependencies: strip-bom "^3.0.0" -del@^2.0.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" +define-properties@^1.1.2, define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== dependencies: - globby "^5.0.0" - is-path-cwd "^1.0.0" - is-path-in-cwd "^1.0.0" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" - rimraf "^2.2.8" + object-keys "^1.0.12" delayed-stream@0.0.5: version "0.0.5" @@ -903,9 +892,18 @@ diff@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" -doctrine@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" +doctrine@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" + integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== dependencies: esutils "^2.0.2" @@ -956,13 +954,46 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -error-ex@^1.3.1: +enquirer@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + +error-ex@^1.2.0, error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== dependencies: is-arrayish "^0.2.1" +es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.5: + version "1.17.6" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" + integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.0" + is-regex "^1.1.0" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + es6-error@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" @@ -977,84 +1008,175 @@ escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" -escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" -eslint-config-google@~0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/eslint-config-google/-/eslint-config-google-0.9.1.tgz#83353c3dba05f72bb123169a4094f4ff120391eb" +eslint-config-prettier@~6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.11.0.tgz#f6d2238c1290d01c859a8b5c1f7d352a0b0da8b1" + integrity sha512-oB8cpLWSAjOVFEJhhyMZh6NOEOtBVziaqdDQ86+qhDHFbZXoRTM7pNSvFRfW/W/L/LrQ38C99J5CGuRBBzBsdA== + dependencies: + get-stdin "^6.0.0" + +eslint-config-standard@~14.1.1: + version "14.1.1" + resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-14.1.1.tgz#830a8e44e7aef7de67464979ad06b406026c56ea" + integrity sha512-Z9B+VR+JIXRxz21udPTL9HpFMyoMUEeX1G251EQ6e05WD9aPVtVBn09XUmZ259wCMlCDmYDSZG62Hhm+ZTJcUg== + +eslint-import-resolver-node@^0.3.3: + version "0.3.4" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" + integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA== + dependencies: + debug "^2.6.9" + resolve "^1.13.1" -eslint-scope@^3.7.1: - version "3.7.3" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.3.tgz#bb507200d3d17f60247636160b4826284b108535" +eslint-module-utils@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz#579ebd094f56af7797d19c9866c9c9486629bfa6" + integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA== + dependencies: + debug "^2.6.9" + pkg-dir "^2.0.0" + +eslint-plugin-es@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz#75a7cdfdccddc0589934aeeb384175f221c57893" + integrity sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ== + dependencies: + eslint-utils "^2.0.0" + regexpp "^3.0.0" + +eslint-plugin-import@^2.22.0: + version "2.22.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz#92f7736fe1fde3e2de77623c838dd992ff5ffb7e" + integrity sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg== + dependencies: + array-includes "^3.1.1" + array.prototype.flat "^1.2.3" + contains-path "^0.1.0" + debug "^2.6.9" + doctrine "1.5.0" + eslint-import-resolver-node "^0.3.3" + eslint-module-utils "^2.6.0" + has "^1.0.3" + minimatch "^3.0.4" + object.values "^1.1.1" + read-pkg-up "^2.0.0" + resolve "^1.17.0" + tsconfig-paths "^3.9.0" + +eslint-plugin-node@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d" + integrity sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g== + dependencies: + eslint-plugin-es "^3.0.0" + eslint-utils "^2.0.0" + ignore "^5.1.1" + minimatch "^3.0.4" + resolve "^1.10.1" + semver "^6.1.0" + +eslint-plugin-prettier@~3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.4.tgz#168ab43154e2ea57db992a2cd097c828171f75c2" + integrity sha512-jZDa8z76klRqo+TdGDTFJSavwbnWK2ZpqGKNZ+VvweMW516pDUMmQ2koXvxEE4JhzNvTv+radye/bWGBmA6jmg== + dependencies: + prettier-linter-helpers "^1.0.0" + +eslint-plugin-promise@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a" + integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw== + +eslint-plugin-standard@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz#ff0519f7ffaff114f76d1bd7c3996eef0f6e20b4" + integrity sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ== + +eslint-scope@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.0.tgz#d0f971dfe59c69e0cada684b23d49dbf82600ce5" + integrity sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w== dependencies: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-visitor-keys@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" +eslint-utils@^2.0.0, eslint-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== -eslint@~4.18.2: - version "4.18.2" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.18.2.tgz#0f81267ad1012e7d2051e186a9004cc2267b8d45" - integrity sha512-qy4i3wODqKMYfz9LUI8N2qYDkHkoieTbiHpMrYUI/WbjhXJQr7lI4VngixTgaG+yHX+NBCv7nW4hA0ShbvaNKw== +eslint@^7.7.0: + version "7.7.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.7.0.tgz#18beba51411927c4b64da0a8ceadefe4030d6073" + integrity sha512-1KUxLzos0ZVsyL81PnRN335nDtQ8/vZUD6uMtWbF+5zDtjKcsklIi78XoE0MVL93QvWTu+E5y44VyyCsOMBrIg== dependencies: - ajv "^5.3.0" - babel-code-frame "^6.22.0" - chalk "^2.1.0" - concat-stream "^1.6.0" - cross-spawn "^5.1.0" - debug "^3.1.0" - doctrine "^2.1.0" - eslint-scope "^3.7.1" - eslint-visitor-keys "^1.0.0" - espree "^3.5.2" - esquery "^1.0.0" + "@babel/code-frame" "^7.0.0" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.0.1" + doctrine "^3.0.0" + enquirer "^2.3.5" + eslint-scope "^5.1.0" + eslint-utils "^2.1.0" + eslint-visitor-keys "^1.3.0" + espree "^7.2.0" + esquery "^1.2.0" esutils "^2.0.2" - file-entry-cache "^2.0.0" + file-entry-cache "^5.0.1" functional-red-black-tree "^1.0.1" - glob "^7.1.2" - globals "^11.0.1" - ignore "^3.3.3" + glob-parent "^5.0.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.0.0" imurmurhash "^0.1.4" - inquirer "^3.0.6" - is-resolvable "^1.0.0" - js-yaml "^3.9.1" + is-glob "^4.0.0" + js-yaml "^3.13.1" json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.3.0" - lodash "^4.17.4" - minimatch "^3.0.2" - mkdirp "^0.5.1" + levn "^0.4.1" + lodash "^4.17.19" + minimatch "^3.0.4" natural-compare "^1.4.0" - optionator "^0.8.2" - path-is-inside "^1.0.2" - pluralize "^7.0.0" + optionator "^0.9.1" progress "^2.0.0" - require-uncached "^1.0.3" - semver "^5.3.0" - strip-ansi "^4.0.0" - strip-json-comments "~2.0.1" - table "4.0.2" - text-table "~0.2.0" - -espree@^3.5.2: - version "3.5.4" - resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7" - dependencies: - acorn "^5.5.0" - acorn-jsx "^3.0.0" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" + table "^5.2.3" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.2.0.tgz#1c263d5b513dbad0ac30c4991b93ac354e948d69" + integrity sha512-H+cQ3+3JYRMEIOl87e7QdHX70ocly5iW4+dttuR8iYSPr/hXKFb+7dBsZ7+u1adC4VrnPlTkv0+OwuPnDop19g== + dependencies: + acorn "^7.3.1" + acorn-jsx "^5.2.0" + eslint-visitor-keys "^1.3.0" esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" -esquery@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708" +esquery@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" + integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== dependencies: - estraverse "^4.0.0" + estraverse "^5.1.0" esrecurse@^4.1.0: version "4.2.1" @@ -1062,10 +1184,15 @@ esrecurse@^4.1.0: dependencies: estraverse "^4.1.0" -estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1: +estraverse@^4.1.0, estraverse@^4.1.1: version "4.2.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" +estraverse@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" + integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== + esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" @@ -1131,14 +1258,6 @@ extend@^3.0.0, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" -external-editor@^2.0.4: - version "2.2.0" - resolved "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" - dependencies: - chardet "^0.4.0" - iconv-lite "^0.4.17" - tmp "^0.0.33" - extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -1159,6 +1278,16 @@ fast-deep-equal@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + fast-json-patch@^2.0.6: version "2.0.7" resolved "https://registry.yarnpkg.com/fast-json-patch/-/fast-json-patch-2.0.7.tgz#55864b08b1e50381d2f37fd472bb2e18fe54a733" @@ -1170,22 +1299,17 @@ fast-json-stable-stringify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" -fast-levenshtein@~2.0.4: +fast-levenshtein@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= -figures@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" - dependencies: - escape-string-regexp "^1.0.5" - -file-entry-cache@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" + integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== dependencies: - flat-cache "^1.2.1" - object-assign "^4.0.1" + flat-cache "^2.0.1" finalhandler@1.1.1: version "1.1.1" @@ -1208,6 +1332,13 @@ find-cache-dir@^2.1.0: make-dir "^2.0.0" pkg-dir "^3.0.0" +find-up@^2.0.0, find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= + dependencies: + locate-path "^2.0.0" + find-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" @@ -1215,14 +1346,19 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" -flat-cache@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.0.tgz#d3030b32b38154f4e3b7e9c709f490f7ef97c481" +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== dependencies: - circular-json "^0.3.1" - del "^2.0.2" - graceful-fs "^4.1.2" - write "^0.2.1" + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" + +flatted@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== follow-redirects@1.5.10: version "1.5.10" @@ -1301,6 +1437,11 @@ fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" @@ -1314,6 +1455,11 @@ get-func-name@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" +get-stdin@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" + integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g== + get-stream@3.0.0, get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" @@ -1332,6 +1478,13 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" +glob-parent@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" + integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + dependencies: + is-glob "^4.0.1" + glob@7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" @@ -1355,7 +1508,7 @@ glob@^7.0.0, glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.3, glob@^7.0.5, glob@^7.1.2: +glob@^7.0.5: version "7.1.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" dependencies: @@ -1366,25 +1519,17 @@ glob@^7.0.3, glob@^7.0.5, glob@^7.1.2: once "^1.3.0" path-is-absolute "^1.0.0" -globals@^11.0.1: - version "11.7.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.7.0.tgz#a583faa43055b1aca771914bf68258e2fc125673" - globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globby@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" +globals@^12.1.0: + version "12.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" + integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== dependencies: - array-union "^1.0.1" - arrify "^1.0.0" - glob "^7.0.3" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" + type-fest "^0.8.1" got@^8.3.2: version "8.3.2" @@ -1453,12 +1598,6 @@ har-validator@~5.1.0: ajv "^5.3.0" har-schema "^2.0.0" -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - dependencies: - ansi-regex "^2.0.0" - has-flag@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" @@ -1467,11 +1606,21 @@ has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + has-symbol-support-x@^1.4.1: version "1.4.2" resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw== +has-symbols@^1.0.0, has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + has-to-string-tag-x@^1.2.0: version "1.4.1" resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d" @@ -1479,6 +1628,13 @@ has-to-string-tag-x@^1.2.0: dependencies: has-symbol-support-x "^1.4.1" +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + hasha@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/hasha/-/hasha-3.0.0.tgz#52a32fab8569d41ca69a61ff1a214f8eb7c8bd39" @@ -1533,15 +1689,23 @@ iconv-lite@0.4.19: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" -iconv-lite@^0.4.17: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - dependencies: - safer-buffer ">= 2.1.2 < 3" +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^3.3.3: - version "3.3.10" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" +ignore@^5.1.1: + version "5.1.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== + +import-fresh@^3.0.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" + integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" imurmurhash@^0.1.4: version "0.1.4" @@ -1564,7 +1728,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.3, inherits@^2.0.3, inherits@~2.0.3: +inherits@2, inherits@2.0.3, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -1573,25 +1737,6 @@ inherits@^2.0.1: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -inquirer@^3.0.6: - version "3.3.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" - dependencies: - ansi-escapes "^3.0.0" - chalk "^2.0.0" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^2.0.4" - figures "^2.0.0" - lodash "^4.3.0" - mute-stream "0.0.7" - run-async "^2.2.0" - rx-lite "^4.0.8" - rx-lite-aggregates "^4.0.8" - string-width "^2.1.0" - strip-ansi "^4.0.0" - through "^2.3.6" - interpret@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" @@ -1634,10 +1779,32 @@ is-builtin-module@^1.0.0: dependencies: builtin-modules "^1.0.0" +is-callable@^1.1.4, is-callable@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" + integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== + +is-date-object@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" +is-glob@^4.0.0, is-glob@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + is-ip@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-ip/-/is-ip-2.0.0.tgz#68eea07e8a0a0a94c2d080dd674c731ab2a461ab" @@ -1650,34 +1817,17 @@ is-object@^1.0.1: resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" integrity sha1-iVJojF7C/9awPsyF52ngKQMINHA= -is-path-cwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" - -is-path-in-cwd@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz#5ac48b345ef675339bd6c7a48a912110b241cf52" - dependencies: - is-path-inside "^1.0.0" - -is-path-inside@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" - dependencies: - path-is-inside "^1.0.1" - is-plain-obj@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= -is-promise@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" - -is-resolvable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" +is-regex@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" + integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== + dependencies: + has-symbols "^1.0.1" is-retry-allowed@^1.1.0: version "1.2.0" @@ -1689,11 +1839,23 @@ is-stream@^1.0.1, is-stream@^1.1.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= +is-string@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" + integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== + +is-symbol@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== + dependencies: + has-symbols "^1.0.1" + is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" -isarray@~1.0.0: +isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -1784,16 +1946,12 @@ joi@~13.0.1: isemail "3.x.x" topo "3.x.x" -js-tokens@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" - js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@^3.11.0, js-yaml@^3.13.1, js-yaml@^3.9.1: +js-yaml@^3.11.0, js-yaml@^3.13.1: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== @@ -1847,6 +2005,13 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -1909,12 +2074,23 @@ lcov-parse@^0.0.10: version "0.0.10" resolved "https://registry.yarnpkg.com/lcov-parse/-/lcov-parse-0.0.10.tgz#1b0b8ff9ac9c7889250582b70b71315d9da6d9a3" -levn@^0.3.0, levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" + prelude-ls "^1.2.1" + type-check "~0.4.0" + +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" load-json-file@^4.0.0: version "4.0.0" @@ -1926,6 +2102,14 @@ load-json-file@^4.0.0: pify "^3.0.0" strip-bom "^3.0.0" +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + locate-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" @@ -1967,11 +2151,16 @@ lodash.once@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" -lodash@4.x, lodash@^4.14.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.4, lodash@^4.3.0, lodash@~4.17.2: +lodash@4.x, lodash@^4.14.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.19, lodash@~4.17.2: version "4.17.19" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== +lodash@^4.17.14: + version "4.17.20" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" + integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== + log-driver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.7.tgz#63b95021f0702fedfa2c9bb0a24e7797d71871d8" @@ -2066,16 +2255,12 @@ mime@^2.4.0: resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA== -mimic-fn@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" - mimic-response@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== -minimatch@^3.0.2, minimatch@^3.0.4: +minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: @@ -2155,10 +2340,6 @@ mustache@~2.3.0: resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.2.tgz#a6d4d9c3f91d13359ab889a812954f9230a3d0c5" integrity sha512-KpMNwdQsYz3O/SBS1qJ/o3sqUJ5wSb8gb0pul8CO0S56b9Y2ALm8zCfsjPXsqGFfoNBkDwZuZIAjhsZI03gYVQ== -mute-stream@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -2280,7 +2461,7 @@ oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" -object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0: +object-assign@^4, object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -2289,6 +2470,36 @@ object-hash@^1.3.1: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df" integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA== +object-inspect@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" + integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== + +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +object.values@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" + integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" + oidc-token-hash@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/oidc-token-hash/-/oidc-token-hash-3.0.2.tgz#5bd4716cc48ad433f4e4e99276811019b165697e" @@ -2310,12 +2521,6 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -onetime@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" - dependencies: - mimic-fn "^1.0.0" - openid-client@2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/openid-client/-/openid-client-2.5.0.tgz#7d4cf552b30dbad26917d7e2722422eda057ea93" @@ -2344,25 +2549,22 @@ optimist@^0.6.1: minimist "~0.0.1" wordwrap "~0.0.2" -optionator@^0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.4" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - wordwrap "~1.0.0" + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" os-homedir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" -os-tmpdir@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - p-any@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/p-any/-/p-any-1.1.0.tgz#1d03835c7eed1e34b8e539c47b7b60d0d015d4e1" @@ -2385,6 +2587,13 @@ p-is-promise@^1.1.0: resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" integrity sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4= +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + p-limit@^2.0.0: version "2.2.1" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.1.tgz#aa07a788cc3151c939b5131f63570f0dd2009537" @@ -2392,6 +2601,13 @@ p-limit@^2.0.0: dependencies: p-try "^2.0.0" +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= + dependencies: + p-limit "^1.1.0" + p-locate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" @@ -2413,6 +2629,11 @@ p-timeout@^2.0.1: dependencies: p-finally "^1.0.0" +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" @@ -2433,6 +2654,20 @@ packet-reader@1.0.0: resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= + dependencies: + error-ex "^1.2.0" + parse-json@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" @@ -2453,15 +2688,16 @@ path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" -path-is-inside@^1.0.1, path-is-inside@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" @@ -2471,6 +2707,13 @@ path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM= + dependencies: + pify "^2.0.0" + path-type@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" @@ -2547,9 +2790,9 @@ pg@^7.17.1: semver "4.3.2" pg@^8.2.1: - version "8.3.0" - resolved "https://registry.yarnpkg.com/pg/-/pg-8.3.0.tgz#941383300d38eef51ecb88a0188cec441ab64d81" - integrity sha512-jQPKWHWxbI09s/Z9aUvoTbvGgoj98AU7FDCcQ7kdejupn/TcNpx56v2gaOTzXkzOajmOEJEdi9eTh9cA2RVAjQ== + version "8.3.2" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.3.2.tgz#52766e41302f5b878fe1efa10d4cdd486f6dff50" + integrity sha512-hOoRCTriXS+VWwyXHchRjWb9yv3Koq8irlwwXniqhdgK0AbfWvEnybGS2HIUE+UdCSTuYAM4WGPujFpPg9Vcaw== dependencies: buffer-writer "2.0.0" packet-reader "1.0.0" @@ -2581,15 +2824,12 @@ pify@^4.0.1: resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== -pinkie-promise@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" +pkg-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= dependencies: - pinkie "^2.0.0" - -pinkie@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + find-up "^2.1.0" pkg-dir@^3.0.0: version "3.0.0" @@ -2598,10 +2838,6 @@ pkg-dir@^3.0.0: dependencies: find-up "^3.0.0" -pluralize@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" - postgres-array@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" @@ -2624,15 +2860,28 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== prepend-http@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@~2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" + integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== + process-nextick-args@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" @@ -2711,6 +2960,14 @@ raw-body@2.3.2: iconv-lite "0.4.19" unpipe "1.0.0" +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4= + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + read-pkg-up@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-4.0.0.tgz#1b221c6088ba7799601c808f91161c66e58f8978" @@ -2719,6 +2976,15 @@ read-pkg-up@^4.0.0: find-up "^3.0.0" read-pkg "^3.0.0" +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg= + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + read-pkg@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" @@ -2728,7 +2994,7 @@ read-pkg@^3.0.0: normalize-package-data "^2.3.2" path-type "^3.0.0" -readable-stream@^2.0.0, readable-stream@^2.2.2, readable-stream@^2.3.5: +readable-stream@^2.0.0, readable-stream@^2.3.5: version "2.3.6" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" dependencies: @@ -2747,6 +3013,11 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" +regexpp@^3.0.0, regexpp@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" + integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== + release-zalgo@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" @@ -2788,17 +3059,6 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== -require-uncached@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" - dependencies: - caller-path "^0.1.0" - resolve-from "^1.0.0" - -resolve-from@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" - resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -2811,6 +3071,13 @@ resolve@^1.1.6: dependencies: path-parse "^1.0.6" +resolve@^1.10.1, resolve@^1.13.1, resolve@^1.17.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== + dependencies: + path-parse "^1.0.6" + responselike@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" @@ -2818,13 +3085,6 @@ responselike@1.0.2: dependencies: lowercase-keys "^1.0.0" -restore-cursor@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" - dependencies: - onetime "^2.0.0" - signal-exit "^3.0.2" - retry-as-promised@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/retry-as-promised/-/retry-as-promised-3.2.0.tgz#769f63d536bec4783549db0777cb56dadd9d8543" @@ -2832,7 +3092,14 @@ retry-as-promised@^3.2.0: dependencies: any-promise "^1.3.0" -rimraf@^2.2.8, rimraf@^2.6.2: +rimraf@2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +rimraf@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" dependencies: @@ -2845,21 +3112,12 @@ rimraf@^2.6.3: dependencies: glob "^7.1.3" -run-async@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" - dependencies: - is-promise "^2.1.0" - -rx-lite-aggregates@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: - rx-lite "*" - -rx-lite@*, rx-lite@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" + glob "^7.1.3" safe-buffer@5.1.1: version "5.1.1" @@ -2874,7 +3132,7 @@ safe-buffer@^5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -2897,11 +3155,16 @@ semver@^5.5.0, semver@^5.6.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@^6.0.0, semver@^6.3.0: +semver@^6.0.0, semver@^6.1.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.2.1: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + send@0.16.2: version "0.16.2" resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" @@ -2973,10 +3236,22 @@ shebang-command@^1.2.0: dependencies: shebang-regex "^1.0.0" +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + shelljs@^0.8.2: version "0.8.3" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.3.tgz#a7f3319520ebf09ee81275b2368adb286659b097" @@ -2995,10 +3270,13 @@ signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" -slice-ansi@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" sort-keys@^2.0.0: @@ -3100,13 +3378,6 @@ strict-uri-encode@^1.0.0: resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= -string-width@^2.1.0, string-width@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - string-width@^3.0.0, string-width@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" @@ -3116,23 +3387,27 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" +string.prototype.trimend@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" + integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== dependencies: - safe-buffer "~5.1.0" + define-properties "^1.1.3" + es-abstract "^1.17.5" -strip-ansi@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" +string.prototype.trimstart@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" + integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== dependencies: - ansi-regex "^2.0.0" + define-properties "^1.1.3" + es-abstract "^1.17.5" -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" dependencies: - ansi-regex "^3.0.0" + safe-buffer "~5.1.0" strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: version "5.2.0" @@ -3141,6 +3416,13 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" @@ -3151,9 +3433,10 @@ strip-eof@^1.0.0: resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" +strip-json-comments@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== superagent@^3.7.0: version "3.8.3" @@ -3177,10 +3460,6 @@ supports-color@4.4.0: dependencies: has-flag "^2.0.0" -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -3194,6 +3473,13 @@ supports-color@^6.1.0: dependencies: has-flag "^3.0.0" +supports-color@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" + integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + dependencies: + has-flag "^4.0.0" + swagger-ui-dist@^3.18.1: version "3.24.3" resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-3.24.3.tgz#99754d11b0ddd314a1a50db850acb415e4b0a0c6" @@ -3206,17 +3492,15 @@ swagger-ui-express@^4.1.2: dependencies: swagger-ui-dist "^3.18.1" -table@4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" - integrity sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA== +table@^5.2.3: + version "5.4.6" + resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" + integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== dependencies: - ajv "^5.2.3" - ajv-keywords "^2.1.0" - chalk "^2.1.0" - lodash "^4.17.4" - slice-ansi "1.0.0" - string-width "^2.1.1" + ajv "^6.10.2" + lodash "^4.17.14" + slice-ansi "^2.1.0" + string-width "^3.0.0" test-exclude@^5.2.3: version "5.2.3" @@ -3228,11 +3512,12 @@ test-exclude@^5.2.3: read-pkg-up "^4.0.0" require-main-filename "^2.0.0" -text-table@~0.2.0: +text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= -through@2, through@^2.3.6: +through@2: version "2.3.8" resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -3241,12 +3526,6 @@ timed-out@^4.0.1: resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - dependencies: - os-tmpdir "~1.0.2" - to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" @@ -3270,6 +3549,16 @@ tough-cookie@~2.4.3: psl "^1.1.24" punycode "^1.4.1" +tsconfig-paths@^3.9.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" + integrity sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.0" + strip-bom "^3.0.0" + tslib@^1.9.3: version "1.10.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" @@ -3285,11 +3574,12 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== dependencies: - prelude-ls "~1.1.2" + prelude-ls "^1.2.1" type-detect@0.1.1: version "0.1.1" @@ -3303,6 +3593,11 @@ type-detect@^4.0.0: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + type-is@~1.6.15, type-is@~1.6.16: version "1.6.16" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" @@ -3310,10 +3605,6 @@ type-is@~1.6.15, type-is@~1.6.16: media-typer "0.3.0" mime-types "~2.1.18" -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - uglify-js@^3.1.4: version "3.6.0" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.0.tgz#704681345c53a8b2079fb6cec294b05ead242ff5" @@ -3384,6 +3675,11 @@ uuid@^3.3.3: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== +v8-compile-cache@^2.0.3: + version "2.1.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" + integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== + validate-npm-package-license@^3.0.1: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" @@ -3418,6 +3714,13 @@ which@^1.2.9, which@^1.3.0: dependencies: isexe "^2.0.0" +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + winston@~2.4.0: version "2.4.4" resolved "https://registry.yarnpkg.com/winston/-/winston-2.4.4.tgz#a01e4d1d0a103cf4eada6fc1f886b3110d71c34b" @@ -3436,15 +3739,16 @@ wkx@^0.4.8: dependencies: "@types/node" "*" +word-wrap@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + wordwrap@~0.0.2: version "0.0.3" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= -wordwrap@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" - wrap-ansi@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" @@ -3467,9 +3771,10 @@ write-file-atomic@^2.4.2: imurmurhash "^0.1.4" signal-exit "^3.0.2" -write@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== dependencies: mkdirp "^0.5.1"