pve 0.1.2 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/LICENSE.txt +661 -0
- data/README.adoc +12 -0
- data/Rakefile +2 -0
- data/lib/pve/cli/base.rb +44 -20
- data/lib/pve/cli/ct.rb +34 -14
- data/lib/pve/cli/ha.rb +3 -3
- data/lib/pve/cli/qm.rb +25 -9
- data/lib/pve/cli/storage.rb +97 -0
- data/lib/pve/cli/task.rb +1 -1
- data/lib/pve/cli.rb +101 -25
- data/lib/pve/helper.rb +63 -24
- data/lib/pve/proxmox.rb +135 -33
- data/lib/pve/templates.rb +57 -15
- data/lib/pve/version.rb +1 -1
- data/pve.gemspec +1 -1
- metadata +7 -6
- data/Gemfile.lock +0 -70
- data/lib/pve/qm.rb +0 -32
data/README.adoc
CHANGED
@@ -14,6 +14,18 @@ You need to provide a config-file `/etc/pve/pvecli.yml`:
|
|
14
14
|
connect:
|
15
15
|
verify_tls: no if you do not use known CA-signed X509-Certificates
|
16
16
|
|
17
|
+
Featurs
|
18
|
+
=======
|
19
|
+
|
20
|
+
The library provides an interface to interact with PVE-servers.
|
21
|
+
Provided abstractions for:
|
22
|
+
|
23
|
+
* Node
|
24
|
+
* LXC (create, delete, modify, use)
|
25
|
+
* Qemu (create, delete, modify, use)
|
26
|
+
* Storages (list, list content)
|
27
|
+
* Appliances (list, download to storage)
|
28
|
+
|
17
29
|
pvecli
|
18
30
|
======
|
19
31
|
|
data/Rakefile
ADDED
data/lib/pve/cli/base.rb
CHANGED
@@ -2,6 +2,8 @@ require 'pmap'
|
|
2
2
|
|
3
3
|
class PVE::Cli
|
4
4
|
|
5
|
+
using IPAddress::ToSWithNetmaskForNetworks
|
6
|
+
|
5
7
|
def cli_base
|
6
8
|
cli.cmd :list, "List CT/VM-IDs", aliases: ['ls'], &lambda {|target=nil|
|
7
9
|
connect
|
@@ -24,11 +26,11 @@ def cli_base
|
|
24
26
|
else
|
25
27
|
lambda {|n| to.virt n }
|
26
28
|
end
|
27
|
-
nodes = Proxmox::Node.all
|
29
|
+
nodes = node ? Proxmox::Node.find_by_name( name) : Proxmox::Node.all
|
30
|
+
nodes.each &push
|
28
31
|
nodes.
|
29
|
-
|
30
|
-
|
31
|
-
each {|m| m.call.each &push }
|
32
|
+
flat_map {|n| [ Thread.new( n, &:lxc), Thread.new( n, &:qemu) ] }.
|
33
|
+
each {|n| n.value.each &push }
|
32
34
|
to.print order: sort.each_char.map {|c| (2*c.ord[5]-1) * (' sainhucmd'.index( c.downcase)) }
|
33
35
|
}).
|
34
36
|
opt( :sort, '-s', '--sort=COLUMNS', "Sort by COLUMNs eg hn for host and name ([s]tatus, h[a], [i]d, [n]ame (default), [h]ost, [u]ptime, [c]pu, [m]em, [d]isk)").
|
@@ -82,15 +84,9 @@ def cli_base
|
|
82
84
|
end
|
83
85
|
|
84
86
|
cli.sub :config, "CT/VM Configuration", min: 2, aliases: %w[cnf] do |ccli|
|
85
|
-
ccli.cmd
|
86
|
-
|
87
|
-
ccli.cmd :show, "Show Config of CT/VM", &lambda {|name_or_id|
|
88
|
-
connect
|
89
|
-
th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
|
90
|
-
show_config th.config
|
91
|
-
}
|
87
|
+
ccli.cmd :help, '', aliases: [nil, '-h', '--help'], &lambda {|*args| help ccli, *args }
|
92
88
|
|
93
|
-
ccli.cmd :set, "Set Configs for CT/VM", &lambda {|name_or_id, *args|
|
89
|
+
ccli.cmd :set, "Set Configs for CT/VM", min: 3, &lambda {|name_or_id, *args|
|
94
90
|
if %w[-h --help].include? name_or_id
|
95
91
|
STDERR.puts "Usage: set -h|--help # Show help"
|
96
92
|
STDERR.puts " set ct|vm --CNF1=VAL1 --CNF2=VAL2 ... # Set config-value. Empty value clears field."
|
@@ -147,21 +143,37 @@ def cli_base
|
|
147
143
|
th.cnfset opts
|
148
144
|
show_config th.config, old
|
149
145
|
}
|
146
|
+
|
147
|
+
ccli.cmd :show, "Show Config of CT/VM", aliases: %w[s], &lambda {|name_or_id|
|
148
|
+
connect
|
149
|
+
th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
|
150
|
+
show_config th.config
|
151
|
+
}
|
150
152
|
end
|
151
153
|
|
152
|
-
cli.cmd :enter, "Enter Console of CT/Node", &lambda {|name_or_id|
|
154
|
+
cli.cmd( :enter, "Enter Console of CT/Node", &lambda {|name_or_id|
|
153
155
|
connect
|
154
156
|
th = Proxmox::LXC.find( name_or_id) || Proxmox::Node.find_by_name( name_or_id)
|
155
157
|
raise UsageError, "Container or Node not found: #{name_or_id}" unless th
|
156
158
|
STDERR.puts "! #{$?.exitstatus}" unless th.enter
|
157
|
-
}
|
159
|
+
}).
|
160
|
+
completion do |*pre, arg|
|
161
|
+
completion_helper *pre, arg do |f|
|
162
|
+
complete_lxc( f) + complete_node( f)
|
163
|
+
end
|
164
|
+
end
|
158
165
|
|
159
|
-
cli.cmd :run, "Starts CT/VM", aliases: %w[start star], &lambda {|name_or_id|
|
166
|
+
cli.cmd( :run, "Starts CT/VM", aliases: %w[start star], &lambda {|name_or_id|
|
160
167
|
connect
|
161
168
|
th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
|
162
169
|
raise UsageError, "Container or Node not found: #{name_or_id}" unless th
|
163
170
|
start th
|
164
|
-
}
|
171
|
+
}).
|
172
|
+
completion do |*pre, arg|
|
173
|
+
completion_helper *pre, arg do |f|
|
174
|
+
complete_lxc( f) + complete_qemu( f)
|
175
|
+
end
|
176
|
+
end
|
165
177
|
|
166
178
|
#cli.cmd :reboot, "Reboot CT/VM (not implemented, yet)", min: 6, &lambda {|name_or_id|
|
167
179
|
# connect
|
@@ -170,16 +182,28 @@ def cli_base
|
|
170
182
|
# reboot th
|
171
183
|
#}
|
172
184
|
|
173
|
-
cli.cmd :stop, "Stops CT/VM", min: 4, &lambda {|name_or_id|
|
185
|
+
cli.cmd( :stop, "Stops CT/VM", min: 4, &lambda {|name_or_id|
|
174
186
|
connect
|
175
187
|
th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
|
176
188
|
raise UsageError, "Container or Node not found: #{name_or_id}" unless th
|
177
189
|
stop th
|
178
|
-
}
|
190
|
+
}).
|
191
|
+
completion do |*pre, arg|
|
192
|
+
completion_helper *pre, arg do |f|
|
193
|
+
complete_lxc( f) + complete_qemu( f)
|
194
|
+
end
|
195
|
+
end
|
179
196
|
|
180
|
-
cli.cmd
|
197
|
+
cli.cmd( :help, '', aliases: ['-h', '--help'], &lambda {|*args, full:|
|
198
|
+
if full
|
199
|
+
cli.help_full *args, output: STDERR
|
200
|
+
else
|
201
|
+
cli.help *args, output: STDERR
|
202
|
+
end
|
203
|
+
}).
|
204
|
+
opt( :full, '-f', '--[no-]full', 'Includes all commands of all subcommands.', default: false)
|
181
205
|
|
182
|
-
cli.cmd
|
206
|
+
cli.cmd :cli, 'Opens interactive console', min: 3, aliases: [nil], &lambda {
|
183
207
|
@interactive = true
|
184
208
|
cli.interactive( File.basename($0,'.rb')).run
|
185
209
|
}
|
data/lib/pve/cli/ct.rb
CHANGED
@@ -3,23 +3,31 @@ def cli_ct
|
|
3
3
|
cli.sub :ct, "Containers", aliases: %w[lx lxc] do |ct_cli|
|
4
4
|
ct_cli.cmd :list, "List CT-IDs", aliases: ['ls'], &lambda {|node=nil|
|
5
5
|
connect
|
6
|
-
nodes = Proxmox::Node.all
|
7
|
-
nodes = nodes.select {|n| node == n.name } if node
|
6
|
+
nodes = node ? Proxmox::Node.find_by_name( name) : Proxmox::Node.all
|
8
7
|
nodes.flat_map do |n|
|
9
8
|
n.lxc.map {|c| c.vmid.to_i }
|
10
9
|
end.sort.each {|c| puts c }
|
11
10
|
}
|
12
11
|
|
13
|
-
ct_cli.cmd :status, "
|
12
|
+
ct_cli.cmd( :status, "Lists CTs with status", aliases: [nil], &lambda {|target=nil, sort: 'n', node: nil|
|
14
13
|
connect
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
14
|
+
node &&= /\A#{node}\z/
|
15
|
+
to = TablizedOutput.new %w[Status HA ID Name Host Uptime CPU/% Mem/MiB Mem/% Disk/MiB Disk/%]
|
16
|
+
push =
|
17
|
+
if target
|
18
|
+
target = /\A#{target}\z/
|
19
|
+
lambda {|n| to.virt n if n === target }
|
20
|
+
else
|
21
|
+
lambda {|n| to.virt n }
|
22
|
+
end
|
23
|
+
nodes = node ? Proxmox::Node.find_by_name( name) : Proxmox::Node.all
|
24
|
+
nodes.
|
25
|
+
map {|n| Thread.new( n, &:lxc) }.
|
26
|
+
each {|n| n.value.each &push }
|
27
|
+
to.print order: sort.each_char.map {|c| (2*c.ord[5]-1) * (' sainhucmd'.index( c.downcase)) }
|
28
|
+
}).
|
29
|
+
opt( :sort, '-s', '--sort=COLUMNS', "Sort by COLUMNs eg hn for host and name ([s]tatus, h[a], [i]d, [n]ame (default), [h]ost, [u]ptime, [c]pu, [m]em, [d]isk)").
|
30
|
+
opt( :node, '-n', '--node=NODE', "List only hosted by this NODE")
|
23
31
|
|
24
32
|
ct_cli.cmd :enter, "Enter Console of CT", &lambda {|name_or_id|
|
25
33
|
connect
|
@@ -57,13 +65,19 @@ def cli_ct
|
|
57
65
|
if %w[-h --help].include? template
|
58
66
|
STDERR.puts "Usage: ct create TEMPLATE -h # Shows template-related options"
|
59
67
|
STDERR.puts " ct create TEMPLATE [OPTIONS] # Creates a container"
|
68
|
+
STDERR.puts " ct create -l # Listing available templates"
|
60
69
|
exit 1
|
70
|
+
elsif %w[-l --list].include? template
|
71
|
+
STDERR.puts PVE::CTTemplate.constants.reject {|c|:Base==c}.map {|c|c.to_s.titlecase.dasherize.downcase}
|
72
|
+
exit 0
|
61
73
|
end
|
62
74
|
ctopts = {}
|
63
75
|
OptionParser.new do |opts|
|
76
|
+
ctt = PVE::CTTemplate.const_get template.classify
|
64
77
|
opts.banner = <<EOU
|
65
78
|
Usage: ct create #{template} [options]
|
66
79
|
|
80
|
+
#{ctt.help}
|
67
81
|
Options: (*=Required)
|
68
82
|
EOU
|
69
83
|
opts.on '-h', '--help', " Help!" do
|
@@ -75,7 +89,6 @@ EOU
|
|
75
89
|
opts.on( '-f', '--[no-]-fire', " Do not wait till running") {|v| ctopts[:start] = v }
|
76
90
|
opts.on( '-t', '--timeout=TIMEOUT', " Wait for max TIMEOUT seconds (default: endless)") {|v| ctopts[:timeout] = v }
|
77
91
|
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
92
|
ctt.requirements.each do |name, (type, req, desc, *args)|
|
80
93
|
req = req ? "*" : " "
|
81
94
|
case type
|
@@ -94,10 +107,17 @@ EOU
|
|
94
107
|
create Proxmox::LXC, template, **ctopts
|
95
108
|
})
|
96
109
|
|
97
|
-
ct_cli.cmd( :config, '', &lambda {|name_or_id|
|
110
|
+
ct_cli.cmd( :config, 'Shows current config', aliases: %w[cnf], &lambda {|name_or_id|
|
111
|
+
connect
|
112
|
+
ct = Proxmox::LXC.find! name_or_id
|
113
|
+
STDOUT.puts JSON.dump( ct.config)
|
114
|
+
})
|
115
|
+
|
116
|
+
ct_cli.cmd( :resize, 'Resize a disk', &lambda {|name_or_id, disk, size|
|
98
117
|
connect
|
99
118
|
ct = Proxmox::LXC.find! name_or_id
|
100
|
-
|
119
|
+
task = ct.resize disk, size
|
120
|
+
wait task, text: "Resizing #{ct.sid} #{disk} to #{size}"
|
101
121
|
})
|
102
122
|
|
103
123
|
ct_cli.cmd( :destroy, '', min: 7, &lambda {|name_or_id, fire:, secs:, timeout:, i_really_want_to_destroy:|
|
data/lib/pve/cli/ha.rb
CHANGED
@@ -15,7 +15,7 @@ def cli_ha
|
|
15
15
|
th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
|
16
16
|
raise UsageError, "Container or VirtualMachine not found: #{name_or_id}" unless th
|
17
17
|
ha = th.ha
|
18
|
-
raise UsageError, "#{th.sid} is already High-Available"
|
18
|
+
raise UsageError, "#{th.sid} is already High-Available" if ha.active?
|
19
19
|
ha.create group: group, comment: comment, max_relocate: max_relocate, max_restart: max_restart
|
20
20
|
}).tap {|cl| opts_ha cl }
|
21
21
|
|
@@ -24,7 +24,7 @@ def cli_ha
|
|
24
24
|
th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
|
25
25
|
raise UsageError, "Container or VirtualMachine not found: #{name_or_id}" unless th
|
26
26
|
ha = th.ha
|
27
|
-
raise UsageError, "#{th.sid} is not High-Available"
|
27
|
+
raise UsageError, "#{th.sid} is not High-Available" unless ha.active?
|
28
28
|
ha.delete
|
29
29
|
}
|
30
30
|
|
@@ -52,7 +52,7 @@ def cli_ha
|
|
52
52
|
ha = ha.create unless ha.active?
|
53
53
|
ha.disabled! if force and ha.error?
|
54
54
|
ha.started!
|
55
|
-
}).opt( :force, "-f", "--force", "If CT/VM is in error-state, first
|
55
|
+
}).opt( :force, "-f", "--force", "If CT/VM is in error-state, first reset HA, than try to start.")
|
56
56
|
|
57
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
58
|
connect
|
data/lib/pve/cli/qm.rb
CHANGED
@@ -10,22 +10,38 @@ def cli_qm
|
|
10
10
|
end.sort.each {|c| puts c }
|
11
11
|
}
|
12
12
|
|
13
|
-
qm.cmd :status, "
|
13
|
+
qm.cmd( :status, "Lists CTs with status", aliases: [nil], &lambda {|target=nil, sort: 'n', node: nil|
|
14
14
|
connect
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
15
|
+
node &&= /\A#{node}\z/
|
16
|
+
to = TablizedOutput.new %w[Status HA ID Name Host Uptime CPU/% Mem/MiB Mem/% Disk/MiB Disk/%]
|
17
|
+
push =
|
18
|
+
if target
|
19
|
+
target = /\A#{target}\z/
|
20
|
+
lambda {|n| to.virt n if n === target }
|
21
|
+
else
|
22
|
+
lambda {|n| to.virt n }
|
23
|
+
end
|
24
|
+
nodes = node ? Proxmox::Node.find_by_name( name) : Proxmox::Node.all
|
25
|
+
nodes.
|
26
|
+
map {|n| Thread.new( n, &:qemu) }.
|
27
|
+
each {|n| n.value.each &push }
|
28
|
+
to.print order: sort.each_char.map {|c| (2*c.ord[5]-1) * (' sainhucmd'.index( c.downcase)) }
|
29
|
+
}).
|
30
|
+
opt( :sort, '-s', '--sort=COLUMNS', "Sort by COLUMNs eg hn for host and name ([s]tatus, h[a], [i]d, [n]ame (default), [h]ost, [u]ptime, [c]pu, [m]em, [d]isk)").
|
31
|
+
opt( :node, '-n', '--node=NODE', "List only hosted by this NODE")
|
23
32
|
|
24
33
|
qm.cmd :exec, "Executes Command in VM via qemu-guest-agent", min: 4, &lambda {|name_or_id, *command|
|
25
34
|
connect
|
26
35
|
STDERR.puts "! #{$?.exitstatus}" unless Proxmox::Qemu.find!( name_or_id).exec *command
|
27
36
|
}
|
28
37
|
|
38
|
+
qm.cmd( :resize, 'Resize a disk', &lambda {|name_or_id, disk, size|
|
39
|
+
connect
|
40
|
+
qm = Proxmox::Qemu.find! name_or_id
|
41
|
+
task = qm.resize disk, size
|
42
|
+
wait task, text: "Resizing #{qm.sid} #{disk} to #{size}"
|
43
|
+
})
|
44
|
+
|
29
45
|
qm.cmd 'help', '', aliases: ['-h', '--help'], &lambda {|*args| help qm, *args }
|
30
46
|
}
|
31
47
|
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
class PVE::Cli
|
2
|
+
def cli_storage
|
3
|
+
cli.sub :storage, "Storages", min: 3 do |cli_sm|
|
4
|
+
cli_sm.cmd :list, "List Storages", aliases: ['ls'], &lambda {|node=nil|
|
5
|
+
connect
|
6
|
+
nodes = node ? [Proxmox::Node.find_by_name!( node)] : Proxmox::Node.all
|
7
|
+
nodes.flat_map do |n|
|
8
|
+
n.lxc.map {|c| c.vmid.to_i }
|
9
|
+
end.sort.each {|c| puts c }
|
10
|
+
}
|
11
|
+
|
12
|
+
cli_sm.cmd :status, "List Storages with status", aliases: [nil], &lambda {|node=nil|
|
13
|
+
connect
|
14
|
+
to = TablizedOutput.new %w[A E S Storage Host Type]
|
15
|
+
nodes = node ? [Proxmox::Node.find_by_name!( node)] : Proxmox::Node.all
|
16
|
+
nodes.each do |n|
|
17
|
+
n.storage.each do |v|
|
18
|
+
to.push [
|
19
|
+
case v.active
|
20
|
+
when 1 then ColoredString.new 'Y', "32"
|
21
|
+
when 0 then ColoredString.new 'n', "31"
|
22
|
+
else v.active.to_s
|
23
|
+
end,
|
24
|
+
case v.enabled
|
25
|
+
when 1 then ColoredString.new 'Y', "32"
|
26
|
+
when 0 then ColoredString.new 'n', "31"
|
27
|
+
else v.enabled.to_s
|
28
|
+
end,
|
29
|
+
1 == v.shared ? 's' : 'l', v.storage, v.node.node, v.type
|
30
|
+
]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
to.print order: [4,5]
|
34
|
+
}
|
35
|
+
|
36
|
+
cli_sm.sub :content, "Content of Storage", aliases: ['cnt'] do |cli_cnt|
|
37
|
+
cli_cnt.cmd :list, "List Content", aliases: ['ls'], &lambda {|node=nil, storage|
|
38
|
+
connect
|
39
|
+
node = node ? Proxmox::Node.find_by_name!( node) : Proxmox::Node.all.first
|
40
|
+
storage = node.storage.select {|sm| storage == sm.storage }.first
|
41
|
+
storage.content.each {|c| puts c.to_s }
|
42
|
+
}
|
43
|
+
cli_cnt.cmd( :help, '', aliases: ['-h', '--help']) {|*args| help cli_cnt, *args }
|
44
|
+
end
|
45
|
+
|
46
|
+
cli_sm.cmd( :help, '', aliases: ['-h', '--help']) {|*args| help cli_sm, *args }
|
47
|
+
#cli_sm.provide_help
|
48
|
+
end
|
49
|
+
|
50
|
+
cli.sub :apl, "Appliances - Downloadable container images", min: 3 do |cli_apl|
|
51
|
+
|
52
|
+
cli_apl.cmd( :content, "Table of all provided appliances", aliases: [nil], &lambda {|node:, regexp:, system:, applications:|
|
53
|
+
connect
|
54
|
+
appliances node, regexp, system, applications
|
55
|
+
}).
|
56
|
+
opt( :node, '-n=NODE', '--node', 'Ask this node for appliances (any node should list the same)', default: nil).
|
57
|
+
opt( :regexp, '-r=REGEXP', '--regexp', 'Filter by template', default: nil).
|
58
|
+
opt( :system, '-s', '--system', 'Only system templates', default: nil).
|
59
|
+
opt( :applications, '-a', '--applications', 'Only applications (non system) templates', default: nil)
|
60
|
+
|
61
|
+
cli_apl.cmd( :system, "Table of provided systems", aliases: [nil], &lambda {|node:, regexp:|
|
62
|
+
connect
|
63
|
+
appliances node, regexp, true, nil
|
64
|
+
}).
|
65
|
+
opt( :node, '-n=NODE', '--node', 'Ask this node for appliances (any node should list the same)', default: nil).
|
66
|
+
opt( :regexp, '-r=REGEXP', '--regexp', 'Filter by template', default: nil)
|
67
|
+
|
68
|
+
cli_apl.cmd( :applications, "Table of provided applications", aliases: [nil], &lambda {|node:, regexp:|
|
69
|
+
connect
|
70
|
+
appliances node, regexp, nil, true
|
71
|
+
}).
|
72
|
+
opt( :node, '-n=NODE', '--node', 'Ask this node for appliances (any node should list the same)', default: nil).
|
73
|
+
opt( :regexp, '-r=REGEXP', '--regexp', 'Filter by template', default: nil)
|
74
|
+
|
75
|
+
cli_apl.cmd( :list, "List provided appliances", aliases: ['ls'], &lambda {|node=nil, regexp:|
|
76
|
+
connect
|
77
|
+
node = node ? Proxmox::Node.find_by_name!( node) : Proxmox::Node.all.first
|
78
|
+
node.aplinfo.each do |apl|
|
79
|
+
puts apl.template
|
80
|
+
end
|
81
|
+
}).
|
82
|
+
opt( :regexp, '-r=REGEXP', '--regexp', 'Filters by name', default: nil)
|
83
|
+
|
84
|
+
cli_apl.cmd :download, "Download appliance", aliases: ['dl'], min: 2, &lambda {|template, node, storage=nil|
|
85
|
+
storage ||= 'local'
|
86
|
+
connect
|
87
|
+
node = Proxmox::Node.find_by_name! node
|
88
|
+
apl = node.aplinfo.find {|apl| apl.template == template }
|
89
|
+
raise UsageError, "Appliance not found" unless apl
|
90
|
+
task = apl.download storage
|
91
|
+
wait task, text: "Download #{apl.template} on #{node.node} to #{storage}"
|
92
|
+
}
|
93
|
+
|
94
|
+
cli_apl.cmd( :help, '', aliases: ['-h', '--help']) {|*args| help cli_apl, *args }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/lib/pve/cli/task.rb
CHANGED
data/lib/pve/cli.rb
CHANGED
@@ -9,10 +9,22 @@ require_relative 'cli/ha'
|
|
9
9
|
require_relative 'cli/task'
|
10
10
|
require_relative 'cli/qm'
|
11
11
|
require_relative 'cli/node'
|
12
|
+
require_relative 'cli/storage'
|
12
13
|
|
13
14
|
class UsageError <RuntimeError
|
14
15
|
end
|
15
16
|
|
17
|
+
class DenCli::Sub
|
18
|
+
def provide_help name: nil, aliases: nil, min: nil
|
19
|
+
base = self
|
20
|
+
name = :help if name.nil?
|
21
|
+
aliases = %w[-h --help] if aliases.nil?
|
22
|
+
min = 1 if min.nil?
|
23
|
+
#p name: name, aliases: aliases, min: min
|
24
|
+
cmd( name, '', aliases: aliases, min: min) {|*args| help *args }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
16
28
|
class PVE::Cli
|
17
29
|
attr_reader :cfg, :cli
|
18
30
|
|
@@ -55,30 +67,42 @@ class PVE::Cli
|
|
55
67
|
exit 1 unless interactive? and r
|
56
68
|
end
|
57
69
|
|
70
|
+
def task_log task, logn, limit = 1024
|
71
|
+
log = task.log start: logn, limit: limit
|
72
|
+
log = [] if [{n: 1, t: 'no content'}] == log
|
73
|
+
unless log.empty?
|
74
|
+
STDERR.printf "\r\e[J"
|
75
|
+
log.each {|l| puts l[:t] }
|
76
|
+
logn = log.last[:n]
|
77
|
+
end
|
78
|
+
logn
|
79
|
+
end
|
80
|
+
|
58
81
|
def wait task, secs: nil, text: nil
|
59
82
|
secs ||= 0.1
|
60
83
|
spinners, spin, logn = "▖▘▝▗", 0, 0
|
61
84
|
STDERR.puts task.upid
|
85
|
+
host = task.host&.name
|
62
86
|
loop do
|
63
87
|
s = task.status
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
88
|
+
logn = self.task_log task, logn
|
89
|
+
if s.finished?
|
90
|
+
loop do
|
91
|
+
r = self.task_log task, logn
|
92
|
+
break if 0 == logn - r
|
93
|
+
logn = r
|
94
|
+
end
|
95
|
+
STDERR.printf "\r[%s] %s %s %s\e[J\n",
|
96
|
+
host || s.id,
|
97
|
+
s.successfull? ? "\e[32;1m✓\e[0m" : "\e[31;1m✗\e[0m",
|
98
|
+
text && "#{text}:",
|
99
|
+
s.stopped? ? :finished : s.status
|
100
|
+
return s
|
73
101
|
end
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
log.each {|l| puts l[:t] }
|
79
|
-
logn = log.last[:n]
|
80
|
-
end
|
81
|
-
STDERR.printf "\r[%s] \e[33;1m%s\e[0m %s...\e[J", task.host ? task.host.name : s[:id], spinners[spin = (spin + 1) % 4], text || "Working"
|
102
|
+
STDERR.printf "\r[%s] \e[33;1m%s\e[0m %s...\e[J",
|
103
|
+
host || s[:id],
|
104
|
+
spinners[spin = (spin + 1) % 4],
|
105
|
+
text || "Working"
|
82
106
|
sleep secs
|
83
107
|
end
|
84
108
|
end
|
@@ -94,9 +118,7 @@ class PVE::Cli
|
|
94
118
|
return
|
95
119
|
end
|
96
120
|
task = host.start
|
97
|
-
unless fire
|
98
|
-
wait task, text: "Starting"
|
99
|
-
end
|
121
|
+
wait task, text: "Starting" unless fire
|
100
122
|
end
|
101
123
|
|
102
124
|
def stop host, timeout: nil, fire: nil, secs: nil
|
@@ -112,12 +134,16 @@ class PVE::Cli
|
|
112
134
|
end
|
113
135
|
|
114
136
|
def create klass, template, timeout: nil, fire: nil, secs: nil, start: nil, **options
|
115
|
-
options[:start] =
|
137
|
+
options[:start] = fire && start
|
116
138
|
task = klass.create template, **options
|
117
139
|
return if fire
|
118
|
-
wait task, text: "Creating"
|
119
|
-
|
120
|
-
|
140
|
+
status = wait task, text: "Creating"
|
141
|
+
if status.successfull?
|
142
|
+
host = task.host.refresh!
|
143
|
+
start host, timeout: timeout, secs: secs if start
|
144
|
+
elsif not interactive?
|
145
|
+
exit 1
|
146
|
+
end
|
121
147
|
end
|
122
148
|
|
123
149
|
def destroy ct, timeout: nil, fire: nil, secs: nil
|
@@ -139,6 +165,36 @@ class PVE::Cli
|
|
139
165
|
opt( :fire, "-f", "--[no-]fire", "Do not wait till running", default: false)
|
140
166
|
end
|
141
167
|
|
168
|
+
def complete_lxc f
|
169
|
+
Proxmox::LXC.all.
|
170
|
+
flat_map {|x| [x.name, x.vmid.to_s] }.
|
171
|
+
select {|x| f =~ x }
|
172
|
+
end
|
173
|
+
|
174
|
+
def complete_qemu f
|
175
|
+
Proxmox::Qemu.all.
|
176
|
+
flat_map {|x| [x.name, x.vmid.to_s] }.
|
177
|
+
select {|x| f =~ x }
|
178
|
+
end
|
179
|
+
|
180
|
+
def complete_node f
|
181
|
+
Proxmox::Qemu.all.
|
182
|
+
map {|x| x.name }.
|
183
|
+
select {|x| f =~ x }
|
184
|
+
end
|
185
|
+
|
186
|
+
def completion_helper *pre, arg, &exe
|
187
|
+
if pre.empty?
|
188
|
+
connect
|
189
|
+
xs = yield /\A#{Regexp.quote arg}/
|
190
|
+
STDOUT.print "\a" if xs.empty?
|
191
|
+
xs
|
192
|
+
else
|
193
|
+
STDOUT.print "\a"
|
194
|
+
[]
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
142
198
|
def prepare
|
143
199
|
cli_node
|
144
200
|
cli_ct
|
@@ -146,14 +202,34 @@ class PVE::Cli
|
|
146
202
|
cli_task
|
147
203
|
cli_ha
|
148
204
|
cli_base
|
205
|
+
cli_storage
|
149
206
|
end
|
150
207
|
|
151
208
|
def call *argv
|
152
209
|
cli.call *argv
|
153
210
|
rescue RestClient::ExceptionWithResponse
|
154
|
-
STDERR.puts "#$! - #{$!.response} (#{$!.class})"
|
211
|
+
STDERR.puts "#$! - #{$!.response} (#{$!.class})" #, $!.backtrace.map {|b|" #{b}"}
|
155
212
|
rescue UsageError, DenCli::UsageError
|
156
213
|
STDERR.puts $!
|
157
214
|
exit 1
|
158
215
|
end
|
216
|
+
|
217
|
+
def appliances node, regexp, system, applications
|
218
|
+
system = applications = true if system.nil? and applications.nil?
|
219
|
+
node = node ? Proxmox::Node.find_by_name!( node) : Proxmox::Node.all.first
|
220
|
+
to = TablizedOutput.new %w<Section Package Version OS Template Description>, format: %w[> > > > > <]
|
221
|
+
node.aplinfo.
|
222
|
+
select {|a| 'system' == a.section ? system : applications}.
|
223
|
+
each do |apl|
|
224
|
+
to.push [
|
225
|
+
apl.section,
|
226
|
+
apl.package,
|
227
|
+
apl.version,
|
228
|
+
apl.os,
|
229
|
+
apl.template,
|
230
|
+
apl.description,
|
231
|
+
]
|
232
|
+
end
|
233
|
+
to.print order: [1,2]
|
234
|
+
end
|
159
235
|
end
|