yoctore/yocto-sftp

View on GitHub
src/modules/sftp/index.js

Summary

Maintainability
D
2 days
Test Coverage
'use strict';

var logger        = require('yocto-logger');
var _             = require('lodash');
var utils         = require('yocto-utils');
var Q             = require('q');
var fs            = require('fs');
var ClientSSH     = require('ssh2').Client;
var path          = require('path');
var async         = require('async');

/**
 *
 * Utility tool to automaticly generate sitemap
 *
 * @date : 06/12/2016
 * @author : Cédric BALARD <cedric@yocto.re>
 * @copyright : Yocto SAS, All right reserved
 *
 * @module SitemapGenerator
 */
function Sftp (l) {
  // set logger
  this.logger = l;
}

/**
 * Method that connect to the sftp server
 *
 * @param  {Object} config the config to connect
 * @return {Object} promise of this method
 */
Sftp.prototype.connect = function (config) {
  // create async process
  var deferred  = Q.defer();

  // create new client ftp
  var client = new ClientSSH();

  this.logger.debug('[ Sftp.connect ] - connecting to server ...');
  // listener for when client will be connected
  client.on('ready', function () {
    // log
    this.logger.debug('[ Sftp.connect.ready ] - connect successful to server');

    // create sftp connection
    client.sftp(function (error, sftp) {
      // check if an error occured
      if (error) {
        this.logger.error('[ Sftp.connect.sftp ] - connection establish to server, but sftp ' +
        'connection can\'t be established, details : ', utils.obj.inspect(error));
      }

      this.logger.info('[ Sftp.connect.sftp ] - connect successful to SFTP server');

      // resolve ssh client and sftp client
      deferred.resolve({
        client  : client,
        sftp    : sftp
      });
    }.bind(this));
  }.bind(this));

  // listener in error case
  client.on('error', function (error) {
    // an error occured
    this.logger.error('[ Sftp.connect.error ] - an error occured ', utils.obj.inspect(error));
    // reject error
    deferred.reject(error);
  }.bind(this));

  // connect
  client.connect(config);

  // return result of control flow
  return deferred.promise;
};

/**
 * Close the connection to the sftp server
 *
 * @param  {Object} client the client instance
 * @return {Object} promise of this method
 */
Sftp.prototype.end = function (client) {

  // create async process
  var deferred  = Q.defer();

  try {

    this.logger.debug('[ Sftp.end ] - try to close the connection');
    // try to close the connection
    client.client.end();
    this.logger.debug('[ Sftp.end ] - the connction was ended');

    // resolve success
    deferred.resolve(true);
  } catch (error) {

    this.logger.error('[ Sftp.end ] - an error occured when end the conection, more details : ',
    utils.obj.inspect(error));
    // an error occured so reject proise
    deferred.reject(error);
  }

  // return result of control flow
  return deferred.promise;
};

/**
 * Method to list folder of an directory on ftp server
 * If client exist use it and don't make an new connection
 *
 * @param  {Object} config the config to connect
 * @param  {Object} remotePathDir the path of folder to list
 * @param  {Object} client the sftp client if already connected
 * @return {Object} promise of this method
 */
Sftp.prototype.ls = function (config, remotePathDir, client) {

  client = client || false;
  // indicate if client was used
  var usedClient = false;
  var result = [];
  // create async process
  var deferred  = Q.defer();
  // normalize the path
  remotePathDir = path.normalize(remotePathDir);

  // control flow
  async.series([
    // check if already connect or should connect
    function (done) {
      // already connected ?
      if (client) {
        // set that client used already euxt
        usedClient = true;
        // end
        return done();
      }

      // connect to the ftp
      this.connect(config).then(function (c) {
        // set clent
        client = c;
        done();
      }.bind(this)).catch(function (error) {
        // reject the error
        done(error);
      });
    }.bind(this),
    // process
    function (done) {
      this.logger.debug('[ Sftp.ls ] - list file in folder : ', remotePathDir);
      // retrieve the list
      client.sftp.readdir(remotePathDir, function (error, list) {
        // check if an error occured
        if (error) {
          this.logger.error('[ Sftp.ls ] - an error occured when execute the list command, more ' +
          'details : ', utils.obj.inspect(error));

          // reject error
          return done(error);
        }
        this.logger.debug('[ Sftp.ls ] - list file command success');
        // resolve the file
        result = list;
        // success
        done();
      }.bind(this));
    }.bind(this)
  ], function (error) {
    // check if client was used
    if (!usedClient) {
      // close the connection because client was not used
      this.end(client);
    }

    // check error
    if (error) {
      // reject error
      return deferred.reject(error);
    }
    // success
    deferred.resolve(result);
  }.bind(this));

  // return result of control flow
  return deferred.promise;
};

/**
 * Method to check if an file exist in an folder
 *
 * @param  {Object} config the config to connect
 * @param  {Object} remotePathFile the path of the file to check if exits
 * @param  {Object} client the sftp client if already connected
 * @return {Object} promise of this method
 */
Sftp.prototype.fileExist = function (config, remotePathFile, client) {

  // create async process
  var deferred  = Q.defer();
  // normalize the path
  remotePathFile = path.normalize(remotePathFile);

  this.logger.info('[ Sftp.fileExist ] - check if the file exist : ', remotePathFile);

  // connect to the ftp
  this.ls(config, path.dirname(remotePathFile), client).then(function (list) {
    // find in the list folder if file exist
    var file = _.find(list, { filename : path.basename(remotePathFile) });
    // check if an file are found
    if (_.isUndefined(file)) {
      this.logger.warning('[ Sftp.fileExist ] - the file < ' + remotePathFile +
      ' > don\'t exist on ftp');
      // reject promise
      return deferred.reject('the file < ' + remotePathFile + ' > don\'t exist on ftp');
    }

    this.logger.info('[ Sftp.fileExist ] - the file < ' + remotePathFile + ' > exist on server');

    // the file exist, resolve it
    deferred.resolve(file);
  }.bind(this)).catch(function (error) {
    // reject the error
    deferred.reject(error);
  });

  // return result of control flow
  return deferred.promise;
};

/**
 * Method that upload an file on server
 *
 * @param  {Object} config the config to connect
 * @param  {Object} localPathFile the path of the file to upload
 * @param  {Object} remotePathFile the path of the file on remote server
 * @return {Object} promise of this method
 */
Sftp.prototype.put = function (config, localPathFile, remotePathFile) {

  // create async process
  var deferred  = Q.defer();

  localPathFile   = path.normalize(localPathFile);
  remotePathFile  = path.normalize(remotePathFile);

  // connect to the ftp
  this.connect(config).then(function (client) {

    this.logger.debug('[ Sftp.put ] - try to put the file < ' + localPathFile + ' > to < ' +
    remotePathFile + '>');

    // create stream content
    var readStream = fs.createReadStream(localPathFile);
    var writeStream = client.sftp.createWriteStream(remotePathFile);

    // Listen when transfer was done
    writeStream.on('close', function () {
      this.logger.info('[ Sftp.put ] - the file was correctly uploaed on path : < ' +
      remotePathFile + ' >');
      // success so resolve promise
      deferred.resolve(true);
      // close connection
      this.end(client);
    }.bind(this));

    // Listen if an error occuded during transfer
    writeStream.on('error', function (error) {
      this.logger.error('[ Sftp.put.ws ] - an error occured, more details : ',
      utils.obj.inspect(error));
      // reject error
      deferred.reject(error);
      // close connection
      this.end(client);
    }.bind(this));

    // Listen if an error occuded with local file
    readStream.on('error', function (error) {
      this.logger.error('[ Sftp.put.rs ] - an error occured, more details : ',
      utils.obj.inspect(error));
      // reject error
      deferred.reject(error);
      // close connection
      this.end(client);
    }.bind(this));

    // initiate transfer of file
    readStream.pipe(writeStream);
  }.bind(this)).catch(function (error) {
    // reject the error
    deferred.reject(error);
  });

  // return result of control flow
  return deferred.promise;
};

/**
 * Method to remove an existing file on ftp server
 *
 * @param  {Object} config the config to connect
 * @param  {Object} remotePathFile the path of the file to remove
 * @return {Object} promise of this method
*/
Sftp.prototype.delete = function (config, remotePathFile) {

  // create async process
  var deferred  = Q.defer();

  remotePathFile  = path.normalize(remotePathFile);

  // connect to the ftp
  this.connect(config).then(function (client) {

    this.logger.debug('[ Sftp.delete ] - delete the file < ' + remotePathFile + ' >');
    // remove the file
    client.sftp.unlink(remotePathFile, function (error) {
      // check if an error occured
      if (error) {
        this.logger.error('[ Sftp.delete ] - an error occured, more details : ' +
        utils.obj.inspect(error));
        // reject error
        deferred.reject(error);
      } else {
        this.logger.info('[ Sftp.delete ] - the file was deleted < ' + remotePathFile + ' >');
        // resolve the promise
        deferred.resolve(true);
      }

      // close the connection
      this.end(client);
    }.bind(this));
  }.bind(this)).catch(function (error) {
    // reject the error
    deferred.reject(error);
  });

  // return result of control flow
  return deferred.promise;
};

/**
 * Copy one file from the remote machine to the local machine
 *
 * @param  {Object} config the config to connect
 * @param  {Object} localPathFile the path of the file downloaded
 * @param  {Object} remotePathFile the path of th file to download
 * @return {Object} promise of this method
 */
Sftp.prototype.get = function (config, localPathFile, remotePathFile) {

  // create async process
  var deferred  = Q.defer();

  localPathFile   = path.normalize(localPathFile);
  remotePathFile  = path.normalize(remotePathFile);

  // connect to the ftp
  this.connect(config).then(function (client) {

    this.logger.debug('[ Sftp.get ] - try to download the file < ' + remotePathFile + ' > on ' +
    'remote to < ' + localPathFile + '>');

    // download the file
    client.sftp.fastGet(remotePathFile, localPathFile, {}, function (error) {
      // check if an error occured
      if (error) {
        this.logger.error('[ Sftp.get ] - an error occured, more details : ' +
        utils.obj.inspect(error));
        // reject the error
        deferred.reject(error);
      } else {
        this.logger.info('[ Sftp.get ] - the file was correctly downloaded to path < ' +
        localPathFile + ' >');
        // resolve the promise
        deferred.resolve(true);
      }

      // close the lien connection
      this.end(client);
    }.bind(this));
  }.bind(this)).catch(function (error) {
    // reject the error
    deferred.reject(error);
  });

  // return result of process
  return deferred.promise;
};

/**
* Method to create folder into sftp
*
* @param  {Object} client the sftp client
* @param  {Object} path the path to create
* @return {Object} promise of this method
 */
Sftp.prototype.createFolder = function (client, path) {
  // create async process
  var deferred  = Q.defer();

  // check if folder already exist
  this.fileExist(null, path, client).then(function () {
    // folder not created bevause exist
    deferred.resolve(false);
  }.bind(this)).catch(function () {
    // create folder
    client.sftp.mkdir(path, function (error) {
      // check if an error occured
      if (error) {
        // reject error
        return deferred.reject(error);
      }
      // resolve the file
      deferred.resolve(path);
    }.bind(this));
  }.bind(this));
  // return result of process
  return deferred.promise;
};

/**
 * Method to handle the creating folder into sftp
 * Parent folder can be created like *mkdirp* command
 *
 * @param  {Object} config the config to connect
 * @param  {String} pathFolder folder to create
 * @param  {Boolean} parent indicate if parent folder should be created
 * @return {Object} promise of this method
 */
Sftp.prototype.mkdir = function (config, pathFolder, parent) {

  // create async process
  var deferred  = Q.defer();
  // normalize the path
  pathFolder = path.normalize(pathFolder);

  // folder to create, by default only one
  var folderToCreate = [ pathFolder ];

  // check if should create parent folder
  if (parent) {
    // parent folder should be created
    var pathTmp = '';
    // split each folder to create subfolder
    folderToCreate = (_.map(_.compact(_.split(pathFolder, '/')), function (p) {
      // return path to create
      return pathTmp += ('/' + p);
    }));
  }

  // connect to the ftp
  this.connect(config).then(function (client) {
    // control flow to create all folder in array
    async.eachSeries(folderToCreate, function (f, nextFolder) {
      this.logger.debug('[ Sftp.mkdir ] - create folder : ', f);
      // create folder
      this.createFolder(client, f).then(function (folderCreated) {

        // success
        this.logger.debug('[ Sftp.mkdir ] - ' + (!folderCreated ?
        ' folder already exist for path : ' : ' folder was created : ') + f);
        // create next folder
        nextFolder();
      }.bind(this)).catch(function (error) {
        // failed
        this.logger.error('[ Sftp.createFolder ] - an error occured , more ' +
        'details : ', utils.obj.inspect(error));
        // error
        nextFolder(error);
      }.bind(this));
    }.bind(this), function (error) {
      // close connection
      this.end(client);
      // check if has error
      if (error) {
        // reject error
        return deferred.reject(error);
      }

      this.logger.info('[ Sftp.mkdir ] - the folder was correctly created : ', pathFolder);
      // success
      deferred.resolve(pathFolder);
    }.bind(this));
  }.bind(this)).catch(function (error) {
    // reject the error
    deferred.reject(error);
  });

  // return result of control flow
  return deferred.promise;
};

// Default export
module.exports = function (l) {
  // is a valid logger ?
  if (_.isUndefined(l) || _.isNull(l)) {
    logger.warning('[ Sftp.constructor ] - Invalid logger given. Use internal logger');
    // assign
    l = logger;
  }
  // default statement
  return new (Sftp)(l);
};