diff --git a/src/rest-server/docs/swagger.yaml b/src/rest-server/docs/swagger.yaml index 5583d0dfd5..4bcfd44b86 100644 --- a/src/rest-server/docs/swagger.yaml +++ b/src/rest-server/docs/swagger.yaml @@ -26,6 +26,8 @@ tags: description: API endpoint for storage - name: job history description: API endpoint for job history +- name: kubernetes + description: API endpoint for kubernetes info paths: /api/docs: get: @@ -1680,6 +1682,54 @@ paths: $ref: '#/components/responses/NoJobError' 501: $ref: '#/components/responses/UnknownError' + /api/v1/kubernetes/nodes: + get: + tags: + - kubernetes + summary: Get kubernetes node list. + description: Get kubernetes node list. Need administrator permission. + operationId: getK8sNodes + security: + - bearerAuth: [] + responses: + 200: + description: Succeeded + content: + application/json: + example: + - Please refer to Kubernetes API doc + - https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#list-node-v1-core + 401: + $ref: '#/components/responses/UnauthorizedUserError' + 403: + $ref: '#/components/responses/ForbiddenUserError' + /api/v1/kubernetes/pods: + get: + tags: + - kubernetes + summary: Get kubernetes pod list. + description: Get kubernetes pod list. Need administrator permission. + operationId: getK8sPods + parameters: + - name: namespace + in: query + description: filter pods with namespace + schema: + type: string + security: + - bearerAuth: [] + responses: + 200: + description: Succeeded + content: + application/json: + example: + - Please refer to Kubernetes API doc + - https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#list-all-namespaces-pod-v1-core + 401: + $ref: '#/components/responses/UnauthorizedUserError' + 403: + $ref: '#/components/responses/ForbiddenUserError' components: parameters: diff --git a/src/rest-server/package.json b/src/rest-server/package.json index 6f92dbd72a..5afa01b581 100644 --- a/src/rest-server/package.json +++ b/src/rest-server/package.json @@ -25,6 +25,7 @@ }, "dependencies": { "@elastic/elasticsearch": "^7.4.0", + "@kubernetes/client-node": "^0.11.0", "ajv": "^6.10.0", "ajv-merge-patch": "~4.1.0", "async": "~2.5.0", @@ -43,7 +44,6 @@ "express": "~4.16.2", "fs-extra": "~7.0.1", "http-errors": "~1.6.3", - "http-proxy-middleware": "^0.20.0", "joi": "~13.0.1", "js-yaml": "^3.13.1", "jsonwebtoken": "~8.1.0", diff --git a/src/rest-server/src/config/kubernetes.js b/src/rest-server/src/config/kubernetes.js index 75c79ddc6c..ab7375e075 100644 --- a/src/rest-server/src/config/kubernetes.js +++ b/src/rest-server/src/config/kubernetes.js @@ -18,18 +18,28 @@ const assert = require('assert'); const {readFileSync} = require('fs'); -const path = require('path'); +const k8s = require('@kubernetes/client-node'); const logger = require('@pai/config/logger'); +const bufferFromFileOrData = (path, data) => { + if (path) { + return readFileSync(path); + } else if (data) { + return Buffer.from(data, 'base64'); + } +}; + const apiserverConfig = {}; const { K8S_APISERVER_URI, K8S_APISERVER_CA_FILE, K8S_APISERVER_TOKEN_FILE, + K8S_KUBECONFIG_PATH, + RBAC_IN_CLUSTER, } = process.env; -if (process.env.RBAC_IN_CLUSTER === 'false') { +if (RBAC_IN_CLUSTER === 'false') { apiserverConfig.uri = K8S_APISERVER_URI; // Should be empty if (K8S_APISERVER_CA_FILE) { @@ -41,20 +51,27 @@ if (process.env.RBAC_IN_CLUSTER === 'false') { apiserverConfig.token = readFileSync(K8S_APISERVER_TOKEN_FILE, 'utf8'); } } else { - const root = process.env.KUBERNETES_CLIENT_SERVICEACCOUNT_ROOT || '/var/run/secrets/kubernetes.io/serviceaccount/'; - // By default, in rbac enabled k8s, the caPath and tokenPath is a fixed value. However, from the perspective of flexibility, - // user can custom the following 2 files' path in the future. - const caPath = K8S_APISERVER_CA_FILE || path.join(root, 'ca.crt'); - const tokenPath = K8S_APISERVER_TOKEN_FILE || path.join(root, 'token'); - const host = process.env.KUBERNETES_SERVICE_HOST; - const port = process.env.KUBERNETES_SERVICE_PORT; - apiserverConfig.uri = `https://${host}:${port}`; - - try { - // Will be a buffer since SSL context can receive a buffer. - apiserverConfig.ca = readFileSync(caPath, 'utf8'); + if (K8S_APISERVER_CA_FILE) { + k8s.Config.SERVICEACCOUNT_CA_PATH = K8S_APISERVER_CA_FILE; + } + if (K8S_APISERVER_TOKEN_FILE) { // Will be a string since http header can only receive a string. - apiserverConfig.token = readFileSync(tokenPath, 'utf8'); + k8s.Config.SERVICEACCOUNT_TOKEN_PATH = K8S_APISERVER_TOKEN_FILE; + } + try { + const kc = new k8s.KubeConfig(); + if (K8S_KUBECONFIG_PATH) { + kc.loadFromFile(K8S_KUBECONFIG_PATH); + } else { + kc.loadFromDefault(); + } + const cluster = kc.getCurrentCluster(); + const user = kc.getCurrentUser(); + apiserverConfig.uri = cluster.server; + apiserverConfig.token = user.token; + apiserverConfig.ca = bufferFromFileOrData(cluster.caFile, cluster.caData); + apiserverConfig.key = bufferFromFileOrData(user.keyFile, user.keyData); + apiserverConfig.cert = bufferFromFileOrData(user.certFile, user.certData); } catch (error) { 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 69f0f1313c..199239fe92 100644 --- a/src/rest-server/src/config/launcher.js +++ b/src/rest-server/src/config/launcher.js @@ -18,7 +18,6 @@ // module dependencies const Joi = require('joi'); -const {apiserver} = require('@pai/config/kubernetes'); // define yarn launcher config schema @@ -90,9 +89,6 @@ const yarnLauncherConfigSchema = Joi.object().keys({ // define k8s launcher config schema const k8sLauncherConfigSchema = Joi.object().keys({ - apiServerUri: Joi.string() - .uri() - .required(), hivedWebserviceUri: Joi.string() .uri() .required(), @@ -201,7 +197,6 @@ if (launcherType === 'yarn') { launcherConfig.type = launcherType; } else if (launcherType === 'k8s') { launcherConfig = { - apiServerUri: apiserver.uri, hivedWebserviceUri: process.env.HIVED_WEBSERVICE_URI, apiVersion: 'frameworkcontroller.microsoft.com/v1', podGracefulDeletionTimeoutSec: 1800, @@ -213,31 +208,30 @@ if (launcherType === 'yarn') { requestHeaders: { 'Accept': 'application/json', 'Content-Type': 'application/json', - ...apiserver.token && {Authorization: `Bearer ${apiserver.token}`}, }, healthCheckPath: () => { - return `${launcherConfig.apiServerUri}/apis/${launcherConfig.apiVersion}`; + return `/apis/${launcherConfig.apiVersion}`; }, frameworksPath: (namespace='default') => { - return `${launcherConfig.apiServerUri}/apis/${launcherConfig.apiVersion}/namespaces/${namespace}/frameworks`; + return `/apis/${launcherConfig.apiVersion}/namespaces/${namespace}/frameworks`; }, frameworkPath: (frameworkName, namespace='default') => { - return `${launcherConfig.apiServerUri}/apis/${launcherConfig.apiVersion}/namespaces/${namespace}/frameworks/${frameworkName}`; + return `/apis/${launcherConfig.apiVersion}/namespaces/${namespace}/frameworks/${frameworkName}`; }, priorityClassesPath: () => { - return `${launcherConfig.apiServerUri}/apis/scheduling.k8s.io/v1/priorityclasses`; + return `/apis/scheduling.k8s.io/v1/priorityclasses`; }, priorityClassPath: (priorityClassName) => { - return `${launcherConfig.apiServerUri}/apis/scheduling.k8s.io/v1/priorityclasses/${priorityClassName}`; + return `/apis/scheduling.k8s.io/v1/priorityclasses/${priorityClassName}`; }, secretsPath: (namespace='default') => { - return `${launcherConfig.apiServerUri}/api/v1/namespaces/${namespace}/secrets`; + return `/api/v1/namespaces/${namespace}/secrets`; }, secretPath: (secretName, namespace='default') => { - return `${launcherConfig.apiServerUri}/api/v1/namespaces/${namespace}/secrets/${secretName}`; + return `/api/v1/namespaces/${namespace}/secrets/${secretName}`; }, podPath: (podName, namespace='default') => { - return `${launcherConfig.apiServerUri}/api/v1/namespaces/${namespace}/pods/${podName}`; + return `/api/v1/namespaces/${namespace}/pods/${podName}`; }, }; diff --git a/src/rest-server/src/config/paiConfig.js b/src/rest-server/src/config/paiConfig.js index 3373f732c2..2048b0959f 100644 --- a/src/rest-server/src/config/paiConfig.js +++ b/src/rest-server/src/config/paiConfig.js @@ -16,14 +16,12 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // module dependencies -const axios = require('axios'); -const {Agent} = require('https'); const Joi = require('joi'); const yaml = require('js-yaml'); const {get} = require('lodash'); const fs = require('fs'); const logger = require('@pai/config/logger'); -const {apiserver} = require('@pai/config/kubernetes'); +const k8sModel = require('@pai/models/kubernetes'); let paiMachineList = []; try { @@ -57,14 +55,8 @@ paiConfigData = value; const fetchPAIVersion = async () => { try { - const res = await axios.request({ - url: '/api/v1/namespaces/default/configmaps/pai-version', - baseURL: apiserver.uri, - maxRedirects: 0, - httpsAgent: apiserver.ca && new Agent({ca: apiserver.ca}), - headers: apiserver.token && {Authorization: `Bearer ${apiserver.token}`}, - }); - + 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(); diff --git a/src/rest-server/src/config/secret.js b/src/rest-server/src/config/secret.js index 7868c077de..e285d5de07 100644 --- a/src/rest-server/src/config/secret.js +++ b/src/rest-server/src/config/secret.js @@ -18,48 +18,25 @@ // module dependencies const Joi = require('joi'); -const {readFileSync} = require('fs'); -const {Agent} = require('https'); const authnConfig = require('@pai/config/authn'); let userSecretConfig = {}; if (authnConfig.authnMethod !== 'OIDC') { userSecretConfig = { - apiServerUri: process.env.K8S_APISERVER_URI, paiUserNameSpace: 'pai-user', adminName: process.env.DEFAULT_PAI_ADMIN_USERNAME, adminPass: process.env.DEFAULT_PAI_ADMIN_PASSWORD, }; } else { userSecretConfig = { - apiServerUri: process.env.K8S_APISERVER_URI, paiUserNameSpace: 'pai-user', }; } -userSecretConfig.requestConfig = () => { - const config = { - baseURL: `${userSecretConfig.apiServerUri}/api/v1/namespaces/`, - maxRedirects: 0, - }; - if ('K8S_APISERVER_CA_FILE' in process.env) { - const ca = readFileSync(process.env.K8S_APISERVER_CA_FILE); - config.httpsAgent = new Agent({ca}); - } - - if ('K8S_APISERVER_TOKEN_FILE' in process.env) { - const token = readFileSync(process.env.K8S_APISERVER_TOKEN_FILE, 'ascii'); - config.headers = {Authorization: `Bearer ${token}`}; - } - return config; -}; - let userSecretConfigSchema = {}; if (authnConfig.authnMethod !== 'OIDC') { userSecretConfigSchema = Joi.object().keys({ - apiServerUri: Joi.string() - .required(), paiUserNameSpace: Joi.string() .default('pai-user'), adminName: Joi.string() @@ -68,19 +45,11 @@ if (authnConfig.authnMethod !== 'OIDC') { adminPass: Joi.string() .min(6) .required(), - requestConfig: Joi.func() - .arity(0) - .required(), }).required(); } else { userSecretConfigSchema = Joi.object().keys({ - apiServerUri: Joi.string() - .required(), paiUserNameSpace: Joi.string() .default('pai-user'), - requestConfig: Joi.func() - .arity(0) - .required(), }).required(); } diff --git a/src/rest-server/src/controllers/kubernetes-proxy.js b/src/rest-server/src/controllers/kubernetes-proxy.js deleted file mode 100644 index 09dcabb843..0000000000 --- a/src/rest-server/src/controllers/kubernetes-proxy.js +++ /dev/null @@ -1,26 +0,0 @@ -const assert = require('assert'); -const {parse} = require('url'); - -const proxy = require('http-proxy-middleware'); - -const {apiserver: {uri, ca, token}} = require('../config/kubernetes'); - -const options = { - target: parse(uri), - changeOrigin: true, - pathRewrite(path, req) { - // Strip leading baseUrl - assert(path.slice(0, req.baseUrl.length) === req.baseUrl); - return path.slice(req.baseUrl.length); - }, -}; -if (ca) { - options.ca = ca; -} -if (token) { - options.headers = {Authorization: `Bearer ${token}`}; -} else { - options.headers = {Authorization: null}; -} - -module.exports = proxy(options); diff --git a/src/rest-server/src/middlewares/token.js b/src/rest-server/src/middlewares/token.js index 33b0e92f2f..e6cf74e05f 100644 --- a/src/rest-server/src/middlewares/token.js +++ b/src/rest-server/src/middlewares/token.js @@ -19,6 +19,7 @@ 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'); +const logger = require('@pai/config/logger'); const getToken = async (req) => { const [scheme, credentials] = req.headers.authorization.split(' '); @@ -48,6 +49,7 @@ const check = async (req, _, next) => { req[userProperty].admin = await userModel.checkAdmin(req[userProperty].username); next(); } catch (error) { + logger.debug(error); return next(createError('Unauthorized', 'UnauthorizedUserError', 'Your token is invalid.')); } }; diff --git a/src/rest-server/src/models/k8s-secret.js b/src/rest-server/src/models/k8s-secret.js index 39fad1f182..fea27cadc4 100644 --- a/src/rest-server/src/models/k8s-secret.js +++ b/src/rest-server/src/models/k8s-secret.js @@ -15,31 +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. -const axios = require('axios'); const _ = require('lodash'); -const {Agent} = require('https'); -const {URL} = require('url'); - -const {apiserver} = require('@pai/config/kubernetes'); +const {encodeSelector, getClient} = require('@pai/models/kubernetes'); const initClient = (namespace) => { if (!namespace) { throw new Error('K8S SECRET: invalid namespace'); } - const config = { - baseURL: new URL(`/api/v1/namespaces/${namespace}/secrets`, apiserver.uri).href, - maxRedirects: 0, - headers: { - 'Accept': 'application/json', - }, - }; - if (apiserver.ca) { - config.httpsAgent = new Agent({ca: apiserver.ca}); - } - if (apiserver.token) { - config.headers['Authorization'] = `Bearer ${apiserver.token}`; - } - return axios.create(config); + return getClient(`/api/v1/namespaces/${namespace}/secrets`); }; @@ -77,43 +60,6 @@ const deserialize = (object) => { return result; }; -const encodeLabeLSelector = (labelSelector) => { - const builder = []; - for (const [key, val] of Object.entries(labelSelector)) { - builder.push(`${key}=${val}`); - } - return builder.join(','); -}; - -const createNamespace = async (namespace) => { - const url = new URL(`/api/v1/namespaces/`, apiserver.uri).href; - const config = { - maxRedirects: 0, - headers: { - 'Accept': 'application/json', - }, - }; - if (apiserver.ca) { - config.httpsAgent = new Agent({ca: apiserver.ca}); - } - if (apiserver.token) { - config.headers['Authorization'] = `Bearer ${apiserver.token}`; - } - try { - await axios.post(url, { - metadata: { - name: namespace, - }, - }, config); - } catch (err) { - if (err.response && err.response.status === 409 && err.response.data.reason === 'AlreadyExists') { - // pass - } else { - throw err; - } - } -}; - const get = async (namespace, key) => { if (!key) { return list(namespace); @@ -136,7 +82,7 @@ const get = async (namespace, key) => { }; const list = async (namespace, labelSelector) => { - const labelSelectorStr = encodeLabeLSelector(labelSelector); + const labelSelectorStr = encodeSelector(labelSelector); const client = initClient(namespace); let continueValue = null; let result = []; @@ -215,7 +161,6 @@ const remove = async (namespace, key) => { }; module.exports = { - createNamespace, get, list, create, diff --git a/src/rest-server/src/models/kubernetes.js b/src/rest-server/src/models/kubernetes.js new file mode 100644 index 0000000000..1c0c5a0c1a --- /dev/null +++ b/src/rest-server/src/models/kubernetes.js @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +const axios = require('axios'); +const {Agent} = require('https'); +const {URL} = require('url'); +const {apiserver} = require('@pai/config/kubernetes'); +const logger = require('@pai/config/logger'); + +const getClient = (baseURL = '') => { + const config = { + baseURL: new URL(baseURL, apiserver.uri).toString(), + maxRedirects: 0, + headers: { + 'Accept': 'application/json', + }, + }; + if (apiserver.ca) { + config.httpsAgent = new Agent({ca: apiserver.ca, cert: apiserver.cert, key: apiserver.key}); + } + if (apiserver.token) { + config.headers['Authorization'] = `Bearer ${apiserver.token}`; + } + return axios.create(config); +}; + +const encodeSelector = (selector = {}, negativeSelector = {}) => { + const builder = []; + for (const [key, val] of Object.entries(selector)) { + builder.push(`${key}=${val}`); + } + for (const [key, val] of Object.entries(negativeSelector)) { + builder.push(`${key}!=${val}`); + } + return builder.join(','); +}; + +const createNamespace = async (namespace) => { + const client = getClient(); + try { + await client.post('/api/v1/namespaces/', { + metadata: { + name: namespace, + }, + }); + logger.info('Token secret namespace created'); + } catch (err) { + if (err.response && err.response.status === 409 && err.response.data.reason === 'AlreadyExists') { + logger.info('Token secret namespace already exists'); + // pass + } else { + throw err; + } + } +}; + +const getNodes = async () => { + const client = getClient(); + const res = await client.get('api/v1/nodes'); + return res.data; +}; + +const getPods = async (options = {}) => { + const {labelSelector, negativeLabelSelector, namespace} = options; + const client = getClient(); + const labelSelectorStr = encodeSelector(labelSelector, negativeLabelSelector); + const requestOptions = {}; + if (labelSelectorStr) { + requestOptions.params = { + labelSelector: labelSelectorStr, + }; + } + let url = '/api/v1/pods'; + if (namespace) { + url = `/api/v1/namespaces/${namespace}/pods`; + } + const res = await client.get(url, requestOptions); + return res.data; +}; + +module.exports = { + getClient, + encodeSelector, + createNamespace, + getNodes, + getPods, +}; diff --git a/src/rest-server/src/models/token.js b/src/rest-server/src/models/token.js index 3dc369f32a..419f845476 100644 --- a/src/rest-server/src/models/token.js +++ b/src/rest-server/src/models/token.js @@ -17,16 +17,15 @@ const jwt = require('jsonwebtoken'); const uuid = require('uuid'); -const logger = require('@pai/config/logger'); const {secret, tokenExpireTime} = require('@pai/config/token'); const k8sSecret = require('@pai/models/k8s-secret'); +const k8sModel = require('@pai/models/kubernetes'); const namespace = process.env.PAI_TOKEN_NAMESPACE || 'pai-user-token'; // create namespace if not exists if (process.env.NODE_ENV !== 'test') { - logger.info(`Create token secret namespace (${namespace}) if not exist`); - k8sSecret.createNamespace(namespace); + k8sModel.createNamespace(namespace); } const sign = async (username, application, expiration) => { diff --git a/src/rest-server/src/models/v2/group.js b/src/rest-server/src/models/v2/group.js index 751997794f..8dc8f0b424 100644 --- a/src/rest-server/src/models/v2/group.js +++ b/src/rest-server/src/models/v2/group.js @@ -23,43 +23,33 @@ const adapter = require('@pai/utils/manager/group/adapter/externalUtil'); const config = require('@pai/config/index'); const logger = require('@pai/config/logger'); const vcModel = require('@pai/models/v2/virtual-cluster'); -const k8sConfig = require('@pai/config/kubernetes'); const crudType = 'k8sSecret'; const crudGroup = crudUtil.getStorageObject(crudType); -let optionConfig = {}; -if (k8sConfig.apiserver.ca) { - optionConfig.k8sAPIServerCaFile = k8sConfig.apiserver.ca; -} -if (k8sConfig.apiserver.token) { - optionConfig.k8sAPIServerTokenFile = k8sConfig.apiserver.token; -} - -const crudConfig = crudGroup.initConfig(k8sConfig.apiserver.uri, optionConfig); let externalName2Groupname = {}; // crud groups const getGroup = async (groupname) => { - return await crudGroup.read(groupname, crudConfig); + return await crudGroup.read(groupname); }; const getAllGroup = async () => { - return await crudGroup.readAll(crudConfig); + return await crudGroup.readAll(); }; const createGroup = async (groupname, groupValue) => { - return await crudGroup.create(groupname, groupValue, crudConfig); + return await crudGroup.create(groupname, groupValue); }; const updateGroup = async (groupname, groupValue) => { - return await crudGroup.update(groupname, groupValue, crudConfig); + return await crudGroup.update(groupname, groupValue); }; const deleteGroup = async (groupname) => { // TODO: workaround for circular dependencies, need redesign module structure const userModel = require('@pai/models/v2/user'); - const ret = await crudGroup.remove(groupname, crudConfig); + const ret = await crudGroup.remove(groupname); // delete group from all user info let userList = await userModel.getAllUser(); let updateUserList = []; diff --git a/src/rest-server/src/models/v2/job-attempt.js b/src/rest-server/src/models/v2/job-attempt.js index d40603eb47..34af7a49d1 100644 --- a/src/rest-server/src/models/v2/job-attempt.js +++ b/src/rest-server/src/models/v2/job-attempt.js @@ -17,16 +17,14 @@ // module dependencies const _ = require('lodash'); -const axios = require('axios'); const {Client} = require('@elastic/elasticsearch'); const base32 = require('base32'); -const {Agent} = require('https'); const {isNil} = require('lodash'); const {convertToJobAttempt} = require('@pai/utils/frameworkConverter'); const launcherConfig = require('@pai/config/launcher'); -const {apiserver} = require('@pai/config/kubernetes'); const createError = require('@pai/utils/error'); +const k8sModel = require('@pai/models/kubernetes'); let elasticSearchClient; if (!_.isNil(process.env.ELASTICSEARCH_URI)) { @@ -82,12 +80,12 @@ const list = async (frameworkName) => { // get latest framework from k8s API let response; try { - response = await axios({ - method: 'get', - url: launcherConfig.frameworkPath(encodeName(frameworkName)), - headers: launcherConfig.requestHeaders, - httpsAgent: apiserver.ca && new Agent({ca: apiserver.ca}), - }); + response = await k8sModel.getClient().get( + launcherConfig.frameworkPath(encodeName(frameworkName)), + { + headers: launcherConfig.requestHeaders, + } + ); } catch (error) { if (error.response != null) { response = error.response; @@ -188,12 +186,12 @@ const get = async (frameworkName, jobAttemptIndex) => { let attemptFramework; let response; try { - response = await axios({ - method: 'get', - url: launcherConfig.frameworkPath(encodeName(frameworkName)), - headers: launcherConfig.requestHeaders, - httpsAgent: apiserver.ca && new Agent({ca: apiserver.ca}), - }); + response = await k8sModel.getClient().get( + launcherConfig.frameworkPath(encodeName(frameworkName)), + { + headers: launcherConfig.requestHeaders, + } + ); } catch (error) { if (error.response != null) { response = error.response; diff --git a/src/rest-server/src/models/v2/job/index.js b/src/rest-server/src/models/v2/job/index.js index 4d1d7a40c1..daba2ffd8e 100644 --- a/src/rest-server/src/models/v2/job/index.js +++ b/src/rest-server/src/models/v2/job/index.js @@ -17,14 +17,13 @@ // module dependencies -const {Agent} = require('https'); const axios = require('axios'); const status = require('statuses'); const config = require('@pai/config/index'); const logger = require('@pai/config/logger'); const yarnConfig = require('@pai/config/yarn'); const launcherConfig = require('@pai/config/launcher'); -const {apiserver} = require('@pai/config/kubernetes'); +const k8sModel = require('@pai/models/kubernetes'); if (launcherConfig.type === 'yarn') { if (config.env !== 'test') { @@ -52,12 +51,12 @@ if (launcherConfig.type === 'yarn') { if (config.env !== 'test') { // framework controller health check (async () => { - const response = await axios({ - method: 'get', - url: launcherConfig.healthCheckPath(), - headers: launcherConfig.requestHeaders, - httpsAgent: apiserver.ca && new Agent({ca: apiserver.ca}), - }); + 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 dfee867224..c00cad1916 100644 --- a/src/rest-server/src/models/v2/job/k8s.js +++ b/src/rest-server/src/models/v2/job/k8s.js @@ -17,25 +17,25 @@ // module dependencies -const {Agent} = require('https'); -const zlib = require('zlib'); const axios = require('axios'); +const zlib = require('zlib'); const yaml = require('js-yaml'); const base32 = require('base32'); const status = require('statuses'); const querystring = require('querystring'); const runtimeEnv = require('./runtime-env'); const launcherConfig = require('@pai/config/launcher'); -const {apiserver} = require('@pai/config/kubernetes'); const createError = require('@pai/utils/error'); const protocolSecret = require('@pai/utils/protocolSecret'); const userModel = require('@pai/models/v2/user'); +const k8sModel = require('@pai/models/kubernetes'); const env = require('@pai/utils/env'); const k8s = require('@pai/utils/k8sUtils'); const path = require('path'); const fs = require('fs'); const _ = require('lodash'); const logger = require('@pai/config/logger'); +const {apiserver} = require('@pai/config/kubernetes'); let exitSpecPath; if (process.env[env.exitSpecPath]) { @@ -184,12 +184,13 @@ const convertTaskDetail = async (taskStatus, ports, userName, jobName, taskRoleN // get container gpus let containerGpus = null; try { - const pod = (await axios({ - method: 'get', - url: launcherConfig.podPath(taskStatus.attemptStatus.podName), - headers: launcherConfig.requestHeaders, - httpsAgent: apiserver.ca && new Agent({ca: apiserver.ca}), - })).data; + const response = await k8sModel.getClient().get( + launcherConfig.podPath(taskStatus.attemptStatus.podName), + { + headers: launcherConfig.requestHeaders, + } + ); + const pod = response.data; if (launcherConfig.enabledHived) { const hivedSpec = yaml.load(pod.metadata.annotations['hivedscheduler.microsoft.com/pod-scheduling-spec']); if (hivedSpec && hivedSpec.affinityGroup && hivedSpec.affinityGroup.name) { @@ -307,12 +308,8 @@ const convertFrameworkDetail = async (framework) => { if (launcherConfig.enabledHived) { const affinityGroups = {}; try { - (await axios({ - method: 'get', - url: `${launcherConfig.hivedWebserviceUri}/v1/inspect/affinitygroups/`, - headers: launcherConfig.requestHeaders, - httpsAgent: apiserver.ca && new Agent({ca: apiserver.ca}), - })).items.forEach((affinityGroup) => { + const res = await axios.get(`${launcherConfig.hivedWebserviceUri}/v1/inspect/affinitygroups/`); + res.data.items.forEach((affinityGroup) => { affinityGroups[affinityGroup.metadata.name] = affinityGroup; }); } catch (err) { @@ -402,7 +399,7 @@ const generateTaskRole = (frameworkName, taskRole, labels, config, storageConfig }, { name: 'KUBE_APISERVER_ADDRESS', - value: launcherConfig.apiServerUri, + value: apiserver.uri, }, { name: 'GANG_ALLOCATION', @@ -663,13 +660,13 @@ const createPriorityClass = async (frameworkName, priority) => { let response; try { - response = await axios({ - method: 'post', - url: launcherConfig.priorityClassesPath(), - headers: launcherConfig.requestHeaders, - httpsAgent: apiserver.ca && new Agent({ca: apiserver.ca}), - data: priorityClass, - }); + response = await k8sModel.getClient().post( + launcherConfig.priorityClassesPath(), + { + headers: launcherConfig.requestHeaders, + data: priorityClass, + } + ); } catch (error) { if (error.response != null) { response = error.response; @@ -686,24 +683,24 @@ const patchPriorityClassOwner = async (frameworkName, frameworkUid) => { try { const headers = {...launcherConfig.requestHeaders}; headers['Content-Type'] = 'application/merge-patch+json'; - await axios({ - method: 'patch', - url: launcherConfig.priorityClassPath(`${encodeName(frameworkName)}-priority`), - headers, - httpsAgent: apiserver.ca && new Agent({ca: apiserver.ca}), - data: { - metadata: { - ownerReferences: [{ - apiVersion: launcherConfig.apiVersion, - kind: 'Framework', - name: encodeName(frameworkName), - uid: frameworkUid, - controller: false, - blockOwnerDeletion: false, - }], + await k8sModel.getClient().patch( + launcherConfig.priorityClassPath(`${encodeName(frameworkName)}-priority`), + { + headers, + data: { + metadata: { + ownerReferences: [{ + apiVersion: launcherConfig.apiVersion, + kind: 'Framework', + name: encodeName(frameworkName), + uid: frameworkUid, + controller: false, + blockOwnerDeletion: false, + }], + }, }, - }, - }); + } + ); } catch (error) { logger.warn('Failed to patch owner reference for priority class', error); } @@ -711,12 +708,12 @@ const patchPriorityClassOwner = async (frameworkName, frameworkUid) => { const deletePriorityClass = async (frameworkName) => { try { - await axios({ - method: 'delete', - url: launcherConfig.priorityClassPath(`${encodeName(frameworkName)}-priority`), - headers: launcherConfig.requestHeaders, - httpsAgent: apiserver.ca && new Agent({ca: apiserver.ca}), - }); + await k8sModel.getClient().delete( + launcherConfig.priorityClassPath(`${encodeName(frameworkName)}-priority`), + { + headers: launcherConfig.requestHeaders, + } + ); } catch (error) { logger.warn('Failed to delete priority class', error); } @@ -751,13 +748,13 @@ const createSecret = async (frameworkName, auths) => { let response; try { - response = await axios({ - method: 'post', - url: launcherConfig.secretsPath(), - headers: launcherConfig.requestHeaders, - httpsAgent: apiserver.ca && new Agent({ca: apiserver.ca}), - data: secret, - }); + response = await k8sModel.getClient().post( + launcherConfig.secretsPath(), + { + headers: launcherConfig.requestHeaders, + data: secret, + } + ); } catch (error) { if (error.response != null) { response = error.response; @@ -774,24 +771,24 @@ const patchSecretOwner = async (frameworkName, frameworkUid) => { try { const headers = {...launcherConfig.requestHeaders}; headers['Content-Type'] = 'application/merge-patch+json'; - await axios({ - method: 'patch', - url: launcherConfig.secretPath(`${encodeName(frameworkName)}-regcred`), - headers, - httpsAgent: apiserver.ca && new Agent({ca: apiserver.ca}), - data: { - metadata: { - ownerReferences: [{ - apiVersion: launcherConfig.apiVersion, - kind: 'Framework', - name: encodeName(frameworkName), - uid: frameworkUid, - controller: true, - blockOwnerDeletion: true, - }], + await k8sModel.getClient().patch( + launcherConfig.secretPath(`${encodeName(frameworkName)}-regcred`), + { + headers, + data: { + metadata: { + ownerReferences: [{ + apiVersion: launcherConfig.apiVersion, + kind: 'Framework', + name: encodeName(frameworkName), + uid: frameworkUid, + controller: true, + blockOwnerDeletion: true, + }], + }, }, - }, - }); + } + ); } catch (error) { logger.warn('Failed to patch owner reference for secret', error); } @@ -799,12 +796,12 @@ const patchSecretOwner = async (frameworkName, frameworkUid) => { const deleteSecret = async (frameworkName) => { try { - await axios({ - method: 'delete', - url: launcherConfig.secretPath(`${encodeName(frameworkName)}-regcred`), - headers: launcherConfig.requestHeaders, - httpsAgent: apiserver.ca && new Agent({ca: apiserver.ca}), - }); + await k8sModel.getClient().delete( + launcherConfig.secretPath(`${encodeName(frameworkName)}-regcred`), + { + headers: launcherConfig.requestHeaders, + } + ); } catch (error) { logger.warn('Failed to delete secret', error); } @@ -814,12 +811,12 @@ const list = async (filters) => { // send request to framework controller let response; try { - response = await axios({ - method: 'get', - url: `${launcherConfig.frameworksPath()}?${querystring.stringify(filters)}`, - headers: launcherConfig.requestHeaders, - httpsAgent: apiserver.ca && new Agent({ca: apiserver.ca}), - }); + response = await k8sModel.getClient().get( + `${launcherConfig.frameworksPath()}?${querystring.stringify(filters)}`, + { + headers: launcherConfig.requestHeaders, + } + ); } catch (error) { if (error.response != null) { response = error.response; @@ -841,12 +838,12 @@ const get = async (frameworkName) => { // send request to framework controller let response; try { - response = await axios({ - method: 'get', - url: launcherConfig.frameworkPath(encodeName(frameworkName)), - headers: launcherConfig.requestHeaders, - httpsAgent: apiserver.ca && new Agent({ca: apiserver.ca}), - }); + response = await k8sModel.getClient().get( + launcherConfig.frameworkPath(encodeName(frameworkName)), + { + headers: launcherConfig.requestHeaders, + } + ); } catch (error) { if (error.response != null) { response = error.response; @@ -902,13 +899,13 @@ const put = async (frameworkName, config, rawConfig) => { // send request to framework controller let response; try { - response = await axios({ - method: 'post', - url: launcherConfig.frameworksPath(), - headers: launcherConfig.requestHeaders, - httpsAgent: apiserver.ca && new Agent({ca: apiserver.ca}), - data: frameworkDescription, - }); + response = await k8sModel.getClient().post( + launcherConfig.frameworksPath(), + { + headers: launcherConfig.requestHeaders, + data: frameworkDescription, + } + ); } catch (error) { if (error.response != null) { response = error.response; @@ -936,17 +933,17 @@ const execute = async (frameworkName, executionType) => { try { const headers = {...launcherConfig.requestHeaders}; headers['Content-Type'] = 'application/merge-patch+json'; - response = await axios({ - method: 'patch', - url: launcherConfig.frameworkPath(encodeName(frameworkName)), - headers, - httpsAgent: apiserver.ca && new Agent({ca: apiserver.ca}), - data: { - spec: { - executionType: `${executionType.charAt(0)}${executionType.slice(1).toLowerCase()}`, + response = await k8sModel.getClient().patch( + launcherConfig.frameworkPath(encodeName(frameworkName)), + { + headers, + data: { + spec: { + executionType: `${executionType.charAt(0)}${executionType.slice(1).toLowerCase()}`, + }, }, - }, - }); + } + ); } catch (error) { if (error.response != null) { response = error.response; @@ -963,12 +960,12 @@ const getConfig = async (frameworkName) => { // send request to framework controller let response; try { - response = await axios({ - method: 'get', - url: launcherConfig.frameworkPath(encodeName(frameworkName)), - headers: launcherConfig.requestHeaders, - httpsAgent: apiserver.ca && new Agent({ca: apiserver.ca}), - }); + response = await k8sModel.getClient().get( + launcherConfig.frameworkPath(encodeName(frameworkName)), + { + headers: launcherConfig.requestHeaders, + } + ); } catch (error) { if (error.response != null) { response = error.response; diff --git a/src/rest-server/src/models/v2/storage.js b/src/rest-server/src/models/v2/storage.js index a3a7f6b49e..36a7af0e97 100644 --- a/src/rest-server/src/models/v2/storage.js +++ b/src/rest-server/src/models/v2/storage.js @@ -17,61 +17,49 @@ // module dependencies const crudUtil = require('@pai/utils/manager/storage/crudUtil'); -const k8sConfig = require('@pai/config/kubernetes'); const crudType = 'k8sSecret'; const crudStorage = crudUtil.getStorageObject(crudType); -let optionConfig = {}; -if (k8sConfig.apiserver.ca) { - optionConfig.k8sAPIServerCaFile = k8sConfig.apiserver.ca; -} -if (k8sConfig.apiserver.token) { - optionConfig.k8sAPIServerTokenFile = k8sConfig.apiserver.token; -} -const crudConfig = crudStorage.initConfig( - k8sConfig.apiserver.uri, - optionConfig -); // crud storage wrappers const getStorageServer = async (name) => { - return await crudStorage.readStorageServer(name, crudConfig); + return await crudStorage.readStorageServer(name); }; const getStorageServers = async (names) => { - return await crudStorage.readStorageServers(names, crudConfig); + return await crudStorage.readStorageServers(names); }; const getStorageConfig = async (name) => { - return await crudStorage.readStorageConfig(name, crudConfig); + return await crudStorage.readStorageConfig(name); }; const getStorageConfigs = async (names) => { - return await crudStorage.readStorageConfigs(names, crudConfig); + return await crudStorage.readStorageConfigs(names); }; const createStorageServer = async (name, value) => { - return await crudStorage.createStorageServer(name, value, crudConfig); + return await crudStorage.createStorageServer(name, value); }; const createStorageConfig = async (name, value) => { - return await crudStorage.createStorageConfig(name, value, crudConfig); + return await crudStorage.createStorageConfig(name, value); }; const updateStorageServer = async (name, value) => { - return await crudStorage.updateStorageServer(name, value, crudConfig); + return await crudStorage.updateStorageServer(name, value); }; const updateStorageConfig = async (name, value) => { - return await crudStorage.updateStorageConfig(name, value, crudConfig); + return await crudStorage.updateStorageConfig(name, value); }; const deleteStorageServer = async (name) => { - return await crudStorage.removeStorageServer(name, crudConfig); + return await crudStorage.removeStorageServer(name); }; const deleteStorageConfig = async (name) => { - return await crudStorage.removeStorageConfig(name, crudConfig); + return await crudStorage.removeStorageConfig(name); }; // module exports diff --git a/src/rest-server/src/models/v2/user.js b/src/rest-server/src/models/v2/user.js index d73b64099d..4855619d66 100644 --- a/src/rest-server/src/models/v2/user.js +++ b/src/rest-server/src/models/v2/user.js @@ -20,38 +20,29 @@ const crudUtil = require('@pai/utils/manager/user/crudUtil'); const user = require('@pai/utils/manager/user/user'); const groupModel = require('@pai/models/v2/group'); -const k8sConfig = require('@pai/config/kubernetes'); const crudType = 'k8sSecret'; const crudUser = crudUtil.getStorageObject(crudType); -let optionConfig = {}; -if (k8sConfig.apiserver.ca) { - optionConfig.k8sAPIServerCaFile = k8sConfig.apiserver.ca; -} -if (k8sConfig.apiserver.token) { - optionConfig.k8sAPIServerTokenFile = k8sConfig.apiserver.token; -} -const crudConfig = crudUser.initConfig(k8sConfig.apiserver.uri, optionConfig); // crud user wrappers const getUser = async (username) => { - return await crudUser.read(username, crudConfig); + return await crudUser.read(username); }; const getAllUser = async () => { - return await crudUser.readAll(crudConfig); + return await crudUser.readAll(); }; const createUser = async (username, value) => { - return await crudUser.create(username, value, crudConfig); + return await crudUser.create(username, value); }; const updateUser = async (username, value, updatePassword = false) => { - return await crudUser.update(username, value, crudConfig, updatePassword); + return await crudUser.update(username, value, updatePassword); }; const deleteUser = async (username) => { - return await crudUser.remove(username, crudConfig); + return await crudUser.remove(username); }; // it's an inplace encrypt! 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 6658712504..e1899fcf92 100644 --- a/src/rest-server/src/models/v2/virtual-cluster/k8s.js +++ b/src/rest-server/src/models/v2/virtual-cluster/k8s.js @@ -16,14 +16,12 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // module dependencies -const {Agent} = require('https'); +const yaml = require('js-yaml'); const createError = require('@pai/utils/error'); const vcConfig = require('@pai/config/vc'); const launcherConfig = require('@pai/config/launcher'); -const {apiserver} = require('@pai/config/kubernetes'); +const kubernetes = require('@pai/models/kubernetes'); const k8s = require('@pai/utils/k8sUtils'); -const axios = require('axios'); -const yaml = require('js-yaml'); const { resourceUnits, @@ -44,35 +42,27 @@ const mergeDict = (d1, d2, op) => { }; const fetchNodes = async (readiness=true) => { - const nodes = await axios({ - method: 'get', - url: `${apiserver.uri}/api/v1/nodes`, - httpsAgent: apiserver.ca && new Agent({ca: apiserver.ca}), - headers: apiserver.token && {Authorization: `Bearer ${apiserver.token}`}, - }); - return nodes.data.items.filter((node) => { + const nodes = await kubernetes.getNodes(); + return nodes.items.filter((node) => { if (node.metadata.labels['pai-worker'] !== 'true') { return false; } + // check node readiness - for (let i = node.status.conditions.length - 1; i >= 0; i --) { - const condition = node.status.conditions[i]; - if (condition.type === 'Ready' && condition.status !== 'Unknown') { - return readiness; - } + const readyCondition = node.status.conditions.find((x) => x.type === 'Ready'); + if (readyCondition && readyCondition.status !== 'Unknown') { + return readiness; + } else { + return !readiness; } - return !readiness; }); }; const fetchPods = async () => { - const pods = await axios({ - method: 'get', - url: `${apiserver.uri}/api/v1/pods?labelSelector=type=kube-launcher-task`, - httpsAgent: apiserver.ca && new Agent({ca: apiserver.ca}), - headers: apiserver.token && {Authorization: `Bearer ${apiserver.token}`}, + const pods = await kubernetes.getPods({ + labelSelector: {type: 'kube-launcher-task'}, }); - return pods.data.items.filter((pod) => { + return pods.items.filter((pod) => { return (pod.spec.nodeName && !(pod.status.phase === 'Succeeded' || pod.status.phase === 'Failed')); }); }; diff --git a/src/rest-server/src/routes/index.js b/src/rest-server/src/routes/index.js index ebdcbeb91c..8647afddba 100644 --- a/src/rest-server/src/routes/index.js +++ b/src/rest-server/src/routes/index.js @@ -25,7 +25,7 @@ const authnRouter = require('@pai/routes/authn'); const tokenRouter = require('@pai/routes/token'); const userRouter = require('@pai/routes/user'); const vcRouter = require('@pai/routes/vc'); -const kubernetesProxy = require('@pai/controllers/kubernetes-proxy'); +const k8sRouter = require('@pai/routes/kubernetes'); const router = new express.Router(); @@ -36,7 +36,7 @@ router.use(rewriteRouter); router.use('/token', tokenRouter); router.use('/user', userRouter); router.use('/virtual-clusters', vcRouter); -router.use('/kubernetes', kubernetesProxy); +router.use('/kubernetes', k8sRouter); router.use('/authn', authnRouter); if (launcherConfig.type === 'yarn') { diff --git a/src/rest-server/src/routes/kubernetes.js b/src/rest-server/src/routes/kubernetes.js new file mode 100644 index 0000000000..e2bf001ee5 --- /dev/null +++ b/src/rest-server/src/routes/kubernetes.js @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +const express = require('express'); +const token = require('@pai/middlewares/token'); +const kubernetes = require('@pai/models/kubernetes'); +const createError = require('@pai/utils/error'); + +const router = new express.Router(); + +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.`)); + } + try { + const nodes = await kubernetes.getNodes(); + res.status(200).json(nodes); + } catch (err) { + next(err); + } + }); + +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.`)); + } + try { + const pods = await kubernetes.getPods({namespace: req.query.namespace}); + res.status(200).json(pods); + } catch (err) { + next(err); + } + }); + +module.exports = router; diff --git a/src/rest-server/src/utils/frameworkConverter.js b/src/rest-server/src/utils/frameworkConverter.js index 9119872875..32cac8c9a8 100644 --- a/src/rest-server/src/utils/frameworkConverter.js +++ b/src/rest-server/src/utils/frameworkConverter.js @@ -1,13 +1,11 @@ const zlib = require('zlib'); -const axios = require('axios'); -const {Agent} = require('https'); const _ = require('lodash'); const yaml = require('js-yaml'); const path = require('path'); const fs = require('fs'); const launcherConfig = require('@pai/config/launcher'); -const {apiserver} = require('@pai/config/kubernetes'); +const k8sModel = require('@pai/models/kubernetes'); const k8s = require('@pai/utils/k8sUtils'); const logger = require('@pai/config/logger'); const env = require('@pai/utils/env'); @@ -318,12 +316,13 @@ const convertTaskDetail = async ( // get container gpus let containerGpus = null; try { - const pod = (await axios({ - method: 'get', - url: launcherConfig.podPath(taskStatus.attemptStatus.podName), - headers: launcherConfig.requestHeaders, - httpsAgent: apiserver.ca && new Agent({ca: apiserver.ca}), - })).data; + const response = await k8sModel.getClient().get( + launcherConfig.podPath(taskStatus.attemptStatus.podName), + { + headers: launcherConfig.requestHeaders, + } + ); + const pod = response.data; if (launcherConfig.enabledHived) { const isolation = pod.metadata.annotations[ diff --git a/src/rest-server/src/utils/manager/group/crudK8sSecret.js b/src/rest-server/src/utils/manager/group/crudK8sSecret.js index a68df4d4c3..420279cae7 100644 --- a/src/rest-server/src/utils/manager/group/crudK8sSecret.js +++ b/src/rest-server/src/utils/manager/group/crudK8sSecret.js @@ -16,19 +16,10 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. const Group = require('./group'); -const axios = require('axios'); -const {Agent} = require('https'); const logger = require('@pai/config/logger'); +const k8sModel = require('@pai/models/kubernetes'); -/** - * @typedef Config - * @property {string} namespace - kubernetes namespace - * @property {Object} requestConfig - RequestConfig - * @property {string} requestConfig.baseURL - BaseURL for axios - * @property {Number} requestConfig.maxRedirects - maxRedirects for axios - * @property {Object} requestConfig.httpsAgent - For kubernetes authn - * @property {Object} requestConfig.headers - For kubernetes authn - */ +const GROUP_NAMESPACE = process.env.PAI_GROUP_NAMESPACE || 'pai-group'; /** * @typedef Group @@ -38,47 +29,17 @@ const logger = require('@pai/config/logger'); * @property {Object} extension - extension field */ -/** - * @function initConfig - Init the kubernetes configuration for user manager's crud. - * @param {string} apiServerUri - Required config, the uri of kubernetes APIServer. - * @param {Object} option - Config for kubernetes APIServer's authn. - * @param {string} option.k8sAPIServerCaFile - Optional config, the ca file path of kubernetes APIServer. - * @param {string} option.k8sAPIServerTokenFile - Optional config, the token file path of kubernetes APIServer. - * @return {Config} config - */ -function initConfig(apiServerUri, option = {}) { - const namespaces = process.env.PAI_GROUP_NAMESPACE; - const config = { - 'apiServerUri': apiServerUri, - 'namespace': namespaces? namespaces : 'pai-group', - 'requestConfig': { - 'baseURL': `${apiServerUri}/api/v1/namespaces/`, - 'maxRedirects': 0, - }, - }; - if ('k8sAPIServerCaFile' in option) { - const ca = option.k8sAPIServerCaFile; - config.requestConfig.httpsAgent = new Agent({ca}); - } - if ('k8sAPIServerTokenFile' in option) { - const token = option.k8sAPIServerTokenFile; - config.requestConfig.headers = {Authorization: `Bearer ${token}`}; - } - return config; -} - /** * @function read - return a Group's info based on the GroupName. * @async * @param {string} key - Group name - * @param {Config} config - Config for kubernetes APIServer. You could generate it from initConfig(apiServerUri, option). * @return {Promise} A promise to the Group instance */ -async function read(key, config) { +async function read(key) { try { - const request = axios.create(config.requestConfig); + const request = k8sModel.getClient('/api/v1/namespaces'); const hexKey = Buffer.from(key).toString('hex'); - const response = await request.get(`${config.namespace}/secrets/${hexKey}`, { + const response = await request.get(`${GROUP_NAMESPACE}/secrets/${hexKey}`, { headers: { 'Accept': 'application/json', }, @@ -103,13 +64,12 @@ async function read(key, config) { /** * @function readAll - return all Groups' info. * @async - * @param {Config} config - Config for kubernetes APIServer. You could generate it from initConfig(apiServerUri, option). * @return {Promise} A promise to all Group instance list. */ -async function readAll(config) { +async function readAll() { try { - const request = axios.create(config.requestConfig); - const response = await request.get(`${config.namespace}/secrets`, { + const request = k8sModel.getClient('/api/v1/namespaces'); + const response = await request.get(`${GROUP_NAMESPACE}/secrets`, { headers: { 'Accept': 'application/json', }, @@ -126,7 +86,7 @@ async function readAll(config) { }); allGroupInstance.push(groupInstance); } catch (error) { - logger.debug(`secret ${item['metadata']['name']} is filtered in ${config.namespace} due to group schema`); + logger.debug(`secret ${item['metadata']['name']} is filtered in ${GROUP_NAMESPACE} due to group schema`); } } return allGroupInstance; @@ -144,12 +104,11 @@ async function readAll(config) { * @async * @param {string} key - Group name * @param {User} value - Group info - * @param {Config} config - Config for kubernetes APIServer. You could generate it from initConfig(apiServerUri, option). * @return {Promise} A promise to the Group instance. */ -async function create(key, value, config) { +async function create(key, value) { try { - const request = axios.create(config.requestConfig); + const request = k8sModel.getClient('/api/v1/namespaces'); const hexKey = key ? Buffer.from(key).toString('hex') : ''; let groupInstance = Group.createGroup({ 'groupname': value['groupname'], @@ -166,7 +125,7 @@ async function create(key, value, config) { 'extension': Buffer.from(JSON.stringify(groupInstance['extension'])).toString('base64'), }, }; - return await request.post(`${config.namespace}/secrets`, groupData); + return await request.post(`${GROUP_NAMESPACE}/secrets`, groupData); } catch (error) { if (error.response) { throw error.response; @@ -181,12 +140,11 @@ async function create(key, value, config) { * @async * @param {string} key - Group name * @param {User} value - Group info - * @param {Config} config - Config for kubernetes APIServer. You could generate it from initConfig(apiServerUri, option). * @return {Promise} A promise to the User instance. */ -async function update(key, value, config) { +async function update(key, value) { try { - const request = axios.create(config.requestConfig); + const request = k8sModel.getClient('/api/v1/namespaces'); const hexKey = Buffer.from(key).toString('hex'); let groupInstance = Group.createGroup({ 'groupname': value['groupname'], @@ -203,7 +161,7 @@ async function update(key, value, config) { 'extension': Buffer.from(JSON.stringify(groupInstance['extension'])).toString('base64'), }, }; - return await request.put(`${config.namespace}/secrets/${hexKey}`, groupData); + return await request.put(`${GROUP_NAMESPACE}/secrets/${hexKey}`, groupData); } catch (error) { if (error.response) { throw error.response; @@ -217,14 +175,13 @@ async function update(key, value, config) { * @function Remove - Remove a group entry to kubernetes secrets. * @async * @param {string} key - Group name - * @param {Config} config - Config for kubernetes APIServer. You could generate it from initConfig(apiServerUri, option). * @return {Promise} */ -async function remove(key, config) { +async function remove(key) { try { - const request = axios.create(config.requestConfig); + const request = k8sModel.getClient('/api/v1/namespaces'); const hexKey = Buffer.from(key).toString('hex'); - return await request.delete(`${config.namespace}/secrets/${hexKey}`, { + return await request.delete(`${GROUP_NAMESPACE}/secrets/${hexKey}`, { headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', @@ -239,4 +196,4 @@ async function remove(key, config) { } } -module.exports = {initConfig, create, read, readAll, update, remove}; +module.exports = {create, read, readAll, update, remove}; diff --git a/src/rest-server/src/utils/manager/storage/crudK8sSecret.js b/src/rest-server/src/utils/manager/storage/crudK8sSecret.js index ae34b75d1a..079a627498 100644 --- a/src/rest-server/src/utils/manager/storage/crudK8sSecret.js +++ b/src/rest-server/src/utils/manager/storage/crudK8sSecret.js @@ -16,20 +16,12 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. const Storage = require('./storage'); -const axios = require('axios'); -const {Agent} = require('https'); const logger = require('@pai/config/logger'); const createError = require('@pai/utils/error'); +const k8sModel = require('@pai/models/kubernetes'); + +const STORAGE_NAMESPACE = process.env.PAI_STORAGE_NAMESPACE || 'pai-storage'; -/** - * @typedef Config - * @property {string} namespace - kubernetes namespace - * @property {Object} requestConfig - RequestConfig - * @property {string} requestConfig.baseURL - BaseURL for axios - * @property {Number} requestConfig.maxRedirects - maxRedirects for axios - * @property {Object} requestConfig.httpsAgent - For kubernetes authn - * @property {Object} requestConfig.headers - For kubernetes authn - */ /** * @typedef StorageServer @@ -46,48 +38,19 @@ const createError = require('@pai/utils/error'); * @property {Array} ConfigInstance.mountInfos - config data */ -/** - * @function initConfig - Init the kubernetes configuration for user manager's crud. - * @param {string} apiServerUri - Required config, the uri of kubernetes APIServer. - * @param {Object} option - Config for kubernetes APIServer's authn. - * @param {string} option.k8sAPIServerCaFile - Optional config, the ca file path of kubernetes APIServer. - * @param {string} option.k8sAPIServerTokenFile - Optional config, the token file path of kubernetes APIServer. - * @return {Config} config - */ -function initConfig(apiServerUri, option = {}) { - const namespaces = process.env.PAI_STORAGE_NAMESPACE; - const config = { - namespace: namespaces ? namespaces : 'pai-storage', - requestConfig: { - baseURL: `${apiServerUri}/api/v1/namespaces/`, - maxRedirects: 0, - }, - }; - if ('k8sAPIServerCaFile' in option) { - const ca = option.k8sAPIServerCaFile; - config.requestConfig.httpsAgent = new Agent({ca}); - } - if ('k8sAPIServerTokenFile' in option) { - const token = option.k8sAPIServerTokenFile; - config.requestConfig.headers = {Authorization: `Bearer ${token}`}; - } - return config; -} - /** * @function readStorageServer - return a Storage Server's info based on spn. * @async * @param {string} key - Server spn - * @param {Config} config - Config for kubernetes APIServer. You could generate it from initConfig(apiServerUri, option). * @return {Promise} A promise to the StorageServer instance */ -async function readStorageServer(key, config) { +async function readStorageServer(key) { try { - const request = axios.create(config.requestConfig); + const request = k8sModel.getClient('/api/v1/namespaces'); - logger.info(`${config.namespace}/secrets/storage-server`); + logger.info(`${STORAGE_NAMESPACE}/secrets/storage-server`); const response = await request.get( - `${config.namespace}/secrets/storage-server`, + `${STORAGE_NAMESPACE}/secrets/storage-server`, { headers: { Accept: 'application/json', @@ -123,14 +86,13 @@ async function readStorageServer(key, config) { * @function readStorageServers - return Storage Server's infos based on spns. * @async * @param {string[]} keys - An array of server spns. If array is empty, return all StorageServers. - * @param {Config} config - Config for kubernetes APIServer. You could generate it from initConfig(apiServerUri, option). * @return {Promise} A promise to the StorageServer instances */ -async function readStorageServers(keys, config) { +async function readStorageServers(keys) { try { - const request = axios.create(config.requestConfig); + const request = k8sModel.getClient('/api/v1/namespaces'); const response = await request.get( - `${config.namespace}/secrets/storage-server`, + `${STORAGE_NAMESPACE}/secrets/storage-server`, { headers: { Accept: 'application/json', @@ -180,14 +142,13 @@ async function readStorageServers(keys, config) { * @function readStorageConfig - return a Storage Config's info based on config name. * @async * @param {string} key - Config name - * @param {Config} config - Config for kubernetes APIServer. You could generate it from initConfig(apiServerUri, option). * @return {Promise} A promise to the StorageConfig instance */ -async function readStorageConfig(key, config) { +async function readStorageConfig(key) { try { - const request = axios.create(config.requestConfig); + const request = k8sModel.getClient('/api/v1/namespaces'); const response = await request.get( - `${config.namespace}/secrets/storage-config`, + `${STORAGE_NAMESPACE}/secrets/storage-config`, { headers: { Accept: 'application/json', @@ -217,14 +178,13 @@ async function readStorageConfig(key, config) { * @function readStorageConfigs - return Storage Configs's infos based on config names. * @async * @param {string[]} keys - An array of config names. If array is empty, return all StorageConfigs. - * @param {Config} config - Config for kubernetes APIServer. You could generate it from initConfig(apiServerUri, option). * @return {Promise} A promise to the StorageConfig instances */ -async function readStorageConfigs(keys, config) { +async function readStorageConfigs(keys) { try { - const request = axios.create(config.requestConfig); + const request = k8sModel.getClient('/api/v1/namespaces'); const response = await request.get( - `${config.namespace}/secrets/storage-config`, + `${STORAGE_NAMESPACE}/secrets/storage-config`, { headers: { Accept: 'application/json', @@ -269,16 +229,15 @@ async function readStorageConfigs(keys, config) { * @param {string} op - Patch operation type, could be add, replace or remove * @param {string} key - Storage Server name * @param {StorageServer} value - Storage Server info, should be null when op is remove - * @param {Config} config - Config for kubernetes APIServer. You could generate it from initConfig(apiServerUri, option). * @return {Promise} A promise to patch result. */ -async function patchStorageServer(op, key, value, config) { +async function patchStorageServer(op, key, value) { if (key === 'empty') { throw createError('Forbidden', 'ForbiddenKeyError', 'Key \'empty\' is system reserved and should not be modified!'); } try { - const request = axios.create(config.requestConfig); + const request = k8sModel.getClient('/api/v1/namespaces'); let serverData = { op: op, path: `/data/${key}`, @@ -299,7 +258,7 @@ async function patchStorageServer(op, key, value, config) { } logger.info(serverData); return await request.patch( - `${config.namespace}/secrets/storage-server`, + `${STORAGE_NAMESPACE}/secrets/storage-server`, [serverData], { headers: { @@ -324,16 +283,15 @@ async function patchStorageServer(op, key, value, config) { * @param {string} op - Patch operation type, could be add, replace or remove * @param {string} key - Storage Config name * @param {StorageServer} value - Storage Config info, should be null when op is remove - * @param {Config} config - Config for kubernetes APIServer. You could generate it from initConfig(apiServerUri, option). * @return {Promise} A promise to patch result. */ -async function patchStorageConfig(op, key, value, config) { +async function patchStorageConfig(op, key, value) { if (key === 'empty') { throw createError('Forbidden', 'ForbiddenKeyError', 'Key \'empty\' is system reserved and should not be modified!'); } try { - const request = axios.create(config.requestConfig); + const request = k8sModel.getClient('/api/v1/namespaces'); let configData = { op: op, path: `/data/${key}`, @@ -345,7 +303,7 @@ async function patchStorageConfig(op, key, value, config) { ); } return await request.patch( - `${config.namespace}/secrets/storage-config`, + `${STORAGE_NAMESPACE}/secrets/storage-config`, [configData], { headers: { @@ -369,11 +327,10 @@ async function patchStorageConfig(op, key, value, config) { * @async * @param {string} key - Storage Server name * @param {StorageServer} value - Storage Server info - * @param {Config} config - Config for kubernetes APIServer. You could generate it from initConfig(apiServerUri, option). * @return {Promise} A promise to patch result. */ -async function createStorageServer(key, value, config) { - return await patchStorageServer('add', key, value, config); +async function createStorageServer(key, value) { + return await patchStorageServer('add', key, value); } /** @@ -381,11 +338,10 @@ async function createStorageServer(key, value, config) { * @async * @param {string} key - Storage config name * @param {StorageConfig} value - Storage config info - * @param {Config} config - Config for kubernetes APIServer. You could generate it from initConfig(apiServerUri, option). * @return {Promise} A promise to patch result. */ -async function createStorageConfig(key, value, config) { - return await patchStorageConfig('add', key, value, config); +async function createStorageConfig(key, value) { + return await patchStorageConfig('add', key, value); } /** @@ -393,11 +349,10 @@ async function createStorageConfig(key, value, config) { * @async * @param {string} key - Stroage server name * @param {StorageServer} value - Stroage server info - * @param {Config} config - Config for kubernetes APIServer. You could generate it from initConfig(apiServerUri, option). * @return {Promise} A promise to patch result. */ -async function updateStorageServer(key, value, config) { - return await patchStorageServer('replace', key, value, config); +async function updateStorageServer(key, value) { + return await patchStorageServer('replace', key, value); } /** @@ -405,37 +360,33 @@ async function updateStorageServer(key, value, config) { * @async * @param {string} key - Stroage Config name * @param {StorageConfig} value - Stroage Config info - * @param {Config} config - Config for kubernetes APIServer. You could generate it from initConfig(apiServerUri, option). * @return {Promise} A promise to patch result. */ -async function updateStorageConfig(key, value, config) { - return await patchStorageConfig('replace', key, value, config); +async function updateStorageConfig(key, value) { + return await patchStorageConfig('replace', key, value); } /** * @function removeStorageServer - Remove a Storage Server entry from kubernetes secrets. * @async * @param {string} key - Storage Server name - * @param {Config} config - Config for kubernetes APIServer. You could generate it from initConfig(apiServerUri, option). * @return {Promise} A promise to patch result. */ -async function removeStorageServer(key, config) { - return await patchStorageServer('remove', key, null, config); +async function removeStorageServer(key) { + return await patchStorageServer('remove', key, null); } /** * @function removeStorageConfig - Remove a Storage Config entry from kubernetes secrets. * @async * @param {string} key - Storage Config name - * @param {Config} config - Config for kubernetes APIServer. You could generate it from initConfig(apiServerUri, option). * @return {Promise} A promise to patch result. */ -async function removeStorageConfig(key, config) { - return await patchStorageConfig('remove', key, null, config); +async function removeStorageConfig(key) { + return await patchStorageConfig('remove', key, null); } module.exports = { - initConfig, createStorageServer, createStorageConfig, readStorageServer, diff --git a/src/rest-server/src/utils/manager/user/crudK8sSecret.js b/src/rest-server/src/utils/manager/user/crudK8sSecret.js index c01f212de4..a3d75f2e0f 100644 --- a/src/rest-server/src/utils/manager/user/crudK8sSecret.js +++ b/src/rest-server/src/utils/manager/user/crudK8sSecret.js @@ -16,19 +16,10 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. const User = require('./user'); -const axios = require('axios'); -const {Agent} = require('https'); const logger = require('@pai/config/logger'); +const k8sModel = require('@pai/models/kubernetes'); -/** - * @typedef Config - * @property {string} namespace - kubernetes namespace - * @property {Object} requestConfig - RequestConfig - * @property {string} requestConfig.baseURL - BaseURL for axios - * @property {Number} requestConfig.maxRedirects - maxRedirects for axios - * @property {Object} requestConfig.httpsAgent - For kubernetes authn - * @property {Object} requestConfig.headers - For kubernetes authn - */ +const USER_NAMESPACE = process.env.PAI_USER_NAMESPACE || 'pai-user-v2'; /** * @typedef User @@ -39,46 +30,17 @@ const logger = require('@pai/config/logger'); * @property {Object} UserInstance.extension - extension field */ -/** - * @function initConfig - Init the kubernetes configuration for user manager's crud. - * @param {string} apiServerUri - Required config, the uri of kubernetes APIServer. - * @param {Object} option - Config for kubernetes APIServer's authn. - * @param {string} option.k8sAPIServerCaFile - Optional config, the ca file path of kubernetes APIServer. - * @param {string} option.k8sAPIServerTokenFile - Optional config, the token file path of kubernetes APIServer. - * @return {Config} config -*/ -function initConfig(apiServerUri, option = {}) { - const namespaces = process.env.PAI_USER_NAMESPACE; - const config = { - 'namespace': namespaces ? namespaces : 'pai-user-v2', - 'requestConfig': { - 'baseURL': `${apiServerUri}/api/v1/namespaces/`, - 'maxRedirects': 0, - }, - }; - if ('k8sAPIServerCaFile' in option) { - const ca = option.k8sAPIServerCaFile; - config.requestConfig.httpsAgent = new Agent({ca}); - } - if ('k8sAPIServerTokenFile' in option) { - const token = option.k8sAPIServerTokenFile; - config.requestConfig.headers = {Authorization: `Bearer ${token}`}; - } - return config; -} - /** * @function read - return a user's info based on the UserName. * @async * @param {string} key - User name - * @param {Config} config - Config for kubernetes APIServer. You could generate it from initConfig(apiServerUri, option). * @return {Promise} A promise to the User instance */ -async function read(key, config) { +async function read(key) { try { - const request = axios.create(config.requestConfig); + const request = k8sModel.getClient('/api/v1/namespaces/'); const hexKey = Buffer.from(key).toString('hex'); - const response = await request.get(`${config.namespace}/secrets/${hexKey}`.toString(), { + const response = await request.get(`${USER_NAMESPACE}/secrets/${hexKey}`.toString(), { headers: { 'Accept': 'application/json', }, @@ -104,13 +66,12 @@ async function read(key, config) { /** * @function readAll - return all users' info. * @async - * @param {Config} config - Config for kubernetes APIServer. You could generate it from initConfig(apiServerUri, option). * @return {Promise} A promise to all User instance list. */ -async function readAll(config) { +async function readAll() { try { - const request = axios.create(config.requestConfig); - const response = await request.get(`${config.namespace}/secrets`.toString(), { + const request = k8sModel.getClient('/api/v1/namespaces/'); + const response = await request.get(`${USER_NAMESPACE}/secrets`.toString(), { headers: { 'Accept': 'application/json', }, @@ -128,7 +89,7 @@ async function readAll(config) { }); allUserInstance.push(userInstance); } catch (error) { - logger.debug(`secret ${item['metadata']['name']} is filtered in ${config.namespace} due to user schema`); + logger.debug(`secret ${item['metadata']['name']} is filtered in ${USER_NAMESPACE} due to user schema`); } } return allUserInstance; @@ -146,12 +107,11 @@ async function readAll(config) { * @async * @param {string} key - User name * @param {User} value - User info - * @param {Config} config - Config for kubernetes APIServer. You could generate it from initConfig(apiServerUri, option). * @return {Promise} A promise to the User instance. */ -async function create(key, value, config) { +async function create(key, value) { try { - const request = axios.create(config.requestConfig); + const request = k8sModel.getClient('/api/v1/namespaces/'); const hexKey = Buffer.from(key).toString('hex'); let userInstance = User.createUser( { @@ -173,7 +133,7 @@ async function create(key, value, config) { 'extension': Buffer.from(JSON.stringify(userInstance['extension'])).toString('base64'), }, }; - let response = await request.post(`${config.namespace}/secrets`, userData); + let response = await request.post(`${USER_NAMESPACE}/secrets`, userData); return response; } catch (error) { if (error.response) { @@ -189,13 +149,12 @@ async function create(key, value, config) { * @async * @param {string} key - User name * @param {User} value - User info - * @param {Config} config - Config for kubernetes APIServer. You could generate it from initConfig(apiServerUri, option). * @param {Boolean} updatePassword - With value false, the password won't be encrypt again. * @return {Promise} A promise to the User instance. */ -async function update(key, value, config, updatePassword = false) { +async function update(key, value, updatePassword = false) { try { - const request = axios.create(config.requestConfig); + const request = k8sModel.getClient('/api/v1/namespaces/'); const hexKey = Buffer.from(key).toString('hex'); let userInstance = User.createUser( { @@ -219,7 +178,7 @@ async function update(key, value, config, updatePassword = false) { 'extension': Buffer.from(JSON.stringify(userInstance['extension'])).toString('base64'), }, }; - let response = await request.put(`${config.namespace}/secrets/${hexKey}`, userData); + let response = await request.put(`${USER_NAMESPACE}/secrets/${hexKey}`, userData); return response; } catch (error) { if (error.response) { @@ -234,14 +193,13 @@ async function update(key, value, config, updatePassword = false) { * @function Remove - Remove an user entry from kubernetes secrets. * @async * @param {string} key - User name - * @param {Config} config - Config for kubernetes APIServer. You could generate it from initConfig(apiServerUri, option). * @return {Promise} */ -async function remove(key, config) { +async function remove(key) { try { - const request = axios.create(config.requestConfig); + const request = k8sModel.getClient('/api/v1/namespaces/'); const hexKey = Buffer.from(key).toString('hex'); - return await request.delete(`${config.namespace}/secrets/${hexKey}`, { + return await request.delete(`${USER_NAMESPACE}/secrets/${hexKey}`, { headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', @@ -256,4 +214,4 @@ async function remove(key, config) { } } -module.exports = {initConfig, create, read, readAll, update, remove}; +module.exports = {create, read, readAll, update, remove}; diff --git a/src/rest-server/src/utils/userSecret.js b/src/rest-server/src/utils/userSecret.js index de30535def..022b6dbb17 100644 --- a/src/rest-server/src/utils/userSecret.js +++ b/src/rest-server/src/utils/userSecret.js @@ -16,21 +16,20 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // module dependencies -const axios = require('axios'); +const {getClient} = require('@pai/models/kubernetes'); const StorageBase = require('./storageBase'); class UserSecret extends StorageBase { constructor(options) { super(); - this.secretRootUri = `${options.paiUserNameSpace}/secrets`; - this.request = axios.create(options.requestConfig); + this.request = getClient(`/api/v1/namespaces/${options.paiUserNameSpace}/secrets`); this.options = options; } async get(key, options) { try { const hexKey = key ? Buffer.from(key).toString('hex') : ''; - const response = await this.request.get(`${this.secretRootUri}/${hexKey}`, { + const response = await this.request.get(`/${hexKey}`, { headers: { 'Accept': 'application/json', }, @@ -81,9 +80,9 @@ class UserSecret extends StorageBase { } let response = null; if (options && options['update']) { - response = await this.request.put(`${this.secretRootUri}/${hexKey}`, userData); + response = await this.request.put(`/${hexKey}`, userData); } else { - response = await this.request.post(`${this.secretRootUri}`, userData); + response = await this.request.post('', userData); } return response; } catch (error) { @@ -94,7 +93,7 @@ class UserSecret extends StorageBase { async delete(key, options) { try { const hexKey = key ? Buffer.from(key).toString('hex') : ''; - let response = await this.request.delete(`${this.secretRootUri}/${hexKey}`, { + let response = await this.request.delete(`/${hexKey}`, { headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', diff --git a/src/rest-server/test/groupModelK8sCRUD.js b/src/rest-server/test/groupModelK8sCRUD.js index 1fb7187501..9050332de8 100644 --- a/src/rest-server/test/groupModelK8sCRUD.js +++ b/src/rest-server/test/groupModelK8sCRUD.js @@ -18,8 +18,6 @@ // test const groupK8sCRUD = require('@pai/utils/manager/group/crudK8sSecret'); -const groupK8sCRUDConfig = groupK8sCRUD.initConfig(process.env.K8S_APISERVER_URI); - describe('Group model k8s secret get function test', () => { afterEach(function() { if (!nock.isDone()) { @@ -107,20 +105,20 @@ describe('Group model k8s secret get function test', () => { // positive test case // get exist single key value pair it('Should return whole group list.', async () => { - const res = await groupK8sCRUD.readAll(groupK8sCRUDConfig); + const res = await groupK8sCRUD.readAll(); return expect(res).to.have.lengthOf(2); }); // negative test case // get non-exist user it('Should report user not found error', async ()=> { - return await expect(groupK8sCRUD.read('non_exist', groupK8sCRUDConfig)).to.be.rejected; + return await expect(groupK8sCRUD.read('non_exist')).to.be.rejected; }); // positive test case // find specific user it('Should return specific group info.', async () => { - const res = await groupK8sCRUD.read('paitest', groupK8sCRUDConfig); + const res = await groupK8sCRUD.read('paitest'); return expect(res).to.deep.equal({ groupname: 'paitest', description: 'test', @@ -216,7 +214,7 @@ describe('Group model k8s secret set function test', () => { 'externalName': '1234', 'extension': {}, }; - const res = await groupK8sCRUD.create('newuser', updateGroup, groupK8sCRUDConfig); + const res = await groupK8sCRUD.create('newuser', updateGroup); return expect(res, 'status').to.have.status(200); }); @@ -228,7 +226,7 @@ describe('Group model k8s secret set function test', () => { 'externalName': '1234', 'extension': {}, }; - const res = await groupK8sCRUD.update('existuser', updateGroup, groupK8sCRUDConfig); + const res = await groupK8sCRUD.update('existuser', updateGroup); return expect(res, 'status').to.have.status(200); }); }); @@ -279,11 +277,11 @@ describe('User Model k8s secret delete function test', () => { // delete exist user it('should delete an exist group successfully', async () => { - const res = await groupK8sCRUD.remove('existuser', groupK8sCRUDConfig); + const res = await groupK8sCRUD.remove('existuser'); return expect(res, 'status').to.have.status(200); }); it('should failed to delete an non-exist user', async () => { - return await expect(groupK8sCRUD.remove('nonexistuser', groupK8sCRUDConfig)).to.be.rejected; + return await expect(groupK8sCRUD.remove('nonexistuser')).to.be.rejected; }); }); diff --git a/src/rest-server/test/k8sModel.js b/src/rest-server/test/k8sModel.js new file mode 100644 index 0000000000..f43394be3c --- /dev/null +++ b/src/rest-server/test/k8sModel.js @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +const chai = require('chai'); +const nockUtils = require('./utils/nock'); +const k8sModel = require('@pai/models/kubernetes'); + +describe('kubernetes model', () => { + afterEach(function() { + if (!nock.isDone()) { + nock.cleanAll(); + throw new Error('Not all nock interceptors were used!'); + } + }); + + it('Case 1 (Positive): encode selector', (done) => { + const res = k8sModel.encodeSelector({pos1: 1, pos2: 'pos2'}, {neg1: 1, neg2: 'neg2'}); + chai.expect(res).to.equal('pos1=1,pos2=pos2,neg1!=1,neg2!=neg2'); + done(); + }); + + it('Case 2 (Positive): create namespace', (done) => { + const namespace = 'test'; + nock(apiServerRootUri) + .post(`/api/v1/namespaces/`, {metadata: {name: namespace}}) + .reply(200); + k8sModel.createNamespace(namespace).then( + () => done() + ).catch( + (err) => done(err) + ); + }); + + it('Case 3 (Positive): get nodes', (done) => { + const username = 'user'; + const token = nockUtils.registerAdminTokenCheck(username); + const nodes = { + kind: 'NodeList', + apiVersion: 'v1', + items: [ + { + metadata: { + name: 'ip1', + }, + }, + { + metadata: { + name: 'ip2', + }, + }, + ], + }; + nock(apiServerRootUri) + .get('/api/v1/nodes') + .reply(200, nodes); + chai.request(global.server) + .get('/api/v1/kubernetes/nodes') + .set('Authorization', `Bearer ${token}`) + .send() + .end((err, res) => { + chai.expect(res, 'status code').to.have.status(200); + chai.expect(res.body).to.be.deep.equal(nodes); + done(); + }); + }); + + it('Case 4 (Positive): get pods', (done) => { + const username = 'user'; + const token = nockUtils.registerAdminTokenCheck(username); + const pods = { + kind: 'PodList', + apiVersion: 'v1', + items: [ + { + metadata: { + name: 'pod1', + }, + }, + { + metadata: { + name: 'pod2', + }, + }, + ], + }; + nock(apiServerRootUri) + .get('/api/v1/pods') + .reply(200, pods); + chai.request(global.server) + .get('/api/v1/kubernetes/pods') + .set('Authorization', `Bearer ${token}`) + .send() + .end((err, res) => { + chai.expect(res, 'status code').to.have.status(200); + chai.expect(res.body).to.be.deep.equal(pods); + done(); + }); + }); +}); diff --git a/src/rest-server/test/k8sSecret.js b/src/rest-server/test/k8sSecret.js index 5fcf7b0796..d8d31b428c 100644 --- a/src/rest-server/test/k8sSecret.js +++ b/src/rest-server/test/k8sSecret.js @@ -21,10 +21,6 @@ const dbUtility = require('@pai/utils/dbUtil'); const db = dbUtility.getStorageObject('UserSecret', { 'paiUserNameSpace': 'pai-user', - 'requestConfig': { - baseURL: process.env.K8S_APISERVER_URI + '/api/v1/namespaces/', - maxRedirects: 0, - }, }); describe('k8s secret get function test', () => { afterEach(function() { diff --git a/src/rest-server/test/userModelK8sCRUD.js b/src/rest-server/test/userModelK8sCRUD.js index 704acff2a1..5f17a1cc18 100644 --- a/src/rest-server/test/userModelK8sCRUD.js +++ b/src/rest-server/test/userModelK8sCRUD.js @@ -18,8 +18,6 @@ // test const userK8sCRUD = require('@pai/utils/manager/user/crudK8sSecret'); -const userK8sCRUDConfig = userK8sCRUD.initConfig(process.env.K8S_APISERVER_URI); - describe('User model k8s secret get function test', () => { afterEach(function() { if (!nock.isDone()) { @@ -110,20 +108,20 @@ describe('User model k8s secret get function test', () => { // positive test case // get exist single key value pair it('Should return whole user list.', async () => { - const res = await userK8sCRUD.readAll(userK8sCRUDConfig); + const res = await userK8sCRUD.readAll(); return expect(res).to.have.lengthOf(2); }); // negative test case // get non-exist user it('Should report user not found error', async ()=> { - return await expect(userK8sCRUD.read('non_exist', userK8sCRUDConfig)).to.be.rejected; + return await expect(userK8sCRUD.read('non_exist')).to.be.rejected; }); // positive test case // find specific user it('Should return specific user info.', async () => { - const res = await userK8sCRUD.read('paitest', userK8sCRUDConfig); + const res = await userK8sCRUD.read('paitest'); return expect(res).to.deep.equal({ username: 'paitest', password: '31a744c3af89056024ff62c356f547ddc353ad727d310a773718812982d5c6efc3bff70db5e1043bd21d2edc883c8cd4f9e74a1e5205433649361148ba896434', @@ -220,7 +218,7 @@ describe('User model k8s secret set function test', () => { 'grouplist': ['test'], 'extension': {}, }; - const res = await userK8sCRUD.create('newuser', updateUser, userK8sCRUDConfig); + const res = await userK8sCRUD.create('newuser', updateUser); return expect(res, 'status').to.have.status(200); }); @@ -233,7 +231,7 @@ describe('User model k8s secret set function test', () => { 'grouplist': ['test'], 'extension': {}, }; - const res = await userK8sCRUD.update('existuser', updateUser, userK8sCRUDConfig, true); + const res = await userK8sCRUD.update('existuser', updateUser, true); return expect(res, 'status').to.have.status(200); }); }); @@ -284,11 +282,11 @@ describe('User Model k8s secret delete function test', () => { // delete exist user it('should delete an exist user successfully', async () => { - const res = await userK8sCRUD.remove('existuser', userK8sCRUDConfig); + const res = await userK8sCRUD.remove('existuser'); return expect(res, 'status').to.have.status(200); }); it('should failed to delete an non-exist user', async () => { - return await expect(userK8sCRUD.remove('nonexistuser', userK8sCRUDConfig)).to.be.rejected; + return await expect(userK8sCRUD.remove('nonexistuser')).to.be.rejected; }); }); diff --git a/src/rest-server/yarn.lock b/src/rest-server/yarn.lock index 7d18688718..cee66727fd 100644 --- a/src/rest-server/yarn.lock +++ b/src/rest-server/yarn.lock @@ -101,6 +101,38 @@ once "^1.4.0" pump "^3.0.0" +"@kubernetes/client-node@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@kubernetes/client-node/-/client-node-0.11.0.tgz#e85a8c970bf784c72ff4d7c63e158a1651ed5394" + integrity sha512-1DQG9rNgn1fNpfStCBnvErGMcuF29ixGuQAjh3CVPLCFVsQCtNQjbSOIEV7XV18YEa2fj2hLU52TmJMSII0Kzw== + dependencies: + "@types/js-yaml" "^3.12.1" + "@types/node" "^10.12.0" + "@types/request" "^2.47.1" + "@types/underscore" "^1.8.9" + "@types/ws" "^6.0.1" + byline "^5.0.0" + execa "1.0.0" + isomorphic-ws "^4.0.1" + js-yaml "^3.13.1" + jsonpath-plus "^0.19.0" + openid-client "2.5.0" + request "^2.88.0" + shelljs "^0.8.2" + tslib "^1.9.3" + underscore "^1.9.1" + ws "^6.1.0" + +"@sindresorhus/is@^0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" + integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow== + +"@types/caseless@*": + version "0.12.2" + resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8" + integrity sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w== + "@types/chai@4": version "4.2.5" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.5.tgz#f8da153ebbe30babb0adc9a528b9ad32be3175a2" @@ -111,11 +143,31 @@ resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.1.tgz#90b68446364baf9efd8e8349bb36bd3852b75b80" integrity sha512-aRnpPa7ysx3aNW60hTiCtLHlQaIFsXFCgQlpakNgDNVFzbtusSY8PwjAQgRWfSk0ekNoBjO51eQRB6upA9uuyw== +"@types/js-yaml@^3.12.1": + version "3.12.1" + resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.1.tgz#5c6f4a1eabca84792fbd916f0cb40847f123c656" + integrity sha512-SGGAhXLHDx+PK4YLNcNGa6goPf9XRWQNAUUbffkwVGGXIxmDKWyGGL4inzq2sPmExu431Ekb9aEMn9BkPqEYFA== + "@types/node@*": version "12.12.14" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.14.tgz#1c1d6e3c75dba466e0326948d56e8bd72a1903d2" integrity sha512-u/SJDyXwuihpwjXy7hOOghagLEV1KdAST6syfnOk6QZAMzZuWZqXy5aYYZbh8Jdpd4escVFP0MvftHNDb9pruA== +"@types/node@^10.12.0": + version "10.17.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.11.tgz#46ba035fb917b31c948280dbea22ab8838f386a4" + integrity sha512-dNd2pp8qTzzNLAs3O8nH3iU9DG9866KHq9L3ISPB7DOGERZN81nW/5/g/KzMJpCU8jrbCiMRBzV9/sCEdRosig== + +"@types/request@^2.47.1": + version "2.48.4" + resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.4.tgz#df3d43d7b9ed3550feaa1286c6eabf0738e6cf7e" + integrity sha512-W1t1MTKYR8PxICH+A4HgEIPuAC3sbljoEVfyZbeFJJDbr30guDspJri2XOaM2E+Un7ZjrihaDi7cf6fPa2tbgw== + dependencies: + "@types/caseless" "*" + "@types/node" "*" + "@types/tough-cookie" "*" + form-data "^2.5.0" + "@types/superagent@^3.8.3": version "3.8.7" resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-3.8.7.tgz#1f1ed44634d5459b3a672eb7235a8e7cfd97704c" @@ -124,6 +176,23 @@ "@types/cookiejar" "*" "@types/node" "*" +"@types/tough-cookie@*": + version "2.3.6" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.6.tgz#c880579e087d7a0db13777ff8af689f4ffc7b0d5" + integrity sha512-wHNBMnkoEBiRAd3s8KTKwIuO9biFtTf0LehITzBhSco+HQI0xkXZbLOD55SW3Aqw3oUkHstkm5SPv58yaAdFPQ== + +"@types/underscore@^1.8.9": + version "1.9.4" + resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.9.4.tgz#22d1a3e6b494608e430221ec085fa0b7ccee7f33" + integrity sha512-CjHWEMECc2/UxOZh0kpiz3lEyX2Px3rQS9HzD20lxMvx571ivOBQKeLnqEjxUY0BMgp6WJWo/pQLRBwMW5v4WQ== + +"@types/ws@^6.0.1": + version "6.0.4" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-6.0.4.tgz#7797707c8acce8f76d8c34b370d4645b70421ff1" + integrity sha512-PpPrX7SZW9re6+Ha8ojZG4Se8AZXgf0GK6zmfqEuCsY49LFDNXO3SByp44X3dFEqtB73lkCDAdUazhAjVPiNwg== + dependencies: + "@types/node" "*" + accepts@~1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" @@ -145,6 +214,14 @@ acorn@^5.5.0: version "5.7.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" +aggregate-error@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-1.0.0.tgz#888344dad0220a72e3af50906117f48771925fac" + integrity sha1-iINE2tAiCnLjr1CQYRf0h3GSX6w= + dependencies: + 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" @@ -253,6 +330,11 @@ assertion-error@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" +async-limiter@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + async@~0.9.0: version "0.9.2" resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" @@ -306,6 +388,11 @@ base32@^0.0.6: dependencies: optimist ">=0.1.0" +base64url@^3.0.0, base64url@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" + integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== + basic-auth@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.0.tgz#015db3f353e02e56377755f962742e8981e7bbba" @@ -340,13 +427,6 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - browser-stdout@1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" @@ -363,10 +443,28 @@ builtin-modules@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" +byline@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" + integrity sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE= + bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" +cacheable-request@^2.1.1: + version "2.1.4" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d" + integrity sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0= + dependencies: + clone-response "1.0.2" + get-stream "3.0.0" + http-cache-semantics "3.8.1" + keyv "3.0.0" + lowercase-keys "1.0.0" + normalize-url "2.0.1" + responselike "1.0.2" + caching-transform@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-3.0.2.tgz#601d46b91eca87687a281e71cef99791b0efca70" @@ -465,6 +563,11 @@ 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" @@ -484,6 +587,13 @@ cliui@^5.0.0: strip-ansi "^5.2.0" wrap-ansi "^5.1.0" +clone-response@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" + integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= + dependencies: + mimic-response "^1.0.0" + clone@2.x: version "2.1.2" resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" @@ -662,6 +772,17 @@ cross-spawn@^5.1.0: 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" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + cycle@1.0.x: version "1.0.3" resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2" @@ -684,7 +805,7 @@ debug@3.1.0, debug@=3.1.0: dependencies: ms "2.0.0" -debug@^3.1.0, debug@^3.2.6: +debug@^3.1.0: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== @@ -703,6 +824,18 @@ decamelize@^1.2.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + +decompress-response@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= + dependencies: + mimic-response "^1.0.0" + decompress-response@^4.2.0: version "4.2.1" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" @@ -783,6 +916,11 @@ dotenv@~4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d" +duplexer3@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" + integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -828,6 +966,11 @@ es6-error@^4.0.1: resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== +es6-promise@^4.2.6: + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -929,10 +1072,18 @@ etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" -eventemitter3@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" - integrity sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA== +execa@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" express@~4.16.2: version "4.16.3" @@ -1029,13 +1180,6 @@ file-entry-cache@^2.0.0: flat-cache "^1.2.1" object-assign "^4.0.1" -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - finalhandler@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" @@ -1080,13 +1224,6 @@ follow-redirects@1.5.10: dependencies: debug "=3.1.0" -follow-redirects@^1.0.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.7.0.tgz#489ebc198dc0e7f64167bd23b03c4c19b5784c76" - integrity sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ== - dependencies: - debug "^3.2.6" - foreground-child@^1.5.6: version "1.5.6" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-1.5.6.tgz#4fd71ad2dfde96789b980a5c0a295937cb2f5ce9" @@ -1106,7 +1243,7 @@ form-data@^0.2.0: combined-stream "~0.0.4" mime-types "~2.0.3" -form-data@^2.3.1: +form-data@^2.3.1, form-data@^2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== @@ -1136,7 +1273,7 @@ fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" -from2@^2.3.0: +from2@^2.1.1, from2@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= @@ -1170,6 +1307,18 @@ 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-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" + integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= + +get-stream@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -1187,9 +1336,10 @@ glob@7.1.2: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.3, glob@^7.0.5, glob@^7.1.2: - version "7.1.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" +glob@^7.0.0, glob@^7.1.3: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -1198,10 +1348,9 @@ glob@^7.0.3, glob@^7.0.5, glob@^7.1.2: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.3: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== +glob@^7.0.3, glob@^7.0.5, glob@^7.1.2: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -1230,6 +1379,29 @@ globby@^5.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +got@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/got/-/got-8.3.2.tgz#1d23f64390e97f776cac52e5b936e5f514d2e937" + integrity sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw== + dependencies: + "@sindresorhus/is" "^0.7.0" + cacheable-request "^2.1.1" + decompress-response "^3.3.0" + duplexer3 "^0.1.4" + get-stream "^3.0.0" + into-stream "^3.1.0" + is-retry-allowed "^1.1.0" + isurl "^1.0.0-alpha5" + lowercase-keys "^1.0.0" + mimic-response "^1.0.0" + p-cancelable "^0.4.0" + p-timeout "^2.0.1" + pify "^3.0.0" + safe-buffer "^5.1.1" + timed-out "^4.0.1" + url-parse-lax "^3.0.0" + url-to-options "^1.0.1" + graceful-fs@^4.1.11, graceful-fs@^4.1.2: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -1288,6 +1460,18 @@ has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" +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-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" + integrity sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw== + dependencies: + has-symbol-support-x "^1.4.1" + hasha@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/hasha/-/hasha-3.0.0.tgz#52a32fab8569d41ca69a61ff1a214f8eb7c8bd39" @@ -1307,6 +1491,11 @@ hosted-git-info@^2.1.4: version "2.7.1" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" +http-cache-semantics@3.8.1: + version "3.8.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" + integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== + http-errors@1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" @@ -1325,25 +1514,6 @@ http-errors@~1.6.2, http-errors@~1.6.3: setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" -http-proxy-middleware@^0.20.0: - version "0.20.0" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.20.0.tgz#5b128f7207985c4ea91b53fab8ad897a48c690d6" - integrity sha512-dNJAk71nEJhPiAczQH9hGvE/MT9kEs+zn2Dh+Hi94PGZe1GluQirC7mw5rdREUtWx6qGS1Gu0bZd4qEAg+REgw== - dependencies: - http-proxy "^1.17.0" - is-glob "^4.0.1" - lodash "^4.17.14" - micromatch "^4.0.2" - -http-proxy@^1.17.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.17.0.tgz#7ad38494658f84605e2f6db4436df410f4e5be9a" - integrity sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g== - dependencies: - eventemitter3 "^3.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -1370,6 +1540,11 @@ imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" +indent-string@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" + integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok= + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -1405,6 +1580,19 @@ inquirer@^3.0.6: 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" + integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== + +into-stream@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6" + integrity sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY= + dependencies: + from2 "^2.1.1" + p-is-promise "^1.1.0" + into-stream@^5.1.0: version "5.1.1" resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-5.1.1.tgz#f9a20a348a11f3c13face22763f2d02e127f4db8" @@ -1437,22 +1625,10 @@ is-builtin-module@^1.0.0: dependencies: builtin-modules "^1.0.0" -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.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" @@ -1460,10 +1636,10 @@ is-ip@^2.0.0: dependencies: ip-regex "^2.0.0" -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +is-object@^1.0.1: + version "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" @@ -1481,6 +1657,11 @@ is-path-inside@^1.0.0: 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" @@ -1489,7 +1670,12 @@ is-resolvable@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" -is-stream@^1.0.1: +is-retry-allowed@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" + integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== + +is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= @@ -1512,6 +1698,11 @@ isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" +isomorphic-ws@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" + integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== + isstream@0.1.x, isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -1568,6 +1759,14 @@ istanbul-reports@^2.2.4: dependencies: handlebars "^4.1.2" +isurl@^1.0.0-alpha5: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" + integrity sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w== + dependencies: + has-to-string-tag-x "^1.2.0" + is-object "^1.0.1" + joi@~13.0.1: version "13.0.2" resolved "https://registry.yarnpkg.com/joi/-/joi-13.0.2.tgz#8cc57a573b7c0b64108fa6fd85061c20fcb0d6b0" @@ -1602,6 +1801,11 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +json-buffer@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" + integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= + json-merge-patch@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-merge-patch/-/json-merge-patch-0.2.3.tgz#fa2c6b5af87da77bae2966a589d52e23ed81fe40" @@ -1641,6 +1845,11 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" +jsonpath-plus@^0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/jsonpath-plus/-/jsonpath-plus-0.19.0.tgz#b901e57607055933dc9a8bef0cc25160ee9dd64c" + integrity sha512-GSVwsrzW9LsA5lzsqe4CkuZ9wp+kxBb2GwNniaWzI2YFn5Ig42rSW8ZxVpWXaAfakXNrx5pgY5AbQq7kzX29kg== + jsonwebtoken@~8.1.0: version "8.1.1" resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.1.1.tgz#b04d8bb2ad847bc93238c3c92170ffdbdd1cb2ea" @@ -1680,6 +1889,13 @@ jws@^3.1.4: jwa "^1.1.5" safe-buffer "^5.0.1" +keyv@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.0.0.tgz#44923ba39e68b12a7cec7df6c3268c031f2ef373" + integrity sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA== + dependencies: + json-buffer "3.0.0" + lcov-parse@^0.0.10: version "0.0.10" resolved "https://registry.yarnpkg.com/lcov-parse/-/lcov-parse-0.0.10.tgz#1b0b8ff9ac9c7889250582b70b71315d9da6d9a3" @@ -1747,7 +1963,7 @@ lodash@4.x, lodash@^4.14.0, lodash@^4.17.13, lodash@^4.17.4, lodash@^4.3.0, loda resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.13.tgz#0bdc3a6adc873d2f4e0c4bac285df91b64fc7b93" integrity sha512-vm3/XWXfWtRua0FkUyEHBZy8kCPjErNBT9fJx8Zvs+U6zjqPbTUOpkaoum3O5uiA8sm+yNMHXfYkTUHFoMxFNA== -lodash@^4.17.14: +lodash@^4.17.11: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== @@ -1756,6 +1972,21 @@ log-driver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.7.tgz#63b95021f0702fedfa2c9bb0a24e7797d71871d8" +long@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + +lowercase-keys@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" + integrity sha1-TjNms55/VFfjXxMkvfb4jQv8cwY= + +lowercase-keys@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== + lru-cache@^4.0.1: version "4.1.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c" @@ -1763,6 +1994,13 @@ lru-cache@^4.0.1: pseudomap "^1.0.2" yallist "^2.1.2" +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + make-dir@^2.0.0, make-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" @@ -1790,14 +2028,6 @@ methods@^1.1.1, methods@^1.1.2, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" -micromatch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" - integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== - dependencies: - braces "^3.0.1" - picomatch "^2.0.5" - "mime-db@>= 1.36.0 < 2", mime-db@~1.36.0: version "1.36.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.36.0.tgz#5020478db3c7fe93aad7bbcc4dcf869c43363397" @@ -1836,6 +2066,11 @@ 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== + mimic-response@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.0.0.tgz#996a51c60adf12cb8a87d7fb8ef24c2f3d5ebb46" @@ -1931,6 +2166,11 @@ nested-error-stacks@^2.0.0: resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz#0fbdcf3e13fe4994781280524f8b96b0cdff9c61" integrity sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug== +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + nock@~9.1.6: version "9.1.10" resolved "https://registry.npmjs.org/nock/-/nock-9.1.10.tgz#b563ff058608cab3368bb3ba59f84dffd4ff9d65" @@ -1952,6 +2192,23 @@ node-cache@~4.2.0: clone "2.x" lodash "4.x" +node-forge@^0.8.1: + version "0.8.5" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.8.5.tgz#57906f07614dc72762c84cef442f427c0e1b86ee" + integrity sha512-vFMQIWt+J/7FLNyKouZ9TazT74PRV3wgv9UT4cRjC8BffxFbKXkgIWR42URCPSnHm/QDz6BOlb2Q0U4+VQT67Q== + +node-jose@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/node-jose/-/node-jose-1.1.3.tgz#26e6bc563101a356c06fb254c7ef0078e8b49670" + integrity sha512-kupfi4uGWhRjnOmtie2T64cLge5a1TZyalEa8uWWWBgtKBcu41A4IGKpI9twZAxRnmviamEUQRK7LSyfFb2w8A== + dependencies: + base64url "^3.0.1" + es6-promise "^4.2.6" + lodash "^4.17.11" + long "^4.0.0" + node-forge "^0.8.1" + uuid "^3.3.2" + normalize-package-data@^2.3.2: version "2.4.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" @@ -1961,6 +2218,22 @@ normalize-package-data@^2.3.2: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" +normalize-url@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6" + integrity sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw== + dependencies: + prepend-http "^2.0.0" + query-string "^5.0.1" + sort-keys "^2.0.0" + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= + dependencies: + path-key "^2.0.0" + nyc@^14.1.1: version "14.1.1" resolved "https://registry.yarnpkg.com/nyc/-/nyc-14.1.1.tgz#151d64a6a9f9f5908a1b73233931e4a0a3075eeb" @@ -1996,10 +2269,20 @@ 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, object-assign@^4.0.1, object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" +object-hash@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df" + integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA== + +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" + integrity sha512-dTzp80/y/da+um+i+sOucNqiPpwRL7M/xPwj7pH1TFA2/bqQ+OK2sJahSXbemEoLtPkHcFLyhLhLWZa9yW5+RA== + on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -2022,6 +2305,20 @@ onetime@^2.0.0: 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" + integrity sha512-t3hFD7xEoW1U25RyBcRFaL19fGGs6hNVTysq9pgmiltH0IVUPzH/bQV9w24pM5Q7MunnGv2/5XjIru6BQcWdxg== + dependencies: + base64url "^3.0.0" + got "^8.3.2" + lodash "^4.17.11" + lru-cache "^5.1.1" + node-jose "^1.1.0" + object-hash "^1.3.1" + oidc-token-hash "^3.0.1" + p-any "^1.1.0" + optimist@>=0.1.0, optimist@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" @@ -2049,6 +2346,28 @@ 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" + integrity sha512-Ef0tVa4CZ5pTAmKn+Cg3w8ABBXh+hHO1aV8281dKOoUHfX+3tjG2EaFcC+aZyagg9b4EYGsHEjz21DnEE8Og2g== + dependencies: + p-some "^2.0.0" + +p-cancelable@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" + integrity sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ== + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= + +p-is-promise@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" + integrity sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4= + p-is-promise@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-3.0.0.tgz#58e78c7dfe2e163cf2a04ff869e7c1dba64a5971" @@ -2068,6 +2387,20 @@ p-locate@^3.0.0: dependencies: p-limit "^2.0.0" +p-some@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/p-some/-/p-some-2.0.1.tgz#65d87c8b154edbcf5221d167778b6d2e150f6f06" + integrity sha1-Zdh8ixVO289SIdFnd4ttLhUPbwY= + dependencies: + aggregate-error "^1.0.0" + +p-timeout@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038" + integrity sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA== + dependencies: + p-finally "^1.0.0" + p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" @@ -2107,6 +2440,16 @@ 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-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + 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" @@ -2126,11 +2469,6 @@ performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" -picomatch@^2.0.5: - version "2.1.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.1.1.tgz#ecdfbea7704adb5fe6fb47f9866c4c0e15e905c5" - integrity sha512-OYMyqkKzK7blWO/+XZYP6w8hH0LDvkBvdvKukti+7kqYFCiEAk+gI3DWnryapc0Dau05ugGTy0foQ6mqn4AHYA== - pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -2170,6 +2508,11 @@ prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" +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= + 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" @@ -2221,6 +2564,15 @@ qs@^6.5.1, qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" +query-string@^5.0.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" + integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw== + dependencies: + decode-uri-component "^0.2.0" + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + querystring@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" @@ -2268,6 +2620,13 @@ readable-stream@^2.0.0, readable-stream@^2.2.2, readable-stream@^2.3.5: string_decoder "~1.1.1" util-deprecate "~1.0.1" +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + integrity sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q= + dependencies: + resolve "^1.1.6" + release-zalgo@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" @@ -2316,11 +2675,6 @@ require-uncached@^1.0.3: caller-path "^0.1.0" resolve-from "^1.0.0" -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= - resolve-from@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" @@ -2330,6 +2684,20 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +resolve@^1.1.6: + version "1.14.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.14.0.tgz#6d14c6f9db9f8002071332b600039abf82053f64" + integrity sha512-uviWSi5N67j3t3UKFxej1loCH0VZn5XuqdNxoLShPcYPw6cUZn74K1VRj+9myynRX03bxIBEkwlkob/ujLsJVw== + 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" + integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= + 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" @@ -2374,6 +2742,11 @@ safe-buffer@5.1.2, safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, s version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" +safe-buffer@^5.1.1: + version "5.2.0" + 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: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -2387,7 +2760,7 @@ sax@>=0.6.0: version "5.5.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" -semver@^5.6.0: +semver@^5.5.0, semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -2446,6 +2819,15 @@ shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" +shelljs@^0.8.2: + version "0.8.3" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.3.tgz#a7f3319520ebf09ee81275b2368adb286659b097" + integrity sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A== + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + 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" @@ -2456,6 +2838,13 @@ slice-ansi@1.0.0: dependencies: is-fullwidth-code-point "^2.0.0" +sort-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" + integrity sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg= + dependencies: + is-plain-obj "^1.0.0" + source-map@^0.5.0: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -2536,6 +2925,11 @@ statuses@~1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" +strict-uri-encode@^1.0.0: + version "1.1.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" @@ -2582,6 +2976,11 @@ strip-bom@^3.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= +strip-eof@^1.0.0: + version "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" @@ -2667,6 +3066,11 @@ through@^2.3.6: version "2.3.8" resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" +timed-out@^4.0.1: + version "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" @@ -2678,13 +3082,6 @@ to-fast-properties@^2.0.0: resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - topo@3.x.x: version "3.0.0" resolved "https://registry.yarnpkg.com/topo/-/topo-3.0.0.tgz#37e48c330efeac784538e0acd3e62ca5e231fe7a" @@ -2698,6 +3095,11 @@ tough-cookie@~2.4.3: psl "^1.1.24" punycode "^1.4.1" +tslib@^1.9.3: + version "1.10.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" + integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== + tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -2749,6 +3151,11 @@ underscore@1.4.x: version "1.4.4" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604" +underscore@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961" + integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg== + unirest@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/unirest/-/unirest-0.6.0.tgz#289b5ae59cc9fa9fdfff3b5866e0dd50bf5eb280" @@ -2773,6 +3180,18 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +url-parse-lax@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" + integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= + dependencies: + prepend-http "^2.0.0" + +url-to-options@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" + integrity sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k= + util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -2862,6 +3281,13 @@ write@^0.2.1: dependencies: mkdirp "^0.5.1" +ws@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" + integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + dependencies: + async-limiter "~1.0.0" + xml2js@~0.4.19: version "0.4.19" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" @@ -2888,6 +3314,11 @@ yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + yargs-parser@^13.0.0, yargs-parser@^13.1.1: version "13.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" diff --git a/src/webportal/src/app/cluster-view/services/service-info.js b/src/webportal/src/app/cluster-view/services/service-info.js index 4524bfd377..da3f1a1e79 100644 --- a/src/webportal/src/app/cluster-view/services/service-info.js +++ b/src/webportal/src/app/cluster-view/services/service-info.js @@ -15,50 +15,58 @@ // 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. -// This function will call kubernetes restful api to get node - podlist - label info, to support service view monitor page. +const userAuth = require('../../user/user-auth/user-auth.component'); +const loading = require('../../job/loading/loading.component'); +const webportalConfig = require('../../config/webportal.config.js'); +const { clearToken } = require('../../user/user-logout/user-logout.component'); -import { isNil } from 'lodash'; - -export const getServiceView = (kubeURL, namespace, callback) => { - $.ajax({ - type: 'GET', - url: kubeURL + '/api/v1/nodes', - dataType: 'json', - success: function(data) { - const items = data.items; - const nodeList = []; - for (const item of items) { - nodeList.push(item); - } - getNodePods(kubeURL, namespace, nodeList, callback); +const fetchWrapper = async url => { + const token = userAuth.checkToken(); + const res = await fetch(url, { + headers: { + Authorization: `Bearer ${token}`, }, }); + if (res.ok) { + const data = await res.json(); + return data.items; + } else { + loading.hideLoading(); + if (res.status === 401) { + clearToken(); + } else { + const data = await res.json(); + alert(data.message); + } + } }; -const getNodePods = (kubeURL, namespace, nodeList, callback) => { - $.ajax({ - type: 'GET', - url: kubeURL + '/api/v1/namespaces/' + namespace + '/pods/', - dataType: 'json', - success: function(pods) { - const podsItems = pods.items; - const nodeDic = []; - - for (const pod of podsItems) { - const nodeName = pod.spec.nodeName; - if (nodeDic[nodeName] == null) { - nodeDic[nodeName] = []; - } - nodeDic[nodeName].push(pod); - } - const resultDic = []; - for (const node of nodeList) { - if (isNil(nodeDic[node.metadata.name])) { - nodeDic[node.metadata.name] = []; - } - resultDic.push({ node: node, podList: nodeDic[node.metadata.name] }); - } - callback(resultDic); - }, - }); +export const getServiceView = async callback => { + const nodeUrl = new URL( + '/api/v1/kubernetes/nodes', + webportalConfig.restServerUri, + ); + const podUrl = new URL( + '/api/v1/kubernetes/pods', + webportalConfig.restServerUri, + ); + podUrl.searchParams.set('namespace', 'default'); + const [nodes, pods] = await Promise.all([ + fetchWrapper(nodeUrl), + fetchWrapper(podUrl), + ]); + const resultDict = {}; + for (const node of nodes) { + resultDict[node.metadata.name] = { + node: node, + podList: [], + }; + } + for (const pod of pods) { + const nodeName = pod.spec.nodeName; + if (resultDict[nodeName]) { + resultDict[nodeName].podList.push(pod); + } + } + callback(Object.values(resultDict)); }; diff --git a/src/webportal/src/app/cluster-view/services/services.component.js b/src/webportal/src/app/cluster-view/services/services.component.js index 4b2909e303..81949355d0 100644 --- a/src/webportal/src/app/cluster-view/services/services.component.js +++ b/src/webportal/src/app/cluster-view/services/services.component.js @@ -16,7 +16,10 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // module dependencies -const { getServiceView } = require('./service-info'); +import 'core-js/stable'; +import 'regenerator-runtime/runtime'; +import 'whatwg-fetch'; +import { getServiceView } from './service-info'; const breadcrumbComponent = require('../../job/breadcrumb/breadcrumb.component.ejs'); const loadingComponent = require('../../job/loading/loading.component.ejs'); @@ -43,31 +46,27 @@ const serviceViewHtml = serviceViewComponent({ const loadServices = () => { loading.showLoading(); - getServiceView( - webportalConfig.restServerUri + '/api/v1/kubernetes', - 'default', - data => { - loading.hideLoading(); - $('#service-table').html( - serviceTableComponent({ - data, - k8sUri: webportalConfig.k8sDashboardUri, - grafanaUri: webportalConfig.grafanaUri, - exporterPort: webportalConfig.exporterPort, - }), - ); - $('#service-datatable') - .dataTable({ - scrollY: $(window).height() - 265 + 'px', - lengthMenu: [[20, 50, 100, -1], [20, 50, 100, 'All']], - columnDefs: [ - { orderDataType: 'dom-text', targets: [1, 2] }, - { type: 'ip-address', targets: [0] }, - ], - }) - .api(); - }, - ); + getServiceView(data => { + loading.hideLoading(); + $('#service-table').html( + serviceTableComponent({ + data, + k8sUri: webportalConfig.k8sDashboardUri, + grafanaUri: webportalConfig.grafanaUri, + exporterPort: webportalConfig.exporterPort, + }), + ); + $('#service-datatable') + .dataTable({ + scrollY: $(window).height() - 265 + 'px', + lengthMenu: [[20, 50, 100, -1], [20, 50, 100, 'All']], + columnDefs: [ + { orderDataType: 'dom-text', targets: [1, 2] }, + { type: 'ip-address', targets: [0] }, + ], + }) + .api(); + }); }; window.loadServices = loadServices; @@ -76,5 +75,3 @@ $('#content-wrapper').html(serviceViewHtml); $(document).ready(() => { loadServices(); }); - -module.exports = { loadServices };