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,330 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "log4r"
5
+ require "pathname"
6
+ require "securerandom"
7
+ require "vagrant/util/scoped_hash_override"
8
+ require "vagrant/util/subprocess"
9
+ require "vagrant/util/which"
10
+
11
+ require_relative "api/session_registry"
12
+ require_relative "errors"
13
+ require_relative "services/base"
14
+ require_relative "services/lookup"
15
+ require_relative "services/networks"
16
+ require_relative "services/ip_detector"
17
+ require_relative "services/vm_builder"
18
+ require_relative "services/tunnels"
19
+
20
+ module VagrantPlugins
21
+ module Xapi
22
+ # Translates between Vagrant (machine, config, ui) and the services.
23
+ # This is the only place besides actions/caps/commands that knows both.
24
+ module Glue
25
+ extend Vagrant::Util::ScopedHashOverride
26
+
27
+ # Under "vagrantplugins", so VAGRANT_LOG shows it (Vagrant only sets up
28
+ # the "vagrant" and "vagrantplugins" loggers). Lines appear as "xapi: ...".
29
+ LOGGER = Log4r::Logger.new("vagrantplugins::xapi")
30
+
31
+ # Maps service message keys to Vagrant UI output, with progress on one
32
+ # rewritten line.
33
+ class UiAdapter
34
+ def initialize(ui)
35
+ @ui = ui
36
+ @last = nil
37
+ end
38
+
39
+ def info(key, **params)
40
+ @ui&.info(t(key, **params))
41
+ end
42
+
43
+ def detail(key, **params)
44
+ @ui&.detail(t(key, **params))
45
+ end
46
+
47
+ def warn(key, **params)
48
+ @ui&.warn(t(key, **params))
49
+ end
50
+
51
+ def progress(key, done, total, **params)
52
+ return unless @ui
53
+
54
+ pct = total.to_f.positive? ? (done.to_f / total * 100).floor : nil
55
+ text = pct ? "#{t(key, **params)} #{pct}%" : "#{t(key, **params)} #{format_bytes(done)}"
56
+ return if text == @last
57
+
58
+ @last = text
59
+ @ui.clear_line
60
+ @ui.detail(text, new_line: false)
61
+ end
62
+
63
+ def clear_progress
64
+ return unless @ui && @last
65
+
66
+ @ui.clear_line
67
+ @last = nil
68
+ end
69
+
70
+ private
71
+
72
+ def t(key, **params)
73
+ Api::Redactor.scrub(I18n.t("vagrant_xapi.messages.#{key}", **params))
74
+ end
75
+
76
+ def format_bytes(n)
77
+ return "#{n} B" if n < 1024
78
+ return format("%.1f MiB", n / 1024.0**2) if n < 1024**3
79
+
80
+ format("%.2f GiB", n / 1024.0**3)
81
+ end
82
+ end
83
+
84
+ module_function
85
+
86
+ def client(machine)
87
+ cfg = machine.provider_config
88
+ missing = { host: cfg.host, username: cfg.username, password: cfg.password }
89
+ .select { |_k, v| v.nil? || v.to_s.empty? }.keys
90
+ raise Errors::ServiceFailed.new(:missing_credentials, options: missing.join(", ")) unless missing.empty?
91
+
92
+ Errors.wrap do
93
+ Api::SessionRegistry.fetch(
94
+ host: cfg.host, port: cfg.port, username: cfg.username, password: cfg.password,
95
+ tls: tls(cfg, machine.env.root_path), api_timeout: cfg.api_timeout, task_timeout: cfg.task_timeout,
96
+ logger: LOGGER
97
+ )
98
+ end
99
+ end
100
+
101
+ # tls_ca_file is relative to the Vagrantfile's directory, like the
102
+ # check in Config#validate (Vagrant may run from a subdirectory).
103
+ def tls(cfg, root = nil)
104
+ {
105
+ verify: true,
106
+ ca_file: cfg.tls_ca_file ? File.expand_path(cfg.tls_ca_file.to_s, root ? root.to_s : Dir.pwd) : nil,
107
+ fingerprint: cfg.tls_fingerprint
108
+ }.compact
109
+ end
110
+
111
+ # Runs commands in a machine's guest for the services (as root, through
112
+ # Vagrant's communicator), see Services::GuestFiles.
113
+ class GuestShell
114
+ def initialize(machine)
115
+ @machine = machine
116
+ end
117
+
118
+ # @return [Array(Integer, String, String)] exit status, stdout, stderr
119
+ def run(command)
120
+ out = +""
121
+ err = +""
122
+ status = @machine.communicate.execute(command, sudo: true, error_check: false) do |type, data|
123
+ (type == :stdout ? out : err) << data.to_s.b
124
+ end
125
+ [status.to_i, out, err]
126
+ end
127
+
128
+ def upload(local, remote)
129
+ @machine.communicate.upload(local, remote)
130
+ end
131
+
132
+ def download(remote, local)
133
+ @machine.communicate.download(remote, local)
134
+ end
135
+ end
136
+
137
+ # @param env [Hash, nil] action env (for env[:interrupted] and env[:ui])
138
+ # @param cancel [#call, nil] Ctrl-C flag of a command
139
+ # @param ui [Vagrant::UI::Interface, nil] instead of the machine's UI
140
+ def context(machine, env = nil, client: nil, cancel: nil, ui: nil)
141
+ ui ||= (env && env[:ui]) || machine.ui
142
+ cancel ||= if env
143
+ -> { env[:interrupted] ? true : false }
144
+ else
145
+ Thread.current[:vagrant_xapi_cancel]
146
+ end
147
+ Services::Context.new(
148
+ client: client || self.client(machine),
149
+ ui: UiAdapter.new(ui),
150
+ cancel: cancel,
151
+ logger: LOGGER
152
+ )
153
+ end
154
+
155
+ # @return [String, nil] VM ref, nil when the machine is not created
156
+ def vm_ref(machine, ctx)
157
+ return nil if machine.id.nil?
158
+
159
+ Errors.wrap { Services::Lookup.new(ctx).vm_by_uuid(machine.id) }
160
+ end
161
+
162
+ def project_path(machine)
163
+ machine.env.root_path.to_s
164
+ end
165
+
166
+ def name_label(machine)
167
+ machine.provider_config.name_label ||
168
+ "#{File.basename(machine.env.root_path.to_s)}_#{machine.name}"
169
+ end
170
+
171
+ def vm_spec(machine)
172
+ cfg = machine.provider_config
173
+ Services::VmSpec.new(
174
+ name_label: name_label(machine),
175
+ description: "Vagrant machine '#{machine.name}' of #{project_path(machine)} (vagrant-xapi)",
176
+ cpus: cfg.cpus,
177
+ cores_per_socket: cfg.cores_per_socket,
178
+ memory: cfg.memory,
179
+ memory_dynamic_min: cfg.memory_dynamic_min,
180
+ firmware: cfg.firmware,
181
+ # BIOS cannot boot securely: a VM switched from :uefi to :bios
182
+ # loses platform:secureboot. Without firmware the template decides.
183
+ secure_boot: { uefi: cfg.secure_boot, bios: false }[cfg.firmware],
184
+ vtpm: cfg.vtpm,
185
+ affinity_host: cfg.affinity_host,
186
+ tags: cfg.tags,
187
+ platform: cfg.platform,
188
+ other_config: cfg.other_config,
189
+ marking: Services::Marking.vm_marking(machine_name: machine.name, project: project_path(machine),
190
+ client_id: client_id(machine.env))
191
+ )
192
+ end
193
+
194
+ # Vagrant networks with xapi__ options applied.
195
+ def networks(machine)
196
+ machine.config.vm.networks.map do |type, opts|
197
+ [type, scoped_hash_override(opts || {}, :xapi)]
198
+ end
199
+ end
200
+
201
+ def himn?(machine)
202
+ cfg = machine.provider_config
203
+ cfg.ip_detection.include?(:himn) && cfg.ssh_via_host
204
+ end
205
+
206
+ def vif_plan(machine)
207
+ Services::Networks.plan(networks(machine),
208
+ default_network: machine.provider_config.default_network,
209
+ himn: himn?(machine),
210
+ project: project_path(machine))
211
+ end
212
+
213
+ # ip: of VIF device 0, if static
214
+ def static_ip(machine)
215
+ first = vif_plan(machine).first
216
+ first&.static_ip? ? first.ip.to_s : nil
217
+ end
218
+
219
+ def ip_detector(machine, ctx)
220
+ Services::IpDetector.new(ctx, sources: machine.provider_config.ip_detection,
221
+ static_ip: static_ip(machine), himn_enabled: himn?(machine))
222
+ end
223
+
224
+ # Option key under which PrepareForwardedPortCollisionParams keeps the
225
+ # Vagrantfile's host port, before HandleForwardedPortCollisions
226
+ # overwrites options[:host] with an auto-corrected one.
227
+ CONFIGURED_HOST = :xapi_configured_host
228
+
229
+ def forwarded_ports(machine)
230
+ cfg = machine.provider_config
231
+ machine.config.vm.networks.filter_map do |type, opts|
232
+ next unless type == :forwarded_port
233
+ next if opts[:id] == "ssh" && !cfg.forward_ssh_port
234
+ next if opts[:disabled]
235
+
236
+ Services::Tunnels::Forward.new(
237
+ id: opts[:id], host_ip: opts[:host_ip] || "127.0.0.1", host: opts[:host].to_i,
238
+ guest_ip: opts[:guest_ip] || "127.0.0.1", guest: opts[:guest].to_i,
239
+ configured_host: (opts[CONFIGURED_HOST] || opts[:host]).to_i, auto_correct: !!opts[:auto_correct]
240
+ )
241
+ end
242
+ end
243
+
244
+ # A random id for this Vagrant installation (machine and user), stored
245
+ # in the Vagrant home (~/.vagrant.d). Hostname and login name alone
246
+ # collide across containers, CI runners and machines with the same names.
247
+ CLIENT_ID_MUTEX = Mutex.new
248
+
249
+ def client_id(env)
250
+ path = data_home(env).join("client-id")
251
+ CLIENT_ID_MUTEX.synchronize do
252
+ existing = read_client_id(path)
253
+ return existing if existing
254
+
255
+ # Write a temp file and hard-link it into place: link fails when
256
+ # another process was first, and nobody ever sees a half file.
257
+ FileUtils.mkdir_p(path.dirname)
258
+ tmp = path.sub_ext(".#{Process.pid}.#{SecureRandom.hex(4)}.tmp")
259
+ File.write(tmp, SecureRandom.uuid)
260
+ begin
261
+ File.link(tmp, path)
262
+ rescue Errno::EEXIST
263
+ nil
264
+ ensure
265
+ FileUtils.rm_f(tmp)
266
+ end
267
+ read_client_id(path) || raise(Errors::ServiceFailed.new(:client_id_unreadable, path: path.to_s))
268
+ end
269
+ end
270
+
271
+ def read_client_id(path)
272
+ return nil unless path.file?
273
+
274
+ id = path.read.strip
275
+ id.empty? ? nil : id
276
+ end
277
+
278
+ def data_path(machine)
279
+ machine.data_dir
280
+ end
281
+
282
+ # VM UUIDs of all machines of this plugin in Vagrant's machine index
283
+ # (any project, any dotfile path, any of Extensions.provider_names).
284
+ def indexed_vm_ids(env)
285
+ ids = []
286
+ env.machine_index.each do |entry|
287
+ id = indexed_vm_id(entry)
288
+ ids << id if id
289
+ end
290
+ ids
291
+ rescue StandardError => e
292
+ LOGGER.warn("could not read Vagrant's machine index: #{e.message}")
293
+ ids || []
294
+ end
295
+
296
+ # The VM UUID of one index entry, or nil. An entry that cannot be read
297
+ # (e.g. during a parallel destroy) is skipped, not the ones after it:
298
+ # the caller keeps the NFS exports of every machine it gets.
299
+ def indexed_vm_id(entry)
300
+ return nil unless Extensions.provider?(entry.provider) && entry.local_data_path
301
+
302
+ id_file = Pathname.new(entry.local_data_path.to_s).join("machines", entry.name.to_s, entry.provider.to_s, "id")
303
+ id = id_file.file? ? id_file.read.strip : ""
304
+ id.empty? ? nil : id
305
+ rescue StandardError => e
306
+ LOGGER.warn("could not read the id of machine #{entry.name}: #{e.message}")
307
+ nil
308
+ end
309
+
310
+ # Our directory in the Vagrant home (~/.vagrant.d/data/vagrant-xapi).
311
+ def data_home(env)
312
+ Pathname.new(env.home_path.to_s).join("data", "vagrant-xapi")
313
+ end
314
+
315
+ # qcow2 -> VHD with qemu-img (argv array, no shell), for hosts that
316
+ # cannot import qcow2.
317
+ def convert_qcow2(src, dst)
318
+ raise Services::Error.new(:qemu_img_missing) unless Vagrant::Util::Which.which("qemu-img")
319
+
320
+ result = Vagrant::Util::Subprocess.execute(
321
+ # -f qcow2: never let qemu-img guess the input format.
322
+ "qemu-img", "convert", "-f", "qcow2", "-O", "vpc", "-o", "subformat=dynamic", src.to_s, dst.to_s
323
+ )
324
+ return if result.exit_code.zero?
325
+
326
+ raise Services::Error.new(:qemu_img_failed, detail: result.stderr.to_s.strip[0, 500])
327
+ end
328
+ end
329
+ end
330
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Temporary: a stand-in for XenApi::Client, the client of the `xenapi` gem
4
+ # that vagrant-xenserver brought along, so triggers in Vagrantfiles written
5
+ # for it that `require "xenapi"` keep working for now (xenapi.rb, loaded
6
+ # through lib/xenapi.rb). It says so once per run when a trigger uses it.
7
+ #
8
+ # Only this directory, lib/xenapi.rb and locales/legacy.en.yml belong to it;
9
+ # docs/architecture.md section 17 lists what to delete to remove it. The
10
+ # compatibility with vagrant-xenserver itself (lib/vagrant-xapi/xenserver)
11
+ # is permanent and does not depend on it.
12
+
13
+ require "pathname"
14
+
15
+ require_relative "../extensions"
16
+
17
+ module VagrantPlugins
18
+ module Xapi
19
+ module Legacy
20
+ # Port, timeout and TLS settings per host, from the provider configs of
21
+ # this run, for XenApi::Client, which only gets a URI.
22
+ def self.connections
23
+ @connections ||= {}
24
+ end
25
+
26
+ def self.remember(config)
27
+ return if config.host.nil? || config.host.to_s.empty?
28
+
29
+ ca_file = config.tls_ca_file && File.expand_path(config.tls_ca_file.to_s, project_root)
30
+ connections[config.host.to_s] = {
31
+ port: config.port, api_timeout: config.api_timeout,
32
+ tls: { verify: true, ca_file: ca_file, fingerprint: config.tls_fingerprint }.compact
33
+ }
34
+ end
35
+
36
+ # The project root as Vagrant finds it (the directory of the
37
+ # Vagrantfile, from VAGRANT_CWD or upwards from the current one), so a
38
+ # relative tls_ca_file means the same as for the provider.
39
+ def self.project_root
40
+ name = ENV["VAGRANT_VAGRANTFILE"].to_s.empty? ? "Vagrantfile" : ENV["VAGRANT_VAGRANTFILE"]
41
+ dir = Pathname.new(File.expand_path(ENV["VAGRANT_CWD"].to_s.empty? ? Dir.pwd : ENV["VAGRANT_CWD"]))
42
+ dir.ascend { |d| return d.to_s if d.join(name).file? }
43
+ Dir.pwd
44
+ end
45
+
46
+ # Once per process, the first time a trigger creates a XenApi::Client.
47
+ # Straight to stderr: Kernel#warn prints nothing when Ruby runs without
48
+ # warnings ($VERBOSE nil), as Vagrant's installer does.
49
+ def self.xenapi_notice
50
+ return if @noticed
51
+
52
+ @noticed = true
53
+ $stderr.puts I18n.t("vagrant_xapi.legacy.xenapi_notice")
54
+ end
55
+ end
56
+
57
+ Extensions.on_config_finalized { |config| Legacy.remember(config) }
58
+
59
+ I18n.load_path << File.expand_path("../../../locales/legacy.en.yml", __dir__)
60
+ end
61
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ require_relative "../api/client"
6
+ require_relative "plugin"
7
+
8
+ # Temporary: a stand-in for the client of the `xenapi` gem that
9
+ # vagrant-xenserver brought along, so triggers in Vagrantfiles written for it
10
+ # keep working (see legacy/plugin.rb). It says once per run that it is
11
+ # temporary.
12
+ #
13
+ # xc = XenApi::Client.new("https://host:443/", 60)
14
+ # xc.login_with_password(user, password)
15
+ # xc.VM.get_VBDs(vm) # any XAPI call: xc.<Class>.<method>(...)
16
+ # xc.session.logout
17
+ #
18
+ #
19
+ # It runs on vagrant-xapi's own client: always HTTPS, with the port and TLS
20
+ # settings of the provider config for that host (or VAGRANT_XAPI_TLS_*),
21
+ # its own session, and HOST_IS_SLAVE handling. XAPI errors become
22
+ # XenApi::Errors::<CamelCaseCode> (all StandardError).
23
+ #
24
+ # Machine ids of vagrant-xapi are VM UUIDs, where vagrant-xenserver used the
25
+ # OpaqueRef. A UUID is passed on as it is, so XAPI answers HANDLE_INVALID to
26
+ # code that uses machine.id as a reference; trigger code written for
27
+ # vagrant-xenserver then falls back to looking the VM up by name. Turning the
28
+ # UUID into a ref here would not be enough: such code also puts it into
29
+ # records and compares it with refs XAPI returns.
30
+ module XenApi
31
+ module Errors
32
+ class GenericError < StandardError
33
+ attr_reader :code, :params
34
+
35
+ def initialize(code = "INTERNAL_ERROR", params = [])
36
+ @code = code.to_s
37
+ @params = Array(params)
38
+ super([@code, *@params].join(" "))
39
+ end
40
+ end
41
+
42
+ def self.class_for(code)
43
+ name = code.to_s.downcase.split(/[^a-z0-9]+/).map(&:capitalize).join
44
+ return GenericError unless name.match?(/\A[A-Z][A-Za-z0-9]*\z/)
45
+ return const_get(name, false) if const_defined?(name, false)
46
+
47
+ const_set(name, Class.new(GenericError))
48
+ end
49
+
50
+ # The ones code written for vagrant-xenserver may rescue by name.
51
+ class_for("SESSION_AUTHENTICATION_FAILED")
52
+ class_for("HANDLE_INVALID")
53
+ end
54
+
55
+ class Client
56
+ # One namespace of calls: xc.VM, xc.pool, xc.session, ... A BasicObject,
57
+ # so that calls named like methods of Object (VM.clone, VM.hash) reach
58
+ # XAPI too.
59
+ class Namespace < BasicObject
60
+ def initialize(client, klass)
61
+ @client = client
62
+ @klass = klass
63
+ end
64
+
65
+ def method_missing(name, *args)
66
+ @client.call("#{@klass}.#{name}", *args)
67
+ end
68
+
69
+ def respond_to_missing?(*)
70
+ true
71
+ end
72
+ end
73
+
74
+ def initialize(uri, timeout = 60, *_rest)
75
+ VagrantPlugins::Xapi::Legacy.xenapi_notice
76
+ @host = URI(uri.to_s).host
77
+ @settings = connection_settings(@host, timeout)
78
+ end
79
+
80
+ def login_with_password(username, password, *_rest)
81
+ @client = VagrantPlugins::Xapi::Api::Client.new(
82
+ host: @host, port: @settings[:port], username: username, password: password,
83
+ tls: @settings[:tls], api_timeout: @settings[:api_timeout]
84
+ )
85
+ translate_errors { @client.login.session_ref }
86
+ end
87
+
88
+ def call(method, *args)
89
+ raise Errors::GenericError.new("SESSION_INVALID", ["not logged in"]) unless @client
90
+ return logout if method == "session.logout"
91
+
92
+ translate_errors { @client.call(method, *args) }
93
+ end
94
+
95
+ def logout
96
+ @client&.logout
97
+ ""
98
+ end
99
+
100
+ def method_missing(name, *args)
101
+ return super unless args.empty? && name.to_s.match?(/\A[A-Za-z_][A-Za-z0-9_]*\z/)
102
+
103
+ Namespace.new(self, name.to_s)
104
+ end
105
+
106
+ def respond_to_missing?(name, include_private = false)
107
+ name.to_s.match?(/\A[A-Za-z_][A-Za-z0-9_]*\z/) || super
108
+ end
109
+
110
+ private
111
+
112
+ def connection_settings(host, timeout)
113
+ known = VagrantPlugins::Xapi::Legacy.connections[host.to_s]
114
+ return known if known
115
+
116
+ env = ->(name) { ENV[name].to_s.empty? ? nil : ENV[name] }
117
+ ca_file = env.call("VAGRANT_XAPI_TLS_CA_FILE")
118
+ { port: 443, api_timeout: timeout.to_i.positive? ? timeout.to_i : 60,
119
+ tls: { verify: true, ca_file: ca_file && File.expand_path(ca_file),
120
+ fingerprint: env.call("VAGRANT_XAPI_TLS_FINGERPRINT") }.compact }
121
+ end
122
+
123
+ def translate_errors
124
+ yield
125
+ rescue VagrantPlugins::Xapi::Api::XapiError => e
126
+ raise Errors.class_for(e.code).new(e.code, e.params)
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "vagrant"
5
+ rescue LoadError
6
+ raise "The vagrant-xapi plugin must be run within Vagrant."
7
+ end
8
+
9
+ require_relative "extensions"
10
+
11
+ module VagrantPlugins
12
+ module Xapi
13
+ # Registrations only; every component is loaded lazily in its block, so
14
+ # this file always loads.
15
+ class Plugin < Vagrant.plugin("2")
16
+ name "xapi"
17
+ description <<~DESC
18
+ Manages VMs on XenServer 8 and XCP-ng through XAPI.
19
+ DESC
20
+
21
+ # Only chosen when a Vagrantfile configures :xapi or --provider=xapi
22
+ # is given; it never becomes the default by accident.
23
+ provider(:xapi, parallel: true, defaultable: false, priority: 5,
24
+ box_format: %w[xapi xenserver libvirt]) do
25
+ require_relative "provider"
26
+ Provider
27
+ end
28
+
29
+ config(:xapi, :provider) do
30
+ require_relative "config"
31
+ Config
32
+ end
33
+
34
+ CAPABILITIES = %i[snapshot_list forwarded_ports public_address nic_mac_addresses configure_disks
35
+ cleanup_disks validate_disk_ext default_disk_exts set_default_disk_ext].freeze
36
+
37
+ CAPABILITIES.each do |cap|
38
+ provider_capability(:xapi, cap) do
39
+ require_relative "cap"
40
+ Cap
41
+ end
42
+ end
43
+
44
+ command("xapi", primary: false) do
45
+ require_relative "command/root"
46
+ Command::Root
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ # Compatibility with Vagrantfiles of vagrant-xenserver (provider :xenserver).
53
+ require_relative "xenserver/plugin"
54
+
55
+ # Temporary: the XenApi::Client stand-in for triggers in those Vagrantfiles.
56
+ # To remove it, delete this line and lib/vagrant-xapi/legacy (see
57
+ # docs/architecture.md, section 17).
58
+ require_relative "legacy/plugin"