diff --git a/.env b/.env new file mode 100644 index 0000000..15c2d08 --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +ACTIVEMQ_PORT= +ACTIVEMQ_HOST= +ACTIVEMQ_USERNAME= +ACTIVEMQ_PASSWORD= \ No newline at end of file diff --git a/lib/exn.js b/lib/exn.js index a86c1ca..ff38d7b 100644 --- a/lib/exn.js +++ b/lib/exn.js @@ -1,5 +1,5 @@ - - +require('dotenv').config(); +const container= require('rhea'); const connection_options={ 'port': process.env.ACTIVEMQ_PORT, @@ -10,14 +10,15 @@ const connection_options={ } -if(!connection_options.port || !connection_options.host) { - console.error("No connection option provided for EXN skipping asynchronous messaging") - return +if (!connection_options.port || !connection_options.host) { + console.error("No connection option provided for EXN skipping asynchronous messaging"); + +} else { + const connection = container.connect(connection_options); } -const container= require('rhea'); -let connection; + let sender_sal_nodecandidate_get; let sender_sal_cloud_get; let sender_sal_cloud_post; @@ -37,8 +38,6 @@ let sender_ui_policies_model_upsert; const correlations = {} container.on('message', (context)=>{ - - // console.log("Received ",context.message) if(context.message.correlation_id in correlations){ if(context.message.body.metaData['status'] >= 400){ correlations[context.message.correlation_id]['reject'](context.message.body['message']) @@ -53,16 +52,16 @@ container.on('connection_open', function (context) { console.log("Connected ",context.container.id); context.connection.open_receiver('topic://eu.nebulouscloud.exn.sal.cloud.get.reply') - context.connection.open_receiver('topic://eu.nebulouscloud.exn.sal.cloud.post.reply') + context.connection.open_receiver('topic://eu.nebulouscloud.exn.sal.cloud.create.reply') context.connection.open_receiver('topic://eu.nebulouscloud.exn.sal.cloud.delete.reply') context.connection.open_receiver('topic://eu.nebulouscloud.exn.sal.nodecandidate.get.reply') - context.connection.open_receiver('topic://eu.nebulouscloud.exn.sal.node.post.reply') + context.connection.open_receiver('topic://eu.nebulouscloud.exn.sal.node.create.reply') sender_sal_nodecandidate_get = context.connection.open_sender('topic://eu.nebulouscloud.exn.sal.nodecandidate.get'); sender_sal_cloud_get = context.connection.open_sender('topic://eu.nebulouscloud.exn.sal.cloud.get'); - sender_sal_cloud_post = context.connection.open_sender('topic://eu.nebulouscloud.exn.sal.cloud.post'); + sender_sal_cloud_post = context.connection.open_sender('topic://eu.nebulouscloud.exn.sal.cloud.create'); sender_sal_cloud_delete = context.connection.open_sender('topic://eu.nebulouscloud.exn.sal.cloud.delete'); - sender_sal_node_post = context.connection.open_sender('topic://eu.nebulouscloud.exn.sal.node.post'); + sender_sal_node_post = context.connection.open_sender('topic://eu.nebulouscloud.exn.sal.node.create'); sender_ui_application_new = context.connection.open_sender('topic://eu.nebulouscloud.ui.application.new'); sender_ui_application_updated = context.connection.open_sender('topic://eu.nebulouscloud.ui.application.updated'); @@ -77,7 +76,6 @@ container.on('connection_open', function (context) { -connection = container.connect(); const {v4: uuidv4} = require("uuid"); @@ -91,14 +89,15 @@ module.exports = { 'reject':reject, }; const message = { - to: sender_ui_deploy_application_new.options.target.address, + to: sender_ui_application_new.options.target.address, correlation_id: correlation_id, + message_annotations: {application: uuid}, body:{ uuid: uuid } } console.log("Send ", message) - sender_ui_deploy_application_new.send(message) + sender_ui_application_new.send(message) }) }, @@ -113,21 +112,28 @@ module.exports = { const message = { to: sender_ui_application_dsl_json.options.target.address, correlation_id: correlation_id, + message_annotations: {application: uuid}, + application_properties: {application: uuid}, body:json } sender_ui_application_dsl_json.send(message) - console.log("Sending ", sender_ui_application_dsl_metric.options.target.address, uuid,json) - const metrci_message = { + const metric_model_payload ={ + 'application': uuid, + 'yaml': yaml + } + + console.log("Sending ", sender_ui_application_dsl_metric.options.target.address, uuid, metric_model_payload) + const metric_message = { to: sender_ui_application_dsl_metric.options.target.address, correlation_id: correlation_id, - body:{ - 'yaml': yaml - } + message_annotations: {application: uuid}, + application_properties: {application: uuid}, + body:metric_model_payload } - sender_ui_application_dsl_metric.send(message) - + sender_ui_application_dsl_metric.send(metric_message) + return resolve() }) }, @@ -139,18 +145,20 @@ module.exports = { 'reject':reject, }; const message = { - to: sender_ui_deploy_application_new.options.target.address, + to: sender_ui_application_updated.options.target.address, correlation_id: correlation_id, + message_annotations: {application: uuid}, + application_properties: {application: uuid}, body:{ uuid: uuid } } console.log("Send ", message) - sender_ui_deploy_application_new.send(message) + sender_ui_application_updated.send(message) }) }, - register_cloud:( uuid, user ,secret ) =>{ + register_cloud:(doc) =>{ return new Promise((resolve,reject)=>{ const correlation_id = uuidv4() @@ -159,6 +167,23 @@ module.exports = { 'reject':reject, }; + /** + * + * // Amazon Web Service Elastic Compute Cloud + * AWSEC2("aws-ec2"), + * // Azure VM + * AZUREVM("azure"), + * // Google CLoud Engine + * GCE("gce"), + * // OpenStack NOVA + * OPENSTACKNOVA("openstack"), + * // BYON, to be used for on-premise baremetal + * BYON("byon"), + * // EDGE nodes + * EDGE("edge"); + * + */ + const message = { to: sender_sal_cloud_post.options.target.address, correlation_id: correlation_id, @@ -167,27 +192,27 @@ module.exports = { userId: "admin" }, body: JSON.stringify([{ - "cloudId": uuid, - "cloudProviderName": "aws-ec2", + "cloudId": doc.uuid, + "cloudProviderName": doc._platform[0].provider_name, "cloudType": "PUBLIC", - "securityGroup": null, - "subnet": null, + "securityGroup": doc.securityGroup || '', + "subnet": doc.subnet || null, "sshCredentials": { - "username": null, - "keyPairName": "mkl", - "privateKey": null + "username": doc.sshCredentials.username, + "keyPairName": doc.sshCredentials.keyPairName, + "privateKey": doc.sshCredentials.privateKey }, - "endpoint": null, + "endpoint": doc.endpoint, "scope": { "prefix": null, "value": null }, - "identityVersion": null, - "defaultNetwork": null, + "identityVersion": doc.identityVersion, + "defaultNetwork": doc.defaultNetwork, "credentials": { - "user": user, - "secret": secret, - "domain": null + "user": doc.credentials.user, + "secret": doc.credentials.secret, + "domain": doc.credentials.domain || null }, "blacklist": null }]) @@ -196,10 +221,6 @@ module.exports = { console.log("Send ", message) sender_sal_cloud_post.send(message) }) - }, - deploy_application: (uuid) => { - - }, get_cloud_candidates: () => { return new Promise((resolve,reject)=> { @@ -213,7 +234,9 @@ module.exports = { const message = { to: sender_sal_nodecandidate_get.options.target.address, correlation_id: correlation_id, - body: {} + body: { + body:[] + } } sender_sal_nodecandidate_get.send(message) }) diff --git a/lib/math.js b/lib/math.js index 5e7bb56..47feda5 100644 --- a/lib/math.js +++ b/lib/math.js @@ -4,8 +4,7 @@ const math = require('mathjs'); module.exports = { extractFromEquation: (equation)=>{ equation = equation || ''; - const lowerCaseEquation = equation.toLowerCase(); - return math.parse(lowerCaseEquation); + return math.parse(equation); }, extractVariableNames: (mathNode) => { let variableNames = new Set(); diff --git a/lib/metric_model.js b/lib/metric_model.js index 6470a8b..5564b18 100644 --- a/lib/metric_model.js +++ b/lib/metric_model.js @@ -4,92 +4,107 @@ const yaml = require('yaml'); module.exports = { yaml: (doc) => { - let object = _.clone(doc) + let object = _.cloneDeep(doc); + let componentsForAppSpecComp = []; + let componentsForAppWideScope = []; - const protectedVariables = ["_id", "type", "metaType", "organization", "_edit", "_publish", "variables", "utilityFunctions", "resources", "parameters",]; + const protectedVariables = ["_id", "type", "metaType", "organization", "_edit", "_publish", "variables", "utilityFunctions", "resources"]; protectedVariables.forEach(p => { delete object[p]; }); - + + //Templates if (object.templates) { - object.templates = object.templates.map(v => { - - return { - - id: v.id, - type: v.type, - range: [v.minValue, v.maxValue], - unit: v.unit - } - }); + object.templates = object.templates.map(v => ({ + id: v.id, + type: v.type, + range: [v.minValue, v.maxValue], + unit: v.unit + })); } object.metrics_comp = []; object.metrics_global = []; - + //Metrics if (object.metrics) { object.metrics.forEach(v => { let metricsDetail = {}; if (v.type === 'composite') { - const componentNames = v.components.map(component => component.componentName).join(', '); - let windowDetail = {}; if (v.isWindowInput && v.input.type && v.input.interval && v.input.unit) { - windowDetail.type = v.input.type; - windowDetail.size = `${v.input.interval} ${v.input.unit}`; + windowDetail = { + type: v.input.type, + size: `${v.input.interval} ${v.input.unit}`, + }; } - if (v.isWindowOutput && v.output.type && v.output.interval && v.output.unit) { - windowDetail.output = `${v.output.type} ${v.output.interval} ${v.output.unit}`; + + // Only add windowDetail to metricsDetail if it's not empty + if (Object.keys(windowDetail).length > 0) { + metricsDetail.window = windowDetail; + } + + let componentNames = v.components ? v.components.map(component => component.componentName) : []; + + if (componentNames.length === 1) { + componentsForAppSpecComp.push(componentNames[0]); + } else if (componentNames.length > 1) { + componentsForAppWideScope = componentsForAppWideScope.concat(componentNames); } metricsDetail = { name: v.name, type: v.type, - template: componentNames, - window: windowDetail + template: v.template, + formula: v.formula, + ...metricsDetail, }; + if (v.isWindowOutput && v.output.type && v.output.interval && v.output.unit) { + metricsDetail.output = `${v.output.type} ${v.output.interval} ${v.output.unit}`; + } } else if (v.type === 'raw') { + let configs = v.config.map(configItem => ({ + [configItem.name]: configItem.value + })); - let windowDetailRaw = {}; + let configObject = configs.reduce((acc, current) => ({ ...acc, ...current }), {}); - if (v.isWindowInputRaw && v.inputRaw.type && v.inputRaw.interval && v.inputRaw.unit) { - windowDetailRaw.type = v.inputRaw.type; - windowDetailRaw.size = `${v.inputRaw.interval} ${v.inputRaw.unit}`; - } - if (v.isWindowOutputRaw && v.outputRaw.type && v.outputRaw.interval && v.outputRaw.unit) { - windowDetailRaw.output = `${v.outputRaw.type} ${v.outputRaw.interval} ${v.outputRaw.unit}`; - } metricsDetail = { name: v.name, type: v.type, sensor: { - type: v.sensor - }, - window: windowDetailRaw + type: v.sensor, + config: configObject, + } }; + + if (v.outputRaw && v.outputRaw.type && v.outputRaw.interval && v.outputRaw.unit) { + metricsDetail.output = `${v.outputRaw.type} ${v.outputRaw.interval} ${v.outputRaw.unit}`; + } + if (v.template) { + metricsDetail.template = v.template; + } + + let componentNames = v.components ? v.components.map(component => component.componentName) : []; + + if (componentNames.length === 1) { + componentsForAppSpecComp.push(componentNames[0]); + } else if (componentNames.length > 1) { + componentsForAppWideScope = componentsForAppWideScope.concat(componentNames); + } } - const metric = { - metrics: metricsDetail - }; - - if (v.type === 'composite' && v.components.length < 2) { - object.metrics_global.push(metric); - } else if (v.type === 'composite' && v.components.length >= 2) { - object.metrics_comp.push(metric); - } else if (v.type === 'raw') { - object.metrics_global.push(metric); + if (v.components && v.components.length === 1) { + object.metrics_comp.push({...metricsDetail}); + } else { + object.metrics_global.push({...metricsDetail}); } }); } - - - - - + + //SLO Violations if (object.sloViolations) { const processSloViolations = (violations) => { const buildConstraint = (v, parentCondition = '') => { @@ -99,29 +114,58 @@ module.exports = { } else { const childConstraints = v.children.map(child => buildConstraint(child, v.condition)).join(` ${v.condition} `); - if (v.not) { - constraint = `NOT (${childConstraints})`; - } else { - constraint = `(${childConstraints})`; - } + constraint = v.not ? `NOT (${childConstraints})` : `(${childConstraints})`; } return constraint; }; - const combinedConstraint = buildConstraint(violations); + const combinedConstraint = buildConstraint(JSON.parse(doc['sloViolations'])); - const requirement = { + return [{ name: 'Combined SLO', type: 'slo', constraint: combinedConstraint - }; - - return [requirement]; + }]; }; - object.sloViolations = processSloViolations(JSON.parse(doc['sloViolations'])); + object.sloViolations = processSloViolations(object.sloViolations); } + + let specContent = { + components: [ + ...componentsForAppSpecComp, + { + name: "spec-comp", + metrics: object.metrics_comp + } + ], scopes: [{ + name: "app-wide-scope", + components: componentsForAppWideScope, + metrics: object.metrics_global, + requirements: object.sloViolations ? object.sloViolations : [], + }] + }; + //Parameters + push to app wide scope + if (object.parameters) { + const parametersMetrics = object.parameters.map(v => ({ + name: v.name, + type: "constant", + initialValue: v.initialValue, + template: v.template + })); + + if (parametersMetrics.length > 0) { + specContent.scopes.push({ + name: "parameters-scope", + components: [], + metrics: parametersMetrics, + }); + + } + } + + //Construct and return the final version const yamlDoc = { apiVersion: "nebulous/v1", kind: "MetricModel", @@ -131,19 +175,10 @@ module.exports = { app: object.title, } }, - common: object.templates, - spec: { - components: object.metrics_comp - }, - scopes: [ - { - name: "app-wide-scope", - requirements: object.sloViolations, - components: object.metrics_global, - } - ] + templates: object.templates, + spec: specContent }; - return yamlDoc; } }; + diff --git a/modules/application/index.js b/modules/application/index.js index 0211aec..64ec6d1 100644 --- a/modules/application/index.js +++ b/modules/application/index.js @@ -134,7 +134,11 @@ module.exports = { template: { type: 'string', label: 'Template' - } + }, + initialValue: { + type: 'float', + label: 'Initial Value' + }, } } }, @@ -188,8 +192,8 @@ module.exports = { type: 'select', label: 'Level', choices: [ - { label: 'Global', value: 'Global' }, - { label: 'Components', value: 'Components' } + { label: 'Global', value: 'global' }, + { label: 'Components', value: 'components' } ], def: 'global' }, @@ -197,7 +201,7 @@ module.exports = { type: 'array', label: 'Components', if: { - level: 'Components' + level: 'components' }, fields: { add: { @@ -210,10 +214,12 @@ module.exports = { }, name: { type: 'string', - label: 'Name', - if: { - type: ['composite', 'raw'] - } + label: 'Name' + }, + template: { + type: 'string', + label: 'Template', + textarea: true, }, formula: { type: 'string', @@ -239,7 +245,7 @@ module.exports = { type: 'select', label: 'Type Input', choices: [ - {label: 'All', value: 'all'}, + {label: 'Batch', value: 'batch'}, {label: 'Sliding', value: 'sliding'} ], }, @@ -255,7 +261,8 @@ module.exports = { {label: 'Sec', value: 'sec'}, {label: 'Min', value: 'min'}, {label: 'Hour', value: 'hour'}, - {label: 'Day', value: 'day'} + {label: 'Day', value: 'day'}, + {label: 'Events', value: 'events'} ], }, }, @@ -280,7 +287,8 @@ module.exports = { type: 'select', choices: [ {label: 'All', value: 'all'}, - {label: 'Sliding', value: 'sliding'} + {label: 'First', value: 'first'}, + {label: 'Last', value: 'last'} ], }, interval: { @@ -330,47 +338,6 @@ module.exports = { } } }, - isWindowInputRaw: { - type: 'boolean', - label: 'Window Input', - if: { - type: 'raw' - } - }, - inputRaw: { - type: 'object', - label: 'Input', - fields: { - add: { - type: { - type: 'select', - label: 'Type Input', - choices: [ - {label: 'All', value: 'all'}, - {label: 'Sliding', value: 'sliding'} - ], - }, - interval: { - type: 'integer', - label: 'Interval', - }, - unit: { - type: 'select', - label: 'Unit', - choices: [ - {label: 'Ms', value: 'ms'}, - {label: 'Sec', value: 'sec'}, - {label: 'Min', value: 'min'}, - {label: 'Hour', value: 'hour'}, - {label: 'Day', value: 'day'} - ], - }, - }, - }, - if: { - isWindowInputRaw: true - } - }, isWindowOutputRaw: { type: 'boolean', label: 'Window Output', @@ -387,7 +354,8 @@ module.exports = { type: 'select', choices: [ {label: 'All', value: 'all'}, - {label: 'Sliding', value: 'sliding'} + {label: 'First', value: 'first'}, + {label: 'Last', value: 'last'} ], }, interval: { @@ -433,6 +401,7 @@ module.exports = { label: 'Function Type', choices: [ {label: 'Maximize', value: 'maximize'}, + {label: 'Minimize', value: 'minimize'}, {label: 'Constant', value: 'constant'} ] }, @@ -558,11 +527,11 @@ module.exports = { application_update_sender.send({ "body":{"uuid":doc.uuid}, "message_annotations":{ - "subject":doc.uuid + "application":doc.uuid } }); application_dsl_generic.send({body:{}, "message_annotations":{ - "subject":doc.uuid + "application":doc.uuid }}); } } @@ -585,11 +554,11 @@ module.exports = { application_update_sender.send({ "body":{"uuid":doc.uuid}, "message_annotations":{ - "subject":doc.uuid + "application":doc.uuid } }); application_dsl_generic.send({body:{}, "message_annotations":{ - "subject":doc.uuid + "application":doc.uuid }}); } } @@ -786,10 +755,10 @@ module.exports = { 'string.base': 'Function Name must be a string.', 'any.required': 'Function Name is required.' }), - functionType: Joi.string().valid('maximize', 'constant').insensitive().required().messages({ + functionType: Joi.string().valid('maximize', 'constant','minimize').insensitive().required().messages({ 'string.base': 'Function Type must be a string.', 'any.required': 'Function Type is required.', - 'any.only': 'Function Type must be either "Maximize" or "Constant".' + 'any.only': 'Function Type must be either "Maximize" , "Constant" or "Minimize.' }), functionExpression: Joi.string().trim().required().messages({ 'string.base': 'Function Expression must be a string.', @@ -982,10 +951,12 @@ module.exports = { try { const updatedApp = await self.find(req,{ uuid: uuid , organization:adminOrganization }).project(projection).toArray(); - const result = await exn.application_dsl(uuid, - kubevela.json(updatedApp.pop()), - "" - ) + const updatedAppItem = updatedApp.pop(); + + await exn.application_dsl(uuid, + kubevela.json(updatedAppItem), metric_model.yaml(updatedAppItem) + ); + //TODO refactor to use apostrophe CMS ORM await self.apos.doc.db.updateOne( { uuid: uuid }, diff --git a/modules/mathparser/index.js b/modules/mathparser/index.js index 88c9de9..bb00cc1 100644 --- a/modules/mathparser/index.js +++ b/modules/mathparser/index.js @@ -8,9 +8,8 @@ module.exports = { try { let parsedEquation = mathutils.extractFromEquation(req.body.equation) const variableNames = mathutils.extractVariableNames(parsedEquation); - const uppercaseVariableNames = variableNames.map(name => name.toUpperCase()); return { - variables: uppercaseVariableNames, + variables: variableNames, }; } catch (error) { throw error; diff --git a/modules/platforms/index.js b/modules/platforms/index.js index b62b536..6cbded2 100644 --- a/modules/platforms/index.js +++ b/modules/platforms/index.js @@ -1,8 +1,13 @@ const {v4: uuidv4} = require("uuid"); +const _ = require('lodash'); + module.exports = { extend: '@apostrophecms/piece-type', options: { label: 'Platform', + sort:{ + sortOrder:1, + } }, fields: { @@ -11,12 +16,29 @@ module.exports = { type: 'string', label: 'UUID', required: false + }, + provider_name: { + type: 'string', + label: 'Provider Name', + required: false + }, + public: { + type: 'boolean', + label: 'Public', + required: true, + def: true + }, + sortOrder: { + type: 'integer', + label: 'Sort Order', + required: true, + def: 0, } }, group: { basics: { label: 'Basics', - fields: ['uuid'] + fields: ['uuid','public','sortOrder'] } } }, @@ -38,10 +60,12 @@ module.exports = { const projection = { title: 1, uuid: 1, + sortOrder: 1, }; try { const platforms = await self.find(req).project(projection).toArray(); - return platforms; + + return _.map(platforms, (p)=> { return {'title': p.title, 'uuid': p.uuid} }) } catch (error) { throw self.apos.error('notfound', 'Platforms not found'); } diff --git a/modules/resources/index.js b/modules/resources/index.js index 9c721fb..378fcdd 100644 --- a/modules/resources/index.js +++ b/modules/resources/index.js @@ -8,54 +8,130 @@ const projection = { uuid: 1, organization: 1, platform: 1, - appId: 1, - appSecret: 1 + subnet: 1, + endpoint: 1, + identityVersion: 1, + credentials: 1, + defaultNetwork: 1, + sshCredentials: 1, + _platform: 1 }; const resourcesSchema = Joi.object({ - appId: Joi.string().required().messages({ - 'string.empty': 'App ID is required.', - 'any.required': 'App ID is a required field.' - }), - appSecret: Joi.string().required().messages({ - 'string.empty': 'App Secret is required.', - 'any.required': 'App Secret is a required field.' - }), - platform: Joi.string().required().messages({ - 'string.empty': 'Platform is required.', + _platform: Joi.required().messages({ 'any.required': 'Platform is a required field.' + }) +}).unknown().options({ abortEarly: false }); + +const credentialsSchema = Joi.object({ + user: Joi.string().required().messages({ + 'string.empty': 'Credentials: Users is required.', + 'any.required': 'Credentials: Users is a required field.' + }), + secret: Joi.string().required().messages({ + 'string.empty': 'Credentials: Secret is required.', + 'any.required': 'Credentials: Secret is a required field.' }), }).unknown().options({ abortEarly: false }); module.exports = { extend: '@apostrophecms/piece-type', options: { - label: 'Resource', + label: 'Resource' }, fields: { add: { uuid: { type: 'string', - label: 'UUID', - readOnly: true + label: 'UUID' }, - platform: { - type: 'string', - label: 'Platform' + _platform: { + label: 'Platform', + type: 'relationship', + withType: "platforms", + max:1 }, - appId: { + securityGroup:{ type: 'string', - label: 'App ID', + label: 'Security Group' }, - appSecret: { - type: 'string', - label: 'App Secret', + sshCredentials: { + type: 'object', + label: 'SSH Credentials', + fields: { + add: { + username: { + type: 'string', + label: 'Username', + }, + privateKey: { + type: 'string', + label: 'Private Key', + textarea: true, + }, + keyPairName: { + type: 'string', + label: 'Key Pair Name', + }, + } + } + }, + subnet:{ + type: 'string', + label: 'Subnet' + }, + endpoint:{ + type: 'string', + label: 'Endpoint' + }, + identityVersion:{ + type: 'select', + label: 'Identity Version', + choices: [ + {'label':'v3', value:'3'} + ], + def: '3' + }, + defaultNetwork:{ + type: 'string', + label: 'Default Network' + }, + credentials:{ + type: 'object', + label: 'Credentials', + fields: { + add: { + user: { + type: 'string', + label: 'Username', + }, + secret: { + type: 'string', + label: 'Secret', + textarea: true, + }, + domain: { + type: 'string', + label: 'Domain', + }, + } + } + } }, group: { basics: { label: 'Details', - fields: ['title', 'uuid', 'platform', 'appId', 'appSecret'] + fields: ['title', 'uuid', '_platform', 'identityVersion'] + }, + network: { + label: 'Network', + fields: ['defaultNetwork','endpoint', 'subnet'] + }, + security: { + label: 'Security', + fields: ['securityGroup','credentials','sshCredentials'] } + } }, handlers(self) { @@ -79,16 +155,7 @@ module.exports = { await generateUuid(doc); try{ - if(doc.aposMode === 'published'){ - const message = await exn.register_cloud( - doc.uuid, - doc.appId, - doc.appSecret, - ) - console.log("Registered ",message); - - } - await self.updateWithPlatformInfo(doc); + await self.updateWithPlatformInfo(req,doc); await assignOrganization(req, doc); }catch(e){ @@ -96,11 +163,10 @@ module.exports = { } } }, - - beforeSave: { async handler(req, doc, options) { try { + await self.updateWithPlatformInfo(req,doc) self.validateDocument(doc); } catch (error) { if (error.name === 'required' && error.error && error.error.length > 0) { @@ -113,28 +179,45 @@ module.exports = { } } } - } + }, } }, - methods(self) { return { - async updateWithPlatformInfo(doc) { - if (doc.platform && !doc.platformUpdated) { - const platformPiece = await self.apos.doc.db.findOne({ - type: 'platforms', - uuid: doc.platform - }); + async updateWithPlatformInfo(req, doc) { - if (platformPiece) { - doc.platform = platformPiece.title; - doc.platformUpdated = true; - } else { - throw self.apos.error('notfound', 'Platform not found'); + if(req.body.platform && req.body.platform.uuid){ + + const platform = await self.apos.modules['platforms'].find(req,{'uuid':req.body.platform.uuid}).toObject(); + if(!platform){ + throw self.apos.error('notfound', 'Platform not found or empty'); } + doc.platformIds = [platform._id] + doc._platform = [platform] + delete req.body.platform } - }, + return doc + }, + cleanUp:function(self,req, resources){ + + _.each(resources,(r)=>{ + r= self.removeForbiddenFields(req,r) + r['platform'] = null + + if( r._platform.length > 0 ){ + r['platform'] = { + 'uuid': r._platform[0].uuid, + 'title': r._platform[0].title, + } + } + + delete r._platform + + }) + + return resources + }, validateDocument(doc) { const validateField = (data, schema) => { const {error} = schema.validate(data); @@ -147,6 +230,7 @@ module.exports = { } }; validateField(doc, resourcesSchema); + validateField(doc.credentials, credentialsSchema); } } }, @@ -159,10 +243,24 @@ module.exports = { const adminOrganization = currentUser.organization; try { const filters = { - organization: adminOrganization + organization: adminOrganization, + }; + const page = req.query.page || 1 + const pageSize = req.query.pageSize || 10 + const count = await self.find(req, filters).toCount() + + const resources = await self.find(req, filters).project(projection) + .withPublished(true) + .perPage(pageSize) + .page(page) + .toArray() + + + return { + "total":count, + "page": page, + "results":self.cleanUp(self,req,resources) }; - const resources = await self.find(req, filters).project(projection).toArray(); - return resources; } catch (error) { throw self.apos.error('notfound', 'Resource not found'); } @@ -182,19 +280,31 @@ module.exports = { throw self.apos.error('notfound', 'Resource not found'); } - return doc; + return self.cleanUp(self,req, [doc])[0] } catch (error) { throw self.apos.error(error.name, error.message); } }, - async 'candidates'(req) { + async ':uuid/candidates'(req) { + const uuid = req.params.uuid; if (!( req.user.organization)) { throw self.apos.error('forbidden', 'You do not have permission to perform this action'); } + const currentUser = req.user; + const adminOrganization = currentUser.organization; + try { + const doc = await self.find(req, { uuid: uuid , organization:adminOrganization}).project(projection).toObject(); + if (!doc) { + throw self.apos.error('notfound', 'Resource not found'); + } + + + await exn.register_cloud(doc) + await new Promise(resolve => setTimeout(resolve, 10000)); const message = await exn.get_cloud_candidates() return _.map(JSON.parse(message.body), (r)=>{ return { @@ -262,7 +372,6 @@ module.exports = { if (!(req.user.role === "admin" && req.user.organization)) { throw self.apos.error('forbidden', 'You do not have permission to perform this action'); } - self.validateDocument(updateData); const adminOrganization = req.user.organization; @@ -271,33 +380,18 @@ module.exports = { uuid: uuid, organization: adminOrganization }; - const resourcesToUpdate = await self.find(req, filters).project(projection).toArray(); - - if (!resourcesToUpdate || resourcesToUpdate.length === 0) { + const doc = await self.find(req, filters).toObject(); + if (!doc) { throw self.apos.error('notfound', 'Resource not found'); } + await self.updateWithPlatformInfo(req,doc) + self.validateDocument(doc) - const doc = resourcesToUpdate[0]; + let update = { ...doc, ...updateData }; + await self.update(req,update) + return self.cleanUp(self,req,[update])[0] - if ('platform' in updateData) { - let docToUpdate = { ...doc, ...updateData }; - await self.updateWithPlatformInfo(docToUpdate); - - await self.apos.doc.db.updateOne( - { uuid: uuid }, - { $set: docToUpdate } - ); - } else { - await self.apos.doc.db.updateOne( - { uuid: uuid }, - { $set: updateData } - ); - } - - const resourceUpdated = await self.find(req, filters).project(projection).toArray(); - - return resourceUpdated; } catch (error) { throw self.apos.error(error.name, error.message); } diff --git a/package.json b/package.json index 04aa088..13e688f 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "dependencies": { "@johmun/vue-tags-input": "^2.1.0", "apostrophe": "^3.61.1", + "dotenv": "^16.4.5", "flat": "^5.0.2", "joi": "^17.11.0", "mathjs": "^12.2.1",