azukiapp/azk

View on GitHub
src/cmds/shell.js

Summary

Maintainability
B
5 hrs
Test Coverage
import { CliTrackerController } from 'azk/cli/cli_tracker_controller';
import { Helpers } from 'azk/cli/helpers';
import { _, config, lazy_require } from 'azk';
import { defer, asyncUnsubscribe } from 'azk/utils/promises';
import { subscribe } from 'azk/utils/postal';

var lazy = lazy_require({
  Manifest: ['azk/manifest'],
  docker  : ['azk/docker', 'default'],
});

export default class Shell extends CliTrackerController {
  index() {
    var { options } = this.normalized_params;
    var args = this.normalized_params.arguments;

    var _subscription = subscribe('docker.pull.status', (data) => {
      this.view('image_pull').render(data);
    });

    return asyncUnsubscribe(this, _subscription, function* () {
      var dir = this.cwd;
      var manifest, system;

      yield Helpers.requireAgent(this.ui);

      if (options.image) {
        // Arbitrary image
        manifest = lazy.Manifest.makeFake(dir, options.image);
        system   = manifest.systemDefault;
      } else {
        manifest = new lazy.Manifest(dir, true);
        Helpers.manifestValidate(this.ui, manifest);

        system   = manifest.systemDefault;
        if (args.system) {
          system = manifest.system(args.system, true);
        }
      }

      // Use or not tty?
      var shell_args = args['shell-args'];
      var tty_default = options.tty || (_.isEmpty(options.command) && _.isEmpty(shell_args));
      var tty = (options['no-tty']) ? (options.tty || false) : tty_default;

      var stdin = this.ui.stdin();
      stdin.custom_pipe = () => { };

      var run_options = {
        interactive: tty,
        pull   : this.ui.stdout(),
        stdout : this.ui.stdout(),
        stderr : this.ui.stderr(),
        stdin  : stdin,
        workdir: options.cwd || null,
      };

      // Support extra envs, ports and mount volumes
      run_options.envs       = this._parse_option(options.env  , /.+=.+/, '=', 'invalid_env');
      run_options.shell_term = process.env.TERM;
      run_options.mounts     = this._parse_option(options.mount, /.+:.+:?.*/, ':', 'invalid_mount', 1, (opts) => {
        return { type: (opts[2] ? opts[1] : 'path'), value: (opts[2] ? opts[2] : opts[0]) };
      });

      // Remove container before run
      var is_remove = !options['no-remove'] ? config("docker:remove_container") : !options['no-remove'];
      run_options = _.merge(run_options, {
        shell          : options.shell,
        shell_args     : shell_args,
        command        : options.command,
        build_force    : options.rebuild || false,
        provision_force: (options.rebuild ? true : options.reprovision) || false,
        remove         : is_remove,
      });

      var result = defer((resolver, reject) => {
        var escape = (key, container, next) => {
          if (key === ".") {
            process.nextTick(() => {
              lazy.docker.getContainer(container).stop({ t: 5000 }).catch(reject);
            });
            return true;
          } else if (key === "?") {
            this.ok("coming soon...");
            process.nextTick(() => next());
            return true;
          }
          return false;
        };

        var _subscription_run = subscribe('docker.run.status', (data) => {
          this._escapeAndPullProgress(escape, system,
            !options.silent, options.verbose, options.stdout)(data);
        });

        system
          .runShell(run_options)
          .then(function (result) {
            _subscription_run.unsubscribe();
            return result;
          })
          .then(resolver, reject)
          .catch(function (err) {
            _subscription_run.unsubscribe();
            throw err;
          });
      });

      result = yield result.catch((error) => {
        return this.parseError(error);
      });

      return result.code;
    });
  }

  parseError(error) {
    if (error.statusCode) {
      if (error.statusCode === 404 && error.reason === "no such container") {
        this.ui.fail("commands.shell.ended.removed");
        return { code: 127 };
      }
    } else if (error.code === 'ECONNRESET') {
      this.ui.fail("commands.shell.ended.docker_end");
      return { code: 127 };
    } else if (error.code === 'ECONNREFUSED') {
      this.ui.fail("commands.shell.ended.docker_not_found");
      return { code: 127 };
    }
    throw error;
  }

  _escapeAndPullProgress(escape, system, show_logs, verbose) {
    return (event) => {
      var escapeCapture   = Helpers.escapeCapture(escape);

      // show verbose output
      if (verbose && event.stream) {
        this.ui.stdout().write('  ' + event.stream);
      }

      if (event.type === "stdin_pipe") {
        escapeCapture(event);
      } else if (show_logs) {
        if (show_logs && event.type === "pull_msg") {
          this.view('image_pull').render(event);
        } else if (event.type === "action") {
          var keys = ["commands", "scale"];
          var actions = ["pull_image", "build_image"];

          if (actions.indexOf(event.action) > -1) {
            var data = { image: system.image.name };
            this.ui.ok([...keys].concat(event.action), data);
          }
        }
      }
    };
  }

  _parse_option(option, regex, split, fail, key_index = 0, format = null) {
    var result = {};
    for (var j = 0; j < option.length; j++) {
      var opt = option[j];
      if (opt.match(regex)) {
        opt = opt.split(split);
        result[opt[key_index]] = format ? format(opt) : opt[1];
      } else {
        this.ui.fail('commands.shell.' + fail, { value: opt });
        return 1;
      }
    }
    return result;
  }
}