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.
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
@@ -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
@@ -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