vagrant-xapi 0.0.1 → 1.0.0

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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +83 -0
  3. data/LICENSE +21 -0
  4. data/README.md +460 -4
  5. data/docs/architecture.md +1420 -0
  6. data/docs/operations.md +531 -0
  7. data/lib/vagrant-xapi/action/base.rb +103 -0
  8. data/lib/vagrant-xapi/action/cloud_init.rb +130 -0
  9. data/lib/vagrant-xapi/action/create.rb +127 -0
  10. data/lib/vagrant-xapi/action/destroy.rb +68 -0
  11. data/lib/vagrant-xapi/action/network.rb +229 -0
  12. data/lib/vagrant-xapi/action/package.rb +103 -0
  13. data/lib/vagrant-xapi/action/power.rb +185 -0
  14. data/lib/vagrant-xapi/action/snapshots.rb +43 -0
  15. data/lib/vagrant-xapi/action.rb +317 -0
  16. data/lib/vagrant-xapi/api/client.rb +393 -0
  17. data/lib/vagrant-xapi/api/errors.rb +98 -0
  18. data/lib/vagrant-xapi/api/http_transfer.rb +332 -0
  19. data/lib/vagrant-xapi/api/redactor.rb +77 -0
  20. data/lib/vagrant-xapi/api/session_registry.rb +59 -0
  21. data/lib/vagrant-xapi/api/task.rb +156 -0
  22. data/lib/vagrant-xapi/api/transport.rb +249 -0
  23. data/lib/vagrant-xapi/cap.rb +90 -0
  24. data/lib/vagrant-xapi/command/adopt.rb +92 -0
  25. data/lib/vagrant-xapi/command/base.rb +79 -0
  26. data/lib/vagrant-xapi/command/console.rb +87 -0
  27. data/lib/vagrant-xapi/command/diff.rb +267 -0
  28. data/lib/vagrant-xapi/command/hosts.rb +63 -0
  29. data/lib/vagrant-xapi/command/info.rb +94 -0
  30. data/lib/vagrant-xapi/command/marked.rb +85 -0
  31. data/lib/vagrant-xapi/command/orphans.rb +96 -0
  32. data/lib/vagrant-xapi/command/root.rb +55 -0
  33. data/lib/vagrant-xapi/command/templates.rb +91 -0
  34. data/lib/vagrant-xapi/config.rb +333 -0
  35. data/lib/vagrant-xapi/errors.rb +113 -0
  36. data/lib/vagrant-xapi/extensions.rb +80 -0
  37. data/lib/vagrant-xapi/glue.rb +330 -0
  38. data/lib/vagrant-xapi/legacy/plugin.rb +61 -0
  39. data/lib/vagrant-xapi/legacy/xenapi.rb +129 -0
  40. data/lib/vagrant-xapi/plugin.rb +58 -0
  41. data/lib/vagrant-xapi/provider.rb +135 -0
  42. data/lib/vagrant-xapi/services/adoption.rb +73 -0
  43. data/lib/vagrant-xapi/services/base.rb +242 -0
  44. data/lib/vagrant-xapi/services/box_source.rb +218 -0
  45. data/lib/vagrant-xapi/services/console.rb +115 -0
  46. data/lib/vagrant-xapi/services/content_diff.rb +96 -0
  47. data/lib/vagrant-xapi/services/destroyer.rb +104 -0
  48. data/lib/vagrant-xapi/services/diff_disks.rb +346 -0
  49. data/lib/vagrant-xapi/services/differ.rb +382 -0
  50. data/lib/vagrant-xapi/services/disk_image.rb +122 -0
  51. data/lib/vagrant-xapi/services/disks.rb +281 -0
  52. data/lib/vagrant-xapi/services/exporter.rb +128 -0
  53. data/lib/vagrant-xapi/services/file_diff.rb +249 -0
  54. data/lib/vagrant-xapi/services/guest_files.rb +730 -0
  55. data/lib/vagrant-xapi/services/hosts.rb +55 -0
  56. data/lib/vagrant-xapi/services/image_upload.rb +172 -0
  57. data/lib/vagrant-xapi/services/inventory.rb +225 -0
  58. data/lib/vagrant-xapi/services/ip_detector.rb +107 -0
  59. data/lib/vagrant-xapi/services/lock.rb +293 -0
  60. data/lib/vagrant-xapi/services/lookup.rb +181 -0
  61. data/lib/vagrant-xapi/services/networks.rb +248 -0
  62. data/lib/vagrant-xapi/services/power.rb +108 -0
  63. data/lib/vagrant-xapi/services/rfb.rb +129 -0
  64. data/lib/vagrant-xapi/services/snapshots.rb +81 -0
  65. data/lib/vagrant-xapi/services/template_cache.rb +263 -0
  66. data/lib/vagrant-xapi/services/tunnels.rb +217 -0
  67. data/lib/vagrant-xapi/services/vm_builder.rb +215 -0
  68. data/lib/vagrant-xapi/version.rb +7 -0
  69. data/lib/vagrant-xapi/xenserver/config.rb +69 -0
  70. data/lib/vagrant-xapi/xenserver/migrate.rb +103 -0
  71. data/lib/vagrant-xapi/xenserver/plugin.rb +96 -0
  72. data/lib/vagrant-xapi/xenserver/provider.rb +22 -0
  73. data/lib/vagrant-xapi/xenserver/takeover.rb +79 -0
  74. data/lib/vagrant-xapi.rb +21 -2
  75. data/lib/xenapi.rb +22 -0
  76. data/locales/en.yml +517 -0
  77. data/locales/legacy.en.yml +6 -0
  78. metadata +86 -5
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "../services/inventory"
5
+
6
+ module VagrantPlugins
7
+ module Xapi
8
+ module Command
9
+ # vagrant xapi templates list | prune [--keep N] | remove NAME|UUID
10
+ #
11
+ # Templates are shared by every user of a pool, so prune and remove show
12
+ # which other users still have clones before asking for confirmation.
13
+ class Templates < Base
14
+ def execute
15
+ options = { keep: 1, force: false }
16
+ opts = OptionParser.new do |o|
17
+ o.banner = "Usage: vagrant xapi templates list|prune|remove [NAME|UUID] [--keep N] [--force]"
18
+ o.on("--keep N", Integer, "prune: newest versions to keep per box (default 1)") { |n| options[:keep] = n }
19
+ o.on("-f", "--force", "Do not ask for confirmation") { options[:force] = true }
20
+ end
21
+ argv = parse_options(opts)
22
+ return 0 if argv.nil?
23
+
24
+ raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp if options[:keep].negative?
25
+
26
+ action = argv.shift || "list"
27
+ machine = connection_machine
28
+ Errors.wrap do
29
+ inventory = Services::Inventory.new(Glue.context(machine))
30
+ case action
31
+ when "list" then list(inventory)
32
+ when "prune" then prune(inventory, options)
33
+ when "remove"
34
+ raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp if argv.empty?
35
+
36
+ remove(inventory, argv.first, options)
37
+ else
38
+ raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp
39
+ end
40
+ end
41
+ 0
42
+ end
43
+
44
+ private
45
+
46
+ def list(inventory)
47
+ rows = inventory.templates
48
+ if rows.empty?
49
+ @env.ui.info(t("no_templates"))
50
+ return
51
+ end
52
+
53
+ header = format("%-30s %-12s %-10s %-20s %10s %7s %s", "BOX", "VERSION", "PROVIDER", "SR", "SIZE",
54
+ "CLONES", "UUID")
55
+ lines = [header]
56
+ rows.each do |r|
57
+ lines << format("%-30s %-12s %-10s %-20s %10s %7d %s", r.box, r.version, r.provider, r.sr_name,
58
+ human_size(r.size), r.clones, r.uuid)
59
+ end
60
+ @env.ui.info(lines.join("\n"), prefix: false)
61
+ end
62
+
63
+ def prune(inventory, options)
64
+ candidates = inventory.prune_candidates(options[:keep])
65
+ if candidates.empty?
66
+ @env.ui.info(t("nothing_to_prune"))
67
+ return
68
+ end
69
+ candidates.each { |c| remove_one(inventory, c, options[:force]) }
70
+ end
71
+
72
+ def remove(inventory, name, options)
73
+ remove_one(inventory, inventory.find_template(name), options[:force])
74
+ end
75
+
76
+ def remove_one(inventory, info, force)
77
+ @env.ui.info(t("template_summary", name: info.name, uuid: info.uuid, clones: info.clones))
78
+ others = info.clone_users - [Services::Marking.user]
79
+ @env.ui.warn(t("template_other_users", users: others.join(", "))) unless others.empty?
80
+ unless confirm(t("confirm_template_remove", name: info.name), force)
81
+ @env.ui.info(t("skipped"))
82
+ return
83
+ end
84
+
85
+ inventory.remove_template(info)
86
+ @env.ui.success(t("template_removed", name: info.name))
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,333 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "vagrant"
4
+ require "vagrant/util/platform"
5
+ require "vagrant/util/scoped_hash_override"
6
+
7
+ require_relative "extensions"
8
+
9
+ module VagrantPlugins
10
+ module Xapi
11
+ # Provider configuration: config.vm.provider :xapi do |xapi| ... end
12
+ #
13
+ # Only host, username and password are required, and all three can come
14
+ # from VAGRANT_XAPI_HOST, VAGRANT_XAPI_USERNAME and VAGRANT_XAPI_PASSWORD.
15
+ class Config < Vagrant.plugin("2", :config)
16
+ include Vagrant::Util::ScopedHashOverride
17
+ FIRMWARES = %i[bios uefi].freeze
18
+ CLONE_MODES = %i[fast full].freeze
19
+ PACKAGE_FORMATS = %i[xva vhd qcow2].freeze
20
+ IP_SOURCES = %i[guest_metrics static himn].freeze
21
+ FINGERPRINT_RE = /\A(?:[0-9a-fA-F]{2}:){31}[0-9a-fA-F]{2}\z|\A[0-9a-fA-F]{64}\z/.freeze
22
+ RESERVED_PREFIX = "vagrant-xapi."
23
+
24
+ # Connection
25
+ attr_accessor :host, :port, :username, :password
26
+ attr_accessor :tls_verify, :tls_ca_file, :tls_fingerprint
27
+ attr_accessor :api_timeout, :task_timeout
28
+
29
+ # VM
30
+ attr_accessor :name_label, :cpus, :cores_per_socket, :memory, :memory_dynamic_min
31
+ attr_accessor :firmware, :secure_boot, :vtpm
32
+ attr_accessor :sr, :template_sr, :clone_mode, :affinity_host
33
+ attr_accessor :tags, :platform, :other_config, :xva_url, :isos
34
+
35
+ # Network / SSH
36
+ attr_accessor :default_network, :ip_detection, :ssh_via_host, :host_ssh_user
37
+ attr_accessor :forward_ssh_port
38
+
39
+ # Lifecycle
40
+ attr_accessor :shutdown_timeout, :snapshot_memory, :package_format
41
+
42
+ def initialize
43
+ super
44
+ @host = UNSET_VALUE
45
+ @port = UNSET_VALUE
46
+ @username = UNSET_VALUE
47
+ @password = UNSET_VALUE
48
+ @tls_verify = UNSET_VALUE
49
+ @tls_ca_file = UNSET_VALUE
50
+ @tls_fingerprint = UNSET_VALUE
51
+ @api_timeout = UNSET_VALUE
52
+ @task_timeout = UNSET_VALUE
53
+
54
+ @name_label = UNSET_VALUE
55
+ @cpus = UNSET_VALUE
56
+ @cores_per_socket = UNSET_VALUE
57
+ @memory = UNSET_VALUE
58
+ @memory_dynamic_min = UNSET_VALUE
59
+ @firmware = UNSET_VALUE
60
+ @secure_boot = UNSET_VALUE
61
+ @vtpm = UNSET_VALUE
62
+ @sr = UNSET_VALUE
63
+ @template_sr = UNSET_VALUE
64
+ @clone_mode = UNSET_VALUE
65
+ @affinity_host = UNSET_VALUE
66
+ @tags = UNSET_VALUE
67
+ @platform = UNSET_VALUE
68
+ @other_config = UNSET_VALUE
69
+ @xva_url = UNSET_VALUE
70
+ @isos = UNSET_VALUE
71
+
72
+ @default_network = UNSET_VALUE
73
+ @ip_detection = UNSET_VALUE
74
+ @ssh_via_host = UNSET_VALUE
75
+ @host_ssh_user = UNSET_VALUE
76
+ @forward_ssh_port = UNSET_VALUE
77
+
78
+ @shutdown_timeout = UNSET_VALUE
79
+ @snapshot_memory = UNSET_VALUE
80
+ @package_format = UNSET_VALUE
81
+ end
82
+
83
+ # Lists and hashes are combined instead of replaced.
84
+ def merge(other)
85
+ super.tap do |result|
86
+ result.tags = merge_list(@tags, other.tags)
87
+ result.isos = merge_list(@isos, other.isos)
88
+ result.platform = merge_hash(@platform, other.platform)
89
+ result.other_config = merge_hash(@other_config, other.other_config)
90
+ end
91
+ end
92
+
93
+ def finalize!
94
+ @host = env_default(@host, "VAGRANT_XAPI_HOST")
95
+ @username = env_default(@username, "VAGRANT_XAPI_USERNAME")
96
+ @password = env_default(@password, "VAGRANT_XAPI_PASSWORD")
97
+ @port = 443 if @port == UNSET_VALUE
98
+ @tls_verify = true if @tls_verify == UNSET_VALUE
99
+ @tls_ca_file = env_default(@tls_ca_file, "VAGRANT_XAPI_TLS_CA_FILE")
100
+ @tls_fingerprint = env_default(@tls_fingerprint, "VAGRANT_XAPI_TLS_FINGERPRINT")
101
+ @api_timeout = 60 if @api_timeout == UNSET_VALUE
102
+ @task_timeout = 3600 if @task_timeout == UNSET_VALUE
103
+
104
+ @name_label = nil if @name_label == UNSET_VALUE
105
+ @cpus = nil if @cpus == UNSET_VALUE
106
+ @cores_per_socket = nil if @cores_per_socket == UNSET_VALUE
107
+ @memory = nil if @memory == UNSET_VALUE
108
+ @memory_dynamic_min = nil if @memory_dynamic_min == UNSET_VALUE
109
+ @firmware = @firmware == UNSET_VALUE || @firmware.nil? ? nil : @firmware.to_sym
110
+ @secure_boot = false if @secure_boot == UNSET_VALUE
111
+ @vtpm = false if @vtpm == UNSET_VALUE
112
+ @sr = nil if @sr == UNSET_VALUE
113
+ @template_sr = nil if @template_sr == UNSET_VALUE
114
+ @clone_mode = @clone_mode == UNSET_VALUE ? :fast : @clone_mode.to_sym
115
+ @affinity_host = nil if @affinity_host == UNSET_VALUE
116
+ @tags = [] if @tags == UNSET_VALUE
117
+ @platform = {} if @platform == UNSET_VALUE
118
+ @other_config = {} if @other_config == UNSET_VALUE
119
+ @xva_url = nil if @xva_url == UNSET_VALUE
120
+ @isos = [] if @isos == UNSET_VALUE
121
+
122
+ @default_network = env_default(@default_network, "VAGRANT_XAPI_DEFAULT_NETWORK")
123
+ @ip_detection = @ip_detection == UNSET_VALUE ? IP_SOURCES.dup : Array(@ip_detection).map(&:to_sym)
124
+ @ssh_via_host = false if @ssh_via_host == UNSET_VALUE
125
+ @host_ssh_user = "root" if @host_ssh_user == UNSET_VALUE
126
+ @forward_ssh_port = false if @forward_ssh_port == UNSET_VALUE
127
+
128
+ @shutdown_timeout = 120 if @shutdown_timeout == UNSET_VALUE
129
+ @snapshot_memory = false if @snapshot_memory == UNSET_VALUE
130
+ @package_format = @package_format == UNSET_VALUE ? :xva : @package_format.to_sym
131
+ Extensions.config_finalized(self)
132
+ end
133
+
134
+ def validate(machine)
135
+ errors = _detected_errors
136
+ t = ->(key, **opts) { I18n.t("vagrant_xapi.config.#{key}", **opts) }
137
+
138
+ errors << t.call(:host_required) if blank?(@host)
139
+ errors << t.call(:username_required) if blank?(@username)
140
+ errors << t.call(:password_required) if blank?(@password)
141
+ errors << t.call(:port_invalid) unless @port.is_a?(Integer) && @port.between?(1, 65_535)
142
+
143
+ pins = fingerprint_entries
144
+ errors << t.call(:tls_verify_off) if @tls_verify == false && pins.empty?
145
+ if @tls_fingerprint && (pins.empty? || !pins.all? { |pin| FINGERPRINT_RE.match?(pin) })
146
+ errors << t.call(:fingerprint_invalid)
147
+ end
148
+ if @tls_ca_file && !File.file?(File.expand_path(@tls_ca_file.to_s, machine.env.root_path.to_s))
149
+ errors << t.call(:ca_file_missing, path: @tls_ca_file)
150
+ end
151
+
152
+ %i[api_timeout task_timeout shutdown_timeout].each do |opt|
153
+ v = instance_variable_get("@#{opt}")
154
+ errors << t.call(:positive_integer, option: opt) unless v.is_a?(Integer) && v.positive?
155
+ end
156
+
157
+ validate_cpu_memory(errors, t)
158
+ validate_enums(errors, t)
159
+ validate_firmware(errors, t)
160
+ validate_maps(errors, t)
161
+ validate_networks(machine, errors, t)
162
+ validate_disks(machine, errors, t)
163
+ warn_architecture(machine)
164
+
165
+ { "XAPI provider" => errors }
166
+ end
167
+
168
+ # Never show the password.
169
+ def to_s
170
+ "XAPI"
171
+ end
172
+
173
+ def instance_variables_hash
174
+ super.tap { |h| h["password"] = "[REDACTED]" if h.key?("password") && h["password"] }
175
+ end
176
+
177
+ def inspect
178
+ "#<#{self.class.name} host=#{@host.inspect} username=#{@username.inspect} password=[REDACTED]>"
179
+ end
180
+
181
+ private
182
+
183
+ def validate_cpu_memory(errors, t)
184
+ if @cpus
185
+ if !@cpus.is_a?(Integer) || @cpus < 1
186
+ errors << t.call(:cpus_invalid)
187
+ elsif @cores_per_socket && (@cores_per_socket.to_i < 1 || (@cpus % @cores_per_socket.to_i) != 0)
188
+ errors << t.call(:cores_per_socket_invalid, cpus: @cpus, cores: @cores_per_socket)
189
+ end
190
+ elsif @cores_per_socket && (!@cores_per_socket.is_a?(Integer) || @cores_per_socket < 1)
191
+ errors << t.call(:cores_per_socket_positive)
192
+ end
193
+
194
+ if @memory
195
+ errors << t.call(:memory_invalid) if !@memory.is_a?(Integer) || @memory < 256
196
+ if @memory_dynamic_min && @memory.is_a?(Integer) && @memory < @memory_dynamic_min.to_i
197
+ errors << t.call(:memory_below_dynamic_min, memory: @memory, min: @memory_dynamic_min)
198
+ end
199
+ end
200
+ if @memory_dynamic_min && (!@memory_dynamic_min.is_a?(Integer) || @memory_dynamic_min < 256)
201
+ errors << t.call(:memory_dynamic_min_invalid)
202
+ end
203
+ end
204
+
205
+ def validate_enums(errors, t)
206
+ if @firmware && !FIRMWARES.include?(@firmware)
207
+ errors << t.call(:enum_invalid, option: "firmware", value: @firmware, allowed: FIRMWARES.join(", "))
208
+ end
209
+ unless CLONE_MODES.include?(@clone_mode)
210
+ errors << t.call(:enum_invalid, option: "clone_mode", value: @clone_mode, allowed: CLONE_MODES.join(", "))
211
+ end
212
+ unless PACKAGE_FORMATS.include?(@package_format)
213
+ errors << t.call(:enum_invalid, option: "package_format", value: @package_format,
214
+ allowed: PACKAGE_FORMATS.join(", "))
215
+ end
216
+ bad = @ip_detection - IP_SOURCES
217
+ unless bad.empty? && !@ip_detection.empty?
218
+ errors << t.call(:enum_invalid, option: "ip_detection", value: bad.join(", "),
219
+ allowed: IP_SOURCES.join(", "))
220
+ end
221
+ if @ip_detection == [:himn] && !@ssh_via_host
222
+ errors << t.call(:himn_needs_ssh_via_host)
223
+ end
224
+ end
225
+
226
+ def validate_firmware(errors, t)
227
+ errors << t.call(:secure_boot_needs_uefi) if @secure_boot && @firmware != :uefi
228
+ errors << t.call(:vtpm_needs_uefi) if @vtpm && @firmware != :uefi
229
+ end
230
+
231
+ def validate_maps(errors, t)
232
+ errors << t.call(:hash_expected, option: "platform") unless @platform.is_a?(Hash)
233
+ if @other_config.is_a?(Hash)
234
+ reserved = @other_config.keys.map(&:to_s).select { |k| k.start_with?(RESERVED_PREFIX) }
235
+ errors << t.call(:other_config_reserved, keys: reserved.join(", ")) unless reserved.empty?
236
+ else
237
+ errors << t.call(:hash_expected, option: "other_config")
238
+ end
239
+ errors << t.call(:array_expected, option: "tags") unless @tags.is_a?(Array)
240
+ errors << t.call(:array_expected, option: "isos") unless @isos.is_a?(Array)
241
+ end
242
+
243
+ def validate_networks(machine, errors, t)
244
+ machine.config.vm.networks.each do |type, opts|
245
+ case type
246
+ when :forwarded_port
247
+ next if opts[:id] == "ssh" && !@forward_ssh_port
248
+ # Never forwarded (Glue.forwarded_ports), so nothing to check.
249
+ next if opts[:disabled]
250
+
251
+ if opts[:protocol].to_s == "udp"
252
+ errors << t.call(:forwarded_port_udp, guest: opts[:guest], host: opts[:host])
253
+ end
254
+ if opts[:host].to_i < 1024
255
+ errors << t.call(:forwarded_port_privileged, host: opts[:host])
256
+ end
257
+ when :private_network
258
+ scoped = scoped_options(opts)
259
+ if scoped[:network].nil? && scoped[:bridge].nil?
260
+ if blank?(scoped[:ip]) || scoped[:type].to_s == "dhcp"
261
+ errors << t.call(:private_network_needs_ip)
262
+ end
263
+ end
264
+ end
265
+ end
266
+ end
267
+
268
+ def validate_disks(machine, errors, t)
269
+ machine.config.vm.disks.each do |disk|
270
+ errors << t.call(:floppy_unsupported, name: disk.name) if disk.type == :floppy
271
+ end
272
+ end
273
+
274
+ # Only while Vagrant still has to find or download the box: with
275
+ # box_architecture :auto it then looks for one of this computer's
276
+ # architecture, which XenServer and XCP-ng cannot run. A box that is
277
+ # already there was found, so there is nothing to say (Vagrantfiles of
278
+ # vagrant-xenserver on an arm64 Mac, say).
279
+ def warn_architecture(machine)
280
+ return if self.class.instance_variable_get(:@arch_warned)
281
+ return unless machine.config.vm.box_architecture.to_s == "auto"
282
+
283
+ host_arch = Vagrant::Util::Platform.architecture.to_s
284
+ return if host_arch == "amd64"
285
+ return if machine.config.vm.box.to_s.empty? || box_present?(machine)
286
+
287
+ self.class.instance_variable_set(:@arch_warned, true)
288
+ machine.ui.warn(I18n.t("vagrant_xapi.config.architecture_warning", arch: host_arch))
289
+ end
290
+
291
+ def box_present?(machine)
292
+ !machine.box.nil?
293
+ rescue StandardError
294
+ false
295
+ end
296
+
297
+ def scoped_options(opts)
298
+ scoped_hash_override(opts, :xapi)
299
+ end
300
+
301
+ def env_default(value, var)
302
+ return value unless value == UNSET_VALUE
303
+
304
+ ENV[var].nil? || ENV[var].empty? ? nil : ENV[var]
305
+ end
306
+
307
+ # tls_fingerprint: one fingerprint, several separated by commas or
308
+ # whitespace, or an Array of them.
309
+ def fingerprint_entries
310
+ Array(@tls_fingerprint).flat_map { |v| v.to_s.split(/[\s,]+/) }.reject(&:empty?)
311
+ end
312
+
313
+ def blank?(value)
314
+ value.nil? || value.to_s.strip.empty?
315
+ end
316
+
317
+ def merge_list(a, b)
318
+ a = [] if a == UNSET_VALUE
319
+ b = [] if b == UNSET_VALUE
320
+ return b if a.nil?
321
+ return a if b.nil?
322
+
323
+ (Array(a) + Array(b)).uniq
324
+ end
325
+
326
+ def merge_hash(a, b)
327
+ a = {} if a == UNSET_VALUE
328
+ b = {} if b == UNSET_VALUE
329
+ (a || {}).merge(b || {})
330
+ end
331
+ end
332
+ end
333
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "vagrant"
4
+
5
+ require_relative "api/errors"
6
+ require_relative "api/redactor"
7
+ require_relative "services/base"
8
+
9
+ module VagrantPlugins
10
+ module Xapi
11
+ # Every error the user can see is a VagrantError with text from
12
+ # locales/en.yml. A stack trace is a bug.
13
+ module Errors
14
+ class XapiError < Vagrant::Errors::VagrantError
15
+ error_namespace("vagrant_xapi.errors")
16
+
17
+ # A missing interpolation value must never turn into a stack trace.
18
+ def translate_error(opts)
19
+ values = opts.dup
20
+ 5.times do
21
+ return super(values)
22
+ rescue I18n::MissingInterpolationArgument => e
23
+ values[e.key.to_sym] = "?"
24
+ end
25
+ super(values)
26
+ end
27
+
28
+ # Never let a secret slip into a message (Exception#message calls
29
+ # to_s).
30
+ def to_s
31
+ Api::Redactor.scrub(super)
32
+ end
33
+ end
34
+
35
+ # Specific classes so callers can rescue them by type.
36
+ class ConnectionFailed < XapiError
37
+ error_key(:connection_failed)
38
+ end
39
+
40
+ class AuthFailed < XapiError
41
+ error_key(:auth_failed)
42
+ end
43
+
44
+ class ServiceFailed < XapiError; end
45
+
46
+ XAPI_CODES = {
47
+ "SESSION_AUTHENTICATION_FAILED" => :auth_failed,
48
+ "RBAC_PERMISSION_DENIED" => :permission_denied,
49
+ "SR_FULL" => :sr_full,
50
+ "SR_BACKEND_FAILURE_44" => :sr_full,
51
+ "HOST_NOT_ENOUGH_FREE_MEMORY" => :not_enough_memory,
52
+ "NO_HOSTS_AVAILABLE" => :no_hosts_available,
53
+ "VM_BAD_POWER_STATE" => :bad_power_state,
54
+ "LICENCE_RESTRICTION" => :licence_restriction,
55
+ "VM_REQUIRES_SR" => :vm_requires_sr,
56
+ "VM_REQUIRES_NETWORK" => :vm_requires_network,
57
+ "OPERATION_NOT_ALLOWED" => :operation_not_allowed,
58
+ "OTHER_OPERATION_IN_PROGRESS" => :operation_in_progress,
59
+ "VM_MISSING_PV_DRIVERS" => :suspend_no_drivers,
60
+ "VDI_IN_USE" => :vdi_in_use,
61
+ "HOST_OFFLINE" => :host_offline
62
+ }.freeze
63
+
64
+ module_function
65
+
66
+ # Turns any client or service error into a VagrantError.
67
+ # @return [Exception] the translated error, or the original one
68
+ def translate(error)
69
+ case error
70
+ when Vagrant::Errors::VagrantError
71
+ error
72
+ when Services::Error
73
+ key = error.key
74
+ key = :generic_service_error unless I18n.exists?("vagrant_xapi.errors.#{key}")
75
+ ServiceFailed.new(key, scrub(error.params).merge(error_key: error.key.to_s))
76
+ when Api::XapiError
77
+ key = XAPI_CODES[error.code] || :xapi_error
78
+ ServiceFailed.new(key, code: error.code, params: scrub(error.params.join(", ")),
79
+ method: error.method_name.to_s,
80
+ first: scrub(error.params[0].to_s), second: scrub(error.params[1].to_s),
81
+ third: scrub(error.params[2].to_s))
82
+ when Api::ConnectionError
83
+ ConnectionFailed.new(host: error.host, reason: scrub(error.reason))
84
+ when Api::TlsError
85
+ ServiceFailed.new(:tls_failed, host: error.host, reason: scrub(error.reason))
86
+ when Api::PoolMemberError
87
+ ServiceFailed.new(:pool_member_pinned, host: error.host, master: error.master)
88
+ when Api::TaskTimeout
89
+ ServiceFailed.new(:task_timeout, label: error.label, timeout: error.timeout)
90
+ when Api::TaskCancelled
91
+ ServiceFailed.new(:cancelled, label: error.label)
92
+ when Api::HttpError
93
+ ServiceFailed.new(:http_error, status: error.status, path: error.path, detail: scrub(error.message))
94
+ when Api::Error
95
+ ServiceFailed.new(:protocol_error, detail: scrub(error.message))
96
+ else
97
+ error
98
+ end
99
+ end
100
+
101
+ # Runs the block and re-raises client/service errors as VagrantErrors.
102
+ def wrap
103
+ yield
104
+ rescue Api::Error, Services::Error => e
105
+ raise translate(e)
106
+ end
107
+
108
+ def scrub(value)
109
+ Api::Redactor.scrub(value)
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VagrantPlugins
4
+ module Xapi
5
+ # Places where a layer hooks into the core: the compatibility with
6
+ # vagrant-xenserver (lib/vagrant-xapi/xenserver) and the temporary
7
+ # XenApi stand-in (lib/vagrant-xapi/legacy); see docs/architecture.md,
8
+ # section 17. The core only calls these and never names a layer.
9
+ # Without one, they change nothing.
10
+ module Extensions
11
+ @provider_names = [:xapi]
12
+ @id_resolvers = []
13
+ @before_destroy = []
14
+ @config_finalized = []
15
+ @subcommands = {}
16
+
17
+ class << self
18
+ # Providers whose machines this plugin runs: their data dirs
19
+ # (machines/<name>/<provider>), entries in Vagrant's machine index,
20
+ # and the machines `vagrant xapi` works on.
21
+ def provider_names
22
+ @provider_names.dup
23
+ end
24
+
25
+ def add_provider_name(name)
26
+ @provider_names |= [name.to_sym]
27
+ end
28
+
29
+ def provider?(name)
30
+ !name.nil? && @provider_names.include?(name.to_s.to_sym)
31
+ end
32
+
33
+ # block.call(machine, ctx) when machine.id is not a VM UUID, e.g. an
34
+ # id written by another plugin. It may set machine.id to a VM UUID.
35
+ def on_foreign_id(&block)
36
+ @id_resolvers << block
37
+ end
38
+
39
+ def resolve_foreign_id(machine, ctx)
40
+ # Here, not at the top: plugin.rb loads this file before any service.
41
+ require_relative "services/lookup"
42
+ @id_resolvers.each do |resolver|
43
+ resolver.call(machine, ctx)
44
+ break if machine.id.nil? || Services::Lookup.uuid?(machine.id)
45
+ end
46
+ end
47
+
48
+ # block.call(machine, ctx, vm_ref) before `vagrant destroy` removes
49
+ # the VM, e.g. to mark more disks as the VM's own.
50
+ def on_before_destroy(&block)
51
+ @before_destroy << block
52
+ end
53
+
54
+ def before_destroy(machine, ctx, vm_ref)
55
+ @before_destroy.each { |hook| hook.call(machine, ctx, vm_ref) }
56
+ end
57
+
58
+ # block.call(config) after a provider config (of any of
59
+ # provider_names) was finalized.
60
+ def on_config_finalized(&block)
61
+ @config_finalized << block
62
+ end
63
+
64
+ def config_finalized(config)
65
+ @config_finalized.each { |hook| hook.call(config) }
66
+ end
67
+
68
+ # Extra `vagrant xapi` subcommands: name => [file to require, class
69
+ # name in Command, description].
70
+ def add_subcommand(name, file, klass, description)
71
+ @subcommands[name.to_sym] = [file, klass, description]
72
+ end
73
+
74
+ def subcommands
75
+ @subcommands.dup
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end