UNPKG

@itentialopensource/adapter-git

Version:

Itential adapter to run git commands

673 lines (621 loc) 24.1 kB
/* @copyright Itential, LLC 2019 (pre-modifications) */ // Set globals /* global eventSystem log */ /* eslint no-underscore-dangle: warn */ /* eslint no-loop-func: warn */ /* eslint no-cond-assign: warn */ /* eslint no-unused-vars: warn */ /* eslint consistent-return: warn */ /* eslint import/no-dynamic-require: warn */ /* eslint global-require: warn */ /* eslint no-await-in-loop: warn */ /* eslint default-param-last: warn */ /* Required libraries. */ const fs = require('fs-extra'); const builtInfs = require('fs'); const path = require('path'); const util = require('util'); /* Fetch in the other needed components for the this Adaptor */ const EventEmitterCl = require('events').EventEmitter; const AjvCl = require('ajv'); const git = require('isomorphic-git'); const http = require('isomorphic-git/http/node'); let myid = null; let errors = []; /** * @summary Build a standard error object from the data provided * * @function formatErrorObject * @param {String} origin - the originator of the error (optional). * @param {String} type - the internal error type (optional). * @param {String} variables - the variables to put into the error message (optional). * @param {Integer} sysCode - the error code from the other system (optional). * @param {Object} sysRes - the raw response from the other system (optional). * @param {Exception} stack - any available stack trace from the issue (optional). * * @return {Object} - the error object, null if missing pertinent information */ function formatErrorObject(origin, type, variables, sysCode, sysRes, stack) { log.trace(`${myid}-adapter-formatErrorObject`); // add the required fields const errorObject = { icode: 'AD.999', IAPerror: { origin: `${myid}-unidentified`, displayString: 'error not provided', recommendation: 'report this issue to the adapter team!' } }; if (origin) { errorObject.IAPerror.origin = origin; } if (type) { errorObject.IAPerror.displayString = type; } // add the messages from the error.json for (let e = 0; e < errors.length; e += 1) { if (errors[e].key === type) { errorObject.icode = errors[e].icode; errorObject.IAPerror.displayString = errors[e].displayString; errorObject.IAPerror.recommendation = errors[e].recommendation; } else if (errors[e].icode === type) { errorObject.icode = errors[e].icode; errorObject.IAPerror.displayString = errors[e].displayString; errorObject.IAPerror.recommendation = errors[e].recommendation; } } // replace the variables let varCnt = 0; while (errorObject.IAPerror.displayString.indexOf('$VARIABLE$') >= 0) { let curVar = ''; // get the current variable if (variables && Array.isArray(variables) && variables.length >= varCnt + 1) { curVar = variables[varCnt]; } varCnt += 1; errorObject.IAPerror.displayString = errorObject.IAPerror.displayString.replace('$VARIABLE$', curVar); } // add all of the optional fields if (sysCode) { errorObject.IAPerror.code = sysCode; } if (sysRes) { errorObject.IAPerror.raw_response = sysRes; } if (stack) { errorObject.IAPerror.stack = stack; } // return the object return errorObject; } /** * This is the adapter/interface into Git */ class Git extends EventEmitterCl { /** * Git Adapter * @constructor */ constructor(prongid, properties) { super(); this.props = properties; this.alive = false; this.id = prongid; myid = prongid; this.username = null; this.password = null; // get the path for the specific error file const errorFile = path.join(__dirname, '/error.json'); // if the file does not exist - error if (!fs.existsSync(errorFile)) { const origin = `${this.id}-adapter-constructor`; log.warn(`${origin}: Could not locate ${errorFile} - errors will be missing details`); } // Read the action from the file system const errorData = JSON.parse(fs.readFileSync(errorFile, 'utf-8')); ({ errors } = errorData); } /** * Connect function is used during Pronghorn startup to provide instantiation feedback. * * @function connect */ connect() { const meth = 'adapter-connect'; const origin = `${this.id}-${meth}`; log.trace(origin); if (this.props.authentication.username) { this.username = this.props.authentication.username; } if (this.props.authentication.password) { this.password = this.props.authentication.password; } this.onAuth = () => ({ username: this.username, password: this.password }); this.emit('ONLINE', { id: this.id }); } /** * @summary HealthCheck function is used to provide Pronghorn the status of this adapter. * * @function healthCheck * @param callback - a callback function to return the result id and status */ healthCheck(callback) { // need to work on this later const retstatus = { id: this.id, status: 'success' }; return callback(retstatus); } /** * 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; } /** * getWorkflowFunctions is used to get all of the workflow function in the adapter * * @function getWorkflowFunctions */ getWorkflowFunctions() { 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] === 'addListener') { // got to the second tier (adapterBase) break; } if (myfunctions[m] !== 'connect' && myfunctions[m] !== 'healthCheck' && myfunctions[m] !== 'getAllFunctions' && myfunctions[m] !== 'getWorkflowFunctions' && myfunctions[m] !== 'refreshProperties') { wffunctions.push(myfunctions[m]); } } return wffunctions; } cloneRepo(options, callback) { const meth = 'adapter-cloneRepo'; const origin = `${this.id}-${meth}`; log.trace(origin); try { // verify the required data has been provided if (options === undefined || options === null || options === '') { const errorObj = formatErrorObject(origin, 'Missing Data', ['options'], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } const copiedOptions = { ...options }; copiedOptions.fs = fs; copiedOptions.http = http; copiedOptions.onAuth = this.onAuth; git.clone(copiedOptions) .then(() => callback({ status: 'success', code: 200 })) .catch((err) => { const errorObj = formatErrorObject(origin, 'Cloning repo failed', null, null, null, err); log.error('error:', err); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); }); } catch (ex) { const errorObj = formatErrorObject(origin, 'Caught Exception', null, null, null, ex); log.error(ex); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } } createBranch(options, callback) { const meth = 'adapter-createBranch'; const origin = `${this.id}-${meth}`; log.trace(origin); try { // verify the required data has been provided if (options === undefined || options === null || options === '') { const errorObj = formatErrorObject(origin, 'Missing Data', ['options'], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } const copiedOptions = { ...options }; copiedOptions.fs = fs; git.branch(copiedOptions) .then(() => callback({ status: 'success', code: 200 })) .catch((err) => { const errorObj = formatErrorObject(origin, 'Creating new branch failed', null, null, null, err); log.error('error:', err); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); }); } catch (ex) { const errorObj = formatErrorObject(origin, 'Caught Exception', null, null, null, ex); log.error(ex); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } } checkoutBranch(options, callback) { const meth = 'adapter-checkoutBranch'; const origin = `${this.id}-${meth}`; log.trace(origin); try { // verify the required data has been provided if (options === undefined || options === null || options === '') { const errorObj = formatErrorObject(origin, 'Missing Data', ['options'], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } const copiedOptions = { ...options }; copiedOptions.fs = fs; git.checkout(copiedOptions) .then(() => callback({ status: 'success', code: 200 })) .catch((err) => { const errorObj = formatErrorObject(origin, 'Checking out failed', null, null, null, err); log.error('error:', err); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); }); } catch (ex) { const errorObj = formatErrorObject(origin, 'Caught Exception', null, null, null, ex); log.error(ex); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } } removeFile(options, callback) { const meth = 'adapter-removeFile'; const origin = `${this.id}-${meth}`; log.trace(origin); try { // verify the required data has been provided if (options === undefined || options === null || options === '') { const errorObj = formatErrorObject(origin, 'Missing Data', ['options'], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } const copiedOptions = { ...options }; copiedOptions.fs = fs; git.remove(copiedOptions) .then(() => callback({ status: 'success', code: 200 })) .catch((err) => { const errorObj = formatErrorObject(origin, 'Removing file failed', null, null, null, err); log.error('error:', err); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); }); } catch (ex) { const errorObj = formatErrorObject(origin, 'Caught Exception', null, null, null, ex); log.error(ex); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } } addFile(options, callback) { const meth = 'adapter-addFile'; const origin = `${this.id}-${meth}`; log.trace(origin); try { // verify the required data has been provided if (options === undefined || options === null || options === '') { const errorObj = formatErrorObject(origin, 'Missing Data', ['options'], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } const copiedOptions = { ...options }; copiedOptions.fs = fs; git.add(copiedOptions) .then(() => callback({ status: 'success', code: 200 })) .catch((err) => { const errorObj = formatErrorObject(origin, 'Adding file failed', null, null, null, err); log.error('error:', err); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); }); } catch (ex) { const errorObj = formatErrorObject(origin, 'Caught Exception', null, null, null, ex); log.error(ex); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } } getStatus(options, callback) { const meth = 'adapter-getStatus'; const origin = `${this.id}-${meth}`; log.trace(origin); try { // verify the required data has been provided if (options === undefined || options === null || options === '') { const errorObj = formatErrorObject(origin, 'Missing Data', ['options'], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } const copiedOptions = { ...options }; copiedOptions.fs = fs; git.status(copiedOptions) .then((stat) => callback({ status: 'success', code: 200, result: stat })) .catch((err) => { const errorObj = formatErrorObject(origin, 'Getting status failed', null, null, null, err); log.error('error:', err); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); }); } catch (ex) { const errorObj = formatErrorObject(origin, 'Caught Exception', null, null, null, ex); log.error(ex); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } } getStatusMatrix(options, callback) { const meth = 'adapter-getStatusMatrix'; const origin = `${this.id}-${meth}`; log.trace(origin); try { // verify the required data has been provided if (options === undefined || options === null || options === '') { const errorObj = formatErrorObject(origin, 'Missing Data', ['options'], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } const copiedOptions = { ...options }; copiedOptions.fs = fs; git.statusMatrix(copiedOptions) .then((stat) => callback({ status: 'success', code: 200, result: stat })) .catch((err) => { const errorObj = formatErrorObject(origin, 'Getting status matrix failed', null, null, null, err); log.error('error:', err); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); }); } catch (ex) { const errorObj = formatErrorObject(origin, 'Caught Exception', null, null, null, ex); log.error(ex); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } } createCommit(options, callback) { const meth = 'adapter-createCommit'; const origin = `${this.id}-${meth}`; log.trace(origin); try { // verify the required data has been provided if (options === undefined || options === null || options === '') { const errorObj = formatErrorObject(origin, 'Missing Data', ['options'], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } const copiedOptions = { ...options }; copiedOptions.fs = fs; git.commit(copiedOptions) .then((res) => callback({ status: 'success', code: 200, result: res })) .catch((err) => { const errorObj = formatErrorObject(origin, 'Creating commit failed', null, null, null, err); log.error('error:', err); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); }); } catch (ex) { const errorObj = formatErrorObject(origin, 'Caught Exception', null, null, null, ex); log.error(ex); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } } pushChanges(options, callback) { const meth = 'adapter-pushChanges'; const origin = `${this.id}-${meth}`; log.trace(origin); try { // verify the required data has been provided if (options === undefined || options === null || options === '') { const errorObj = formatErrorObject(origin, 'Missing Data', ['options'], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } const copiedOptions = { ...options }; copiedOptions.fs = fs; copiedOptions.http = http; copiedOptions.onAuth = this.onAuth; git.push(copiedOptions) .then((res) => callback({ status: 'success', code: 200, result: res })) .catch((err) => { const errorObj = formatErrorObject(origin, 'Pushing changes failed', null, null, null, err); log.error('error:', err); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); }); } catch (ex) { const errorObj = formatErrorObject(origin, 'Caught Exception', null, null, null, ex); log.error(ex); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } } removeAndPushFile(cloneOptions, createAndCheckoutNewBranch = false, checkoutExistingBranch = false, branchOptions, checkoutOptions, removeFileOptions, commitOptions, pushOptions, callback) { const meth = 'adapter-removeAndPushFile'; const origin = `${this.id}-${meth}`; log.trace(origin); try { // verify the required data has been provided if (cloneOptions === undefined || cloneOptions === null || cloneOptions === '') { const errorObj = formatErrorObject(origin, 'Missing Data', ['clone options'], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } if (removeFileOptions === undefined || removeFileOptions === null || removeFileOptions === '') { const errorObj = formatErrorObject(origin, 'Missing Data', ['remove file options'], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } if (commitOptions === undefined || commitOptions === null || commitOptions === '') { const errorObj = formatErrorObject(origin, 'Missing Data', ['commit options'], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } if (pushOptions === undefined || pushOptions === null || pushOptions === '') { const errorObj = formatErrorObject(origin, 'Missing Data', ['push options'], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } if (createAndCheckoutNewBranch) { if (branchOptions === undefined || branchOptions === null || branchOptions === '') { const errorObj = formatErrorObject(origin, 'Missing Data', ['branch options'], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } if (checkoutOptions === undefined || checkoutOptions === null || checkoutOptions === '') { const errorObj = formatErrorObject(origin, 'Missing Data', ['checkout options'], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } } else if (!createAndCheckoutNewBranch && checkoutExistingBranch) { if (checkoutOptions === undefined || checkoutOptions === null || checkoutOptions === '') { const errorObj = formatErrorObject(origin, 'Missing Data', ['checkout options'], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } } const copiedCloneOptions = { ...cloneOptions }; copiedCloneOptions.fs = fs; copiedCloneOptions.http = http; copiedCloneOptions.onAuth = this.onAuth; const copiedRemoveFileOptions = { ...removeFileOptions }; copiedRemoveFileOptions.fs = fs; const copiedCommitOptions = { ...commitOptions }; copiedCommitOptions.fs = fs; const copiedPushOptions = { ...pushOptions }; copiedPushOptions.fs = fs; copiedPushOptions.http = http; copiedPushOptions.onAuth = this.onAuth; const successfulResult = { status: 'success', code: 200, results: [] }; git.clone(copiedCloneOptions) .then(() => { successfulResult.results.push({ task: 'clone', result: 'success' }); if (createAndCheckoutNewBranch) { const copiedBranchOptions = { ...branchOptions }; const copiedCheckoutOptions = { ...checkoutOptions }; copiedBranchOptions.fs = fs; copiedCheckoutOptions.fs = fs; return git.branch(copiedBranchOptions) .then(() => git.checkout(copiedCheckoutOptions)); } if (checkoutExistingBranch) { const copiedCheckoutOptions = { ...checkoutOptions }; copiedCheckoutOptions.fs = fs; return git.checkout(copiedCheckoutOptions); } }) .then(() => { if (createAndCheckoutNewBranch) { successfulResult.results.push({ task: 'create and checkout new branch', result: 'success' }); } else if (!createAndCheckoutNewBranch && checkoutExistingBranch) { successfulResult.results.push({ task: 'checkout existing branch', result: 'success' }); } if (Array.isArray(removeFileOptions.filepath)) { return Promise.all( removeFileOptions.filepath.map((file) => { copiedRemoveFileOptions.filepath = file; return git.remove(copiedRemoveFileOptions); }) ); } return git.remove(copiedRemoveFileOptions); }) .then(() => { successfulResult.results.push({ task: 'remove a file', result: 'success' }); return git.commit(copiedCommitOptions); }) .then((res) => { successfulResult.results.push({ task: 'commit', result: res }); return git.push(copiedPushOptions); }) .then((res) => { successfulResult.results.push({ task: 'push', result: res }); // delete the repo after pushing changes successfully builtInfs.rmdirSync(copiedCloneOptions.dir, { recursive: true }); return callback(successfulResult); }) .catch((err) => { const errorObj = formatErrorObject(origin, 'Removing And Pushing file failed', null, null, null, err); log.error('error:', err); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); }); } catch (ex) { const errorObj = formatErrorObject(origin, 'Missing Data', ['push options'], null, null, null); log.error(`${origin}: ${errorObj.IAPerror.displayString}`); return callback(null, errorObj); } } } module.exports = Git;