248 lines
8.3 KiB
Ruby
248 lines
8.3 KiB
Ruby
require 'pmap'
|
|
|
|
class PVE::Cli
|
|
|
|
def exc2warn dret, exc = Exception
|
|
yield
|
|
rescue exc
|
|
warn "#{$!} (#{$!.class})"
|
|
dret
|
|
end
|
|
|
|
using IPAddress::ToSWithNetmaskForNetworks
|
|
|
|
def cli_base
|
|
cli.cmd :list, "List CT/VM-IDs", aliases: ['ls'], &lambda {|target=nil|
|
|
connect
|
|
nodes = Proxmox::Node.all
|
|
nodes.
|
|
flat_map {|n| [ n.method(:lxc), n.method(:qemu) ] }.
|
|
flat_pmap {|m| m.call.map {|c| c.vmid.to_i } }.
|
|
sort.
|
|
each {|c| puts c }
|
|
}
|
|
|
|
cli.cmd( :status, "Lists Nodes/VMs/CTs with status", &lambda {|target=nil, sort:, node: nil, status: nil, tags: nil|
|
|
hosting_table target: target, status: status, sort: sort, tags: tags do |push|
|
|
node_opt( node).
|
|
each( &push).
|
|
flat_map {|n| [ n.method(:lxc), n.method(:qemu) ] }.
|
|
peach {|m| exc2warn( nil) { m.call.each &push } }
|
|
end
|
|
}).
|
|
opt( :sort, '-s', '--sort=COLUMNS', "Sort by COLUMNs eg hn for host and name ([s]tatus, h[a], [i]d, [n]ame (default), [h]ost, [u]ptime, [c]pu, [m]em, [d]isk)", default: 'n').
|
|
opt( :node, '-n', '--node=NODE', "List only hosted by this NODE").
|
|
opt( :status, '-S', '--status=STATUS', "Filter for status (running, stopped, ...) (default: no filter)").
|
|
opt( :tags, '-t', '--tags=TAGS', "Filter by comma-seperated tags. All tags must be present for Machine")
|
|
|
|
def val2str v
|
|
case v
|
|
when true then 1
|
|
when false then 0
|
|
else v
|
|
end
|
|
end
|
|
|
|
def prepare_show_config cnf
|
|
r = {}
|
|
cnf.each do |k,v|
|
|
case k
|
|
when :network
|
|
v.each do |net|
|
|
s =
|
|
net.
|
|
reject {|k, v| :card == k }.
|
|
sort_by {|k, v| :name == k ? :AAAAAAAAA : k }.
|
|
map {|k, v| "#{k}=#{val2str v}" }
|
|
r[net[:card].to_sym] = s.join ','
|
|
end
|
|
when :sshkeys
|
|
r[k] = CGI.unescape( v).gsub( /^/, " "*14).gsub /\A {14}|\n\z/, ''
|
|
when :features
|
|
r[k] = v.map {|k, v| "#{k}=#{val2str v}" }.join ','
|
|
else
|
|
r[k] = val2str( v).to_s.gsub( /$^/, " "*14).gsub /\n\z/, ''
|
|
end
|
|
end
|
|
r
|
|
end
|
|
|
|
def show_config cnf, old = nil
|
|
cnf = prepare_show_config cnf
|
|
l = cnf.keys.map( &:length).max
|
|
if old
|
|
old = prepare_show_config old
|
|
(cnf.keys+old.keys).uniq.sort.each do |k|
|
|
v, o = cnf[k], old[k]
|
|
if v == o
|
|
puts "#{k}:#{' ' * (l-k.length)} #{v}"
|
|
else
|
|
puts "\e[31m#{k}:#{' ' * (l-k.length)} #{o}\e[0m" unless o.nil?
|
|
puts "\e[32m#{k}:#{' ' * (l-k.length)} #{v}\e[0m" unless v.nil?
|
|
end
|
|
end
|
|
else
|
|
cnf.sort_by {|k,_| k }.each do |k,v|
|
|
puts "#{k}:#{' ' * (l-k.length)} #{v}"
|
|
end
|
|
end
|
|
end
|
|
|
|
cli.sub :config, "CT/VM Configuration", min: 2, aliases: %w[cnf] do |ccli|
|
|
ccli.cmd :help, '', aliases: [nil, '-h', '--help'], &lambda {|*args| help ccli, *args }
|
|
|
|
ccli.cmd :set, "Set Configs for CT/VM", min: 3, &lambda {|name_or_id, *args|
|
|
if %w[-h --help].include? name_or_id
|
|
STDERR.puts "Usage: set -h|--help # Show help"
|
|
STDERR.puts " set ct|vm --CNF1=VAL1 --CNF2=VAL2 ... # Set config-value. Empty value clears field."
|
|
exit 1
|
|
end
|
|
opts = {}
|
|
until args.empty?
|
|
case arg = args.shift
|
|
when /\A--no-(\w+)\z/
|
|
opts[$1.to_sym] = false
|
|
when /\A--(\w+)=(.*)\z/
|
|
opts[$1.to_sym] = $2
|
|
when /\A--(\w+)!\z/, /\A--del-(\w+)\z/
|
|
opts[$1.to_sym] = nil
|
|
when /\A--(\w+)\z/
|
|
n = $1.to_sym
|
|
opts[n] =
|
|
if args.empty? or /\A--/ == args.first
|
|
true
|
|
else opts[n] = args.shift
|
|
end
|
|
else
|
|
raise UsageError, "Expection option to set. What do you mean with: #{arg}"
|
|
end
|
|
end
|
|
opts.each do |k, v|
|
|
opts[k] =
|
|
case v
|
|
when '' then nil
|
|
else v
|
|
end
|
|
end
|
|
%i[migrate_downtime].each do |k|
|
|
next unless opts.has_key? k
|
|
opts[k] =
|
|
case v = opts[k]
|
|
when nil, '', 'nil' then nil
|
|
else v.to_f
|
|
end
|
|
end
|
|
%i[memory background_delay balloon cores cpulimit cpuunits migrate_speed shares smp sockets vcpus swap tty].each do |k|
|
|
next unless opts.has_key? k
|
|
opts[k] =
|
|
case v = opts[k]
|
|
when nil, '', 'nil' then nil
|
|
else v.to_i
|
|
end
|
|
end
|
|
%i[unprivileged debug onboot protection template].each do |k|
|
|
next unless opts.has_key? k
|
|
opts[k] =
|
|
case v = opts[k]
|
|
when true, *%w[1 T TRUE t true True Y YES y yes Yes] then true
|
|
when false, *%w[0 F FALSE f false False N NO n no No] then false
|
|
when '', 'nil', nil then nil
|
|
else raise UsageError, "Boolean expected, given: #{v.inspect}"
|
|
end
|
|
end
|
|
connect
|
|
th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
|
|
raise UsageError, "Container or Node not found: #{name_or_id}" unless th
|
|
old = th.config
|
|
opts[:digest] ||= old[:digest]
|
|
th.cnfset opts
|
|
show_config th.config, old
|
|
}
|
|
|
|
ccli.cmd :show, "Show Config of CT/VM", &lambda {|name_or_id|
|
|
connect
|
|
th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
|
|
raise UsageError, "Container or Node not found: #{name_or_id}" unless th
|
|
show_config th.config
|
|
}
|
|
end
|
|
|
|
cli.cmd( :enter, "Enter Console of CT/Node", &lambda {|name_or_id|
|
|
connect
|
|
th = Proxmox::LXC.find( name_or_id) || Proxmox::Node.find_by_name( name_or_id)
|
|
raise UsageError, "Container or Node not found: #{name_or_id}" unless th
|
|
STDERR.puts "! #{$?.exitstatus}" unless th.enter
|
|
}).
|
|
completion do |*pre, arg|
|
|
completion_helper *pre, arg do |f|
|
|
complete_lxc( f) + complete_node( f)
|
|
end
|
|
end
|
|
|
|
cli.cmd( :run, "Starts CT/VM", aliases: %w[start star], &lambda {|*names_or_ids|
|
|
connect
|
|
raise UsageError, "Nothing to start?" if names_or_ids.empty?
|
|
per_argument names_or_ids, print: "\e[34;1mRunning CT/VM\e[0m %s:\n" do |name_or_id|
|
|
th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
|
|
raise UsageError, "Container or Node not found: #{name_or_id}" unless th
|
|
start th
|
|
end
|
|
}).
|
|
completion do |*pre, arg|
|
|
completion_helper *pre, arg do |f|
|
|
complete_lxc( f) + complete_qemu( f)
|
|
end
|
|
end
|
|
|
|
cli.cmd( :migrate, "Migrates (halted) CTs/VMs to an other host", min: 2, &lambda {|target, *names_or_ids, fire:, timeout:, secs:|
|
|
connect
|
|
node = Proxmox::Node.find_by_name! target
|
|
per_argument names_or_ids, print: "\e[1;34mCT/VM(s)\e[0m %s:\n" do |name_or_id|
|
|
th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
|
|
raise UsageError, "CT/VM not found: #{name_or_id}" unless th
|
|
unless th.stopped?
|
|
raise UsageError, "VM #{name_or_id} is running. Shutdown or for VM-online-migration see `help qm migrate`" if Proxmox::Qemu === th
|
|
raise UsageError, "CT #{name_or_id} is running. You have to shutdown it."
|
|
end
|
|
task = th.migrate node
|
|
wait task, text: "Migrating", timeout: timeout unless fire
|
|
end
|
|
}).tap {|c| opts_wait c }
|
|
|
|
#cli.cmd :reboot, "Reboot CT/VM (not implemented, yet)", min: 6, &lambda {|name_or_id|
|
|
# connect
|
|
# th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
|
|
# raise UsageError, "Container or Node not found: #{name_or_id}" unless th
|
|
# reboot th
|
|
#}
|
|
|
|
cli.cmd( :stop, "Stops CT/VM", min: 4, &lambda {|*names_or_ids|
|
|
connect
|
|
per_argument names_or_ids, print: "\e[34;1mStopping CT/VM\e[0m %s:\n" do |name_or_id|
|
|
th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
|
|
raise UsageError, "Container or Node not found: #{name_or_id}" unless th
|
|
stop th
|
|
end
|
|
}).
|
|
completion do |*pre, arg|
|
|
completion_helper *pre, arg do |f|
|
|
complete_lxc( f) + complete_qemu( f)
|
|
end
|
|
end
|
|
|
|
cli.cmd( :help, '', aliases: ['-h', '--help'], &lambda {|*args, full:|
|
|
if full
|
|
cli.help_full *args, output: STDERR
|
|
else
|
|
cli.help *args, output: STDERR
|
|
end
|
|
}).
|
|
opt( :full, '-f', '--[no-]full', 'Includes all commands of all subcommands.', default: false)
|
|
|
|
cli.cmd :cli, 'Opens interactive console', min: 3, aliases: [nil], &lambda {
|
|
@interactive = true
|
|
cli.interactive( File.basename($0,'.rb')).run
|
|
}
|
|
end
|
|
end
|