knife-vsphere 1.0.1 → 1.2.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/lib/chef/knife/base_vsphere_command.rb +383 -371
  3. data/lib/chef/knife/customization_helper.rb +40 -0
  4. data/lib/chef/knife/vsphere_cluster_list.rb +47 -0
  5. data/lib/chef/knife/vsphere_cpu_ratio.rb +41 -45
  6. data/lib/chef/knife/vsphere_customization_list.rb +24 -29
  7. data/lib/chef/knife/vsphere_datastore_list.rb +68 -72
  8. data/lib/chef/knife/vsphere_datastore_maxfree.rb +48 -49
  9. data/lib/chef/knife/vsphere_datastorecluster_list.rb +66 -71
  10. data/lib/chef/knife/vsphere_datastorecluster_maxfree.rb +75 -84
  11. data/lib/chef/knife/vsphere_folder_list.rb +28 -30
  12. data/lib/chef/knife/vsphere_hosts_list.rb +42 -42
  13. data/lib/chef/knife/vsphere_pool_list.rb +46 -48
  14. data/lib/chef/knife/vsphere_pool_query.rb +58 -58
  15. data/lib/chef/knife/vsphere_template_list.rb +30 -32
  16. data/lib/chef/knife/vsphere_vlan_create.rb +51 -0
  17. data/lib/chef/knife/vsphere_vlan_list.rb +35 -37
  18. data/lib/chef/knife/vsphere_vm_clone.rb +834 -581
  19. data/lib/chef/knife/vsphere_vm_config.rb +48 -46
  20. data/lib/chef/knife/vsphere_vm_delete.rb +70 -66
  21. data/lib/chef/knife/vsphere_vm_execute.rb +62 -66
  22. data/lib/chef/knife/vsphere_vm_list.rb +57 -61
  23. data/lib/chef/knife/vsphere_vm_markastemplate.rb +48 -54
  24. data/lib/chef/knife/vsphere_vm_migrate.rb +73 -0
  25. data/lib/chef/knife/vsphere_vm_move.rb +88 -0
  26. data/lib/chef/knife/vsphere_vm_net.rb +57 -0
  27. data/lib/chef/knife/vsphere_vm_property_get.rb +44 -46
  28. data/lib/chef/knife/vsphere_vm_property_set.rb +83 -84
  29. data/lib/chef/knife/vsphere_vm_query.rb +48 -48
  30. data/lib/chef/knife/vsphere_vm_snapshot.rb +124 -130
  31. data/lib/chef/knife/vsphere_vm_state.rb +122 -127
  32. data/lib/chef/knife/vsphere_vm_toolsconfig.rb +54 -52
  33. data/lib/chef/knife/vsphere_vm_vmdk_add.rb +234 -241
  34. data/lib/chef/knife/vsphere_vm_wait_sysprep.rb +54 -0
  35. data/lib/knife-vsphere/version.rb +3 -4
  36. metadata +43 -15
  37. data/lib/chef/knife/vshpere_vm_migrate.rb +0 -80
  38. data/lib/chef/knife/vshpere_vm_move.rb +0 -92
  39. data/lib/chef/knife/vshpere_vm_net.rb +0 -57
@@ -1,58 +1,58 @@
1
- require 'chef/knife'
2
- require 'chef/knife/base_vsphere_command'
3
- require 'rbvmomi'
4
- require 'netaddr'
5
-
6
- class Chef::Knife::VspherePoolQuery < Chef::Knife::BaseVsphereCommand
7
- banner "knife vsphere pool query POOLNAME QUERY. See \"http://pubs.vmware.com/vi3/sdk/ReferenceGuide/vim.ComputeResource.html\" for allowed QUERY values."
8
-
9
- get_common_options
10
-
11
- def traverse_folders_for_pool(folder, poolname)
12
- children = folder.children.find_all
13
- children.each do |child|
14
- if child.class == RbVmomi::VIM::ClusterComputeResource || child.class == RbVmomi::VIM::ComputeResource || child.class == RbVmomi::VIM::ResourcePool
15
- if child.name == poolname then return child end
16
- elsif child.class == RbVmomi::VIM::Folder
17
- pool = traverse_folders_for_pool(child, poolname)
18
- if pool then return pool end
19
- end
20
- end
21
- return false
22
- end
23
-
24
- def run
25
- $stdout.sync = true
26
- poolname = @name_args[0]
27
- if poolname.nil?
28
- show_usage
29
- fatal_exit("You must specify a resource poor or cluster name (see knife vsphere pool list)")
30
- end
31
-
32
- query_string = @name_args[1]
33
- if query_string.nil?
34
- show_usage
35
- fatal_exit("You must specify a QUERY value (e.g. summary.overallStatus )")
36
- end
37
-
38
- vim = get_vim_connection
39
-
40
- dc = get_datacenter
41
- folder = dc.hostFolder
42
-
43
- pool = traverse_folders_for_pool(folder, poolname) or abort "Pool #{poolname} not found"
44
-
45
- # split QUERY by dots, and walk the object model
46
- query = query_string.split '.'
47
- result = pool
48
- query.each do |part|
49
- message, index = part.split(/[\[\]]/)
50
- unless result.respond_to? message.to_sym
51
- fatal_exit("\"#{query_string}\" not recognized.")
52
- end
53
-
54
- result = index ? result.send(message)[index.to_i] : result.send(message)
55
- end
56
- puts result
57
- end
58
- end
1
+ require 'chef/knife'
2
+ require 'chef/knife/base_vsphere_command'
3
+ require 'rbvmomi'
4
+ require 'netaddr'
5
+
6
+ class Chef::Knife::VspherePoolQuery < Chef::Knife::BaseVsphereCommand
7
+ banner "knife vsphere pool query POOLNAME QUERY. See \"http://pubs.vmware.com/vi3/sdk/ReferenceGuide/vim.ComputeResource.html\" for allowed QUERY values."
8
+
9
+ common_options
10
+
11
+ def traverse_folders_for_pool(folder, poolname)
12
+ children = folder.children.find_all
13
+ children.each do |child|
14
+ if child.class == RbVmomi::VIM::ClusterComputeResource || child.class == RbVmomi::VIM::ComputeResource || child.class == RbVmomi::VIM::ResourcePool
15
+ return child if child.name == poolname
16
+ elsif child.class == RbVmomi::VIM::Folder
17
+ pool = traverse_folders_for_pool(child, poolname)
18
+ return pool if pool
19
+ end
20
+ end
21
+ false
22
+ end
23
+
24
+ def run
25
+ $stdout.sync = true
26
+ poolname = @name_args[0]
27
+ if poolname.nil?
28
+ show_usage
29
+ fatal_exit('You must specify a resource poor or cluster name (see knife vsphere pool list)')
30
+ end
31
+
32
+ query_string = @name_args[1]
33
+ if query_string.nil?
34
+ show_usage
35
+ fatal_exit('You must specify a QUERY value (e.g. summary.overallStatus )')
36
+ end
37
+
38
+ vim_connection
39
+
40
+ dc = datacenter
41
+ folder = dc.hostFolder
42
+
43
+ pool = traverse_folders_for_pool(folder, poolname) || abort("Pool #{poolname} not found")
44
+
45
+ # split QUERY by dots, and walk the object model
46
+ query = query_string.split '.'
47
+ result = pool
48
+ query.each do |part|
49
+ message, index = part.split(/[\[\]]/)
50
+ unless result.respond_to? message.to_sym
51
+ fatal_exit("\"#{query_string}\" not recognized.")
52
+ end
53
+
54
+ result = index ? result.send(message)[index.to_i] : result.send(message)
55
+ end
56
+ puts result
57
+ end
58
+ end
@@ -1,32 +1,30 @@
1
- #
2
- # Author:: Ezra Pagel (<ezra@cpan.org>)
3
- # License:: Apache License, Version 2.0
4
- #
5
-
6
- require 'chef/knife'
7
- require 'chef/knife/base_vsphere_command'
8
-
9
- # Lists all known VM templates in the configured datacenter
10
- class Chef::Knife::VsphereTemplateList < Chef::Knife::BaseVsphereCommand
11
-
12
- banner "knife vsphere template list"
13
-
14
- get_common_options
15
-
16
- def run
17
-
18
- $stdout.sync = true
19
- $stderr.sync = true
20
-
21
- vim = get_vim_connection
22
-
23
- baseFolder = find_folder(get_config(:folder));
24
-
25
- vms = find_all_in_folder(baseFolder, RbVmomi::VIM::VirtualMachine).
26
- select { |v| !v.config.nil? && v.config.template == true }
27
-
28
- vms.each do |vm|
29
- puts "#{ui.color("Template Name", :cyan)}: #{vm.name}"
30
- end
31
- end
32
- end
1
+ #
2
+ # Author:: Ezra Pagel (<ezra@cpan.org>)
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+
6
+ require 'chef/knife'
7
+ require 'chef/knife/base_vsphere_command'
8
+
9
+ # Lists all known VM templates in the configured datacenter
10
+ class Chef::Knife::VsphereTemplateList < Chef::Knife::BaseVsphereCommand
11
+ banner 'knife vsphere template list'
12
+
13
+ common_options
14
+
15
+ def run
16
+ $stdout.sync = true
17
+ $stderr.sync = true
18
+
19
+ vim_connection
20
+
21
+ base_folder = find_folder(get_config(:folder))
22
+
23
+ vms = find_all_in_folder(base_folder, RbVmomi::VIM::VirtualMachine)
24
+ .select { |v| !v.config.nil? && v.config.template == true }
25
+
26
+ vms.each do |vm|
27
+ puts "#{ui.color('Template Name', :cyan)}: #{vm.name}"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,51 @@
1
+ require 'chef/knife'
2
+ require 'chef/knife/base_vsphere_command'
3
+
4
+ # Lists all known data stores in datacenter with sizes
5
+ class Chef::Knife::VsphereVlanCreate < Chef::Knife::BaseVsphereCommand
6
+ banner 'knife vsphere vlan create NAME VID'
7
+
8
+ common_options
9
+
10
+ option :switch,
11
+ long: '--switch DVSNAME',
12
+ description: 'The DVSwitch that will hold this VLAN'
13
+
14
+ def run
15
+ $stdout.sync = true
16
+
17
+ vim_connection
18
+ net = datacenter.networkFolder
19
+
20
+ switches = net.children.select { |n| n.class == RbVmomi::VIM::VmwareDistributedVirtualSwitch }
21
+ switch = if config[:switch]
22
+ switches.find { |s| s.name == config[:switch] }
23
+ else
24
+ ui.warn 'Multiple switches found. Choosing the first switch. Use --switch to select a switch.' if switches.count > 1
25
+ switches.first
26
+ end
27
+
28
+ fatal_exit 'No switches found.' if switch.nil?
29
+
30
+ ui.info "Found #{switch.name}" if log_verbose?
31
+ switch.AddDVPortgroup_Task(spec: [add_port_spec(@name_args[0], @name_args[1])])
32
+ end
33
+
34
+ private
35
+
36
+ def add_port_spec(name, vlan_id)
37
+ spec = RbVmomi::VIM::DVPortgroupConfigSpec(
38
+ defaultPortConfig: RbVmomi::VIM::VMwareDVSPortSetting(
39
+ vlan: RbVmomi::VIM::VmwareDistributedVirtualSwitchVlanIdSpec(
40
+ vlanId: vlan_id.to_i,
41
+ inherited: false
42
+ )
43
+ ),
44
+ name: name,
45
+ numPorts: 128,
46
+ type: 'earlyBinding'
47
+ )
48
+ pp spec if log_verbose?
49
+ spec
50
+ end
51
+ end
@@ -1,37 +1,35 @@
1
- # Author: Jesse Campbell
2
- #
3
- # Permission to use, copy, modify, and/or distribute this software for
4
- # any purpose with or without fee is hereby granted, provided that the
5
- # above copyright notice and this permission notice appear in all
6
- # copies.
7
- #
8
- # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
9
- # WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
10
- # WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
11
- # AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
12
- # DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
13
- # OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
14
- # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
- # PERFORMANCE OF THIS SOFTWARE
16
-
17
- require 'chef/knife'
18
- require 'chef/knife/base_vsphere_command'
19
-
20
- # Lists all known data stores in datacenter with sizes
21
- class Chef::Knife::VsphereVlanList < Chef::Knife::BaseVsphereCommand
22
-
23
- banner "knife vsphere vlan list"
24
-
25
- get_common_options
26
-
27
- def run
28
- $stdout.sync = true
29
-
30
- vim = get_vim_connection
31
- dc = get_datacenter
32
- dc.network.each do |network|
33
- puts "#{ui.color("VLAN", :cyan)}: #{network.name}"
34
- end
35
- end
36
- end
37
-
1
+ # Author: Jesse Campbell
2
+ #
3
+ # Permission to use, copy, modify, and/or distribute this software for
4
+ # any purpose with or without fee is hereby granted, provided that the
5
+ # above copyright notice and this permission notice appear in all
6
+ # copies.
7
+ #
8
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
9
+ # WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
10
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
11
+ # AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
12
+ # DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
13
+ # OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
14
+ # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ # PERFORMANCE OF THIS SOFTWARE
16
+
17
+ require 'chef/knife'
18
+ require 'chef/knife/base_vsphere_command'
19
+
20
+ # Lists all known data stores in datacenter with sizes
21
+ class Chef::Knife::VsphereVlanList < Chef::Knife::BaseVsphereCommand
22
+ banner 'knife vsphere vlan list'
23
+
24
+ common_options
25
+
26
+ def run
27
+ $stdout.sync = true
28
+
29
+ vim_connection
30
+ dc = datacenter
31
+ dc.network.each do |network|
32
+ puts "#{ui.color('VLAN', :cyan)}: #{network.name}"
33
+ end
34
+ end
35
+ end
@@ -1,581 +1,834 @@
1
- #
2
- # Author:: Ezra Pagel (<ezra@cpan.org>)
3
- # Contributor:: Jesse Campbell (<hikeit@gmail.com>)
4
- # Contributor:: Bethany Erskine (<bethany@paperlesspost.com>)
5
- # Contributor:: Adrian Stanila (https://github.com/sacx)
6
- # License:: Apache License, Version 2.0
7
- #
8
-
9
- require 'chef/knife'
10
- require 'chef/knife/base_vsphere_command'
11
- require 'rbvmomi'
12
- require 'netaddr'
13
-
14
- # Clone an existing template into a new VM, optionally applying a customization specification.
15
- # usage:
16
- # knife vsphere vm clone NewNode UbuntuTemplate --cspec StaticSpec \
17
- # --cips 192.168.0.99/24,192.168.1.99/24 \
18
- # --chostname NODENAME --cdomain NODEDOMAIN
19
- class Chef::Knife::VsphereVmClone < Chef::Knife::BaseVsphereCommand
20
-
21
- banner "knife vsphere vm clone VMNAME (options)"
22
-
23
- get_common_options
24
-
25
- option :dest_folder,
26
- :long => "--dest-folder FOLDER",
27
- :description => "The folder into which to put the cloned VM"
28
-
29
- option :datastore,
30
- :long => "--datastore STORE",
31
- :description => "The datastore into which to put the cloned VM"
32
-
33
- option :datastorecluster,
34
- :long => "--datastorecluster STORE",
35
- :description => "The datastorecluster into which to put the cloned VM"
36
-
37
- option :resource_pool,
38
- :long => "--resource-pool POOL",
39
- :description => "The resource pool into which to put the cloned VM"
40
-
41
- option :source_vm,
42
- :long => "--template TEMPLATE",
43
- :description => "The source VM / Template to clone from"
44
-
45
- option :linked_clone,
46
- :long => "--linked-clone",
47
- :description => "Indicates whether to use linked clones.",
48
- :boolean => false
49
-
50
- option :linked_clone,
51
- :long => "--linked-clone",
52
- :description => "Indicates whether to use linked clones.",
53
- :boolean => false
54
-
55
- option :thin_provision,
56
- :long => "--thin-provision",
57
- :description => "Indicates whether disk should be thin provisioned.",
58
- :boolean => true
59
-
60
- option :annotation,
61
- :long => "--annotation TEXT",
62
- :description => "Add TEXT in Notes field from annotation"
63
-
64
- option :customization_spec,
65
- :long => "--cspec CUST_SPEC",
66
- :description => "The name of any customization specification to apply"
67
-
68
- option :customization_plugin,
69
- :long => "--cplugin CUST_PLUGIN_PATH",
70
- :description => "Path to plugin that implements KnifeVspherePlugin.customize_clone_spec and/or KnifeVspherePlugin.reconfig_vm"
71
-
72
- option :customization_plugin_data,
73
- :long => "--cplugin-data CUST_PLUGIN_DATA",
74
- :description => "String of data to pass to the plugin. Use any format you wish."
75
-
76
- option :customization_vlan,
77
- :long => "--cvlan CUST_VLANS",
78
- :description => "Comma-delimited list of VLAN names for network adapters to join"
79
-
80
- option :customization_ips,
81
- :long => "--cips CUST_IPS",
82
- :description => "Comma-delimited list of CIDR IPs for customization"
83
-
84
- option :customization_dns_ips,
85
- :long => "--cdnsips CUST_DNS_IPS",
86
- :description => "Comma-delimited list of DNS IP addresses"
87
-
88
- option :customization_dns_suffixes,
89
- :long => "--cdnssuffix CUST_DNS_SUFFIXES",
90
- :description => "Comma-delimited list of DNS search suffixes"
91
-
92
- option :customization_gw,
93
- :long => "--cgw CUST_GW",
94
- :description => "CIDR IP of gateway for customization"
95
-
96
- option :customization_hostname,
97
- :long => "--chostname CUST_HOSTNAME",
98
- :description => "Unqualified hostname for customization"
99
-
100
- option :customization_domain,
101
- :long => "--cdomain CUST_DOMAIN",
102
- :description => "Domain name for customization"
103
-
104
- option :customization_tz,
105
- :long => "--ctz CUST_TIMEZONE",
106
- :description => "Timezone invalid 'Area/Location' format"
107
-
108
- option :customization_cpucount,
109
- :long => "--ccpu CUST_CPU_COUNT",
110
- :description => "Number of CPUs"
111
-
112
- option :customization_memory,
113
- :long => "--cram CUST_MEMORY_GB",
114
- :description => "Gigabytes of RAM"
115
-
116
- option :power,
117
- :long => "--start",
118
- :description => "Indicates whether to start the VM after a successful clone",
119
- :boolean => false
120
-
121
- option :bootstrap,
122
- :long => "--bootstrap",
123
- :description => "Indicates whether to bootstrap the VM",
124
- :boolean => false
125
-
126
- option :environment,
127
- :long => "--environment ENVIRONMENT",
128
- :description => "Environment to add the node to for bootstrapping"
129
-
130
- option :fqdn,
131
- :long => "--fqdn SERVER_FQDN",
132
- :description => "Fully qualified hostname for bootstrapping"
133
-
134
- option :ssh_user,
135
- :short => "-x USERNAME",
136
- :long => "--ssh-user USERNAME",
137
- :description => "The ssh username"
138
- $default[:ssh_user] = "root"
139
-
140
- option :ssh_password,
141
- :short => "-P PASSWORD",
142
- :long => "--ssh-password PASSWORD",
143
- :description => "The ssh password"
144
-
145
- option :ssh_port,
146
- :short => "-p PORT",
147
- :long => "--ssh-port PORT",
148
- :description => "The ssh port"
149
- $default[:ssh_port] = 22
150
-
151
- option :identity_file,
152
- :short => "-i IDENTITY_FILE",
153
- :long => "--identity-file IDENTITY_FILE",
154
- :description => "The SSH identity file used for authentication"
155
-
156
- option :chef_node_name,
157
- :short => "-N NAME",
158
- :long => "--node-name NAME",
159
- :description => "The Chef node name for your new node"
160
-
161
- option :prerelease,
162
- :long => "--prerelease",
163
- :description => "Install the pre-release chef gems",
164
- :boolean => false
165
-
166
- option :bootstrap_version,
167
- :long => "--bootstrap-version VERSION",
168
- :description => "The version of Chef to install",
169
- :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
170
-
171
- option :bootstrap_proxy,
172
- :long => "--bootstrap-proxy PROXY_URL",
173
- :description => "The proxy server for the node being bootstrapped",
174
- :proc => Proc.new { |p| Chef::Config[:knife][:bootstrap_proxy] = p }
175
-
176
- option :distro,
177
- :short => "-d DISTRO",
178
- :long => "--distro DISTRO",
179
- :description => "Bootstrap a distro using a template"
180
-
181
- option :template_file,
182
- :long => "--template-file TEMPLATE",
183
- :description => "Full path to location of template to use"
184
-
185
- option :run_list,
186
- :short => "-r RUN_LIST",
187
- :long => "--run-list RUN_LIST",
188
- :description => "Comma separated list of roles/recipes to apply"
189
- $default[:run_list] = ''
190
-
191
- option :secret_file,
192
- :long => "--secret-file SECRET_FILE",
193
- :description => "A file containing the secret key to use to encrypt data bag item values",
194
- :proc => lambda { |secret_file| Chef::Config[:knife][:secret_file] = secret_file }
195
-
196
- option :hint,
197
- :long => "--hint HINT_NAME[=HINT_FILE]",
198
- :description => "Specify Ohai Hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.",
199
- :proc => Proc.new { |h|
200
- Chef::Config[:knife][:hints] ||= Hash.new
201
- name, path = h.split("=")
202
- Chef::Config[:knife][:hints][name] = path ? JSON.parse(::File.read(path)) : Hash.new }
203
- $default[:hint] = ''
204
-
205
- option :no_host_key_verify,
206
- :long => "--no-host-key-verify",
207
- :description => "Disable host key verification",
208
- :boolean => true
209
-
210
- option :first_boot_attributes,
211
- :short => "-j JSON_ATTRIBS",
212
- :long => "--json-attributes",
213
- :description => "A JSON string to be added to the first run of chef-client",
214
- :proc => lambda { |o| JSON.parse(o) },
215
- :default => {}
216
-
217
- option :disable_customization,
218
- :long => "--disable-customization",
219
- :description => "Disable default customization",
220
- :boolean => true,
221
- :default => false
222
-
223
- option :log_level,
224
- :short => "-l LEVEL",
225
- :long => "--log_level",
226
- :description => "Set the log level (debug, info, warn, error, fatal) for chef-client",
227
- :proc => lambda { |l| l.to_sym }
228
-
229
- option :mark_as_template,
230
- :long => "--mark_as_template",
231
- :description => "Indicates whether to mark the new vm as a template",
232
- :boolean => false
233
-
234
- def run
235
- $stdout.sync = true
236
-
237
- vmname = @name_args[0]
238
- if vmname.nil?
239
- show_usage
240
- fatal_exit("You must specify a virtual machine name")
241
- end
242
- config[:chef_node_name] = vmname unless config[:chef_node_name]
243
- config[:vmname] = vmname
244
-
245
- if get_config(:bootstrap) && get_config(:distro) && !@@chef_config_dir
246
- fatal_exit("Can't find .chef for bootstrap files. chdir to a location with a .chef directory and try again")
247
- end
248
-
249
- vim = get_vim_connection
250
- vdm = vim.serviceContent.virtualDiskManager
251
-
252
- dc = get_datacenter
253
-
254
- src_folder = find_folder(get_config(:folder)) || dc.vmFolder
255
-
256
- abort "--template or knife[:source_vm] must be specified" unless config[:source_vm]
257
-
258
- src_vm = find_in_folder(src_folder, RbVmomi::VIM::VirtualMachine, config[:source_vm]) or
259
- abort "VM/Template not found"
260
-
261
- if get_config(:linked_clone)
262
- create_delta_disk(src_vm)
263
- end
264
-
265
- clone_spec = generate_clone_spec(src_vm.config)
266
-
267
- cust_folder = config[:dest_folder] || get_config(:folder)
268
-
269
- dest_folder = cust_folder.nil? ? src_vm.vmFolder : find_folder(cust_folder)
270
-
271
- task = src_vm.CloneVM_Task(:folder => dest_folder, :name => vmname, :spec => clone_spec)
272
- puts "Cloning template #{config[:source_vm]} to new VM #{vmname}"
273
- task.wait_for_completion
274
- puts "Finished creating virtual machine #{vmname}"
275
-
276
- if customization_plugin && customization_plugin.respond_to?(:reconfig_vm)
277
- target_vm = find_in_folder(dest_folder, RbVmomi::VIM::VirtualMachine, vmname) or abort "VM could not be found in #{dest_folder}"
278
- customization_plugin.reconfig_vm(target_vm)
279
- end
280
-
281
- if !get_config(:mark_as_template)
282
- if get_config(:power) || get_config(:bootstrap)
283
- vm = find_in_folder(dest_folder, RbVmomi::VIM::VirtualMachine, vmname) or
284
- fatal_exit("VM #{vmname} not found")
285
- vm.PowerOnVM_Task.wait_for_completion
286
- puts "Powered on virtual machine #{vmname}"
287
- end
288
-
289
-
290
- if get_config(:bootstrap)
291
- sleep 2 until vm.guest.ipAddress
292
- config[:fqdn] = vm.guest.ipAddress unless config[:fqdn]
293
- print "Waiting for sshd..."
294
- print "." until tcp_test_ssh(config[:fqdn])
295
- puts "done"
296
-
297
- bootstrap_for_node.run
298
- end
299
- end
300
- end
301
-
302
- def create_delta_disk(src_vm)
303
- disks = src_vm.config.hardware.device.grep(RbVmomi::VIM::VirtualDisk)
304
- disks.select { |disk| disk.backing.parent == nil }.each do |disk|
305
- spec = {
306
- :deviceChange => [
307
- {
308
- :operation => :remove,
309
- :device => disk
310
- },
311
- {
312
- :operation => :add,
313
- :fileOperation => :create,
314
- :device => disk.dup.tap { |new_disk|
315
- new_disk.backing = new_disk.backing.dup
316
- new_disk.backing.fileName = "[#{disk.backing.datastore.name}]"
317
- new_disk.backing.parent = disk.backing
318
- },
319
- }
320
- ]
321
- }
322
- src_vm.ReconfigVM_Task(:spec => spec).wait_for_completion
323
- end
324
- end
325
-
326
- # Builds a CloneSpec
327
- def generate_clone_spec (src_config)
328
- rspec = nil
329
- if get_config(:resource_pool)
330
- rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => find_pool(get_config(:resource_pool)))
331
- else
332
- dc = get_datacenter
333
- hosts = find_all_in_folder(dc.hostFolder, RbVmomi::VIM::ComputeResource)
334
- rp = hosts.first.resourcePool
335
- rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => rp)
336
- end
337
-
338
- if get_config(:linked_clone)
339
- rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:diskMoveType => :moveChildMostDiskBacking)
340
- end
341
-
342
- if get_config(:datastore) && get_config(:datastorecluster)
343
- abort "Please select either datastore or datastorecluster"
344
- end
345
-
346
- if get_config(:datastore)
347
- rspec.datastore = find_datastore(get_config(:datastore))
348
- end
349
-
350
- if get_config(:datastorecluster)
351
- dsc = find_datastorecluster(get_config(:datastorecluster))
352
-
353
- dsc.childEntity.each do |store|
354
- if (rspec.datastore == nil or rspec.datastore.summary[:freeSpace] < store.summary[:freeSpace])
355
- rspec.datastore = store
356
- end
357
- end
358
- end
359
-
360
- if get_config(:thin_provision)
361
- rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:transform => :sparse, :pool => find_pool(get_config(:resource_pool)))
362
- end
363
-
364
- is_template = !get_config(:mark_as_template).nil?
365
- clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(:location => rspec, :powerOn => false,:template => is_template)
366
-
367
- clone_spec.config = RbVmomi::VIM.VirtualMachineConfigSpec(:deviceChange => Array.new)
368
-
369
- if get_config(:annotation)
370
- clone_spec.config.annotation = get_config(:annotation)
371
- end
372
-
373
- if get_config(:customization_cpucount)
374
- clone_spec.config.numCPUs = get_config(:customization_cpucount)
375
- end
376
-
377
- if get_config(:customization_memory)
378
- clone_spec.config.memoryMB = Integer(get_config(:customization_memory)) * 1024
379
- end
380
-
381
- if get_config(:customization_vlan)
382
- vlan_list = get_config(:customization_vlan).split(',')
383
- networks = vlan_list.map { |vlan| find_network(vlan) }
384
-
385
- cards = src_config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard)
386
-
387
- networks.each_with_index do |network, index|
388
- card = cards[index] or abort "Can't find source network card to customize for vlan #{vlan_list[index]}"
389
- begin
390
- switch_port = RbVmomi::VIM.DistributedVirtualSwitchPortConnection(:switchUuid => network.config.distributedVirtualSwitch.uuid, :portgroupKey => network.key)
391
- card.backing.port = switch_port
392
- rescue
393
- # not connected to a distibuted switch?
394
- card.backing = RbVmomi::VIM::VirtualEthernetCardNetworkBackingInfo(:network => network, :deviceName => network.name)
395
- end
396
- dev_spec = RbVmomi::VIM.VirtualDeviceConfigSpec(:device => card, :operation => "edit")
397
- clone_spec.config.deviceChange.push dev_spec
398
- end
399
- end
400
-
401
- if get_config(:customization_spec)
402
- csi = find_customization(get_config(:customization_spec)) or
403
- fatal_exit("failed to find customization specification named #{get_config(:customization_spec)}")
404
-
405
- cust_spec = csi.spec
406
- else
407
- global_ipset = RbVmomi::VIM.CustomizationGlobalIPSettings
408
- cust_spec = RbVmomi::VIM.CustomizationSpec(:globalIPSettings => global_ipset)
409
- end
410
-
411
- if get_config(:customization_dns_ips)
412
- cust_spec.globalIPSettings.dnsServerList = get_config(:customization_dns_ips).split(',')
413
- end
414
-
415
- if get_config(:customization_dns_suffixes)
416
- cust_spec.globalIPSettings.dnsSuffixList = get_config(:customization_dns_suffixes).split(',')
417
- end
418
-
419
- if config[:customization_ips]
420
- if get_config(:customization_gw)
421
- cust_spec.nicSettingMap = config[:customization_ips].split(',').map { |i| generate_adapter_map(i, get_config(:customization_gw)) }
422
- else
423
- cust_spec.nicSettingMap = config[:customization_ips].split(',').map { |i| generate_adapter_map(i) }
424
- end
425
- end
426
-
427
- unless get_config(:disable_customization)
428
- use_ident = !config[:customization_hostname].nil? || !get_config(:customization_domain).nil? || cust_spec.identity.nil?
429
-
430
- if use_ident
431
- hostname = if config[:customization_hostname]
432
- config[:customization_hostname]
433
- else
434
- config[:vmname]
435
- end
436
-
437
- if src_config.guestId.downcase.include?("windows")
438
- if cust_spec.identity.nil?
439
- fatal_exit("Please provide Windows Guest Customization")
440
- else
441
- cust_spec.identity.userData.computerName = RbVmomi::VIM.CustomizationFixedName(:name => hostname)
442
- end
443
- else
444
- ident = RbVmomi::VIM.CustomizationLinuxPrep
445
- ident.hostName = RbVmomi::VIM.CustomizationFixedName(:name => hostname)
446
- if get_config(:customization_domain)
447
- ident.domain = get_config(:customization_domain)
448
- else
449
- ident.domain = ''
450
- end
451
- cust_spec.identity = ident
452
- end
453
- end
454
-
455
- clone_spec.customization = cust_spec
456
-
457
- if customization_plugin && customization_plugin.respond_to?(:customize_clone_spec)
458
- clone_spec = customization_plugin.customize_clone_spec(src_config, clone_spec)
459
- end
460
- end
461
- clone_spec
462
- end
463
-
464
- # Loads the customization plugin if one was specified
465
- # @return [KnifeVspherePlugin] the loaded and initialized plugin or nil
466
- def customization_plugin
467
- if @customization_plugin.nil?
468
- if cplugin_path = get_config(:customization_plugin)
469
- if File.exists? cplugin_path
470
- require cplugin_path
471
- else
472
- abort "Customization plugin could not be found at #{cplugin_path}"
473
- end
474
-
475
- if Object.const_defined? 'KnifeVspherePlugin'
476
- @customization_plugin = Object.const_get('KnifeVspherePlugin').new
477
- if cplugin_data = get_config(:customization_plugin_data)
478
- if @customization_plugin.respond_to?(:data=)
479
- @customization_plugin.data = cplugin_data
480
- else
481
- abort "Customization plugin has no :data= accessor to receive the --cplugin-data argument. Define both or neither."
482
- end
483
- end
484
- else
485
- abort "KnifeVspherePlugin class is not defined in #{cplugin_path}"
486
- end
487
- end
488
- end
489
-
490
- @customization_plugin
491
- end
492
-
493
- # Retrieves a CustomizationSpecItem that matches the supplied name
494
- # @param vim [Connection] VI Connection to use
495
- # @param name [String] name of customization
496
- # @return [RbVmomi::VIM::CustomizationSpecItem]
497
- def find_customization(name)
498
- csm = config[:vim].serviceContent.customizationSpecManager
499
- csm.GetCustomizationSpec(:name => name)
500
- end
501
-
502
- # Generates a CustomizationAdapterMapping (currently only single IPv4 address) object
503
- # @param ip [String] Any static IP address to use, or "dhcp" for DHCP
504
- # @param gw [String] If static, the gateway for the interface, otherwise network address + 1 will be used
505
- # @return [RbVmomi::VIM::CustomizationIPSettings]
506
- def generate_adapter_map (ip=nil, gw=nil, dns1=nil, dns2=nil, domain=nil)
507
-
508
- settings = RbVmomi::VIM.CustomizationIPSettings
509
-
510
- if ip.nil? || ip.downcase == "dhcp"
511
- settings.ip = RbVmomi::VIM::CustomizationDhcpIpGenerator.new
512
- else
513
- cidr_ip = NetAddr::CIDR.create(ip)
514
- settings.ip = RbVmomi::VIM::CustomizationFixedIp(:ipAddress => cidr_ip.ip)
515
- settings.subnetMask = cidr_ip.netmask_ext
516
-
517
- # TODO - want to confirm gw/ip are in same subnet?
518
- # Only set gateway on first IP.
519
- if config[:customization_ips].split(',').first == ip
520
- if gw.nil?
521
- settings.gateway = [cidr_ip.network(:Objectify => true).next_ip]
522
- else
523
- gw_cidr = NetAddr::CIDR.create(gw)
524
- settings.gateway = [gw_cidr.ip]
525
- end
526
- end
527
- end
528
-
529
- adapter_map = RbVmomi::VIM.CustomizationAdapterMapping
530
- adapter_map.adapter = settings
531
- adapter_map
532
- end
533
-
534
- def bootstrap_for_node()
535
- Chef::Knife::Bootstrap.load_deps
536
- bootstrap = Chef::Knife::Bootstrap.new
537
- bootstrap.name_args = [config[:fqdn]]
538
- bootstrap.config[:run_list] = get_config(:run_list).split(/[\s,]+/)
539
- bootstrap.config[:secret_file] = get_config(:secret_file)
540
- bootstrap.config[:hint] = get_config(:hint)
541
- bootstrap.config[:ssh_user] = get_config(:ssh_user)
542
- bootstrap.config[:ssh_password] = get_config(:ssh_password)
543
- bootstrap.config[:ssh_port] = get_config(:ssh_port)
544
- bootstrap.config[:identity_file] = get_config(:identity_file)
545
- bootstrap.config[:chef_node_name] = get_config(:chef_node_name)
546
- bootstrap.config[:prerelease] = get_config(:prerelease)
547
- bootstrap.config[:bootstrap_version] = get_config(:bootstrap_version)
548
- bootstrap.config[:distro] = get_config(:distro)
549
- bootstrap.config[:use_sudo] = true unless get_config(:ssh_user) == 'root'
550
- bootstrap.config[:template_file] = get_config(:template_file)
551
- bootstrap.config[:environment] = get_config(:environment)
552
- bootstrap.config[:first_boot_attributes] = get_config(:first_boot_attributes)
553
- bootstrap.config[:log_level] = get_config(:log_level)
554
- # may be needed for vpc_mode
555
- bootstrap.config[:no_host_key_verify] = get_config(:no_host_key_verify)
556
- bootstrap
557
- end
558
-
559
- def tcp_test_ssh(hostname)
560
- tcp_socket = TCPSocket.new(hostname, get_config(:ssh_port))
561
- readable = IO.select([tcp_socket], nil, nil, 5)
562
- if readable
563
- Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}")
564
- true
565
- else
566
- false
567
- end
568
- rescue Errno::ETIMEDOUT
569
- false
570
- rescue Errno::EPERM
571
- false
572
- rescue Errno::ECONNREFUSED
573
- sleep 2
574
- false
575
- rescue Errno::EHOSTUNREACH, Errno::ENETUNREACH
576
- sleep 2
577
- false
578
- ensure
579
- tcp_socket && tcp_socket.close
580
- end
581
- end
1
+ #
2
+ # Author:: Ezra Pagel (<ezra@cpan.org>)
3
+ # Contributor:: Jesse Campbell (<hikeit@gmail.com>)
4
+ # Contributor:: Bethany Erskine (<bethany@paperlesspost.com>)
5
+ # Contributor:: Adrian Stanila (https://github.com/sacx)
6
+ # License:: Apache License, Version 2.0
7
+ #
8
+
9
+ require 'chef/knife'
10
+ require 'chef/knife/base_vsphere_command'
11
+ require 'rbvmomi'
12
+ require 'netaddr'
13
+ require 'securerandom'
14
+ require 'chef/knife/winrm_base'
15
+
16
+ # Clone an existing template into a new VM, optionally applying a customization specification.
17
+ # usage:
18
+ # knife vsphere vm clone NewNode UbuntuTemplate --cspec StaticSpec \
19
+ # --cips 192.168.0.99/24,192.168.1.99/24 \
20
+ # --chostname NODENAME --cdomain NODEDOMAIN
21
+ class Chef::Knife::VsphereVmClone < Chef::Knife::BaseVsphereCommand
22
+ banner 'knife vsphere vm clone VMNAME (options)'
23
+
24
+ include Chef::Knife::WinrmBase
25
+ include CustomizationHelper
26
+ deps do
27
+ require 'chef/json_compat'
28
+ require 'chef/knife/bootstrap'
29
+ Chef::Knife::Bootstrap.load_deps
30
+ end
31
+
32
+ common_options
33
+
34
+ option :dest_folder,
35
+ long: '--dest-folder FOLDER',
36
+ description: 'The folder into which to put the cloned VM'
37
+
38
+ option :datastore,
39
+ long: '--datastore STORE',
40
+ description: 'The datastore into which to put the cloned VM'
41
+
42
+ option :datastorecluster,
43
+ long: '--datastorecluster STORE',
44
+ description: 'The datastorecluster into which to put the cloned VM'
45
+
46
+ option :resource_pool,
47
+ long: '--resource-pool POOL',
48
+ description: 'The resource pool or cluster into which to put the cloned VM'
49
+
50
+ option :source_vm,
51
+ long: '--template TEMPLATE',
52
+ description: 'The source VM / Template to clone from'
53
+
54
+ option :linked_clone,
55
+ long: '--linked-clone',
56
+ description: 'Indicates whether to use linked clones.',
57
+ boolean: false
58
+
59
+ option :thin_provision,
60
+ long: '--thin-provision',
61
+ description: 'Indicates whether disk should be thin provisioned.',
62
+ boolean: true
63
+
64
+ option :annotation,
65
+ long: '--annotation TEXT',
66
+ description: 'Add TEXT in Notes field from annotation'
67
+
68
+ option :customization_spec,
69
+ long: '--cspec CUST_SPEC',
70
+ description: 'The name of any customization specification to apply'
71
+
72
+ option :customization_plugin,
73
+ long: '--cplugin CUST_PLUGIN_PATH',
74
+ description: 'Path to plugin that implements KnifeVspherePlugin.customize_clone_spec and/or KnifeVspherePlugin.reconfig_vm'
75
+
76
+ option :customization_plugin_data,
77
+ long: '--cplugin-data CUST_PLUGIN_DATA',
78
+ description: 'String of data to pass to the plugin. Use any format you wish.'
79
+
80
+ option :customization_vlan,
81
+ long: '--cvlan CUST_VLANS',
82
+ description: 'Comma-delimited list of VLAN names for network adapters to join'
83
+
84
+ option :customization_ips,
85
+ long: '--cips CUST_IPS',
86
+ description: 'Comma-delimited list of CIDR IPs for customization'
87
+
88
+ option :customization_dns_ips,
89
+ long: '--cdnsips CUST_DNS_IPS',
90
+ description: 'Comma-delimited list of DNS IP addresses'
91
+
92
+ option :customization_dns_suffixes,
93
+ long: '--cdnssuffix CUST_DNS_SUFFIXES',
94
+ description: 'Comma-delimited list of DNS search suffixes'
95
+
96
+ option :customization_gw,
97
+ long: '--cgw CUST_GW',
98
+ description: 'CIDR IP of gateway for customization'
99
+
100
+ option :customization_hostname,
101
+ long: '--chostname CUST_HOSTNAME',
102
+ description: 'Unqualified hostname for customization'
103
+
104
+ option :customization_domain,
105
+ long: '--cdomain CUST_DOMAIN',
106
+ description: 'Domain name for customization'
107
+
108
+ option :customization_tz,
109
+ long: '--ctz CUST_TIMEZONE',
110
+ description: "Timezone invalid 'Area/Location' format"
111
+
112
+ option :customization_cpucount,
113
+ long: '--ccpu CUST_CPU_COUNT',
114
+ description: 'Number of CPUs'
115
+
116
+ option :customization_memory,
117
+ long: '--cram CUST_MEMORY_GB',
118
+ description: 'Gigabytes of RAM'
119
+
120
+ option :power,
121
+ long: '--start',
122
+ description: 'Indicates whether to start the VM after a successful clone',
123
+ boolean: false
124
+
125
+ option :bootstrap,
126
+ long: '--bootstrap',
127
+ description: 'Indicates whether to bootstrap the VM',
128
+ boolean: false
129
+
130
+ option :environment,
131
+ long: '--environment ENVIRONMENT',
132
+ description: 'Environment to add the node to for bootstrapping'
133
+
134
+ option :fqdn,
135
+ long: '--fqdn SERVER_FQDN',
136
+ description: 'Fully qualified hostname for bootstrapping'
137
+
138
+ option :bootstrap_protocol,
139
+ long: '--bootstrap-protocol protocol',
140
+ description: 'Protocol to bootstrap windows servers. options: winrm/ssh',
141
+ proc: proc { |key| Chef::Config[:knife][:bootstrap_protocol] = key },
142
+ default: nil
143
+
144
+ option :ssh_user,
145
+ short: '-x USERNAME',
146
+ long: '--ssh-user USERNAME',
147
+ description: 'The ssh username',
148
+ default: 'root'
149
+
150
+ option :ssh_password,
151
+ short: '-P PASSWORD',
152
+ long: '--ssh-password PASSWORD',
153
+ description: 'The ssh password'
154
+
155
+ option :ssh_port,
156
+ short: '-p PORT',
157
+ long: '--ssh-port PORT',
158
+ description: 'The ssh port',
159
+ default: '22'
160
+
161
+ option :identity_file,
162
+ short: '-i IDENTITY_FILE',
163
+ long: '--identity-file IDENTITY_FILE',
164
+ description: 'The SSH identity file used for authentication'
165
+
166
+ option :chef_node_name,
167
+ short: '-N NAME',
168
+ long: '--node-name NAME',
169
+ description: 'The Chef node name for your new node'
170
+
171
+ option :prerelease,
172
+ long: '--prerelease',
173
+ description: 'Install the pre-release chef gems',
174
+ boolean: false
175
+
176
+ option :bootstrap_version,
177
+ long: '--bootstrap-version VERSION',
178
+ description: 'The version of Chef to install',
179
+ proc: proc { |v| Chef::Config[:knife][:bootstrap_version] = v }
180
+
181
+ option :bootstrap_proxy,
182
+ long: '--bootstrap-proxy PROXY_URL',
183
+ description: 'The proxy server for the node being bootstrapped',
184
+ proc: proc { |p| Chef::Config[:knife][:bootstrap_proxy] = p }
185
+
186
+ option :bootstrap_vault_file,
187
+ long: '--bootstrap-vault-file VAULT_FILE',
188
+ description: 'A JSON file with a list of vault(s) and item(s) to be updated'
189
+
190
+ option :bootstrap_vault_json,
191
+ long: '--bootstrap-vault-json VAULT_JSON',
192
+ description: 'A JSON string with the vault(s) and item(s) to be updated'
193
+
194
+ option :bootstrap_vault_item,
195
+ long: '--bootstrap-vault-item VAULT_ITEM',
196
+ description: 'A single vault and item to update as "vault:item"',
197
+ proc: proc { |i|
198
+ (vault, item) = i.split(/:/)
199
+ Chef::Config[:knife][:bootstrap_vault_item] ||= {}
200
+ Chef::Config[:knife][:bootstrap_vault_item][vault] ||= []
201
+ Chef::Config[:knife][:bootstrap_vault_item][vault].push(item)
202
+ Chef::Config[:knife][:bootstrap_vault_item]
203
+ }
204
+
205
+ option :distro,
206
+ short: '-d DISTRO',
207
+ long: '--distro DISTRO',
208
+ description: 'Bootstrap a distro using a template; default is "chef-full"',
209
+ proc: proc { |d| Chef::Config[:knife][:distro] = d },
210
+ default: 'chef-full'
211
+
212
+ option :template_file,
213
+ long: '--template-file TEMPLATE',
214
+ description: 'Full path to location of template to use'
215
+
216
+ option :run_list,
217
+ short: '-r RUN_LIST',
218
+ long: '--run-list RUN_LIST',
219
+ description: 'Comma separated list of roles/recipes to apply',
220
+ proc: -> (o) { o.split(/[\s,]+/) },
221
+ default: []
222
+
223
+ option :secret_file,
224
+ long: '--secret-file SECRET_FILE',
225
+ description: 'A file containing the secret key to use to encrypt data bag item values',
226
+ proc: ->(secret_file) { Chef::Config[:knife][:secret_file] = secret_file }
227
+
228
+ # rubocop:disable Style/Blocks
229
+ option :hint,
230
+ long: '--hint HINT_NAME[=HINT_FILE]',
231
+ description: 'Specify Ohai Hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.',
232
+ proc: proc { |h|
233
+ Chef::Config[:knife][:hints] ||= {}
234
+ name, path = h.split('=')
235
+ Chef::Config[:knife][:hints][name] = path ? JSON.parse(::File.read(path)) : {}
236
+ },
237
+ default: ''
238
+ # rubocop:enable Style/Blocks
239
+
240
+ option :no_host_key_verify,
241
+ long: '--no-host-key-verify',
242
+ description: 'Disable host key verification',
243
+ boolean: true
244
+
245
+ option :first_boot_attributes,
246
+ short: '-j JSON_ATTRIBS',
247
+ long: '--json-attributes',
248
+ description: 'A JSON string to be added to the first run of chef-client',
249
+ proc: ->(o) { JSON.parse(o) },
250
+ default: {}
251
+
252
+ option :disable_customization,
253
+ long: '--disable-customization',
254
+ description: 'Disable default customization',
255
+ boolean: true,
256
+ default: false
257
+
258
+ option :log_level,
259
+ short: '-l LEVEL',
260
+ long: '--log_level',
261
+ description: 'Set the log level (debug, info, warn, error, fatal) for chef-client',
262
+ proc: ->(l) { l.to_sym }
263
+
264
+ option :mark_as_template,
265
+ long: '--mark_as_template',
266
+ description: 'Indicates whether to mark the new vm as a template',
267
+ boolean: false
268
+
269
+ option :random_vmname,
270
+ long: '--random-vmname',
271
+ description: 'Creates a random VMNAME starts with vm-XXXXXXXX',
272
+ boolean: false
273
+
274
+ option :random_vmname_prefix,
275
+ long: '--random-vmname-prefix PREFIX',
276
+ description: 'Change the VMNAME prefix',
277
+ default: 'vm-'
278
+
279
+ option :sysprep_timeout,
280
+ long: '--sysprep_timeout TIMEOUT',
281
+ description: 'Wait TIMEOUT seconds for sysprep event before continuing with bootstrap',
282
+ default: 600
283
+
284
+ def run
285
+ $stdout.sync = true
286
+
287
+ unless using_supplied_hostname? ^ using_random_hostname?
288
+ show_usage
289
+ fatal_exit('You must specify a virtual machine name OR use --random-vmname')
290
+ end
291
+
292
+ config[:chef_node_name] = vmname unless get_config(:chef_node_name)
293
+ config[:vmname] = vmname
294
+
295
+ vim = vim_connection
296
+ vim.serviceContent.virtualDiskManager
297
+
298
+ dc = datacenter
299
+
300
+ src_folder = find_folder(get_config(:folder)) || dc.vmFolder
301
+
302
+ abort '--template or knife[:source_vm] must be specified' unless config[:source_vm]
303
+
304
+ src_vm = find_in_folder(src_folder, RbVmomi::VIM::VirtualMachine, config[:source_vm]) ||
305
+ abort('VM/Template not found')
306
+
307
+ create_delta_disk(src_vm) if get_config(:linked_clone)
308
+
309
+ clone_spec = generate_clone_spec(src_vm.config)
310
+
311
+ cust_folder = config[:dest_folder] || get_config(:folder)
312
+
313
+ dest_folder = cust_folder.nil? ? src_vm.vmFolder : find_folder(cust_folder)
314
+
315
+ task = src_vm.CloneVM_Task(folder: dest_folder, name: vmname, spec: clone_spec)
316
+ puts "Cloning template #{config[:source_vm]} to new VM #{vmname}"
317
+ task.wait_for_completion
318
+ puts "Finished creating virtual machine #{vmname}"
319
+
320
+ if customization_plugin && customization_plugin.respond_to?(:reconfig_vm)
321
+ target_vm = find_in_folder(dest_folder, RbVmomi::VIM::VirtualMachine, vmname) || abort("VM could not be found in #{dest_folder}")
322
+ customization_plugin.reconfig_vm(target_vm)
323
+ end
324
+
325
+ return if get_config(:mark_as_template)
326
+ if get_config(:power) || get_config(:bootstrap)
327
+ vm = find_in_folder(dest_folder, RbVmomi::VIM::VirtualMachine, vmname) ||
328
+ fatal_exit("VM #{vmname} not found")
329
+ vm.PowerOnVM_Task.wait_for_completion
330
+ puts "Powered on virtual machine #{vmname}"
331
+ end
332
+
333
+ return unless get_config(:bootstrap)
334
+ sleep 2 until vm.guest.ipAddress
335
+
336
+ connect_host = config[:fqdn] = config[:fqdn] ? get_config(:fqdn) : vm.guest.ipAddress
337
+ Chef::Log.debug("Connect Host for Bootstrap: #{connect_host}")
338
+ connect_port = get_config(:ssh_port)
339
+ protocol = get_config(:bootstrap_protocol)
340
+ if windows?(src_vm.config)
341
+ protocol ||= 'winrm'
342
+ # Set distro to windows-chef-client-msi
343
+ config[:distro] = 'windows-chef-client-msi' if config[:distro].nil? || config[:distro] == 'chef-full'
344
+ unless config[:disable_customization]
345
+ # Wait for customization to complete
346
+ # TODO: Figure out how to find the customization complete event from the vsphere logs. The
347
+ # customization can take up to 10 minutes to complete from what I have seen perhaps
348
+ # even longer. For now I am simply sleeping, but if anyone knows how to do this
349
+ # better fix it.
350
+ puts 'Waiting for customization to complete...'
351
+ CustomizationHelper.wait_for_sysprep(vm, vim, get_config(:sysprep_timeout), 10)
352
+ puts 'Customization Complete'
353
+ sleep 2 until vm.guest.ipAddress
354
+ connect_host = config[:fqdn] = config[:fqdn] ? get_config(:fqdn) : vm.guest.ipAddress
355
+ end
356
+ wait_for_access(connect_host, connect_port, protocol)
357
+ ssh_override_winrm
358
+ bootstrap_for_windows_node.run
359
+ else
360
+ protocol ||= 'ssh'
361
+ wait_for_access(connect_host, connect_port, protocol)
362
+ ssh_override_winrm
363
+ bootstrap_for_node.run
364
+ end
365
+ end
366
+
367
+ def wait_for_access(connect_host, connect_port, protocol)
368
+ if protocol == 'winrm'
369
+ load_winrm_deps
370
+ connect_port = get_config(:winrm_port)
371
+ print "\n#{ui.color('Waiting for winrm access to become available', :magenta)}"
372
+ print('.') until tcp_test_winrm(connect_host, connect_port) do
373
+ sleep 10
374
+ puts('done')
375
+ end
376
+ else
377
+ print "\n#{ui.color('Waiting for sshd access to become available', :magenta)}"
378
+ # If FreeSSHd, winsshd etc are available
379
+ print('.') until tcp_test_ssh(connect_host, connect_port) do
380
+ sleep 10
381
+ puts('done')
382
+ end
383
+ end
384
+ connect_port
385
+ end
386
+
387
+ def create_delta_disk(src_vm)
388
+ disks = src_vm.config.hardware.device.grep(RbVmomi::VIM::VirtualDisk)
389
+ disks.select { |disk| disk.backing.parent.nil? }.each do |disk|
390
+ spec = {
391
+ deviceChange: [
392
+ {
393
+ operation: :remove,
394
+ device: disk
395
+ },
396
+ {
397
+ operation: :add,
398
+ fileOperation: :create,
399
+ device: disk.dup.tap do |new_disk|
400
+ new_disk.backing = new_disk.backing.dup
401
+ new_disk.backing.fileName = "[#{disk.backing.datastore.name}]"
402
+ new_disk.backing.parent = disk.backing
403
+ end
404
+ }
405
+ ]
406
+ }
407
+ src_vm.ReconfigVM_Task(spec: spec).wait_for_completion
408
+ end
409
+ end
410
+
411
+ # Builds a CloneSpec
412
+ def generate_clone_spec(src_config)
413
+ rspec = nil
414
+ if get_config(:resource_pool)
415
+ rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(pool: find_pool(get_config(:resource_pool)))
416
+ else
417
+ dc = datacenter
418
+ hosts = traverse_folders_for_computeresources(dc.hostFolder)
419
+ fatal_exit('No ComputeResource found - Use --resource-pool to specify a resource pool or a cluster') if hosts.empty?
420
+ hosts.reject!(&:nil?)
421
+ hosts.reject! { |host| host.host.all? { |h| h.runtime.inMaintenanceMode } }
422
+ fatal_exit 'All hosts in maintenance mode!' if hosts.empty?
423
+
424
+ if get_config(:datastore)
425
+ hosts.reject! { |host| !host.datastore.include?(find_datastore(get_config(:datastore))) }
426
+ end
427
+
428
+ fatal_exit "No hosts have the requested Datastore available! #{get_config(:datastore)}" if hosts.empty?
429
+
430
+ if get_config(:datastorecluster)
431
+ hosts.reject! { |host| !host.datastore.include?(find_datastorecluster(get_config(:datastorecluster))) }
432
+ end
433
+
434
+ fatal_exit "No hosts have the requested DatastoreCluster available! #{get_config(:datastorecluster)}" if hosts.empty?
435
+
436
+ if get_config(:customization_vlan)
437
+ hosts.reject! { |host| !host.network.include?(find_network(get_config(:customization_vlan))) }
438
+ end
439
+
440
+ fatal_exit "No hosts have the requested Network available! #{get_config(:customization_vlan)}" if hosts.empty?
441
+
442
+ rp = hosts.first.resourcePool
443
+ rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(pool: rp)
444
+ end
445
+
446
+ if get_config(:linked_clone)
447
+ rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(diskMoveType: :moveChildMostDiskBacking)
448
+ end
449
+
450
+ if get_config(:datastore) && get_config(:datastorecluster)
451
+ abort 'Please select either datastore or datastorecluster'
452
+ end
453
+
454
+ if get_config(:datastore)
455
+ rspec.datastore = find_datastore(get_config(:datastore))
456
+ end
457
+
458
+ if get_config(:datastorecluster)
459
+ dsc = find_datastorecluster(get_config(:datastorecluster))
460
+
461
+ dsc.childEntity.each do |store|
462
+ if rspec.datastore.nil? || rspec.datastore.summary[:freeSpace] < store.summary[:freeSpace]
463
+ rspec.datastore = store
464
+ end
465
+ end
466
+ end
467
+
468
+ if get_config(:thin_provision)
469
+ rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(transform: :sparse, pool: find_pool(get_config(:resource_pool)))
470
+ end
471
+
472
+ is_template = !get_config(:mark_as_template).nil?
473
+ clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(location: rspec, powerOn: false, template: is_template)
474
+
475
+ clone_spec.config = RbVmomi::VIM.VirtualMachineConfigSpec(deviceChange: [])
476
+
477
+ if get_config(:annotation)
478
+ clone_spec.config.annotation = get_config(:annotation)
479
+ end
480
+
481
+ if get_config(:customization_cpucount)
482
+ clone_spec.config.numCPUs = get_config(:customization_cpucount)
483
+ end
484
+
485
+ if get_config(:customization_memory)
486
+ clone_spec.config.memoryMB = Integer(get_config(:customization_memory)) * 1024
487
+ end
488
+
489
+ if get_config(:customization_vlan)
490
+ vlan_list = get_config(:customization_vlan).split(',')
491
+ networks = vlan_list.map { |vlan| find_network(vlan) }
492
+
493
+ cards = src_config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard)
494
+
495
+ networks.each_with_index do |network, index|
496
+ card = cards[index] || abort("Can't find source network card to customize for vlan #{vlan_list[index]}")
497
+ begin
498
+ switch_port = RbVmomi::VIM.DistributedVirtualSwitchPortConnection(switchUuid: network.config.distributedVirtualSwitch.uuid, portgroupKey: network.key)
499
+ card.backing.port = switch_port
500
+ rescue
501
+ # not connected to a distibuted switch?
502
+ card.backing = RbVmomi::VIM::VirtualEthernetCardNetworkBackingInfo(network: network, deviceName: network.name)
503
+ end
504
+ dev_spec = RbVmomi::VIM.VirtualDeviceConfigSpec(device: card, operation: 'edit')
505
+ clone_spec.config.deviceChange.push dev_spec
506
+ end
507
+ end
508
+
509
+ if get_config(:customization_spec)
510
+ csi = find_customization(get_config(:customization_spec)) ||
511
+ fatal_exit("failed to find customization specification named #{get_config(:customization_spec)}")
512
+
513
+ cust_spec = csi.spec
514
+ else
515
+ global_ipset = RbVmomi::VIM.CustomizationGlobalIPSettings
516
+ cust_spec = RbVmomi::VIM.CustomizationSpec(globalIPSettings: global_ipset)
517
+ end
518
+
519
+ if get_config(:customization_dns_ips)
520
+ cust_spec.globalIPSettings.dnsServerList = get_config(:customization_dns_ips).split(',')
521
+ end
522
+
523
+ if get_config(:customization_dns_suffixes)
524
+ cust_spec.globalIPSettings.dnsSuffixList = get_config(:customization_dns_suffixes).split(',')
525
+ end
526
+
527
+ if config[:customization_ips]
528
+ if get_config(:customization_gw)
529
+ cust_spec.nicSettingMap = config[:customization_ips].split(',').map { |i| generate_adapter_map(i, get_config(:customization_gw)) }
530
+ else
531
+ cust_spec.nicSettingMap = config[:customization_ips].split(',').map { |i| generate_adapter_map(i) }
532
+ end
533
+ end
534
+
535
+ unless get_config(:disable_customization)
536
+ use_ident = !config[:customization_hostname].nil? || !get_config(:customization_domain).nil? || cust_spec.identity.nil?
537
+
538
+ if use_ident
539
+ hostname = if config[:customization_hostname]
540
+ config[:customization_hostname]
541
+ else
542
+ config[:vmname]
543
+ end
544
+ if windows?(src_config)
545
+ identification = RbVmomi::VIM.CustomizationIdentification(
546
+ joinWorkgroup: cust_spec.identity.identification.joinWorkgroup
547
+ )
548
+ license_file_print_data = RbVmomi::VIM.CustomizationLicenseFilePrintData(
549
+ autoMode: cust_spec.identity.licenseFilePrintData.autoMode
550
+ )
551
+
552
+ user_data = RbVmomi::VIM.CustomizationUserData(
553
+ fullName: cust_spec.identity.userData.fullName,
554
+ orgName: cust_spec.identity.userData.orgName,
555
+ productId: cust_spec.identity.userData.productId,
556
+ computerName: cust_spec.identity.userData.computerName
557
+ )
558
+ gui_unattended = RbVmomi::VIM.CustomizationGuiUnattended(
559
+ autoLogon: cust_spec.identity.guiUnattended.autoLogon,
560
+ autoLogonCount: cust_spec.identity.guiUnattended.autoLogonCount,
561
+ password: RbVmomi::VIM.CustomizationPassword(
562
+ plainText: cust_spec.identity.guiUnattended.password.plainText,
563
+ value: cust_spec.identity.guiUnattended.password.value
564
+ ),
565
+ timeZone: cust_spec.identity.guiUnattended.timeZone
566
+ )
567
+ runonce = RbVmomi::VIM.CustomizationGuiRunOnce(
568
+ commandList: ['cust_spec.identity.guiUnattended.commandList']
569
+ )
570
+ ident = RbVmomi::VIM.CustomizationSysprep
571
+ ident.guiRunOnce = runonce
572
+ ident.guiUnattended = gui_unattended
573
+ ident.identification = identification
574
+ ident.licenseFilePrintData = license_file_print_data
575
+ ident.userData = user_data
576
+ cust_spec.identity = ident
577
+ elsif linux?(src_config)
578
+ ident = RbVmomi::VIM.CustomizationLinuxPrep
579
+ ident.hostName = RbVmomi::VIM.CustomizationFixedName(name: hostname)
580
+
581
+ if get_config(:customization_domain)
582
+ ident.domain = get_config(:customization_domain)
583
+ else
584
+ ident.domain = ''
585
+ end
586
+ cust_spec.identity = ident
587
+ else
588
+ ui.error('Customization only supports Linux and Windows currently.')
589
+ exit 1
590
+ end
591
+ end
592
+ clone_spec.customization = cust_spec
593
+
594
+ if customization_plugin && customization_plugin.respond_to?(:customize_clone_spec)
595
+ clone_spec = customization_plugin.customize_clone_spec(src_config, clone_spec)
596
+ end
597
+ end
598
+ clone_spec
599
+ end
600
+
601
+ # Loads the customization plugin if one was specified
602
+ # @return [KnifeVspherePlugin] the loaded and initialized plugin or nil
603
+ def customization_plugin
604
+ if @customization_plugin.nil?
605
+ cplugin_path = get_config(:customization_plugin)
606
+ if cplugin_path
607
+ if File.exist? cplugin_path
608
+ require cplugin_path
609
+ else
610
+ abort "Customization plugin could not be found at #{cplugin_path}"
611
+ end
612
+
613
+ if Object.const_defined? 'KnifeVspherePlugin'
614
+ @customization_plugin = Object.const_get('KnifeVspherePlugin').new
615
+ cplugin_data = get_config(:customization_plugin_data)
616
+ if cplugin_data
617
+ if @customization_plugin.respond_to?(:data=)
618
+ @customization_plugin.data = cplugin_data
619
+ else
620
+ abort 'Customization plugin has no :data= accessor to receive the --cplugin-data argument. Define both or neither.'
621
+ end
622
+ end
623
+ else
624
+ abort "KnifeVspherePlugin class is not defined in #{cplugin_path}"
625
+ end
626
+ end
627
+ end
628
+
629
+ @customization_plugin
630
+ end
631
+
632
+ # Retrieves a CustomizationSpecItem that matches the supplied name
633
+ # @param vim [Connection] VI Connection to use
634
+ # @param name [String] name of customization
635
+ # @return [RbVmomi::VIM::CustomizationSpecItem]
636
+ def find_customization(name)
637
+ csm = config[:vim].serviceContent.customizationSpecManager
638
+ csm.GetCustomizationSpec(name: name)
639
+ end
640
+
641
+ # Generates a CustomizationAdapterMapping (currently only single IPv4 address) object
642
+ # @param ip [String] Any static IP address to use, or "dhcp" for DHCP
643
+ # @param gw [String] If static, the gateway for the interface, otherwise network address + 1 will be used
644
+ # @return [RbVmomi::VIM::CustomizationIPSettings]
645
+ def generate_adapter_map(ip = nil, gw = nil)
646
+ settings = RbVmomi::VIM.CustomizationIPSettings
647
+
648
+ if ip.nil? || ip.downcase == 'dhcp'
649
+ settings.ip = RbVmomi::VIM::CustomizationDhcpIpGenerator.new
650
+ else
651
+ cidr_ip = NetAddr::CIDR.create(ip)
652
+ settings.ip = RbVmomi::VIM::CustomizationFixedIp(ipAddress: cidr_ip.ip)
653
+ settings.subnetMask = cidr_ip.netmask_ext
654
+
655
+ # TODO: want to confirm gw/ip are in same subnet?
656
+ # Only set gateway on first IP.
657
+ if config[:customization_ips].split(',').first == ip
658
+ if gw.nil?
659
+ settings.gateway = [cidr_ip.network(Objectify: true).next_ip]
660
+ else
661
+ gw_cidr = NetAddr::CIDR.create(gw)
662
+ settings.gateway = [gw_cidr.ip]
663
+ end
664
+ end
665
+ end
666
+
667
+ adapter_map = RbVmomi::VIM.CustomizationAdapterMapping
668
+ adapter_map.adapter = settings
669
+ adapter_map
670
+ end
671
+
672
+ def bootstrap_common_params(bootstrap)
673
+ bootstrap.config[:run_list] = config[:run_list]
674
+ bootstrap.config[:bootstrap_version] = get_config(:bootstrap_version)
675
+ bootstrap.config[:distro] = get_config(:distro)
676
+ bootstrap.config[:template_file] = get_config(:template_file)
677
+ bootstrap.config[:environment] = get_config(:environment)
678
+ bootstrap.config[:prerelease] = get_config(:prerelease)
679
+ bootstrap.config[:first_boot_attributes] = get_config(:first_boot_attributes)
680
+ bootstrap.config[:hint] = get_config(:hint)
681
+ bootstrap.config[:chef_node_name] = get_config(:chef_node_name)
682
+ bootstrap.config[:bootstrap_vault_file] = get_config(:bootstrap_vault_file)
683
+ bootstrap.config[:bootstrap_vault_json] = get_config(:bootstrap_vault_json)
684
+ bootstrap.config[:bootstrap_vault_item] = get_config(:bootstrap_vault_item)
685
+ # may be needed for vpc mode
686
+ bootstrap.config[:no_host_key_verify] = get_config(:no_host_key_verify)
687
+ bootstrap
688
+ end
689
+
690
+ def bootstrap_for_windows_node
691
+ Chef::Knife::Bootstrap.load_deps
692
+ if get_config(:bootstrap_protocol) == 'winrm' || get_config(:bootstrap_protocol).nil?
693
+ bootstrap = Chef::Knife::BootstrapWindowsWinrm.new
694
+ bootstrap.name_args = [config[:fqdn]]
695
+ bootstrap.config[:winrm_user] = get_config(:winrm_user)
696
+ bootstrap.config[:winrm_password] = get_config(:winrm_password)
697
+ bootstrap.config[:winrm_transport] = get_config(:winrm_transport)
698
+ bootstrap.config[:winrm_port] = get_config(:winrm_port)
699
+ elsif get_config(:bootstrap_protocol) == 'ssh'
700
+ bootstrap = Chef::Knife::BootstrapWindowsSsh.new
701
+ bootstrap.config[:ssh_user] = get_config(:ssh_user)
702
+ bootstrap.config[:ssh_password] = get_config(:ssh_password)
703
+ bootstrap.config[:ssh_port] = get_config(:ssh_port)
704
+ else
705
+ ui.error('Unsupported Bootstrapping Protocol. Supports : winrm, ssh')
706
+ exit 1
707
+ end
708
+ bootstrap_common_params(bootstrap)
709
+ end
710
+
711
+ def bootstrap_for_node
712
+ Chef::Knife::Bootstrap.load_deps
713
+ bootstrap = Chef::Knife::Bootstrap.new
714
+ bootstrap.name_args = [config[:fqdn]]
715
+ bootstrap.config[:secret_file] = get_config(:secret_file)
716
+ bootstrap.config[:ssh_user] = get_config(:ssh_user)
717
+ bootstrap.config[:ssh_password] = get_config(:ssh_password)
718
+ bootstrap.config[:ssh_port] = get_config(:ssh_port)
719
+ bootstrap.config[:identity_file] = get_config(:identity_file)
720
+ bootstrap.config[:use_sudo] = true unless get_config(:ssh_user) == 'root'
721
+ bootstrap.config[:log_level] = get_config(:log_level)
722
+ bootstrap_common_params(bootstrap)
723
+ end
724
+
725
+ def ssh_override_winrm
726
+ # unchanged ssh_user and changed winrm_user, override ssh_user
727
+ if get_config(:ssh_user).eql?(options[:ssh_user][:default]) &&
728
+ !get_config(:winrm_user).eql?(options[:winrm_user][:default])
729
+ config[:ssh_user] = get_config(:winrm_user)
730
+ end
731
+
732
+ # unchanged ssh_port and changed winrm_port, override ssh_port
733
+ if get_config(:ssh_port).eql?(options[:ssh_port][:default]) &&
734
+ !get_config(:winrm_port).eql?(options[:winrm_port][:default])
735
+ config[:ssh_port] = get_config(:winrm_port)
736
+ end
737
+
738
+ # unset ssh_password and set winrm_password, override ssh_password
739
+ if get_config(:ssh_password).nil? &&
740
+ !get_config(:winrm_password).nil?
741
+ config[:ssh_password] = get_config(:winrm_password)
742
+ end
743
+
744
+ # unset identity_file and set kerberos_keytab_file, override identity_file
745
+ return unless get_config(:identity_file).nil? && !get_config(:kerberos_keytab_file).nil?
746
+
747
+ config[:identity_file] = get_config(:kerberos_keytab_file)
748
+ end
749
+
750
+ def tcp_test_ssh(hostname, ssh_port)
751
+ tcp_socket = TCPSocket.new(hostname, ssh_port)
752
+ readable = IO.select([tcp_socket], nil, nil, 5)
753
+ if readable
754
+ ssh_banner = tcp_socket.gets
755
+ if ssh_banner.nil? || ssh_banner.empty?
756
+ false
757
+ else
758
+ Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{ssh_banner}")
759
+ yield
760
+ true
761
+ end
762
+ else
763
+ false
764
+ end
765
+ rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, IOError
766
+ Chef::Log.debug("ssh failed to connect: #{hostname}")
767
+ sleep 2
768
+ false
769
+ rescue Errno::EPERM, Errno::ETIMEDOUT
770
+ Chef::Log.debug("ssh timed out: #{hostname}")
771
+ false
772
+ rescue Errno::ECONNRESET
773
+ Chef::Log.debug("ssh reset its connection: #{hostname}")
774
+ sleep 2
775
+ false
776
+ ensure
777
+ tcp_socket && tcp_socket.close
778
+ end
779
+
780
+ def tcp_test_winrm(hostname, port)
781
+ tcp_socket = TCPSocket.new(hostname, port)
782
+ yield
783
+ true
784
+ rescue SocketError
785
+ sleep 2
786
+ false
787
+ rescue Errno::ETIMEDOUT
788
+ false
789
+ rescue Errno::EPERM
790
+ false
791
+ rescue Errno::ECONNREFUSED
792
+ sleep 2
793
+ false
794
+ rescue Errno::EHOSTUNREACH
795
+ sleep 2
796
+ false
797
+ rescue Errno::ENETUNREACH
798
+ sleep 2
799
+ false
800
+ ensure
801
+ tcp_socket && tcp_socket.close
802
+ end
803
+
804
+ def load_winrm_deps
805
+ require 'winrm'
806
+ require 'em-winrm'
807
+ require 'chef/knife/winrm'
808
+ require 'chef/knife/bootstrap_windows_winrm'
809
+ require 'chef/knife/bootstrap_windows_ssh'
810
+ require 'chef/knife/core/windows_bootstrap_context'
811
+ end
812
+
813
+ private
814
+
815
+ def vmname
816
+ supplied_hostname || random_hostname
817
+ end
818
+
819
+ def using_random_hostname?
820
+ config[:random_vmname]
821
+ end
822
+
823
+ def using_supplied_hostname?
824
+ !supplied_hostname.nil?
825
+ end
826
+
827
+ def supplied_hostname
828
+ @name_args[0]
829
+ end
830
+
831
+ def random_hostname
832
+ @random_hostname ||= config[:random_vmname_prefix] + SecureRandom.hex(4)
833
+ end
834
+ end