azukiapp/azk

View on GitHub
src/docker/docker.js

Summary

Maintainability
A
2 hrs
Test Coverage
import { config, _, log, lazy_require } from 'azk';
import { promisifyClass } from 'azk/utils/promises';
import Utils from 'azk/utils';
import { default as tracker } from 'azk/utils/tracker';

// Composer
import { pull as pull_func  } from 'azk/docker/pull';
import { run as run_func   } from 'azk/docker/run';
import { build as build_func } from 'azk/docker/build';

var lazy = lazy_require({
  parseRepositoryTag: ['dockerode/lib/util'],
  uuid              : 'node-uuid',
});

export class Image extends promisifyClass('dockerode/lib/image') {
  static parseRepositoryTag(...args) {
    return lazy.parseRepositoryTag(...args);
  }
}

export class Container extends promisifyClass('dockerode/lib/container') {
  get Id() {
    return this.id;
  }

  inspect(...args) {
    return super.inspect(...args).then((data) => {
      // setup container data
      data.Annotations = Container.unserializeAnnotations(data.Name);
      data.NetworkSettings = Container.parsePortsFromNetwork(data.NetworkSettings);
      return data;
    });
  }

  stop(...args) {
    return super.stop(...args).then((data) => {
      return this._track(data, 'stop');
    });
  }

  remove(...args) {
    return super.remove(...args).then((data) => {
      return this._track(data, 'remove');
    });
  }

  kill(...args) {
    return super.kill(...args).then((data) => {
      return this._track(data, 'kill');
    });
  }

  _track(data, action) {
    var event_data = { event_type: action, id: this.Id };
    return tracker.sendEvent("container", event_data).then(() => data, (err) => {
      log.warn(err);
      return data;
    });
  }

  static parsePorts(ports) {
    return _.reduce(ports, (ports, port) => {
      ports[port.PrivatePort] = {
        name: port.PrivatePort,
        protocol: port.Type,
        gateway: port.IP,
        port: port.PublicPort,
      };
      return ports;
    }, {});
  }

  static parsePortsFromNetwork(network) {
    network.Access = {};
    _.each(network.Ports, (port, name) => {
      name = name.match(/(\d*)\/(.*)/);
      if (port) {
        network.Access[name[1]] = {
          name    : name[1],
          protocol: name[2],
          gateway : network.Gateway,
          port    : port[0].HostPort,
        };
      }
    });
    return network;
  }

  static parseStatus(status) {
    var state = {
      ExitCode: 0,
      Paused:  (status.match(/^Up.*\(Paused\)$/)) ? true : false,
      Running: (status.match(/^Up/)) ? true : false
    };

    // Exited? Get return code
    if (status.match(/Exited/)) {
      state.ExitCode = parseInt(
        status.replace(/Exited \((.*)\).*/, "$1")
      );
    }

    return state;
  }

  // Serialize annotations to a container name format
  static serializeAnnotations(annotations = { azk: {} }) {
    var azk = annotations.azk;

    // Unique id generator
    if (!azk.uid) {
      azk.uid = lazy.uuid.v1().replace(/-/g, "").slice(0, 10);
    }

    // Mount string
    var map_result = (_.map(azk, (value, key) => {
      return key + "." + value;
    }));
    return [config('docker:namespace'), ...map_result].join("_");
  }

  // Unserialize annotations from a container name
  static unserializeAnnotations(name) {
    name = name.replace(/\/(.*)/, "$1");
    var data = name.split('_');
    return _.reduce(data, (annotations, values) => {
      var key_value = values.split(".");
      annotations.azk[key_value[0]] = key_value[1];
      return annotations;
    }, { azk: {} });
  }

  static envsFromAnnotations(annotations = { azk: {}}) {
    return _.reduce(annotations.azk, (envs, value, key) => {
      if (key == 'azk') {
        key = "env";
      }
      envs[`AZK_${key.toUpperCase()}`] = value;
      return envs;
    }, {});
  }
}

export class Docker extends promisifyClass('dockerode') {
  constructor(opts) {
    var info = opts.socketPath || `${opts.host}:${opts.port}`;
    log.info("Connecting to", info);
    log.debug("[docker] connection options:", opts);
    super(opts);

    this.c_regex = RegExp(`\/${Utils.escapeRegExp(config('docker:namespace'))}`);
  }

  getImage(name) {
    return new Image(this.modem, name);
  }

  getContainer(id) {
    return new Container(this.modem, id);
  }

  __findObj(obj) {
    return obj.inspect().then(
      (/*_data*/) => { return obj; },
      (err  ) => {
        if (err.statusCode == 404) {
          return null;
        }
        throw err;
      }
    );
  }

  azkListContainers(...args) {
    return this.listContainers(...args).then((containers) => {
      return _.reduce(containers, (result, container) => {
        let name = _.find(container.Names, (name) => name.match(this.c_regex));
        if (!_.isEmpty(name)) {
          container.Name = name;
          container.Annotations = Container.unserializeAnnotations(name);
          container.State = Container.parseStatus(container.Status);
          container.NetworkSettings = { Access: Container.parsePorts(container.Ports) };
          result.push(container);
        }
        return result;
      }, []);
    });
  }

  findImage(name) {
    return this.__findObj(this.getImage(name));
  }

  findContainer(id) {
    return this.__findObj(this.getContainer(id));
  }

  pull(...args) {
    return pull_func(this, ...args);
  }

  build(...args) {
    return build_func(this, ...args);
  }

  run(...args) {
    return run_func(this, Container, ...args);
  }
}