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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +83 -0
- data/LICENSE +21 -0
- data/README.md +460 -4
- data/docs/architecture.md +1420 -0
- data/docs/operations.md +531 -0
- data/lib/vagrant-xapi/action/base.rb +103 -0
- data/lib/vagrant-xapi/action/cloud_init.rb +130 -0
- data/lib/vagrant-xapi/action/create.rb +127 -0
- data/lib/vagrant-xapi/action/destroy.rb +68 -0
- data/lib/vagrant-xapi/action/network.rb +229 -0
- data/lib/vagrant-xapi/action/package.rb +103 -0
- data/lib/vagrant-xapi/action/power.rb +185 -0
- data/lib/vagrant-xapi/action/snapshots.rb +43 -0
- data/lib/vagrant-xapi/action.rb +317 -0
- data/lib/vagrant-xapi/api/client.rb +393 -0
- data/lib/vagrant-xapi/api/errors.rb +98 -0
- data/lib/vagrant-xapi/api/http_transfer.rb +332 -0
- data/lib/vagrant-xapi/api/redactor.rb +77 -0
- data/lib/vagrant-xapi/api/session_registry.rb +59 -0
- data/lib/vagrant-xapi/api/task.rb +156 -0
- data/lib/vagrant-xapi/api/transport.rb +249 -0
- data/lib/vagrant-xapi/cap.rb +90 -0
- data/lib/vagrant-xapi/command/adopt.rb +92 -0
- data/lib/vagrant-xapi/command/base.rb +79 -0
- data/lib/vagrant-xapi/command/console.rb +87 -0
- data/lib/vagrant-xapi/command/diff.rb +267 -0
- data/lib/vagrant-xapi/command/hosts.rb +63 -0
- data/lib/vagrant-xapi/command/info.rb +94 -0
- data/lib/vagrant-xapi/command/marked.rb +85 -0
- data/lib/vagrant-xapi/command/orphans.rb +96 -0
- data/lib/vagrant-xapi/command/root.rb +55 -0
- data/lib/vagrant-xapi/command/templates.rb +91 -0
- data/lib/vagrant-xapi/config.rb +333 -0
- data/lib/vagrant-xapi/errors.rb +113 -0
- data/lib/vagrant-xapi/extensions.rb +80 -0
- data/lib/vagrant-xapi/glue.rb +330 -0
- data/lib/vagrant-xapi/legacy/plugin.rb +61 -0
- data/lib/vagrant-xapi/legacy/xenapi.rb +129 -0
- data/lib/vagrant-xapi/plugin.rb +58 -0
- data/lib/vagrant-xapi/provider.rb +135 -0
- data/lib/vagrant-xapi/services/adoption.rb +73 -0
- data/lib/vagrant-xapi/services/base.rb +242 -0
- data/lib/vagrant-xapi/services/box_source.rb +218 -0
- data/lib/vagrant-xapi/services/console.rb +115 -0
- data/lib/vagrant-xapi/services/content_diff.rb +96 -0
- data/lib/vagrant-xapi/services/destroyer.rb +104 -0
- data/lib/vagrant-xapi/services/diff_disks.rb +346 -0
- data/lib/vagrant-xapi/services/differ.rb +382 -0
- data/lib/vagrant-xapi/services/disk_image.rb +122 -0
- data/lib/vagrant-xapi/services/disks.rb +281 -0
- data/lib/vagrant-xapi/services/exporter.rb +128 -0
- data/lib/vagrant-xapi/services/file_diff.rb +249 -0
- data/lib/vagrant-xapi/services/guest_files.rb +730 -0
- data/lib/vagrant-xapi/services/hosts.rb +55 -0
- data/lib/vagrant-xapi/services/image_upload.rb +172 -0
- data/lib/vagrant-xapi/services/inventory.rb +225 -0
- data/lib/vagrant-xapi/services/ip_detector.rb +107 -0
- data/lib/vagrant-xapi/services/lock.rb +293 -0
- data/lib/vagrant-xapi/services/lookup.rb +181 -0
- data/lib/vagrant-xapi/services/networks.rb +248 -0
- data/lib/vagrant-xapi/services/power.rb +108 -0
- data/lib/vagrant-xapi/services/rfb.rb +129 -0
- data/lib/vagrant-xapi/services/snapshots.rb +81 -0
- data/lib/vagrant-xapi/services/template_cache.rb +263 -0
- data/lib/vagrant-xapi/services/tunnels.rb +217 -0
- data/lib/vagrant-xapi/services/vm_builder.rb +215 -0
- data/lib/vagrant-xapi/version.rb +7 -0
- data/lib/vagrant-xapi/xenserver/config.rb +69 -0
- data/lib/vagrant-xapi/xenserver/migrate.rb +103 -0
- data/lib/vagrant-xapi/xenserver/plugin.rb +96 -0
- data/lib/vagrant-xapi/xenserver/provider.rb +22 -0
- data/lib/vagrant-xapi/xenserver/takeover.rb +79 -0
- data/lib/vagrant-xapi.rb +21 -2
- data/lib/xenapi.rb +22 -0
- data/locales/en.yml +517 -0
- data/locales/legacy.en.yml +6 -0
- 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
|