@itentialopensource/adapter-git
Version:
Itential adapter to run git commands
673 lines (621 loc) • 24.1 kB
JavaScript
/* @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;