Source: lib/deploy.js

/**
 * @file lesslms-cli tool for serverless application deployment in AWS.
 * @author Oscar Sanz Llopis <osanzl@uoc.edu>
 * @module lib/deploy
 * @license MIT
 */
const _sourcesUri = 'https://api.github.com/repos/oskrs111/lesslms-devel/releases/latest';
const fs = require('fs');
const AWS = require('aws-sdk');
const csv = require('csv-parser')
const log = require('../src/common').log;
const path = require('path');
const got = require('got');
const gotZip = require('got-zip');
const _path = path.join(__dirname, '../', 'serverless');

/**
 * Variable to store s3 instance.
 * @var
 */
let s3 = {};

/**
 * Variable to store AWS config.
 * @var
 */
let _config = {};

/**
 * Variable to store generator instances.
 * @var
 */
let _gen = {};

/**
 * Variable to store generator instances.
 * @var
 */
let _gend = {};

/**
 * Entry point for lib/deploy module.
 * @function
 */
function _main() {
    log('> lesslms deploy...', 'info');
    let _param = _configGet('region');
    if (_param == null) {
        log(`Param "region" is not defined in "serverless.json"`, 'error');
        return;
    }

    _getCredentials((error, credentials) => {
        if (error != undefined) {
            log(`> ERROR > getting credentials from "credentials.csv": ${JSON.stringify(error)}`, 'error');
            _gend.return(false);
        }
        //OSLL: Prepare AWS interface configuration.
        _config = new AWS.Config({
            accessKeyId: credentials['Access key ID'],
            secretAccessKey: credentials['Secret access key'],
            region: _param,
            apiVersions: {
                cloudformation: '2010-05-15',
                s3: '2006-03-01'
            }
        });

        AWS.config.update(_config);
        s3 = new AWS.S3(); //OSLL: prepare the s3 interface with new credentials.

        _gend = _deploy((error) => {});
        _gend.next();
    });
}

/**
 * Runs the deployment sequence.
 * @generator
 * @param {stobject} credentials AWS credentials object.
 * @callback cbk
 */

/**
 * Called on completion.
 * @param {cbk} callback Error first callback. 
 */
function* _deploy(callback) {
    _gen = _getSources(_sourcesUri, (error) => {
        if (error != undefined) {
            log(`> ERROR > running deployment: ${JSON.stringify(error)}`, 'error');
            _gend.return(false);
        }
        _gend.next();
    });
    _gen.next(); //OSLL: Activates '_getSources()' generator.
    yield;

    let _bucket = {};
    _gen = _createBucket(_config.region, (error, bucket) => {
        if (error != undefined) {
            log(`> ERROR > creating bucket: ${JSON.stringify(error)}`, 'error');
            _gend.return(false);
        } else {
            _configSet('bucketName', bucket.name);
            _configSet('bucketUri', bucket.uri);
            _bucket = bucket;
            _gend.next();
        }
    });
    _gen.next(); //OSLL: Activates '_createBucket()' generator.
    yield;

    _gen = _populateBucket(_bucket, (error) => {
        if (error != undefined) {
            log(`> ERROR > populating bucket: ${JSON.stringify(error)}`, 'error');
            _gend.return(false);
        } else {
            _gend.next();
        }
    });
    _gen.next(); //OSLL: Activates '_populateBucket()' generator.
    yield;

    callback(); //OSLL: Success!
}

/**
 * Proceses AWS "credentials.csv" file.
 * @generator
 * @callback cbk
 */

/**
 * Called on completion.
 * @param {cbk} callback Error first callback, returns also with credentials object.
 */
function _getCredentials(callback) {
    let _cred = {};
    fs.createReadStream(path.join(__dirname, '../', 'serverless', 'credentials.csv'))
        .pipe(csv())
        .on('data', (data) => {
            _cred = data;
        })
        .on('end', () => {
            callback(null, _cred);
        });
}

/**
 * Fetches the zipped sources functions from releases repository.
 * @generator
 * @param {string} uri Github API uri reference to get last release information.
 * @callback cbk
 */

/**
 * Called on completion.
 * @param {cbk} callback Error first callback. 
 */
function* _getSources(uri, callback) {
    log(`> Fetching last release source from: ${uri}`, 'info');
    let _source = '';
    let _sourceName = '';
    let _got = got(_sourcesUri);

    _got.then((data) => {
        _source = JSON.parse(data.body).assets[0].browser_download_url;
        _sourceName = JSON.parse(data.body).assets[0].name;
        _gen.next();

    }, (reason) => {
        log(`> ERROR > getting last release: ${JSON.stringify(reason)}`, 'error');
        _gen.return(false); //OSLL: Error!
        callback(reason);
    });
    yield;

    log(`> Getting Sources from: ${_source}`, 'info');
    let _argsz = {
        dest: _path,
        extract: true,
        cleanup: false,
        strip: 0
    };

    _got = gotZip(_source, _argsz);
    _got.then((data) => {
        log(`> Getting Lambdas END`, 'info');
        _gen.next();
    }, (reason) => {
        log(`> ERROR > getting Lambdas: ${JSON.stringify(reason)}`, 'error');
        _gen.return(false); //OSLL: Error!
        callback(reason);
    });
    yield;
    callback(); //OSLL: Success!
}

/**
 * Creates a s3 bucket in AWS and uploads the Lambda source code.
 * @generator
 * @param {string} region AWS region where create the new bucket.
 * @callback cbk
 */

/**
 * Called on completion.
 * @param {cbk} callback Error first callback, returns also with the new bucket name.
 */
function* _createBucket(region, callback) {
    let _location = {};
    let _bucket = `lesslms-${new Date().getTime()}`;
    let params = {
        Bucket: _bucket,
        CreateBucketConfiguration: {
            LocationConstraint: region
        }
    };

    log(`> Setting up a new s3 bucket "${_bucket}"`, 'info');
    s3.createBucket(params, function(error, data) {
        if (error) {
            _gen.return(false); //OSLL: Error!
            callback(error);
        } else {
            _location.name = _bucket;
            _location.uri = data.Location;
            _gen.next();
        }
    });
    yield;
    log(`> New bucket is located at "${_location.uri}"`, 'info');
    callback(null, _location); //OSLL: Success!
}

/**
 * Uploads the lesslms sources to the s3 bucket.
 * @generator
 * @param {object} bucket s3 bucket data where perform the uploads.
 * @callback cbk
 */

/**
 * Called on completion.
 * @param {cbk} callback Error first callback.
 */
function* _populateBucket(bucket, callback) {
    log(`> Prepare file list for uploading...`, 'info');

    //OSLL: Get top level structure of source directory. Only 2 levels are expected to use.
    let _upload = [];
    let _l1Path = path.join(_path, 'lesslms');
    let _l1 = fs.readdirSync(_l1Path, { withFileTypes: true });
    for (let i of _l1) {
        //OSLL: Get the contents of second level and push to upload quewe...
        if (i.isDirectory() == true) {
            let _l2Path = path.join(_path, 'lesslms', i.name);
            let _l2 = fs.readdirSync(_l2Path, { withFileTypes: true });
            for (let t of _l2) {
                if (t.isFile() == true) {
                    t.path = path.join(_l2Path, t.name);
                    t.bucket = `${bucket.name}/${i.name}`;
                    t.key = t.name;
                    _upload.push(t);
                    log(`> File added to upload quewe: "${t.path}"`, 'info');
                }
            }
        }
    }

    let _params = {};
    for (let b of _upload) {
        let _buffer = fs.readFileSync(b.path);
        _params = {
            Body: _buffer,
            Bucket: b.bucket,
            Key: b.key
        };

        debugger;
        s3.putObject(_params, (error, data) => {
            if (error) {
                _gen.return(false); //OSLL: Error!
                callback(error);
            } else {
                log(`> File uploaded success: "${b.bucket}"`, 'info');
                _gen.next();
            }
        });
        yield;
    }
    callback(null); //OSLL: Success!
}

/**
 * Launches the CloudFormation script.
 * @generator
 * @param {string} region AWS region where create the new bucket.
 * @callback cbk
 */

/**
 * Called on completion.
 * @param {cbk} callback Error first callback.
 */
function* _launchFormation(region, callback) {

    log(`> New bucket is located at "${_location.uri}"`, 'info');
    callback();
}

/**
 * Helper function to retrieve parameters from 'serverless.json' config file.
 * @function
 * @param {string} parameter Parameter name to retrieve.
 */
function _configGet(parameter) {
    let _read = fs.readFileSync(path.join(__dirname, '../', 'serverless.json'));
    let _obj = JSON.parse(_read);
    if (_obj[parameter] != undefined) {
        return _obj[parameter];
    } else return null;
}

/**
 * Helper function to store parameters to 'serverless.json' config file.
 * @function
 * @param {string} parameter Parameter name to update.
 * @param {string} value Parameter value to update.
 */
function _configSet(parameter, value) {
    let _read = fs.readFileSync(path.join(__dirname, '../', 'serverless.json'));
    let _obj = JSON.parse(_read);
    _obj[parameter] = value;

    fs.writeFileSync(path.join(__dirname, '../', 'serverless.json'), JSON.stringify(_obj, null, 4));
}


module.exports = _main;