<commit_before>fs            = require "fs"
child_process = require "child_process"
async         = require "async"
uuid          = require "node-uuid"
DepTree       = require "deptree"
read          = require "read"
Docker        = require "dockerode"

MultiplexStream = require "./multiplex_stream"

docker = new Docker socketPath: "/var/run/docker.sock"
logger = process.stdout
log    = (data) -> logger.write "#{data}\n"

module.exports =
class Decking
  constructor: (options) ->
    {@command, @args} = options

    @config = @loadConfig "./decking.json"

  processConfig: (config) ->
    for name, details of config.containers
      if typeof details is "string"
        details = config.containers[name] =
          image: details

      details.name = name

      details.dependencies = [] if not details.dependencies
      details.aliases = []

      for dependency,i in details.dependencies
        # it's nicer for rest of the app to work with dependencies and alises
        # as separate arrays
        [name, alias] = dependency.split ":"

        alias = name if not alias # if we didn't get dep:alias, assume dep:dep

        details.dependencies[i] = name
        details.aliases[i] = alias

    return config

  parseConfig: (data) -> JSON.parse data

  loadConfig: (file) ->
    #log "Loading package file..."
    @processConfig @parseConfig fs.readFileSync file

  commands:
    help: (done) ->
      log ""
      help =
      """
      Usage: decking COMMAND [arg...]

      Commands:
        build   build an image or pass 'all' to build all
        create   create a cluster
        start    start a cluster of containers
        stop     stop a cluster
        restart  restart a cluster
        status   check the status of a cluster's containers
        attach   attach to all running containers in a cluster
      """

      log help

      done null

    build: (done) ->
      [image] = @args

      if image is "all"
        images = (key for key,val of @config.images)

        async.eachSeries images,
        (image, callback) =>
          @build image, callback
        , done
      else
        @build image, done

    create: (done) -> @_run "create", done
    start: (done) -> @_run "start", done
    stop: (done) -> @_run "stop", done
    restart: (done) -> @_run "restart", done
    status: (done) -> @_run "status", done
    attach: (done) -> @_run "attach", done

  _run: (cmd, done) ->
    [cluster] = @args
    target = getCluster @config, cluster
    this[cmd](target, done)

  start: (cluster, done) ->

    iterator = (details, callback) ->
      name = details.name
      container = docker.getContainer name
      isRunning container, (err, running) ->
        if not running
          logAction name, "starting..."
          container.start callback
        else
          logAction name, "skipping (already running)"
          callback null

    resolveOrder @config, cluster, (list) ->

      validateContainerPresence list, (err) ->
        return done err if err

        async.eachSeries list, iterator, done


  stop: (cluster, done) ->

    # @TODO reverse dependency order? shutdown process might
    # involve signalling to them (e.g. final write, disconnect)

    iterator = (details, callback) ->
      name = details.name
      container = docker.getContainer name
      isRunning container, (err, running) ->
        if running
          logAction name, "stopping..."
          container.stop callback
        else
          logAction name, "skipping (already stopped)"
          callback null

    resolveOrder @config, cluster, (list) ->

      validateContainerPresence list, (err) ->
        return done err if err

        async.eachSeries list, iterator, done

  restart: (cluster, done) ->
    iterator = (details, callback) ->
      name = details.name
      container = docker.getContainer name
      isRunning container, (err, running) ->
        if running
          logAction name, "restarting..."
          container.stop (err) ->
            container.start callback
        else
          logAction name, "starting..."
          container.start callback

    resolveOrder @config, cluster, (list) ->

      validateContainerPresence list, (err) ->
        return done err if err

        async.eachSeries list, iterator, done

  attach: (cluster, done) ->

    options =
      stream: true
      stdout: true
      stderr: true
      logs: true
      tty: false

    iterator = (details, callback) ->
      name = details.name
      container = docker.getContainer name
      container.attach options, (err, stream) ->
        new MultiplexStream container, stream, padName(name, "(", ")")

        callback null

    resolveOrder @config, cluster, (list) ->

      validateContainerPresence list, (err) ->
        return done err if err

        async.eachSeries list, iterator, done
<commit_msg>somewhat hack-ridden but effective re-attach logic when containers stop for < 10 seconds<commit_after>fs            = require "fs"
child_process = require "child_process"
async         = require "async"
uuid          = require "node-uuid"
DepTree       = require "deptree"
read          = require "read"
Docker        = require "dockerode"

MultiplexStream = require "./multiplex_stream"

docker = new Docker socketPath: "/var/run/docker.sock"
logger = process.stdout
log    = (data) -> logger.write "#{data}\n"

module.exports =
class Decking
  constructor: (options) ->
    {@command, @args} = options

    @config = @loadConfig "./decking.json"

  processConfig: (config) ->
    for name, details of config.containers
      if typeof details is "string"
        details = config.containers[name] =
          image: details

      details.name = name

      details.dependencies = [] if not details.dependencies
      details.aliases = []

      for dependency,i in details.dependencies
        # it's nicer for rest of the app to work with dependencies and alises
        # as separate arrays
        [name, alias] = dependency.split ":"

        alias = name if not alias # if we didn't get dep:alias, assume dep:dep

        details.dependencies[i] = name
        details.aliases[i] = alias

    return config

  parseConfig: (data) -> JSON.parse data

  loadConfig: (file) ->
    #log "Loading package file..."
    @processConfig @parseConfig fs.readFileSync file

  commands:
    help: (done) ->
      log ""
      help =
      """
      Usage: decking COMMAND [arg...]

      Commands:
        build   build an image or pass 'all' to build all
        create   create a cluster
        start    start a cluster of containers
        stop     stop a cluster
        restart  restart a cluster
        status   check the status of a cluster's containers
        attach   attach to all running containers in a cluster
      """

      log help

      done null

    build: (done) ->
      [image] = @args

      if image is "all"
        images = (key for key,val of @config.images)

        async.eachSeries images,
        (image, callback) =>
          @build image, callback
        , done
      else
        @build image, done

    create: (done) -> @_run "create", done
    start: (done) -> @_run "start", done
    stop: (done) -> @_run "stop", done
    restart: (done) -> @_run "restart", done
    status: (done) -> @_run "status", done
    attach: (done) -> @_run "attach", done

  _run: (cmd, done) ->
    [cluster] = @args
    target = getCluster @config, cluster
    this[cmd](target, done)

  start: (cluster, done) ->

    iterator = (details, callback) ->
      name = details.name
      container = docker.getContainer name
      isRunning container, (err, running) ->
        if not running
          logAction name, "starting..."
          container.start callback
        else
          logAction name, "skipping (already running)"
          callback null

    resolveOrder @config, cluster, (list) ->

      validateContainerPresence list, (err) ->
        return done err if err

        async.eachSeries list, iterator, done


  stop: (cluster, done) ->

    # @TODO reverse dependency order? shutdown process might
    # involve signalling to them (e.g. final write, disconnect)

    iterator = (details, callback) ->
      name = details.name
      container = docker.getContainer name
      isRunning container, (err, running) ->
        if running
          logAction name, "stopping..."
          container.stop callback
        else
          logAction name, "skipping (already stopped)"
          callback null

    resolveOrder @config, cluster, (list) ->

      validateContainerPresence list, (err) ->
        return done err if err

        async.eachSeries list, iterator, done

  restart: (cluster, done) ->
    iterator = (details, callback) ->
      name = details.name
      container = docker.getContainer name
      isRunning container, (err, running) ->
        if running
          logAction name, "restarting..."
          container.stop (err) ->
            container.start callback
        else
          logAction name, "starting..."
          container.start callback

    resolveOrder @config, cluster, (list) ->

      validateContainerPresence list, (err) ->
        return done err if err

        async.eachSeries list, iterator, done

  attach: (cluster, done) ->


    timeout = 200

    reAttach = (name, container, attempts = 0) ->
      setTimeout ->
        isRunning container, (err, running) ->
          if running
            attach name, container, false, ->
              logAction name, "re-attached"
          else
            if attempts < 50
              reAttach name, container, attempts + 1
            else
              logAction name, "max re-attach attempts reached, bailing..."
      , timeout

    attach = (name, container, fetchLogs, callback) ->
      options =
        stream: true
        stdout: true
        stderr: true
        tty: false
        logs: fetchLogs

      container.attach options, (err, stream) ->
        new MultiplexStream container, stream, padName(name, "(", ")")

        stream.on "end", ->
          logAction name, "gone away, will try to re-attaching for 10 seconds..."
          reAttach name, container

        callback? err

    iterator = (details, callback) ->
      container = docker.getContainer details.name
      attach details.name, container, true, callback

    resolveOrder @config, cluster, (list) ->

      validateContainerPresence list, (err) ->
        return done err if err

        async.eachSeries list, iterator, done

{'fraction_non_alphanumeric': 0.09978163865945125, 'fraction_numerical': 0.0010443368461027249, 'mean_word_length': 2.123962040332147, 'pattern_counts': {'":': 2, '<': 5, '<?xml version=': 0, '>': 81, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2312971', 'n_tokens_mistral': 2922, 'n_tokens_neox': 2767, 'n_words': 980}