Tab-completion, APL, Storages
The interactive shell provides tab-completion for LXC-/VM-/Node-names and IDs. CLI provides storage-management, including listing storages and there contents. Also added: Listing and downloading of APLs to storages.
This commit is contained in:
parent
38ea2125ff
commit
ca44934546
|
@ -13,6 +13,17 @@ require_relative 'cli/node'
|
|||
class UsageError <RuntimeError
|
||||
end
|
||||
|
||||
class DenCli::Sub
|
||||
def provide_help name: nil, aliases: nil, min: nil
|
||||
base = self
|
||||
name = :help if name.nil?
|
||||
aliases = %w[-h --help] if aliases.nil?
|
||||
min = 1 if min.nil?
|
||||
#p name: name, aliases: aliases, min: min
|
||||
cmd( name, '', aliases: aliases, min: min) {|*args| help *args }
|
||||
end
|
||||
end
|
||||
|
||||
class PVE::Cli
|
||||
attr_reader :cfg, :cli
|
||||
|
||||
|
@ -153,6 +164,36 @@ class PVE::Cli
|
|||
opt( :fire, "-f", "--[no-]fire", "Do not wait till running", default: false)
|
||||
end
|
||||
|
||||
def complete_lxc f
|
||||
Proxmox::LXC.all.
|
||||
flat_map {|x| [x.name, x.vmid.to_s] }.
|
||||
select {|x| f =~ x }
|
||||
end
|
||||
|
||||
def complete_qemu f
|
||||
Proxmox::Qemu.all.
|
||||
flat_map {|x| [x.name, x.vmid.to_s] }.
|
||||
select {|x| f =~ x }
|
||||
end
|
||||
|
||||
def complete_node f
|
||||
Proxmox::Qemu.all.
|
||||
map {|x| x.name }.
|
||||
select {|x| f =~ x }
|
||||
end
|
||||
|
||||
def completion_helper *pre, arg, &exe
|
||||
if pre.empty?
|
||||
connect
|
||||
xs = yield /\A#{Regexp.quote arg}/
|
||||
STDOUT.print "\a" if xs.empty?
|
||||
xs
|
||||
else
|
||||
STDOUT.print "\a"
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def prepare
|
||||
cli_node
|
||||
cli_ct
|
||||
|
@ -170,4 +211,23 @@ class PVE::Cli
|
|||
STDERR.puts $!
|
||||
exit 1
|
||||
end
|
||||
|
||||
def appliances node, regexp, system, applications
|
||||
system = applications = true if system.nil? and applications.nil?
|
||||
node = node ? Proxmox::Node.find_by_name!( node) : Proxmox::Node.all.first
|
||||
to = TablizedOutput.new %w<Section Package Version OS Template Description>, 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
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
|
97
lib/pve/cli/storage.rb
Normal file
97
lib/pve/cli/storage.rb
Normal file
|
@ -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
|
|
@ -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__
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue