knife-ovh-cloud 1.0.0.pre.1
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.
- checksums.yaml +7 -0
- data/lib/chef/knife/base_ovh_cloud_command.rb +358 -0
- data/lib/chef/knife/ovh_cloud_customization_list.rb +29 -0
- data/lib/chef/knife/ovh_cloud_datastore_list.rb +58 -0
- data/lib/chef/knife/ovh_cloud_datastore_maxfree.rb +49 -0
- data/lib/chef/knife/ovh_cloud_datastorecluster_list.rb +60 -0
- data/lib/chef/knife/ovh_cloud_datastorecluster_maxfree.rb +48 -0
- data/lib/chef/knife/ovh_cloud_hosts_list.rb +49 -0
- data/lib/chef/knife/ovh_cloud_pool_list.rb +44 -0
- data/lib/chef/knife/ovh_cloud_pool_query.rb +58 -0
- data/lib/chef/knife/ovh_cloud_template_list.rb +32 -0
- data/lib/chef/knife/ovh_cloud_vlan_list.rb +37 -0
- data/lib/chef/knife/ovh_cloud_vm_clone.rb +567 -0
- data/lib/chef/knife/ovh_cloud_vm_config.rb +46 -0
- data/lib/chef/knife/ovh_cloud_vm_delete.rb +66 -0
- data/lib/chef/knife/ovh_cloud_vm_execute.rb +66 -0
- data/lib/chef/knife/ovh_cloud_vm_list.rb +66 -0
- data/lib/chef/knife/ovh_cloud_vm_markastemplate.rb +54 -0
- data/lib/chef/knife/ovh_cloud_vm_migrate.rb +80 -0
- data/lib/chef/knife/ovh_cloud_vm_move.rb +47 -0
- data/lib/chef/knife/ovh_cloud_vm_net.rb +57 -0
- data/lib/chef/knife/ovh_cloud_vm_property_get.rb +46 -0
- data/lib/chef/knife/ovh_cloud_vm_property_set.rb +84 -0
- data/lib/chef/knife/ovh_cloud_vm_query.rb +48 -0
- data/lib/chef/knife/ovh_cloud_vm_snapshot.rb +129 -0
- data/lib/chef/knife/ovh_cloud_vm_state.rb +127 -0
- data/lib/chef/knife/ovh_cloud_vm_vmdk_add.rb +241 -0
- data/lib/knife-ovh-cloud/version.rb +4 -0
- metadata +112 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 560ff92c7398e4253a1da4f57c702411e2f34c9c
|
4
|
+
data.tar.gz: 40d70a9004086478882349b37a48a8060c2cf18e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 22aa6e2eac62d522fb690447a2c50e95b673a400f2c0b22ca5ac85ce6e26b8c6ce32afa2adf1f449ab7130e11a7f0f73a357678d77c8d0efb31e42fb81ed586e
|
7
|
+
data.tar.gz: efc304d1a7af10de6f2b403e1a1ff02a251ba9bef54baab2c8f6e9ee63dd409750d90da52408134108b58b6b1f021529ea95870e623c2a501c27baf28db34e4c
|
@@ -0,0 +1,358 @@
|
|
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 BaseOvhCloudCommand < 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 :ovh_cloud_user,
|
31
|
+
:short => "-u USERNAME",
|
32
|
+
:long => "--vsuser USERNAME",
|
33
|
+
:description => "The username for vsphere"
|
34
|
+
|
35
|
+
option :ovh_cloud_pass,
|
36
|
+
:short => "-p PASSWORD",
|
37
|
+
:long => "--vspass PASSWORD",
|
38
|
+
:description => "The password for vsphere"
|
39
|
+
|
40
|
+
option :ovh_cloud_host,
|
41
|
+
:long => "--vshost HOST",
|
42
|
+
:description => "The vsphere host"
|
43
|
+
|
44
|
+
option :ovh_cloud_dc,
|
45
|
+
:short => "-D DATACENTER",
|
46
|
+
:long => "--vsdc DATACENTER",
|
47
|
+
:description => "The Datacenter for vsphere"
|
48
|
+
|
49
|
+
option :ovh_cloud_path,
|
50
|
+
:long => "--vspath SOAP_PATH",
|
51
|
+
:description => "The vsphere SOAP endpoint path"
|
52
|
+
$default[:ovh_cloud_path] = "/sdk"
|
53
|
+
|
54
|
+
option :ovh_cloud_port,
|
55
|
+
:long => "--vsport PORT",
|
56
|
+
:description => "The VI SDK port number to use"
|
57
|
+
$default[:ovh_cloud_port] = 443
|
58
|
+
|
59
|
+
option :vshere_nossl,
|
60
|
+
:long => "--vsnossl",
|
61
|
+
:description => "Disable SSL connectivity"
|
62
|
+
|
63
|
+
option :ovh_cloud_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(:ovh_cloud_host),
|
94
|
+
:path => get_config(:ovh_cloud_path),
|
95
|
+
:port => get_config(:ovh_cloud_port),
|
96
|
+
:use_ssl => !get_config(:ovh_cloud_nossl),
|
97
|
+
:user => get_config(:ovh_cloud_user),
|
98
|
+
:password => get_config(:ovh_cloud_pass),
|
99
|
+
:insecure => get_config(:ovh_cloud_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
|
+
children = folder.children.find_all
|
130
|
+
children.each do |child|
|
131
|
+
if child.class == RbVmomi::VIM::VirtualMachine && child.name == vmname
|
132
|
+
return child
|
133
|
+
elsif child.class == RbVmomi::VIM::Folder
|
134
|
+
vm = traverse_folders_for_vm(child, vmname)
|
135
|
+
if vm then return vm end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
return false
|
139
|
+
end
|
140
|
+
|
141
|
+
def get_vms(vmname)
|
142
|
+
vim = get_vim_connection
|
143
|
+
baseFolder = find_folder(get_config(:folder));
|
144
|
+
retval = traverse_folders_for_vms(baseFolder, vmname)
|
145
|
+
return retval
|
146
|
+
end
|
147
|
+
|
148
|
+
def traverse_folders_for_vms(folder, vmname)
|
149
|
+
retval = []
|
150
|
+
children = folder.children.find_all
|
151
|
+
children.each do |child|
|
152
|
+
if child.class == RbVmomi::VIM::VirtualMachine && child.name == vmname
|
153
|
+
retval << child
|
154
|
+
elsif child.class == RbVmomi::VIM::Folder
|
155
|
+
retval.concat(traverse_folders_for_vms(child, vmname))
|
156
|
+
end
|
157
|
+
end
|
158
|
+
return retval
|
159
|
+
end
|
160
|
+
|
161
|
+
def traverse_folders_for_dc(folder, dcname)
|
162
|
+
children = folder.children.find_all
|
163
|
+
children.each do |child|
|
164
|
+
if child.class == RbVmomi::VIM::Datacenter && child.name == dcname
|
165
|
+
return child
|
166
|
+
elsif child.class == RbVmomi::VIM::Folder
|
167
|
+
dc = traverse_folders_for_dc(child, dcname)
|
168
|
+
if dc then return dc end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
return false
|
172
|
+
end
|
173
|
+
|
174
|
+
def get_datacenter
|
175
|
+
dcname = get_config(:ovh_cloud_dc)
|
176
|
+
traverse_folders_for_dc(config[:vim].rootFolder, dcname) or abort "datacenter not found"
|
177
|
+
end
|
178
|
+
|
179
|
+
def find_folder(folderName)
|
180
|
+
dc = get_datacenter
|
181
|
+
baseEntity = dc.vmFolder
|
182
|
+
entityArray = folderName.split('/')
|
183
|
+
entityArray.each do |entityArrItem|
|
184
|
+
if entityArrItem != ''
|
185
|
+
baseEntity = baseEntity.childEntity.grep(RbVmomi::VIM::Folder).find { |f| f.name == entityArrItem } or
|
186
|
+
abort "no such folder #{folderName} while looking for #{entityArrItem}"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
baseEntity
|
190
|
+
end
|
191
|
+
|
192
|
+
def find_network(networkName)
|
193
|
+
dc = get_datacenter
|
194
|
+
baseEntity = dc.network
|
195
|
+
baseEntity.find { |f| f.name == networkName } or abort "no such network #{networkName}"
|
196
|
+
end
|
197
|
+
|
198
|
+
def find_pool(poolName)
|
199
|
+
dc = get_datacenter
|
200
|
+
baseEntity = dc.hostFolder
|
201
|
+
entityArray = poolName.split('/')
|
202
|
+
entityArray.each do |entityArrItem|
|
203
|
+
if entityArrItem != ''
|
204
|
+
if baseEntity.is_a? RbVmomi::VIM::Folder
|
205
|
+
baseEntity = baseEntity.childEntity.find { |f| f.name == entityArrItem } or
|
206
|
+
abort "no such pool #{poolName} while looking for #{entityArrItem}"
|
207
|
+
elsif baseEntity.is_a? RbVmomi::VIM::ClusterComputeResource or baseEntity.is_a? RbVmomi::VIM::ComputeResource
|
208
|
+
baseEntity = baseEntity.resourcePool.resourcePool.find { |f| f.name == entityArrItem } or
|
209
|
+
abort "no such pool #{poolName} while looking for #{entityArrItem}"
|
210
|
+
elsif baseEntity.is_a? RbVmomi::VIM::ResourcePool
|
211
|
+
baseEntity = baseEntity.resourcePool.find { |f| f.name == entityArrItem } or
|
212
|
+
abort "no such pool #{poolName} while looking for #{entityArrItem}"
|
213
|
+
else
|
214
|
+
abort "Unexpected Object type encountered #{baseEntity.type} while finding resourcePool"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
baseEntity = baseEntity.resourcePool if not baseEntity.is_a?(RbVmomi::VIM::ResourcePool) and baseEntity.respond_to?(:resourcePool)
|
220
|
+
baseEntity
|
221
|
+
end
|
222
|
+
|
223
|
+
def choose_datastore(dstores, size)
|
224
|
+
vmdk_size_kb = size.to_i * 1024 * 1024
|
225
|
+
vmdk_size_B = size.to_i * 1024 * 1024 * 1024
|
226
|
+
|
227
|
+
candidates = []
|
228
|
+
dstores.each do |store|
|
229
|
+
avail = number_to_human_size(store.summary[:freeSpace])
|
230
|
+
cap = number_to_human_size(store.summary[:capacity])
|
231
|
+
puts "#{ui.color("Datastore", :cyan)}: #{store.name} (#{avail}(#{store.summary[:freeSpace]}) / #{cap})"
|
232
|
+
|
233
|
+
# vm's can span multiple datastores, so instead of grabbing the first one
|
234
|
+
# let's find the first datastore with the available space on a LUN the vm
|
235
|
+
# is already using, or use a specified LUN (if given)
|
236
|
+
|
237
|
+
|
238
|
+
if (store.summary[:freeSpace] - vmdk_size_B) > 0
|
239
|
+
# also let's not use more than 90% of total space to save room for snapshots.
|
240
|
+
cap_remains = 100 * ((store.summary[:freeSpace].to_f - vmdk_size_B.to_f) / store.summary[:capacity].to_f)
|
241
|
+
if (cap_remains.to_i > 10)
|
242
|
+
candidates.push(store)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
if candidates.length > 0
|
247
|
+
vmdk_datastore = candidates[0]
|
248
|
+
else
|
249
|
+
puts "Insufficient space on all LUNs designated or assigned to the virtual machine. Please specify a new target."
|
250
|
+
vmdk_datastore = nil
|
251
|
+
end
|
252
|
+
return vmdk_datastore
|
253
|
+
end
|
254
|
+
|
255
|
+
|
256
|
+
def find_datastores_regex(regex)
|
257
|
+
stores = Array.new()
|
258
|
+
puts "Looking for all datastores that match /#{regex}/"
|
259
|
+
dc = get_datacenter
|
260
|
+
baseEntity = dc.datastore
|
261
|
+
baseEntity.each do |ds|
|
262
|
+
if ds.name.match /#{regex}/
|
263
|
+
stores.push ds
|
264
|
+
end
|
265
|
+
end
|
266
|
+
return stores
|
267
|
+
end
|
268
|
+
|
269
|
+
def find_datastore(dsName)
|
270
|
+
dc = get_datacenter
|
271
|
+
baseEntity = dc.datastore
|
272
|
+
baseEntity.find { |f| f.info.name == dsName } or abort "no such datastore #{dsName}"
|
273
|
+
end
|
274
|
+
|
275
|
+
def find_datastorecluster(dsName)
|
276
|
+
dc = get_datacenter
|
277
|
+
baseEntity = dc.datastoreFolder.childEntity
|
278
|
+
baseEntity.find { |f| f.name == dsName and f.instance_of?(RbVmomi::VIM::StoragePod) } or abort "no such datastorecluster #{dsName}"
|
279
|
+
end
|
280
|
+
|
281
|
+
def find_device(vm, deviceName)
|
282
|
+
vm.config.hardware.device.each do |device|
|
283
|
+
return device if device.deviceInfo.label == deviceName
|
284
|
+
end
|
285
|
+
nil
|
286
|
+
end
|
287
|
+
|
288
|
+
def find_all_in_folder(folder, type)
|
289
|
+
if folder.instance_of?(RbVmomi::VIM::ClusterComputeResource) or folder.instance_of?(RbVmomi::VIM::ComputeResource)
|
290
|
+
folder = folder.resourcePool
|
291
|
+
end
|
292
|
+
if folder.instance_of?(RbVmomi::VIM::ResourcePool)
|
293
|
+
folder.resourcePool.grep(type)
|
294
|
+
elsif folder.instance_of?(RbVmomi::VIM::Folder)
|
295
|
+
folder.childEntity.grep(type)
|
296
|
+
else
|
297
|
+
puts "Unknown type #{folder.class}, not enumerating"
|
298
|
+
nil
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def get_path_to_object(object)
|
303
|
+
if object.is_a?(RbVmomi::VIM:: ManagedEntity)
|
304
|
+
if object.parent.is_a?(RbVmomi::VIM:: ManagedEntity)
|
305
|
+
return get_path_to_object(object.parent) + "/" + object.parent.name
|
306
|
+
else
|
307
|
+
return ""
|
308
|
+
end
|
309
|
+
else
|
310
|
+
puts "Unknown type #{object.class}, not enumerating"
|
311
|
+
nil
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def find_in_folder(folder, type, name)
|
316
|
+
folder.childEntity.grep(type).find { |o| o.name == name }
|
317
|
+
end
|
318
|
+
|
319
|
+
def fatal_exit(msg)
|
320
|
+
ui.fatal(msg)
|
321
|
+
exit 1
|
322
|
+
end
|
323
|
+
|
324
|
+
def tcp_test_port_vm(vm, port)
|
325
|
+
ip = vm.guest.ipAddress
|
326
|
+
if ip.nil?
|
327
|
+
sleep 2
|
328
|
+
return false
|
329
|
+
end
|
330
|
+
tcp_test_port(ip, port)
|
331
|
+
end
|
332
|
+
|
333
|
+
def tcp_test_port(hostname, port)
|
334
|
+
tcp_socket = TCPSocket.new(hostname, port)
|
335
|
+
readable = IO.select([tcp_socket], nil, nil, 5)
|
336
|
+
if readable
|
337
|
+
Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}") if port == 22
|
338
|
+
true
|
339
|
+
else
|
340
|
+
false
|
341
|
+
end
|
342
|
+
rescue Errno::ETIMEDOUT
|
343
|
+
false
|
344
|
+
rescue Errno::EPERM
|
345
|
+
false
|
346
|
+
rescue Errno::ECONNREFUSED
|
347
|
+
sleep 2
|
348
|
+
false
|
349
|
+
rescue Errno::EHOSTUNREACH, Errno::ENETUNREACH
|
350
|
+
sleep 2
|
351
|
+
false
|
352
|
+
ensure
|
353
|
+
tcp_socket && tcp_socket.close
|
354
|
+
end
|
355
|
+
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
@@ -0,0 +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/base_ovh_cloud_command'
|
7
|
+
|
8
|
+
# Lists all customization specifications in the configured datacenter
|
9
|
+
class Chef::Knife::OvhCloudCustomizationList < Chef::Knife::BaseOvhCloudCommand
|
10
|
+
|
11
|
+
banner "knife ovh cloud 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
|
+
|
@@ -0,0 +1,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_ovh_cloud_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::OvhCloudDatastoreList < Chef::Knife::BaseOvhCloudCommand
|
41
|
+
|
42
|
+
banner "knife ovh cloud datastore list"
|
43
|
+
|
44
|
+
get_common_options
|
45
|
+
|
46
|
+
def run
|
47
|
+
$stdout.sync = true
|
48
|
+
|
49
|
+
vim = get_vim_connection
|
50
|
+
dc = get_datacenter
|
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
|
+
|