knife-vsphere 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,402 +1,420 @@
1
- #
2
- # Author:: Ezra Pagel (<ezra@cpan.org>)
3
- # Contributor:: Jesse Campbell (<hikeit@gmail.com>)
4
- # License:: Apache License, Version 2.0
5
- #
6
-
7
- require 'chef/knife'
8
- require 'chef/knife/BaseVsphereCommand'
9
- require 'rbvmomi'
10
- require 'netaddr'
11
-
12
- # Clone an existing template into a new VM, optionally applying a customization specification.
13
- #
14
- # usage:
15
- # knife vsphere vm clone NewNode UbuntuTemplate --cspec StaticSpec \
16
- # --cips 192.168.0.99/24,192.168.1.99/24 \
17
- # --chostname NODENAME --cdomain NODEDOMAIN
18
- class Chef::Knife::VsphereVmClone < Chef::Knife::BaseVsphereCommand
19
-
20
- banner "knife vsphere vm clone VMNAME (options)"
21
-
22
- get_common_options
23
-
24
- option :dest_folder,
25
- :long => "--dest-folder FOLDER",
26
- :description => "The folder into which to put the cloned VM"
27
-
28
- option :datastore,
29
- :long => "--datastore STORE",
30
- :description => "The datastore into which to put the cloned VM"
31
-
32
- option :resource_pool,
33
- :long => "--resource-pool POOL",
34
- :description => "The resource pool into which to put the cloned VM"
35
-
36
- option :source_vm,
37
- :long => "--template TEMPLATE",
38
- :description => "The source VM / Template to clone from",
39
- :required => true
40
-
41
- option :customization_spec,
42
- :long => "--cspec CUST_SPEC",
43
- :description => "The name of any customization specification to apply"
44
-
45
- option :customization_vlan,
46
- :long => "--cvlan CUST_VLAN",
47
- :description => "VLAN name for network adapter to join"
48
-
49
- option :customization_ips,
50
- :long => "--cips CUST_IPS",
51
- :description => "Comma-delimited list of CIDR IPs for customization"
52
-
53
- option :customization_dns_ips,
54
- :long => "--cdnsips CUST_DNS_IPS",
55
- :description => "Comma-delimited list of DNS IP addresses"
56
-
57
- option :customization_dns_suffixes,
58
- :long => "--cdnssuffix CUST_DNS_SUFFIXES",
59
- :description => "Comma-delimited list of DNS search suffixes"
60
-
61
- option :customization_gw,
62
- :long => "--cgw CUST_GW",
63
- :description => "CIDR IP of gateway for customization"
64
-
65
- option :customization_hostname,
66
- :long => "--chostname CUST_HOSTNAME",
67
- :description => "Unqualified hostname for customization"
68
-
69
- option :customization_domain,
70
- :long => "--cdomain CUST_DOMAIN",
71
- :description => "Domain name for customization"
72
-
73
- option :customization_tz,
74
- :long => "--ctz CUST_TIMEZONE",
75
- :description => "Timezone invalid 'Area/Location' format"
76
-
77
- option :customization_cpucount,
78
- :long => "--ccpu CUST_CPU_COUNT",
79
- :description => "Number of CPUs"
80
-
81
- option :customization_memory,
82
- :long => "--cram CUST_MEMORY_GB",
83
- :description => "Gigabytes of RAM"
84
-
85
- option :power,
86
- :long => "--start",
87
- :description => "Indicates whether to start the VM after a successful clone",
88
- :boolean => false
89
-
90
- option :bootstrap,
91
- :long => "--bootstrap",
92
- :description => "Indicates whether to bootstrap the VM",
93
- :boolean => false
94
-
95
- option :fqdn,
96
- :long => "--fqdn SERVER_FQDN",
97
- :description => "Fully qualified hostname for bootstrapping"
98
-
99
- option :ssh_user,
100
- :short => "-x USERNAME",
101
- :long => "--ssh-user USERNAME",
102
- :description => "The ssh username"
103
- $default[:ssh_user] = "root"
104
-
105
- option :ssh_password,
106
- :short => "-P PASSWORD",
107
- :long => "--ssh-password PASSWORD",
108
- :description => "The ssh password"
109
-
110
- option :ssh_port,
111
- :short => "-p PORT",
112
- :long => "--ssh-port PORT",
113
- :description => "The ssh port"
114
- $default[:ssh_port] = 22
115
-
116
- option :identity_file,
117
- :short => "-i IDENTITY_FILE",
118
- :long => "--identity-file IDENTITY_FILE",
119
- :description => "The SSH identity file used for authentication"
120
-
121
- option :chef_node_name,
122
- :short => "-N NAME",
123
- :long => "--node-name NAME",
124
- :description => "The Chef node name for your new node"
125
-
126
- option :prerelease,
127
- :long => "--prerelease",
128
- :description => "Install the pre-release chef gems",
129
- :boolean => false
130
-
131
- option :bootstrap_version,
132
- :long => "--bootstrap-version VERSION",
133
- :description => "The version of Chef to install",
134
- :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
135
-
136
- option :bootstrap_proxy,
137
- :long => "--bootstrap-proxy PROXY_URL",
138
- :description => "The proxy server for the node being bootstrapped",
139
- :proc => Proc.new { |p| Chef::Config[:knife][:bootstrap_proxy] = p }
140
-
141
- option :distro,
142
- :short => "-d DISTRO",
143
- :long => "--distro DISTRO",
144
- :description => "Bootstrap a distro using a template"
145
- $default[:distro] = "ubuntu10.04-gems"
146
-
147
- option :template_file,
148
- :long => "--template-file TEMPLATE",
149
- :description => "Full path to location of template to use"
150
-
151
- option :run_list,
152
- :short => "-r RUN_LIST",
153
- :long => "--run-list RUN_LIST",
154
- :description => "Comma separated list of roles/recipes to apply"
155
- $default[:run_list] = ''
156
-
157
- option :no_host_key_verify,
158
- :long => "--no-host-key-verify",
159
- :description => "Disable host key verification",
160
- :boolean => true
161
-
162
- def run
163
- $stdout.sync = true
164
-
165
- vmname = @name_args[0]
166
- if vmname.nil?
167
- show_usage
168
- fatal_exit("You must specify a virtual machine name")
169
- end
170
- config[:chef_node_name] = vmname unless config[:chef_node_name]
171
- config[:vmname] = vmname
172
-
173
- if get_config(:bootstrap) && get_config(:distro) && !@@chef_config_dir
174
- fatal_exit("Can't find .chef for bootstrap files. chdir to a location with a .chef directory and try again")
175
- end
176
-
177
- vim = get_vim_connection
178
-
179
- dcname = get_config(:vsphere_dc)
180
- dc = vim.serviceInstance.find_datacenter(dcname) or abort "datacenter not found"
181
-
182
- src_folder = find_folder(get_config(:folder)) || dc.vmFolder
183
-
184
- src_vm = find_in_folder(src_folder, RbVmomi::VIM::VirtualMachine, config[:source_vm]) or
185
- abort "VM/Template not found"
186
-
187
- clone_spec = generate_clone_spec(src_vm.config)
188
-
189
- cust_folder = config[:dest_folder] || get_config(:folder)
190
-
191
- dest_folder = cust_folder.nil? ? src_vm.vmFolder : find_folder(cust_folder)
192
-
193
- task = src_vm.CloneVM_Task(:folder => dest_folder, :name => vmname, :spec => clone_spec)
194
- puts "Cloning template #{config[:source_vm]} to new VM #{vmname}"
195
- task.wait_for_completion
196
- puts "Finished creating virtual machine #{vmname}"
197
-
198
- if get_config(:power) || get_config(:bootstrap)
199
- vm = find_in_folder(dest_folder, RbVmomi::VIM::VirtualMachine, vmname) or
200
- fatal_exit("VM #{vmname} not found")
201
- vm.PowerOnVM_Task.wait_for_completion
202
- puts "Powered on virtual machine #{vmname}"
203
- end
204
-
205
- if get_config(:bootstrap)
206
- sleep 2 until vm.guest.ipAddress
207
- config[:fqdn] = vm.guest.ipAddress unless config[:fqdn]
208
- print "Waiting for sshd..."
209
- print "." until tcp_test_ssh(config[:fqdn])
210
- puts "done"
211
-
212
- bootstrap_for_node.run
213
- end
214
- end
215
-
216
- # Builds a CloneSpec
217
- def generate_clone_spec (src_config)
218
-
219
- rspec = nil
220
- if get_config(:resource_pool)
221
- rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => find_pool(get_config(:resource_pool)))
222
- else
223
- dcname = get_config(:vsphere_dc)
224
- dc = config[:vim].serviceInstance.find_datacenter(dcname) or abort "datacenter not found"
225
- hosts = find_all_in_folder(dc.hostFolder, RbVmomi::VIM::ComputeResource)
226
- rp = hosts.first.resourcePool
227
- rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => rp)
228
- end
229
-
230
- if get_config(:datastore)
231
- rspec.datastore = find_datastore(get_config(:datastore))
232
- end
233
-
234
- clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(:location => rspec,
235
- :powerOn => false,
236
- :template => false)
237
-
238
- clone_spec.config = RbVmomi::VIM.VirtualMachineConfigSpec(:deviceChange => Array.new)
239
-
240
- if get_config(:customization_cpucount)
241
- clone_spec.config.numCPUs = get_config(:customization_cpucount)
242
- end
243
-
244
- if get_config(:customization_memory)
245
- clone_spec.config.memoryMB = Integer(get_config(:customization_memory)) * 1024
246
- end
247
-
248
- if get_config(:customization_vlan)
249
- network = find_network(get_config(:customization_vlan))
250
- card = src_config.hardware.device.find { |d| d.deviceInfo.label == "Network adapter 1" } or
251
- abort "Can't find source network card to customize"
252
- begin
253
- switch_port = RbVmomi::VIM.DistributedVirtualSwitchPortConnection(:switchUuid => network.config.distributedVirtualSwitch.uuid ,:portgroupKey => network.key)
254
- card.backing.port = switch_port
255
- rescue
256
- # not connected to a distibuted switch?
257
- card.backing.deviceName = network.name
258
- end
259
- dev_spec = RbVmomi::VIM.VirtualDeviceConfigSpec(:device => card, :operation => "edit")
260
- clone_spec.config.deviceChange.push dev_spec
261
- end
262
-
263
- if get_config(:customization_spec)
264
- csi = find_customization(get_config(:customization_spec)) or
265
- fatal_exit("failed to find customization specification named #{get_config(:customization_spec)}")
266
-
267
- if csi.info.type != "Linux"
268
- fatal_exit("Only Linux customization specifications are currently supported")
269
- end
270
- cust_spec = csi.spec
271
- else
272
- global_ipset = RbVmomi::VIM.CustomizationGlobalIPSettings
273
- cust_spec = RbVmomi::VIM.CustomizationSpec(:globalIPSettings => global_ipset)
274
- end
275
-
276
- if get_config(:customization_dns_ips)
277
- cust_spec.globalIPSettings.dnsServerList = get_config(:customization_dns_ips).split(',')
278
- end
279
-
280
- if get_config(:customization_dns_suffixes)
281
- cust_spec.globalIPSettings.dnsSuffixList = get_config(:customization_dns_suffixes).split(',')
282
- end
283
-
284
- if config[:customization_ips]
285
- if get_config(:customization_gw)
286
- cust_spec.nicSettingMap = config[:customization_ips].split(',').map { |i| generate_adapter_map(i,get_config(:customization_gw)) }
287
- else
288
- cust_spec.nicSettingMap = config[:customization_ips].split(',').map { |i| generate_adapter_map(i) }
289
- end
290
- end
291
-
292
- use_ident = !config[:customization_hostname].nil? || !get_config(:customization_domain).nil? || cust_spec.identity.nil?
293
-
294
- if use_ident
295
- # TODO - verify that we're deploying a linux spec, at least warn
296
- ident = RbVmomi::VIM.CustomizationLinuxPrep
297
-
298
- ident.hostName = RbVmomi::VIM.CustomizationFixedName
299
- if config[:customization_hostname]
300
- ident.hostName.name = config[:customization_hostname]
301
- else
302
- ident.hostName.name = config[:vmname]
303
- end
304
-
305
- if get_config(:customization_domain)
306
- ident.domain = get_config(:customization_domain)
307
- else
308
- ident.domain = ''
309
- end
310
-
311
- cust_spec.identity = ident
312
- end
313
-
314
- clone_spec.customization = cust_spec
315
- clone_spec
316
- end
317
-
318
- # Retrieves a CustomizationSpecItem that matches the supplied name
319
- # @param vim [Connection] VI Connection to use
320
- # @param name [String] name of customization
321
- # @return [RbVmomi::VIM::CustomizationSpecItem]
322
- def find_customization(name)
323
- csm = config[:vim].serviceContent.customizationSpecManager
324
- csm.GetCustomizationSpec(:name => name)
325
- end
326
-
327
- # Generates a CustomizationAdapterMapping (currently only single IPv4 address) object
328
- # @param ip [String] Any static IP address to use, otherwise DHCP
329
- # @param gw [String] If static, the gateway for the interface, otherwise network address + 1 will be used
330
- # @return [RbVmomi::VIM::CustomizationIPSettings]
331
- def generate_adapter_map (ip=nil, gw=nil, dns1=nil, dns2=nil, domain=nil)
332
-
333
- settings = RbVmomi::VIM.CustomizationIPSettings
334
-
335
- if ip.nil?
336
- settings.ip = RbVmomi::VIM::CustomizationDhcpIpGenerator
337
- else
338
- cidr_ip = NetAddr::CIDR.create(ip)
339
- settings.ip = RbVmomi::VIM::CustomizationFixedIp(:ipAddress => cidr_ip.ip)
340
- settings.subnetMask = cidr_ip.netmask_ext
341
-
342
- # TODO - want to confirm gw/ip are in same subnet?
343
- # Only set gateway on first IP.
344
- if config[:customization_ips].split(',').first == ip
345
- if gw.nil?
346
- settings.gateway = [cidr_ip.network(:Objectify => true).next_ip]
347
- else
348
- gw_cidr = NetAddr::CIDR.create(gw)
349
- settings.gateway = [gw_cidr.ip]
350
- end
351
- end
352
- end
353
-
354
- adapter_map = RbVmomi::VIM.CustomizationAdapterMapping
355
- adapter_map.adapter = settings
356
- adapter_map
357
- end
358
-
359
- def bootstrap_for_node()
360
- Chef::Knife::Bootstrap.load_deps
361
- bootstrap = Chef::Knife::Bootstrap.new
362
- bootstrap.name_args = [config[:fqdn]]
363
- bootstrap.config[:run_list] = get_config(:run_list).split(/[\s,]+/)
364
- bootstrap.config[:ssh_user] = get_config(:ssh_user)
365
- bootstrap.config[:ssh_password] = get_config(:ssh_password)
366
- bootstrap.config[:ssh_port] = get_config(:ssh_port)
367
- bootstrap.config[:identity_file] = get_config(:identity_file)
368
- bootstrap.config[:chef_node_name] = get_config(:chef_node_name)
369
- bootstrap.config[:prerelease] = get_config(:prerelease)
370
- bootstrap.config[:bootstrap_version] = get_config(:bootstrap_version)
371
- bootstrap.config[:distro] = get_config(:distro)
372
- bootstrap.config[:use_sudo] = true unless get_config(:ssh_user) == 'root'
373
- bootstrap.config[:template_file] = get_config(:template_file)
374
- bootstrap.config[:environment] = get_config(:environment)
375
- # may be needed for vpc_mode
376
- bootstrap.config[:no_host_key_verify] = get_config(:no_host_key_verify)
377
- bootstrap
378
- end
379
-
380
- def tcp_test_ssh(hostname)
381
- tcp_socket = TCPSocket.new(hostname, get_config(:ssh_port))
382
- readable = IO.select([tcp_socket], nil, nil, 5)
383
- if readable
384
- Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}")
385
- true
386
- else
387
- false
388
- end
389
- rescue Errno::ETIMEDOUT
390
- false
391
- rescue Errno::EPERM
392
- false
393
- rescue Errno::ECONNREFUSED
394
- sleep 2
395
- false
396
- rescue Errno::EHOSTUNREACH, Errno::ENETUNREACH
397
- sleep 2
398
- false
399
- ensure
400
- tcp_socket && tcp_socket.close
401
- end
402
- 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/BaseVsphereCommand'
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 :resource_pool,
34
+ :long => "--resource-pool POOL",
35
+ :description => "The resource pool into which to put the cloned VM"
36
+
37
+ option :source_vm,
38
+ :long => "--template TEMPLATE",
39
+ :description => "The source VM / Template to clone from",
40
+ :required => true
41
+
42
+ option :customization_spec,
43
+ :long => "--cspec CUST_SPEC",
44
+ :description => "The name of any customization specification to apply"
45
+
46
+ option :customization_vlan,
47
+ :long => "--cvlan CUST_VLAN",
48
+ :description => "VLAN name for network adapter to join"
49
+
50
+ option :customization_ips,
51
+ :long => "--cips CUST_IPS",
52
+ :description => "Comma-delimited list of CIDR IPs for customization"
53
+
54
+ option :customization_dns_ips,
55
+ :long => "--cdnsips CUST_DNS_IPS",
56
+ :description => "Comma-delimited list of DNS IP addresses"
57
+
58
+ option :customization_dns_suffixes,
59
+ :long => "--cdnssuffix CUST_DNS_SUFFIXES",
60
+ :description => "Comma-delimited list of DNS search suffixes"
61
+
62
+ option :customization_gw,
63
+ :long => "--cgw CUST_GW",
64
+ :description => "CIDR IP of gateway for customization"
65
+
66
+ option :customization_hostname,
67
+ :long => "--chostname CUST_HOSTNAME",
68
+ :description => "Unqualified hostname for customization"
69
+
70
+ option :customization_domain,
71
+ :long => "--cdomain CUST_DOMAIN",
72
+ :description => "Domain name for customization"
73
+
74
+ option :customization_tz,
75
+ :long => "--ctz CUST_TIMEZONE",
76
+ :description => "Timezone invalid 'Area/Location' format"
77
+
78
+ option :customization_cpucount,
79
+ :long => "--ccpu CUST_CPU_COUNT",
80
+ :description => "Number of CPUs"
81
+
82
+ option :customization_memory,
83
+ :long => "--cram CUST_MEMORY_GB",
84
+ :description => "Gigabytes of RAM"
85
+
86
+ option :power,
87
+ :long => "--start",
88
+ :description => "Indicates whether to start the VM after a successful clone",
89
+ :boolean => false
90
+
91
+ option :bootstrap,
92
+ :long => "--bootstrap",
93
+ :description => "Indicates whether to bootstrap the VM",
94
+ :boolean => false
95
+
96
+ option :fqdn,
97
+ :long => "--fqdn SERVER_FQDN",
98
+ :description => "Fully qualified hostname for bootstrapping"
99
+
100
+ option :ssh_user,
101
+ :short => "-x USERNAME",
102
+ :long => "--ssh-user USERNAME",
103
+ :description => "The ssh username"
104
+ $default[:ssh_user] = "root"
105
+
106
+ option :ssh_password,
107
+ :short => "-P PASSWORD",
108
+ :long => "--ssh-password PASSWORD",
109
+ :description => "The ssh password"
110
+
111
+ option :ssh_port,
112
+ :short => "-p PORT",
113
+ :long => "--ssh-port PORT",
114
+ :description => "The ssh port"
115
+ $default[:ssh_port] = 22
116
+
117
+ option :identity_file,
118
+ :short => "-i IDENTITY_FILE",
119
+ :long => "--identity-file IDENTITY_FILE",
120
+ :description => "The SSH identity file used for authentication"
121
+
122
+ option :chef_node_name,
123
+ :short => "-N NAME",
124
+ :long => "--node-name NAME",
125
+ :description => "The Chef node name for your new node"
126
+
127
+ option :prerelease,
128
+ :long => "--prerelease",
129
+ :description => "Install the pre-release chef gems",
130
+ :boolean => false
131
+
132
+ option :bootstrap_version,
133
+ :long => "--bootstrap-version VERSION",
134
+ :description => "The version of Chef to install",
135
+ :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
136
+
137
+ option :bootstrap_proxy,
138
+ :long => "--bootstrap-proxy PROXY_URL",
139
+ :description => "The proxy server for the node being bootstrapped",
140
+ :proc => Proc.new { |p| Chef::Config[:knife][:bootstrap_proxy] = p }
141
+
142
+ option :distro,
143
+ :short => "-d DISTRO",
144
+ :long => "--distro DISTRO",
145
+ :description => "Bootstrap a distro using a template"
146
+ $default[:distro] = "ubuntu10.04-gems"
147
+
148
+ option :template_file,
149
+ :long => "--template-file TEMPLATE",
150
+ :description => "Full path to location of template to use"
151
+
152
+ option :run_list,
153
+ :short => "-r RUN_LIST",
154
+ :long => "--run-list RUN_LIST",
155
+ :description => "Comma separated list of roles/recipes to apply"
156
+ $default[:run_list] = ''
157
+
158
+ option :no_host_key_verify,
159
+ :long => "--no-host-key-verify",
160
+ :description => "Disable host key verification",
161
+ :boolean => true
162
+
163
+ option :first_boot_attributes,
164
+ :short => "-j JSON_ATTRIBS",
165
+ :long => "--json-attributes",
166
+ :description => "A JSON string to be added to the first run of chef-client",
167
+ :proc => lambda { |o| JSON.parse(o) },
168
+ :default => {}
169
+
170
+ option :disable_customization,
171
+ :long => "--disable-customization",
172
+ :description => "Disable default customization",
173
+ :boolean => true,
174
+ :default => false
175
+
176
+ def run
177
+ $stdout.sync = true
178
+
179
+ vmname = @name_args[0]
180
+ if vmname.nil?
181
+ show_usage
182
+ fatal_exit("You must specify a virtual machine name")
183
+ end
184
+ config[:chef_node_name] = vmname unless config[:chef_node_name]
185
+ config[:vmname] = vmname
186
+
187
+ if get_config(:bootstrap) && get_config(:distro) && !@@chef_config_dir
188
+ fatal_exit("Can't find .chef for bootstrap files. chdir to a location with a .chef directory and try again")
189
+ end
190
+
191
+ vim = get_vim_connection
192
+
193
+ dcname = get_config(:vsphere_dc)
194
+ dc = vim.serviceInstance.find_datacenter(dcname) or abort "datacenter not found"
195
+
196
+ src_folder = find_folder(get_config(:folder)) || dc.vmFolder
197
+
198
+ src_vm = find_in_folder(src_folder, RbVmomi::VIM::VirtualMachine, config[:source_vm]) or
199
+ abort "VM/Template not found"
200
+
201
+ clone_spec = generate_clone_spec(src_vm.config)
202
+
203
+ cust_folder = config[:dest_folder] || get_config(:folder)
204
+
205
+ dest_folder = cust_folder.nil? ? src_vm.vmFolder : find_folder(cust_folder)
206
+
207
+ task = src_vm.CloneVM_Task(:folder => dest_folder, :name => vmname, :spec => clone_spec)
208
+ puts "Cloning template #{config[:source_vm]} to new VM #{vmname}"
209
+ task.wait_for_completion
210
+ puts "Finished creating virtual machine #{vmname}"
211
+
212
+ if get_config(:power) || get_config(:bootstrap)
213
+ vm = find_in_folder(dest_folder, RbVmomi::VIM::VirtualMachine, vmname) or
214
+ fatal_exit("VM #{vmname} not found")
215
+ vm.PowerOnVM_Task.wait_for_completion
216
+ puts "Powered on virtual machine #{vmname}"
217
+ end
218
+
219
+ if get_config(:bootstrap)
220
+ sleep 2 until vm.guest.ipAddress
221
+ config[:fqdn] = vm.guest.ipAddress unless config[:fqdn]
222
+ print "Waiting for sshd..."
223
+ print "." until tcp_test_ssh(config[:fqdn])
224
+ puts "done"
225
+
226
+ bootstrap_for_node.run
227
+ end
228
+ end
229
+
230
+ # Builds a CloneSpec
231
+ def generate_clone_spec (src_config)
232
+
233
+ rspec = nil
234
+ if get_config(:resource_pool)
235
+ rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => find_pool(get_config(:resource_pool)))
236
+ else
237
+ dcname = get_config(:vsphere_dc)
238
+ dc = config[:vim].serviceInstance.find_datacenter(dcname) or abort "datacenter not found"
239
+ hosts = find_all_in_folder(dc.hostFolder, RbVmomi::VIM::ComputeResource)
240
+ rp = hosts.first.resourcePool
241
+ rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => rp)
242
+ end
243
+
244
+ if get_config(:datastore)
245
+ rspec.datastore = find_datastore(get_config(:datastore))
246
+ end
247
+
248
+ clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(:location => rspec,
249
+ :powerOn => false,
250
+ :template => false)
251
+
252
+ clone_spec.config = RbVmomi::VIM.VirtualMachineConfigSpec(:deviceChange => Array.new)
253
+
254
+ if get_config(:customization_cpucount)
255
+ clone_spec.config.numCPUs = get_config(:customization_cpucount)
256
+ end
257
+
258
+ if get_config(:customization_memory)
259
+ clone_spec.config.memoryMB = Integer(get_config(:customization_memory)) * 1024
260
+ end
261
+
262
+ if get_config(:customization_vlan)
263
+ network = find_network(get_config(:customization_vlan))
264
+ card = src_config.hardware.device.find { |d| d.deviceInfo.label == "Network adapter 1" } or
265
+ abort "Can't find source network card to customize"
266
+ begin
267
+ switch_port = RbVmomi::VIM.DistributedVirtualSwitchPortConnection(:switchUuid => network.config.distributedVirtualSwitch.uuid ,:portgroupKey => network.key)
268
+ card.backing.port = switch_port
269
+ rescue
270
+ # not connected to a distibuted switch?
271
+ card.backing.deviceName = network.name
272
+ end
273
+ dev_spec = RbVmomi::VIM.VirtualDeviceConfigSpec(:device => card, :operation => "edit")
274
+ clone_spec.config.deviceChange.push dev_spec
275
+ end
276
+
277
+ if get_config(:customization_spec)
278
+ csi = find_customization(get_config(:customization_spec)) or
279
+ fatal_exit("failed to find customization specification named #{get_config(:customization_spec)}")
280
+
281
+ if csi.info.type != "Linux"
282
+ fatal_exit("Only Linux customization specifications are currently supported")
283
+ end
284
+ cust_spec = csi.spec
285
+ else
286
+ global_ipset = RbVmomi::VIM.CustomizationGlobalIPSettings
287
+ cust_spec = RbVmomi::VIM.CustomizationSpec(:globalIPSettings => global_ipset)
288
+ end
289
+
290
+ if get_config(:customization_dns_ips)
291
+ cust_spec.globalIPSettings.dnsServerList = get_config(:customization_dns_ips).split(',')
292
+ end
293
+
294
+ if get_config(:customization_dns_suffixes)
295
+ cust_spec.globalIPSettings.dnsSuffixList = get_config(:customization_dns_suffixes).split(',')
296
+ end
297
+
298
+ if config[:customization_ips]
299
+ if get_config(:customization_gw)
300
+ cust_spec.nicSettingMap = config[:customization_ips].split(',').map { |i| generate_adapter_map(i,get_config(:customization_gw)) }
301
+ else
302
+ cust_spec.nicSettingMap = config[:customization_ips].split(',').map { |i| generate_adapter_map(i) }
303
+ end
304
+ end
305
+
306
+ unless get_config(:disable_customization)
307
+ use_ident = !config[:customization_hostname].nil? || !get_config(:customization_domain).nil? || cust_spec.identity.nil?
308
+ end
309
+
310
+
311
+ if use_ident
312
+ # TODO - verify that we're deploying a linux spec, at least warn
313
+ ident = RbVmomi::VIM.CustomizationLinuxPrep
314
+
315
+ ident.hostName = RbVmomi::VIM.CustomizationFixedName
316
+ if config[:customization_hostname]
317
+ ident.hostName.name = config[:customization_hostname]
318
+ else
319
+ ident.hostName.name = config[:vmname]
320
+ end
321
+
322
+ if get_config(:customization_domain)
323
+ ident.domain = get_config(:customization_domain)
324
+ else
325
+ ident.domain = ''
326
+ end
327
+
328
+ cust_spec.identity = ident
329
+ end
330
+
331
+ clone_spec.customization = cust_spec
332
+ clone_spec
333
+ end
334
+
335
+ # Retrieves a CustomizationSpecItem that matches the supplied name
336
+ # @param vim [Connection] VI Connection to use
337
+ # @param name [String] name of customization
338
+ # @return [RbVmomi::VIM::CustomizationSpecItem]
339
+ def find_customization(name)
340
+ csm = config[:vim].serviceContent.customizationSpecManager
341
+ csm.GetCustomizationSpec(:name => name)
342
+ end
343
+
344
+ # Generates a CustomizationAdapterMapping (currently only single IPv4 address) object
345
+ # @param ip [String] Any static IP address to use, otherwise DHCP
346
+ # @param gw [String] If static, the gateway for the interface, otherwise network address + 1 will be used
347
+ # @return [RbVmomi::VIM::CustomizationIPSettings]
348
+ def generate_adapter_map (ip=nil, gw=nil, dns1=nil, dns2=nil, domain=nil)
349
+
350
+ settings = RbVmomi::VIM.CustomizationIPSettings
351
+
352
+ if ip.nil?
353
+ settings.ip = RbVmomi::VIM::CustomizationDhcpIpGenerator
354
+ else
355
+ cidr_ip = NetAddr::CIDR.create(ip)
356
+ settings.ip = RbVmomi::VIM::CustomizationFixedIp(:ipAddress => cidr_ip.ip)
357
+ settings.subnetMask = cidr_ip.netmask_ext
358
+
359
+ # TODO - want to confirm gw/ip are in same subnet?
360
+ # Only set gateway on first IP.
361
+ if config[:customization_ips].split(',').first == ip
362
+ if gw.nil?
363
+ settings.gateway = [cidr_ip.network(:Objectify => true).next_ip]
364
+ else
365
+ gw_cidr = NetAddr::CIDR.create(gw)
366
+ settings.gateway = [gw_cidr.ip]
367
+ end
368
+ end
369
+ end
370
+
371
+ adapter_map = RbVmomi::VIM.CustomizationAdapterMapping
372
+ adapter_map.adapter = settings
373
+ adapter_map
374
+ end
375
+
376
+ def bootstrap_for_node()
377
+ Chef::Knife::Bootstrap.load_deps
378
+ bootstrap = Chef::Knife::Bootstrap.new
379
+ bootstrap.name_args = [config[:fqdn]]
380
+ bootstrap.config[:run_list] = get_config(:run_list).split(/[\s,]+/)
381
+ bootstrap.config[:ssh_user] = get_config(:ssh_user)
382
+ bootstrap.config[:ssh_password] = get_config(:ssh_password)
383
+ bootstrap.config[:ssh_port] = get_config(:ssh_port)
384
+ bootstrap.config[:identity_file] = get_config(:identity_file)
385
+ bootstrap.config[:chef_node_name] = get_config(:chef_node_name)
386
+ bootstrap.config[:prerelease] = get_config(:prerelease)
387
+ bootstrap.config[:bootstrap_version] = get_config(:bootstrap_version)
388
+ bootstrap.config[:distro] = get_config(:distro)
389
+ bootstrap.config[:use_sudo] = true unless get_config(:ssh_user) == 'root'
390
+ bootstrap.config[:template_file] = get_config(:template_file)
391
+ bootstrap.config[:environment] = get_config(:environment)
392
+ bootstrap.config[:first_boot_attributes] = get_config(:first_boot_attributes)
393
+ # may be needed for vpc_mode
394
+ bootstrap.config[:no_host_key_verify] = get_config(:no_host_key_verify)
395
+ bootstrap
396
+ end
397
+
398
+ def tcp_test_ssh(hostname)
399
+ tcp_socket = TCPSocket.new(hostname, get_config(:ssh_port))
400
+ readable = IO.select([tcp_socket], nil, nil, 5)
401
+ if readable
402
+ Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}")
403
+ true
404
+ else
405
+ false
406
+ end
407
+ rescue Errno::ETIMEDOUT
408
+ false
409
+ rescue Errno::EPERM
410
+ false
411
+ rescue Errno::ECONNREFUSED
412
+ sleep 2
413
+ false
414
+ rescue Errno::EHOSTUNREACH, Errno::ENETUNREACH
415
+ sleep 2
416
+ false
417
+ ensure
418
+ tcp_socket && tcp_socket.close
419
+ end
420
+ end
@@ -1,4 +1,4 @@
1
1
  module KnifeVsphere
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
4
4
 
metadata CHANGED
@@ -1,56 +1,60 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: knife-vsphere
3
- version: !ruby/object:Gem::Version
4
- version: 0.3.0
3
+ version: !ruby/object:Gem::Version
5
4
  prerelease:
5
+ version: 0.4.0
6
6
  platform: ruby
7
- authors:
7
+ authors:
8
8
  - Ezra Pagel
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-18 00:00:00.000000000 -05:00
12
+
13
+ date: 2013-03-21 00:00:00 -05:00
13
14
  default_executable:
14
- dependencies:
15
- - !ruby/object:Gem::Dependency
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
16
17
  name: netaddr
17
- requirement: &70142499693560 !ruby/object:Gem::Requirement
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
18
20
  none: false
19
- requirements:
21
+ requirements:
20
22
  - - ~>
21
- - !ruby/object:Gem::Version
23
+ - !ruby/object:Gem::Version
22
24
  version: 1.5.0
23
25
  type: :runtime
24
- prerelease: false
25
- version_requirements: *70142499693560
26
- - !ruby/object:Gem::Dependency
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
27
28
  name: chef
28
- requirement: &70142499693040 !ruby/object:Gem::Requirement
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
29
31
  none: false
30
- requirements:
31
- - - ! '>='
32
- - !ruby/object:Gem::Version
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
33
35
  version: 0.10.0
34
36
  type: :runtime
35
- prerelease: false
36
- version_requirements: *70142499693040
37
- - !ruby/object:Gem::Dependency
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
38
39
  name: rbvmomi
39
- requirement: &70142499692440 !ruby/object:Gem::Requirement
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
40
42
  none: false
41
- requirements:
43
+ requirements:
42
44
  - - ~>
43
- - !ruby/object:Gem::Version
45
+ - !ruby/object:Gem::Version
44
46
  version: 1.5.1
45
47
  type: :runtime
46
- prerelease: false
47
- version_requirements: *70142499692440
48
+ version_requirements: *id003
48
49
  description: VMware vSphere Support for Chef's Knife Command
49
50
  email: ezra@cpan.org
50
51
  executables: []
52
+
51
53
  extensions: []
54
+
52
55
  extra_rdoc_files: []
53
- files:
56
+
57
+ files:
54
58
  - lib/chef/knife/BaseVsphereCommand.rb
55
59
  - lib/chef/knife/vsphere_customization_list.rb
56
60
  - lib/chef/knife/vsphere_datastore_list.rb
@@ -66,26 +70,30 @@ files:
66
70
  has_rdoc: true
67
71
  homepage: http://github.com/ezrapagel/knife-vsphere
68
72
  licenses: []
73
+
69
74
  post_install_message:
70
75
  rdoc_options: []
71
- require_paths:
76
+
77
+ require_paths:
72
78
  - lib
73
- required_ruby_version: !ruby/object:Gem::Requirement
79
+ required_ruby_version: !ruby/object:Gem::Requirement
74
80
  none: false
75
- requirements:
76
- - - ! '>='
77
- - !ruby/object:Gem::Version
78
- version: '0'
79
- required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: "0"
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
86
  none: false
81
- requirements:
82
- - - ! '>='
83
- - !ruby/object:Gem::Version
84
- version: '0'
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: "0"
85
91
  requirements: []
92
+
86
93
  rubyforge_project:
87
94
  rubygems_version: 1.6.2
88
95
  signing_key:
89
96
  specification_version: 3
90
97
  summary: vSphere Support for Knife
91
98
  test_files: []
99
+