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 +4 -4
- data/Gemfile.lock +6 -6
- data/README.adoc +12 -0
- data/lib/pve/cli/base.rb +29 -15
- data/lib/pve/cli/ct.rb +3 -4
- data/lib/pve/cli/ha.rb +3 -3
- data/lib/pve/cli/qm.rb +7 -0
- data/lib/pve/cli/storage.rb +97 -0
- data/lib/pve/cli/task.rb +1 -1
- data/lib/pve/cli.rb +90 -21
- data/lib/pve/helper.rb +7 -4
- data/lib/pve/proxmox.rb +123 -23
- data/lib/pve/templates.rb +2 -1
- data/lib/pve/version.rb +1 -1
- data/pve.gemspec +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a44ba988b57b1f008d06b521e2f10821a36db9532bb4947ee60627a324cd4c23
|
4
|
+
data.tar.gz: 1c048a050ee35b3e479c23d527eca2d44d7d56246b25ee38f9843ececb2269e0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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 :
|
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
|
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"
|
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
@@ -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
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
|
-
|
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
|
68
|
-
|
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
|
74
|
-
return
|
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
|
-
|
125
|
-
|
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 = "#{
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
590
|
-
|
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
|
-
|
594
|
-
|
685
|
+
class AplInfo < Base
|
686
|
+
def rest_prefix
|
687
|
+
@rest_prefix ||= "/nodes/#{@node.node}/aplinfo"
|
595
688
|
end
|
596
689
|
|
597
|
-
def
|
598
|
-
|
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
|
-
|
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
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.
|
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
|
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-
|
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.
|
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.
|
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
|