lib/spoon.rb in docker-spoon-0.7.0 vs lib/spoon.rb in docker-spoon-0.8.0

- old
+ new

@@ -10,15 +10,10 @@ include Methadone::SH version(Spoon::VERSION) main do |instance| - # Read config file & set options - if File.exists?(options[:config]) - eval(File.open(options[:config]).read) - end - D options.inspect if options[:list] instance_list elsif options["list-images"] image_list @@ -43,33 +38,53 @@ on("-d", "--destroy NAME", "Destroy spoon instance with NAME") on("-b", "--build", "Build image from Dockerfile using name passed to --image") on("-n", "--network NAME", "Display exposed ports using name passed to NAME") # Configurables - options[:builddir] = '.' + options[:config] ||= "#{ENV['HOME']}/.spoonrc" + on("-c FILE", "--config", "Config file to use for spoon options") + + # Read config file & set options + if File.exists?(options[:config]) + eval(File.open(options[:config]).read) + end + + options[:builddir] ||= '.' on("--builddir DIR", "Directory containing Dockerfile") on("--pre-build-commands", "List of commands to run locally before building image") # These are config only options - options[:copy_on_create] = [] - options[:add_authorized_keys] = false - options[:url] = Docker.url + options[:copy_on_create] ||= [] + options[:run_on_create] ||= [] + options[:add_authorized_keys] ||= false + options[:url] ||= Docker.url on("-u", "--url URL", "Docker url to connect to") on("-L", "--list-images", "List available spoon images") - options[:image] = "spoon-pairing" + options[:image] ||= "spoon-pairing" on("-i", "--image NAME", "Use image for spoon instance") - options[:prefix] = 'spoon-' + options[:prefix] ||= 'spoon-' on("-p", "--prefix PREFIX", "Prefix for container names") - options[:command] = '' - options[:config] = "#{ENV['HOME']}/.spoonrc" - on("-c FILE", "--config", "Config file to use for spoon options") + options[:command] ||= '' + on("-f", "--force", "Skip any confirmations") on("--debug", "Enable debug") + on("--debugssh", "Enable SSH debugging") + on("-P PORT", "--portforwards", "Forward PORT over ssh (must be > 1023)") arg(:instance, :optional, "Spoon instance to connect to") use_log_level_option + def self.confirm_delete?(name) + if options[:force] + return true + else + print "Are you sure you want to delete #{name}? (y/n) " + answer = $stdin.gets.chomp.downcase + return answer == "y" + end + end + def self.apply_prefix(name) "#{options[:prefix]}#{name}" end def self.remove_prefix(name) @@ -127,10 +142,11 @@ if not instance_exists? name puts "The `#{name}` container doesn't exist, creating..." instance_create(name) instance_copy_authorized_keys(name, options[:add_authorized_keys]) instance_copy_files(name) + instance_run_actions(name) end container = get_container(name) unless is_running?(container) instance_start(container) @@ -141,12 +157,15 @@ end def self.instance_list docker_url puts "List of available spoon containers:" - container_list = get_all_containers.select { |c| c.info["Names"].first.to_s.start_with? "/#{options[:prefix]}" } - .sort { |c1, c2| c1.info["Names"].first.to_s <=> c2.info["Names"].first.to_s } + container_list = get_spoon_containers + if container_list.empty? + puts "No spoon containers running at #{options[:url]}" + exit + end max_width_container_name = remove_prefix(container_list.max_by {|c| c.info["Names"].first.to_s.length }.info["Names"].first.to_s) max_name_width = max_width_container_name.length container_list.each do |container| name = container.info["Names"].first.to_s running = is_running?(container) ? Rainbow("Running").green : Rainbow("Stopped").red @@ -198,65 +217,87 @@ def self.instance_destroy(name) docker_url container = get_container(name) if container - puts "Destroying #{name}" - begin - container.kill - rescue - puts "Failed to kill container #{container.id}" - end + if confirm_delete?(name) + puts "Destroying #{name}" + begin + container.kill + rescue + puts "Failed to kill container #{container.id}" + end - container.wait(10) + container.wait(10) - begin - container.delete(:force => true) - rescue - puts "Failed to remove container #{container.id}" + begin + container.delete(:force => true) + rescue + puts "Failed to remove container #{container.id}" + end + puts "Done!" + else + puts "Delete aborted.. #{name} lives to pair another day." end - puts "Done!" else puts "No container named: #{name}" end end def self.instance_exists?(name) get_container(name) end - def self.instance_ssh(name, command='') + def self.get_port_forwards(forwards = "") + if options[:portforwards] + options[:portforwards].split.each do |port| + (lport,rport) = port.split(':') + forwards << "-L #{lport}:127.0.0.1:#{rport || lport} " + end + end + return forwards + end + + def self.instance_ssh(name, command='', exec=true) container = get_container(name) + forwards = get_port_forwards + D "Got forwards: #{forwards}" host = URI.parse(options[:url]).host if container ssh_command = "\"#{command}\"" if not command.empty? ssh_port = get_port('22', container) puts "Waiting for #{name}:#{ssh_port}..." until host_available?(host, ssh_port) - exec("ssh -t -o StrictHostKeyChecking=no -p #{ssh_port} pairing@#{host} #{ssh_command}") + ssh_options = "-t -o StrictHostKeyChecking=no -p #{ssh_port} #{forwards} " + ssh_options << "-v " if options[:debugssh] + ssh_cmd = "ssh #{ssh_options} pairing@#{host} #{ssh_command}" + D "SSH CMD: #{ssh_cmd}" + if exec + exec(ssh_cmd) + else + system(ssh_cmd) + end else puts "No container named: #{container.inspect}" end end def self.instance_copy_authorized_keys(name, keyfile) + D "Setting up authorized_keys file" + # We sleep this once to cope w/ slow starting ssh daemon on create + sleep 1 if keyfile - container = get_container(name) - host = URI.parse(options[:url]).host - key = File.read("#{ENV['HOME']}/.ssh/#{keyfile}") - if container - ssh_port = get_port('22', container) - puts "Waiting for #{name}:#{ssh_port}..." until host_available?(host, ssh_port) - system("ssh -t -o StrictHostKeyChecking=no -p #{ssh_port} pairing@#{host} \"mkdir -p .ssh && chmod 700 .ssh && echo '#{key}' >> ~/.ssh/authorized_keys\"") - else - puts "No container named: #{container.inspect}" - end + full_keyfile = "#{ENV['HOME']}/.ssh/#{keyfile}" + key = File.read(full_keyfile).chop + D "Read keyfile `#{full_keyfile}` with contents:\n#{key}" + cmd = "mkdir -p .ssh ; chmod 700 .ssh ; echo '#{key}' >> .ssh/authorized_keys" + instance_ssh(name, cmd, false) end end def self.instance_copy_files(name) options[:copy_on_create].each do |file| - puts "Copying file #{file}" + D "Copying file #{file}" container = get_container(name) host = URI.parse(options[:url]).host if container ssh_port = get_port('22', container) puts "Waiting for #{name}:#{ssh_port}..." until host_available?(host, ssh_port) @@ -265,14 +306,30 @@ puts "No container named: #{container.inspect}" end end end + def self.instance_run_actions(name) + options[:run_on_create].each do |action| + puts "Running command: #{action}" + instance_ssh(name, action, false) + end + end + def self.get_all_containers Docker::Container.all(:all => true) end + def self.get_spoon_containers + container_list = get_all_containers.select { |c| c.info["Names"].first.to_s.start_with? "/#{options[:prefix]}" } + unless container_list.empty? + return container_list.sort { |c1, c2| c1.info["Names"].first.to_s <=> c2.info["Names"].first.to_s } + else + return container_list + end + end + def self.get_running_containers Docker::Container.all end def self.instance_start(container) @@ -330,7 +387,5 @@ end end go! end - -# option :debug, :type => :boolean, :default => true