diff --git a/src/fireedge/src/client/components/DebugLog/utils.js b/src/fireedge/src/client/components/DebugLog/utils.js index 67aef56cef..33873a5c1b 100644 --- a/src/fireedge/src/client/components/DebugLog/utils.js +++ b/src/fireedge/src/client/components/DebugLog/utils.js @@ -64,10 +64,15 @@ export const concatNewMessageToLog = (log, message = {}) => { const { data, command, commandId } = message - return { - ...log, - [command]: { + if (log?.[command]?.[commandId] !== undefined) { + log[command][commandId]?.push(data) + } else if (log?.[command] !== undefined) { + log[command][commandId] = [data] + } else { + log[command] = { [commandId]: [...(log?.[command]?.[commandId] ?? []), data], - }, + } } + + return { ...log } } diff --git a/src/fireedge/src/client/containers/Providers/Create.js b/src/fireedge/src/client/containers/Providers/Create.js index d1fd2d0934..37535f183d 100644 --- a/src/fireedge/src/client/containers/Providers/Create.js +++ b/src/fireedge/src/client/containers/Providers/Create.js @@ -49,7 +49,10 @@ function ProviderCreateForm() { const { enqueueSuccess, enqueueError } = useGeneralApi() const [createProvider] = useCreateProviderMutation() - const [updateProvider] = useUpdateProviderMutation() + const [ + updateProvider, + { isSuccess: successUpdate, originalArgs: { id: updatedProviderId } = {} }, + ] = useUpdateProviderMutation() const { data: providerConfig, error: errorConfig } = useGetProviderConfigQuery(undefined, { refetchOnMountOrArgChange: false }) @@ -71,7 +74,6 @@ function ProviderCreateForm() { if (id !== undefined) { await updateProvider({ id, data: submitData }) - enqueueSuccess(`Provider updated - ID: ${id}`) } else { if (!isValidProviderTemplate(submitData, providerConfig)) { enqueueError( @@ -81,7 +83,7 @@ function ProviderCreateForm() { } const responseId = await createProvider({ data: submitData }).unwrap() - enqueueSuccess(`Provider created - ID: ${responseId}`) + responseId && enqueueSuccess(`Provider created - ID: ${responseId}`) } history.push(PATH.PROVIDERS.LIST) @@ -93,6 +95,11 @@ function ProviderCreateForm() { id && getConnection(id) }, []) + useEffect(() => { + successUpdate && + enqueueSuccess(`Provider updated - ID: ${updatedProviderId}`) + }, [successUpdate]) + if (errorConfig || errorConnection || errorProvider) { return } diff --git a/src/fireedge/src/client/containers/Providers/index.js b/src/fireedge/src/client/containers/Providers/index.js index 7904518c7f..ebd3d75f97 100644 --- a/src/fireedge/src/client/containers/Providers/index.js +++ b/src/fireedge/src/client/containers/Providers/index.js @@ -49,8 +49,14 @@ function Providers() { const { enqueueSuccess } = useGeneralApi() const { data: providerConfig } = useGetProviderConfigQuery() - const [deleteProvider, { isLoading: isDeleting }] = - useDeleteProviderMutation() + const [ + deleteProvider, + { + isLoading: isDeleting, + isSuccess: successDelete, + originalArgs: { id: deletedProviderId } = {}, + }, + ] = useDeleteProviderMutation() const { refetch, @@ -68,10 +74,14 @@ function Providers() { try { hide() await deleteProvider({ id }) - enqueueSuccess(`Provider deleted - ID: ${id}`) } catch {} } + useEffect(() => { + successDelete && + enqueueSuccess(`Provider deleted - ID: ${deletedProviderId}`) + }, [successDelete]) + return ( <> { const { enqueueSuccess } = useGeneralApi() - const [removeResource, { isLoading: loadingRemove }] = - useRemoveResourceMutation() + const [ + removeResource, + { + isLoading: loadingRemove, + isSuccess: successRemove, + originalArgs: { id: deletedDatastoreId } = {}, + }, + ] = useRemoveResourceMutation() const { data } = useGetProvisionQuery(id) const provisionDatastores = @@ -42,6 +48,11 @@ const Datastores = memo( (datastore) => +datastore.id ) ?? [] + useEffect(() => { + successRemove && + enqueueSuccess(`Datastore deleted - ID: ${deletedDatastoreId}`) + }, [successRemove]) + return ( } diff --git a/src/fireedge/src/client/containers/Provisions/DialogInfo/hosts.js b/src/fireedge/src/client/containers/Provisions/DialogInfo/hosts.js index 0a742d5905..edcb983406 100644 --- a/src/fireedge/src/client/containers/Provisions/DialogInfo/hosts.js +++ b/src/fireedge/src/client/containers/Provisions/DialogInfo/hosts.js @@ -40,7 +40,7 @@ import { T } from 'client/constants' const Hosts = memo(({ id }) => { const [amount, setAmount] = useState(() => 1) - const { enqueueSuccess, enqueueInfo } = useGeneralApi() + const { enqueueInfo } = useGeneralApi() const [addHost, { isLoading: loadingAddHost }] = useAddHostToProvisionMutation() @@ -77,7 +77,7 @@ const Hosts = memo(({ id }) => { isSubmitting={loadingAddHost} onClick={async () => { addHost({ id, amount }) - enqueueSuccess(`Host added ${amount}x`) + enqueueInfo(`Adding ${amount} Host${amount > 1 ? 's' : ''}`) }} /> @@ -125,7 +125,7 @@ const Hosts = memo(({ id }) => { id: host.ID, resource: 'host', }) - enqueueSuccess(`Host deleted - ID: ${host.ID}`) + enqueueInfo(`Deleting Host - ID:${host.ID}`) }} /> diff --git a/src/fireedge/src/client/features/OneApi/provision.js b/src/fireedge/src/client/features/OneApi/provision.js index 7edfed8dc9..97be922b09 100644 --- a/src/fireedge/src/client/features/OneApi/provision.js +++ b/src/fireedge/src/client/features/OneApi/provision.js @@ -220,7 +220,7 @@ const provisionApi = oneApi.injectEndpoints({ * @returns {object} Object of document deleted * @throws Fails when response isn't code 200 */ - query: ({ provision: _, ...params }) => { + query: (params) => { const name = Actions.PROVISION_DELETE_RESOURCE const command = { name, ...Commands[name] } diff --git a/src/fireedge/src/server/routes/api/oneprovision/provision/functions.js b/src/fireedge/src/server/routes/api/oneprovision/provision/functions.js index f034442946..2f80955798 100644 --- a/src/fireedge/src/server/routes/api/oneprovision/provision/functions.js +++ b/src/fireedge/src/server/routes/api/oneprovision/provision/functions.js @@ -15,7 +15,6 @@ * ------------------------------------------------------------------------- */ const { parse } = require('yaml') -const { v4 } = require('uuid') const { Validator } = require('jsonschema') const { createWriteStream } = require('fs-extra') const { lockSync, checkSync, unlockSync } = require('lockfile') @@ -24,7 +23,6 @@ const { sprintf } = require('sprintf-js') const { Actions } = require('server/utils/constants/commands/document') const { defaults, httpCodes } = require('server/utils/constants') -const { publish } = require('server/utils/server') const { httpResponse, parsePostData, @@ -33,7 +31,6 @@ const { getDirectories, getFilesbyEXT, executeCommand, - executeCommandAsync, removeFile, } = require('server/utils/server') const { checkEmptyObject } = require('server/utils/general') @@ -49,230 +46,37 @@ const { getSpecificConfig, } = require('server/routes/api/oneprovision/utils') const { provision } = require('server/routes/api/oneprovision/schemas') +const { + executeWithEmit, + logData, + addResourceSync, + relName, + ext, + logFile, + appendError, + executingMessage, +} = require('server/routes/api/oneprovision/provision/helpers') const { defaultFolderTmpProvision, defaultCommandProvision, defaultEmptyFunction, defaultErrorTemplate, - defaultRegexpStartJSON, - defaultRegexpEndJSON, - defaultRegexpSplitLine, defaultRegexID, } = defaults const { ok, notFound, accepted, internalServerError } = httpCodes const httpInternalError = httpResponse(internalServerError, '', '') -const logFile = { - name: 'stdouterr', - ext: 'log', -} const provisionFile = { name: 'provision', ext: 'yaml', } -const relName = 'provision-mapping' -const ext = 'yml' -const appendError = '.ERROR' +const messageExecuting = 'Executing command:' + +const optionalParameters = ['--debug', '--json', '--batch'] /** - * Execute command Async and emit in WS. - * - * @param {string} command - command to execute - * @param {object} actions - external functions when command emit in stderr, stdout and finalize - * @param {Function} actions.err - emit when have stderr - * @param {Function} actions.out - emit when have stdout - * @param {Function} actions.close - emit when finalize - * @param {object} dataForLog - data - * @param {number} dataForLog.id - data id - * @param {string} dataForLog.command - data command - * @returns {boolean} check if emmit data - */ -const executeWithEmit = (command = [], actions = {}, dataForLog = {}) => { - if ( - command && - Array.isArray(command) && - command.length > 0 && - actions && - dataForLog - ) { - const { err: externalErr, out: externalOut, close: externalClose } = actions - const err = - externalErr && typeof externalErr === 'function' - ? externalErr - : defaultEmptyFunction - const out = - externalOut && typeof externalOut === 'function' - ? externalOut - : defaultEmptyFunction - const close = - externalClose && typeof externalClose === 'function' - ? actions.close - : defaultEmptyFunction - - // data for log - const id = (dataForLog && dataForLog.id) || '' - const commandName = (dataForLog && dataForLog.command) || '' - - let lastLine = '' - const uuid = v4() - - let pendingMessages = '' - - /** - * Emit data of command. - * - * @param {string} message - line of command CLI - * @param {Function} callback - function when recieve a information - */ - const emit = (message, callback = defaultEmptyFunction) => { - /** - * Publisher data to WS. - * - * @param {string} line - command CLI line - */ - const publisher = (line = '') => { - const resposeData = callback(line, uuid) || { - id, - data: line, - command: commandName, - commandId: uuid, - } - publish(defaultCommandProvision, resposeData) - } - - message - .toString() - .split(defaultRegexpSplitLine) - .forEach((line) => { - if (line) { - if ( - (defaultRegexpStartJSON.test(line) && - defaultRegexpEndJSON.test(line)) || - (!defaultRegexpStartJSON.test(line) && - !defaultRegexpEndJSON.test(line) && - pendingMessages.length === 0) - ) { - lastLine = line - publisher(lastLine) - } else if ( - (defaultRegexpStartJSON.test(line) && - !defaultRegexpEndJSON.test(line)) || - (!defaultRegexpStartJSON.test(line) && - !defaultRegexpEndJSON.test(line) && - pendingMessages.length > 0) - ) { - pendingMessages += line - } else { - lastLine = pendingMessages + line - publisher(lastLine) - pendingMessages = '' - } - } - }) - } - - executeCommandAsync( - defaultCommandProvision, - command, - getSpecificConfig('oneprovision_prepend_command'), - { - err: (message) => { - emit(message, err) - }, - out: (message) => { - emit(message, out) - }, - close: (success) => { - close(success, lastLine) - }, - } - ) - - return true - } -} - -/** - * Find log data. - * - * @param {string} id - id of provision - * @param {boolean} fullPath - if need return the path of log - * @returns {object} data of log - */ -const logData = (id, fullPath = false) => { - let rtn = false - if (typeof id !== 'undefined') { - const basePath = `${global.paths.CPI}/provision` - const relFile = `${basePath}/${relName}` - const relFileYML = `${relFile}.${ext}` - const find = findRecursiveFolder(basePath, id) - - /** - * Not found log. - */ - const rtnNotFound = () => { - rtn = false - } - - /** - * Found log. - * - * @param {string} path - path of log - * @param {string} uuid - uuid of log - */ - const rtnFound = (path = '', uuid) => { - if (path) { - const stringPath = `${path}/${logFile.name}.${logFile.ext}` - existsFile( - stringPath, - (filedata) => { - rtn = { uuid, log: filedata.split(defaultRegexpSplitLine) } - if (fullPath) { - rtn.fullPath = stringPath - } - }, - rtnNotFound - ) - } - } - - if (find) { - rtnFound(find) - } else { - existsFile( - relFileYML, - (filedata) => { - const fileData = parse(filedata) || {} - if (fileData[id]) { - const findPending = findRecursiveFolder(basePath, fileData[id]) - if (findPending) { - rtnFound(findPending, fileData[id]) - } else { - const findError = findRecursiveFolder( - basePath, - fileData[id] + appendError - ) - if (findError) { - rtnFound(findError, fileData[id]) - } else { - rtnNotFound() - } - } - } else { - rtnNotFound() - } - }, - rtnNotFound - ) - } - } - - return rtn -} - -/** - * Get default provisions. + * Get default provisions (Sync route). * * @param {object} res - http response * @param {Function} next - express stepper @@ -289,129 +93,129 @@ const getProvisionDefaults = ( ) => { const extFiles = 'yml' const { user, password } = userData - let rtn = httpInternalError const files = {} const path = `${global.paths.SHARE_CPI}` - const endpoint = getEndpoint() - if (user && password) { - const authCommand = ['--user', user, '--password', password] - const directories = getDirectories(path) - let description = '' - let providers = {} - let provisions = {} - /** - * Fill description of provision. - * - * @param {string} content - content of description provision - */ - const fillDescription = (content = '') => { - if (content) { - description = content - } + if (!(user && password)) { + res.locals.httpCode = httpInternalError + next() + + return + } + + const authCommand = ['--user', user, '--password', password] + const directories = getDirectories(path) + let description = '' + let providers = {} + let provisions = {} + /** + * Fill description of provision. + * + * @param {string} content - content of description provision + */ + const fillDescription = (content = '') => { + if (content) { + description = content } - /** - * Fill providers. - * - * @param {string} content - content of provider - * @param {string} name - name of provider - */ - const fillProviders = (content = '', name = '') => { - if (content && name) { - if (!providers[name]) { - providers[name] = [] + } + /** + * Fill providers. + * + * @param {string} content - content of provider + * @param {string} name - name of provider + */ + const fillProviders = (content = '', name = '') => { + if (content && name) { + if (!providers[name]) { + providers[name] = [] + } + try { + providers[name].push(parse(content)) + } catch (error) {} + } + } + /** + * Fill provisions. + * + * @param {string} content - content of provision + * @param {string} filePath - path of provision yamls + * @param {string} pathCli - path for command + */ + const fillProvisions = (content = '', filePath = '', pathCli = '') => { + if (content && filePath && path) { + const name = basename(filePath).replace(`.${extFiles}`, '') + const paramsCommand = [ + 'validate', + '--dump', + filePath, + ...authCommand, + ...endpoint, + ] + const executedCommand = executeCommand( + defaultCommandProvision, + paramsCommand, + getSpecificConfig('oneprovision_prepend_command'), + { cwd: pathCli } + ) + if (executedCommand && executedCommand.success) { + if (!provisions[name]) { + provisions[name] = [] } try { - providers[name].push(parse(content)) - } catch (error) {} + provisions[name].push(parse(executedCommand.data)) + } catch (err) {} } } - /** - * Fill provisions. - * - * @param {string} content - content of provision - * @param {string} filePath - path of provision yamls - * @param {string} pathCli - path for command - */ - const fillProvisions = (content = '', filePath = '', pathCli = '') => { - if (content && filePath && path) { - const name = basename(filePath).replace(`.${extFiles}`, '') - const paramsCommand = [ - 'validate', - '--dump', - filePath, - ...authCommand, - ...endpoint, - ] - const executedCommand = executeCommand( - defaultCommandProvision, - paramsCommand, - getSpecificConfig('oneprovision_prepend_command'), - { cwd: pathCli } - ) - if (executedCommand && executedCommand.success) { - if (!provisions[name]) { - provisions[name] = [] - } - try { - provisions[name].push(parse(executedCommand.data)) - } catch (err) {} - } - } - } - - directories.forEach((directory = {}) => { - if (directory.filename && directory.path) { - // description - existsFile(`${directory.path}/description.md`, fillDescription) - - // providers - getDirectories(`${directory.path}/providers`).forEach( - (provider = {}) => { - if (provider.filename && provider.path) { - getFilesbyEXT(provider.path, extFiles).forEach((file) => { - existsFile(file, (content) => - fillProviders(content, provider.filename) - ) - }) - } - } - ) - - // provisions - getFilesbyEXT(`${directory.path}/provisions`, extFiles).forEach( - (file) => { - existsFile(file, (content, filePath) => - fillProvisions(content, filePath, dirname(file)) - ) - } - ) - - if ( - description && - !checkEmptyObject(providers) && - !checkEmptyObject(provisions) - ) { - files[directory.filename] = { - description, - providers, - provisions, - } - // clear - description = '' - providers = {} - provisions = {} - } - } - }) - rtn = httpResponse(ok, files) } - res.locals.httpCode = rtn + + directories.forEach((directory = {}) => { + if (directory.filename && directory.path) { + // description + existsFile(`${directory.path}/description.md`, fillDescription) + + // providers + getDirectories(`${directory.path}/providers`).forEach((provider = {}) => { + if (provider.filename && provider.path) { + getFilesbyEXT(provider.path, extFiles).forEach((file) => { + existsFile(file, (content) => + fillProviders(content, provider.filename) + ) + }) + } + }) + + // provisions + getFilesbyEXT(`${directory.path}/provisions`, extFiles).forEach( + (file) => { + existsFile(file, (content, filePath) => + fillProvisions(content, filePath, dirname(file)) + ) + } + ) + + if ( + description && + !checkEmptyObject(providers) && + !checkEmptyObject(provisions) + ) { + files[directory.filename] = { + description, + providers, + provisions, + } + // clear + description = '' + providers = {} + provisions = {} + } + } + }) + res.locals.httpCode = httpResponse(ok, files) next() } /** - * Get list for resource provisions. + * Get list for resource provisions (Sync route). * * @param {object} res - http response * @param {Function} next - express stepper @@ -429,41 +233,46 @@ const getListResourceProvision = ( ) => { const { user, password } = userData const { resource } = params - let rtn = httpInternalError - if (resource && user && password) { - const endpoint = getEndpoint() - const authCommand = ['--user', user, '--password', password] - const paramsCommand = [ - `${resource}`.toLowerCase(), - 'list', - ...authCommand, - ...endpoint, - '--json', - ] - const executedCommand = executeCommand( - defaultCommandProvision, - paramsCommand, - getSpecificConfig('oneprovision_prepend_command') - ) - try { - const response = executedCommand.success ? ok : internalServerError - res.locals.httpCode = httpResponse( - response, - JSON.parse(executedCommand.data) - ) - next() + if (!(resource && user && password)) { + res.locals.httpCode = httpInternalError + next() - return - } catch (error) { - rtn = httpResponse(internalServerError, '', executedCommand.data) - } + return + } + + const endpoint = getEndpoint() + const authCommand = ['--user', user, '--password', password] + const paramsCommand = [ + `${resource}`.toLowerCase(), + 'list', + ...authCommand, + ...endpoint, + '--json', + ] + const executedCommand = executeCommand( + defaultCommandProvision, + paramsCommand, + getSpecificConfig('oneprovision_prepend_command') + ) + try { + const response = executedCommand.success ? ok : internalServerError + res.locals.httpCode = httpResponse( + response, + JSON.parse(executedCommand.data) + ) + next() + } catch (error) { + res.locals.httpCode = httpResponse( + internalServerError, + '', + executedCommand.data + ) + next() } - res.locals.httpCode = rtn - next() } /** - * Get list provisions. + * Get list provisions (Sync route). * * @param {object} res - http response * @param {Function} next - express stepper @@ -481,66 +290,64 @@ const getListProvisions = ( ) => { const { user, password } = userData const { id } = params - let rtn = httpInternalError - if (user && password) { - const endpoint = getEndpoint() - const authCommand = ['--user', user, '--password', password] - let paramsCommand = ['list', ...authCommand, ...endpoint, '--json'] - if (Number.isInteger(parseInt(id, 10))) { - paramsCommand = [ - 'show', - `${id}`.toLowerCase(), - ...authCommand, - ...endpoint, - '--json', - ] - } - const executedCommand = executeCommand( - defaultCommandProvision, - paramsCommand, - getSpecificConfig('oneprovision_prepend_command') - ) - try { - const response = executedCommand.success ? ok : internalServerError - const data = JSON.parse(executedCommand.data) - - /** - * Parse provision.TEMPLATE.BODY to JSON. - * - * @param {object} oneProvision - provision - * @returns {object} provision with TEMPLATE.BODY in JSON - */ - const parseTemplateBody = (oneProvision) => { - if ( - oneProvision && - oneProvision.TEMPLATE && - oneProvision.TEMPLATE.BODY - ) { - oneProvision.TEMPLATE.BODY = JSON.parse(oneProvision.TEMPLATE.BODY) - } - - return oneProvision - } - - if (data && data.DOCUMENT_POOL && data.DOCUMENT_POOL.DOCUMENT) { - data.DOCUMENT_POOL.DOCUMENT = Array.isArray(data.DOCUMENT_POOL.DOCUMENT) - ? data.DOCUMENT_POOL.DOCUMENT.map(parseTemplateBody) - : parseTemplateBody(data.DOCUMENT_POOL.DOCUMENT) - } - res.locals.httpCode = httpResponse(response, data) - next() - - return - } catch (error) { - rtn = httpResponse(internalServerError, '', executedCommand.data) - } + if (!(user && password)) { + res.locals.httpCode = httpInternalError + next() + } + const endpoint = getEndpoint() + const authCommand = ['--user', user, '--password', password] + let paramsCommand = ['list', ...authCommand, ...endpoint, '--json'] + if (Number.isInteger(parseInt(id, 10))) { + paramsCommand = [ + 'show', + `${id}`.toLowerCase(), + ...authCommand, + ...endpoint, + '--json', + ] + } + const executedCommand = executeCommand( + defaultCommandProvision, + paramsCommand, + getSpecificConfig('oneprovision_prepend_command') + ) + try { + const response = executedCommand.success ? ok : internalServerError + const data = JSON.parse(executedCommand.data) + + /** + * Parse provision.TEMPLATE.BODY to JSON. + * + * @param {object} oneProvision - provision + * @returns {object} provision with TEMPLATE.BODY in JSON + */ + const parseTemplateBody = (oneProvision) => { + if (oneProvision?.TEMPLATE?.BODY) { + oneProvision.TEMPLATE.BODY = JSON.parse(oneProvision.TEMPLATE.BODY) + } + + return oneProvision + } + + if (data?.DOCUMENT_POOL?.DOCUMENT) { + data.DOCUMENT_POOL.DOCUMENT = Array.isArray(data.DOCUMENT_POOL.DOCUMENT) + ? data.DOCUMENT_POOL.DOCUMENT.map(parseTemplateBody) + : parseTemplateBody(data.DOCUMENT_POOL.DOCUMENT) + } + res.locals.httpCode = httpResponse(response, data) + next() + } catch (error) { + res.locals.httpCode = httpResponse( + internalServerError, + '', + executedCommand.data + ) + next() } - res.locals.httpCode = rtn - next() } /** - * Delete resource provisions. + * Delete resource provisions (Async route). * * @param {object} res - http response * @param {Function} next - express stepper @@ -558,39 +365,77 @@ const deleteResource = ( userData = {} ) => { const { user, password } = userData - const { resource, id } = params - let rtn = httpInternalError - if (resource && Number.isInteger(parseInt(id, 10)) && user && password) { - const endpoint = getEndpoint() - const authCommand = ['--user', user, '--password', password] - const paramsCommand = [ - `${resource}`.toLowerCase(), - 'delete', - `${id}`.toLowerCase(), - ...authCommand, - ...endpoint, - ] - const executedCommand = executeCommand( - defaultCommandProvision, - paramsCommand, - getSpecificConfig('oneprovision_prepend_command') + const { id, resource, provision: provisionId } = params + if ( + !( + resource && + Number.isInteger(parseInt(id, 10)) && + Number.isInteger(parseInt(provisionId, 10)) && + user && + password ) - try { - const response = executedCommand.success ? ok : internalServerError - rtn = httpResponse( - response, - executedCommand.data ? JSON.parse(executedCommand.data) : params.id - ) - } catch (error) { - rtn = httpResponse(internalServerError, '', executedCommand.data) - } + ) { + res.locals.httpCode = httpInternalError + next() + + return } - res.locals.httpCode = rtn + + const endpoint = getEndpoint() + const authCommand = ['--user', user, '--password', password] + const paramsCommand = [ + `${resource}`.toLowerCase(), + 'delete', + `${id}`.toLowerCase(), + ...optionalParameters, + ...authCommand, + ...endpoint, + ] + const commandString = `${paramsCommand[0]} ${paramsCommand[1]}` + let flagSeparateLog = true + const dataLog = logData(provisionId, true) + const stream = + dataLog?.fullPath && createWriteStream(dataLog.fullPath, { flags: 'a' }) + const emit = (lastLine, uuid) => { + const renderLine = { + id: provisionId, + data: lastLine, + command: commandString, + commandId: uuid, + } + if (flagSeparateLog) { + renderLine.data = executingMessage(`${messageExecuting} ${commandString}`) + stream?.write?.(`${JSON.stringify(renderLine)}\n`) + flagSeparateLog = false + renderLine.data = lastLine + } + stream?.write?.(`${JSON.stringify(renderLine)}\n`) + + return renderLine + } + + // execute Async Command + const executedCommand = executeWithEmit( + paramsCommand, + { + close: (success, lastLine) => { + stream?.end() + }, + out: emit, + err: emit, + }, + { id: provisionId, command: commandString } + ) + + res.locals.httpCode = httpResponse( + executedCommand ? accepted : internalServerError, + provisionId + ) next() } /** - * Delete provision. + * Delete provision (Async route). * * @param {object} res - http response * @param {Function} next - express stepper @@ -615,136 +460,126 @@ const deleteProvision = ( const relFileLOCK = `${relFile}.lock` const { user, password } = userData const { id, cleanup, force } = params - const rtn = httpInternalError - if (Number.isInteger(parseInt(id, 10)) && user && password) { - const command = 'delete' - const endpoint = getEndpoint() - const authCommand = ['--user', user, '--password', password] - const cleanUpTag = cleanup ? ['--cleanup'] : [] - const forceTag = force ? ['--force'] : [] - const paramsCommand = [ - command, - id, - '--batch', - '--debug', - '--json', - ...cleanUpTag, - ...forceTag, - ...authCommand, - ...endpoint, - ] - - // get Log file - const dataLog = logData(params.id, true) - - // create stream for write into file - const stream = - dataLog && - dataLog.fullPath && - createWriteStream(dataLog.fullPath, { flags: 'a' }) - - /** - * This function is performed for each command line response. - * - * @param {string} lastLine - last line command - * @param {string} uuid - uuid commnand - */ - const emit = (lastLine, uuid) => { - const renderLine = { - id: params.id, - data: lastLine, - command: command, - commandId: uuid, - } - stream && stream.write && stream.write(`${JSON.stringify(renderLine)}\n`) - } - - /** - * This function is only executed if the command is completed. - * - * @param {boolean} success - check in command complete succefully - * @param {string} lastLine - last line command - */ - const close = (success, lastLine) => { - if (success) { - stream && stream.end && stream.end() - existsFile(relFileYML, (filedata) => { - let uuid = '' - if (!checkSync(relFileLOCK)) { - lockSync(relFileLOCK) - const fileData = parse(filedata) || {} - if (fileData[params.id]) { - uuid = fileData[params.id] - delete fileData[params.id] - createTemporalFile( - basePath, - ext, - createYMLContent( - Object.keys(fileData).length !== 0 && - fileData.constructor === Object && - fileData - ), - relName - ) - } - unlockSync(relFileLOCK) - if (uuid) { - // provisions in deploy - const provisionFolder = findRecursiveFolder( - `${global.paths.CPI}/provision`, - uuid - ) - provisionFolder && removeFile(provisionFolder) - // provisions in error - const findFolderERROR = findRecursiveFolder( - `${global.paths.CPI}/provision`, - uuid + appendError - ) - findFolderERROR && removeFile(findFolderERROR) - } - } - }) - const findFolder = findRecursiveFolder( - `${global.paths.CPI}/provision`, - params.id - ) - findFolder && removeFile(findFolder) - } else { - const oneConnect = oneConnection(user, password) - oneConnect({ - action: Actions.DOCUMENT_UPDATE, - parameters: [ - parseInt(params.id, 10), - sprintf(defaultErrorTemplate, lastLine), - 1, - ], - callback: defaultEmptyFunction, - }) - } - } - - // execute Async Command - const executedCommand = executeWithEmit( - paramsCommand, - { close, out: emit, err: emit }, - { id: params.id, command } - ) - - // response Http - res.locals.httpCode = httpResponse( - executedCommand ? accepted : internalServerError, - params.id - ) + if (!(Number.isInteger(parseInt(id, 10)) && user && password)) { + res.locals.httpCode = httpInternalError next() return } - res.locals.httpCode = rtn + + const command = 'delete' + const endpoint = getEndpoint() + const authCommand = ['--user', user, '--password', password] + const cleanUpTag = cleanup ? ['--cleanup'] : [] + const forceTag = force ? ['--force'] : [] + const paramsCommand = [ + command, + id, + ...optionalParameters, + ...cleanUpTag, + ...forceTag, + ...authCommand, + ...endpoint, + ] + + const dataLog = logData(params.id, true) // get Log file + const stream = + dataLog?.fullPath && createWriteStream(dataLog.fullPath, { flags: 'a' }) + + const emit = (lastLine, uuid) => { + const renderLine = { + id: params.id, + data: lastLine, + command: command, + commandId: uuid, + } + + stream?.write?.(`${JSON.stringify(renderLine)}\n`) + + return renderLine + } + + /** + * This function is only executed if the command is completed. + * + * @param {boolean} success - check in command complete succefully + * @param {string} lastLine - last line command + */ + const close = (success, lastLine) => { + if (success) { + stream?.end?.() + existsFile(relFileYML, (filedata) => { + let uuid = '' + if (!checkSync(relFileLOCK)) { + lockSync(relFileLOCK) + const fileData = parse(filedata) || {} + if (fileData[params.id]) { + uuid = fileData[params.id] + delete fileData[params.id] + createTemporalFile( + basePath, + ext, + createYMLContent( + Object.keys(fileData).length !== 0 && + fileData.constructor === Object && + fileData + ), + relName + ) + } + unlockSync(relFileLOCK) + if (uuid) { + // provisions in deploy + const provisionFolder = findRecursiveFolder( + `${global.paths.CPI}/provision`, + uuid + ) + provisionFolder && removeFile(provisionFolder) + // provisions in error + const findFolderERROR = findRecursiveFolder( + `${global.paths.CPI}/provision`, + uuid + appendError + ) + findFolderERROR && removeFile(findFolderERROR) + } + } + }) + const findFolder = findRecursiveFolder( + `${global.paths.CPI}/provision`, + params.id + ) + findFolder && removeFile(findFolder) + } else { + const oneConnect = oneConnection(user, password) + oneConnect({ + action: Actions.DOCUMENT_UPDATE, + parameters: [ + parseInt(params.id, 10), + sprintf(defaultErrorTemplate, lastLine), + 1, + ], + callback: defaultEmptyFunction, + }) + } + } + + // execute Async Command + const executedCommand = executeWithEmit( + paramsCommand, + { close, out: emit, err: emit }, + { id: params.id, command } + ) + + // response Http + res.locals.httpCode = httpResponse( + executedCommand ? accepted : internalServerError, + params.id + ) next() } /** - * Execute command of host into provision. + * Execute command of host into provision (Sync route). * * @param {object} res - http response * @param {Function} next - express stepper @@ -763,41 +598,46 @@ const hostCommand = ( ) => { const { user, password } = userData const { action, id } = params - let rtn = httpInternalError - if (action && Number.isInteger(parseInt(id, 10)) && user && password) { - const endpoint = getEndpoint() - const authCommand = ['--user', user, '--password', password] - const paramsCommand = [ - 'host', - `${action}`.toLowerCase(), - `${id}`.toLowerCase(), - ...authCommand, - ...endpoint, - ] - const executedCommand = executeCommand( - defaultCommandProvision, - paramsCommand, - getSpecificConfig('oneprovision_prepend_command') - ) - try { - const response = executedCommand.success ? ok : internalServerError - res.locals.httpCode = httpResponse( - response, - executedCommand.data ? JSON.parse(executedCommand.data) : id - ) - next() + if (!(action && Number.isInteger(parseInt(id, 10)) && user && password)) { + res.locals.httpCode = httpInternalError + next() - return - } catch (error) { - rtn = httpResponse(internalServerError, '', executedCommand.data) - } + return + } + + const endpoint = getEndpoint() + const authCommand = ['--user', user, '--password', password] + const paramsCommand = [ + 'host', + `${action}`.toLowerCase(), + `${id}`.toLowerCase(), + ...authCommand, + ...endpoint, + ] + const executedCommand = executeCommand( + defaultCommandProvision, + paramsCommand, + getSpecificConfig('oneprovision_prepend_command') + ) + try { + const response = executedCommand.success ? ok : internalServerError + res.locals.httpCode = httpResponse( + response, + executedCommand.data ? JSON.parse(executedCommand.data) : id + ) + next() + } catch (error) { + res.locals.httpCode = httpResponse( + internalServerError, + '', + executedCommand.data + ) + next() } - res.locals.httpCode = rtn - next() } /** - * Create a provision. + * Create a provision (Async route). * * @param {object} res - http response * @param {Function} next - express stepper @@ -820,179 +660,177 @@ const createProvision = ( const relFileLOCK = `${relFile}.lock` const { user, password, id } = userData const { data } = params - const rtn = httpInternalError - if (data && user && password) { - const optionalCommand = addOptionalCreateCommand() - const content = createYMLContent(parsePostData(data)) + const optionalCommand = addOptionalCreateCommand() + const content = createYMLContent(parsePostData(data)) - if (content) { - const command = 'create' - const authCommand = ['--user', user, '--password', password] - const endpoint = getEndpoint() - const files = createFolderWithFiles( - `${global.paths.CPI}/provision/${id}/tmp`, - [ - { name: logFile.name, ext: logFile.ext }, - { name: provisionFile.name, ext: provisionFile.ext, content }, - ] - ) - if (files && files.name && files.files) { - /** - * Find file in created files. - * - * @param {string} val - filename - * @param {string} extension - file extension - * @param {Array} arr - array of files - * @returns {Array} path file - */ - const find = (val = '', extension = '', arr = files.files) => - arr.find( - (e) => - e && - e.path && - e.ext && - e.name && - e.name === val && - e.ext === extension - ) + const responseInternalError = () => { + res.locals.httpCode = httpInternalError + next() + } + if (!(data && user && password) || !content) { + responseInternalError() - const config = find(provisionFile.name, provisionFile.ext) - const log = find(logFile.name, logFile.ext) - if (config && log) { - /** - * Create provision. - * - * @param {string} filedata - provision data - */ - const create = (filedata = '') => { - const paramsCommand = [ - command, - config.path, - '--batch', - '--debug', - '--json', - ...optionalCommand, - ...authCommand, - ...endpoint, - ] + return + } - // stream file log - const stream = createWriteStream(log.path, { flags: 'a' }) + const command = 'create' + const authCommand = ['--user', user, '--password', password] + const endpoint = getEndpoint() + const files = createFolderWithFiles( + `${global.paths.CPI}/provision/${id}/tmp`, + [ + { name: logFile.name, ext: logFile.ext }, + { name: provisionFile.name, ext: provisionFile.ext, content }, + ] + ) + if (!(files.name && files.files)) { + responseInternalError() - /** - * This function is performed for each command line response. - * - * @param {string} lastLine - last line command - * @param {string} uuid - UUID command - * @returns {object} string line of command - */ - const emit = (lastLine, uuid) => { - if (lastLine && uuid) { - if (defaultRegexID.test(lastLine) && !checkSync(relFileLOCK)) { - const fileData = parse(filedata) || {} - const parseID = lastLine.match('\\d+') - const idResource = parseID[0] - if (idResource && !fileData[idResource]) { - lockSync(relFileLOCK) - fileData[idResource] = files.name - createTemporalFile( - basePath, - ext, - createYMLContent(fileData), - relName - ) - unlockSync(relFileLOCK) - } - } - const renderLine = { - id: files.name, - data: lastLine, - command: command, - commandId: uuid, - } - stream.write(`${JSON.stringify(renderLine)}\n`) + return + } - return renderLine - } - } + /** + * Find file in created files. + * + * @param {string} val - filename + * @param {string} extension - file extension + * @param {Array} arr - array of files + * @returns {Array} path file + */ + const find = (val = '', extension = '', arr = files.files) => + arr.find( + (e) => + e && e.path && e.ext && e.name && e.name === val && e.ext === extension + ) - /** - * This function is only executed if the command is completed. - * - * @param {boolean} success - check if command finish successfully - * @param {string} lastLine - last line command finish - */ - const close = (success, lastLine) => { - stream.end() - if (success && defaultRegexID.test(lastLine)) { - const newPath = renameFolder( - config.path, - lastLine.match('\\d+'), - 'replace' - ) - if (newPath) { - existsFile(relFileYML, (file) => { - if (!checkSync(relFileLOCK)) { - lockSync(relFileLOCK) - const fileData = parse(file) || {} - const findKey = Object.keys(fileData).find( - (key) => fileData[key] === files.name - ) - if (findKey) { - delete fileData[findKey] - createTemporalFile( - basePath, - ext, - createYMLContent( - Object.keys(fileData).length !== 0 && - fileData.constructor === Object && - fileData - ), - relName - ) - } - unlockSync(relFileLOCK) - } - }) - moveToFolder(newPath, '/../../../') - } - } - if (success === false) { - renameFolder(config.path, appendError, 'append') - } - } - executeWithEmit( - paramsCommand, - { close, out: emit, err: emit }, - { command } + const config = find(provisionFile.name, provisionFile.ext) + const log = find(logFile.name, logFile.ext) + if (!(config && log)) { + responseInternalError() + + return + } + + /** + * Create provision. + * + * @param {string} filedata - provision data + */ + const create = (filedata = '') => { + const paramsCommand = [ + command, + config.path, + ...optionalParameters, + ...optionalCommand, + ...authCommand, + ...endpoint, + ] + + // stream file log + const stream = createWriteStream(log.path, { flags: 'a' }) + + /** + * This function is performed for each command line response. + * + * @param {string} lastLine - last line command + * @param {string} uuid - UUID command + * @returns {object} string line of command + */ + const emit = (lastLine, uuid) => { + if (lastLine && uuid) { + if (defaultRegexID.test(lastLine) && !checkSync(relFileLOCK)) { + const fileData = parse(filedata) || {} + const parseID = lastLine.match('\\d+') + const idResource = parseID[0] + if (idResource && !fileData[idResource]) { + lockSync(relFileLOCK) + fileData[idResource] = files.name + createTemporalFile( + basePath, + ext, + createYMLContent(fileData), + relName ) + unlockSync(relFileLOCK) } - - existsFile( - relFileYML, - (filedata) => { - create(filedata) - }, - () => { - createFile(relFileYML, '', (filedata) => { - create(filedata) - }) - } - ) - res.locals.httpCode = httpResponse(accepted, files.name) - next() - - return } + const renderLine = { + id: files.name, + data: lastLine, + command: command, + commandId: uuid, + } + stream.write(`${JSON.stringify(renderLine)}\n`) + + return renderLine } } + + /** + * This function is only executed if the command is completed. + * + * @param {boolean} success - check if command finish successfully + * @param {string} lastLine - last line command finish + */ + const close = (success, lastLine) => { + stream.end() + if (success && defaultRegexID.test(lastLine)) { + const newPath = renameFolder( + config.path, + lastLine.match('\\d+'), + 'replace' + ) + if (newPath) { + existsFile(relFileYML, (file) => { + if (!checkSync(relFileLOCK)) { + lockSync(relFileLOCK) + const fileData = parse(file) || {} + const findKey = Object.keys(fileData).find( + (key) => fileData[key] === files.name + ) + if (findKey) { + delete fileData[findKey] + createTemporalFile( + basePath, + ext, + createYMLContent( + Object.keys(fileData).length !== 0 && + fileData.constructor === Object && + fileData + ), + relName + ) + } + unlockSync(relFileLOCK) + } + }) + moveToFolder(newPath, '/../../../') + } + } + if (success === false) { + renameFolder(config.path, appendError, 'append') + } + } + executeWithEmit(paramsCommand, { close, out: emit, err: emit }, { command }) } - res.locals.httpCode = rtn + + existsFile( + relFileYML, + (filedata) => { + create(filedata) + }, + () => { + createFile(relFileYML, '', (filedata) => { + create(filedata) + }) + } + ) + res.locals.httpCode = httpResponse(accepted, files.name) next() } /** - * Configure provision. + * Configure provision (Async route). * * @param {object} res - http response * @param {Function} next - express stepper @@ -1011,79 +849,73 @@ const configureProvision = ( const { user, password } = userData const { id } = params const rtn = httpInternalError - if (Number.isInteger(parseInt(id, 10)) && user && password) { - const command = 'configure' - const endpoint = getEndpoint() - const authCommand = ['--user', user, '--password', password] - const paramsCommand = [ - command, - id, - '--debug', - '--json', - '--fail_cleanup', - '--batch', - '--force', - ...authCommand, - ...endpoint, - ] - // get Log file - const dataLog = logData(id, true) - - // create stream for write into file - const stream = - dataLog && - dataLog.fullPath && - createWriteStream(dataLog.fullPath, { flags: 'a' }) - - /** - * This function is performed for each command line response. - * - * @param {string} lastLine - last line command - * @param {string} uuid - UUID command - */ - const emit = (lastLine, uuid) => { - const renderLine = { - id, - data: lastLine, - command: command, - commandId: uuid, - } - stream && stream.write && stream.write(`${JSON.stringify(renderLine)}\n`) - } - - /** - * This function is only executed if the command is completed. - * - * @param {boolean} success - check if command complete without errors - * @param {string} lastLine - last line command - */ - const close = (success, lastLine) => { - stream && stream.end && stream.end() - } - - // execute Async Command - const executedCommand = executeWithEmit( - paramsCommand, - { close, out: emit, err: emit }, - { id, command } - ) - - // response Http - res.locals.httpCode = httpResponse( - executedCommand ? accepted : internalServerError, - id - ) + if (!(Number.isInteger(parseInt(id, 10)) && user && password)) { + res.locals.httpCode = rtn next() return } - res.locals.httpCode = rtn + + const command = 'configure' + const endpoint = getEndpoint() + const authCommand = ['--user', user, '--password', password] + const paramsCommand = [ + command, + id, + '--fail_cleanup', + '--force', + ...optionalParameters, + ...authCommand, + ...endpoint, + ] + + // get Log file + const dataLog = logData(id, true) + + // create stream for write into file + const stream = + dataLog?.fullPath && createWriteStream(dataLog.fullPath, { flags: 'a' }) + + const emit = (lastLine, uuid) => { + const renderLine = { + id, + data: lastLine, + command: command, + commandId: uuid, + } + stream?.write?.(`${JSON.stringify(renderLine)}\n`) + + return renderLine + } + + /** + * This function is only executed if the command is completed. + * + * @param {boolean} success - check if command complete without errors + * @param {string} lastLine - last line command + */ + const close = (success, lastLine) => { + stream?.end?.() + } + + // execute Async Command + const executedCommand = executeWithEmit( + paramsCommand, + { close, out: emit, err: emit }, + { id, command } + ) + + // response Http + res.locals.httpCode = httpResponse( + executedCommand ? accepted : internalServerError, + id + ) next() } /** - * Configure host provision. + * Configure host provision (Async route). * * @param {object} res - http response * @param {Function} next - express stepper @@ -1101,79 +933,73 @@ const configureHost = ( ) => { const { user, password } = userData const { id } = params - const rtn = httpInternalError - if (Number.isInteger(parseInt(id, 10)) && user && password) { - const command = 'configure' - const endpoint = getEndpoint() - const authCommand = ['--user', user, '--password', password] - const paramsCommand = [ - 'host', - command, - `${id}`.toLowerCase(), - '--debug', - '--fail_cleanup', - '--batch', - ...authCommand, - ...endpoint, - ] - - // get Log file - const dataLog = logData(id, true) - - // create stream for write into file - const stream = - dataLog && - dataLog.fullPath && - createWriteStream(dataLog.fullPath, { flags: 'a' }) - - /** - * This function is performed for each command line response. - * - * @param {string} lastLine - last line command - * @param {string} uuid - uuid command - */ - const emit = (lastLine, uuid) => { - const renderLine = { - id, - data: lastLine, - command: `host ${command}`, - commandId: uuid, - } - stream && stream.write && stream.write(`${JSON.stringify(renderLine)}\n`) - } - - /** - * This function is only executed if the command is completed. - * - * @param {boolean} success - check if command complete without error - * @param {string} lastLine - last line command - */ - const close = (success, lastLine) => { - stream && stream.end && stream.end() - } - - // execute Async Command - const executedCommand = executeWithEmit( - paramsCommand, - { close, out: emit, err: emit }, - { id, command: `host ${command}` } - ) - - // response Http - res.locals.httpCode = httpResponse( - executedCommand ? accepted : internalServerError, - id - ) + if (!(Number.isInteger(parseInt(id, 10)) && user && password)) { + res.locals.httpCode = httpInternalError next() return } - res.locals.httpCode = rtn + + const command = 'configure' + const endpoint = getEndpoint() + const authCommand = ['--user', user, '--password', password] + const paramsCommand = [ + 'host', + command, + `${id}`.toLowerCase(), + '--debug', + '--fail_cleanup', + '--batch', + ...authCommand, + ...endpoint, + ] + + // get Log file + const dataLog = logData(id, true) + + // create stream for write into file + const stream = + dataLog?.fullPath && createWriteStream(dataLog.fullPath, { flags: 'a' }) + + const emit = (lastLine, uuid) => { + const renderLine = { + id, + data: lastLine, + command: `host ${command}`, + commandId: uuid, + } + stream && stream.write && stream.write(`${JSON.stringify(renderLine)}\n`) + + return renderLine + } + + /** + * This function is only executed if the command is completed. + * + * @param {boolean} success - check if command complete without error + * @param {string} lastLine - last line command + */ + const close = (success, lastLine) => { + stream?.end?.() + } + + // execute Async Command + const executedCommand = executeWithEmit( + paramsCommand, + { close, out: emit, err: emit }, + { id, command: `host ${command}` } + ) + + // response Http + res.locals.httpCode = httpResponse( + executedCommand ? accepted : internalServerError, + id + ) next() } /** - * Validate provision file. + * Validate provision file (Sync route). * * @param {object} res - http response * @param {Function} next - express stepper @@ -1189,61 +1015,67 @@ const validate = ( ) => { const { user, password } = userData const { resource } = params - let rtn = httpInternalError - if (resource && user && password) { - const endpoint = getEndpoint() - const authCommand = ['--user', user, '--password', password] - const schemaValidator = new Validator() - const parsedResource = parsePostData(resource) - const valSchema = schemaValidator.validate(parsedResource, provision) - if (valSchema.valid) { - const content = createYMLContent(parsedResource) - if (content) { - const file = createTemporalFile( - `${global.paths.CPI}/${defaultFolderTmpProvision}`, - 'yaml', - content - ) - if (file && file.name && file.path) { - const paramsCommand = [ - 'validate', - '--dump', - file.path, - ...authCommand, - ...endpoint, - ] - const executedCommand = executeCommand( - defaultCommandProvision, - paramsCommand, - getSpecificConfig('oneprovision_prepend_command') - ) - let response = internalServerError - if (executedCommand && executedCommand.success) { - response = ok - } - removeFile(file) - res.locals.httpCode = httpResponse(response) - next() + const rtn = httpInternalError + if (!(resource && user && password)) { + res.locals.httpCode = rtn + next() - return + return + } + + const endpoint = getEndpoint() + const authCommand = ['--user', user, '--password', password] + const schemaValidator = new Validator() + const parsedResource = parsePostData(resource) + const valSchema = schemaValidator.validate(parsedResource, provision) + if (valSchema?.valid) { + const content = createYMLContent(parsedResource) + if (content) { + const file = createTemporalFile( + `${global.paths.CPI}/${defaultFolderTmpProvision}`, + 'yaml', + content + ) + if (file?.name && file?.path) { + const paramsCommand = [ + 'validate', + '--dump', + file.path, + ...authCommand, + ...endpoint, + ] + const executedCommand = executeCommand( + defaultCommandProvision, + paramsCommand, + getSpecificConfig('oneprovision_prepend_command') + ) + let response = internalServerError + if (executedCommand && executedCommand.success) { + response = ok } - } - } else { - const errors = [] - if (valSchema && valSchema.errors) { - valSchema.errors.forEach((error) => { - errors.push(error.stack.replace(/^instance./, '')) - }) - rtn = httpResponse(internalServerError, '', errors.toString()) + removeFile(file) + res.locals.httpCode = httpResponse(response) + next() } } + } else { + const errors = [] + if (valSchema?.errors) { + valSchema.errors.forEach((error) => { + errors.push(error.stack.replace(/^instance./, '')) + }) + } + res.locals.httpCode = httpResponse( + internalServerError, + '', + errors.toString() + ) + next() } - res.locals.httpCode = rtn - next() } /** - * Get provision log. + * Get provision log (Sync route). * * @param {object} res - http response * @param {Function} next - express stepper @@ -1254,47 +1086,21 @@ const getLogProvisions = ( next = defaultEmptyFunction, params = {} ) => { - let rtn = httpInternalError - if (params && params.id) { - const foundLogs = logData(params.id) - if (foundLogs) { - rtn = httpResponse(ok, foundLogs) - } else { - rtn = notFound - } + if (!(params && params.id)) { + res.locals.httpCode = httpInternalError + next() + } + const foundLogs = logData(params.id) + if (foundLogs) { + res.locals.httpCode = httpResponse(ok, foundLogs) + } else { + res.locals.httpCode = notFound } - res.locals.httpCode = rtn next() } /** - * Execute Command sync and return http response. - * - * @param {any[]} params - params for command. - * @returns {object} httpResponse - */ -const addResourceSync = (params) => { - if (params && Array.isArray(params)) { - const executedCommand = executeCommand( - defaultCommandProvision, - params, - getSpecificConfig('oneprovision_prepend_command') - ) - try { - const response = executedCommand.success ? ok : internalServerError - - return httpResponse( - response, - executedCommand.data ? JSON.parse(executedCommand.data) : params.id - ) - } catch (error) { - return httpResponse(internalServerError, '', executedCommand.data) - } - } -} - -/** - * Add Host to provision. + * Add Host to provision (Async route). * * @param {object} res - http response * @param {Function} next - express stepper @@ -1311,80 +1117,86 @@ const hostAdd = ( const { user, password } = userData const { id, amount } = params if ( - Number.isInteger(parseInt(id, 10)) && - Number.isInteger(parseInt(amount, 10)) && - user && - password + !( + Number.isInteger(parseInt(id, 10)) && + Number.isInteger(parseInt(amount, 10)) && + user && + password + ) ) { - const endpoint = getEndpoint() - const authCommand = ['--user', user, '--password', password] - const paramsCommand = [ - 'host', - 'add', - id, - '--amount', - amount, - ...authCommand, - ...endpoint, - ] - - // get Log file - const dataLog = logData(id, true) - - // create stream for write into file - const stream = - dataLog && - dataLog.fullPath && - createWriteStream(dataLog.fullPath, { flags: 'a' }) - - /** - * This function is performed for each command line response. - * - * @param {string} lastLine - last line command - * @param {string} uuid - uuid command - */ - const emit = (lastLine, uuid) => { - const renderLine = { - id, - data: lastLine, - command: 'add host', - commandId: uuid, - } - stream && stream.write && stream.write(`${JSON.stringify(renderLine)}\n`) - } - - /** - * This function is only executed if the command is completed. - * - * @param {boolean} success - check if command complete without error - * @param {string} lastLine - last line command - */ - const close = (success, lastLine) => { - stream && stream.end && stream.end() - } - - // execute Async Command - const executedCommand = executeWithEmit( - paramsCommand, - { close, out: emit, err: emit }, - { id, command: 'add host' } - ) - - // response Http - res.locals.httpCode = httpResponse( - executedCommand ? accepted : internalServerError, - id - ) + res.locals.httpCode = httpInternalError next() return } - res.locals.httpCode = httpInternalError + + const endpoint = getEndpoint() + const authCommand = ['--user', user, '--password', password] + const paramsCommand = [ + 'host', + 'add', + id, + '--amount', + amount, + ...authCommand, + ...endpoint, + ] + + // get Log file + const dataLog = logData(id, true) + + // create stream for write into file + const stream = + dataLog?.fullPath && createWriteStream(dataLog.fullPath, { flags: 'a' }) + + /** + * This function is performed for each command line response. + * + * @param {string} lastLine - last line command + * @param {string} uuid - uuid command + */ + let flagSeparateLog = true + const emit = (lastLine, uuid) => { + const renderLine = { + id, + data: lastLine, + command: 'add host', + commandId: uuid, + } + if (flagSeparateLog) { + renderLine.data = executingMessage(`${messageExecuting} host add`) + stream?.write?.(`${JSON.stringify(renderLine)}\n`) + flagSeparateLog = false + renderLine.data = lastLine + } + stream?.write?.(`${JSON.stringify(renderLine)}\n`) + + return renderLine + } + + // execute Async Command + const executedCommand = executeWithEmit( + paramsCommand, + { + close: (success, lastLine) => { + stream && stream.end && stream.end() + }, + out: emit, + err: emit, + }, + { id, command: 'add host' } + ) + + // response Http + res.locals.httpCode = httpResponse( + executedCommand ? accepted : internalServerError, + id + ) next() } /** - * Add Ips to provision. + * Add Ips to provision (Sync route). * * @param {object} res - http response * @param {Function} next - express stepper @@ -1398,34 +1210,38 @@ const ipAdd = ( params = {}, userData = {} ) => { - let rtn = httpInternalError const { id, amount } = params const { user, password } = userData if ( - Number.isInteger(parseInt(id, 10)) && - Number.isInteger(parseInt(amount, 10)) && - user && - password + !( + Number.isInteger(parseInt(id, 10)) && + Number.isInteger(parseInt(amount, 10)) && + user && + password + ) ) { - const authCommand = ['--user', user, '--password', password] - const endpoint = getEndpoint() + res.locals.httpCode = httpInternalError + next() - rtn = - addResourceSync([ - 'ip', - 'add', - id, - '--amount', - amount, - ...authCommand, - ...endpoint, - ]) || httpInternalError + return } - res.locals.httpCode = rtn + const authCommand = ['--user', user, '--password', password] + const endpoint = getEndpoint() + + res.locals.httpCode = + addResourceSync([ + 'ip', + 'add', + id, + '--amount', + amount, + ...authCommand, + ...endpoint, + ]) || httpInternalError next() } -const provisionFunctionsApi = { +module.exports = { getProvisionDefaults, getLogProvisions, getListResourceProvision, @@ -1440,4 +1256,3 @@ const provisionFunctionsApi = { hostAdd, ipAdd, } -module.exports = provisionFunctionsApi diff --git a/src/fireedge/src/server/routes/api/oneprovision/provision/helpers.js b/src/fireedge/src/server/routes/api/oneprovision/provision/helpers.js new file mode 100644 index 0000000000..c954a15a1e --- /dev/null +++ b/src/fireedge/src/server/routes/api/oneprovision/provision/helpers.js @@ -0,0 +1,295 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2022, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +const btoa = require('btoa') +const { parse } = require('yaml') +const { v4 } = require('uuid') +const { DateTime } = require('luxon') +const { publish } = require('server/utils/server') +const { + httpResponse, + existsFile, + rotateBySize, + executeCommand, + executeCommandAsync, +} = require('server/utils/server') +const { defaults, httpCodes } = require('server/utils/constants') +const { + findRecursiveFolder, + getSpecificConfig, +} = require('server/routes/api/oneprovision/utils') + +const { + defaultCommandProvision, + defaultEmptyFunction, + defaultRegexpStartJSON, + defaultRegexpEndJSON, + defaultRegexpSplitLine, + defaultSizeRotate, +} = defaults +const { ok, internalServerError } = httpCodes +const relName = 'provision-mapping' +const ext = 'yml' +const logFile = { + name: 'stdouterr', + ext: 'log', +} +const appendError = '.ERROR' + +/** + * Execute command Async and emit in WS. + * + * @param {string} command - command to execute + * @param {object} actions - external functions when command emit in stderr, stdout and finalize + * @param {Function} actions.err - emit when have stderr + * @param {Function} actions.out - emit when have stdout + * @param {Function} actions.close - emit when finalize + * @param {object} dataForLog - data + * @param {number} dataForLog.id - data id + * @param {string} dataForLog.command - data command + * @returns {boolean} check if emmit data + */ +const executeWithEmit = (command = [], actions = {}, dataForLog = {}) => { + if ( + !( + command && + Array.isArray(command) && + command.length > 0 && + actions && + dataForLog + ) + ) { + return + } + + const { err: externalErr, out: externalOut, close: externalClose } = actions + const err = + externalErr && typeof externalErr === 'function' + ? externalErr + : defaultEmptyFunction + const out = + externalOut && typeof externalOut === 'function' + ? externalOut + : defaultEmptyFunction + const close = + externalClose && typeof externalClose === 'function' + ? actions.close + : defaultEmptyFunction + + // data for log + const id = (dataForLog && dataForLog.id) || '' + const commandName = (dataForLog && dataForLog.command) || '' + + let lastLine = '' + const uuid = v4() + + let pendingMessages = '' + + /** + * Emit data of command. + * + * @param {string} message - line of command CLI + * @param {Function} callback - function when recieve a information + */ + const emit = (message, callback = defaultEmptyFunction) => { + /** + * Publisher data to WS. + * + * @param {string} line - command CLI line + */ + const publisher = (line = '') => { + const resposeData = callback(line, uuid) || { + id, + data: line, + command: commandName, + commandId: uuid, + } + publish(defaultCommandProvision, resposeData) + } + + message + .toString() + .split(defaultRegexpSplitLine) + .forEach((line) => { + if (line) { + if ( + (defaultRegexpStartJSON.test(line) && + defaultRegexpEndJSON.test(line)) || + (!defaultRegexpStartJSON.test(line) && + !defaultRegexpEndJSON.test(line) && + pendingMessages.length === 0) + ) { + lastLine = line + publisher(lastLine) + } else if ( + (defaultRegexpStartJSON.test(line) && + !defaultRegexpEndJSON.test(line)) || + (!defaultRegexpStartJSON.test(line) && + !defaultRegexpEndJSON.test(line) && + pendingMessages.length > 0) + ) { + pendingMessages += line + } else { + lastLine = pendingMessages + line + publisher(lastLine) + pendingMessages = '' + } + } + }) + } + + executeCommandAsync( + defaultCommandProvision, + command, + getSpecificConfig('oneprovision_prepend_command'), + { + err: (message) => { + emit(message, err) + }, + out: (message) => { + emit(message, out) + }, + close: (success) => { + close(success, lastLine) + }, + } + ) + + return true +} + +/** + * Find log data. + * + * @param {string} id - id of provision + * @param {boolean} fullPath - if need return the path of log + * @returns {object} data of log + */ +const logData = (id, fullPath = false) => { + let rtn = false + if (!Number.isInteger(parseInt(id, 10))) { + return rtn + } + const basePath = `${global.paths.CPI}/provision` + const relFile = `${basePath}/${relName}` + const relFileYML = `${relFile}.${ext}` + const find = findRecursiveFolder(basePath, id) + + /** + * Found log. + * + * @param {string} path - path of log + * @param {string} uuid - uuid of log + */ + const rtnFound = (path = '', uuid) => { + if (!path) { + return + } + + const stringPath = `${path}/${logFile.name}.${logFile.ext}` + existsFile( + stringPath, + (filedata) => { + rotateBySize(stringPath, defaultSizeRotate) + rtn = { uuid, log: filedata.split(defaultRegexpSplitLine) } + if (fullPath) { + rtn.fullPath = stringPath + } + }, + defaultEmptyFunction + ) + } + + if (find) { + rtnFound(find) + } else { + // Temporal provision + existsFile( + relFileYML, + (filedata) => { + const fileData = parse(filedata) || {} + if (!fileData[id]) { + return + } + + const findPending = findRecursiveFolder(basePath, fileData[id]) + if (findPending) { + rtnFound(findPending, fileData[id]) + } else { + const findError = findRecursiveFolder( + basePath, + fileData[id] + appendError + ) + findError && rtnFound(findError, fileData[id]) + } + }, + defaultEmptyFunction + ) + } + + return rtn +} + +/** + * Execute Command sync and return http response. + * + * @param {any[]} params - params for command. + * @returns {object} httpResponse + */ +const addResourceSync = (params) => { + if (!(params && Array.isArray(params))) { + return + } + + const executedCommand = executeCommand( + defaultCommandProvision, + params, + getSpecificConfig('oneprovision_prepend_command') + ) + try { + const response = executedCommand.success ? ok : internalServerError + + return httpResponse( + response, + executedCommand.data ? JSON.parse(executedCommand.data) : params.id + ) + } catch (error) { + return httpResponse(internalServerError, '', executedCommand.data) + } +} + +/** + * Executing line for provision logs. + * + * @param {string} message - message to log + * @returns {string} line message stringify + */ +const executingMessage = (message = '') => + JSON.stringify({ + timestamp: DateTime.now().toFormat('yyyy-MM-dd HH:mm:ss ZZZ'), + severity: 'DEBUG', + message: btoa(message), + }) + +module.exports = { + executeWithEmit, + logData, + addResourceSync, + executingMessage, + relName, + ext, + logFile, + appendError, +} diff --git a/src/fireedge/src/server/routes/api/oneprovision/provision/routes.js b/src/fireedge/src/server/routes/api/oneprovision/provision/routes.js index ea6df607d3..8da6e1b99d 100644 --- a/src/fireedge/src/server/routes/api/oneprovision/provision/routes.js +++ b/src/fireedge/src/server/routes/api/oneprovision/provision/routes.js @@ -140,7 +140,7 @@ module.exports = { }, }, [PROVISION_DELETE_RESOURCE]: { - path: `${basepath}/resource/:resource/:id`, + path: `${basepath}/resource/:resource/:id/:provision`, httpMethod: DELETE, auth: true, params: { @@ -150,6 +150,9 @@ module.exports = { id: { from: resource, }, + provision: { + from: resource, + }, }, }, [PROVISION_HOST_ACTION]: { diff --git a/src/fireedge/src/server/utils/constants/defaults.js b/src/fireedge/src/server/utils/constants/defaults.js index 4ecab3c53e..f8f7b08786 100644 --- a/src/fireedge/src/server/utils/constants/defaults.js +++ b/src/fireedge/src/server/utils/constants/defaults.js @@ -57,6 +57,7 @@ const defaults = { defaultRegexID: /^ID: (?\d+)/, defaultRegexpEndJSON: /}$/, defaultRegexpSplitLine: /\r|\n/, + defaultSizeRotate: '100k', defaultAppName: appName, defaultConfigErrorMessage: { color: 'red', diff --git a/src/fireedge/src/server/utils/server.js b/src/fireedge/src/server/utils/server.js index 9be39e35fa..14079e15af 100644 --- a/src/fireedge/src/server/utils/server.js +++ b/src/fireedge/src/server/utils/server.js @@ -36,14 +36,17 @@ const { readdirSync, statSync, removeSync, + moveSync, + ensureFileSync, } = require('fs-extra') const { spawnSync, spawn } = require('child_process') const events = require('events') +const { DateTime } = require('luxon') +const { request: axios } = require('axios') const { defaults, httpCodes } = require('server/utils/constants') const { messageTerminal } = require('server/utils/general') const { validateAuth } = require('server/utils/jwt') const { writeInLogger } = require('server/utils/logger') -const { request: axios } = require('axios') const eventsEmitter = new events.EventEmitter() const { @@ -304,6 +307,48 @@ const decrypt = (data = '', decryptKey = '', iv = '') => { return rtn } +const getSize = (limit) => { + const size = limit?.toLowerCase?.()?.match(/^((?:0\.)?\d+)([kmg])$/) + const limitNumber = parseInt(limit, 10) + if (size) { + switch (size[2]) { + case 'k': + return size[1] * 1024 + case 'm': + return size[1] * 1024 ** 2 + case 'g': + return size[1] * 1024 ** 3 + } + } else if (Number.isInteger(limitNumber)) { + return limitNumber + } +} + +/** + * Rotate file by size. + * + * + * @param {string} filepath - file path + * @param {number} limit - size to rotate + */ +const rotateBySize = (filepath = '', limit) => { + try { + const fileStats = statSync(filepath) + if (fileStats.size >= getSize(limit)) { + moveSync(filepath, `${filepath}.${DateTime.now().toSeconds()}`) + ensureFileSync(filepath) + } + } catch (error) { + const errorData = (error && error.message) || '' + writeInLogger(errorData) + messageTerminal({ + color: 'red', + message: 'Error: %s', + error: errorData, + }) + } +} + /** * Check if file exist. * @@ -1005,4 +1050,5 @@ module.exports = { publish, subscriber, executeRequest, + rotateBySize, }