pve 0.1.3 → 0.2.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a06ddcb47642c05e1741f36743dddc8549fe0a662d72246b8965d63474f437a2
4
- data.tar.gz: 458054ab599bc236cbae0e708180778a38b4d9136d6278f4c3866047af0f4819
3
+ metadata.gz: a44ba988b57b1f008d06b521e2f10821a36db9532bb4947ee60627a324cd4c23
4
+ data.tar.gz: 1c048a050ee35b3e479c23d527eca2d44d7d56246b25ee38f9843ececb2269e0
5
5
  SHA512:
6
- metadata.gz: e967e72848d1ca823b8ebb85737a78018116e3ac28c7bc070d1d3a8936ad6c79e9e09a2ff11344f0ce1aa62fa54634602e3993f5081126bf8f72d2b7ac841a32
7
- data.tar.gz: 673189e3d7318103b2dad96f653c072c1cef4a1a9a329aa920b17e8f43ecc8741763379b2a2a3f07ccaaf8af8c46eac2c9cef715225f6b6faa8353edaafa1a8a
6
+ metadata.gz: 1e6c00aa82db977e9616f7fd29cb72c8b442881f5253446610551f964c7a11f9d4a49cc7b5e0830e54e88a563ec03a0f6d7c231d05aa660bea10126c2e026cf3
7
+ data.tar.gz: 95b41594c329cfbd03633a77bc1ed32100a3b35ef9f9082578b7e8550095a768daacf3ae41417d630136c6c07c1c21c2143c5f43a4fa539b50c4ed53408c70f0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pve (0.1.1)
4
+ pve (0.1.3)
5
5
  activesupport (>= 2)
6
6
  dencli (~> 0.3.1)
7
7
  ipaddress (~> 0.8.3)
@@ -11,26 +11,26 @@ PATH
11
11
  GEM
12
12
  remote: https://rubygems.org/
13
13
  specs:
14
- activesupport (6.1.3.1)
14
+ activesupport (6.1.4.1)
15
15
  concurrent-ruby (~> 1.0, >= 1.0.2)
16
16
  i18n (>= 1.6, < 2)
17
17
  minitest (>= 5.1)
18
18
  tzinfo (~> 2.0)
19
19
  zeitwerk (~> 2.3)
20
- concurrent-ruby (1.1.8)
20
+ concurrent-ruby (1.1.9)
21
21
  dencli (0.3.1)
22
22
  diff-lcs (1.4.4)
23
23
  domain_name (0.5.20190701)
24
24
  unf (>= 0.0.5, < 1.0.0)
25
25
  http-accept (1.7.0)
26
- http-cookie (1.0.3)
26
+ http-cookie (1.0.4)
27
27
  domain_name (~> 0.5)
28
28
  i18n (1.8.10)
29
29
  concurrent-ruby (~> 1.0)
30
30
  ipaddress (0.8.3)
31
31
  mime-types (3.3.1)
32
32
  mime-types-data (~> 3.2015)
33
- mime-types-data (3.2021.0225)
33
+ mime-types-data (3.2021.0901)
34
34
  minitest (5.14.4)
35
35
  netrc (0.11.0)
36
36
  pmap (1.1.1)
@@ -56,7 +56,7 @@ GEM
56
56
  concurrent-ruby (~> 1.0)
57
57
  unf (0.1.4)
58
58
  unf_ext
59
- unf_ext (0.0.7.7)
59
+ unf_ext (0.0.8)
60
60
  zeitwerk (2.4.2)
61
61
 
62
62
  PLATFORMS
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/lib/pve/cli/base.rb CHANGED
@@ -26,9 +26,8 @@ def cli_base
26
26
  else
27
27
  lambda {|n| to.virt n }
28
28
  end
29
- nodes = Proxmox::Node.all
29
+ nodes = node ? Proxmox::Node.find_by_name( name) : Proxmox::Node.all
30
30
  nodes.
31
- select {|n| not node or n === node }.
32
31
  flat_map {|n| [ n.method(:lxc), n.method(:qemu) ] }.
33
32
  each {|m| m.call.each &push }
34
33
  to.print order: sort.each_char.map {|c| (2*c.ord[5]-1) * (' sainhucmd'.index( c.downcase)) }
@@ -86,13 +85,7 @@ def cli_base
86
85
  cli.sub :config, "CT/VM Configuration", min: 2, aliases: %w[cnf] do |ccli|
87
86
  ccli.cmd 'help', '', aliases: [nil, '-h', '--help'], &lambda {|*args| help ccli, *args }
88
87
 
89
- ccli.cmd :show, "Show Config of CT/VM", &lambda {|name_or_id|
90
- connect
91
- th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
92
- show_config th.config
93
- }
94
-
95
- ccli.cmd :set, "Set Configs for CT/VM", &lambda {|name_or_id, *args|
88
+ ccli.cmd :set, "Set Configs for CT/VM", min: 3, &lambda {|name_or_id, *args|
96
89
  if %w[-h --help].include? name_or_id
97
90
  STDERR.puts "Usage: set -h|--help # Show help"
98
91
  STDERR.puts " set ct|vm --CNF1=VAL1 --CNF2=VAL2 ... # Set config-value. Empty value clears field."
@@ -149,21 +142,37 @@ def cli_base
149
142
  th.cnfset opts
150
143
  show_config th.config, old
151
144
  }
145
+
146
+ ccli.cmd :show, "Show Config of CT/VM", aliases: %w[s], &lambda {|name_or_id|
147
+ connect
148
+ th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
149
+ show_config th.config
150
+ }
152
151
  end
153
152
 
154
- cli.cmd :enter, "Enter Console of CT/Node", &lambda {|name_or_id|
153
+ cli.cmd( :enter, "Enter Console of CT/Node", &lambda {|name_or_id|
155
154
  connect
156
155
  th = Proxmox::LXC.find( name_or_id) || Proxmox::Node.find_by_name( name_or_id)
157
156
  raise UsageError, "Container or Node not found: #{name_or_id}" unless th
158
157
  STDERR.puts "! #{$?.exitstatus}" unless th.enter
159
- }
158
+ }).
159
+ completion do |*pre, arg|
160
+ completion_helper *pre, arg do |f|
161
+ complete_lxc( f) + complete_node( f)
162
+ end
163
+ end
160
164
 
161
- cli.cmd :run, "Starts CT/VM", aliases: %w[start star], &lambda {|name_or_id|
165
+ cli.cmd( :run, "Starts CT/VM", aliases: %w[start star], &lambda {|name_or_id|
162
166
  connect
163
167
  th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
164
168
  raise UsageError, "Container or Node not found: #{name_or_id}" unless th
165
169
  start th
166
- }
170
+ }).
171
+ completion do |*pre, arg|
172
+ completion_helper *pre, arg do |f|
173
+ complete_lxc( f) + complete_qemu( f)
174
+ end
175
+ end
167
176
 
168
177
  #cli.cmd :reboot, "Reboot CT/VM (not implemented, yet)", min: 6, &lambda {|name_or_id|
169
178
  # connect
@@ -172,12 +181,17 @@ def cli_base
172
181
  # reboot th
173
182
  #}
174
183
 
175
- cli.cmd :stop, "Stops CT/VM", min: 4, &lambda {|name_or_id|
184
+ cli.cmd( :stop, "Stops CT/VM", min: 4, &lambda {|name_or_id|
176
185
  connect
177
186
  th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
178
187
  raise UsageError, "Container or Node not found: #{name_or_id}" unless th
179
188
  stop th
180
- }
189
+ }).
190
+ completion do |*pre, arg|
191
+ completion_helper *pre, arg do |f|
192
+ complete_lxc( f) + complete_qemu( f)
193
+ end
194
+ end
181
195
 
182
196
  cli.cmd 'help', '', aliases: ['-h', '--help'], &lambda {|*args| help cli, *args }
183
197
 
data/lib/pve/cli/ct.rb CHANGED
@@ -3,8 +3,7 @@ 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 }
@@ -99,10 +98,10 @@ EOU
99
98
  create Proxmox::LXC, template, **ctopts
100
99
  })
101
100
 
102
- ct_cli.cmd( :config, 'Shows current config', &lambda {|name_or_id|
101
+ ct_cli.cmd( :config, 'Shows current config', aliases: %w[cnf], &lambda {|name_or_id|
103
102
  connect
104
103
  ct = Proxmox::LXC.find! name_or_id
105
- STDOUT.puts ct.config.to_json
104
+ STDOUT.puts JSON.dump( ct.config)
106
105
  })
107
106
 
108
107
  ct_cli.cmd( :resize, 'Resize a disk', &lambda {|name_or_id, disk, size|
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
@@ -26,6 +26,13 @@ def cli_qm
26
26
  STDERR.puts "! #{$?.exitstatus}" unless Proxmox::Qemu.find!( name_or_id).exec *command
27
27
  }
28
28
 
29
+ qm.cmd( :resize, 'Resize a disk', &lambda {|name_or_id, disk, size|
30
+ connect
31
+ qm = Proxmox::Qemu.find! name_or_id
32
+ task = qm.resize disk, size
33
+ wait task, text: "Resizing #{qm.sid} #{disk} to #{size}"
34
+ })
35
+
29
36
  qm.cmd 'help', '', aliases: ['-h', '--help'], &lambda {|*args| help qm, *args }
30
37
  }
31
38
  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
@@ -13,6 +13,17 @@ require_relative 'cli/node'
13
13
  class UsageError <RuntimeError
14
14
  end
15
15
 
16
+ class DenCli::Sub
17
+ def provide_help name: nil, aliases: nil, min: nil
18
+ base = self
19
+ name = :help if name.nil?
20
+ aliases = %w[-h --help] if aliases.nil?
21
+ min = 1 if min.nil?
22
+ #p name: name, aliases: aliases, min: min
23
+ cmd( name, '', aliases: aliases, min: min) {|*args| help *args }
24
+ end
25
+ end
26
+
16
27
  class PVE::Cli
17
28
  attr_reader :cfg, :cli
18
29
 
@@ -55,6 +66,17 @@ class PVE::Cli
55
66
  exit 1 unless interactive? and r
56
67
  end
57
68
 
69
+ def task_log task, logn, limit = 1024
70
+ log = task.log start: logn, limit: limit
71
+ log = [] if [{n: 1, t: 'no content'}] == log
72
+ unless log.empty?
73
+ STDERR.printf "\r\e[J"
74
+ log.each {|l| puts l[:t] }
75
+ logn = log.last[:n]
76
+ end
77
+ logn
78
+ end
79
+
58
80
  def wait task, secs: nil, text: nil
59
81
  secs ||= 0.1
60
82
  spinners, spin, logn = "▖▘▝▗", 0, 0
@@ -62,23 +84,19 @@ class PVE::Cli
62
84
  host = task.host&.name
63
85
  loop do
64
86
  s = task.status
65
- if s[:exitstatus]
87
+ logn = self.task_log task, logn
88
+ if s.finished?
89
+ loop do
90
+ r = self.task_log task, logn
91
+ break if 0 == logn - r
92
+ logn = r
93
+ end
66
94
  STDERR.printf "\r[%s] %s %s %s\e[J\n",
67
- host || s[:id],
68
- case s[:exitstatus]
69
- when "OK" then "\e[32;1m✓\e[0m"
70
- else "\e[31;1m✗\e[0m"
71
- end,
95
+ host || s.id,
96
+ s.successfull? ? "\e[32;1m✓\e[0m" : "\e[31;1m✗\e[0m",
72
97
  text && "#{text}:",
73
- s[:status] == 'stopped' ? :finished : s[:status]
74
- return task
75
- end
76
- log = task.log start: logn
77
- log = [] if [{n: 1, t: 'no content'}] == log
78
- unless log.empty?
79
- STDERR.printf "\r\e[J"
80
- log.each {|l| puts l[:t] }
81
- logn = log.last[:n]
98
+ s.stopped? ? :finished : s.status
99
+ return s
82
100
  end
83
101
  STDERR.printf "\r[%s] \e[33;1m%s\e[0m %s...\e[J",
84
102
  host || s[:id],
@@ -99,9 +117,7 @@ class PVE::Cli
99
117
  return
100
118
  end
101
119
  task = host.start
102
- unless fire
103
- wait task, text: "Starting"
104
- end
120
+ wait task, text: "Starting" unless fire
105
121
  end
106
122
 
107
123
  def stop host, timeout: nil, fire: nil, secs: nil
@@ -120,9 +136,13 @@ class PVE::Cli
120
136
  options[:start] = fire && start
121
137
  task = klass.create template, **options
122
138
  return if fire
123
- wait task, text: "Creating"
124
- host = task.host.refresh!
125
- start host, timeout: timeout, secs: secs if start
139
+ status = wait task, text: "Creating"
140
+ if status.successfull?
141
+ host = task.host.refresh!
142
+ start host, timeout: timeout, secs: secs if start
143
+ elsif not interactive?
144
+ exit 1
145
+ end
126
146
  end
127
147
 
128
148
  def destroy ct, timeout: nil, fire: nil, secs: nil
@@ -144,6 +164,36 @@ class PVE::Cli
144
164
  opt( :fire, "-f", "--[no-]fire", "Do not wait till running", default: false)
145
165
  end
146
166
 
167
+ def complete_lxc f
168
+ Proxmox::LXC.all.
169
+ flat_map {|x| [x.name, x.vmid.to_s] }.
170
+ select {|x| f =~ x }
171
+ end
172
+
173
+ def complete_qemu f
174
+ Proxmox::Qemu.all.
175
+ flat_map {|x| [x.name, x.vmid.to_s] }.
176
+ select {|x| f =~ x }
177
+ end
178
+
179
+ def complete_node f
180
+ Proxmox::Qemu.all.
181
+ map {|x| x.name }.
182
+ select {|x| f =~ x }
183
+ end
184
+
185
+ def completion_helper *pre, arg, &exe
186
+ if pre.empty?
187
+ connect
188
+ xs = yield /\A#{Regexp.quote arg}/
189
+ STDOUT.print "\a" if xs.empty?
190
+ xs
191
+ else
192
+ STDOUT.print "\a"
193
+ []
194
+ end
195
+ end
196
+
147
197
  def prepare
148
198
  cli_node
149
199
  cli_ct
@@ -161,4 +211,23 @@ class PVE::Cli
161
211
  STDERR.puts $!
162
212
  exit 1
163
213
  end
214
+
215
+ def appliances node, regexp, system, applications
216
+ system = applications = true if system.nil? and applications.nil?
217
+ node = node ? Proxmox::Node.find_by_name!( node) : Proxmox::Node.all.first
218
+ to = TablizedOutput.new %w<Section Package Version OS Template Description>, format: %w[> > > > > <]
219
+ node.aplinfo.
220
+ select {|a| 'system' == a.section ? system : applications}.
221
+ each do |apl|
222
+ to.push [
223
+ apl.section,
224
+ apl.package,
225
+ apl.version,
226
+ apl.os,
227
+ apl.template,
228
+ apl.description,
229
+ ]
230
+ end
231
+ to.print order: [1,2]
232
+ end
164
233
  end
data/lib/pve/helper.rb CHANGED
@@ -84,9 +84,10 @@ end
84
84
 
85
85
 
86
86
  class TablizedOutput
87
- def initialize header, stdout: nil
87
+ def initialize header, stdout: nil, format: nil
88
88
  @header = header.map &:to_s
89
89
  @columnc = header.size
90
+ @format = format || ['>']*@columnc
90
91
  @maxs = header.map &:length
91
92
  @stdout ||= STDOUT
92
93
  @lines = []
@@ -112,10 +113,12 @@ class TablizedOutput
112
113
  def inspect() "#<TO:Percentage #{@v}%>" end
113
114
 
114
115
  def to_s
115
- y = w - (v*w).round
116
+ #y = w - (v*w).round
117
+ y = (v*w).round
116
118
  x = (100*v).round
117
119
  r = "%*s" % [w, 0==x ? '·' : x]
118
- "\e[0m#{r[0...y]}\e[1;4;#{0.75>v ? 32 : 31}m#{r[y..-1]}\e[0m"
120
+ #"\e[0m#{r[0...y]}\e[1;4;#{0.75>v ? 32 : 31}m#{r[y..-1]}\e[0m"
121
+ "\e[1;4;#{0.75>v ? 32 : 31}m#{r[0...y]}\e[0m#{r[y..-1]}"
119
122
  end
120
123
  end
121
124
 
@@ -136,7 +139,7 @@ class TablizedOutput
136
139
  end
137
140
 
138
141
  def print order: nil
139
- format = "#{(["\e[%%sm%% %ds\e[0m"] * @columnc).join( ' ') % @maxs}\n"
142
+ format = "#{@format.map {|f| "\e[%%sm%%#{case f when '<' then '-' else ' ' end}%ds\e[0m"}.join( ' ') % @maxs}\n"
140
143
  ls = @lines
141
144
  if order
142
145
  eval <<-EOC, binding, __FILE__, 1+__LINE__
data/lib/pve/proxmox.rb CHANGED
@@ -123,10 +123,14 @@ module Proxmox
123
123
  n.send :__update__, data
124
124
  end
125
125
  private :__new__
126
+
127
+ def fetch predata
128
+ __new__( predata).refresh!
129
+ end
126
130
  end
127
131
 
128
- def respond_to? method
129
- super or instance_variable_defined?( "@#{method}")
132
+ def respond_to? method, also_private = false
133
+ instance_variable_defined?( "@#{method}") or super(method, also_private)
130
134
  end
131
135
 
132
136
  def method_missing method, *args, &exe
@@ -147,6 +151,7 @@ module Proxmox
147
151
  initialize
148
152
  self
149
153
  end
154
+ private :__update__
150
155
 
151
156
  def refresh!
152
157
  __update__ rest_get( @rest_prefix)
@@ -176,9 +181,13 @@ module Proxmox
176
181
  def === t
177
182
  @name =~ t or @vmid.to_s =~ t or @sid =~ t
178
183
  end
184
+
185
+ def rest_prefix
186
+ @rest_prefix ||= "/nodes/#{@node}"
187
+ end
179
188
 
180
189
  def initialize
181
- @rest_prefix = "/nodes/#{@node}"
190
+ rest_prefix
182
191
  @sid = "nd:#{@node}"
183
192
  @name = @node
184
193
  end
@@ -191,6 +200,20 @@ module Proxmox
191
200
  rest_get( "#{@rest_prefix}/lxc").map {|d| LXC.send :__new__, d.merge( node: self, t: 'ct') }
192
201
  end
193
202
 
203
+ def aplinfo
204
+ return [] if offline?
205
+ rest_get( "#{@rest_prefix}/aplinfo").map do |d|
206
+ AplInfo.send :__new__, d.merge( node: self, t: 'apl')
207
+ end
208
+ end
209
+
210
+ def storage
211
+ return [] if offline?
212
+ rest_get( "#{@rest_prefix}/storage").map do |d|
213
+ Storage.send :__new__, d.merge( node: self, t: 'sm')
214
+ end
215
+ end
216
+
194
217
  def qemu
195
218
  return [] if offline?
196
219
  rest_get( "#{@rest_prefix}/qemu").map {|d| Qemu.send :__new__, d.merge( node: self, t: 'qm') }
@@ -211,21 +234,51 @@ module Proxmox
211
234
  end
212
235
 
213
236
  class Task < Base
214
- def initialize
237
+ class Status < Base
238
+ def rest_prefix
239
+ @rest_prefix ||= '/nodes/%s/tasks/%s/status' % [@node.node, @upid]
240
+ end
241
+
242
+ def refresh!
243
+ d = rest_get @rest_prefix
244
+ d[:starttime] &&= Time.at d[:starttime]
245
+ d = {exitstatus: nil}.merge d
246
+ __update__ d.merge( node: @node, t: 'status', upid: @upid, task: @task)
247
+ end
248
+
249
+ def initialize
250
+ rest_prefix
251
+ @sid = upid
252
+ end
253
+
254
+ def inspect
255
+ h = instance_variables - %i[@node @task @sid @rest_prefix @upid @t]
256
+ h.map! {|k| "#{k[1..-1]}=#{instance_variable_get(k).inspect}" }
257
+ "#<#{self.class.name}|#{@upid} node=#{@node.node} #{h.join ' '}>"
258
+ end
259
+
260
+ def running?() 'running' == @status end
261
+ def finished?() 'stopped' == @status end
262
+ alias stopped? finished?
263
+ def successfull?() stopped? ? 'OK' == @exitstatus : nil end
264
+ def failed?() stopped? ? 'OK' != @exitstatus : nil end
265
+ end
266
+
267
+ def rest_prefix
215
268
  @rest_prefix = "/nodes/#{@node.node}/tasks/#{upid}"
269
+ end
270
+
271
+ def initialize
272
+ rest_prefix
216
273
  @sid = upid
217
274
  end
218
275
 
219
276
  def inspect
220
- "#<#{self.class.name} #{upid}>"
277
+ "#<#{self.class.name} #{@upid}>"
221
278
  end
222
279
 
223
- #def finished?
224
- # rest_get( "/nodes/#{node}/tasks/")
225
- #end
226
-
227
280
  def status
228
- rest_get( "#{@rest_prefix}/status")
281
+ Status.fetch node: @node, task: self, upid: @upid
229
282
  end
230
283
 
231
284
  def log start: nil, limit: nil
@@ -235,8 +288,7 @@ module Proxmox
235
288
 
236
289
  class Hosted < Base
237
290
  def refresh!
238
- node, t = @node, @t
239
- __update__ rest_get( "#{@rest_prefix}/status/current").merge( node: node, t: t)
291
+ __update__ rest_get( "#{@rest_prefix}/status/current").merge( node: @node, t: @t)
240
292
  end
241
293
 
242
294
  def === t
@@ -392,8 +444,12 @@ module Proxmox
392
444
  end
393
445
  end
394
446
 
447
+ def rest_prefix
448
+ @rest_prefix ||= "/nodes/%s/qemu/%d" % [@node.node, @vmid]
449
+ end
450
+
395
451
  def initialize
396
- @rest_prefix = "/nodes/%s/qemu/%d" % [@node.node, @vmid]
452
+ rest_prefix
397
453
  @sid = "qm:#{@vmid}"
398
454
  end
399
455
 
@@ -404,6 +460,11 @@ module Proxmox
404
460
  def exec *args
405
461
  node.exec 'qm', 'guest', 'exec', vmid, '--', *args
406
462
  end
463
+
464
+ def resize disk, size
465
+ upid = rest_put "#{@rest_prefix}/resize", disk: disk, size: size
466
+ Task.send :__new__, node: @node, host: self, upid: upid
467
+ end
407
468
  end
408
469
 
409
470
  class LXC < Hosted
@@ -502,8 +563,12 @@ module Proxmox
502
563
  end
503
564
  end
504
565
 
566
+ def rest_prefix
567
+ @rest_prefix ||= "/nodes/%s/lxc/%d" % [@node.node, @vmid]
568
+ end
569
+
505
570
  def initialize
506
- @rest_prefix = "/nodes/%s/lxc/%d" % [@node.node, @vmid]
571
+ rest_prefix
507
572
  @sid = "ct:#{@vmid}"
508
573
  end
509
574
 
@@ -521,8 +586,12 @@ module Proxmox
521
586
  end
522
587
 
523
588
  class HA < Base
589
+ def rest_prefix
590
+ @rest_prefix ||= "/cluster/ha/resources/#{virt.sid}"
591
+ end
592
+
524
593
  def initialize
525
- @rest_prefix = "/cluster/ha/resources/#{virt.sid}"
594
+ rest_prefix
526
595
  end
527
596
 
528
597
  class <<self
@@ -582,20 +651,51 @@ module Proxmox
582
651
  self
583
652
  end
584
653
 
585
- def started?
586
- 'started' == self.state
654
+ def started?() 'started' == self.state end
655
+ def stopped?() 'stopped' == self.state end
656
+ def error?() 'error' == self.state end
657
+ def active?() ! ! digest end
658
+ end
659
+
660
+ class Storage < Base
661
+ def rest_prefix
662
+ @rest_prefix ||= "/nodes/#{@node.node}/storage/#{@storage}"
587
663
  end
588
664
 
589
- def stopped?
590
- 'stopped' == self.state
665
+ def content
666
+ rest_get( "#{@rest_prefix}/content").map do |c|
667
+ Content.send :__new__, c.merge( node: @node, storage: self, t: 'smc')
668
+ end
669
+ end
670
+
671
+ def initialize() rest_prefix end
672
+
673
+ def to_s() "#{@node.node}:#{@storage}" end
674
+
675
+ class Content < Base
676
+ def rest_prefix
677
+ @rest_prefix ||= "/nodes/#{@node.node}/storage/#{@storage}/content/#{@content}"
678
+ end
679
+
680
+ def initialize() rest_prefix end
681
+ def to_s() "#{node.node} #{volid}" end
591
682
  end
683
+ end
592
684
 
593
- def error?
594
- 'error' == self.state
685
+ class AplInfo < Base
686
+ def rest_prefix
687
+ @rest_prefix ||= "/nodes/#{@node.node}/aplinfo"
595
688
  end
596
689
 
597
- def active?
598
- ! ! digest
690
+ def initialize() rest_prefix end
691
+ def name() @template end
692
+ def system?() 'system' == @section end
693
+ def debian?() %r[\Adebian-] =~ @os end
694
+ def lxc?() 'lxc' == @type end
695
+
696
+ def download storage
697
+ upid = rest_post "#{@rest_prefix}", template: @template, storage: storage.to_s
698
+ Task.send :__new__, node: @node, host: self, upid: upid
599
699
  end
600
700
  end
601
701
  end
data/lib/pve/templates.rb CHANGED
@@ -186,7 +186,8 @@ module PVE::CTTemplate
186
186
  options.ostemplate ||
187
187
  case ostype
188
188
  when 'debian'
189
- 'local:vztmpl/debian-10-standard_10.5-1_amd64.tar.gz'
189
+ # TODO: how to determine which template?
190
+ 'local:vztmpl/debian-11-standard_11.0-1_amd64.tar.gz'
190
191
  else
191
192
  raise ArgumentError, "OS-Template for ostype #{ostype} not found or ostemplate not provided."
192
193
  end
data/lib/pve/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module PVE
2
- VERSION = '0.1.3'
2
+ VERSION = '0.2.1'
3
3
  end
data/pve.gemspec CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.metadata["source_code_uri"] = spec.homepage
20
20
  spec.metadata["changelog_uri"] = spec.homepage
21
21
 
22
- spec.add_dependency 'dencli', '~> 0.3.1'
22
+ spec.add_dependency 'dencli', '~> 0.4'
23
23
  spec.add_dependency 'rest-client', '~> 2.1'
24
24
  spec.add_dependency 'ipaddress', '~> 0.8.3'
25
25
  spec.add_dependency 'activesupport', '>= 2'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pve
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Denis Knauf
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-16 00:00:00.000000000 Z
11
+ date: 2021-11-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dencli
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.3.1
19
+ version: '0.4'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.3.1
26
+ version: '0.4'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rest-client
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -114,6 +114,7 @@ files:
114
114
  - lib/pve/cli/ha.rb
115
115
  - lib/pve/cli/node.rb
116
116
  - lib/pve/cli/qm.rb
117
+ - lib/pve/cli/storage.rb
117
118
  - lib/pve/cli/task.rb
118
119
  - lib/pve/helper.rb
119
120
  - lib/pve/proxmox.rb