djo-vagrant-vsphere 1.6.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 +7 -0
- data/.bumpversion.cfg +11 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +13 -0
- data/.rubocop_todo.yml +80 -0
- data/.travis.yml +11 -0
- data/CHANGELOG.md +250 -0
- data/DEVELOPMENT.md +67 -0
- data/Gemfile +16 -0
- data/LICENSE.txt +24 -0
- data/README.md +310 -0
- data/Rakefile +20 -0
- data/example_box/metadata.json +3 -0
- data/lib/vSphere/action.rb +195 -0
- data/lib/vSphere/action/clone.rb +239 -0
- data/lib/vSphere/action/close_vsphere.rb +22 -0
- data/lib/vSphere/action/connect_vsphere.rb +29 -0
- data/lib/vSphere/action/destroy.rb +41 -0
- data/lib/vSphere/action/get_ssh_info.rb +37 -0
- data/lib/vSphere/action/get_state.rb +41 -0
- data/lib/vSphere/action/is_created.rb +16 -0
- data/lib/vSphere/action/is_running.rb +16 -0
- data/lib/vSphere/action/message_already_created.rb +18 -0
- data/lib/vSphere/action/message_not_created.rb +18 -0
- data/lib/vSphere/action/message_not_running.rb +18 -0
- data/lib/vSphere/action/power_off.rb +40 -0
- data/lib/vSphere/action/power_on.rb +28 -0
- data/lib/vSphere/cap/public_address.rb +15 -0
- data/lib/vSphere/config.rb +60 -0
- data/lib/vSphere/errors.rb +11 -0
- data/lib/vSphere/plugin.rb +44 -0
- data/lib/vSphere/provider.rb +39 -0
- data/lib/vSphere/util/vim_helpers.rb +91 -0
- data/lib/vSphere/util/vm_helpers.rb +39 -0
- data/lib/vSphere/version.rb +5 -0
- data/lib/vagrant-vsphere.rb +18 -0
- data/locales/en.yml +64 -0
- data/spec/action_spec.rb +162 -0
- data/spec/clone_spec.rb +102 -0
- data/spec/connect_vsphere_spec.rb +26 -0
- data/spec/destroy_spec.rb +31 -0
- data/spec/get_ssh_info_spec.rb +31 -0
- data/spec/get_state_spec.rb +55 -0
- data/spec/is_created_spec.rb +29 -0
- data/spec/power_off_spec.rb +35 -0
- data/spec/spec_helper.rb +147 -0
- data/vSphere.gemspec +30 -0
- data/vsphere_screenshot.png +0 -0
- metadata +212 -0
@@ -0,0 +1,239 @@
|
|
1
|
+
require 'rbvmomi'
|
2
|
+
require 'i18n'
|
3
|
+
require 'vSphere/util/vim_helpers'
|
4
|
+
|
5
|
+
module VagrantPlugins
|
6
|
+
module VSphere
|
7
|
+
module Action
|
8
|
+
class Clone
|
9
|
+
include Util::VimHelpers
|
10
|
+
|
11
|
+
def initialize(app, _env)
|
12
|
+
@app = app
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
machine = env[:machine]
|
17
|
+
config = machine.provider_config
|
18
|
+
connection = env[:vSphere_connection]
|
19
|
+
name = get_name machine, config, env[:root_path]
|
20
|
+
dc = get_datacenter connection, machine
|
21
|
+
template = dc.find_vm config.template_name
|
22
|
+
fail Errors::VSphereError, :'missing_template' if template.nil?
|
23
|
+
vm_base_folder = get_vm_base_folder dc, template, config
|
24
|
+
fail Errors::VSphereError, :'invalid_base_path' if vm_base_folder.nil?
|
25
|
+
|
26
|
+
begin
|
27
|
+
# Storage DRS does not support vSphere linked clones. http://www.vmware.com/files/pdf/techpaper/vsphere-storage-drs-interoperability.pdf
|
28
|
+
ds = get_datastore dc, machine
|
29
|
+
fail Errors::VSphereError, :'invalid_configuration_linked_clone_with_sdrs' if config.linked_clone && ds.is_a?(RbVmomi::VIM::StoragePod)
|
30
|
+
|
31
|
+
location = get_location ds, dc, machine, template
|
32
|
+
spec = RbVmomi::VIM.VirtualMachineCloneSpec location: location, powerOn: true, template: false
|
33
|
+
spec[:config] = RbVmomi::VIM.VirtualMachineConfigSpec
|
34
|
+
customization_info = get_customization_spec_info_by_name connection, machine
|
35
|
+
|
36
|
+
spec[:customization] = get_customization_spec(machine, customization_info) unless customization_info.nil?
|
37
|
+
add_custom_address_type(template, spec, config.addressType) unless config.addressType.nil?
|
38
|
+
add_custom_mac(template, spec, config.mac) unless config.mac.nil?
|
39
|
+
add_custom_vlan(template, dc, spec, config.vlan) unless config.vlan.nil?
|
40
|
+
add_custom_memory(spec, config.memory_mb) unless config.memory_mb.nil?
|
41
|
+
add_custom_cpu(spec, config.cpu_count) unless config.cpu_count.nil?
|
42
|
+
add_custom_cpu_reservation(spec, config.cpu_reservation) unless config.cpu_reservation.nil?
|
43
|
+
add_custom_mem_reservation(spec, config.mem_reservation) unless config.mem_reservation.nil?
|
44
|
+
|
45
|
+
if !config.clone_from_vm && ds.is_a?(RbVmomi::VIM::StoragePod)
|
46
|
+
|
47
|
+
storage_mgr = connection.serviceContent.storageResourceManager
|
48
|
+
pod_spec = RbVmomi::VIM.StorageDrsPodSelectionSpec(storagePod: ds)
|
49
|
+
# TODO: May want to add option on type?
|
50
|
+
storage_spec = RbVmomi::VIM.StoragePlacementSpec(type: 'clone', cloneName: name, folder: vm_base_folder, podSelectionSpec: pod_spec, vm: template, cloneSpec: spec)
|
51
|
+
|
52
|
+
env[:ui].info I18n.t('vsphere.requesting_sdrs_recommendation')
|
53
|
+
env[:ui].info " -- DatastoreCluster: #{ds.name}"
|
54
|
+
env[:ui].info " -- Template VM: #{template.pretty_path}"
|
55
|
+
env[:ui].info " -- Target VM: #{vm_base_folder.pretty_path}/#{name}"
|
56
|
+
|
57
|
+
result = storage_mgr.RecommendDatastores(storageSpec: storage_spec)
|
58
|
+
|
59
|
+
recommendation = result.recommendations[0]
|
60
|
+
key = recommendation.key ||= ''
|
61
|
+
if key == ''
|
62
|
+
fail Errors::VSphereError, :missing_datastore_recommendation
|
63
|
+
end
|
64
|
+
|
65
|
+
env[:ui].info I18n.t('vsphere.creating_cloned_vm_sdrs')
|
66
|
+
env[:ui].info " -- Storage DRS recommendation: #{recommendation.target.name} #{recommendation.reasonText}"
|
67
|
+
|
68
|
+
apply_sr_result = storage_mgr.ApplyStorageDrsRecommendation_Task(key: [key]).wait_for_completion
|
69
|
+
new_vm = apply_sr_result.vm
|
70
|
+
|
71
|
+
else
|
72
|
+
|
73
|
+
env[:ui].info I18n.t('vsphere.creating_cloned_vm')
|
74
|
+
env[:ui].info " -- #{config.clone_from_vm ? 'Source' : 'Template'} VM: #{template.pretty_path}"
|
75
|
+
env[:ui].info " -- Target VM: #{vm_base_folder.pretty_path}/#{name}"
|
76
|
+
|
77
|
+
new_vm = template.CloneVM_Task(folder: vm_base_folder, name: name, spec: spec).wait_for_completion
|
78
|
+
|
79
|
+
config.custom_attributes.each do |k, v|
|
80
|
+
new_vm.setCustomValue(key: k, value: v)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
rescue Errors::VSphereError
|
84
|
+
raise
|
85
|
+
rescue StandardError => e
|
86
|
+
raise Errors::VSphereError.new, e.message
|
87
|
+
end
|
88
|
+
|
89
|
+
# TODO: handle interrupted status in the environment, should the vm be destroyed?
|
90
|
+
|
91
|
+
machine.id = new_vm.config.uuid
|
92
|
+
|
93
|
+
env[:ui].info I18n.t('vsphere.vm_clone_success')
|
94
|
+
|
95
|
+
@app.call env
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def get_customization_spec(machine, spec_info)
|
101
|
+
customization_spec = spec_info.spec.clone
|
102
|
+
|
103
|
+
# find all the configured private networks
|
104
|
+
private_networks = machine.config.vm.networks.find_all { |n| n[0].eql? :private_network }
|
105
|
+
return customization_spec if private_networks.nil?
|
106
|
+
|
107
|
+
# make sure we have enough NIC settings to override with the private network settings
|
108
|
+
fail Errors::VSphereError, :'too_many_private_networks' if private_networks.length > customization_spec.nicSettingMap.length
|
109
|
+
|
110
|
+
# assign the private network IP to the NIC
|
111
|
+
private_networks.each_index do |idx|
|
112
|
+
customization_spec.nicSettingMap[idx + 1].adapter.ip.ipAddress = private_networks[idx + 1][1][:ip]
|
113
|
+
end
|
114
|
+
|
115
|
+
customization_spec
|
116
|
+
end
|
117
|
+
|
118
|
+
def get_location(datastore, dc, machine, template)
|
119
|
+
if machine.provider_config.linked_clone
|
120
|
+
# The API for linked clones is quite strange. We can't create a linked
|
121
|
+
# straight from any VM. The disks of the VM for which we can create a
|
122
|
+
# linked clone need to be read-only and thus VC demands that the VM we
|
123
|
+
# are cloning from uses delta-disks. Only then it will allow us to
|
124
|
+
# share the base disk.
|
125
|
+
#
|
126
|
+
# Thus, this code first create a delta disk on top of the base disk for
|
127
|
+
# the to-be-cloned VM, if delta disks aren't used already.
|
128
|
+
disks = template.config.hardware.device.grep(RbVmomi::VIM::VirtualDisk)
|
129
|
+
disks.select { |disk| disk.backing.parent.nil? }.each do |disk|
|
130
|
+
spec = {
|
131
|
+
deviceChange: [
|
132
|
+
{
|
133
|
+
operation: :remove,
|
134
|
+
device: disk
|
135
|
+
},
|
136
|
+
{
|
137
|
+
operation: :add,
|
138
|
+
fileOperation: :create,
|
139
|
+
device: disk.dup.tap do |new_disk|
|
140
|
+
new_disk.backing = new_disk.backing.dup
|
141
|
+
new_disk.backing.fileName = "[#{disk.backing.datastore.name}]"
|
142
|
+
new_disk.backing.parent = disk.backing
|
143
|
+
end
|
144
|
+
}
|
145
|
+
]
|
146
|
+
}
|
147
|
+
template.ReconfigVM_Task(spec: spec).wait_for_completion
|
148
|
+
end
|
149
|
+
|
150
|
+
location = RbVmomi::VIM.VirtualMachineRelocateSpec(diskMoveType: :moveChildMostDiskBacking)
|
151
|
+
elsif datastore.is_a? RbVmomi::VIM::StoragePod
|
152
|
+
location = RbVmomi::VIM.VirtualMachineRelocateSpec
|
153
|
+
else
|
154
|
+
location = RbVmomi::VIM.VirtualMachineRelocateSpec
|
155
|
+
|
156
|
+
location[:datastore] = datastore unless datastore.nil?
|
157
|
+
end
|
158
|
+
location[:pool] = get_resource_pool(dc, machine) unless machine.provider_config.clone_from_vm
|
159
|
+
location
|
160
|
+
end
|
161
|
+
|
162
|
+
def get_name(machine, config, root_path)
|
163
|
+
return config.name unless config.name.nil?
|
164
|
+
|
165
|
+
prefix = "#{root_path.basename}_#{machine.name}"
|
166
|
+
prefix.gsub!(/[^-a-z0-9_\.]/i, '')
|
167
|
+
# milliseconds + random number suffix to allow for simultaneous `vagrant up` of the same box in different dirs
|
168
|
+
prefix + "_#{(Time.now.to_f * 1000.0).to_i}_#{rand(100_000)}"
|
169
|
+
end
|
170
|
+
|
171
|
+
def get_vm_base_folder(dc, template, config)
|
172
|
+
if config.vm_base_path.nil?
|
173
|
+
template.parent
|
174
|
+
else
|
175
|
+
dc.vmFolder.traverse(config.vm_base_path, RbVmomi::VIM::Folder, true)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def modify_network_card(template, spec)
|
180
|
+
spec[:config][:deviceChange] ||= []
|
181
|
+
@card ||= template.config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard).first
|
182
|
+
|
183
|
+
fail Errors::VSphereError, :missing_network_card if @card.nil?
|
184
|
+
|
185
|
+
yield(@card)
|
186
|
+
|
187
|
+
dev_spec = RbVmomi::VIM.VirtualDeviceConfigSpec(device: @card, operation: 'edit')
|
188
|
+
spec[:config][:deviceChange].push dev_spec
|
189
|
+
spec[:config][:deviceChange].uniq!
|
190
|
+
end
|
191
|
+
|
192
|
+
def add_custom_address_type(template, spec, addressType)
|
193
|
+
spec[:config][:deviceChange] = []
|
194
|
+
config = template.config
|
195
|
+
card = config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard).first || fail(Errors::VSphereError, :missing_network_card)
|
196
|
+
card.addressType = addressType
|
197
|
+
card_spec = { :deviceChange => [{ :operation => :edit, :device => card }] }
|
198
|
+
template.ReconfigVM_Task(:spec => card_spec).wait_for_completion
|
199
|
+
end
|
200
|
+
|
201
|
+
def add_custom_mac(template, spec, mac)
|
202
|
+
modify_network_card(template, spec) do |card|
|
203
|
+
card.macAddress = mac
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def add_custom_vlan(template, dc, spec, vlan)
|
208
|
+
network = get_network_by_name(dc, vlan)
|
209
|
+
|
210
|
+
modify_network_card(template, spec) do |card|
|
211
|
+
begin
|
212
|
+
switch_port = RbVmomi::VIM.DistributedVirtualSwitchPortConnection(switchUuid: network.config.distributedVirtualSwitch.uuid, portgroupKey: network.key)
|
213
|
+
card.backing.port = switch_port
|
214
|
+
rescue
|
215
|
+
# not connected to a distibuted switch?
|
216
|
+
card.backing = RbVmomi::VIM::VirtualEthernetCardNetworkBackingInfo(network: network, deviceName: network.name)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def add_custom_memory(spec, memory_mb)
|
222
|
+
spec[:config][:memoryMB] = Integer(memory_mb)
|
223
|
+
end
|
224
|
+
|
225
|
+
def add_custom_cpu(spec, cpu_count)
|
226
|
+
spec[:config][:numCPUs] = Integer(cpu_count)
|
227
|
+
end
|
228
|
+
|
229
|
+
def add_custom_cpu_reservation(spec, cpu_reservation)
|
230
|
+
spec[:config][:cpuAllocation] = RbVmomi::VIM.ResourceAllocationInfo(reservation: cpu_reservation)
|
231
|
+
end
|
232
|
+
|
233
|
+
def add_custom_mem_reservation(spec, mem_reservation)
|
234
|
+
spec[:config][:memoryAllocation] = RbVmomi::VIM.ResourceAllocationInfo(reservation: mem_reservation)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rbvmomi'
|
2
|
+
|
3
|
+
module VagrantPlugins
|
4
|
+
module VSphere
|
5
|
+
module Action
|
6
|
+
class CloseVSphere
|
7
|
+
def initialize(app, _env)
|
8
|
+
@app = app
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
env[:vSphere_connection].close
|
13
|
+
@app.call env
|
14
|
+
rescue Errors::VSphereError
|
15
|
+
raise
|
16
|
+
rescue StandardError => e
|
17
|
+
raise Errors::VSphereError.new, e.message
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rbvmomi'
|
2
|
+
|
3
|
+
module VagrantPlugins
|
4
|
+
module VSphere
|
5
|
+
module Action
|
6
|
+
class ConnectVSphere
|
7
|
+
def initialize(app, _env)
|
8
|
+
@app = app
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
config = env[:machine].provider_config
|
13
|
+
|
14
|
+
begin
|
15
|
+
env[:vSphere_connection] = RbVmomi::VIM.connect host: config.host,
|
16
|
+
user: config.user, password: config.password,
|
17
|
+
insecure: config.insecure, proxyHost: config.proxy_host,
|
18
|
+
proxyPort: config.proxy_port
|
19
|
+
@app.call env
|
20
|
+
rescue Errors::VSphereError
|
21
|
+
raise
|
22
|
+
rescue StandardError => e
|
23
|
+
raise Errors::VSphereError.new, e.message
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'rbvmomi'
|
2
|
+
require 'i18n'
|
3
|
+
require 'vSphere/util/vim_helpers'
|
4
|
+
|
5
|
+
module VagrantPlugins
|
6
|
+
module VSphere
|
7
|
+
module Action
|
8
|
+
class Destroy
|
9
|
+
include Util::VimHelpers
|
10
|
+
|
11
|
+
def initialize(app, _env)
|
12
|
+
@app = app
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
destroy_vm env
|
17
|
+
env[:machine].id = nil
|
18
|
+
|
19
|
+
@app.call env
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def destroy_vm(env)
|
25
|
+
return if env[:machine].state.id == :not_created
|
26
|
+
vm = get_vm_by_uuid env[:vSphere_connection], env[:machine]
|
27
|
+
return if vm.nil?
|
28
|
+
|
29
|
+
begin
|
30
|
+
env[:ui].info I18n.t('vsphere.destroy_vm')
|
31
|
+
vm.Destroy_Task.wait_for_completion
|
32
|
+
rescue Errors::VSphereError
|
33
|
+
raise
|
34
|
+
rescue StandardError => e
|
35
|
+
raise Errors::VSphereError.new, e.message
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rbvmomi'
|
2
|
+
require 'vSphere/util/vim_helpers'
|
3
|
+
|
4
|
+
module VagrantPlugins
|
5
|
+
module VSphere
|
6
|
+
module Action
|
7
|
+
class GetSshInfo
|
8
|
+
include Util::VimHelpers
|
9
|
+
|
10
|
+
def initialize(app, _env)
|
11
|
+
@app = app
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
env[:machine_ssh_info] = get_ssh_info(env[:vSphere_connection], env[:machine])
|
16
|
+
|
17
|
+
@app.call env
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def get_ssh_info(connection, machine)
|
23
|
+
return nil if machine.id.nil?
|
24
|
+
|
25
|
+
vm = get_vm_by_uuid connection, machine
|
26
|
+
|
27
|
+
return nil if vm.nil?
|
28
|
+
return nil if vm.guest.ipAddress.nil? || vm.guest.ipAddress.empty?
|
29
|
+
{
|
30
|
+
host: vm.guest.ipAddress,
|
31
|
+
port: 22
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'rbvmomi'
|
2
|
+
require 'vSphere/util/vim_helpers'
|
3
|
+
require 'vSphere/util/vm_helpers'
|
4
|
+
|
5
|
+
module VagrantPlugins
|
6
|
+
module VSphere
|
7
|
+
module Action
|
8
|
+
class GetState
|
9
|
+
include Util::VimHelpers
|
10
|
+
include Util::VmHelpers
|
11
|
+
|
12
|
+
def initialize(app, _env)
|
13
|
+
@app = app
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
env[:machine_state_id] = get_state(env[:vSphere_connection], env[:machine])
|
18
|
+
|
19
|
+
@app.call env
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def get_state(connection, machine)
|
25
|
+
return :not_created if machine.id.nil?
|
26
|
+
|
27
|
+
vm = get_vm_by_uuid connection, machine
|
28
|
+
|
29
|
+
return :not_created if vm.nil?
|
30
|
+
|
31
|
+
if powered_on?(vm)
|
32
|
+
:running
|
33
|
+
else
|
34
|
+
# If the VM is powered off or suspended, we consider it to be powered off. A power on command will either turn on or resume the VM
|
35
|
+
:poweroff
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|