require_relative 'hoosegow/docker' require_relative 'hoosegow/exceptions' require_relative 'hoosegow/image_bundle' require_relative 'hoosegow/protocol' class Hoosegow # Public: Initialize a Hoosegow instance. # # options - # :no_proxy - Development mode. Use this if you don't want to # setup Docker on your development instance, but # still need to test rendering files. This is how # Hoosegow runs inside the Docker container. # :inmate_dir - Dependency directory to be coppied to the hoosegow # image. This should include a file called # `inmate.rb` that defines a Hoosegow::Inmate module. # :image_name - The name of the Docker image to use. If this isn't # specified, we will infer the image name from the # hash the files present. # :ruby_version - The Ruby version to install in the Docker # container (Default RUBY_VERSION). # :socket - Path to Unix socket where Docker daemon is # running. (optional. defaults to # "/var/run/docker.sock") # :host - IP or hostname where Docker daemon is running. # Don't set this if Docker is listening locally on a # Unix socket. # :port - TCP port where Docker daemon is running. Don't set # this if Docker is listening locally on a Unix # socket. def initialize(options = {}) options = options.dup @no_proxy = options.delete(:no_proxy) @inmate_dir = options.delete(:inmate_dir) || '/hoosegow/inmate' @image_name = options.delete(:image_name) @ruby_version = options.delete(:ruby_version) || RUBY_VERSION @docker_options = options load_inmate_methods # Don't want to have to require these in the container. unless no_proxy? require 'tmpdir' require 'fileutils' require 'open3' require 'digest' end end # Public: The thing that defines which files go into the docker image tarball. def image_bundle @image_bundle ||= Hoosegow::ImageBundle.new.tap do |image| image.add(File.expand_path('../../*', __FILE__), :ignore_hidden => true) image.add(File.join(@inmate_dir, "*"), :prefix => 'inmate') image.ruby_version = @ruby_version end end # Public: Proxies method call to instance running in a Docker container. # # name - The method to call in the Docker instance. # args - Arguments that should be passed to the Docker instance method. # block - A block that can be yielded to. # # See docs/dispatch.md for more information. # # Returns the return value from the Docker instance method. def proxy_send(name, args, &block) proxy = Hoosegow::Protocol::Proxy.new( :stdout => $stdout, :stderr => $stderr, :yield => block ) encoded_send = proxy.encode_send(name, args) docker.run_container(image_name, encoded_send) do |type, msg| proxy.receive(type, msg) end proxy.return_value end # Public: Load inmate methods from #{inmate_dir}/inmate.rb and hook them up # to proxied to the Docker container. If we are in the container, the methods # are loaded and setup to be called directly. # # Returns nothing. Raises InmateImportError if there is a problem. def load_inmate_methods inmate_file = File.join @inmate_dir, 'inmate.rb' unless File.exist?(inmate_file) raise Hoosegow::InmateImportError, "inmate file doesn't exist" end require inmate_file unless Hoosegow.const_defined?(:Inmate) && Hoosegow::Inmate.is_a?(Module) raise Hoosegow::InmateImportError, "inmate file doesn't define Hoosegow::Inmate" end if no_proxy? self.extend Hoosegow::Inmate else inmate_methods = Hoosegow::Inmate.instance_methods inmate_methods.each do |name| define_singleton_method name do |*args, &block| proxy_send name, args, &block end end end end # Public: We create/start a container after every run to reduce latency. This # needs to be called before the process ends to cleanup that remaining # container. # # Returns nothing. def cleanup docker.stop_container docker.delete_container end # Check if the Docker image exists. # # Returns true/false. def image_exists? docker.image_exist? image_name end # Public: Build a Docker image from the Dockerfile in the root directory of # the gem. # # Returns build output text. Raises ImageBuildError if there is a problem. def build_image(&block) docker.build_image image_name, image_bundle.tarball, &block end # Private: The name of the docker image to use. If not specified manually, # this will be infered from the hash of the tarball. # # Returns string image name. def image_name @image_name || image_bundle.image_name end private # Private: Get or create a Docker instance. # # Returns an Docker instance. def docker @docker ||= Docker.new @docker_options end # Returns true if we are in the Docker instance or are in develpment mode. # # Returns true/false. def no_proxy? !!@no_proxy end end