'use strict';

Object.defineProperty(exports, "__esModule", {
    value: true
});
exports.ExecutorDocker = undefined;

var _regenerator = require('babel-runtime/regenerator');

var _regenerator2 = _interopRequireDefault(_regenerator);

var _promise = require('babel-runtime/core-js/promise');

var _promise2 = _interopRequireDefault(_promise);

var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');

var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);

var _keys = require('babel-runtime/core-js/object/keys');

var _keys2 = _interopRequireDefault(_keys);

var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');

var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);

var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');

var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);

var _createClass2 = require('babel-runtime/helpers/createClass');

var _createClass3 = _interopRequireDefault(_createClass2);

var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');

var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);

var _inherits2 = require('babel-runtime/helpers/inherits');

var _inherits3 = _interopRequireDefault(_inherits2);

require('source-map-support/register');

var _Executor2 = require('../agent/Executor');

var _dockerode = require('dockerode');

var _dockerode2 = _interopRequireDefault(_dockerode);

var _stream = require('stream');

var _stream2 = _interopRequireDefault(_stream);

var _fs = require('fs');

var _fs2 = _interopRequireDefault(_fs);

var _appRootPath = require('app-root-path');

var _appRootPath2 = _interopRequireDefault(_appRootPath);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/**
 * Sample .githubTaskManager.json task config
 *
 * see: https://github.com/zotoio/github-task-manager/wiki/Structure-of-.githubTaskManager.json
 *
 {
   "executor": "Docker",
   "context": "run ls in latest alpine",
   "options": {
     "image": "alpine:latest",
     "command": "/bin/ls -ltr /bin",
     "env": {
        "myvar": "myval",
        "var2": "val2"
     },
     "validator": {
        "type": "outputRegex",
        "regex": ".*HOSTNAME.*"
     }
   }
 }
 */

var ExecutorDocker = exports.ExecutorDocker = function (_Executor) {
    (0, _inherits3.default)(ExecutorDocker, _Executor);

    function ExecutorDocker(eventData, log) {
        (0, _classCallCheck3.default)(this, ExecutorDocker);

        var _this = (0, _possibleConstructorReturn3.default)(this, (ExecutorDocker.__proto__ || (0, _getPrototypeOf2.default)(ExecutorDocker)).call(this, eventData, log));

        _this.log = log;
        _this.options = _this.getOptions();
        _this._taskOutputTail = '';
        return _this;
    }

    // will contain last 100 lines of output


    (0, _createClass3.default)(ExecutorDocker, [{
        key: 'validateImage',
        value: function validateImage(image) {
            var log = this.log;
            var valid = false;
            var imageList = this.options.GTM_DOCKER_IMAGE_WHITELIST ? this.options.GTM_DOCKER_IMAGE_WHITELIST.split(',') : [];

            imageList = this.options.GTM_DOCKER_IMAGE_WHITELIST_FILE ? _fs2.default.readFileSync(_appRootPath2.default + '/' + this.options.GTM_DOCKER_IMAGE_WHITELIST_FILE, 'utf-8').toString().split('\n') : imageList;

            if (imageList && imageList.length > 0) {
                imageList.forEach(function (imagePattern) {
                    if (!valid) {
                        var pattern = new RegExp(imagePattern.trim());
                        if (pattern.test(image)) {
                            log.info('matched whitelist image pattern \'' + imagePattern.trim() + '\'');
                            valid = true;
                        }
                    }
                });
            }
            return valid;
        }
    }, {
        key: 'formatEnv',
        value: function formatEnv(envObj) {
            var envArray = [];
            (0, _keys2.default)(envObj).forEach(function (key) {
                envArray.push(key + '=' + envObj[key]);
            });
            return envArray;
        }
    }, {
        key: 'executeTask',
        value: function () {
            var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(task) {
                var _this2 = this;

                var log, image, command, env, message, resultSummary, _message, _resultSummary, docker, that;

                return _regenerator2.default.wrap(function _callee$(_context) {
                    while (1) {
                        switch (_context.prev = _context.next) {
                            case 0:
                                log = this.log;
                                image = task.options.image;
                                command = task.options.command;
                                env = task.options.env || {};

                                if (!(command && (!this.options.GTM_DOCKER_COMMANDS_ALLOWED || this.options.GTM_DOCKER_COMMANDS_ALLOWED !== 'true'))) {
                                    _context.next = 10;
                                    break;
                                }

                                message = 'docker image commands are not allowed with the current configuration.';

                                log.error(message);
                                resultSummary = {
                                    passed: false,
                                    url: 'https://github.com/apocas/dockerode',
                                    message: message
                                };


                                task.results = resultSummary;

                                return _context.abrupt('return', _promise2.default.reject(task));

                            case 10:
                                if (this.validateImage(image)) {
                                    _context.next = 16;
                                    break;
                                }

                                _message = 'image \'' + image + '\' is not whitelisted.';

                                log.error(_message);
                                _resultSummary = {
                                    passed: false,
                                    url: 'https://github.com/apocas/dockerode',
                                    message: _message
                                };


                                task.results = _resultSummary;

                                return _context.abrupt('return', _promise2.default.reject(task));

                            case 16:

                                log.info('Starting local docker container \'' + image + '\' to run: ' + command);

                                docker = new _dockerode2.default();
                                that = this;
                                return _context.abrupt('return', this.pullImage(docker, image).then(function () {
                                    return docker.createContainer({
                                        Image: image,
                                        Cmd: command,
                                        Env: _this2.formatEnv(env)
                                    });
                                }).then(function (container) {
                                    return container.start();
                                }).then(function (container) {
                                    return _this2.containerLogs(that, container);
                                }).then(function (taskOutput) {
                                    var resultSummary = void 0;
                                    var lines = taskOutput.split('\n');
                                    var lineCount = lines.length - 1;
                                    var tail = lineCount <= 30 ? taskOutput : lines.slice(-30).join('\n');
                                    if (!_this2.validate(task, taskOutput)) {
                                        resultSummary = {
                                            passed: false,
                                            url: 'https://github.com/apocas/dockerode',
                                            message: 'Docker output validation failed for ' + task.options.validator.type,
                                            details: '\n\n**output tail (' + lineCount + ' lines total):**\n\n```\n...\n' + tail + '\n```\n\n'
                                        };
                                    } else {
                                        resultSummary = {
                                            passed: true,
                                            url: 'https://github.com/apocas/dockerode',
                                            message: 'Execution completed.',
                                            details: '\n\n**output tail (' + lineCount + ' lines total):**\n\n```\n...\n' + tail + '\n```\n\n'
                                        };
                                    }

                                    task.results = resultSummary;

                                    return _promise2.default.resolve(task);
                                }).catch(function (e) {
                                    log.error(e.message);
                                    var resultSummary = {
                                        passed: false,
                                        url: 'https://github.com/apocas/dockerode',
                                        message: 'docker execution error',
                                        details: e.message
                                    };

                                    task.results = resultSummary;
                                    return _promise2.default.reject(task);
                                }));

                            case 20:
                            case 'end':
                                return _context.stop();
                        }
                    }
                }, _callee, this);
            }));

            function executeTask(_x) {
                return _ref.apply(this, arguments);
            }

            return executeTask;
        }()

        /**
         * Get logs from running container
         */

    }, {
        key: 'containerLogs',
        value: function () {
            var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(executor, container) {
                var log;
                return _regenerator2.default.wrap(function _callee2$(_context2) {
                    while (1) {
                        switch (_context2.prev = _context2.next) {
                            case 0:
                                log = this.log;
                                return _context2.abrupt('return', new _promise2.default(function (resolve, reject) {
                                    var logBuffer = [];

                                    // create a single stream for stdin and stdout
                                    var logStream = new _stream2.default.PassThrough();
                                    logStream.on('data', function (chunk) {
                                        logBuffer.push(chunk.toString('utf8'));
                                        if (logBuffer.length % 50 === 0) {
                                            var lines = logBuffer.join('');
                                            if (lines.length * 4 > 250000) {
                                                lines.match(/.{1,50000}/g).forEach(function (line) {
                                                    log.info(line);
                                                });
                                            } else {
                                                log.info(lines);
                                            }
                                            executor.taskOutputTail += lines;
                                            logBuffer = [];
                                        }
                                    });

                                    return container.logs({
                                        follow: true,
                                        stdout: true,
                                        stderr: true
                                    }, function (err, stream) {
                                        if (err) {
                                            reject(log.error(err.message));
                                        }
                                        container.modem.demuxStream(stream, logStream, logStream);
                                        stream.on('end', function () {
                                            var lines = logBuffer.reverse().join('');
                                            log.info(lines);
                                            executor.taskOutputTail += lines;
                                            logBuffer = [];
                                            logStream.end('!stop!');

                                            log.info('container stopped, removing..');
                                            container.remove();

                                            resolve(executor.taskOutputTail);
                                        });
                                    });
                                }));

                            case 2:
                            case 'end':
                                return _context2.stop();
                        }
                    }
                }, _callee2, this);
            }));

            function containerLogs(_x2, _x3) {
                return _ref2.apply(this, arguments);
            }

            return containerLogs;
        }()
    }, {
        key: 'pullImage',
        value: function pullImage(docker, image) {
            var log = this.log;
            return new _promise2.default(function (resolve, reject) {
                if (process.env.GTM_DOCKER_ALLOW_PULL !== 'false') {
                    log.debug('pulling image ' + image + '..');
                    docker.pull(image, function (err, stream) {
                        if (err) {
                            return reject(err);
                        }

                        docker.modem.followProgress(stream, onFinished, onProgress);

                        function onFinished(err, output) {
                            if (err) {
                                return reject(err);
                            }
                            return resolve(output);
                        }

                        function onProgress(event) {
                            log.info(event);
                        }
                    });
                } else {
                    return resolve(true);
                }
            });
        }
    }, {
        key: 'validate',
        value: function validate(task, output) {
            var log = this.log;
            var valid = true;

            if (task.options.validator && task.options.validator.type === 'outputRegex') {
                log.info('validating docker output: outputRegex');

                try {
                    var regex = task.options.validator.regex;

                    log.debug('checking ' + regex + ' matches ' + output);

                    var pattern = new RegExp(regex);
                    if (!pattern.test(output)) {
                        log.error('docker stdout/stderr did not match regex ' + regex);
                        valid = false;
                    }
                } catch (e) {
                    log.error('docker outputRegex validation failed', e);
                    valid = false;
                }
            }

            return valid;
        }
    }, {
        key: 'taskOutputTail',
        set: function set(output) {
            this._taskOutputTail = output;
        },
        get: function get() {
            return this._taskOutputTail;
        }
    }]);
    return ExecutorDocker;
}(_Executor2.Executor);

_Executor2.Executor.register('Docker', ExecutorDocker);