pve 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +70 -0
- data/README.adoc +22 -0
- data/bin/pvecli +5 -0
- data/lib/pve.rb +4 -0
- data/lib/pve/cli.rb +159 -0
- data/lib/pve/cli/base.rb +187 -0
- data/lib/pve/cli/ct.rb +115 -0
- data/lib/pve/cli/ha.rb +78 -0
- data/lib/pve/cli/node.rb +46 -0
- data/lib/pve/cli/qm.rb +32 -0
- data/lib/pve/cli/task.rb +26 -0
- data/lib/pve/helper.rb +167 -0
- data/lib/pve/proxmox.rb +597 -0
- data/lib/pve/qm.rb +32 -0
- data/lib/pve/templates.rb +154 -0
- data/lib/pve/version.rb +3 -0
- data/pve.gemspec +37 -0
- metadata +150 -0
data/lib/pve/cli/ct.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
class PVE::Cli
|
2
|
+
def cli_ct
|
3
|
+
cli.sub :ct, "Containers", aliases: %w[lx lxc] do |ct_cli|
|
4
|
+
ct_cli.cmd :list, "List CT-IDs", aliases: ['ls'], &lambda {|node=nil|
|
5
|
+
connect
|
6
|
+
nodes = Proxmox::Node.all
|
7
|
+
nodes = nodes.select {|n| node == n.name } if node
|
8
|
+
nodes.flat_map do |n|
|
9
|
+
n.lxc.map {|c| c.vmid.to_i }
|
10
|
+
end.sort.each {|c| puts c }
|
11
|
+
}
|
12
|
+
|
13
|
+
ct_cli.cmd :status, "List CTs with status", aliases: [nil], &lambda {|node=nil|
|
14
|
+
connect
|
15
|
+
to = TablizedOutput.new %w[Status HA ID Name Host Uptime CPU Mem/MiB Disk/MiB]
|
16
|
+
nodes = Proxmox::Node.all
|
17
|
+
nodes = nodes.select {|n| node == n.name } if node
|
18
|
+
nodes.each do |n|
|
19
|
+
n.lxc.each &to.method( :virt)
|
20
|
+
end
|
21
|
+
to.print order: [3]
|
22
|
+
}
|
23
|
+
|
24
|
+
ct_cli.cmd :enter, "Enter Console of CT", &lambda {|name_or_id|
|
25
|
+
connect
|
26
|
+
STDERR.puts "! #{$?.exitstatus}" unless Proxmox::LXC.find!( name_or_id).enter
|
27
|
+
}
|
28
|
+
|
29
|
+
ct_cli.cmd :exec, "Executes Command in CT", min: 4, &lambda {|name_or_id, *command|
|
30
|
+
connect
|
31
|
+
STDERR.puts "! #{$?.exitstatus}" unless Proxmox::LXC.find!( name_or_id).exec *command
|
32
|
+
}
|
33
|
+
|
34
|
+
ct_cli.cmd( :start, "Starts CT", min: 3, &lambda {|name_or_id, node: nil, fire:, timeout:, secs:|
|
35
|
+
connect
|
36
|
+
ct = Proxmox::LXC.find! name_or_id
|
37
|
+
start ct, node: node, fire: fire, timeout: timeout, secs: secs
|
38
|
+
}).
|
39
|
+
opt( :node, "-nNODE", "--node=NODE", "On NODE (default, as is, so without migration)").
|
40
|
+
tap {|c| opts_wait c }
|
41
|
+
|
42
|
+
ct_cli.cmd( :stop, "Stops CT", min: 3, &lambda {|name_or_id, fire: nil, timeout:, secs:|
|
43
|
+
connect
|
44
|
+
ct = Proxmox::LXC.find! name_or_id
|
45
|
+
stop ct, fire: fire, timeout: timeout, secs: secs
|
46
|
+
}).tap {|c| opts_wait c }
|
47
|
+
|
48
|
+
ct_cli.cmd( :wait, "Wait till CT is in state", &lambda {|name_or_id, state, timeout: nil, secs: nil|
|
49
|
+
connect
|
50
|
+
ct = Proxmox::LXC.find! name_or_id
|
51
|
+
wait ct, state, timeout: timeout, secs: secs
|
52
|
+
}).
|
53
|
+
opt( :timeout, "-tTIMEOUT", "--timeout=TIMEOUT", "Wait for max TIMEOUT seconds (default: endless)", default: nil).
|
54
|
+
opt( :secs, "-sSECONDS", "--seconds=SECONDS", "Check every SECONDS for state (default: 0.2)", default: 0.2)
|
55
|
+
|
56
|
+
ct_cli.cmd( :create, "Creates a new container", &lambda {|template, *options| #, fire:, timeout:, secs:, start:|
|
57
|
+
if %w[-h --help].include? template
|
58
|
+
STDERR.puts "Usage: ct create TEMPLATE -h # Shows template-related options"
|
59
|
+
STDERR.puts " ct create TEMPLATE [OPTIONS] # Creates a container"
|
60
|
+
exit 1
|
61
|
+
end
|
62
|
+
ctopts = {}
|
63
|
+
OptionParser.new do |opts|
|
64
|
+
opts.banner = <<EOU
|
65
|
+
Usage: ct create #{template} [options]
|
66
|
+
|
67
|
+
Options: (*=Required)
|
68
|
+
EOU
|
69
|
+
opts.on '-h', '--help', " Help!" do
|
70
|
+
STDERR.puts opts
|
71
|
+
exit 1 unless interactive?
|
72
|
+
return
|
73
|
+
end
|
74
|
+
opts.on( '-r', '--[no-]-start', " Start container after creation") {|v| ctopts[:start] = v }
|
75
|
+
opts.on( '-f', '--[no-]-fire', " Do not wait till running") {|v| ctopts[:start] = v }
|
76
|
+
opts.on( '-t', '--timeout=TIMEOUT', " Wait for max TIMEOUT seconds (default: endless)") {|v| ctopts[:timeout] = v }
|
77
|
+
opts.on( '-s', '--seconds=SECONDS', " Check every SECONDS for state (default: 0.2)") {|v| ctopts[:seconds] = v }
|
78
|
+
ctt = PVE::CTTemplate.const_get template.classify
|
79
|
+
ctt.requirements.each do |name, (type, req, desc, *args)|
|
80
|
+
req = req ? "*" : " "
|
81
|
+
case type
|
82
|
+
when :boolean
|
83
|
+
opts.on( "--[no-]#{name}", "#{req}#{desc}") {|v| ctopts[name] = v }
|
84
|
+
when :string, :numeric
|
85
|
+
opts.on( "--#{name}=#{type.upcase}", "#{req}#{desc}") {|v| ctopts[name] = v }
|
86
|
+
when :enum
|
87
|
+
opts.on( "--#{name}=#{type.upcase}", "#{req}#{desc} (#{args.first.join ', '})") do |v|
|
88
|
+
ctopts[name] = v
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end.parse! options
|
93
|
+
connect
|
94
|
+
create Proxmox::LXC, template, **ctopts
|
95
|
+
})
|
96
|
+
|
97
|
+
ct_cli.cmd( :config, '', &lambda {|name_or_id|
|
98
|
+
connect
|
99
|
+
ct = Proxmox::LXC.find! name_or_id
|
100
|
+
STDOUT.puts ct.config.to_json
|
101
|
+
})
|
102
|
+
|
103
|
+
ct_cli.cmd( :destroy, '', min: 7, &lambda {|name_or_id, fire:, secs:, timeout:, i_really_want_to_destroy:|
|
104
|
+
raise UsageError, "Name/ID is not what you want to destroy" unless name_or_id == i_really_want_to_destroy
|
105
|
+
connect
|
106
|
+
ct = Proxmox::LXC.find! name_or_id
|
107
|
+
raise UsageError, "Container is not stopped" unless ct.stopped?
|
108
|
+
destroy ct, fire: fire, timeout: timeout, secs: secs
|
109
|
+
}).tap {|c| opts_wait c }.
|
110
|
+
opt( :i_really_want_to_destroy, "--i-really-want-to-destroy=NAMEORID", "Repeat the name/ID")
|
111
|
+
|
112
|
+
ct_cli.cmd( :help, '', aliases: ['-h', '--help']) {|*args| help ct_cli, *args }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/pve/cli/ha.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
class PVE::Cli
|
2
|
+
def opts_ha cl
|
3
|
+
cl.
|
4
|
+
opt( :group, "-gGROUP", "--group=GROUP", "Put host in GROUP", default: 'all').
|
5
|
+
opt( :comment, "-cCOMMENT", "--comment=COMMENT", "Set comment").
|
6
|
+
opt( :max_relocate, "-lCOUNT", "--max-relocate=COUNT", "How often host can be relocate before givingup?", default: 2).
|
7
|
+
opt( :max_restart, "-rCOUNT", "--max-restart=COUNT", "How often host can be restarted before givingup?", default: 2).
|
8
|
+
opt( :state, "-sSTATE", "--state=STATE", "Host should have STATE. If you start/stop be `pct/qm/e start/stop` STATE will be set before action.", default: "started")
|
9
|
+
end
|
10
|
+
|
11
|
+
def cli_ha
|
12
|
+
cli.sub :ha, "Inspect High-Availability" do |hacli|
|
13
|
+
hacli.cmd( :create, "Create HA for CT/VM", &lambda {|name_or_id, group:, comment: nil, max_relocate:, max_restart:, state:|
|
14
|
+
connect
|
15
|
+
th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
|
16
|
+
raise UsageError, "Container or VirtualMachine not found: #{name_or_id}" unless th
|
17
|
+
ha = th.ha
|
18
|
+
raise UsageError, "#{th.sid} is already High-Available" unless ha.active?
|
19
|
+
ha.create group: group, comment: comment, max_relocate: max_relocate, max_restart: max_restart
|
20
|
+
}).tap {|cl| opts_ha cl }
|
21
|
+
|
22
|
+
hacli.cmd :remove, "Remove CT/VM from HA", &lambda {|name_or_id|
|
23
|
+
connect
|
24
|
+
th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
|
25
|
+
raise UsageError, "Container or VirtualMachine not found: #{name_or_id}" unless th
|
26
|
+
ha = th.ha
|
27
|
+
raise UsageError, "#{th.sid} is not High-Available" if ha.active?
|
28
|
+
ha.delete
|
29
|
+
}
|
30
|
+
|
31
|
+
hacli.cmd( :active, "CT/VM should be high-available. Options are only for defaults, if not activated, yet.", &lambda {|name_or_id, group:, comment: nil, max_relocate:, max_restart:, state:|
|
32
|
+
connect
|
33
|
+
th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
|
34
|
+
raise UsageError, "Container or VirtualMachine not found: #{name_or_id}" unless th
|
35
|
+
ha = th.ha
|
36
|
+
ha.create group: group, comment: comment, max_relocate: max_relocate, max_restart: max_restart if ha.active?
|
37
|
+
}).tap {|cl| opts_ha cl }
|
38
|
+
|
39
|
+
hacli.cmd :deactive, "CT/VM should NOT be high-available.", &lambda {|name_or_id|
|
40
|
+
connect
|
41
|
+
th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
|
42
|
+
raise UsageError, "Container or VirtualMachine not found: #{name_or_id}" unless th
|
43
|
+
ha = th.ha
|
44
|
+
ha.delete unless ha.active?
|
45
|
+
}
|
46
|
+
|
47
|
+
hacli.cmd( :started, "CT/VM should be in state started. By stopping CT/VM via pct/e state will be changed in HA, too.", &lambda {|name_or_id, force: nil|
|
48
|
+
connect
|
49
|
+
th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
|
50
|
+
raise UsageError, "Container or VirtualMachine not found: #{name_or_id}" unless th
|
51
|
+
ha = th.ha
|
52
|
+
ha = ha.create unless ha.active?
|
53
|
+
ha.disabled! if force and ha.error?
|
54
|
+
ha.started!
|
55
|
+
}).opt( :force, "-f", "--force", "If CT/VM is in error-state, first disable HA, than try to start.")
|
56
|
+
|
57
|
+
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|
|
58
|
+
connect
|
59
|
+
th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
|
60
|
+
raise UsageError, "Container or VirtualMachine not found: #{name_or_id}" unless th
|
61
|
+
ha = th.ha
|
62
|
+
ha = ha.create unless ha.active?
|
63
|
+
ha.stopped!
|
64
|
+
}
|
65
|
+
|
66
|
+
hacli.cmd :reset, "If state of CT/VM is failed, Proxmox will not start/stop it anyway. You have to reset state (state=disabled), first", &lambda {|name_or_id|
|
67
|
+
connect
|
68
|
+
th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
|
69
|
+
raise UsageError, "Container or VirtualMachine not found: #{name_or_id}" unless th
|
70
|
+
ha = th.ha
|
71
|
+
raise UsageError, "#{th.sid} is not High-Available" if ha.active?
|
72
|
+
ha.state = :disabled
|
73
|
+
}
|
74
|
+
|
75
|
+
hacli.cmd 'help', '', aliases: ['-h', '--help'], &lambda {|*args| help hacli, *args }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/pve/cli/node.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
class PVE::Cli
|
2
|
+
def cli_node
|
3
|
+
cli.sub :node, "Nodes" do |nod_cli|
|
4
|
+
nod_cli.cmd :status, "Lists nodes with status", aliases: [nil], &lambda {|node=nil|
|
5
|
+
connect
|
6
|
+
to = TablizedOutput.new %w[Status Node Uptime CPU Mem/MiB Disk/MiB]
|
7
|
+
nodes = Proxmox::Node.all
|
8
|
+
nodes = nodes.select {|n| node == n.name } if node
|
9
|
+
nodes.each do |n|
|
10
|
+
to.push [
|
11
|
+
n.status,
|
12
|
+
n.node,
|
13
|
+
Measured.seconds( n.uptime),
|
14
|
+
"%.02f/%d" % [n.cpu, n.maxcpu],
|
15
|
+
"#{Measured.bytes( n.mem)}/#{Measured.bytes( n.maxmem)}",
|
16
|
+
"#{Measured.bytes( n.disk)}/#{Measured.bytes( n.maxdisk)}",
|
17
|
+
]
|
18
|
+
end
|
19
|
+
to.print order: [1]
|
20
|
+
}
|
21
|
+
|
22
|
+
nod_cli.cmd :exec, "Executes command on node", min: 4 do |name, *args|
|
23
|
+
connect
|
24
|
+
STDERR.puts "! #{$?.exitstatus}" unless Proxmox::Node.find_by_name!( name).exec *args
|
25
|
+
end
|
26
|
+
|
27
|
+
nod_cli.cmd :enter, "Enter Console of node" do |name, *args|
|
28
|
+
connect
|
29
|
+
STDERR.puts "! #{$?.exitstatus}" unless Proxmox::Node.find_by_name!( name).enter *args
|
30
|
+
end
|
31
|
+
|
32
|
+
nod_cli.sub :task, "Inspect tasks" do |tcli|
|
33
|
+
tcli.cmd :list, "List done tasks", aliases: [nil, 'ls'], &lambda {|node|
|
34
|
+
connect
|
35
|
+
Proxmox::Node.find_by_name!( node).
|
36
|
+
tasks.
|
37
|
+
map( &:upid).
|
38
|
+
sort.
|
39
|
+
each {|upid| puts upid }
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
nod_cli.cmd( 'help', '', aliases: ['-h', '--help']) {|*args| help nod_cli, *args }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/pve/cli/qm.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
class PVE::Cli
|
2
|
+
def cli_qm
|
3
|
+
cli.sub :qm, "Virtual Machines", aliases: %w[v vm qemu], &lambda {|qm|
|
4
|
+
qm.cmd :list, "List VM-IDs", aliases: ['ls'], &lambda {|node=nil|
|
5
|
+
connect
|
6
|
+
nodes = Proxmox::Node.all
|
7
|
+
nodes = nodes.select {|n| node == n.name } if node
|
8
|
+
nodes.flat_map do |n|
|
9
|
+
n.qemu.map {|c| c.vmid.to_i }
|
10
|
+
end.sort.each {|c| puts c }
|
11
|
+
}
|
12
|
+
|
13
|
+
qm.cmd :status, "List VMs with status", aliases: [nil], &lambda {|node=nil|
|
14
|
+
connect
|
15
|
+
to = TablizedOutput.new %w[Status HA ID Name Host Uptime CPU Mem/MiB Disk/MiB]
|
16
|
+
nodes = Proxmox::Node.all
|
17
|
+
nodes = nodes.select {|n| node == n.name } if node
|
18
|
+
nodes.each do |n|
|
19
|
+
n.qemu.each &to.method( :virt)
|
20
|
+
end
|
21
|
+
to.print order: [3]
|
22
|
+
}
|
23
|
+
|
24
|
+
qm.cmd :exec, "Executes Command in VM via qemu-guest-agent", min: 4, &lambda {|name_or_id, *command|
|
25
|
+
connect
|
26
|
+
STDERR.puts "! #{$?.exitstatus}" unless Proxmox::Qemu.find!( name_or_id).exec *command
|
27
|
+
}
|
28
|
+
|
29
|
+
qm.cmd 'help', '', aliases: ['-h', '--help'], &lambda {|*args| help qm, *args }
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
data/lib/pve/cli/task.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
class PVE::Cli
|
2
|
+
def cli_task
|
3
|
+
cli.sub :task, "Inspect tasks" do |tcli|
|
4
|
+
tcli.cmd :list, "List done tasks", &lambda {|node=nil|
|
5
|
+
connect
|
6
|
+
nodes = Proxmox::Node.all
|
7
|
+
nodes = nodes.select {|n| node == n.name } if node
|
8
|
+
nodes.flat_map do |n|
|
9
|
+
n.tasks.map &:upid
|
10
|
+
end.sort.each {|upid| puts upid }
|
11
|
+
}
|
12
|
+
|
13
|
+
tcli.cmd :get, "Inspect a task", &lambda {|upid|
|
14
|
+
connect
|
15
|
+
Proxmox::Node.all.each do |n|
|
16
|
+
n.tasks.each do |t|
|
17
|
+
next unless t.upid == upid
|
18
|
+
puts t.upid
|
19
|
+
t.log.each {|l| puts l[:l] }
|
20
|
+
return
|
21
|
+
end
|
22
|
+
end
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/pve/helper.rb
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
class Measured
|
2
|
+
class <<self
|
3
|
+
def bytes1 v
|
4
|
+
v = v.to_f
|
5
|
+
return "%d B" % v if 512 > v
|
6
|
+
%w[KiB MiBy GiByt TiByte ExiByte PetiByte].each_with_index do |m|
|
7
|
+
v /= 1024
|
8
|
+
#return "%.1f %s" % [v, m] if 10 > v
|
9
|
+
#return "%d %s" % [v, m] if 512 > v
|
10
|
+
return "%.1f %s" % [v, m] if 512 > v
|
11
|
+
end
|
12
|
+
"%d PetiByte" % v
|
13
|
+
end
|
14
|
+
|
15
|
+
def bytes2 v
|
16
|
+
r = (v.to_i / 1024 / 1024).to_s
|
17
|
+
return '·' if 0 == r
|
18
|
+
r.
|
19
|
+
reverse.
|
20
|
+
each_char.
|
21
|
+
each_slice( 3).
|
22
|
+
to_a.
|
23
|
+
reverse.
|
24
|
+
map {|a| a.reverse.join }.
|
25
|
+
join " "
|
26
|
+
end
|
27
|
+
alias bytes bytes2
|
28
|
+
|
29
|
+
def seconds i
|
30
|
+
i = i.to_i
|
31
|
+
return '·' if 0 == i
|
32
|
+
return "%d s" % i if 90 > i
|
33
|
+
i /= 60
|
34
|
+
return "%d mi" % i if 90 > i
|
35
|
+
i /= 60
|
36
|
+
return "%d hou" % i if 36 > i
|
37
|
+
i /= 24
|
38
|
+
return "%d days" % i if 14 > i
|
39
|
+
j = i / 7
|
40
|
+
return "%d weeks" % j if 25 > j
|
41
|
+
i /= 365
|
42
|
+
return "%.1f years" if 550 > i
|
43
|
+
"%dy" % i
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class ColoredString
|
49
|
+
attr_reader :string, :color_codes
|
50
|
+
|
51
|
+
def initialize string, color_codes
|
52
|
+
@string, @color_codes = string, color_codes
|
53
|
+
end
|
54
|
+
|
55
|
+
def inspect
|
56
|
+
"#<ColoredString #{@color_codes} #{@string.inspect}>"
|
57
|
+
end
|
58
|
+
|
59
|
+
def length() @string.length end
|
60
|
+
alias size length
|
61
|
+
#def to_str() self end
|
62
|
+
def to_s() "\e[#{@color_codes}m#{@string}\e[0m" end
|
63
|
+
alias to_str to_s
|
64
|
+
#alias inspect to_str
|
65
|
+
|
66
|
+
include Comparable
|
67
|
+
def <=>(o) @string <=> o.string end
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
class TablizedOutput
|
73
|
+
def initialize header, stdout: nil
|
74
|
+
@header = header.map &:to_s
|
75
|
+
@columnc = header.size
|
76
|
+
@maxs = header.map &:length
|
77
|
+
@stdout ||= STDOUT
|
78
|
+
@lines = []
|
79
|
+
end
|
80
|
+
|
81
|
+
class B
|
82
|
+
include Comparable
|
83
|
+
def <=>(o) @v <=> o.v end
|
84
|
+
end
|
85
|
+
|
86
|
+
class V < B
|
87
|
+
attr_reader :v, :s
|
88
|
+
def initialize( v, s=nil) @v, @s = v, s || "#{v}" end
|
89
|
+
def to_s() @s end
|
90
|
+
def length() @s.length end
|
91
|
+
def inspect() "#<TO:V #{@v.inspect} #{@s.inspect}>" end
|
92
|
+
end
|
93
|
+
|
94
|
+
class Percentage < B
|
95
|
+
attr_reader :v, :w
|
96
|
+
def initialize( v, w=nil) @v, @w = v, w || 10 end
|
97
|
+
def length() @w end
|
98
|
+
def inspect() "#<TO:Percentage #{@v}%>" end
|
99
|
+
|
100
|
+
def to_s
|
101
|
+
y = w - (v*w).round
|
102
|
+
x = (100*v).round
|
103
|
+
r = "%*s" % [w, 0==x ? '·' : x]
|
104
|
+
"\e[0m#{r[0...y]}\e[1;4;#{0.75>v ? 32 : 31}m#{r[y..-1]}\e[0m"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def push fields
|
109
|
+
fields =
|
110
|
+
fields.map do |x|
|
111
|
+
case x
|
112
|
+
when String, ColoredString, B then x
|
113
|
+
else V.new x
|
114
|
+
end
|
115
|
+
end
|
116
|
+
@maxs = @columnc.times.map {|i| [@maxs[i], fields[i].length].max }
|
117
|
+
@lines.push fields
|
118
|
+
end
|
119
|
+
|
120
|
+
def pushs lines
|
121
|
+
lines.each &method( :push)
|
122
|
+
end
|
123
|
+
|
124
|
+
def print order: nil
|
125
|
+
format = "#{(["\e[%%sm%% %ds\e[0m"] * @columnc).join( ' ') % @maxs}\n"
|
126
|
+
ls = @lines
|
127
|
+
if order
|
128
|
+
eval <<-EOC, binding, __FILE__, 1+__LINE__
|
129
|
+
ls = ls.sort {|a,b|
|
130
|
+
[#{order.map {|i| 0 < i ? "a[#{i-1}]" : "b[#{-i-1}]" }.join ', '}] <=>
|
131
|
+
[#{order.map {|i| 0 < i ? "b[#{i-1}]" : "a[#{-i-1}]" }.join ', '}]
|
132
|
+
}
|
133
|
+
EOC
|
134
|
+
end
|
135
|
+
#ls = ls.sort_by {|e| p e; order.map &e.method(:[]) } if order
|
136
|
+
@stdout.printf format, *@header.flat_map {|s|['',s]}
|
137
|
+
ls.each {|l| @stdout.printf format, *l.flat_map {|s| s.is_a?(ColoredString) ? [s.color_codes, s.string] : ["", s.to_s] } }
|
138
|
+
end
|
139
|
+
|
140
|
+
def virt v
|
141
|
+
ha = v.respond_to?( :ha) ? v.ha : nil
|
142
|
+
unknown = V.new 0, '-'
|
143
|
+
push [
|
144
|
+
case v.status
|
145
|
+
when "running", "online" then ColoredString.new v.status, "32"
|
146
|
+
when "stopped" then ColoredString.new v.status, "31"
|
147
|
+
else v.status
|
148
|
+
end,
|
149
|
+
ha&.state || '·',
|
150
|
+
case v.t
|
151
|
+
when "nd" then ColoredString.new v.sid, "33"
|
152
|
+
when "qm" then ColoredString.new v.sid, "35"
|
153
|
+
when "ct" then ColoredString.new v.sid, "36"
|
154
|
+
else v.sid
|
155
|
+
end,
|
156
|
+
v.name, v.node.is_a?(String) ? v.node : v.node.node,
|
157
|
+
v.respond_to?(:uptime) ? V.new( v.uptime, Measured.seconds( v.uptime)) : unknown,
|
158
|
+
v.respond_to?(:cpu) ? Percentage.new( v.cpu) : unknown,
|
159
|
+
v.respond_to?(:mem) ? V.new( v.mem, Measured.bytes( v.mem)) : unknown,
|
160
|
+
v.respond_to?(:maxmem) ? Percentage.new( v.mem/v.maxmem.to_f) : unknown,
|
161
|
+
v.respond_to?(:disk) ? V.new( v.disk.to_i, Measured.bytes( v.disk.to_i)) : unknown,
|
162
|
+
if v.respond_to?(:maxdisk) and 0 < v.maxdisk.to_i
|
163
|
+
Percentage.new( v.disk.to_f/v.maxdisk.to_f)
|
164
|
+
else unknown end,
|
165
|
+
]
|
166
|
+
end
|
167
|
+
end
|