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