knife-vsphere 0.9.0 → 0.9.5

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.
@@ -0,0 +1,304 @@
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,29 +1,29 @@
1
- #
2
- # Author:: Ezra Pagel (<ezra@cpan.org>)
3
- # License:: Apache License, Version 2.0
4
- #
5
- require 'chef/knife'
6
- require 'chef/knife/BaseVsphereCommand'
7
-
8
- # Lists all customization specifications in the configured datacenter
9
- class Chef::Knife::VsphereCustomizationList < Chef::Knife::BaseVsphereCommand
10
-
11
- banner "knife vsphere customization list"
12
-
13
- get_common_options
14
-
15
- def run
16
-
17
- $stdout.sync = true
18
-
19
- vim = get_vim_connection
20
-
21
- csm = vim.serviceContent.customizationSpecManager
22
- csm.info.each do |c|
23
- puts "#{ui.color("Customization Name", :cyan)}: #{c.name}"
24
-
25
- end
26
-
27
- end
28
- end
29
-
1
+ #
2
+ # Author:: Ezra Pagel (<ezra@cpan.org>)
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ require 'chef/knife'
6
+ require 'chef/knife/base_vsphere_command'
7
+
8
+ # Lists all customization specifications in the configured datacenter
9
+ class Chef::Knife::VsphereCustomizationList < Chef::Knife::BaseVsphereCommand
10
+
11
+ banner "knife vsphere customization list"
12
+
13
+ get_common_options
14
+
15
+ def run
16
+
17
+ $stdout.sync = true
18
+
19
+ vim = get_vim_connection
20
+
21
+ csm = vim.serviceContent.customizationSpecManager
22
+ csm.info.each do |c|
23
+ puts "#{ui.color("Customization Name", :cyan)}: #{c.name}"
24
+
25
+ end
26
+
27
+ end
28
+ end
29
+
@@ -1,58 +1,59 @@
1
- # Copyright (C) 2012, SCM Ventures AB
2
- # Author: Ian Delahorne <ian@scmventures.se>
3
- #
4
- # Permission to use, copy, modify, and/or distribute this software for
5
- # any purpose with or without fee is hereby granted, provided that the
6
- # above copyright notice and this permission notice appear in all
7
- # copies.
8
- #
9
- # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
10
- # WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
11
- # WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
12
- # AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
13
- # DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
14
- # OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
15
- # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
16
- # PERFORMANCE OF THIS SOFTWARE
17
-
18
- require 'chef/knife'
19
- require 'chef/knife/BaseVsphereCommand'
20
-
21
- def number_to_human_size(number)
22
- number = number.to_f
23
- storage_units_fmt = ["byte", "kB", "MB", "GB", "TB"]
24
- base = 1024
25
- if number.to_i < base
26
- unit = storage_units_fmt[0]
27
- else
28
- max_exp = storage_units_fmt.size - 1
29
- exponent = (Math.log(number) / Math.log(base)).to_i # Convert to base
30
- exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
31
- number /= base ** exponent
32
- unit = storage_units_fmt[exponent]
33
- end
34
-
35
- return sprintf("%0.2f %s", number, unit)
36
- end
37
-
38
-
39
- # Lists all known data stores in datacenter with sizes
40
- class Chef::Knife::VsphereDatastoreList < Chef::Knife::BaseVsphereCommand
41
-
42
- banner "knife vsphere datastore list"
43
-
44
- get_common_options
45
- def run
46
- $stdout.sync = true
47
-
48
- vim = get_vim_connection
49
- dcname = get_config(:vsphere_dc)
50
- dc = config[:vim].serviceInstance.find_datacenter(dcname) or abort "datacenter not found"
51
- dc.datastore.each do |store|
52
- avail = number_to_human_size(store.summary[:freeSpace])
53
- cap = number_to_human_size(store.summary[:capacity])
54
- puts "#{ui.color("Datastore", :cyan)}: #{store.name} (#{avail} / #{cap})"
55
- end
56
- end
57
- end
58
-
1
+ # Copyright (C) 2012, SCM Ventures AB
2
+ # Author: Ian Delahorne <ian@scmventures.se>
3
+ #
4
+ # Permission to use, copy, modify, and/or distribute this software for
5
+ # any purpose with or without fee is hereby granted, provided that the
6
+ # above copyright notice and this permission notice appear in all
7
+ # copies.
8
+ #
9
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
10
+ # WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
11
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
12
+ # AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
13
+ # DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
14
+ # OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
15
+ # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
16
+ # PERFORMANCE OF THIS SOFTWARE
17
+
18
+ require 'chef/knife'
19
+ require 'chef/knife/base_vsphere_command'
20
+
21
+ def number_to_human_size(number)
22
+ number = number.to_f
23
+ storage_units_fmt = ["byte", "kB", "MB", "GB", "TB"]
24
+ base = 1024
25
+ if number.to_i < base
26
+ unit = storage_units_fmt[0]
27
+ else
28
+ max_exp = storage_units_fmt.size - 1
29
+ exponent = (Math.log(number) / Math.log(base)).to_i # Convert to base
30
+ exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
31
+ number /= base ** exponent
32
+ unit = storage_units_fmt[exponent]
33
+ end
34
+
35
+ return sprintf("%0.2f %s", number, unit)
36
+ end
37
+
38
+
39
+ # Lists all known data stores in datacenter with sizes
40
+ class Chef::Knife::VsphereDatastoreList < Chef::Knife::BaseVsphereCommand
41
+
42
+ banner "knife vsphere datastore list"
43
+
44
+ get_common_options
45
+
46
+ def run
47
+ $stdout.sync = true
48
+
49
+ vim = get_vim_connection
50
+ dcname = get_config(:vsphere_dc)
51
+ dc = config[:vim].serviceInstance.find_datacenter(dcname) or abort "datacenter not found"
52
+ dc.datastore.each do |store|
53
+ avail = number_to_human_size(store.summary[:freeSpace])
54
+ cap = number_to_human_size(store.summary[:capacity])
55
+ puts "#{ui.color("Datastore", :cyan)}: #{store.name} (#{avail} / #{cap})"
56
+ end
57
+ end
58
+ end
59
+