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.
Files changed (49) 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 +250 -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 +195 -0
  15. data/lib/vSphere/action/clone.rb +239 -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 +16 -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 +28 -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/vim_helpers.rb +91 -0
  34. data/lib/vSphere/util/vm_helpers.rb +39 -0
  35. data/lib/vSphere/version.rb +5 -0
  36. data/lib/vagrant-vsphere.rb +18 -0
  37. data/locales/en.yml +64 -0
  38. data/spec/action_spec.rb +162 -0
  39. data/spec/clone_spec.rb +102 -0
  40. data/spec/connect_vsphere_spec.rb +26 -0
  41. data/spec/destroy_spec.rb +31 -0
  42. data/spec/get_ssh_info_spec.rb +31 -0
  43. data/spec/get_state_spec.rb +55 -0
  44. data/spec/is_created_spec.rb +29 -0
  45. data/spec/power_off_spec.rb +35 -0
  46. data/spec/spec_helper.rb +147 -0
  47. data/vSphere.gemspec +30 -0
  48. data/vsphere_screenshot.png +0 -0
  49. 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
@@ -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,16 @@
1
+ module VagrantPlugins
2
+ module VSphere
3
+ module Action
4
+ class IsRunning
5
+ def initialize(app, _env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ env[:result] = env[:machine].state.id == :running
11
+ @app.call env
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end