Skip to content

Instantly share code, notes, and snippets.

@minimum2scp
Last active August 29, 2015 14:06
Show Gist options
  • Save minimum2scp/1897c669b83b412a1ce2 to your computer and use it in GitHub Desktop.
Save minimum2scp/1897c669b83b412a1ce2 to your computer and use it in GitHub Desktop.
docker ps や docker images の結果を peco で絞りこんで ssh, scp したり docker kill, stop, rm, rmi とかできます。
## for zsh user
docker () {
local -a peco
zparseopts -D -E -peco=peco
if [ $#peco -eq 1 -a "${peco[1]}" = "--peco" ]
then
peco-docker "$@"
else
command docker "$@"
fi
}
#! /usr/bin/env ruby
require 'optparse'
require 'tempfile'
begin
require 'term/ansicolor'
rescue LoadError => e
raise e
warn "term-ansicolor gem not found, disabled color output\n"
sleep 1
end
DOCKER_COMMANDS = {
:container => %w[attach commit cp diff inspect kill logs port pause restart rm start stop top unpause wait],
:image => %w[history rmi run save tag]
}
def select_container(all_flag:true, peco_opts:"", multi:true, header:false)
docker_ps_opts = all_flag ? "-a" : ""
docker_ps_output = `docker ps #{docker_ps_opts}`
max_width = docker_ps_output.lines.map(&:chomp).map(&:length).max
container_ids = docker_ps_output.lines[1..-1].map{|line| line.split(/\s{2,}/).first}
ip_address_mapping = Hash[ `docker inspect -f '{{ printf "%.12s" .Id }} {{ .NetworkSettings.IPAddress }}' #{container_ids.join(' ')}`.lines.map(&:chomp).map(&:split) ]
selected = IO.popen("peco #{peco_opts}", "r+"){|peco|
docker_ps_output.lines.each do |line|
container_id, image, command, created, status, ports, names = line.split(/\s{2,}/)
case container_id
when 'CONTAINER ID'
peco.puts '%-15s %s' % ['IP ADDRESS', line]
else
if status =~ /\Aup/i
peco.puts '%-15s %s' % [ip_address_mapping[container_id], line]
else
peco.puts '%-15s %s' % ['(none)', line]
end
end
end
peco.close_write
peco.read
}
selected = selected.lines.map(&:chomp).map{|line|
ip_address, container_id, image, command, created, status, ports, names = line.split(/\s{2,}/)
{
:line => line,
:ip_address => ip_address,
:container_id => container_id,
:image => image,
:created => created,
:status => status,
:ports => ports,
:names => names
}
}
if !header
selected = selected.select{|container_hash| container_hash[:container_id] != 'CONTAINER ID'}
end
if !multi && selected.size != 1
abort "select just only 1 container"
end
if block_given?
yield selected
else
return selected
end
end
def select_container_id(all_flag:true, multi:true, header:false, peco_opts:"")
select_container(all_flag:all_flag, multi:multi, header:header, peco_opts:peco_opts) do |container_hash_list|
container_hash_list.map{|container_hash| container_hash[:container_id]}
end
end
def select_container_ip(all_flag:true, multi:true, header:false, peco_opts:"")
select_container(all_flag:all_flag, multi:multi, header:header, peco_opts:peco_opts) do |container_hash_list|
container_hash_list.map{|container_hash| container_hash[:ip_address]}
end
end
def select_image_id(tree_flag:false, tag_flag:false, peco_opts:"")
docker_opts = tree_flag ? '--tree' : ''
selected = `docker images #{docker_opts} 2>/dev/null | peco #{peco_opts}`
if tree_flag
if tag_flag
selected.lines.map(&:chomp).map{|line| line =~ /Tags: (.+)$/ && $1.split(/, /)}.flatten.compact.select{|tag| tag != '<none>:<none>' }
else
selected.lines.map(&:chomp).map{|line| line.gsub(/^.+([0-9a-f]{12}) Virtual Size:.+$/, '\1') }
end
else
if tag_flag
selected.lines.map(&:chomp).map{|line| line.split[0..1].join(":") }.select{|tag| tag != '<none>:<none>' }
else
selected.lines.map(&:chomp).map{|line| line.split[2] }
end
end
end
def scp_command(argv, ssh_user: nil, placeholder: nil)
ip = select_container_ip(multi:false).first
remote = ssh_user ? ssh_user + '@' + ip : ip
if ! ARGV.any?{|a| a.include?(placeholder)}
warn "placeholder #{placeholder} is required for scp\n"
exit 1
end
scp_argv = argv_placeholder_replace(argv, opts[:placeholder], [remote])
"scp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no #{scp_argv.join(' ')}"
end
def ssh_command(argv, ssh_user: nil, placeholder: nil)
ip = select_container_ip(multi:false).first
remote = ssh_user ? ssh_user + '@' + ip : ip
ssh_argv = argv_placeholder_replace(argv, placeholder, [remote])
"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no #{ssh_argv.join(' ')}"
end
def ssh_copy_id_command(argv, ssh_user: nil, placeolder: nil)
ip = select_container_ip(multi:false).first
remote = ssh_user ? ssh_user + '@' + ip : ip
ssh_copy_id_argv = argv_placeholder_replace(argv, placeholder, [remote])
"ssh-copy-id -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no #{ssh_copy_id_argv.join(' ')}"
end
def diff_images(tree_flag: false, tag_flag: false, diff_images: :fileopts)
image1, image2 = select_image_id(tree_flag: tree_flag, tag_flag: tag_flag, peco_opts: "--prompt \"select 2 images>\"")
if !(image1 && image2)
puts "select 2 images for diff-images"
exit 1
end
cmd = case diff_images
when :package then "bash -c 'export LANG=C; export COLUMNS=120; dpkg -l'"
when :filename then "bash -c 'export LANG=C; find / | sort'"
end
tempfile1, tempfile2 = [image1, image2].map do |img|
t = Tempfile.new(["docker-diff.", ".out"])
t.close
run_cmd = "docker run --rm #{img} #{cmd} > #{t.path}"
yield run_cmd
t
end
diff_cmd = "diff -u #{tempfile1.path} #{tempfile2.path}"
yield diff_cmd
end
def docker_command(c: nil, argv: nil, type: nil, placeholder: nil, tree_flag: nil, tag_flag: nil)
case type
when :container
container_ids = select_container_id
docker_subcmd_argv = argv_placeholder_replace(argv, placeholder, container_ids)
when :image
image_ids = select_image_id(tree_flag: tree_flag, tag_flag: tag_flag)
docker_subcmd_argv = argv_placeholder_replace(argv, placeholder, image_ids)
end
"docker #{c} #{docker_subcmd_argv.join(' ')}"
end
def argv_placeholder_replace(argv, placeholder, replaces)
if argv.any?{|a| a.include?(placeholder)}
argv.map{|a| a.include?(placeholder) ? replaces.map{|r| a.gsub(placeholder, r)} : a}.flatten
else
argv + replaces
end
end
def invoke_command(cmd, use_exec:true, dry_run:false, interactive:false)
puts ->(c){
c ? (dry_run ? c.blue{c.bold{ cmd }} : c.green{c.bold{ cmd }}) : cmd
}[defined?(Term::ANSIColor) && Term::ANSIColor]
if interactive
print "run command? [Y/n] "
if $stdin.gets.chomp !~ /\A(|y|Y)\Z/
puts "aborted."
exit 0
end
end
if !dry_run
use_exec ? exec(cmd) : system(cmd)
end
end
##
## main
##
opts = {
:ssh_user => 'debian',
:tree => false,
:diff_images => :filename,
:dry_run => false,
:placeholder => '{}',
:interactive => true,
:tag => false,
}
ARGV.options do |q|
q.banner = "#{File.basename(__FILE__)} [options] [actions] -- [args_for_docker]\n"
actions = {
"c" => "alias of container",
"container" => "select container id from docker ps",
"i" => "alias of image",
"image" => "select image id from docker images",
"I" => "alias of ip",
"ip" => "select container ip from docker ps and docker inspect",
"scp" => "scp from/to container",
"ssh" => "ssh into container",
"ssh-copy-id" => "ssh-copy-id to container",
"d" => "alias of diff-images",
"diff-images" => "select 2 images and diff images",
}
DOCKER_COMMANDS[:container].each do |c|
actions[c] = "invoke \"docker #{c} [args_for_docker]\" with container id"
end
DOCKER_COMMANDS[:image].each do |c|
actions[c] = "invoke \"docker #{c} [args_for_docker]\" with image id"
end
q.banner << "[actions]\n"
actions.each do |k,v|
q.banner << " %-16s : %s\n" % [k,v]
end
q.banner << "\n"
q.banner << "[options]\n"
q.on("-i", "--[no-]interactive", TrueClass){ |arg|
opts[:interactive] = arg
}
q.on("-f", "--force", "same as --no-interactive"){
opts[:interactive] = false
}
q.on("-n", "--dry-run"){
opts[:dry_run] = true
}
q.on("-p", "--diff-packages", "diff-images with package" ){
opts[:diff_images] = :package
}
q.on("-q", "--place-holder=STR", "placeholder for conainer ids, image ids (default is #{opts[:placeholder]})"){ |arg|
opts[:placeholder] = arg
}
q.on("-t", "--tree", "use --tree option for docker images"){
opts[:tree] = true
}
q.on("-T", "--tag", "select tag instead of image id"){
opts[:tag] = true
}
q.on("-u", "--[no-]ssh-user=USER", "ssh username for \"#{File.basename(__FILE__)} ssh\" (default is #{opts[:ssh_user]})"){ |arg|
opts[:ssh_user] = arg
}
q.parse!
end
case c = ARGV.shift
when 'c', 'container'
puts select_container(header:true){ |container_hash_list| container_hash_list.map{|container_hash| container_hash[:line]}.join("\n") }
when 'i', 'image'
puts select_image_id(tree_flag: opts[:tree], tag_flag: opts[:tag]).join(" ")
when 'I', 'ip'
puts select_container_ip.join(" ")
when 'd', 'diff-images'
diff_images(tree_flag: opts[:tree], tag_flag: opts[:tag], diff_images: opts[:diff_images]) do |cmd|
invoke_command(cmd, use_exec:false, dry_run:opts[:dry_run], interactive:opts[:interactive])
end
when 'scp'
cmd = scp_command(ARGV, ssh_user: opts[:ssh_user], placeholder: opts[:placeholder])
invoke_command(cmd, use_exec:true, dry_run:opts[:dry_run], interactive:opts[:interactive])
when 'ssh'
cmd = ssh_command(ARGV, ssh_user: opts[:ssh_user], placeholder: opts[:placeholder])
invoke_command(cmd, use_exec:true, dry_run:opts[:dry_run], interactive:opts[:interactive])
when 'ssh-copy-id'
cmd = ssh_copy_id_command(ARGV, ssh_user: opts[:ssh_user], placeholder: opts[:placeholder])
invoke_command(cmd, use_exec:true, dry_run:opts[:dry_run], interactive:opts[:interactive])
when *DOCKER_COMMANDS[:container]
cmd = docker_command(c: c, argv: ARGV, type: :container, placeholder: opts[:placeholder], tree_flag: opts[:tree], tag_flag: opts[:tag])
invoke_command(cmd, use_exec:true, dry_run:opts[:dry_run], interactive:opts[:interactive])
when *DOCKER_COMMANDS[:image]
cmd = docker_command(c: c, argv: ARGV, type: :image, placeholder: opts[:placeholder], tree_flag: opts[:tree], tag_flag: opts[:tag])
invoke_command(cmd, use_exec:true, dry_run:opts[:dry_run], interactive:opts[:interactive])
else
warn "unknown command: #{c}"
puts ARGV.options
exit 1
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment