require 'json' module Jarl module Docker INSECURE_KEY_PATH = '/home/vagrant/.ssh/insecure_key' # Returns true if Docker is installed # def self.installed? !`which docker`.empty? end # Returns available docker images # def self.images @docker_images ||= Pathname.glob(Pathname.new(DOCKER_IMAGES_PATH).join('*/')).sort.map do |path| Image.new(path.basename, path) end end # Returns a list of used images # def self.images_used used_images_list = File.readlines(USED_IMAGES_FILE) rescue [''] used_images_list.map!(&:chomp) @docker_images_used ||= images.select { |di| used_images_list.include?(di.name) } end # Returns a list of unused images # def self.images_unused images - images_used end # Adds image to a list of used images # def self.images_used_add(name) list = images_used.map(&:name) list << name File.open(USED_IMAGES_FILE, 'w') do |f| f.puts list.join("\n") end reload! end # Removes image from a list of used images # def self.images_used_remove(name) list = images_used.map(&:name).select { |n| n != name } File.open(USED_IMAGES_FILE, 'w') do |f| f.puts list.join("\n") end reload! end # Returns a list of running docker containers # def self.containers_running return @docker_containers_running if @docker_containers_running container_ids = `docker ps -q`.split("\n") @docker_containers_running = container_ids.map do |id| Container.new id end @docker_containers_running end # Reloads lists of images and containers # def self.reload! @docker_images = nil @docker_images_used = nil @docker_containers_running = nil end # Cleans unused images # def self.clean_images sh "docker rmi $(docker images -a | grep '^' | awk '{print $3}') >/dev/null 2>&1" rescue RuntimeError => e # omit conflicting images error reports raise unless e.to_s =~ /Command failed/ end # Cleans stopped containers # def self.clean_containers sh "docker rm $(docker ps -a | grep 'Exited' | awk '{print $1}')" rescue RuntimeError => e # omit conflicting images error reports raise unless e.to_s =~ /Command failed/ end # Start container defined by params # def self.start(params) container_name = params[:name] Docker::Container.clean_containers(container_name) opts = [] opts << params[:volumes].map do |v| "-v #{v}" end.join(' ') opts << params[:ports].map do |p| "-p #{p}" end.join(' ') opts << params[:environment].map do |k, v| "-e #{k}=#{v}" end.join(' ') docker_cmd = "docker run -d #{opts.join(' ')} " \ " --hostname #{params[:hostname]} " \ " --name #{container_name} #{params[:image]} #{params[:command]}" # puts docker_cmd sh docker_cmd end # Execute container in foreground, defined by params # def self.execute(params) container_name = "#{params[:name]}.execute" Docker::Container.clean_containers(container_name) opts = [] opts << params[:volumes].map do |v| "-v #{v}" end.join(' ') opts << params[:ports].map do |p| "-p #{p}" end.join(' ') opts << params[:environment].map do |k, v| "-e #{k}=#{v}" end.join(' ') docker_cmd = "docker run -t -i --rm #{opts.join(' ')} " \ " --hostname #{params[:hostname]} " \ " --name #{container_name} #{params[:image]} #{params[:command]}" # puts docker_cmd sh docker_cmd end # Image # class Image attr_accessor :name, :path def initialize(name, path) @name = name.to_s @path = path end def prebuilt? self.class.prebuilt_image_names.include?(name) end def running? !!running_container end def running_container Docker.containers_running.select { |c| c.name == name }.first end def build! sh "docker build -t #{name} #{path}" end def pull! sh "docker pull #{name}" end def start! Container.clean_containers(name) run_flags = "--name=#{name} --hostname=#{name} -d" sh "docker run #{run_flags} #{name}" end def stop! running_container.stop! end def open_ssh_session! running_container.open_ssh_session! end def use! build! unless prebuilt? Docker.images_used_add(name) end def unuse! Docker.images_used_remove(name) end def used? Docker.images_used.map(&:name).include?(name) end def self.prebuilt_image_names return @prebuilt_image_names if @prebuilt_image_names di = `docker images` @prebuilt_image_names = di.split("\n").map do |line| m = line.match(/^(\S*)\s*/) m[1] end @prebuilt_image_names end end # class Image # Container # class Container attr_accessor :id, :long_id, :name, :image, :image_id, :ip, :ps, :ports, :params def initialize(id) @id = id @ps = `docker ps | grep #{id}` container_inspect = `docker inspect #{id}` @params = JSON.parse(container_inspect).first @long_id = params['Id'] @name = params['Name'].gsub('/', '') @image = params['Config']['Image'] @image_id = params['Image'] @ip = params['NetworkSettings']['IPAddress'] port_maps = params['NetworkSettings']['Ports'] @ports = port_maps.keys.map do |port| { from: port, to: port_maps[port] } end end def open_ssh_session!(params = {}, command = nil) ssh_flags = ['-oStrictHostKeyChecking=no'] if params['ssh_identity'] ssh_flags << "-i #{params['ssh_identity']}" if params['ssh_identity'].is_a?(String) if params['ssh_identity'].is_a?(Array) ssh_flags << params['ssh_identity'].map { |i| "-i #{i}" } end end ssh_user = params['ssh_user'] ? "#{params['ssh_user']}@" : '' ssh_cmd = command ? "-C \"#{command.join(' ')}\"" : '' sh "ssh #{ssh_flags.join(' ')} #{ssh_user}#{ip} #{ssh_cmd}" end def stop! sh "docker stop #{name}" clean! end def clean! self.class.clean_containers(name) end def uptime params['State'] && params['State']['StartedAt'] && Time.now.utc - DateTime.parse(params['State']['StartedAt']).to_time.utc end def self.clean_containers(*names) names.each do |name| begin sh "docker rm #{name} >/dev/null 2>&1" rescue RuntimeError => e # omit reports on missing container raise unless e.to_s =~ /Command failed/ end end end end # class ContainerInfo end # module Docker end # module Jarl