fog-vsphere 0.1.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 (123) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/.travis.yml +32 -0
  4. data/CONTRIBUTING.md +18 -0
  5. data/CONTRIBUTORS.md +59 -0
  6. data/Gemfile +5 -0
  7. data/LICENSE.md +20 -0
  8. data/README.md +31 -0
  9. data/Rakefile +8 -0
  10. data/fog-vsphere.gemspec +32 -0
  11. data/gemfiles/Gemfile.1.9.2+ +8 -0
  12. data/gemfiles/Gemfile.1.9.2- +11 -0
  13. data/lib/fog/vsphere.rb +41 -0
  14. data/lib/fog/vsphere/compute.rb +473 -0
  15. data/lib/fog/vsphere/models/compute/cluster.rb +28 -0
  16. data/lib/fog/vsphere/models/compute/clusters.rb +22 -0
  17. data/lib/fog/vsphere/models/compute/customfield.rb +16 -0
  18. data/lib/fog/vsphere/models/compute/customfields.rb +23 -0
  19. data/lib/fog/vsphere/models/compute/customvalue.rb +14 -0
  20. data/lib/fog/vsphere/models/compute/customvalues.rb +33 -0
  21. data/lib/fog/vsphere/models/compute/datacenter.rb +44 -0
  22. data/lib/fog/vsphere/models/compute/datacenters.rb +19 -0
  23. data/lib/fog/vsphere/models/compute/datastore.rb +21 -0
  24. data/lib/fog/vsphere/models/compute/datastores.rb +21 -0
  25. data/lib/fog/vsphere/models/compute/folder.rb +24 -0
  26. data/lib/fog/vsphere/models/compute/folders.rb +23 -0
  27. data/lib/fog/vsphere/models/compute/interface.rb +91 -0
  28. data/lib/fog/vsphere/models/compute/interfaces.rb +66 -0
  29. data/lib/fog/vsphere/models/compute/interfacetype.rb +22 -0
  30. data/lib/fog/vsphere/models/compute/interfacetypes.rb +34 -0
  31. data/lib/fog/vsphere/models/compute/network.rb +18 -0
  32. data/lib/fog/vsphere/models/compute/networks.rb +22 -0
  33. data/lib/fog/vsphere/models/compute/process.rb +17 -0
  34. data/lib/fog/vsphere/models/compute/resource_pool.rb +19 -0
  35. data/lib/fog/vsphere/models/compute/resource_pools.rb +22 -0
  36. data/lib/fog/vsphere/models/compute/scsicontroller.rb +16 -0
  37. data/lib/fog/vsphere/models/compute/server.rb +325 -0
  38. data/lib/fog/vsphere/models/compute/servers.rb +36 -0
  39. data/lib/fog/vsphere/models/compute/servertype.rb +36 -0
  40. data/lib/fog/vsphere/models/compute/servertypes.rb +23 -0
  41. data/lib/fog/vsphere/models/compute/snapshot.rb +35 -0
  42. data/lib/fog/vsphere/models/compute/snapshots.rb +27 -0
  43. data/lib/fog/vsphere/models/compute/template.rb +11 -0
  44. data/lib/fog/vsphere/models/compute/templates.rb +19 -0
  45. data/lib/fog/vsphere/models/compute/volume.rb +99 -0
  46. data/lib/fog/vsphere/models/compute/volumes.rb +53 -0
  47. data/lib/fog/vsphere/requests/compute/cloudinit_to_customspec.rb +65 -0
  48. data/lib/fog/vsphere/requests/compute/create_folder.rb +22 -0
  49. data/lib/fog/vsphere/requests/compute/create_vm.rb +169 -0
  50. data/lib/fog/vsphere/requests/compute/current_time.rb +18 -0
  51. data/lib/fog/vsphere/requests/compute/get_cluster.rb +25 -0
  52. data/lib/fog/vsphere/requests/compute/get_compute_resource.rb +41 -0
  53. data/lib/fog/vsphere/requests/compute/get_datacenter.rb +31 -0
  54. data/lib/fog/vsphere/requests/compute/get_datastore.rb +30 -0
  55. data/lib/fog/vsphere/requests/compute/get_folder.rb +74 -0
  56. data/lib/fog/vsphere/requests/compute/get_interface_type.rb +15 -0
  57. data/lib/fog/vsphere/requests/compute/get_network.rb +59 -0
  58. data/lib/fog/vsphere/requests/compute/get_resource_pool.rb +26 -0
  59. data/lib/fog/vsphere/requests/compute/get_server_type.rb +32 -0
  60. data/lib/fog/vsphere/requests/compute/get_template.rb +16 -0
  61. data/lib/fog/vsphere/requests/compute/get_virtual_machine.rb +57 -0
  62. data/lib/fog/vsphere/requests/compute/get_vm_first_scsi_controller.rb +26 -0
  63. data/lib/fog/vsphere/requests/compute/list_child_snapshots.rb +71 -0
  64. data/lib/fog/vsphere/requests/compute/list_clusters.rb +72 -0
  65. data/lib/fog/vsphere/requests/compute/list_compute_resources.rb +92 -0
  66. data/lib/fog/vsphere/requests/compute/list_customfields.rb +21 -0
  67. data/lib/fog/vsphere/requests/compute/list_datacenters.rb +53 -0
  68. data/lib/fog/vsphere/requests/compute/list_datastores.rb +40 -0
  69. data/lib/fog/vsphere/requests/compute/list_folders.rb +44 -0
  70. data/lib/fog/vsphere/requests/compute/list_interface_types.rb +25 -0
  71. data/lib/fog/vsphere/requests/compute/list_networks.rb +38 -0
  72. data/lib/fog/vsphere/requests/compute/list_processes.rb +40 -0
  73. data/lib/fog/vsphere/requests/compute/list_resource_pools.rb +38 -0
  74. data/lib/fog/vsphere/requests/compute/list_server_types.rb +54 -0
  75. data/lib/fog/vsphere/requests/compute/list_templates.rb +48 -0
  76. data/lib/fog/vsphere/requests/compute/list_virtual_machines.rb +80 -0
  77. data/lib/fog/vsphere/requests/compute/list_vm_customvalues.rb +20 -0
  78. data/lib/fog/vsphere/requests/compute/list_vm_interfaces.rb +63 -0
  79. data/lib/fog/vsphere/requests/compute/list_vm_snapshots.rb +66 -0
  80. data/lib/fog/vsphere/requests/compute/list_vm_volumes.rb +52 -0
  81. data/lib/fog/vsphere/requests/compute/modify_vm_interface.rb +59 -0
  82. data/lib/fog/vsphere/requests/compute/modify_vm_volume.rb +25 -0
  83. data/lib/fog/vsphere/requests/compute/revert_to_snapshot.rb +30 -0
  84. data/lib/fog/vsphere/requests/compute/set_vm_customvalue.rb +17 -0
  85. data/lib/fog/vsphere/requests/compute/vm_clone.rb +727 -0
  86. data/lib/fog/vsphere/requests/compute/vm_config_vnc.rb +45 -0
  87. data/lib/fog/vsphere/requests/compute/vm_destroy.rb +23 -0
  88. data/lib/fog/vsphere/requests/compute/vm_execute.rb +47 -0
  89. data/lib/fog/vsphere/requests/compute/vm_migrate.rb +33 -0
  90. data/lib/fog/vsphere/requests/compute/vm_power_off.rb +39 -0
  91. data/lib/fog/vsphere/requests/compute/vm_power_on.rb +26 -0
  92. data/lib/fog/vsphere/requests/compute/vm_reboot.rb +31 -0
  93. data/lib/fog/vsphere/requests/compute/vm_reconfig_cpus.rb +23 -0
  94. data/lib/fog/vsphere/requests/compute/vm_reconfig_hardware.rb +24 -0
  95. data/lib/fog/vsphere/requests/compute/vm_reconfig_memory.rb +23 -0
  96. data/lib/fog/vsphere/requests/compute/vm_take_snapshot.rb +37 -0
  97. data/lib/fog/vsphere/version.rb +5 -0
  98. data/tests/compute_tests.rb +53 -0
  99. data/tests/helper.rb +8 -0
  100. data/tests/helpers/mock_helper.rb +9 -0
  101. data/tests/helpers/succeeds_helper.rb +9 -0
  102. data/tests/models/compute/server_tests.rb +70 -0
  103. data/tests/models/compute/servers_tests.rb +15 -0
  104. data/tests/requests/compute/current_time_tests.rb +12 -0
  105. data/tests/requests/compute/get_network_tests.rb +50 -0
  106. data/tests/requests/compute/list_child_snapshots_tests.rb +10 -0
  107. data/tests/requests/compute/list_clusters_tests.rb +11 -0
  108. data/tests/requests/compute/list_virtual_machines_tests.rb +38 -0
  109. data/tests/requests/compute/list_vm_snapshots_tests.rb +10 -0
  110. data/tests/requests/compute/revert_to_snapshot_tests.rb +15 -0
  111. data/tests/requests/compute/set_vm_customvalue_tests.rb +20 -0
  112. data/tests/requests/compute/vm_clone_tests.rb +50 -0
  113. data/tests/requests/compute/vm_config_vnc_tests.rb +19 -0
  114. data/tests/requests/compute/vm_destroy_tests.rb +17 -0
  115. data/tests/requests/compute/vm_migrate_tests.rb +16 -0
  116. data/tests/requests/compute/vm_power_off_tests.rb +26 -0
  117. data/tests/requests/compute/vm_power_on_tests.rb +17 -0
  118. data/tests/requests/compute/vm_reboot_tests.rb +26 -0
  119. data/tests/requests/compute/vm_reconfig_cpus_tests.rb +19 -0
  120. data/tests/requests/compute/vm_reconfig_hardware_tests.rb +19 -0
  121. data/tests/requests/compute/vm_reconfig_memory_tests.rb +19 -0
  122. data/tests/requests/compute/vm_take_snapshot_tests.rb +19 -0
  123. metadata +289 -0
@@ -0,0 +1,59 @@
1
+ module Fog
2
+ module Compute
3
+ class Vsphere
4
+ class Real
5
+ def add_vm_interface(vmid, options = {})
6
+ raise ArgumentError, "instance id is a required parameter" unless vmid
7
+
8
+ interface = get_interface_from_options(vmid, options.merge(:server_id => vmid))
9
+ vm_reconfig_hardware('instance_uuid' => vmid, 'hardware_spec' => {'deviceChange'=>[create_interface(interface, 0, :add, options)]})
10
+ end
11
+
12
+ def destroy_vm_interface(vmid, options = {})
13
+ raise ArgumentError, "instance id is a required parameter" unless vmid
14
+
15
+ interface = get_interface_from_options(vmid, options.merge(:server_id => vmid))
16
+ vm_reconfig_hardware('instance_uuid' => vmid, 'hardware_spec' => {'deviceChange'=>[create_interface(interface, interface.key, :remove)]})
17
+ end
18
+
19
+ def update_vm_interface(vmid, options = {})
20
+ raise ArgumentError, "instance id is a required parameter" unless vmid
21
+
22
+ interface = get_interface_from_options(vmid, options.merge(:server_id => vmid))
23
+ vm_reconfig_hardware('instance_uuid' => vmid, 'hardware_spec' => {'deviceChange'=>[create_interface(interface, interface.key, :edit)]})
24
+ end
25
+
26
+ private
27
+ def get_interface_from_options(vmid, options)
28
+ if options and options[:interface]
29
+ options[:interface]
30
+
31
+ elsif options[:key] and options[:key]>0
32
+ oldattributes = get_vm_interface(vmid, options)
33
+ Fog::Compute::Vsphere::Interface.new(oldattributes.merge(options))
34
+
35
+ elsif options[:type] and options[:network]
36
+ Fog::Compute::Vsphere::Interface.new options
37
+
38
+ else
39
+ raise ArgumentError, "interface is a required parameter or pass options with type and network"
40
+ end
41
+ end
42
+ end
43
+
44
+ class Mock
45
+ def add_vm_interface(vmid, options = {})
46
+ raise ArgumentError, "instance id is a required parameter" unless vmid
47
+ raise ArgumentError, "interface is a required parameter" unless options and options[:interface]
48
+ true
49
+ end
50
+
51
+ def destroy_vm_interface(vmid, options = {})
52
+ raise ArgumentError, "instance id is a required parameter" unless vmid
53
+ raise ArgumentError, "interface is a required parameter" unless options and options[:interface]
54
+ true
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,25 @@
1
+ module Fog
2
+ module Compute
3
+ class Vsphere
4
+ class Real
5
+ def add_vm_volume(volume)
6
+ vm_reconfig_hardware('instance_uuid' => volume.server_id, 'hardware_spec' => {'deviceChange'=>[create_disk(volume, volume.unit_number, :add)]})
7
+ end
8
+
9
+ def destroy_vm_volume(volume)
10
+ vm_reconfig_hardware('instance_uuid' => volume.server_id, 'hardware_spec' => {'deviceChange'=>[create_disk(volume, volume.unit_number, :remove)]})
11
+ end
12
+ end
13
+
14
+ class Mock
15
+ def add_vm_volume(volume)
16
+ true
17
+ end
18
+
19
+ def destroy_vm_volume(volume)
20
+ true
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,30 @@
1
+ module Fog
2
+ module Compute
3
+ class Vsphere
4
+ class Real
5
+ def revert_to_snapshot(snapshot)
6
+ unless Snapshot === snapshot
7
+ fail ArgumentError, 'snapshot is a required parameter'
8
+ end
9
+
10
+ task = snapshot.mo_ref.RevertToSnapshot_Task
11
+ task.wait_for_completion
12
+
13
+ {
14
+ 'state' => task.info.state
15
+ }
16
+ end
17
+ end
18
+
19
+ class Mock
20
+ def revert_to_snapshot(snapshot)
21
+ fail ArgumentError, 'snapshot is a required parameter' if snapshot.nil?
22
+
23
+ {
24
+ 'state' => 'success'
25
+ }
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ module Fog
2
+ module Compute
3
+ class Vsphere
4
+ class Real
5
+ def set_vm_customvalue(vm_id, key, value)
6
+ vm_ref = get_vm_ref(vm_id)
7
+ vm_ref.setCustomValue(:key => key, :value => value)
8
+ end
9
+ end
10
+ class Mock
11
+ def set_vm_customvalue(vm_id, key, value)
12
+ nil
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,727 @@
1
+ module Fog
2
+ module Compute
3
+ class Vsphere
4
+ module Shared
5
+ private
6
+ def vm_clone_check_options(options)
7
+ default_options = {
8
+ 'force' => false,
9
+ 'linked_clone' => false,
10
+ 'nic_type' => 'VirtualE1000',
11
+ }
12
+ options = default_options.merge(options)
13
+ # Backwards compat for "path" option
14
+ options["template_path"] ||= options["path"]
15
+ options["path"] ||= options["template_path"]
16
+ required_options = %w{ datacenter template_path name }
17
+ required_options.each do |param|
18
+ raise ArgumentError, "#{required_options.join(', ')} are required" unless options.key? param
19
+ end
20
+ raise Fog::Compute::Vsphere::NotFound, "Datacenter #{options["datacenter"]} Doesn't Exist!" unless get_datacenter(options["datacenter"])
21
+ raise Fog::Compute::Vsphere::NotFound, "Template #{options["template_path"]} Doesn't Exist!" unless get_virtual_machine(options["template_path"], options["datacenter"])
22
+ options
23
+ end
24
+ end
25
+
26
+ class Real
27
+ include Shared
28
+
29
+ # Clones a VM from a template or existing machine on your vSphere
30
+ # Server.
31
+ #
32
+ # ==== Parameters
33
+ # * options<~Hash>:
34
+ # * 'datacenter'<~String> - *REQUIRED* Datacenter name your cloning
35
+ # in. Make sure this datacenter exists, should if you're using
36
+ # the clone function in server.rb model.
37
+ # * 'template_path'<~String> - *REQUIRED* The path to the machine you
38
+ # want to clone FROM. Relative to Datacenter (Example:
39
+ # "FolderNameHere/VMNameHere")
40
+ # * 'name'<~String> - *REQUIRED* The VMName of the Destination
41
+ # * 'dest_folder'<~String> - Destination Folder of where 'name' will
42
+ # be placed on your cluster. Relative Path to Datacenter E.G.
43
+ # "FolderPlaceHere/anotherSub Folder/onemore"
44
+ # * 'power_on'<~Boolean> - Whether to power on machine after clone.
45
+ # Defaults to true.
46
+ # * 'wait'<~Boolean> - Whether the method should wait for the virtual
47
+ # machine to finish cloning before returning information from
48
+ # vSphere. Broken right now as you cannot return a model of a serer
49
+ # that isn't finished cloning. Defaults to True
50
+ # * 'resource_pool'<~Array> - The resource pool on your datacenter
51
+ # cluster you want to use. Only works with clusters within same
52
+ # same datacenter as where you're cloning from. Datacenter grabbed
53
+ # from template_path option.
54
+ # Example: ['cluster_name_here','resource_pool_name_here']
55
+ # * 'datastore'<~String> - The datastore you'd like to use.
56
+ # (datacenterObj.datastoreFolder.find('name') in API)
57
+ # * 'transform'<~String> - Not documented - see http://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/vim.vm.RelocateSpec.html
58
+ # * 'numCPUs'<~Integer> - the number of Virtual CPUs of the Destination VM
59
+ # * 'memoryMB'<~Integer> - the size of memory of the Destination VM in MB
60
+ # * customization_spec<~Hash>: Options are marked as required if you
61
+ # use this customization_spec.
62
+ # As defined https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.Specification.html
63
+ # * encryptionKey <~array of bytes> Used to encrypt/decrypt password
64
+ # * globalIPSettings expects a hash, REQUIRED
65
+ # * identity expects a hash, REQUIRED - either LinuxPrep, Sysprep or SysprepText
66
+ # * nicSettingMap expects an array
67
+ # * options expects a hash
68
+ # * All options can be parsed using a yaml template with cloudinit_to_customspec.rb
69
+ #
70
+ # OLD Values still supported:
71
+ # This only support cloning and setting DHCP on the first interface
72
+ # * 'domain'<~String> - *REQUIRED* This is put into
73
+ # /etc/resolve.conf (we hope)
74
+ # * 'hostname'<~String> - Hostname of the Guest Os - default is
75
+ # options['name']
76
+ # * 'hw_utc_clock'<~Boolean> - *REQUIRED* Is hardware clock UTC?
77
+ # Default true
78
+ # * 'time_zone'<~String> - *REQUIRED* Only valid linux options
79
+ # are valid - example: 'America/Denver'
80
+ # * 'interfaces' <~Array> - interfaces object to apply to
81
+ # the template when cloning: overrides the
82
+ # network_label, network_adapter_device_key and nic_type attributes
83
+ # * 'volumes' <~Array> - volumes object to apply to
84
+ # the template when cloning: this allows to resize the
85
+ # existing disks as well as add or remove them. The
86
+ # resizing is applied only when the size is bigger then the
87
+ # in size in the template
88
+ def vm_clone(options = {})
89
+ # Option handling
90
+ options = vm_clone_check_options(options)
91
+
92
+ # Added for people still using options['path']
93
+ template_path = options['path'] || options['template_path']
94
+
95
+ # Options['template_path']<~String>
96
+ # Added for people still using options['path']
97
+ template_path = options['path'] || options['template_path']
98
+ # Now find the template itself using the efficient find method
99
+ vm_mob_ref = get_vm_ref(template_path, options['datacenter'])
100
+
101
+ # Options['dest_folder']<~String>
102
+ # Grab the destination folder object if it exists else use cloned mach
103
+ dest_folder_path = options.fetch('dest_folder','/') # default to root path ({dc_name}/vm/)
104
+ dest_folder = get_raw_vmfolder(dest_folder_path, options['datacenter'])
105
+
106
+ # Options['resource_pool']<~Array>
107
+ # Now find _a_ resource pool to use for the clone if one is not specified
108
+ if ( options.key?('resource_pool') && options['resource_pool'].is_a?(Array) && options['resource_pool'].length == 2 )
109
+ cluster_name = options['resource_pool'][0]
110
+ pool_name = options['resource_pool'][1]
111
+ resource_pool = get_raw_resource_pool(pool_name, cluster_name, options['datacenter'])
112
+ elsif ( vm_mob_ref.resourcePool == nil )
113
+ # If the template is really a template then there is no associated resource pool,
114
+ # so we need to find one using the template's parent host or cluster
115
+ esx_host = vm_mob_ref.collect!('runtime.host')['runtime.host']
116
+ # The parent of the ESX host itself is a ComputeResource which has a resourcePool
117
+ resource_pool = esx_host.parent.resourcePool
118
+ end
119
+ # If the vm given did return a valid resource pool, default to using it for the clone.
120
+ # Even if specific pools aren't implemented in this environment, we will still get back
121
+ # at least the cluster or host we can pass on to the clone task
122
+ # This catches if resource_pool option is set but comes back nil and if resourcePool is
123
+ # already set.
124
+ resource_pool ||= vm_mob_ref.resourcePool.nil? ? esx_host.parent.resourcePool : vm_mob_ref.resourcePool
125
+
126
+ # Options['datastore']<~String>
127
+ # Grab the datastore object if option is set
128
+ datastore_obj = get_raw_datastore(options['datastore'], options['datacenter']) if options.key?('datastore')
129
+ # confirm nil if nil or option is not set
130
+ datastore_obj ||= nil
131
+ virtual_machine_config_spec = RbVmomi::VIM::VirtualMachineConfigSpec()
132
+
133
+ device_change = []
134
+ # fully futured interfaces api: replace the current nics
135
+ # with the new based on the specification
136
+ if (options.key?('interfaces') )
137
+ if options.key?('network_label')
138
+ raise ArgumentError, "interfaces option can't be specified together with network_label"
139
+ end
140
+ device_change.concat(modify_template_nics_specs(template_path, options['interfaces'], options['datacenter']))
141
+ elsif options.key?('network_label')
142
+ device_change << modify_template_nics_simple_spec(options['network_label'], options['nic_type'], options['network_adapter_device_key'], options['datacenter'])
143
+ end
144
+ if disks = options['volumes']
145
+ device_change.concat(modify_template_volumes_specs(vm_mob_ref, options['volumes']))
146
+ end
147
+ virtual_machine_config_spec.deviceChange = device_change if device_change.any?
148
+ # Options['numCPUs'] or Options['memoryMB']
149
+ # Build up the specification for Hardware, for more details see ____________
150
+ # https://github.com/rlane/rbvmomi/blob/master/test/test_serialization.rb
151
+ # http://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/vim.vm.ConfigSpec.html
152
+ # FIXME: pad this out with the rest of the useful things in VirtualMachineConfigSpec
153
+ virtual_machine_config_spec.numCPUs = options['numCPUs'] if ( options.key?('numCPUs') )
154
+ virtual_machine_config_spec.memoryMB = options['memoryMB'] if ( options.key?('memoryMB') )
155
+ virtual_machine_config_spec.cpuHotAddEnabled = options['cpuHotAddEnabled'] if ( options.key?('cpuHotAddEnabled') )
156
+ virtual_machine_config_spec.memoryHotAddEnabled = options['memoryHotAddEnabled'] if ( options.key?('memoryHotAddEnabled') )
157
+ virtual_machine_config_spec.firmware = options['firmware'] if ( options.key?('firmware') )
158
+ # Options['customization_spec']
159
+ # OLD Options still supported
160
+ # * domain <~String> - *REQUIRED* - Sets the server's domain for customization
161
+ # * dnsSuffixList <~Array> - Optional - Sets the dns search paths in resolv - Example: ["dev.example.com", "example.com"]
162
+ # * time_zone <~String> - Required - Only valid linux options are valid - example: 'America/Denver'
163
+ # * ipsettings <~Hash> - Optional - If not set defaults to dhcp
164
+ # * ip <~String> - *REQUIRED* Sets the ip address of the VM - Example: 10.0.0.10
165
+ # * dnsServerList <~Array> - Optional - Sets the nameservers in resolv - Example: ["10.0.0.2", "10.0.0.3"]
166
+ # * gateway <~Array> - Optional - Sets the gateway for the interface - Example: ["10.0.0.1"]
167
+ # * subnetMask <~String> - *REQUIRED* - Set the netmask of the interface - Example: "255.255.255.0"
168
+ # For other ip settings options see http://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/vim.vm.customization.IPSettings.html
169
+ #
170
+ # Implement complete customization spec as per https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.Specification.html
171
+ # * encryptionKey <~Array> - Optional, encryption key used to encypt any encrypted passwords
172
+ # https://pubs.vmware.com/vsphere-51/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.GlobalIPSettings.html
173
+ # * globalIPSettings <~Hash> - REQUIRED
174
+ # * dnsServerList <~Array> - Optional, list of dns servers - Example: ["10.0.0.2", "10.0.0.3"]
175
+ # * dnsSuffixList <~Array> - Optional, List of name resolution suffixes - Example: ["dev.example.com", "example.com"]
176
+ # * identity <~Hash> - REQUIRED, Network identity and settings, similar to Microsoft's Sysprep tool. This is a Sysprep, LinuxPrep, or SysprepText object
177
+ # * Sysprep <~Hash> - Optional, representation of a Windows sysprep.inf answer file.
178
+ # * guiRunOnce: <~Hash> -Optional, representation of the sysprep GuiRunOnce key
179
+ # * commandList: <~Array> - REQUIRED, list of commands to run at first user logon, after guest customization. - Example: ["c:\sysprep\runaftersysprep.cmd", "c:\sysprep\installpuppet.ps1"]
180
+ # * guiUnattended: <~Hash> - REQUIRED, representation of the sysprep GuiUnattended key
181
+ # * autoLogin: boolean - REQUIRED, Flag to determine whether or not the machine automatically logs on as Administrator.
182
+ # * autoLogonCount: int - REQUIRED, specifies the number of times the machine should automatically log on as Administrator
183
+ # * password: <~Hash> - REQUIRED, new administrator password for the machine
184
+ # * plainText: boolean - REQUIRED, specify whether or not the password is in plain text, rather than encrypted
185
+ # * value: <~String> - REQUIRED, password string
186
+ # * timeZone: <~int> - REQUIRED, (see here for values https://msdn.microsoft.com/en-us/library/ms912391(v=winembedded.11).aspx)
187
+ # * identification: <~Hash> - REQUIRED, representation of the sysprep Identification key
188
+ # * domainAdmin: <~String> - Optional, domain user account used for authentication if the virtual machine is joining a domain
189
+ # * domainAdminPassword: <~Hash> - Optional, password for the domain user account used for authentication
190
+ # * plainText: boolean - REQUIRED, specify whether or not the password is in plain text, rather than encrypted
191
+ # * value: <~String> - REQUIRED, password string
192
+ # * joinDomain: <~String> - Optional, The domain that the virtual machine should join. If this value is supplied, then domainAdmin and domainAdminPassword must also be supplied
193
+ # * joinWorkgroup: <~String> - Optional, The workgroup that the virtual machine should join.
194
+ # * licenseFilePrintData: <~Hash> - Optional, representation of the sysprep LicenseFilePrintData key
195
+ # * autoMode: <~String> - REQUIRED, Server licensing mode. Two strings are supported: 'perSeat' or 'perServer'
196
+ # * autoUsers: <~Int> - Optional, This key is valid only if AutoMode = PerServer. The integer value indicates the number of client licenses
197
+ # * userData: <~Hash> - REQUIRED, representation of the sysprep UserData key
198
+ # * computerName: <~String> - REQUIRED, The computer name of the (Windows) virtual machine. Will be truncates to 15 characters
199
+ # * fullName: <~String> - REQUIRED, User's full name
200
+ # * orgName: <~String> - REQUIRED, User's organization
201
+ # * productId: <~String> - REQUIRED, serial number for os, ignored if using volume licensed instance
202
+ # * LinuxPrep: <~Hash> - Optional, contains machine-wide settings (note the uppercase P)
203
+ # * domain: <~String> - REQUIRED, The fully qualified domain name.
204
+ # * hostName: <~String> - REQUIRED, the network host name
205
+ # * hwClockUTC: <~Boolean> - Optional, Specifies whether the hardware clock is in UTC or local time
206
+ # * timeZone: <~String> - Optional, Case sensistive timezone, valid values can be found at https://pubs.vmware.com/vsphere-51/topic/com.vmware.wssdk.apiref.doc/timezone.html
207
+ # * SysprepText: <~Hash> - Optional, alternate way to specify the sysprep.inf answer file.
208
+ # * value: <~String> - REQUIRED, Text for the sysprep.inf answer file.
209
+ # * nicSettingMap: <~Array> - Optional, IP settings that are specific to a particular virtual network adapter
210
+ # * Each item in array:
211
+ # * adapter: <~Hash> - REQUIRED, IP settings for the associated virtual network adapter
212
+ # * dnsDomain: <~String> - Optional, DNS domain suffix for adapter
213
+ # * dnsServerList: <~Array> - Optional, list of dns server ip addresses - Example: ["10.0.0.2", "10.0.0.3"]
214
+ # * gateway: <~Array> - Optional, list of gateways - Example: ["10.0.0.2", "10.0.0.3"]
215
+ # * ip: <~String> - Optional, but required if static IP
216
+ # * ipV6Spec: <~Hash> - Optional, IPv^ settings
217
+ # * ipAddress: <~String> - Optional, but required if setting static IP
218
+ # * gateway: <~Array> - Optional, list of ipv6 gateways
219
+ # * netBIOS: <~String> - Optional, NetBIOS settings, if supplied must be one of: disableNetBIOS','enableNetBIOS','enableNetBIOSViaDhcp'
220
+ # * primaryWINS: <~String> - Optional, IP address of primary WINS server
221
+ # * secondaryWINS: <~String> - Optional, IP address of secondary WINS server
222
+ # * subnetMask: <~String> - Optional, subnet mask for adapter
223
+ # * macAddress: <~String> - Optional, MAC address of adapter being customized. This cannot be set by the client
224
+ # * options: <~Hash> Optional operations, currently only win options have any value
225
+ # * changeSID: <~Boolean> - REQUIRED, The customization process should modify the machine's security identifier
226
+ # * deleteAccounts: <~Boolean> - REQUIRED, If deleteAccounts is true, then all user accounts are removed from the system
227
+ # * reboot: <~String> - Optional, (defaults to reboot), Action to be taken after running sysprep, must be one of: 'noreboot', 'reboot', 'shutdown'
228
+ #
229
+ if ( options.key?('customization_spec') )
230
+ custom_spec = options['customization_spec']
231
+
232
+ # backwards compatablity
233
+ if custom_spec.key?('domain')
234
+ # doing this means the old options quash any new ones passed as well... might not be the best way to do it?
235
+ # any 'old' options overwrite the following:
236
+ # - custom_spec['identity']['LinuxPrep']
237
+ # - custom_spec['globalIPSettings['['dnsServerList']
238
+ # - custom_spec['globalIPSettings']['dnsSuffixList']
239
+ # - custom_spec['nicSettingMap'][0]['adapter']['ip']
240
+ # - custom_spec['nicSettingMap'][0]['adapter']['gateway']
241
+ # - custom_spec['nicSettingMap'][0]['adapter']['subnetMask']
242
+ # - custom_spec['nicSettingMap'][0]['adapter']['dnsDomain']
243
+ # - custom_spec['nicSettingMap'][0]['adapter']['dnsServerList']
244
+ #
245
+ # we can assume old parameters being passed
246
+ cust_hostname = custom_spec['hostname'] || options['name']
247
+ custom_spec['identity'] = Hash.new unless custom_spec.key?('identity')
248
+ custom_spec['identity']['LinuxPrep'] = {"domain" => custom_spec['domain'], "hostName" => cust_hostname, "timeZone" => custom_spec['time_zone']}
249
+
250
+ if custom_spec.key?('ipsettings')
251
+ custom_spec['globalIPSettings']=Hash.new unless custom_spec.key?('globalIPSettings')
252
+ custom_spec['globalIPSettings']['dnsServerList'] = custom_spec['ipsettings']['dnsServerList'] if custom_spec['ipsettings'].key?('dnsServerList')
253
+ custom_spec['globalIPSettings']['dnsSuffixList'] = custom_spec['dnsSuffixList'] || [custom_spec['domain']] if ( custom_spec['dnsSuffixList'] || custom_spec['domain'])
254
+ end
255
+
256
+ if (custom_spec['ipsettings'].key?('ip') or custom_spec['ipsettings'].key?('gateway') or custom_spec['ipsettings'].key?('subnetMask') or custom_spec['ipsettings'].key?('domain') or custom_spec['ipsettings'].key?('dnsServerList'))
257
+ if custom_spec['ipsettings'].key?('ip')
258
+ raise ArgumentError, "subnetMask is required for static ip" unless custom_spec["ipsettings"].key?("subnetMask")
259
+ end
260
+ custom_spec['nicSettingMap']=Array.new unless custom_spec.key?('nicSettingMap')
261
+ custom_spec['nicSettingMap'][0]=Hash.new unless custom_spec['nicSettingMap'].length > 0
262
+ custom_spec['nicSettingMap'][0]['adapter']=Hash.new unless custom_spec['nicSettingMap'][0].key?('adapter')
263
+ custom_spec['nicSettingMap'][0]['adapter']['ip'] = custom_spec['ipsettings']['ip'] if custom_spec['ipsettings'].key?('ip')
264
+ custom_spec['nicSettingMap'][0]['adapter']['gateway'] = custom_spec['ipsettings']['gateway'] if custom_spec['ipsettings'].key?('gateway')
265
+ custom_spec['nicSettingMap'][0]['adapter']['subnetMask'] = custom_spec['ipsettings']['subnetMask'] if custom_spec['ipsettings'].key?('subnetMask')
266
+ custom_spec['nicSettingMap'][0]['adapter']['dnsDomain'] = custom_spec['ipsettings']['domain'] if custom_spec['ipsettings'].key?('domain')
267
+ custom_spec['nicSettingMap'][0]['adapter']['dnsServerList'] = custom_spec['ipsettings']['dnsServerList'] if custom_spec['ipsettings'].key?('dnsServerList')
268
+ end
269
+ end
270
+ ### End of backwards compatability
271
+
272
+ ## requirements check here ##
273
+ raise ArgumentError, "globalIPSettings are required when using Customization Spec" unless custom_spec.key?('globalIPSettings')
274
+ raise ArgumentError, "identity is required when using Customization Spec" unless custom_spec.key?('identity')
275
+
276
+ # encryptionKey
277
+ custom_encryptionKey = custom_spec['encryptionKey'] if custom_spec.key?('encryptionKey')
278
+ custom_encryptionKey ||= nil
279
+
280
+ # globalIPSettings
281
+ # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.GlobalIPSettings.html
282
+ custom_globalIPSettings = RbVmomi::VIM::CustomizationGlobalIPSettings.new()
283
+ custom_globalIPSettings.dnsServerList = custom_spec['globalIPSettings']['dnsServerList'] if custom_spec['globalIPSettings'].key?("dnsServerList")
284
+ custom_globalIPSettings.dnsSuffixList = custom_spec['globalIPSettings']['dnsSuffixList'] if custom_spec['globalIPSettings'].key?("dnsSuffixList")
285
+
286
+ # identity
287
+ # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.IdentitySettings.html
288
+ # Accepts the 3 supported CustomizationIdentitySettings Types:
289
+ # 1. CustomizationLinuxPrep (LinuxPrep) - note the uppercase P
290
+ # 2. CustomizationSysprep (Sysprep)
291
+ # 3. CustomizationSysprepText (SysprepText)
292
+ # At least one of these is required
293
+ #
294
+ identity = custom_spec['identity']
295
+ if identity.key?("LinuxPrep")
296
+ # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.LinuxPrep.html
297
+ # Fields:
298
+ # * domain: string **REQUIRED**
299
+ # * hostName: string (CustomizationName) **REQUIRED** Will use options['name'] if not provided.
300
+ # * hwClockUTC: boolean
301
+ # * timeZone: string (https://pubs.vmware.com/vsphere-55/topic/com.vmware.wssdk.apiref.doc/timezone.html)
302
+ raise ArgumentError, "domain is required when using LinuxPrep identity" unless identity['LinuxPrep'].key?('domain')
303
+ custom_identity = RbVmomi::VIM::CustomizationLinuxPrep(:domain => identity['LinuxPrep']['domain'])
304
+ cust_hostname = RbVmomi::VIM::CustomizationFixedName(:name => identity['LinuxPrep']['hostName']) if identity['LinuxPrep'].key?('hostName')
305
+ cust_hostname ||= RbVmomi::VIM::CustomizationFixedName(:name => options['name'])
306
+ custom_identity.hostName = cust_hostname
307
+ custom_identity.hwClockUTC = identity['LinuxPrep']['hwClockUTC'] if identity['LinuxPrep'].key?('hwClockUTC')
308
+ custom_identity.timeZone = identity['LinuxPrep']['timeZone'] if identity['LinuxPrep'].key?('timeZone')
309
+ elsif identity.key?("Sysprep")
310
+ # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.Sysprep.html
311
+ # Fields:
312
+ # * guiRunOnce: CustomizationGuiRunOnce
313
+ # * guiUnattended: CustomizationGuiUnattended **REQUIRED**
314
+ # * identification: CustomizationIdentification **REQUIRED**
315
+ # * licenseFilePrintData: CustomizationLicenseFilePrintData
316
+ # * userData: CustomizationUserData **REQUIRED**
317
+ #
318
+ raise ArgumentError, "guiUnattended is required when using Sysprep identity" unless identity['Sysprep'].key?('guiUnattended')
319
+ raise ArgumentError, "identification is required when using Sysprep identity" unless identity['Sysprep'].key?('identification')
320
+ raise ArgumentError, "userData is required when using Sysprep identity" unless identity['Sysprep'].key?('userData')
321
+ if identity['Sysprep']['guiRunOnce']
322
+ # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.GuiRunOnce.html
323
+ # Fields:
324
+ # * commandList: array of string **REQUIRED***
325
+ #
326
+ raise ArgumentError, "commandList is required when using Sysprep identity and guiRunOnce" unless identity['Sysprep']['guiRunOnce'].key?('commandList')
327
+ cust_guirunonce = RbVmomi::VIM.CustomizationGuiRunOnce( :commandList => identity['Sysprep']['guiRunOnce']['commandList'] )
328
+ end
329
+ # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.GuiUnattended.html
330
+ # Fields:
331
+ # * autoLogin: boolean **REQUIRED**
332
+ # * autoLogonCount: int **REQUIRED**
333
+ # * timeZone: int (see here for values https://msdn.microsoft.com/en-us/library/ms912391(v=winembedded.11).aspx) **REQUIRED**
334
+ # * password: CustomizationPassword
335
+ raise ArgumentError, "guiUnattended->autoLogon is required when using Sysprep identity" unless identity['Sysprep']['guiUnattended'].key?('autoLogon')
336
+ raise ArgumentError, "guiUnattended->autoLogonCount is required when using Sysprep identity" unless identity['Sysprep']['guiUnattended'].key?('autoLogonCount')
337
+ raise ArgumentError, "guiUnattended->timeZone is required when using Sysprep identity" unless identity['Sysprep']['guiUnattended'].key?('timeZone')
338
+ custom_guiUnattended = RbVmomi::VIM.CustomizationGuiUnattended(
339
+ :autoLogon => identity['Sysprep']['guiUnattended']['autoLogon'],
340
+ :autoLogonCount => identity['Sysprep']['guiUnattended']['autoLogonCount'],
341
+ :timeZone => identity['Sysprep']['guiUnattended']['timeZone']
342
+ )
343
+ if identity['Sysprep']['guiUnattended']['password']
344
+ # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.Password.html
345
+ # Fields:
346
+ # * plainText: boolean **REQUIRED**
347
+ # * value: string **REQUIRED**
348
+ raise ArgumentError, "guiUnattended->password->plainText is required when using Sysprep identity and guiUnattended -> password" unless identity['Sysprep']['guiUnattended']['password'].key?('plainText')
349
+ raise ArgumentError, "guiUnattended->password->value is required when using Sysprep identity and guiUnattended -> password" unless identity['Sysprep']['guiUnattended']['password'].key?('value')
350
+ custom_guiUnattended.password = RbVmomi::VIM.CustomizationPassword(
351
+ :plainText => identity['Sysprep']['guiUnattended']['password']['plainText'],
352
+ :value => identity['Sysprep']['guiUnattended']['password']['value']
353
+ )
354
+ end
355
+ # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.Identification.html
356
+ # Fields:
357
+ # * domainAdmin: string
358
+ # * domainAdminPassword: CustomizationPassword
359
+ # * joinDomain: string *If supplied domainAdmin and domainAdminPassword must be set
360
+ # * joinWorkgroup: string *If supplied, joinDomain, domainAdmin and domainAdminPassword will be ignored
361
+ custom_identification = RbVmomi::VIM.CustomizationIdentification()
362
+ if identity['Sysprep']['identification'].key?('joinWorkgroup')
363
+ custom_identification.joinWorkgroup = identity['Sysprep']['identification']['joinWorkgroup']
364
+ elsif identity['Sysprep']['identification'].key?('joinDomain')
365
+ raise ArgumentError, "identification->domainAdmin is required when using Sysprep identity and identification -> joinDomain" unless identity['Sysprep']['identification'].key?('domainAdmin')
366
+ raise ArgumentError, "identification->domainAdminPassword is required when using Sysprep identity and identification -> joinDomain" unless identity['Sysprep']['identification'].key?('domainAdmin')
367
+ raise ArgumentError, "identification->domainAdminPassword->plainText is required when using Sysprep identity and identification -> joinDomain" unless identity['Sysprep']['identification']['domainAdminPassword'].key?('plainText')
368
+ raise ArgumentError, "identification->domainAdminPassword->value is required when using Sysprep identity and identification -> joinDomain" unless identity['Sysprep']['identification']['domainAdminPassword'].key?('value')
369
+ custom_identification.joinDomain = identity['Sysprep']['identification']['joinDomain']
370
+ custom_identification.domainAdmin = identity['Sysprep']['identification']['domainAdmin']
371
+ # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.Password.html
372
+ # Fields:
373
+ # * plainText: boolean **REQUIRED**
374
+ # * value: string **REQUIRED**
375
+ custom_identification.domainAdminPassword = RbVmomi::VIM.CustomizationPassword(
376
+ :plainText => identity['Sysprep']['identification']['domainAdminPassword']['plainText'],
377
+ :value => identity['Sysprep']['identification']['domainAdminPassword']['value']
378
+ )
379
+ else
380
+ raise ArgumentError, "No valid Indentification found, valid values are 'joinWorkgroup' and 'joinDomain'"
381
+ end
382
+ if identity['Sysprep'].key?('licenseFilePrintData')
383
+ # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.LicenseFilePrintData.html
384
+ # Fields:
385
+ # * autoMode: string (CustomizationLicenseDataMode) ** REQUIRED **, valid strings are: 'perSeat' or 'perServer'
386
+ # * autoUsers: int (valid only if AutoMode = PerServer)
387
+ raise ArgumentError, "licenseFilePrintData->autoMode is required when using Sysprep identity and licenseFilePrintData" unless identity['Sysprep']['licenseFilePrintData'].key?('autoMode')
388
+ raise ArgumentError, "Unsupported autoMode, supported modes are : 'perSeat' or 'perServer'" unless ['perSeat', 'perServer'].include? identity['Sysprep']['licenseFilePrintData']['autoMode']
389
+ custom_licenseFilePrintData = RbVmomi::VIM.CustomizationLicenseFilePrintData(
390
+ :autoMode => RbVmomi::VIM.CustomizationLicenseDataMode(identity['Sysprep']['licenseFilePrintData']['autoMode'])
391
+ )
392
+ if identity['Sysprep']['licenseFilePrintData'].key?('autoUsers')
393
+ custom_licenseFilePrintData.autoUsers = identity['Sysprep']['licenseFilePrintData']['autoUsers'] if identity['Sysprep']['licenseFilePrintData']['autoMode'] == "PerServer"
394
+ end
395
+ end
396
+ # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.UserData.html
397
+ # Fields:
398
+ # * computerName: string (CustomizationFixedName) **REQUIRED**
399
+ # * fullName: string **REQUIRED**
400
+ # * orgName: string **REQUIRED**
401
+ # * productID: string **REQUIRED**
402
+ raise ArgumentError, "userData->computerName is required when using Sysprep identity" unless identity['Sysprep']['userData'].key?('computerName')
403
+ raise ArgumentError, "userData->fullName is required when using Sysprep identity" unless identity['Sysprep']['userData'].key?('fullName')
404
+ raise ArgumentError, "userData->orgName is required when using Sysprep identity" unless identity['Sysprep']['userData'].key?('orgName')
405
+ raise ArgumentError, "userData->productId is required when using Sysprep identity" unless identity['Sysprep']['userData'].key?('productId')
406
+ custom_userData = RbVmomi::VIM.CustomizationUserData(
407
+ :fullName => identity['Sysprep']['userData']['fullName'],
408
+ :orgName => identity['Sysprep']['userData']['orgName'],
409
+ :productId => identity['Sysprep']['userData']['productId'],
410
+ :computerName => RbVmomi::VIM.CustomizationFixedName(:name => identity['Sysprep']['userData']['computerName'])
411
+ )
412
+
413
+ custom_identity = RbVmomi::VIM::CustomizationSysprep(
414
+ :guiUnattended => custom_guiUnattended,
415
+ :identification => custom_identification,
416
+ :userData => custom_userData
417
+ )
418
+ custom_identity.guiRunOnce = cust_guirunonce if defined?(cust_guirunonce)
419
+ custom_identity.licenseFilePrintData = custom_licenseFilePrintData if defined?(custom_licenseFilePrintData)
420
+ elsif identity.key?("SysprepText")
421
+ # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.SysprepText.html
422
+ # Fields:
423
+ # * value: string **REQUIRED**
424
+ raise ArgumentError, "SysprepText -> value is required when using SysprepText identity" unless identity['SysprepText'].key?('value')
425
+ custom_identity = RbVmomi::VIM::CustomizationSysprepText(:value => identity['SysprepText']['value'])
426
+ else
427
+ raise ArgumentError, "At least one of the following valid identities must be supplied: LinuxPrep, Sysprep, SysprepText"
428
+ end
429
+
430
+ if custom_spec.key?("nicSettingMap")
431
+ # custom_spec['nicSettingMap'] is an array of adapater mappings:
432
+ # custom_spec['nicSettingMap'][0]['macAddress']
433
+ # custom_spec['nicSettingMap'][0]['adapter']['ip']
434
+ #https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.AdapterMapping.html
435
+ # Fields:
436
+ # * adapter: CustomizationIPSettings **REQUIRED**
437
+ # * macAddress: string
438
+ raise ArgumentError, "At least one nicSettingMap is required when using nicSettingMap" unless custom_spec['nicSettingMap'].length > 0
439
+ raise ArgumentError, "Adapter is required when using nicSettingMap" unless custom_spec['nicSettingMap'][0].key?('adapter')
440
+
441
+ custom_nicSettingMap = []
442
+ # need to go through array here for each apapter
443
+ custom_spec['nicSettingMap'].each do | nic |
444
+ # https://pubs.vmware.com/vsphere-55/index.jsp?topic=%2Fcom.vmware.wssdk.apiref.doc%2Fvim.vm.customization.IPSettings.html
445
+ # Fields:
446
+ # * dnsDomain: string
447
+ # * gateway: array of string
448
+ # * ip: CustomizationIpGenerator (string) **REQUIRED IF Assigning Static IP***
449
+ # * ipV6Spec: CustomizationIPSettingsIpV6AddressSpec
450
+ # * netBIOS: CustomizationNetBIOSMode (string)
451
+ # * primaryWINS: string
452
+ # * secondaryWINS: string
453
+ # * subnetMask: string - Required if assigning static IP
454
+ if nic['adapter'].key?('ip')
455
+ raise ArgumentError, "SubnetMask is required when assigning static IP when using nicSettingMap -> Adapter" unless nic['adapter'].key?('subnetMask')
456
+ custom_ip = RbVmomi::VIM.CustomizationFixedIp(:ipAddress => nic['adapter']['ip'])
457
+ else
458
+ custom_ip = RbVmomi::VIM::CustomizationDhcpIpGenerator.new()
459
+ end
460
+ custom_adapter = RbVmomi::VIM.CustomizationIPSettings(:ip => custom_ip)
461
+ custom_adapter.dnsDomain = nic['adapter']['dnsDomain'] if nic['adapter'].key?('dnsDomain')
462
+ custom_adapter.dnsServerList = nic['adapter']['dnsServerList'] if nic['adapter'].key?('dnsServerList')
463
+ custom_adapter.gateway = nic['adapter']['gateway'] if nic['adapter'].key?('gateway')
464
+ if nic['adapter'].key?('ipV6Spec')
465
+ # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.IPSettings.IpV6AddressSpec.html
466
+ # Fields:
467
+ # * gateway: array of string
468
+ # * ip: CustomizationIpV6Generator[] **Required if setting static IP **
469
+ if nic['adapter']['ipV6Spec'].key?('ipAddress')
470
+ raise ArgumentError, "SubnetMask is required when assigning static IPv6 when using nicSettingMap -> Adapter -> ipV6Spec" unless nic['adapter']['ipV6Spec'].key?('subnetMask')
471
+ # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.FixedIpV6.html
472
+ # * ipAddress: string **REQUIRED**
473
+ # * subnetMask: int **REQUIRED**
474
+ custom_ipv6 = RbVmomi::VIM.CustomizationFixedIpV6(
475
+ :ipAddress => nic['adapter']['ipV6Spec']['ipAddress'],
476
+ :subnetMask => nic['adapter']['ipV6Spec']['subnetMask']
477
+ )
478
+ else
479
+ custom_ipv6 = RbVmomi::VIM::CustomizationDhcpIpV6Generator.new()
480
+ end
481
+ custom_ipv6Spec = RbVmomi::VIM.CustomizationIPSettingsIpV6AddressSpec(:ip => custom_ipv6)
482
+ custom_ipv6Spec.gateway = nic['adapter']['ipV6Spec']['gateway'] if nic['adapter']['ipV6Spec'].key?('gateway')
483
+ custom_adapter.ipV6Spec = custom_ipv6Spec
484
+ end
485
+ if nic['adapter'].key?('netBIOS')
486
+ # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.IPSettings.NetBIOSMode.html
487
+ # Fields:
488
+ # netBIOS: string matching: 'disableNetBIOS','enableNetBIOS' or 'enableNetBIOSViaDhcp' ** REQUIRED **
489
+ #
490
+ raise ArgumentError, "Unsupported NetBIOSMode, supported modes are : 'disableNetBIOS','enableNetBIOS' or 'enableNetBIOSViaDhcp'" unless ['disableNetBIOS','enableNetBIOS','enableNetBIOSViaDhcp'].include? nic['adapter']['netBIOS']
491
+ custom_adapter.netBIOS = RbVmomi::VIM.CustomizationNetBIOSMode(nic['adapter']['netBIOS'])
492
+ end
493
+ custom_adapter.primaryWINS = nic['adapter']['primaryWINS'] if nic['adapter'].key?('primaryWINS')
494
+ custom_adapter.secondaryWINS = nic['adapter']['secondaryWINS'] if nic['adapter'].key?('secondaryWINS')
495
+ custom_adapter.subnetMask = nic['adapter']['subnetMask'] if nic['adapter'].key?('subnetMask')
496
+
497
+ custom_adapter_mapping = RbVmomi::VIM::CustomizationAdapterMapping(:adapter => custom_adapter)
498
+ custom_adapter_mapping.macAddress = nic['macAddress'] if nic.key?('macAddress')
499
+
500
+ # build the adapters array, creates it if not already created, otherwise appends to it
501
+ custom_nicSettingMap << custom_adapter_mapping
502
+ end
503
+ end
504
+ custom_nicSettingMap = nil if custom_nicSettingMap.length < 1
505
+
506
+ if custom_spec.key?("options")
507
+ # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.Options.html
508
+ # this currently doesn't have any Linux options, just windows
509
+ # Fields:
510
+ # * changeSID: boolean **REQUIRED**
511
+ # * deleteAccounts: boolean **REQUIRED** **note deleteAccounts is deprecated as of VI API 2.5 so can be ignored
512
+ # * reboot: CustomizationSysprepRebootOption: (string) one of following 'noreboot', reboot' or 'shutdown' (defaults to reboot)
513
+ raise ArgumentError, "changeSID id required when using Windows Options" unless custom_spec['options'].key?('changeSID')
514
+ raise ArgumentError, "deleteAccounts id required when using Windows Options" unless custom_spec['options'].key?('deleteAccounts')
515
+ custom_options = RbVmomi::VIM::CustomizationWinOptions(
516
+ :changeSID => custom_spec['options']['changeSID'],
517
+ :deleteAccounts => custom_spec['options']['deleteAccounts']
518
+ )
519
+ if custom_spec['options'].key?('reboot')
520
+ raise ArgumentError, "Unsupported reboot option, supported options are : 'noreboot', 'reboot' or 'shutdown'" unless ['noreboot','reboot','shutdown'].include? custom_spec['options']['reboot']
521
+ custom_options.reboot = RBVmomi::VIM.CustomizationSysprepRebootOption(custom_spec['options']['reboot'])
522
+ end
523
+ end
524
+ custom_options ||=nil
525
+
526
+ # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.Specification.html
527
+ customization_spec = RbVmomi::VIM::CustomizationSpec(
528
+ :globalIPSettings => custom_globalIPSettings,
529
+ :identity => custom_identity
530
+ )
531
+ customization_spec.encryptionKey = custom_encryptionKey if defined?(custom_encryptionKey)
532
+ customization_spec.nicSettingMap = custom_nicSettingMap if defined?(custom_nicSettingMap)
533
+ customization_spec.options = custom_options if defined?(custom_options)
534
+
535
+ end
536
+ customization_spec ||= nil
537
+
538
+ relocation_spec=nil
539
+ if ( options['linked_clone'] )
540
+ # cribbed heavily from the rbvmomi clone_vm.rb
541
+ # this chunk of code reconfigures the disk of the clone source to be read only,
542
+ # and then creates a delta disk on top of that, this is required by the API in order to create
543
+ # linked clondes
544
+ disks = vm_mob_ref.config.hardware.device.select do |vm_device|
545
+ vm_device.class == RbVmomi::VIM::VirtualDisk
546
+ end
547
+ disks.select{|vm_device| vm_device.backing.parent == nil}.each do |disk|
548
+ disk_spec = {
549
+ :deviceChange => [
550
+ {
551
+ :operation => :remove,
552
+ :device => disk
553
+ },
554
+ {
555
+ :operation => :add,
556
+ :fileOperation => :create,
557
+ :device => disk.dup.tap{|disk_backing|
558
+ disk_backing.backing = disk_backing.backing.dup;
559
+ disk_backing.backing.fileName = "[#{disk.backing.datastore.name}]";
560
+ disk_backing.backing.parent = disk.backing
561
+ }
562
+ }
563
+ ]
564
+ }
565
+ vm_mob_ref.ReconfigVM_Task(:spec => disk_spec).wait_for_completion
566
+ end
567
+ # Next, create a Relocation Spec instance
568
+ relocation_spec = RbVmomi::VIM.VirtualMachineRelocateSpec(:datastore => datastore_obj,
569
+ :pool => resource_pool,
570
+ :diskMoveType => :moveChildMostDiskBacking)
571
+ else
572
+ relocation_spec = RbVmomi::VIM.VirtualMachineRelocateSpec(:datastore => datastore_obj,
573
+ :pool => resource_pool,
574
+ :transform => options['transform'] || 'sparse')
575
+ end
576
+ # And the clone specification
577
+ clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(:location => relocation_spec,
578
+ :config => virtual_machine_config_spec,
579
+ :customization => customization_spec,
580
+ :powerOn => options.key?('power_on') ? options['power_on'] : true,
581
+ :template => false)
582
+
583
+ # Perform the actual Clone Task
584
+ task = vm_mob_ref.CloneVM_Task(:folder => dest_folder,
585
+ :name => options['name'],
586
+ :spec => clone_spec)
587
+ # Waiting for the VM to complete allows us to get the VirtulMachine
588
+ # object of the new machine when it's done. It is HIGHLY recommended
589
+ # to set 'wait' => true if your app wants to wait. Otherwise, you're
590
+ # going to have to reload the server model over and over which
591
+ # generates a lot of time consuming API calls to vmware.
592
+ if options.fetch('wait', true) then
593
+ # REVISIT: It would be awesome to call a block passed to this
594
+ # request to notify the application how far along in the process we
595
+ # are. I'm thinking of updating a progress bar, etc...
596
+ new_vm = task.wait_for_completion
597
+ else
598
+ tries = 0
599
+ new_vm = begin
600
+ # Try and find the new VM (folder.find is quite efficient)
601
+ dest_folder.find(options['name'], RbVmomi::VIM::VirtualMachine) or raise Fog::Vsphere::Errors::NotFound
602
+ rescue Fog::Vsphere::Errors::NotFound
603
+ tries += 1
604
+ if tries <= 10 then
605
+ sleep 15
606
+ retry
607
+ end
608
+ nil
609
+ end
610
+ end
611
+
612
+ # Return hash
613
+ {
614
+ 'vm_ref' => new_vm ? new_vm._ref : nil,
615
+ 'new_vm' => new_vm ? convert_vm_mob_ref_to_attr_hash(new_vm) : nil,
616
+ 'task_ref' => task._ref
617
+ }
618
+ end
619
+
620
+ # Build up the network config spec for simple case:
621
+ # simple case: apply just the network_label, nic_type and network_adapter_device_key
622
+ def modify_template_nics_simple_spec(network_label, nic_type, network_adapter_device_key, datacenter)
623
+ config_spec_operation = RbVmomi::VIM::VirtualDeviceConfigSpecOperation('edit')
624
+ # Get the portgroup and handle it from there.
625
+ network = get_raw_network(network_label, datacenter)
626
+ if ( network.kind_of? RbVmomi::VIM::DistributedVirtualPortgroup)
627
+ # Create the NIC backing for the distributed virtual portgroup
628
+ nic_backing_info = RbVmomi::VIM::VirtualEthernetCardDistributedVirtualPortBackingInfo(
629
+ :port => RbVmomi::VIM::DistributedVirtualSwitchPortConnection(
630
+ :portgroupKey => network.key,
631
+ :switchUuid => network.config.distributedVirtualSwitch.uuid
632
+ )
633
+ )
634
+ else
635
+ # Otherwise it's a non distributed port group
636
+ nic_backing_info = RbVmomi::VIM::VirtualEthernetCardNetworkBackingInfo(:deviceName => network_label)
637
+ end
638
+ connectable = RbVmomi::VIM::VirtualDeviceConnectInfo(
639
+ :allowGuestControl => true,
640
+ :connected => true,
641
+ :startConnected => true)
642
+ device = RbVmomi::VIM.public_send "#{nic_type}",
643
+ :backing => nic_backing_info,
644
+ :deviceInfo => RbVmomi::VIM::Description(:label => "Network adapter 1", :summary => network_label),
645
+ :key => network_adapter_device_key,
646
+ :connectable => connectable
647
+ device_spec = RbVmomi::VIM::VirtualDeviceConfigSpec(
648
+ :operation => config_spec_operation,
649
+ :device => device)
650
+ return device_spec
651
+ end
652
+
653
+
654
+ def modify_template_nics_specs(template_path, new_nics, datacenter)
655
+ template_nics = list_vm_interfaces(template_path, datacenter).map do |old_attributes|
656
+ Fog::Compute::Vsphere::Interface.new(old_attributes)
657
+ end
658
+ specs = []
659
+
660
+ template_nics.each do |interface|
661
+ specs << create_interface(interface, interface.key, :remove, :datacenter => datacenter)
662
+ end
663
+
664
+ new_nics.each do |interface|
665
+ specs << create_interface(interface, 0, :add, :datacenter => datacenter)
666
+ end
667
+
668
+ return specs
669
+ end
670
+
671
+ def modify_template_volumes_specs(vm_mob_ref, volumes)
672
+ template_volumes = vm_mob_ref.config.hardware.device.grep(RbVmomi::VIM::VirtualDisk)
673
+ modified_volumes = volumes.take(template_volumes.size)
674
+ new_volumes = volumes.drop(template_volumes.size)
675
+
676
+ specs = []
677
+ template_volumes.zip(modified_volumes).each do |template_volume, new_volume|
678
+ if new_volume
679
+ # updated the attribtues on the existing volume
680
+ # it's not allowed to reduce the size of the volume when cloning
681
+ if new_volume.size > template_volume.capacityInKB
682
+ template_volume.capacityInKB = new_volume.size
683
+ end
684
+ template_volume.backing.diskMode = new_volume.mode
685
+ template_volume.backing.thinProvisioned = new_volume.thin
686
+ specs << { :operation => :edit, :device => template_volume }
687
+ else
688
+ specs << { :operation => :remove,
689
+ :fileOperation => :destroy,
690
+ :device => template_volume }
691
+ end
692
+ end
693
+ specs.concat(new_volumes.map { |volume| create_disk(volume, volumes.index(volume)) })
694
+ return specs
695
+ end
696
+ end
697
+
698
+ class Mock
699
+ include Shared
700
+ def vm_clone(options = {})
701
+ # Option handling TODO Needs better method of checking
702
+ options = vm_clone_check_options(options)
703
+ notfound = lambda { raise Fog::Compute::Vsphere::NotFound, "Could not find VM template" }
704
+ template = list_virtual_machines.find(notfound) do |vm|
705
+ vm['name'] == options['template_path'].split("/")[-1]
706
+ end
707
+
708
+ # generate a random id
709
+ id = [8,4,4,4,12].map{|i| Fog::Mock.random_hex(i)}.join("-")
710
+ new_vm = template.clone.merge({
711
+ "name" => options['name'],
712
+ "id" => id,
713
+ "instance_uuid" => id,
714
+ "path" => "/Datacenters/#{options['datacenter']}/#{options['dest_folder'] ? options['dest_folder']+"/" : ""}#{options['name']}"
715
+ })
716
+ self.data[:servers][id] = new_vm
717
+
718
+ {
719
+ 'vm_ref' => "vm-#{Fog::Mock.random_numbers(3)}",
720
+ 'new_vm' => new_vm,
721
+ 'task_ref' => "task-#{Fog::Mock.random_numbers(4)}",
722
+ }
723
+ end
724
+ end
725
+ end
726
+ end
727
+ end