pve 0.1.2 → 0.2.3

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/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
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
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
- select {|n| not node or n === node }.
30
- flat_map {|n| [ n.method(:lxc), n.method(:qemu) ] }.
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 'help', '', aliases: [nil, '-h', '--help'], &lambda {|*args| help ccli, *args }
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 'help', '', aliases: ['-h', '--help'], &lambda {|*args| help cli, *args }
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 'cli', 'Opens interactive console', min: 3, aliases: [nil], &lambda {
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, "List CTs with status", aliases: [nil], &lambda {|node=nil|
12
+ ct_cli.cmd( :status, "Lists CTs with status", aliases: [nil], &lambda {|target=nil, sort: 'n', node: nil|
14
13
  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
- }
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
- STDOUT.puts ct.config.to_json
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" unless ha.active?
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" if ha.active?
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 disable HA, than try to start.")
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, "List VMs with status", aliases: [nil], &lambda {|node=nil|
13
+ qm.cmd( :status, "Lists CTs with status", aliases: [nil], &lambda {|target=nil, sort: 'n', node: nil|
14
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
- }
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
@@ -16,7 +16,7 @@ def cli_task
16
16
  n.tasks.each do |t|
17
17
  next unless t.upid == upid
18
18
  puts t.upid
19
- t.log.each {|l| puts l[:l] }
19
+ t.log( start: 0, limit: 1024).each {|l| puts l[:t] }
20
20
  return
21
21
  end
22
22
  end
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
- if s[:exitstatus]
65
- STDERR.printf "\r[%s] %s %s\e[J\n",
66
- task.host ? task.host.name : s[:id],
67
- case s[:exitstatus]
68
- when "OK" then "\e[32;1m✓\e[0m"
69
- else "\e[31;1m✗\e[0m"
70
- end,
71
- s[:status]
72
- return task
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
- log = task.log start: logn
75
- log = [] if [{n: 1, t: 'no content'}] == log
76
- unless log.empty?
77
- STDERR.printf "\r\e[J"
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] = true if fire and start
137
+ options[:start] = fire && start
116
138
  task = klass.create template, **options
117
139
  return if fire
118
- wait task, text: "Creating"
119
- host = task.host.refresh!
120
- start host, timeout: timeout, secs: secs
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})", $!.backtrace.map {|b|" #{b}"}
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