diff --git a/lib/pve/cli.rb b/lib/pve/cli.rb index 2bbeb87..a19b147 100644 --- a/lib/pve/cli.rb +++ b/lib/pve/cli.rb @@ -13,6 +13,17 @@ require_relative 'cli/node' class UsageError , format: %w[> > > > > <] + node.aplinfo. + select {|a| 'system' == a.section ? system : applications}. + each do |apl| + to.push [ + apl.section, + apl.package, + apl.version, + apl.os, + apl.template, + apl.description, + ] + end + to.print order: [1,2] + end end diff --git a/lib/pve/cli/base.rb b/lib/pve/cli/base.rb index eb77d62..0236255 100644 --- a/lib/pve/cli/base.rb +++ b/lib/pve/cli/base.rb @@ -26,9 +26,8 @@ def cli_base else lambda {|n| to.virt n } end - nodes = Proxmox::Node.all + nodes = node ? Proxmox::Node.find_by_name( name) : Proxmox::Node.all nodes. - select {|n| not node or n === node }. flat_map {|n| [ n.method(:lxc), n.method(:qemu) ] }. each {|m| m.call.each &push } to.print order: sort.each_char.map {|c| (2*c.ord[5]-1) * (' sainhucmd'.index( c.downcase)) } @@ -151,19 +150,29 @@ def cli_base } end - cli.cmd :enter, "Enter Console of CT/Node", &lambda {|name_or_id| + 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 {|name_or_id| + cli.cmd( :run, "Starts CT/VM", aliases: %w[start star], &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 start th - } + }). + completion do |*pre, arg| + completion_helper *pre, arg do |f| + complete_lxc( f) + complete_qemu( f) + end + end #cli.cmd :reboot, "Reboot CT/VM (not implemented, yet)", min: 6, &lambda {|name_or_id| # connect @@ -172,12 +181,17 @@ def cli_base # reboot th #} - cli.cmd :stop, "Stops CT/VM", min: 4, &lambda {|name_or_id| + cli.cmd( :stop, "Stops CT/VM", min: 4, &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 stop th - } + }). + 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| help cli, *args } diff --git a/lib/pve/cli/ct.rb b/lib/pve/cli/ct.rb index 13be1a7..31bc3d8 100644 --- a/lib/pve/cli/ct.rb +++ b/lib/pve/cli/ct.rb @@ -3,8 +3,7 @@ def cli_ct cli.sub :ct, "Containers", aliases: %w[lx lxc] do |ct_cli| ct_cli.cmd :list, "List CT-IDs", aliases: ['ls'], &lambda {|node=nil| connect - nodes = Proxmox::Node.all - nodes = nodes.select {|n| node == n.name } if node + nodes = node ? Proxmox::Node.find_by_name( name) : Proxmox::Node.all nodes.flat_map do |n| n.lxc.map {|c| c.vmid.to_i } end.sort.each {|c| puts c } diff --git a/lib/pve/cli/ha.rb b/lib/pve/cli/ha.rb index 74d2461..04b3b7f 100644 --- a/lib/pve/cli/ha.rb +++ b/lib/pve/cli/ha.rb @@ -15,7 +15,7 @@ def cli_ha th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id) raise UsageError, "Container or VirtualMachine not found: #{name_or_id}" unless th ha = th.ha - raise UsageError, "#{th.sid} is already High-Available" unless ha.active? + raise UsageError, "#{th.sid} is already High-Available" if ha.active? ha.create group: group, comment: comment, max_relocate: max_relocate, max_restart: max_restart }).tap {|cl| opts_ha cl } @@ -24,7 +24,7 @@ def cli_ha th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id) raise UsageError, "Container or VirtualMachine not found: #{name_or_id}" unless th ha = th.ha - raise UsageError, "#{th.sid} is not High-Available" if ha.active? + raise UsageError, "#{th.sid} is not High-Available" unless ha.active? ha.delete } @@ -52,7 +52,7 @@ def cli_ha ha = ha.create unless ha.active? ha.disabled! if force and ha.error? ha.started! - }).opt( :force, "-f", "--force", "If CT/VM is in error-state, first disable HA, than try to start.") + }).opt( :force, "-f", "--force", "If CT/VM is in error-state, first reset HA, than try to start.") hacli.cmd :stopped, "CT/VM should be in state stopped. By starting CT/VM via pct/e state will be changed in HA, too.", min: 3, &lambda {|name_or_id| connect diff --git a/lib/pve/cli/storage.rb b/lib/pve/cli/storage.rb new file mode 100644 index 0000000..26b06ae --- /dev/null +++ b/lib/pve/cli/storage.rb @@ -0,0 +1,97 @@ +class PVE::Cli +def cli_storage + cli.sub :storage, "Storages", min: 3 do |cli_sm| + cli_sm.cmd :list, "List Storages", aliases: ['ls'], &lambda {|node=nil| + connect + nodes = node ? [Proxmox::Node.find_by_name!( node)] : Proxmox::Node.all + nodes.flat_map do |n| + n.lxc.map {|c| c.vmid.to_i } + end.sort.each {|c| puts c } + } + + cli_sm.cmd :status, "List Storages with status", aliases: [nil], &lambda {|node=nil| + connect + to = TablizedOutput.new %w[A E S Storage Host Type] + nodes = node ? [Proxmox::Node.find_by_name!( node)] : Proxmox::Node.all + nodes.each do |n| + n.storage.each do |v| + to.push [ + case v.active + when 1 then ColoredString.new 'Y', "32" + when 0 then ColoredString.new 'n', "31" + else v.active.to_s + end, + case v.enabled + when 1 then ColoredString.new 'Y', "32" + when 0 then ColoredString.new 'n', "31" + else v.enabled.to_s + end, + 1 == v.shared ? 's' : 'l', v.storage, v.node.node, v.type + ] + end + end + to.print order: [4,5] + } + + cli_sm.sub :content, "Content of Storage", aliases: ['cnt'] do |cli_cnt| + cli_cnt.cmd :list, "List Content", aliases: ['ls'], &lambda {|node=nil, storage| + connect + node = node ? Proxmox::Node.find_by_name!( node) : Proxmox::Node.all.first + storage = node.storage.select {|sm| storage == sm.storage }.first + storage.content.each {|c| puts c.to_s } + } + cli_cnt.cmd( :help, '', aliases: ['-h', '--help']) {|*args| help cli_cnt, *args } + end + + cli_sm.cmd( :help, '', aliases: ['-h', '--help']) {|*args| help cli_sm, *args } + #cli_sm.provide_help + end + + cli.sub :apl, "Appliances - Downloadable container images", min: 3 do |cli_apl| + + cli_apl.cmd( :content, "Table of all provided appliances", aliases: [nil], &lambda {|node:, regexp:, system:, applications:| + connect + appliances node, regexp, system, applications + }). + opt( :node, '-n=NODE', '--node', 'Ask this node for appliances (any node should list the same)', default: nil). + opt( :regexp, '-r=REGEXP', '--regexp', 'Filter by template', default: nil). + opt( :system, '-s', '--system', 'Only system templates', default: nil). + opt( :applications, '-a', '--applications', 'Only applications (non system) templates', default: nil) + + cli_apl.cmd( :system, "Table of provided systems", aliases: [nil], &lambda {|node:, regexp:| + connect + appliances node, regexp, true, nil + }). + opt( :node, '-n=NODE', '--node', 'Ask this node for appliances (any node should list the same)', default: nil). + opt( :regexp, '-r=REGEXP', '--regexp', 'Filter by template', default: nil) + + cli_apl.cmd( :applications, "Table of provided applications", aliases: [nil], &lambda {|node:, regexp:| + connect + appliances node, regexp, nil, true + }). + opt( :node, '-n=NODE', '--node', 'Ask this node for appliances (any node should list the same)', default: nil). + opt( :regexp, '-r=REGEXP', '--regexp', 'Filter by template', default: nil) + + cli_apl.cmd( :list, "List provided appliances", aliases: ['ls'], &lambda {|node=nil, regexp:| + connect + node = node ? Proxmox::Node.find_by_name!( node) : Proxmox::Node.all.first + node.aplinfo.each do |apl| + puts apl.template + end + }). + opt( :regexp, '-r=REGEXP', '--regexp', 'Filters by name', default: nil) + + cli_apl.cmd :download, "Download appliance", aliases: ['dl'], min: 2, &lambda {|template, node, storage=nil| + storage ||= 'local' + connect + node = Proxmox::Node.find_by_name! node + apl = node.aplinfo.find {|apl| apl.template == template } + raise UsageError, "Appliance not found" unless apl + task = apl.download storage + wait task, text: "Download #{apl.template} on #{node.node} to #{storage}" + } + + cli_apl.cmd( :help, '', aliases: ['-h', '--help']) {|*args| help cli_apl, *args } + end +end +end diff --git a/lib/pve/helper.rb b/lib/pve/helper.rb index 1b662a5..365d2c0 100644 --- a/lib/pve/helper.rb +++ b/lib/pve/helper.rb @@ -84,9 +84,10 @@ end class TablizedOutput - def initialize header, stdout: nil + def initialize header, stdout: nil, format: nil @header = header.map &:to_s @columnc = header.size + @format = format || ['>']*@columnc @maxs = header.map &:length @stdout ||= STDOUT @lines = [] @@ -138,7 +139,7 @@ class TablizedOutput end def print order: nil - format = "#{(["\e[%%sm%% %ds\e[0m"] * @columnc).join( ' ') % @maxs}\n" + format = "#{@format.map {|f| "\e[%%sm%%#{case f when '<' then '-' else ' ' end}%ds\e[0m"}.join( ' ') % @maxs}\n" ls = @lines if order eval <<-EOC, binding, __FILE__, 1+__LINE__ diff --git a/lib/pve/proxmox.rb b/lib/pve/proxmox.rb index 63e5be3..4434188 100644 --- a/lib/pve/proxmox.rb +++ b/lib/pve/proxmox.rb @@ -129,8 +129,8 @@ module Proxmox end end - def respond_to? method - super or instance_variable_defined?( "@#{method}") + def respond_to? method, also_private = false + instance_variable_defined?( "@#{method}") or super(method, also_private) end def method_missing method, *args, &exe @@ -154,7 +154,6 @@ module Proxmox private :__update__ def refresh! - p self: self, refresh: @rest_prefix __update__ rest_get( @rest_prefix) end end @@ -201,6 +200,20 @@ module Proxmox rest_get( "#{@rest_prefix}/lxc").map {|d| LXC.send :__new__, d.merge( node: self, t: 'ct') } end + def aplinfo + return [] if offline? + rest_get( "#{@rest_prefix}/aplinfo").map do |d| + AplInfo.send :__new__, d.merge( node: self, t: 'apl') + end + end + + def storage + return [] if offline? + rest_get( "#{@rest_prefix}/storage").map do |d| + Storage.send :__new__, d.merge( node: self, t: 'sm') + end + end + def qemu return [] if offline? rest_get( "#{@rest_prefix}/qemu").map {|d| Qemu.send :__new__, d.merge( node: self, t: 'qm') } @@ -638,20 +651,51 @@ module Proxmox self end - def started? - 'started' == self.state + def started?() 'started' == self.state end + def stopped?() 'stopped' == self.state end + def error?() 'error' == self.state end + def active?() ! ! digest end + end + + class Storage < Base + def rest_prefix + @rest_prefix ||= "/nodes/#{@node.node}/storage/#{@storage}" end - def stopped? - 'stopped' == self.state + def content + rest_get( "#{@rest_prefix}/content").map do |c| + Content.send :__new__, c.merge( node: @node, storage: self, t: 'smc') + end end - def error? - 'error' == self.state + def initialize() rest_prefix end + + def to_s() "#{@node.node}:#{@storage}" end + + class Content < Base + def rest_prefix + @rest_prefix ||= "/nodes/#{@node.node}/storage/#{@storage}/content/#{@content}" + end + + def initialize() rest_prefix end + def to_s() "#{node.node} #{volid}" end + end + end + + class AplInfo < Base + def rest_prefix + @rest_prefix ||= "/nodes/#{@node.node}/aplinfo" end - def active? - ! ! digest + def initialize() rest_prefix end + def name() @template end + def system?() 'system' == @section end + def debian?() %r[\Adebian-] =~ @os end + def lxc?() 'lxc' == @type end + + def download storage + upid = rest_post "#{@rest_prefix}", template: @template, storage: storage.to_s + Task.send :__new__, node: @node, host: self, upid: upid end end end diff --git a/lib/pve/templates.rb b/lib/pve/templates.rb index fc067c4..617271c 100644 --- a/lib/pve/templates.rb +++ b/lib/pve/templates.rb @@ -187,7 +187,7 @@ module PVE::CTTemplate case ostype when 'debian' # TODO: how to determine which template? - 'local:vztmpl/debian-10-standard_10.7-1_amd64.tar.gz' + 'local:vztmpl/debian-11-standard_11.0-1_amd64.tar.gz' else raise ArgumentError, "OS-Template for ostype #{ostype} not found or ostemplate not provided." end