knife-vsphere 1.0.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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