UNPKG

@itentialopensource/adapter-avi_controller

Version:

This adapter integrates with system described as: AviController Networks

1,352 lines (1,217 loc) 53.2 kB
/* @copyright Itential, LLC 2018-9 */ // Set globals /* global log */ /* eslint class-methods-use-this:warn */ /* eslint import/no-dynamic-require: warn */ /* eslint no-loop-func: warn */ /* eslint no-cond-assign: warn */ /* eslint global-require: warn */ /* eslint no-unused-vars: warn */ /* eslint prefer-destructuring: warn */ /* Required libraries. */ const path = require('path'); const { execSync } = require('child_process'); const { spawnSync } = require('child_process'); const EventEmitterCl = require('events').EventEmitter; const fs = require('fs-extra'); const jsonQuery = require('json-query'); const sampleProperties = require(`${__dirname}/sampleProperties.json`).properties; /* The schema validator */ const AjvCl = require('ajv'); const { Test } = require('mocha'); /* Fetch in the other needed components for the this Adaptor */ const PropUtilCl = require('@itentialopensource/adapter-utils').PropertyUtility; const RequestHandlerCl = require('@itentialopensource/adapter-utils').RequestHandler; const entitiesToDB = require(path.join(__dirname, 'utils/entitiesToDB')); const troubleshootingAdapter = require(path.join(__dirname, 'utils/troubleshootingAdapter')); const tbUtils = require(path.join(__dirname, 'utils/tbUtils')); const taskMover = require(path.join(__dirname, 'utils/taskMover')); let propUtil = null; let choosepath = null; /* * INTERNAL FUNCTION: force fail the adapter - generally done to cause restart */ function forceFail(packChg) { if (packChg !== undefined && packChg !== null && packChg === true) { execSync(`rm -rf ${__dirname}/node modules`, { encoding: 'utf-8' }); execSync(`rm -rf ${__dirname}/package-lock.json`, { encoding: 'utf-8' }); execSync('npm install', { encoding: 'utf-8' }); } log.error('NEED TO RESTART ADAPTER - FORCE FAIL'); const errorObj = { origin: 'adapter-forceFail', type: 'Force Fail so adapter will restart', vars: [] }; setTimeout(() => { throw new Error(JSON.stringify(errorObj)); }, 1000); } /* * INTERNAL FUNCTION: update the action.json */ function updateAction(entityPath, action, changes) { // if the action file does not exist - error const actionFile = path.join(entityPath, '/action.json'); if (!fs.existsSync(actionFile)) { return 'Missing Action File'; } // read in the file as a json object const ajson = require(path.resolve(entityPath, 'action.json')); let chgAct = {}; // get the action we need to change for (let a = 0; a < ajson.actions.length; a += 1) { if (ajson.actions[a].name === action) { chgAct = ajson.actions[a]; break; } } // merge the changes into the desired action chgAct = propUtil.mergeProperties(changes, chgAct); fs.writeFileSync(actionFile, JSON.stringify(ajson, null, 2)); return null; } /* * INTERNAL FUNCTION: update the schema file */ function updateSchema(entityPath, configFile, changes) { // if the schema file does not exist - error const schemaFile = path.join(entityPath, `/${configFile}`); if (!fs.existsSync(schemaFile)) { return 'Missing Schema File'; } // read in the file as a json object let schema = require(path.resolve(entityPath, configFile)); // merge the changes into the schema file schema = propUtil.mergeProperties(changes, schema); fs.writeFileSync(schemaFile, JSON.stringify(schema, null, 2)); return null; } /* * INTERNAL FUNCTION: update the mock data file */ function updateMock(mockPath, configFile, changes, replace) { // if the mock file does not exist - create it const mockFile = path.join(mockPath, `/${configFile}`); if (!fs.existsSync(mockFile)) { const newMock = {}; fs.writeFileSync(mockFile, JSON.stringify(newMock, null, 2)); } // read in the file as a json object let mock = require(path.resolve(mockPath, configFile)); // merge the changes into the mock file if (replace === true) { mock = changes; } else { mock = propUtil.mergeProperties(changes, mock); } fs.writeFileSync(mockFile, JSON.stringify(mock, null, 2)); return null; } /* * INTERNAL FUNCTION: update the package dependencies */ function updatePackage(changes) { // if the schema file does not exist - error const packFile = path.join(__dirname, '/package.json'); if (!fs.existsSync(packFile)) { return 'Missing Pacakge File'; } // read in the file as a json object const pack = require(path.resolve(__dirname, 'package.json')); // only certain changes are allowed if (changes.dependencies) { const keys = Object.keys(changes.dependencies); for (let k = 0; k < keys.length; k += 1) { pack.dependencies[keys[k]] = changes.dependencies[keys[k]]; } } fs.writeFileSync(packFile, JSON.stringify(pack, null, 2)); return null; } /* GENERAL ADAPTER FUNCTIONS THESE SHOULD NOT BE DIRECTLY MODIFIED */ /* IF YOU NEED MODIFICATIONS, REDEFINE THEM IN adapter.js!!! */ class AdapterBase extends EventEmitterCl { /** * [System] Adapter * @constructor */ constructor(prongid, properties) { // Instantiate the EventEmitter super class super(); // IAP home directory injected by core when running the adapter within IAP [, , , process.env.iap_home] = process.argv; try { // Capture the adapter id this.id = prongid; this.propUtilInst = new PropUtilCl(prongid, __dirname); propUtil = this.propUtilInst; this.initProps = properties; this.alive = false; this.healthy = false; this.suspended = false; this.suspendMode = 'pause'; this.caching = false; this.repeatCacheCount = 0; this.allowFailover = 'AD.300'; this.noFailover = 'AD.500'; // set up the properties I care about this.refreshProperties(properties); // Instantiate the other components for this Adapter this.requestHandlerInst = new RequestHandlerCl(this.id, this.allProps, __dirname); } catch (e) { // handle any exception const origin = `${this.id}-adapterBase-constructor`; log.error(`${origin}: Adapter may not have started properly. ${e}`); } } /** * @callback healthCallback * @param {Object} result - the result of the get request (contains an id and a status) */ /** * @callback getCallback * @param {Object} result - the result of the get request (entity/ies) * @param {String} error - any error that occured */ /** * @callback createCallback * @param {Object} item - the newly created entity * @param {String} error - any error that occured */ /** * @callback updateCallback * @param {String} status - the status of the update action * @param {String} error - any error that occured */ /** * @callback deleteCallback * @param {String} status - the status of the delete action * @param {String} error - any error that occured */ /** * refreshProperties is used to set up all of the properties for the connector. * It allows properties to be changed later by simply calling refreshProperties rather * than having to restart the connector. * * @function refreshProperties * @param {Object} properties - an object containing all of the properties * @param {boolean} init - are we initializing -- is so no need to refresh throtte engine */ refreshProperties(properties) { const meth = 'adapterBase-refreshProperties'; const origin = `${this.id}-${meth}`; log.trace(origin); try { // Read the properties schema from the file system const propertiesSchema = JSON.parse(fs.readFileSync(path.join(__dirname, 'propertiesSchema.json'), 'utf-8')); // add any defaults to the data const defProps = this.propUtilInst.setDefaults(propertiesSchema); this.allProps = this.propUtilInst.mergeProperties(properties, defProps); // validate the entity against the schema const ajvInst = new AjvCl({ strictSchema: false, allowUnionTypes: true }); const validate = ajvInst.compile(propertiesSchema); const result = validate(this.allProps); // if invalid properties throw an error if (!result) { if (this.requestHandlerInst) { const errorObj = this.requestHandlerInst.formatErrorObject(this.id, meth, 'Invalid Properties', [JSON.stringify(validate.errors)], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); throw new Error(JSON.stringify(errorObj)); } else { log.error(`${origin}: ${JSON.stringify(validate.errors)}`); throw new Error(`${origin}: ${JSON.stringify(validate.errors)}`); } } // properties that this code cares about this.healthcheckType = this.allProps.healthcheck.type; this.healthcheckInterval = this.allProps.healthcheck.frequency; this.healthcheckQuery = this.allProps.healthcheck.query_object; // set the failover codes from properties if (this.allProps.request.failover_codes) { if (Array.isArray(this.allProps.request.failover_codes)) { this.failoverCodes = this.allProps.request.failover_codes; } else { this.failoverCodes = [this.allProps.request.failover_codes]; } } else { this.failoverCodes = []; } // set the caching flag from properties if (this.allProps.cache_location) { if (this.allProps.cache_location === 'redis' || this.allProps.cache_location === 'local') { this.caching = true; } } // if this is truly a refresh and we have a request handler, refresh it if (this.requestHandlerInst) { this.requestHandlerInst.refreshProperties(properties); } } catch (e) { log.error(`${origin}: Properties may not have been set properly. ${e}`); } } /** * @summary Connect function is used during Pronghorn startup to provide instantiation feedback. * * @function connect */ connect() { const origin = `${this.id}-adapterBase-connect`; log.trace(origin); // initially set as off this.emit('OFFLINE', { id: this.id }); this.alive = true; // if there is no healthcheck just change the emit to ONLINE // We do not recommend no healthcheck!!! if (this.healthcheckType === 'none') { log.error(`${origin}: Waiting 1 Seconds to emit Online`); setTimeout(() => { this.emit('ONLINE', { id: this.id }); this.healthy = true; }, 1000); } // is the healthcheck only suppose to run on startup // (intermittent runs on startup and after that) if (this.healthcheckType === 'startup' || this.healthcheckType === 'intermittent') { // run an initial healthcheck this.healthCheck(null, (status) => { log.spam(`${origin}: ${status}`); }); } // is the healthcheck suppose to run intermittently if (this.healthcheckType === 'intermittent') { // run the healthcheck in an interval setInterval(() => { // try to see if mongo is available this.healthCheck(null, (status) => { log.spam(`${origin}: ${status}`); }); }, this.healthcheckInterval); } } /** * @summary HealthCheck function is used to provide Pronghorn the status of this adapter. * * @function healthCheck */ healthCheck(reqObj, callback) { const origin = `${this.id}-adapterBase-healthCheck`; log.trace(origin); // if there is healthcheck query_object property, it needs to be added to the adapter let myRequest = reqObj; if (this.healthcheckQuery && Object.keys(this.healthcheckQuery).length > 0) { if (myRequest && myRequest.uriQuery) { myRequest.uriQuery = { ...myRequest.uriQuery, ...this.healthcheckQuery }; } else if (myRequest) { myRequest.uriQuery = this.healthcheckQuery; } else { myRequest = { uriQuery: this.healthcheckQuery }; } } // call to the healthcheck in connector return this.requestHandlerInst.identifyHealthcheck(myRequest, (res, error) => { // unhealthy if (error) { // if we were healthy, toggle health if (this.healthy) { this.emit('OFFLINE', { id: this.id }); this.emit('DEGRADED', { id: this.id }); this.healthy = false; if (typeof error === 'object') { log.error(`${origin}: HEALTH CHECK - Error ${JSON.stringify(error)}`); } else { log.error(`${origin}: HEALTH CHECK - Error ${error}`); } } else if (typeof error === 'object') { // still log but set the level to trace log.trace(`${origin}: HEALTH CHECK - Still Errors ${JSON.stringify(error)}`); } else { log.trace(`${origin}: HEALTH CHECK - Still Errors ${error}`); } return callback(false); } // if we were unhealthy, toggle health if (!this.healthy) { this.emit('FIXED', { id: this.id }); this.emit('ONLINE', { id: this.id }); this.healthy = true; log.info(`${origin}: HEALTH CHECK SUCCESSFUL`); } else { // still log but set the level to trace log.trace(`${origin}: HEALTH CHECK STILL SUCCESSFUL`); } return callback(true); }); } /** * getAllFunctions is used to get all of the exposed function in the adapter * * @function getAllFunctions */ getAllFunctions() { let myfunctions = []; let obj = this; // find the functions in this class do { const l = Object.getOwnPropertyNames(obj) .concat(Object.getOwnPropertySymbols(obj).map((s) => s.toString())) .sort() .filter((p, i, arr) => typeof obj[p] === 'function' && p !== 'constructor' && (i === 0 || p !== arr[i - 1]) && myfunctions.indexOf(p) === -1); myfunctions = myfunctions.concat(l); } while ( (obj = Object.getPrototypeOf(obj)) && Object.getPrototypeOf(obj) ); return myfunctions; } /** * iapGetAdapterWorkflowFunctions is used to get all of the workflow function in the adapter * @param {array} ignoreThese - additional methods to ignore (optional) * * @function iapGetAdapterWorkflowFunctions */ iapGetAdapterWorkflowFunctions(ignoreThese) { const myfunctions = this.getAllFunctions(); const wffunctions = []; // remove the functions that should not be in a Workflow for (let m = 0; m < myfunctions.length; m += 1) { if (myfunctions[m] === 'checkActionFiles') { // got to the second tier (adapterBase) break; } if (!(myfunctions[m].endsWith('Emit') || myfunctions[m].match(/Emit__v[0-9]+/))) { let found = false; if (ignoreThese && Array.isArray(ignoreThese)) { for (let i = 0; i < ignoreThese.length; i += 1) { if (myfunctions[m].toUpperCase() === ignoreThese[i].toUpperCase()) { found = true; } } } if (!found) { wffunctions.push(myfunctions[m]); } } } return wffunctions; } /** * checkActionFiles is used to update the validation of the action files. * * @function checkActionFiles */ checkActionFiles() { const origin = `${this.id}-adapterBase-checkActionFiles`; log.trace(origin); // validate the action files for the adapter try { return this.requestHandlerInst.checkActionFiles(); } catch (e) { return ['Exception increase log level']; } } /** * checkProperties is used to validate the adapter properties. * * @function checkProperties * @param {Object} properties - an object containing all of the properties */ checkProperties(properties) { const origin = `${this.myid}-adapterBase-checkProperties`; log.trace(origin); // validate the properties for the adapter try { return this.requestHandlerInst.checkProperties(properties); } catch (e) { return { exception: 'Exception increase log level' }; } } /** * @summary Takes in property text and an encoding/encryption and returns the resulting * encoded/encrypted string * * @function encryptProperty * @param {String} property - the property to encrypt * @param {String} technique - the technique to use to encrypt * * @param {Callback} callback - a callback function to return the result * Encrypted String or the Error */ encryptProperty(property, technique, callback) { const origin = `${this.id}-adapterBase-encryptProperty`; log.trace(origin); // Make the call - // encryptProperty(property, technique, callback) return this.requestHandlerInst.encryptProperty(property, technique, callback); } /** * iapUpdateAdapterConfiguration is used to update any of the adapter configuration files. This * allows customers to make changes to adapter configuration without having to be on the * file system. * * @function iapUpdateAdapterConfiguration * @param {string} configFile - the name of the file being updated (required) * @param {Object} changes - an object containing all of the changes = formatted like the configuration file (required) * @param {string} entity - the entity to be changed, if an action, schema or mock data file (optional) * @param {string} type - the type of entity file to change, (action, schema, mock) (optional) * @param {string} action - the action to be changed, if an action, schema or mock data file (optional) * @param {boolean} replace - true to replace entire mock data, false to merge/append (optional) * @param {Callback} callback - The results of the call */ iapUpdateAdapterConfiguration(configFile, changes, entity, type, action, replace, callback) { const meth = 'adapterBase-iapUpdateAdapterConfiguration'; const origin = `${this.id}-${meth}`; log.trace(origin); // verify the parameters are valid if (changes === undefined || changes === null || typeof changes !== 'object' || Object.keys(changes).length === 0) { const result = { response: 'No configuration updates to make' }; log.info(result.response); return callback(result, null); } if (configFile === undefined || configFile === null || configFile === '') { const errorObj = this.requestHandlerInst.formatErrorObject(this.id, meth, 'Missing Data', ['configFile'], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } // take action based on configFile being changed if (configFile === 'package.json') { const pres = updatePackage(changes); if (pres) { const errorObj = this.requestHandlerInst.formatErrorObject(this.id, meth, `Incomplete Configuration Change: ${pres}`, [], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } const result = { response: 'Package updates completed - restarting adapter' }; log.info(result.response); forceFail(true); return callback(result, null); } if (entity === undefined || entity === null || entity === '') { const errorObj = this.requestHandlerInst.formatErrorObject(this.id, meth, 'Unsupported Configuration Change or Missing Entity', [], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } // this means we are changing an entity file so type is required if (type === undefined || type === null || type === '') { const errorObj = this.requestHandlerInst.formatErrorObject(this.id, meth, 'Missing Data', ['type'], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } // if the entity does not exist - error const epath = `${__dirname}/entities/${entity}`; if (!fs.existsSync(epath)) { const errorObj = this.requestHandlerInst.formatErrorObject(this.id, meth, `Incomplete Configuration Change: Invalid Entity - ${entity}`, [], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } // take action based on type of file being changed if (type === 'action') { // BACKUP??? const ares = updateAction(epath, action, changes); if (ares) { const errorObj = this.requestHandlerInst.formatErrorObject(this.id, meth, `Incomplete Configuration Change: ${ares}`, [], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } // AJV CHECK??? // RESTORE IF NEEDED??? const result = { response: `Action updates completed to entity: ${entity} - ${action}` }; log.info(result.response); return callback(result, null); } if (type === 'schema') { const sres = updateSchema(epath, configFile, changes); if (sres) { const errorObj = this.requestHandlerInst.formatErrorObject(this.id, meth, `Incomplete Configuration Change: ${sres}`, [], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } const result = { response: `Schema updates completed to entity: ${entity} - ${configFile}` }; log.info(result.response); return callback(result, null); } if (type === 'mock') { // if the mock directory does not exist - error const mpath = `${__dirname}/entities/${entity}/mockdatafiles`; if (!fs.existsSync(mpath)) { fs.mkdirSync(mpath); } // this means we are changing a mock data file so replace is required if (replace === undefined || replace === null || replace === '') { const errorObj = this.requestHandlerInst.formatErrorObject(this.id, meth, 'Missing Data', ['replace'], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } const mres = updateMock(mpath, configFile, changes, replace); if (mres) { const errorObj = this.requestHandlerInst.formatErrorObject(this.id, meth, `Incomplete Configuration Change: ${mres}`, [], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } const result = { response: `Mock data updates completed to entity: ${entity} - ${configFile}` }; log.info(result.response); return callback(result, null); } const errorObj = this.requestHandlerInst.formatErrorObject(this.id, meth, `Incomplete Configuration Change: Unsupported Type - ${type}`, [], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } /** * @summary Suspends the adapter * @param {Callback} callback - The adapater suspension status * @function iapSuspendAdapter */ iapSuspendAdapter(mode, callback) { const origin = `${this.id}-adapterBase-iapSuspendAdapter`; if (this.suspended) { throw new Error(`${origin}: Adapter is already suspended`); } try { this.suspended = true; this.suspendMode = mode; if (this.suspendMode === 'pause') { const props = JSON.parse(JSON.stringify(this.initProps)); // To suspend adapter, enable throttling and set concurrent max to 0 props.throttle.throttle_enabled = true; props.throttle.concurrent_max = 0; this.refreshProperties(props); } return callback({ suspended: true }); } catch (error) { return callback(null, error); } } /** * @summary Unsuspends the adapter * @param {Callback} callback - The adapater suspension status * * @function iapUnsuspendAdapter */ iapUnsuspendAdapter(callback) { const origin = `${this.id}-adapterBase-iapUnsuspendAdapter`; if (!this.suspended) { throw new Error(`${origin}: Adapter is not suspended`); } if (this.suspendMode === 'pause') { const props = JSON.parse(JSON.stringify(this.initProps)); // To unsuspend adapter, keep throttling enabled and begin processing queued requests in order props.throttle.throttle_enabled = true; props.throttle.concurrent_max = 1; this.refreshProperties(props); setTimeout(() => { this.getQueue((q, error) => { // console.log("Items in queue: " + String(q.length)) if (q.length === 0) { // if queue is empty, return to initial properties state this.refreshProperties(this.initProps); this.suspended = false; return callback({ suspended: false }); } // recursive call to check queue again every second return this.iapUnsuspendAdapter(callback); }); }, 1000); } else { this.suspended = false; callback({ suspend: false }); } } /** * iapGetAdapterQueue is used to get information for all of the requests currently in the queue. * * @function iapGetAdapterQueue * @param {Callback} callback - a callback function to return the result (Queue) or the error */ iapGetAdapterQueue(callback) { const origin = `${this.id}-adapterBase-iapGetAdapterQueue`; log.trace(origin); return this.requestHandlerInst.getQueue(callback); } /* ********************************************** */ /* */ /* EXPOSES ADAPTER SCRIPTS */ /* */ /* ********************************************** */ /** * See if the API path provided is found in this adapter * * @function iapFindAdapterPath * @param {string} apiPath - the api path to check on * @param {Callback} callback - The results of the call */ iapFindAdapterPath(apiPath, callback) { const result = { apiPath }; // verify the path was provided if (!apiPath) { log.error('NO API PATH PROVIDED!'); result.found = false; result.message = 'NO PATH PROVIDED!'; return callback(null, result); } if (typeof this.allProps.choosepath === 'string') { choosepath = this.allProps.choosepath; } // make sure the entities directory exists const entitydir = path.join(__dirname, 'entities'); if (!fs.statSync(entitydir).isDirectory()) { log.error('Could not find the entities directory'); result.found = false; result.message = 'Could not find the entities directory'; return callback(null, result); } const entities = fs.readdirSync(entitydir); const fitems = []; // need to go through each entity in the entities directory for (let e = 0; e < entities.length; e += 1) { // make sure the entity is a directory - do not care about extra files // only entities (dir) if (fs.statSync(`${entitydir}/${entities[e]}`).isDirectory()) { // see if the action file exists in the entity if (fs.existsSync(`${entitydir}/${entities[e]}/action.json`)) { // Read the entity actions from the file system const actions = require(`${entitydir}/${entities[e]}/action.json`); // go through all of the actions set the appropriate info in the newActions for (let a = 0; a < actions.actions.length; a += 1) { if (actions.actions[a].entitypath && typeof actions.actions[a].entitypath === 'object') { const entityKeys = Object.keys(actions.actions[a].entitypath); if (entityKeys.length > 0) { for (let entityKey = 0; entityKey < entityKeys.length; entityKey += 1) { if (choosepath && entityKeys[entityKey] === choosepath && actions.actions[a].entitypath[entityKeys[entityKey]].indexOf(apiPath) >= 0) { log.info(` Found - entity: ${entities[e]} action: ${actions.actions[a].name}`); log.info(` method: ${actions.actions[a].method} path: ${actions.actions[a].entitypath[entityKeys[entityKey]]}`); const fitem = { entity: entities[e], action: actions.actions[a].name, method: actions.actions[a].method, path: actions.actions[a].entitypath[entityKeys[entityKey]] }; fitems.push(fitem); break; } } } } else if (actions.actions[a].entitypath.indexOf(apiPath) >= 0) { log.info(` Found - entity: ${entities[e]} action: ${actions.actions[a].name}`); log.info(` method: ${actions.actions[a].method} path: ${actions.actions[a].entitypath}`); const fitem = { entity: entities[e], action: actions.actions[a].name, method: actions.actions[a].method, path: actions.actions[a].entitypath }; fitems.push(fitem); } } } else { log.error(`Could not find entities ${entities[e]} action.json file`); result.found = false; result.message = `Could not find entities ${entities[e]} action.json file`; return callback(null, result); } } else { log.error(`Could not find entities ${entities[e]} directory`); result.found = false; result.message = `Could not find entities ${entities[e]} directory`; return callback(null, result); } } if (fitems.length === 0) { log.info('PATH NOT FOUND!'); result.found = false; result.message = 'API PATH NOT FOUND!'; return callback(null, result); } result.foundIn = fitems; result.found = true; result.message = 'API PATH FOUND!'; return callback(result, null); } /** * @summary runs troubleshoot scripts for adapter * * @function iapTroubleshootAdapter * @param {Object} props - the connection, healthcheck and authentication properties * @param {boolean} persistFlag - whether the adapter properties should be updated * @param {Adapter} adapter - adapter instance to troubleshoot * @param {Callback} callback - callback function to return troubleshoot results */ async iapTroubleshootAdapter(props, persistFlag, adapter, callback) { try { const result = await troubleshootingAdapter.troubleshoot(props, false, persistFlag, adapter); if (result.healthCheck && result.connectivity.failCount === 0 && result.basicGet.failCount === 0) { return callback(result); } return callback(null, result); } catch (error) { return callback(null, error); } } /** * @summary runs healthcheck script for adapter * * @function iapRunAdapterHealthcheck * @param {Adapter} adapter - adapter instance to troubleshoot * @param {Callback} callback - callback function to return healthcheck status */ async iapRunAdapterHealthcheck(adapter, callback) { try { const result = await tbUtils.healthCheck(adapter); if (result) { return callback(result); } return callback(null, 'Healthcheck failed'); } catch (error) { return callback(null, error); } } /** * @summary runs connectivity check script for adapter * * @function iapRunAdapterConnectivity * @param {Adapter} adapter - adapter instance to troubleshoot * @param {Callback} callback - callback function to return connectivity status */ async iapRunAdapterConnectivity(callback) { try { const { host } = this.allProps; const result = tbUtils.runConnectivity(host, false); if (result.failCount > 0) { return callback(null, result); } return callback(result); } catch (error) { return callback(null, error); } } /** * @summary runs basicGet script for adapter * * @function iapRunAdapterBasicGet * @param {Callback} callback - callback function to return basicGet result */ iapRunAdapterBasicGet(callback) { try { const result = tbUtils.runBasicGet(false); if (result.failCount > 0) { return callback(null, result); } return callback(result); } catch (error) { return callback(null, error); } } /** * @summary moves entities to mongo database * * @function iapMoveAdapterEntitiesToDB * * @return {Callback} - containing the response from the mongo transaction */ async iapMoveAdapterEntitiesToDB(callback) { const meth = 'adapterBase-iapMoveAdapterEntitiesToDB'; const origin = `${this.id}-${meth}`; log.trace(origin); try { const result = await entitiesToDB.moveEntitiesToDB(__dirname, { pronghornProps: this.allProps, id: this.id }); return callback(result, null); } catch (err) { const errorObj = this.requestHandlerInst.formatErrorObject(this.id, meth, 'Caught Exception', null, null, null, err); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, err.message); } } /** * @function iapDeactivateTasks * * @param {Array} tasks - List of tasks to deactivate * @param {Callback} callback */ iapDeactivateTasks(tasks, callback) { const meth = 'adapterBase-iapDeactivateTasks'; const origin = `${this.id}-${meth}`; log.trace(origin); let data; try { data = taskMover.deactivateTasks(__dirname, tasks); } catch (ex) { taskMover.rollbackChanges(__dirname); taskMover.deleteBackups(__dirname); return callback(null, ex); } taskMover.deleteBackups(__dirname); return callback(data, null); } /** * @function iapActivateTasks * * @param {Array} tasks - List of tasks to deactivate * @param {Callback} callback */ iapActivateTasks(tasks, callback) { const meth = 'adapterBase-iapActivateTasks'; const origin = `${this.id}-${meth}`; log.trace(origin); let data; try { data = taskMover.activateTasks(__dirname, tasks); } catch (ex) { taskMover.rollbackChanges(__dirname); taskMover.deleteBackups(__dirname); return callback(null, ex); } taskMover.deleteBackups(__dirname); return callback(data, null); } /* ********************************************** */ /* */ /* EXPOSES CACHE CALLS */ /* */ /* ********************************************** */ /** * @summary Populate the cache for the given entities * * @function iapPopulateEntityCache * @param {String/Array of Strings} entityType - the entity type(s) to populate * @param {Callback} callback - whether the cache was updated or not for each entity type * @returns return of the callback */ iapPopulateEntityCache(entityTypes, callback) { const origin = `${this.myid}-adapterBase-iapPopulateEntityCache`; log.trace(origin); return this.requestHandlerInst.populateEntityCache(entityTypes, callback); } /** * @summary Retrieves data from cache for specified entity type * * @function iapRetrieveEntitiesCache * @param {String} entityType - entity of which to retrieve * @param {Object} options - settings of which data to return and how to return it * @param {Callback} callback - the data if it was retrieved */ iapRetrieveEntitiesCache(entityType, options, callback) { const origin = `${this.myid}-adapterBase-iapRetrieveEntitiesCache`; log.trace(origin); return this.requestHandlerInst.retrieveEntitiesCache(entityType, options, callback); } /* ********************************************** */ /* */ /* EXPOSES BROKER CALLS */ /* */ /* ********************************************** */ /** * @summary Determines if this adapter supports any in a list of entities * * @function hasEntities * @param {String} entityType - the entity type to check for * @param {Array} entityList - the list of entities we are looking for * * @param {Callback} callback - A map where the entity is the key and the * value is true or false */ hasEntities(entityType, entityList, callback) { const origin = `${this.id}-adapterBase-hasEntities`; log.trace(origin); return this.requestHandlerInst.hasEntities(entityType, entityList, callback); } /** * @summary Determines if this adapter supports any in a list of entities * * @function hasEntitiesAuth * @param {String} entityType - the entity type to check for * @param {Array} entityList - the list of entities we are looking for * @param {Object} callOptions - Additional options used to make request, including auth headers, AWS service, or datatypes * * @param {Callback} callback - A map where the entity is the key and the * value is true or false */ hasEntitiesAuth(entityType, entityList, callOptions, callback) { const origin = `${this.id}-adapterBase-hasEntitiesAuth`; log.trace(origin); return this.requestHandlerInst.hasEntitiesAuth(entityType, entityList, callOptions, callback); } /** * @summary Get Appliance that match the deviceName * * @function getDevice * @param {String} deviceName - the deviceName to find (required) * * @param {getCallback} callback - a callback function to return the result * (appliance) or the error */ getDevice(deviceName, callback) { const origin = `${this.id}-adapterBase-getDevice`; log.trace(origin); return this.requestHandlerInst.getDevice(deviceName, callback); } /** * @summary Get Appliance that match the deviceName * * @function getDeviceAuth * @param {String} deviceName - the deviceName to find (required) * @param {Object} callOptions - Additional options used to make request, including auth headers, AWS service, or datatypes * * @param {getCallback} callback - a callback function to return the result * (appliance) or the error */ getDeviceAuth(deviceName, callOptions, callback) { const origin = `${this.id}-adapterBase-getDeviceAuth`; log.trace(origin); return this.requestHandlerInst.getDeviceAuth(deviceName, callOptions, callback); } /** * @summary Get Appliances that match the filter * * @function getDevicesFiltered * @param {Object} options - the data to use to filter the appliances (optional) * * @param {getCallback} callback - a callback function to return the result * (appliances) or the error */ getDevicesFiltered(options, callback) { const origin = `${this.id}-adapterBase-getDevicesFiltered`; log.trace(origin); return this.requestHandlerInst.getDevicesFiltered(options, callback); } /** * @summary Get Appliances that match the filter * * @function getDevicesFilteredAuth * @param {Object} options - the data to use to filter the appliances (optional) * @param {Object} callOptions - Additional options used to make request, including auth headers, AWS service, or datatypes * * @param {getCallback} callback - a callback function to return the result * (appliances) or the error */ getDevicesFilteredAuth(options, callOptions, callback) { const origin = `${this.id}-adapterBase-getDevicesFilteredAuth`; log.trace(origin); return this.requestHandlerInst.getDevicesFilteredAuth(options, callOptions, callback); } /** * @summary Gets the status for the provided appliance * * @function isAlive * @param {String} deviceName - the deviceName of the appliance. (required) * * @param {configCallback} callback - callback function to return the result * (appliance isAlive) or the error */ isAlive(deviceName, callback) { const origin = `${this.id}-adapterBase-isAlive`; log.trace(origin); return this.requestHandlerInst.isAlive(deviceName, callback); } /** * @summary Gets the status for the provided appliance * * @function isAliveAuth * @param {String} deviceName - the deviceName of the appliance. (required) * @param {Object} callOptions - Additional options used to make request, including auth headers, AWS service, or datatypes * * @param {configCallback} callback - callback function to return the result * (appliance isAliveAuth) or the error */ isAliveAuth(deviceName, callOptions, callback) { const origin = `${this.id}-adapterBase-isAliveAuth`; log.trace(origin); return this.requestHandlerInst.isAliveAuth(deviceName, callOptions, callback); } /** * @summary Gets a config for the provided Appliance * * @function getConfig * @param {String} deviceName - the deviceName of the appliance. (required) * @param {String} format - the desired format of the config. (optional) * * @param {configCallback} callback - callback function to return the result * (appliance config) or the error */ getConfig(deviceName, format, callback) { const origin = `${this.id}-adapterBase-getConfig`; log.trace(origin); return this.requestHandlerInst.getConfig(deviceName, format, callback); } /** * @summary Gets a config for the provided Appliance * * @function getConfigAuth * @param {String} deviceName - the deviceName of the appliance. (required) * @param {String} format - the desired format of the config. (optional) * @param {Object} callOptions - Additional options used to make request, including auth headers, AWS service, or datatypes * * @param {configCallback} callback - callback function to return the result * (appliance config) or the error */ getConfigAuth(deviceName, format, callOptions, callback) { const origin = `${this.id}-adapterBase-getConfigAuth`; log.trace(origin); return this.requestHandlerInst.getConfigAuth(deviceName, format, callOptions, callback); } /** * @summary Gets the device count from the system * * @function iapGetDeviceCount * * @param {getCallback} callback - callback function to return the result * (count) or the error */ iapGetDeviceCount(callback) { const origin = `${this.id}-adapterBase-iapGetDeviceCount`; log.trace(origin); return this.requestHandlerInst.iapGetDeviceCount(callback); } /** * @summary Gets the device count from the system * * @function iapGetDeviceCountAuth * @param {Object} callOptions - Additional options used to make request, including auth headers, AWS service, or datatypes * * @param {getCallback} callback - callback function to return the result * (count) or the error */ iapGetDeviceCountAuth(callOptions, callback) { const origin = `${this.id}-adapterBase-iapGetDeviceCountAuth`; log.trace(origin); return this.requestHandlerInst.iapGetDeviceCountAuth(callOptions, callback); } /* ********************************************** */ /* */ /* EXPOSES GENERIC HANDLER */ /* */ /* ********************************************** */ /** * Makes the requested generic call * * @function iapExpandedGenericAdapterRequest * @param {Object} metadata - metadata for the call (optional). * Can be a stringified Object. * @param {String} uriPath - the path of the api call - do not include the host, port, base path or version (optional) * @param {String} restMethod - the rest method (GET, POST, PUT, PATCH, DELETE) (optional) * @param {Object} pathVars - the parameters to be put within the url path (optional). * Can be a stringified Object. * @param {Object} queryData - the parameters to be put on the url (optional). * Can be a stringified Object. * @param {Object} requestBody - the body to add to the request (optional). * Can be a stringified Object. * @param {Object} addlHeaders - additional headers to be put on the call (optional). * Can be a stringified Object. * @param {getCallback} callback - a callback function to return the result (Generics) * or the error */ iapExpandedGenericAdapterRequest(metadata, uriPath, restMethod, pathVars, queryData, requestBody, addlHeaders, callback) { const origin = `${this.myid}-adapterBase-iapExpandedGenericAdapterRequest`; log.trace(origin); return this.requestHandlerInst.expandedGenericAdapterRequest(metadata, uriPath, restMethod, pathVars, queryData, requestBody, addlHeaders, callback); } /** * Makes the requested generic call * * @function genericAdapterRequest * @param {String} uriPath - the path of the api call - do not include the host, port, base path or version (required) * @param {String} restMethod - the rest method (GET, POST, PUT, PATCH, DELETE) (required) * @param {Object} queryData - the parameters to be put on the url (optional). * Can be a stringified Object. * @param {Object} requestBody - the body to add to the request (optional). * Can be a stringified Object. * @param {Object} addlHeaders - additional headers to be put on the call (optional). * Can be a stringified Object. * @param {getCallback} callback - a callback function to return the result (Generics) * or the error */ genericAdapterRequest(uriPath, restMethod, queryData, requestBody, addlHeaders, callback) { const origin = `${this.myid}-adapterBase-genericAdapterRequest`; log.trace(origin); return this.requestHandlerInst.genericAdapterRequest(uriPath, restMethod, queryData, requestBody, addlHeaders, callback); } /** * Makes the requested generic call with no base path or version * * @function genericAdapterRequestNoBasePath * @param {String} uriPath - the path of the api call - do not include the host, port, base path or version (required) * @param {String} restMethod - the rest method (GET, POST, PUT, PATCH, DELETE) (required) * @param {Object} queryData - the parameters to be put on the url (optional). * Can be a stringified Object. * @param {Object} requestBody - the body to add to the request (optional). * Can be a stringified Object. * @param {Object} addlHeaders - additional headers to be put on the call (optional). * Can be a stringified Object. * @param {getCallback} callback - a callback function to return the result (Generics) * or the error */ genericAdapterRequestNoBasePath(uriPath, restMethod, queryData, requestBody, addlHeaders, callback) { const origin = `${this.myid}-adapterBase-genericAdapterRequestNoBasePath`; log.trace(origin); return this.requestHandlerInst.genericAdapterRequestNoBasePath(uriPath, restMethod, queryData, requestBody, addlHeaders, callback); } /* ********************************************** */ /* */ /* EXPOSES INVENTORY CALLS */ /* */ /* ********************************************** */ /** * @summary run the adapter lint script to return the results. * * @function iapRunAdapterLint * * @return {Object} - containing the results of the lint call. */ iapRunAdapterLint(callback) { const meth = 'adapterBase-iapRunAdapterLint'; const origin = `${this.id}-${meth}`; log.trace(origin); let command = null; if (fs.existsSync('package.json')) { const packageData = require('./package.json'); // check if 'test', 'test:unit', 'test:integration' exists in package.json file if (!packageData.scripts || !packageData.scripts['lint:errors']) { log.error('The required script does not exist in the package.json file'); return callback(null, 'The required script does not exist in the package.json file'); } // execute 'npm run lint:errors' command command = spawnSync('npm', ['run', 'lint:errors'], { cwd: __dirname, encoding: 'utf-8' }); // analyze and format the response const result = { status: 'SUCCESS' }; if (command.status !== 0) { result.status = 'FAILED'; result.output = command.stdout; } return callback(result); } log.error('Package Not Found'); return callback(null, 'Package Not Found'); } /** * @summary run the adapter test scripts (baseunit and unit) to return the results. * can not run integration as there can be implications with that. * * @function iapRunAdapterTests * * @return {Object} - containing the results of the baseunit and unit tests. */ iapRunAdapterTests(callback) { const meth = 'adapterBase-iapRunAdapterTests'; const origin = `${this.id}-${meth}`; log.trace(origin); let basecommand = null; let command = null; if (fs.existsSync('package.json')) { const packageData = require('./package.json'); // check if 'test', 'test:unit', 'test:integration' exists in package.json file