pve 0.1.2

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