require 'pmap' class PVE::Cli 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).lazy. flat_map {|n| [ Thread.new( n, &:lxc), Thread.new( n, &:qemu) ] }. each {|n| n.value.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