knife-vsphere 0.9.5 → 0.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,304 +1,311 @@
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 'rbvmomi'
9
-
10
- # Base class for vsphere knife commands
11
- class Chef
12
- class Knife
13
- class BaseVsphereCommand < Knife
14
-
15
- deps do
16
- require 'chef/knife/bootstrap'
17
- Chef::Knife::Bootstrap.load_deps
18
- require 'fog'
19
- require 'socket'
20
- require 'net/ssh/multi'
21
- require 'readline'
22
- require 'chef/json_compat'
23
- end
24
-
25
- def self.get_common_options
26
- unless defined? $default
27
- $default = Hash.new
28
- end
29
-
30
- option :vsphere_user,
31
- :short => "-u USERNAME",
32
- :long => "--vsuser USERNAME",
33
- :description => "The username for vsphere"
34
-
35
- option :vsphere_pass,
36
- :short => "-p PASSWORD",
37
- :long => "--vspass PASSWORD",
38
- :description => "The password for vsphere"
39
-
40
- option :vsphere_host,
41
- :long => "--vshost HOST",
42
- :description => "The vsphere host"
43
-
44
- option :vsphere_dc,
45
- :short => "-d DATACENTER",
46
- :long => "--vsdc DATACENTER",
47
- :description => "The Datacenter for vsphere"
48
-
49
- option :vsphere_path,
50
- :long => "--vspath SOAP_PATH",
51
- :description => "The vsphere SOAP endpoint path"
52
- $default[:vsphere_path] = "/sdk"
53
-
54
- option :vsphere_port,
55
- :long => "--vsport PORT",
56
- :description => "The VI SDK port number to use"
57
- $default[:vsphere_port] = 443
58
-
59
- option :vshere_nossl,
60
- :long => "--vsnossl",
61
- :description => "Disable SSL connectivity"
62
-
63
- option :vsphere_insecure,
64
- :long => "--vsinsecure",
65
- :description => "Disable SSL certificate verification"
66
-
67
- option :folder,
68
- :short => "-f FOLDER",
69
- :long => "--folder FOLDER",
70
- :description => "The folder to get VMs from"
71
- $default[:folder] = ''
72
- end
73
-
74
- def get_config(key)
75
- key = key.to_sym
76
- rval = config[key] || Chef::Config[:knife][key] || $default[key]
77
- Chef::Log.debug("value for config item #{key}: #{rval}")
78
- rval
79
- end
80
-
81
- def get_vim_connection
82
-
83
- conn_opts = {
84
- :host => get_config(:vsphere_host),
85
- :path => get_config(:vshere_path),
86
- :port => get_config(:vsphere_port),
87
- :use_ssl => !get_config(:vsphere_nossl),
88
- :user => get_config(:vsphere_user),
89
- :password => get_config(:vsphere_pass),
90
- :insecure => get_config(:vsphere_insecure)
91
- }
92
-
93
- # Grab the password from the command line
94
- # if tt is not in the config file
95
- if not conn_opts[:password]
96
- conn_opts[:password] = get_password
97
- end
98
-
99
- # opt :debug, "Log SOAP messages", :short => 'd', :default => (ENV['RBVMOMI_DEBUG'] || false)
100
-
101
- vim = RbVmomi::VIM.connect conn_opts
102
- config[:vim] = vim
103
- return vim
104
- end
105
-
106
- def get_password
107
- @password ||= ui.ask("Enter your password: ") { |q| q.echo = false }
108
- end
109
-
110
- def get_vm(vmname)
111
- vim = get_vim_connection
112
- baseFolder = find_folder(get_config(:folder));
113
- retval = traverse_folders_for_vm(baseFolder, vmname)
114
- return retval
115
- end
116
-
117
- def traverse_folders_for_vm(folder, vmname)
118
- # not sure why @vm is necessary, but it returns class Array
119
- # instead of class VirtualMachine without it... ugh
120
- @vm = nil
121
- folders = find_all_in_folder(folder, RbVmomi::VIM::Folder)
122
- folders.each do |child|
123
- traverse_folders_for_vm(child, vmname)
124
- vms = find_all_in_folder(folder, RbVmomi::VIM::VirtualMachine)
125
- vms.each do |vm|
126
- if vm.name == vmname
127
- @vm = vm
128
- return @vm
129
- end
130
- end
131
- end
132
- return @vm
133
- end
134
-
135
- def get_datacenter
136
- dc = config[:vim].serviceInstance.find_datacenter(get_config(:vsphere_dc)) or abort "datacenter not found"
137
- end
138
-
139
- def find_folder(folderName)
140
- dcname = get_config(:vsphere_dc)
141
- dc = config[:vim].serviceInstance.find_datacenter(dcname) or abort "datacenter not found"
142
- baseEntity = dc.vmFolder
143
- entityArray = folderName.split('/')
144
- entityArray.each do |entityArrItem|
145
- if entityArrItem != ''
146
- baseEntity = baseEntity.childEntity.grep(RbVmomi::VIM::Folder).find { |f| f.name == entityArrItem } or
147
- abort "no such folder #{folderName} while looking for #{entityArrItem}"
148
- end
149
- end
150
- baseEntity
151
- end
152
-
153
- def find_network(networkName)
154
- dcname = get_config(:vsphere_dc)
155
- dc = config[:vim].serviceInstance.find_datacenter(dcname) or abort "datacenter not found"
156
- baseEntity = dc.network
157
- baseEntity.find { |f| f.name == networkName } or abort "no such network #{networkName}"
158
- end
159
-
160
- def find_pool(poolName)
161
- dcname = get_config(:vsphere_dc)
162
- dc = config[:vim].serviceInstance.find_datacenter(dcname) or abort "datacenter not found"
163
- baseEntity = dc.hostFolder
164
- entityArray = poolName.split('/')
165
- entityArray.each do |entityArrItem|
166
- if entityArrItem != ''
167
- if baseEntity.is_a? RbVmomi::VIM::Folder
168
- baseEntity = baseEntity.childEntity.find { |f| f.name == entityArrItem } or
169
- abort "no such pool #{poolName} while looking for #{entityArrItem}"
170
- elsif baseEntity.is_a? RbVmomi::VIM::ClusterComputeResource or baseEntity.is_a? RbVmomi::VIM::ComputeResource
171
- baseEntity = baseEntity.resourcePool.resourcePool.find { |f| f.name == entityArrItem } or
172
- abort "no such pool #{poolName} while looking for #{entityArrItem}"
173
- elsif baseEntity.is_a? RbVmomi::VIM::ResourcePool
174
- baseEntity = baseEntity.resourcePool.find { |f| f.name == entityArrItem } or
175
- abort "no such pool #{poolName} while looking for #{entityArrItem}"
176
- else
177
- abort "Unexpected Object type encountered #{baseEntity.type} while finding resourcePool"
178
- end
179
- end
180
- end
181
-
182
- baseEntity = baseEntity.resourcePool if not baseEntity.is_a?(RbVmomi::VIM::ResourcePool) and baseEntity.respond_to?(:resourcePool)
183
- baseEntity
184
- end
185
-
186
- def choose_datastore(dstores, size)
187
- vmdk_size_kb = size.to_i * 1024 * 1024
188
- vmdk_size_B = size.to_i * 1024 * 1024 * 1024
189
-
190
- candidates = []
191
- dstores.each do |store|
192
- avail = number_to_human_size(store.summary[:freeSpace])
193
- cap = number_to_human_size(store.summary[:capacity])
194
- puts "#{ui.color("Datastore", :cyan)}: #{store.name} (#{avail}(#{store.summary[:freeSpace]}) / #{cap})"
195
-
196
- # vm's can span multiple datastores, so instead of grabbing the first one
197
- # let's find the first datastore with the available space on a LUN the vm
198
- # is already using, or use a specified LUN (if given)
199
-
200
-
201
- if (store.summary[:freeSpace] - vmdk_size_B) > 0
202
- # also let's not use more than 90% of total space to save room for snapshots.
203
- cap_remains = 100 * ((store.summary[:freeSpace].to_f - vmdk_size_B.to_f) / store.summary[:capacity].to_f)
204
- if (cap_remains.to_i > 10)
205
- candidates.push(store)
206
- end
207
- end
208
- end
209
- if candidates.length > 0
210
- vmdk_datastore = candidates[0]
211
- else
212
- puts "Insufficient space on all LUNs designated or assigned to the virtual machine. Please specify a new target."
213
- vmdk_datastore = nil
214
- end
215
- return vmdk_datastore
216
- end
217
-
218
-
219
- def find_datastores_regex(regex)
220
- stores = Array.new()
221
- puts "Looking for all datastores that match /#{regex}/"
222
- dcname = get_config(:vsphere_dc)
223
- dc = config[:vim].serviceInstance.find_datacenter(dcname) or abort "datacenter not found"
224
- baseEntity = dc.datastore
225
- baseEntity.each do |ds|
226
- if ds.name.match /#{regex}/
227
- stores.push ds
228
- end
229
- end
230
- return stores
231
- end
232
-
233
- def find_datastore(dsName)
234
- dcname = get_config(:vsphere_dc)
235
- dc = config[:vim].serviceInstance.find_datacenter(dcname) or abort "datacenter not found"
236
- baseEntity = dc.datastore
237
- baseEntity.find { |f| f.info.name == dsName } or abort "no such datastore #{dsName}"
238
- end
239
-
240
- def find_device(vm, deviceName)
241
- vm.config.hardware.device.each do |device|
242
- return device if device.deviceInfo.label == deviceName
243
- end
244
- nil
245
- end
246
-
247
- def find_all_in_folder(folder, type)
248
- if folder.instance_of?(RbVmomi::VIM::ClusterComputeResource) or folder.instance_of?(RbVmomi::VIM::ComputeResource)
249
- folder = folder.resourcePool
250
- end
251
- if folder.instance_of?(RbVmomi::VIM::ResourcePool)
252
- folder.resourcePool.grep(type)
253
- elsif folder.instance_of?(RbVmomi::VIM::Folder)
254
- folder.childEntity.grep(type)
255
- else
256
- puts "Unknown type #{folder.class}, not enumerating"
257
- nil
258
- end
259
- end
260
-
261
- def find_in_folder(folder, type, name)
262
- folder.childEntity.grep(type).find { |o| o.name == name }
263
- end
264
-
265
- def fatal_exit(msg)
266
- ui.fatal(msg)
267
- exit 1
268
- end
269
-
270
- def tcp_test_port_vm(vm, port)
271
- ip = vm.guest.ipAddress
272
- if ip.nil?
273
- sleep 2
274
- return false
275
- end
276
- tcp_test_port(ip, port)
277
- end
278
-
279
- def tcp_test_port(hostname, port)
280
- tcp_socket = TCPSocket.new(hostname, port)
281
- readable = IO.select([tcp_socket], nil, nil, 5)
282
- if readable
283
- Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}") if port == 22
284
- true
285
- else
286
- false
287
- end
288
- rescue Errno::ETIMEDOUT
289
- false
290
- rescue Errno::EPERM
291
- false
292
- rescue Errno::ECONNREFUSED
293
- sleep 2
294
- false
295
- rescue Errno::EHOSTUNREACH, Errno::ENETUNREACH
296
- sleep 2
297
- false
298
- ensure
299
- tcp_socket && tcp_socket.close
300
- end
301
-
302
- end
303
- end
304
- end
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 'rbvmomi'
9
+
10
+ # Base class for vsphere knife commands
11
+ class Chef
12
+ class Knife
13
+ class BaseVsphereCommand < Knife
14
+
15
+ deps do
16
+ require 'chef/knife/bootstrap'
17
+ Chef::Knife::Bootstrap.load_deps
18
+ require 'fog'
19
+ require 'socket'
20
+ require 'net/ssh/multi'
21
+ require 'readline'
22
+ require 'chef/json_compat'
23
+ end
24
+
25
+ def self.get_common_options
26
+ unless defined? $default
27
+ $default = Hash.new
28
+ end
29
+
30
+ option :vsphere_user,
31
+ :short => "-u USERNAME",
32
+ :long => "--vsuser USERNAME",
33
+ :description => "The username for vsphere"
34
+
35
+ option :vsphere_pass,
36
+ :short => "-p PASSWORD",
37
+ :long => "--vspass PASSWORD",
38
+ :description => "The password for vsphere"
39
+
40
+ option :vsphere_host,
41
+ :long => "--vshost HOST",
42
+ :description => "The vsphere host"
43
+
44
+ option :vsphere_dc,
45
+ :short => "-d DATACENTER",
46
+ :long => "--vsdc DATACENTER",
47
+ :description => "The Datacenter for vsphere"
48
+
49
+ option :vsphere_path,
50
+ :long => "--vspath SOAP_PATH",
51
+ :description => "The vsphere SOAP endpoint path"
52
+ $default[:vsphere_path] = "/sdk"
53
+
54
+ option :vsphere_port,
55
+ :long => "--vsport PORT",
56
+ :description => "The VI SDK port number to use"
57
+ $default[:vsphere_port] = 443
58
+
59
+ option :vshere_nossl,
60
+ :long => "--vsnossl",
61
+ :description => "Disable SSL connectivity"
62
+
63
+ option :vsphere_insecure,
64
+ :long => "--vsinsecure",
65
+ :description => "Disable SSL certificate verification"
66
+
67
+ option :folder,
68
+ :short => "-f FOLDER",
69
+ :long => "--folder FOLDER",
70
+ :description => "The folder to get VMs from"
71
+
72
+ option :proxy_host,
73
+ :long => '--proxyhost PROXY_HOSTNAME',
74
+ :description => 'Proxy hostname'
75
+
76
+ option :proxy_port,
77
+ :long => '--proxyport PROXY_PORT',
78
+ :description => 'Proxy port'
79
+
80
+ $default[:folder] = ''
81
+ end
82
+
83
+ def get_config(key)
84
+ key = key.to_sym
85
+ rval = config[key] || Chef::Config[:knife][key] || $default[key]
86
+ Chef::Log.debug("value for config item #{key}: #{rval}")
87
+ rval
88
+ end
89
+
90
+ def get_vim_connection
91
+
92
+ conn_opts = {
93
+ :host => get_config(:vsphere_host),
94
+ :path => get_config(:vshere_path),
95
+ :port => get_config(:vsphere_port),
96
+ :use_ssl => !get_config(:vsphere_nossl),
97
+ :user => get_config(:vsphere_user),
98
+ :password => get_config(:vsphere_pass),
99
+ :insecure => get_config(:vsphere_insecure),
100
+ :proxyHost => get_config(:proxy_host),
101
+ :proxyPort => get_config(:proxy_port)
102
+ }
103
+
104
+ # Grab the password from the command line
105
+ # if tt is not in the config file
106
+ if not conn_opts[:password]
107
+ conn_opts[:password] = get_password
108
+ end
109
+
110
+ # opt :debug, "Log SOAP messages", :short => 'd', :default => (ENV['RBVMOMI_DEBUG'] || false)
111
+
112
+ vim = RbVmomi::VIM.connect conn_opts
113
+ config[:vim] = vim
114
+ return vim
115
+ end
116
+
117
+ def get_password
118
+ @password ||= ui.ask("Enter your password: ") { |q| q.echo = false }
119
+ end
120
+
121
+ def get_vm(vmname)
122
+ vim = get_vim_connection
123
+ baseFolder = find_folder(get_config(:folder));
124
+ retval = traverse_folders_for_vm(baseFolder, vmname)
125
+ return retval
126
+ end
127
+
128
+ def traverse_folders_for_vm(folder, vmname)
129
+ # not sure why @vm is necessary, but it returns class Array
130
+ # instead of class VirtualMachine without it... ugh
131
+ @vm = nil
132
+ folders = find_all_in_folder(folder, RbVmomi::VIM::Folder)
133
+ folders.each do |child|
134
+ traverse_folders_for_vm(child, vmname)
135
+ vms = find_all_in_folder(folder, RbVmomi::VIM::VirtualMachine)
136
+ vms.each do |vm|
137
+ if vm.name == vmname
138
+ @vm = vm
139
+ return @vm
140
+ end
141
+ end
142
+ end
143
+ return @vm
144
+ end
145
+
146
+ def get_datacenter
147
+ dcname = get_config(:vsphere_dc)
148
+ config[:vim].rootFolder.children.find { |child| child.name == dcname && child.class == RbVmomi::VIM::Datacenter } or abort "datacenter not found"
149
+ end
150
+
151
+ def find_folder(folderName)
152
+ dc = get_datacenter
153
+ baseEntity = dc.vmFolder
154
+ entityArray = folderName.split('/')
155
+ entityArray.each do |entityArrItem|
156
+ if entityArrItem != ''
157
+ baseEntity = baseEntity.childEntity.grep(RbVmomi::VIM::Folder).find { |f| f.name == entityArrItem } or
158
+ abort "no such folder #{folderName} while looking for #{entityArrItem}"
159
+ end
160
+ end
161
+ baseEntity
162
+ end
163
+
164
+ def find_network(networkName)
165
+ dc = get_datacenter
166
+ baseEntity = dc.network
167
+ baseEntity.find { |f| f.name == networkName } or abort "no such network #{networkName}"
168
+ end
169
+
170
+ def find_pool(poolName)
171
+ dc = get_datacenter
172
+ baseEntity = dc.hostFolder
173
+ entityArray = poolName.split('/')
174
+ entityArray.each do |entityArrItem|
175
+ if entityArrItem != ''
176
+ if baseEntity.is_a? RbVmomi::VIM::Folder
177
+ baseEntity = baseEntity.childEntity.find { |f| f.name == entityArrItem } or
178
+ abort "no such pool #{poolName} while looking for #{entityArrItem}"
179
+ elsif baseEntity.is_a? RbVmomi::VIM::ClusterComputeResource or baseEntity.is_a? RbVmomi::VIM::ComputeResource
180
+ baseEntity = baseEntity.resourcePool.resourcePool.find { |f| f.name == entityArrItem } or
181
+ abort "no such pool #{poolName} while looking for #{entityArrItem}"
182
+ elsif baseEntity.is_a? RbVmomi::VIM::ResourcePool
183
+ baseEntity = baseEntity.resourcePool.find { |f| f.name == entityArrItem } or
184
+ abort "no such pool #{poolName} while looking for #{entityArrItem}"
185
+ else
186
+ abort "Unexpected Object type encountered #{baseEntity.type} while finding resourcePool"
187
+ end
188
+ end
189
+ end
190
+
191
+ baseEntity = baseEntity.resourcePool if not baseEntity.is_a?(RbVmomi::VIM::ResourcePool) and baseEntity.respond_to?(:resourcePool)
192
+ baseEntity
193
+ end
194
+
195
+ def choose_datastore(dstores, size)
196
+ vmdk_size_kb = size.to_i * 1024 * 1024
197
+ vmdk_size_B = size.to_i * 1024 * 1024 * 1024
198
+
199
+ candidates = []
200
+ dstores.each do |store|
201
+ avail = number_to_human_size(store.summary[:freeSpace])
202
+ cap = number_to_human_size(store.summary[:capacity])
203
+ puts "#{ui.color("Datastore", :cyan)}: #{store.name} (#{avail}(#{store.summary[:freeSpace]}) / #{cap})"
204
+
205
+ # vm's can span multiple datastores, so instead of grabbing the first one
206
+ # let's find the first datastore with the available space on a LUN the vm
207
+ # is already using, or use a specified LUN (if given)
208
+
209
+
210
+ if (store.summary[:freeSpace] - vmdk_size_B) > 0
211
+ # also let's not use more than 90% of total space to save room for snapshots.
212
+ cap_remains = 100 * ((store.summary[:freeSpace].to_f - vmdk_size_B.to_f) / store.summary[:capacity].to_f)
213
+ if (cap_remains.to_i > 10)
214
+ candidates.push(store)
215
+ end
216
+ end
217
+ end
218
+ if candidates.length > 0
219
+ vmdk_datastore = candidates[0]
220
+ else
221
+ puts "Insufficient space on all LUNs designated or assigned to the virtual machine. Please specify a new target."
222
+ vmdk_datastore = nil
223
+ end
224
+ return vmdk_datastore
225
+ end
226
+
227
+
228
+ def find_datastores_regex(regex)
229
+ stores = Array.new()
230
+ puts "Looking for all datastores that match /#{regex}/"
231
+ dc = get_datacenter
232
+ baseEntity = dc.datastore
233
+ baseEntity.each do |ds|
234
+ if ds.name.match /#{regex}/
235
+ stores.push ds
236
+ end
237
+ end
238
+ return stores
239
+ end
240
+
241
+ def find_datastore(dsName)
242
+ dc = get_datacenter
243
+ baseEntity = dc.datastore
244
+ baseEntity.find { |f| f.info.name == dsName } or abort "no such datastore #{dsName}"
245
+ end
246
+
247
+ def find_device(vm, deviceName)
248
+ vm.config.hardware.device.each do |device|
249
+ return device if device.deviceInfo.label == deviceName
250
+ end
251
+ nil
252
+ end
253
+
254
+ def find_all_in_folder(folder, type)
255
+ if folder.instance_of?(RbVmomi::VIM::ClusterComputeResource) or folder.instance_of?(RbVmomi::VIM::ComputeResource)
256
+ folder = folder.resourcePool
257
+ end
258
+ if folder.instance_of?(RbVmomi::VIM::ResourcePool)
259
+ folder.resourcePool.grep(type)
260
+ elsif folder.instance_of?(RbVmomi::VIM::Folder)
261
+ folder.childEntity.grep(type)
262
+ else
263
+ puts "Unknown type #{folder.class}, not enumerating"
264
+ nil
265
+ end
266
+ end
267
+
268
+ def find_in_folder(folder, type, name)
269
+ folder.childEntity.grep(type).find { |o| o.name == name }
270
+ end
271
+
272
+ def fatal_exit(msg)
273
+ ui.fatal(msg)
274
+ exit 1
275
+ end
276
+
277
+ def tcp_test_port_vm(vm, port)
278
+ ip = vm.guest.ipAddress
279
+ if ip.nil?
280
+ sleep 2
281
+ return false
282
+ end
283
+ tcp_test_port(ip, port)
284
+ end
285
+
286
+ def tcp_test_port(hostname, port)
287
+ tcp_socket = TCPSocket.new(hostname, port)
288
+ readable = IO.select([tcp_socket], nil, nil, 5)
289
+ if readable
290
+ Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}") if port == 22
291
+ true
292
+ else
293
+ false
294
+ end
295
+ rescue Errno::ETIMEDOUT
296
+ false
297
+ rescue Errno::EPERM
298
+ false
299
+ rescue Errno::ECONNREFUSED
300
+ sleep 2
301
+ false
302
+ rescue Errno::EHOSTUNREACH, Errno::ENETUNREACH
303
+ sleep 2
304
+ false
305
+ ensure
306
+ tcp_socket && tcp_socket.close
307
+ end
308
+
309
+ end
310
+ end
311
+ end