Runnable/docker-mock

View on GitHub
lib/models/image-store.js

Summary

Maintainability
B
5 hrs
Test Coverage
'use strict'

var util = require('util')
var utils = require('../utils')
var BaseStore = require('./base-store')
var NotFoundError = BaseStore.NotFoundError

var exists = require('101/exists')
var tar = require('tar')
var zlib = require('zlib')
var duplexEmitter = require('duplex-emitter')
var keypather = require('keypather')()
var Promise = require('bluebird')

module.exports = ImageStore

function ImageStore () {
  this._tags = {}
  this._imageHistory = {}
  BaseStore.call(this)
}
util.inherits(ImageStore, BaseStore)

ImageStore.prototype.findOneByName = function (name) {
  return Promise.resolve()
    .bind(this)
    .then(function () {
      var image = this._store[name]
      var id
      if (!image) {
        if (name.indexOf(':') === -1) { name += ':latest' }
        id = this._tags[name]
        if (!id) { throw new NotFoundError('Image not found') }
        image = this._store[id]
        if (!image) { throw new NotFoundError('Image not found') }
        image.RepoTags = [this._tags[name]]
      }
      return image
    })
}

ImageStore.prototype.deleteByName = function (name) {
  return this.findOneByName(name)
    .bind(this)
    .then(function (image) {
      var id = image.Id
      var name
      // FIXME: this condition isn't tested by unit tests :(
      if (image.RepoTags) {
        name = image.RepoTags[0]
      }
      delete this._store[id]
      if (name) {
        delete this._tags[name]
      }
      return true
    })
}

ImageStore.prototype.listImages = function () {
  return Promise.resolve()
    .bind(this)
    .then(function () {
      return Object.keys(this._store).map(function (id) {
        // if we have full image info, we can return that instead
        // assume if container_config exist on image, it is full object
        if (this._store[id].container_config) {
          return this._store[id]
        }
        // fake return object if we do not have all info
        return {
          Id: id,
          Created: this._store[id].Created,
          RepoTags: Object.keys(this._tags).reduce(function (memo, tag) {
            if (this._tags[tag] === id) { memo.push(tag) }
            return memo
          }.bind(this), [])
        }
      }.bind(this))
    })
}

ImageStore.prototype.create = function (req, res, callback) {
  // don't promisify this, it's actually middleware atm.
  var id = utils.randomId()
  var from = req.query.fromImage
  var emitter = duplexEmitter(res)
  if (!from) {
    emitter.emit({ status: 'Downloading from http://' })
    emitter.emit({
      errorDetail: {
        message: 'Get http://: http: no Host in request URL'
      },
      error: 'Get http://: http: no Host in request URL'
    })
    return res.end()
  }
  this._store[id] = {
    Id: id,
    Created: parseInt(keypather.get(req, 'query.Created'), 10) ||
      Math.floor(Date.now() / 1000)
  }
  this._tags[from + ':' + (req.query.tag || 'latest')] = id
  emitter.emit({
    status: 'The image you are pulling has been verified',
    id: from
  })
  for (var i = 0; i < 100; i++) {
    emitter.emit({
      status: 'Pulling',
      progress: i + ' B/ 100 B',
      progressDetail: {
        current: i,
        total: 100
      }
    })
  }
  emitter.emit({
    status: 'Status: Image is up to date for' + from
  })
  callback()
}

ImageStore.prototype.commitContainer = function (container, query) {
  return Promise.resolve()
    .bind(this)
    .then(function () {
      var repo = query.repo
      var tag = query.tag || 'latest'
      var name = [ repo, tag ].filter(exists).join(':')
      var imageId = utils.randomId()

      this._tags[name] = imageId
      this._store[imageId] = {
        Id: imageId,
        Container: container.Id,
        RepoTags: [name]
      }
      return { Id: imageId }
    })
}

ImageStore.prototype.loadImage = function (data) {
  return Promise.resolve()
    .bind(this)
    .then(function () {
      this._store[data.Id] = data
      if (data.RepoTags.length) {
        data.RepoTags.forEach(function (tag) {
          this._tags[tag] = data.Id
        }.bind(this))
      }
      return true
    })
}

ImageStore.prototype.getHistory = function (id) {
  var history = this._imageHistory[id]
  return Promise.resolve(history)
}

ImageStore.prototype.build = function (req) {
  var self = this
  return new Promise(function (resolve, reject) {
    var foundDockerFile = false
    // TODO query.t is required
    var repoSplit = req.query.t.split(':')
    var repo = repoSplit[0]
    var tag = repoSplit[1] || 'latest'
    var name = repo + ':' + tag
    var intentionalFail = req.query.fail || false
    // for a little extra flexability, we'll support gzip
    if (req.headers['content-type'] === 'application/x-gzip') {
      req = req.pipe(zlib.createGunzip())
    }
    var dockerfile = ''
    req.pipe(tar.Parse()) // eslint-disable-line new-cap
      .on('entry', function (entry) {
        if (entry.props.path === './Dockerfile') { foundDockerFile = true }
        if (entry.props.path === 'Dockerfile') {
          foundDockerFile = true
          entry.on('data', function (d) {
            dockerfile += d.toString()
          })
        }
      })
      .on('end', function () {
        if (intentionalFail) {
          return resError(500, 'Intentional failure.')
        } else if (!foundDockerFile) {
          return resError(500, 'A Dockerfile is required')
        }
        var id = utils.randomId()
        var now = Date.now()
        self._store[id] = {
          Id: id,
          Created: Math.floor(now / 1000)
        }
        self._tags[name] = id
        self._imageHistory[id] = dockerfile
          .split('\n')
          .reverse()
          .map(function (line, index) {
            return {
              Id: utils.randomId(),
              Created: now - (100000 * index),
              CreatedBy: line,
              Size: line.length,
              Tags: null
            }
          })
        resolve({ stream: 'Successfully built ' + id })
      })

    function resError (code, message) {
      var error = new Error('Server Error - ' + message, {
        code: code,
        message: message
      })
      reject(error)
    }
  })
}