pve 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|