pve 0.1.3 → 0.2.1

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