knife-vsphere 1.0.0.pre.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/chef/knife/base_vsphere_command.rb +18 -8
- data/lib/chef/knife/vshpere_vm_move.rb +50 -5
- data/lib/chef/knife/vsphere_cpu_ratio.rb +45 -0
- data/lib/chef/knife/vsphere_datastore_list.rb +14 -0
- data/lib/chef/knife/vsphere_datastorecluster_list.rb +19 -8
- data/lib/chef/knife/vsphere_datastorecluster_maxfree.rb +44 -8
- data/lib/chef/knife/vsphere_folder_list.rb +30 -0
- data/lib/chef/knife/vsphere_hosts_list.rb +18 -25
- data/lib/chef/knife/vsphere_pool_list.rb +9 -7
- data/lib/chef/knife/vsphere_vm_clone.rb +29 -20
- data/lib/chef/knife/vsphere_vm_delete.rb +66 -66
- data/lib/chef/knife/vsphere_vm_list.rb +30 -35
- data/lib/chef/knife/vsphere_vm_snapshot.rb +1 -0
- data/lib/chef/knife/vsphere_vm_toolsconfig.rb +52 -0
- data/lib/knife-vsphere/version.rb +1 -1
- metadata +49 -38
- checksums.yaml +0 -7
@@ -146,7 +146,7 @@ class Chef
|
|
146
146
|
end
|
147
147
|
|
148
148
|
def traverse_folders_for_vms(folder, vmname)
|
149
|
-
|
149
|
+
retval = []
|
150
150
|
children = folder.children.find_all
|
151
151
|
children.each do |child|
|
152
152
|
if child.class == RbVmomi::VIM::VirtualMachine && child.name == vmname
|
@@ -204,7 +204,7 @@ class Chef
|
|
204
204
|
if baseEntity.is_a? RbVmomi::VIM::Folder
|
205
205
|
baseEntity = baseEntity.childEntity.find { |f| f.name == entityArrItem } or
|
206
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
|
207
|
+
elsif baseEntity.is_a? RbVmomi::VIM::ClusterComputeResource or baseEntity.is_a? RbVmomi::VIM::ComputeResource
|
208
208
|
baseEntity = baseEntity.resourcePool.resourcePool.find { |f| f.name == entityArrItem } or
|
209
209
|
abort "no such pool #{poolName} while looking for #{entityArrItem}"
|
210
210
|
elsif baseEntity.is_a? RbVmomi::VIM::ResourcePool
|
@@ -272,10 +272,20 @@ class Chef
|
|
272
272
|
baseEntity.find { |f| f.info.name == dsName } or abort "no such datastore #{dsName}"
|
273
273
|
end
|
274
274
|
|
275
|
-
def find_datastorecluster(dsName)
|
276
|
-
|
277
|
-
|
278
|
-
|
275
|
+
def find_datastorecluster(dsName, folder = nil)
|
276
|
+
if ! folder
|
277
|
+
dc = get_datacenter
|
278
|
+
folder = dc.datastoreFolder
|
279
|
+
end
|
280
|
+
folder.childEntity.each do |child|
|
281
|
+
if child.class.to_s == 'Folder'
|
282
|
+
ds = find_datastorecluster(dsName, child)
|
283
|
+
if ds then return ds end
|
284
|
+
elsif child.class.to_s == 'StoragePod' && child.name == dsName
|
285
|
+
return child
|
286
|
+
end
|
287
|
+
end
|
288
|
+
return nil
|
279
289
|
end
|
280
290
|
|
281
291
|
def find_device(vm, deviceName)
|
@@ -304,8 +314,8 @@ class Chef
|
|
304
314
|
if object.parent.is_a?(RbVmomi::VIM:: ManagedEntity)
|
305
315
|
return get_path_to_object(object.parent) + "/" + object.parent.name
|
306
316
|
else
|
307
|
-
|
308
|
-
|
317
|
+
return ""
|
318
|
+
end
|
309
319
|
else
|
310
320
|
puts "Unknown type #{object.class}, not enumerating"
|
311
321
|
nil
|
@@ -21,6 +21,50 @@ class Chef::Knife::VsphereVmMove < Chef::Knife::BaseVsphereCommand
|
|
21
21
|
:long => "--dest-folder FOLDER",
|
22
22
|
:description => "The destination folder into which the VM or template should be moved"
|
23
23
|
|
24
|
+
option :datastore,
|
25
|
+
:long => "--datastore STORE",
|
26
|
+
:description => "The datastore into which to put the cloned VM"
|
27
|
+
|
28
|
+
option :thin_provision,
|
29
|
+
:long => "--thin-provision",
|
30
|
+
:description => "Indicates whether disk should be thin provisioned.",
|
31
|
+
:boolean => true
|
32
|
+
|
33
|
+
option :thick_provision,
|
34
|
+
:long => "--thick-provision",
|
35
|
+
:description => "Indicates whether disk should be thick provisioned.",
|
36
|
+
:boolean => true
|
37
|
+
|
38
|
+
# Convert VM
|
39
|
+
def convert_vm(vm)
|
40
|
+
rspec = nil
|
41
|
+
dc = get_datacenter
|
42
|
+
hosts = find_all_in_folder(dc.hostFolder, RbVmomi::VIM::ComputeResource)
|
43
|
+
rp = hosts.first.resourcePool
|
44
|
+
rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => rp)
|
45
|
+
|
46
|
+
if get_config(:thin_provision)
|
47
|
+
puts "Thin provsisioning #{vm.name}"
|
48
|
+
rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:datastore => find_datastore(get_config(:datastore)), :transform => :sparse)
|
49
|
+
end
|
50
|
+
|
51
|
+
if get_config(:thick_provision)
|
52
|
+
puts "Thick provsisioning #{vm.name}"
|
53
|
+
rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:datastore => find_datastore(get_config(:datastore)), :transform => :flat)
|
54
|
+
end
|
55
|
+
|
56
|
+
task = vm.RelocateVM_Task(:spec => rspec)
|
57
|
+
task.wait_for_completion
|
58
|
+
end
|
59
|
+
|
60
|
+
# Move VM
|
61
|
+
def move_vm(vm)
|
62
|
+
dest_name = config[:dest_name] || vmname
|
63
|
+
dest_folder = config[:dest_folder].nil? ? (vm.parent) : (find_folder(get_config(:dest_folder)))
|
64
|
+
|
65
|
+
vm.Rename_Task(:newName => dest_name).wait_for_completion unless vmname == dest_name
|
66
|
+
dest_folder.MoveIntoFolder_Task(:list => [vm]).wait_for_completion unless folder == dest_folder
|
67
|
+
end
|
24
68
|
|
25
69
|
def run
|
26
70
|
$stdout.sync = true
|
@@ -38,10 +82,11 @@ class Chef::Knife::VsphereVmMove < Chef::Knife::BaseVsphereCommand
|
|
38
82
|
vm = find_in_folder(folder, RbVmomi::VIM::VirtualMachine, vmname) or
|
39
83
|
abort "VM #{vmname} not found"
|
40
84
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
85
|
+
if get_config(:thin_provision) || get_config(:thick_provision)
|
86
|
+
convert_vm(vm)
|
87
|
+
else
|
88
|
+
move_vm(vm)
|
89
|
+
end
|
46
90
|
end
|
47
91
|
end
|
92
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'chef/knife'
|
2
|
+
require 'chef/knife/base_vsphere_command'
|
3
|
+
|
4
|
+
class Chef::Knife::VsphereCpuRatio < Chef::Knife::BaseVsphereCommand
|
5
|
+
banner 'knife vsphere cpu ratio [CLUSTER] [HOST]'
|
6
|
+
|
7
|
+
get_common_options
|
8
|
+
|
9
|
+
def run
|
10
|
+
$stdout.sync = true
|
11
|
+
|
12
|
+
cluster_name = @name_args[0]
|
13
|
+
host_name = @name_args[1]
|
14
|
+
|
15
|
+
get_vim_connection
|
16
|
+
|
17
|
+
dc = get_datacenter
|
18
|
+
hf = dc.hostFolder
|
19
|
+
|
20
|
+
cluster = cluster_name.nil? ? hf.childEntity : hf.childEntity.select { |c| c.name == cluster_name }
|
21
|
+
|
22
|
+
if cluster.empty?
|
23
|
+
fatal_exit("Cluster #{cluster_name} not found.")
|
24
|
+
end
|
25
|
+
|
26
|
+
cluster.each { |c|
|
27
|
+
host = host_name.nil? ? c.host : c.host.select { |h| h.name == host_name }
|
28
|
+
if host.empty?
|
29
|
+
fatal_exit("Host not found in cluster #{c.name}.")
|
30
|
+
end
|
31
|
+
|
32
|
+
puts "### Cluster #{c.name} ###"
|
33
|
+
|
34
|
+
host.each { |h|
|
35
|
+
v_cpu = h.vm.inject(0) { |sum, vm| sum + vm.config.hardware.numCPU }
|
36
|
+
p_cpu = h.summary.hardware.numCpuThreads
|
37
|
+
|
38
|
+
ratio = 1.0 * v_cpu/p_cpu
|
39
|
+
|
40
|
+
puts "#{h.name}: #{ratio}"
|
41
|
+
}
|
42
|
+
puts ''
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
@@ -43,6 +43,12 @@ class Chef::Knife::VsphereDatastoreList < Chef::Knife::BaseVsphereCommand
|
|
43
43
|
|
44
44
|
get_common_options
|
45
45
|
|
46
|
+
option :list,
|
47
|
+
:long => "--list",
|
48
|
+
:short => "-L",
|
49
|
+
:description => "Indicates whether to list VM's in datastore",
|
50
|
+
:boolean => true
|
51
|
+
|
46
52
|
def run
|
47
53
|
$stdout.sync = true
|
48
54
|
|
@@ -52,6 +58,14 @@ class Chef::Knife::VsphereDatastoreList < Chef::Knife::BaseVsphereCommand
|
|
52
58
|
avail = number_to_human_size(store.summary[:freeSpace])
|
53
59
|
cap = number_to_human_size(store.summary[:capacity])
|
54
60
|
puts "#{ui.color("Datastore", :cyan)}: #{store.name} (#{avail} / #{cap})"
|
61
|
+
if get_config(:list)
|
62
|
+
store.vm.each do |vms|
|
63
|
+
hostName = vms.guest[:hostName]
|
64
|
+
guestFullName = vms.guest[:guestFullName]
|
65
|
+
guestState = vms.guest[:guestState]
|
66
|
+
puts "#{ui.color("VM Name:", :green)} #{hostName} #{ui.color("OS:", :magenta)} #{guestFullName} #{ui.color("State:", :cyan)} #{guestState}"
|
67
|
+
end
|
68
|
+
end
|
55
69
|
end
|
56
70
|
end
|
57
71
|
end
|
@@ -35,6 +35,24 @@ def number_to_human_size(number)
|
|
35
35
|
return sprintf("%0.2f %s", number, unit)
|
36
36
|
end
|
37
37
|
|
38
|
+
def traverse_folders_for_dsclusters(folder)
|
39
|
+
print_dsclusters_in_folder(folder)
|
40
|
+
folder.childEntity.each do |child|
|
41
|
+
if child.class.to_s == 'Folder'
|
42
|
+
traverse_folders_for_dsclusters(child)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def print_dsclusters_in_folder (folder)
|
48
|
+
folder.childEntity.each do |child|
|
49
|
+
if child.class.to_s == "StoragePod"
|
50
|
+
avail = number_to_human_size(child.summary[:freeSpace])
|
51
|
+
cap = number_to_human_size(child.summary[:capacity])
|
52
|
+
puts "#{ui.color("DatastoreCluster", :cyan)}: #{child.name} (#{avail} / #{cap})"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
38
56
|
|
39
57
|
# Lists all known data store cluster in datacenter with sizes
|
40
58
|
class Chef::Knife::VsphereDatastoreclusterList < Chef::Knife::BaseVsphereCommand
|
@@ -45,16 +63,9 @@ class Chef::Knife::VsphereDatastoreclusterList < Chef::Knife::BaseVsphereCommand
|
|
45
63
|
|
46
64
|
def run
|
47
65
|
$stdout.sync = true
|
48
|
-
|
49
66
|
vim = get_vim_connection
|
50
67
|
dc = get_datacenter
|
51
|
-
dc.datastoreFolder
|
52
|
-
if store.class.to_s == "StoragePod"
|
53
|
-
avail = number_to_human_size(store.summary[:freeSpace])
|
54
|
-
cap = number_to_human_size(store.summary[:capacity])
|
55
|
-
puts "#{ui.color("DatastoreCluster", :cyan)}: #{store.name} (#{avail} / #{cap})"
|
56
|
-
end
|
57
|
-
end
|
68
|
+
traverse_folders_for_dsclusters(dc.datastoreFolder)
|
58
69
|
end
|
59
70
|
end
|
60
71
|
|
@@ -18,6 +18,36 @@
|
|
18
18
|
require 'chef/knife'
|
19
19
|
require 'chef/knife/base_vsphere_command'
|
20
20
|
|
21
|
+
def is_max_dscluster(dscluster, max_dscluster)
|
22
|
+
if ! max_dscluster
|
23
|
+
return true
|
24
|
+
end
|
25
|
+
|
26
|
+
if dscluster.summary[:freeSpace] > max_dscluster.summary[:freeSpace]
|
27
|
+
return true
|
28
|
+
end
|
29
|
+
|
30
|
+
return false
|
31
|
+
end
|
32
|
+
|
33
|
+
def find_max_dscluster(folder, max_dscluster, regex)
|
34
|
+
folder.childEntity.each do |child|
|
35
|
+
if child.class.to_s == 'Folder'
|
36
|
+
sub_max = find_max_dscluster(child, max_dscluster, regex)
|
37
|
+
if is_max_dscluster(sub_max, max_dscluster)
|
38
|
+
max_dscluster = sub_max
|
39
|
+
end
|
40
|
+
elsif child.class.to_s == 'StoragePod'
|
41
|
+
|
42
|
+
if is_max_dscluster(child, max_dscluster) && regex.match(child.name)
|
43
|
+
max_dscluster = child
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
return max_dscluster
|
49
|
+
end
|
50
|
+
|
21
51
|
# Gets the data store cluster with the most free space in datacenter
|
22
52
|
class Chef::Knife::VsphereDatastoreclusterMaxfree < Chef::Knife::BaseVsphereCommand
|
23
53
|
|
@@ -35,14 +65,20 @@ class Chef::Knife::VsphereDatastoreclusterMaxfree < Chef::Knife::BaseVsphereComm
|
|
35
65
|
|
36
66
|
vim = get_vim_connection
|
37
67
|
dcname = get_config(:vsphere_dc)
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
68
|
+
regex = /#{Regexp.escape( get_config(:regex))}/
|
69
|
+
max_dscluster = nil
|
70
|
+
|
71
|
+
vim = get_vim_connection
|
72
|
+
dc = get_datacenter
|
73
|
+
|
74
|
+
max_dscluster = find_max_dscluster(dc.datastoreFolder, max_dscluster, regex)
|
75
|
+
|
76
|
+
if max_dscluster
|
77
|
+
puts max_dscluster.name
|
78
|
+
else
|
79
|
+
puts "No datastore clusters found"
|
80
|
+
exit 1
|
45
81
|
end
|
46
|
-
|
82
|
+
|
47
83
|
end
|
48
84
|
end
|
@@ -0,0 +1,30 @@
|
|
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 vm folders
|
9
|
+
class Chef::Knife::VsphereFolderList < Chef::Knife::BaseVsphereCommand
|
10
|
+
|
11
|
+
banner "knife vsphere folder list"
|
12
|
+
|
13
|
+
get_common_options
|
14
|
+
|
15
|
+
def traverse_folders(folder, indent_level)
|
16
|
+
|
17
|
+
puts "#{" " * indent_level} #{ui.color("Folder", :cyan)}: " + folder.name
|
18
|
+
|
19
|
+
folders = find_all_in_folder(folder, RbVmomi::VIM::Folder)
|
20
|
+
folders.each do |child|
|
21
|
+
traverse_folders(child, indent_level + 1)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def run
|
26
|
+
vim = get_vim_connection
|
27
|
+
baseFolder = find_folder(get_config(:folder));
|
28
|
+
traverse_folders(baseFolder, 0)
|
29
|
+
end
|
30
|
+
end
|
@@ -2,6 +2,7 @@ require 'chef/knife'
|
|
2
2
|
require 'chef/knife/base_vsphere_command'
|
3
3
|
require 'rbvmomi'
|
4
4
|
require 'netaddr'
|
5
|
+
|
5
6
|
#list hosts belonging to pool
|
6
7
|
class Chef::Knife::VsphereHostsList < Chef::Knife::BaseVsphereCommand
|
7
8
|
banner "knife vsphere hosts list"
|
@@ -12,38 +13,30 @@ class Chef::Knife::VsphereHostsList < Chef::Knife::BaseVsphereCommand
|
|
12
13
|
:short => "-h",
|
13
14
|
:description => "Target pool"
|
14
15
|
|
15
|
-
def
|
16
|
-
|
17
|
-
|
18
|
-
if child.class == RbVmomi::VIM::ClusterComputeResource || child.class == RbVmomi::VIM::ComputeResource || child.class == RbVmomi::VIM::ResourcePool
|
19
|
-
if child.name == poolname then return child end
|
20
|
-
elsif child.class == RbVmomi::VIM::Folder
|
21
|
-
pool = traverse_folders_for_pool(child, poolname)
|
22
|
-
if pool then return pool end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
return false
|
16
|
+
def find_pools(folder, poolname = nil)
|
17
|
+
pools = folder.children.find_all.select {|p| p.is_a?(RbVmomi::VIM::ComputeResource) || p.is_a?(RbVmomi::VIM::ResourcePool) }
|
18
|
+
poolname.nil? ? pools : pools.select {|p| p.name == poolname }
|
26
19
|
end
|
27
20
|
|
28
21
|
def run
|
29
|
-
poolname = config[:pool]
|
30
|
-
if poolname.nil?
|
31
|
-
show_usage
|
32
|
-
fatal_exit("You must specify a resource pool or cluster name (see knife vsphere pool list)")
|
33
|
-
end
|
34
|
-
|
35
|
-
|
36
22
|
vim = get_vim_connection
|
37
23
|
dc = get_datacenter
|
38
24
|
folder = dc.hostFolder
|
39
25
|
|
40
|
-
|
26
|
+
target_pool = config[:pool]
|
41
27
|
|
42
|
-
|
43
|
-
|
28
|
+
pools = find_pools(folder, target_pool)
|
29
|
+
if target_pool && pools.empty?
|
30
|
+
puts "Pool #{target_pool} not found"
|
31
|
+
return
|
32
|
+
end
|
33
|
+
|
34
|
+
pools.each do |pool|
|
35
|
+
puts "#{ui.color("Pool", :cyan)}: #{pool.name}"
|
36
|
+
hosts = pool.host || []
|
44
37
|
hosts.each do |hostc|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
end
|
38
|
+
puts " #{ui.color("Host", :cyan)}: #{hostc.name}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
49
42
|
end
|
@@ -13,13 +13,16 @@ class Chef::Knife::VspherePoolList < Chef::Knife::BaseVsphereCommand
|
|
13
13
|
get_common_options
|
14
14
|
|
15
15
|
def traverse_folders(folder)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
16
|
+
return if folder.is_a? RbVmomi::VIM::VirtualApp
|
17
|
+
|
18
|
+
puts "#{ui.color("Pool", :cyan)}: "+(folder.path[3..-1].map { |x| x[1] }.* '/') if
|
19
|
+
folder.is_a? RbVmomi::VIM::ResourcePool
|
20
|
+
|
21
|
+
folders = find_all_in_folder(folder, RbVmomi::VIM::ManagedObject) || []
|
22
|
+
folders.each do |child|
|
23
|
+
traverse_folders(child)
|
22
24
|
end
|
25
|
+
|
23
26
|
end
|
24
27
|
|
25
28
|
def find_pool_folder(folderName)
|
@@ -36,7 +39,6 @@ class Chef::Knife::VspherePoolList < Chef::Knife::BaseVsphereCommand
|
|
36
39
|
end
|
37
40
|
|
38
41
|
def run
|
39
|
-
$stdout.sync = true
|
40
42
|
vim = get_vim_connection
|
41
43
|
baseFolder = find_pool_folder(get_config(:folder));
|
42
44
|
traverse_folders(baseFolder)
|
@@ -47,6 +47,16 @@ class Chef::Knife::VsphereVmClone < Chef::Knife::BaseVsphereCommand
|
|
47
47
|
:description => "Indicates whether to use linked clones.",
|
48
48
|
:boolean => false
|
49
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
|
+
|
50
60
|
option :annotation,
|
51
61
|
:long => "--annotation TEXT",
|
52
62
|
:description => "Add TEXT in Notes field from annotation"
|
@@ -113,6 +123,10 @@ class Chef::Knife::VsphereVmClone < Chef::Knife::BaseVsphereCommand
|
|
113
123
|
:description => "Indicates whether to bootstrap the VM",
|
114
124
|
:boolean => false
|
115
125
|
|
126
|
+
option :environment,
|
127
|
+
:long => "--environment ENVIRONMENT",
|
128
|
+
:description => "Environment to add the node to for bootstrapping"
|
129
|
+
|
116
130
|
option :fqdn,
|
117
131
|
:long => "--fqdn SERVER_FQDN",
|
118
132
|
:description => "Fully qualified hostname for bootstrapping"
|
@@ -233,6 +247,7 @@ class Chef::Knife::VsphereVmClone < Chef::Knife::BaseVsphereCommand
|
|
233
247
|
end
|
234
248
|
|
235
249
|
vim = get_vim_connection
|
250
|
+
vdm = vim.serviceContent.virtualDiskManager
|
236
251
|
|
237
252
|
dc = get_datacenter
|
238
253
|
|
@@ -310,7 +325,6 @@ class Chef::Knife::VsphereVmClone < Chef::Knife::BaseVsphereCommand
|
|
310
325
|
|
311
326
|
# Builds a CloneSpec
|
312
327
|
def generate_clone_spec (src_config)
|
313
|
-
|
314
328
|
rspec = nil
|
315
329
|
if get_config(:resource_pool)
|
316
330
|
rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => find_pool(get_config(:resource_pool)))
|
@@ -343,15 +357,13 @@ class Chef::Knife::VsphereVmClone < Chef::Knife::BaseVsphereCommand
|
|
343
357
|
end
|
344
358
|
end
|
345
359
|
|
346
|
-
if get_config(:
|
347
|
-
|
348
|
-
:powerOn => false,
|
349
|
-
:template => true)
|
350
|
-
else
|
351
|
-
clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(:location => rspec,
|
352
|
-
:powerOn => false,
|
353
|
-
:template => false)
|
360
|
+
if get_config(:thin_provision)
|
361
|
+
rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:transform => :sparse, :pool => find_pool(get_config(:resource_pool)))
|
354
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
|
+
|
355
367
|
clone_spec.config = RbVmomi::VIM.VirtualMachineConfigSpec(:deviceChange => Array.new)
|
356
368
|
|
357
369
|
if get_config(:annotation)
|
@@ -375,7 +387,7 @@ class Chef::Knife::VsphereVmClone < Chef::Knife::BaseVsphereCommand
|
|
375
387
|
card.backing.port = switch_port
|
376
388
|
rescue
|
377
389
|
# not connected to a distibuted switch?
|
378
|
-
card.backing
|
390
|
+
card.backing = RbVmomi::VIM::VirtualEthernetCardNetworkBackingInfo(:network => network, :deviceName => network.name)
|
379
391
|
end
|
380
392
|
dev_spec = RbVmomi::VIM.VirtualDeviceConfigSpec(:device => card, :operation => "edit")
|
381
393
|
clone_spec.config.deviceChange.push dev_spec
|
@@ -417,24 +429,21 @@ class Chef::Knife::VsphereVmClone < Chef::Knife::BaseVsphereCommand
|
|
417
429
|
config[:vmname]
|
418
430
|
end
|
419
431
|
|
420
|
-
if src_config.guestId.downcase.include?("
|
432
|
+
if src_config.guestId.downcase.include?("windows")
|
433
|
+
if cust_spec.identity.nil?
|
434
|
+
fatal_exit("Please provide Windows Guest Customization")
|
435
|
+
else
|
436
|
+
cust_spec.identity.userData.computerName = RbVmomi::VIM.CustomizationFixedName(:name => hostname)
|
437
|
+
end
|
438
|
+
else
|
421
439
|
ident = RbVmomi::VIM.CustomizationLinuxPrep
|
422
|
-
|
423
440
|
ident.hostName = RbVmomi::VIM.CustomizationFixedName(:name => hostname)
|
424
|
-
|
425
441
|
if get_config(:customization_domain)
|
426
442
|
ident.domain = get_config(:customization_domain)
|
427
443
|
else
|
428
444
|
ident.domain = ''
|
429
445
|
end
|
430
|
-
|
431
446
|
cust_spec.identity = ident
|
432
|
-
elsif src_config.guestId.downcase.include?("windows")
|
433
|
-
if cust_spec.identity.nil?
|
434
|
-
fatal_exit("Please provide Windows Guest Customization")
|
435
|
-
else
|
436
|
-
cust_spec.identity.userData.computerName = RbVmomi::VIM.CustomizationFixedName(:name => hostname)
|
437
|
-
end
|
438
447
|
end
|
439
448
|
end
|
440
449
|
|
@@ -1,66 +1,66 @@
|
|
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
|
-
require 'rbvmomi'
|
9
|
-
|
10
|
-
# These two are needed for the '--purge' deletion case
|
11
|
-
require 'chef/node'
|
12
|
-
require 'chef/api_client'
|
13
|
-
|
14
|
-
# Delete a virtual machine from vCenter
|
15
|
-
class Chef::Knife::VsphereVmDelete < Chef::Knife::BaseVsphereCommand
|
16
|
-
|
17
|
-
banner "knife vsphere vm delete VMNAME"
|
18
|
-
|
19
|
-
option :purge,
|
20
|
-
:short => "-P",
|
21
|
-
:long => "--purge",
|
22
|
-
:boolean => true,
|
23
|
-
:description => "Destroy corresponding node and client on the Chef Server, in addition to destroying the VM itself."
|
24
|
-
|
25
|
-
get_common_options
|
26
|
-
|
27
|
-
# Extracted from Chef::Knife.delete_object, because it has a
|
28
|
-
# confirmation step built in... By specifying the '--purge'
|
29
|
-
# flag (and also explicitly confirming the server destruction!)
|
30
|
-
# the user is already making their intent known. It is not
|
31
|
-
# necessary to make them confirm two more times.
|
32
|
-
def destroy_item(itemClass, name, type_name)
|
33
|
-
object = itemClass.load(name)
|
34
|
-
object.destroy
|
35
|
-
puts "Deleted #{type_name} #{name}"
|
36
|
-
end
|
37
|
-
|
38
|
-
def run
|
39
|
-
$stdout.sync = true
|
40
|
-
|
41
|
-
vmname = @name_args[0]
|
42
|
-
|
43
|
-
if vmname.nil?
|
44
|
-
show_usage
|
45
|
-
fatal_exit("You must specify a virtual machine name")
|
46
|
-
end
|
47
|
-
|
48
|
-
vim = get_vim_connection
|
49
|
-
|
50
|
-
baseFolder = find_folder(get_config(:folder));
|
51
|
-
|
52
|
-
vm =
|
53
|
-
fatal_exit("VM #{vmname} not found")
|
54
|
-
|
55
|
-
vm.PowerOffVM_Task.wait_for_completion unless vm.runtime.powerState == "poweredOff"
|
56
|
-
vm.Destroy_Task
|
57
|
-
puts "Deleted virtual machine #{vmname}"
|
58
|
-
|
59
|
-
if config[:purge]
|
60
|
-
destroy_item(Chef::Node, vmname, "node")
|
61
|
-
destroy_item(Chef::ApiClient, vmname, "client")
|
62
|
-
else
|
63
|
-
puts "Corresponding node and client for the #{vmname} server were not deleted and remain registered with the Chef Server"
|
64
|
-
end
|
65
|
-
end
|
66
|
-
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
|
+
require 'rbvmomi'
|
9
|
+
|
10
|
+
# These two are needed for the '--purge' deletion case
|
11
|
+
require 'chef/node'
|
12
|
+
require 'chef/api_client'
|
13
|
+
|
14
|
+
# Delete a virtual machine from vCenter
|
15
|
+
class Chef::Knife::VsphereVmDelete < Chef::Knife::BaseVsphereCommand
|
16
|
+
|
17
|
+
banner "knife vsphere vm delete VMNAME"
|
18
|
+
|
19
|
+
option :purge,
|
20
|
+
:short => "-P",
|
21
|
+
:long => "--purge",
|
22
|
+
:boolean => true,
|
23
|
+
:description => "Destroy corresponding node and client on the Chef Server, in addition to destroying the VM itself."
|
24
|
+
|
25
|
+
get_common_options
|
26
|
+
|
27
|
+
# Extracted from Chef::Knife.delete_object, because it has a
|
28
|
+
# confirmation step built in... By specifying the '--purge'
|
29
|
+
# flag (and also explicitly confirming the server destruction!)
|
30
|
+
# the user is already making their intent known. It is not
|
31
|
+
# necessary to make them confirm two more times.
|
32
|
+
def destroy_item(itemClass, name, type_name)
|
33
|
+
object = itemClass.load(name)
|
34
|
+
object.destroy
|
35
|
+
puts "Deleted #{type_name} #{name}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def run
|
39
|
+
$stdout.sync = true
|
40
|
+
|
41
|
+
vmname = @name_args[0]
|
42
|
+
|
43
|
+
if vmname.nil?
|
44
|
+
show_usage
|
45
|
+
fatal_exit("You must specify a virtual machine name")
|
46
|
+
end
|
47
|
+
|
48
|
+
vim = get_vim_connection
|
49
|
+
|
50
|
+
baseFolder = find_folder(get_config(:folder));
|
51
|
+
|
52
|
+
vm = traverse_folders_for_vm(baseFolder, vmname) or
|
53
|
+
fatal_exit("VM #{vmname} not found")
|
54
|
+
|
55
|
+
vm.PowerOffVM_Task.wait_for_completion unless vm.runtime.powerState == "poweredOff"
|
56
|
+
vm.Destroy_Task
|
57
|
+
puts "Deleted virtual machine #{vmname}"
|
58
|
+
|
59
|
+
if config[:purge]
|
60
|
+
destroy_item(Chef::Node, vmname, "node")
|
61
|
+
destroy_item(Chef::ApiClient, vmname, "client")
|
62
|
+
else
|
63
|
+
puts "Corresponding node and client for the #{vmname} server were not deleted and remain registered with the Chef Server"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -15,52 +15,47 @@ class Chef::Knife::VsphereVmList < Chef::Knife::BaseVsphereCommand
|
|
15
15
|
option :recursive,
|
16
16
|
:long => "--recursive",
|
17
17
|
:short => "-r",
|
18
|
-
:description => "Recurse
|
18
|
+
:description => "Recurse into sub-folders"
|
19
19
|
|
20
|
-
|
21
|
-
:long => "--only-folders",
|
22
|
-
:description => "Print only sub-folders"
|
20
|
+
def traverse_folders(folder, is_top = false, recurse = false)
|
23
21
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
22
|
+
vms = find_all_in_folder(folder, RbVmomi::VIM::VirtualMachine).select {|v| v.config && !v.config.template }
|
23
|
+
if vms.any?
|
24
|
+
puts "#{ui.color("Folder", :cyan)}: "+(folder.path[3..-1].map { |x| x[1] }.* '/')
|
25
|
+
vms.each { |v| print_vm(v) }
|
26
|
+
elsif is_top
|
27
|
+
puts "#{ui.color("No VMs", :cyan)}"
|
30
28
|
end
|
31
|
-
end
|
32
29
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
ui.color("on", :green)
|
39
|
-
when PsOff
|
40
|
-
ui.color("off", :red)
|
41
|
-
when PsSuspended
|
42
|
-
ui.color("suspended", :yellow)
|
43
|
-
end
|
44
|
-
puts "#{ui.color("VM Name:", :cyan)} #{vm.name}\t#{ui.color("IP:", :magenta)} #{vm.guest.ipAddress}\t#{ui.color("RAM:", :magenta)} #{vm.summary.config.memorySizeMB}\t#{ui.color("State:", :cyan)} #{state}"
|
30
|
+
if (recurse)
|
31
|
+
folders = find_all_in_folder(folder, RbVmomi::VIM::Folder)
|
32
|
+
folders.each do |child|
|
33
|
+
traverse_folders(child, false, recurse)
|
34
|
+
end
|
45
35
|
end
|
36
|
+
|
46
37
|
end
|
47
38
|
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
39
|
+
def print_vm(vm)
|
40
|
+
state = case vm.runtime.powerState
|
41
|
+
when PsOn
|
42
|
+
ui.color("on", :green)
|
43
|
+
when PsOff
|
44
|
+
ui.color("off", :red)
|
45
|
+
when PsSuspended
|
46
|
+
ui.color("suspended", :yellow)
|
47
|
+
end
|
48
|
+
puts "\t#{ui.color("VM Name:", :cyan)} #{vm.name}"
|
49
|
+
"\t\t#{ui.color("IP:", :magenta)} #{vm.guest.ipAddress}"
|
50
|
+
"\t\t#{ui.color("RAM:", :magenta)} #{vm.summary.config.memorySizeMB}"
|
51
|
+
"\t\t#{ui.color("State:", :cyan)} #{state}"
|
53
52
|
end
|
54
53
|
|
55
54
|
def run
|
56
|
-
$stdout.sync = true
|
57
55
|
vim = get_vim_connection
|
58
56
|
baseFolder = find_folder(get_config(:folder));
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
print_subfolders(baseFolder)
|
63
|
-
print_vms_in_folder(baseFolder)
|
64
|
-
end
|
57
|
+
recurse = get_config(:recursive)
|
58
|
+
is_top = true
|
59
|
+
traverse_folders(baseFolder, is_top, recurse)
|
65
60
|
end
|
66
61
|
end
|
@@ -106,6 +106,7 @@ class Chef::Knife::VsphereVmSnapshot < Chef::Knife::BaseVsphereCommand
|
|
106
106
|
tree.each do |node|
|
107
107
|
if node.name == name
|
108
108
|
snapshot = node.snapshot
|
109
|
+
break
|
109
110
|
elsif !node.childSnapshotList.empty?
|
110
111
|
snapshot = find_node(node.childSnapshotList, name)
|
111
112
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# Author:: Malte Heidenreich
|
2
|
+
# License:: Apache License, Version 2.0
|
3
|
+
|
4
|
+
require 'chef/knife'
|
5
|
+
require 'chef/knife/base_vsphere_command'
|
6
|
+
require 'rbvmomi'
|
7
|
+
require 'netaddr'
|
8
|
+
|
9
|
+
class Chef::Knife::VsphereVmToolsconfig < Chef::Knife::BaseVsphereCommand
|
10
|
+
banner "knife vsphere vm toolsconfig PROPERTY VALUE. See \"https://www.vmware.com/support/developer/vc-sdk/visdk25pubs/ReferenceGuide/vim.vm.ToolsConfigInfo.html\" for available properties and types."
|
11
|
+
|
12
|
+
option :empty,
|
13
|
+
:short => "-e",
|
14
|
+
:long => "--empty",
|
15
|
+
:description => "Allow empty string"
|
16
|
+
get_common_options
|
17
|
+
|
18
|
+
def run
|
19
|
+
$stdout.sync = true
|
20
|
+
vmname = @name_args[0]
|
21
|
+
if vmname.nil?
|
22
|
+
show_usage
|
23
|
+
fatal_exit("You must specify a virtual machine name")
|
24
|
+
end
|
25
|
+
|
26
|
+
property = @name_args[1]
|
27
|
+
if property.nil?
|
28
|
+
show_usage
|
29
|
+
fatal_exit("You must specify a property to modify")
|
30
|
+
end
|
31
|
+
|
32
|
+
value = @name_args[2]
|
33
|
+
if value.nil? && !get_config(:empty)
|
34
|
+
show_usage
|
35
|
+
fatal_exit("You must specify a value")
|
36
|
+
end
|
37
|
+
|
38
|
+
value = "" if get_config(:empty)
|
39
|
+
|
40
|
+
vim = get_vim_connection
|
41
|
+
|
42
|
+
dc = get_datacenter
|
43
|
+
folder = find_folder(get_config(:folder)) || dc.vmFolder
|
44
|
+
|
45
|
+
vm = traverse_folders_for_vm(folder, vmname) or abort "VM #{vmname} not found"
|
46
|
+
|
47
|
+
vmConfigSpec = RbVmomi::VIM.VirtualMachineConfigSpec(:tools => RbVmomi::VIM.ToolsConfigInfo(property => value))
|
48
|
+
vm.ReconfigVM_Task(:spec => vmConfigSpec)
|
49
|
+
|
50
|
+
puts "property #{property} updated successfully"
|
51
|
+
end
|
52
|
+
end
|
metadata
CHANGED
@@ -1,53 +1,60 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: knife-vsphere
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
5
6
|
platform: ruby
|
6
7
|
authors:
|
7
8
|
- Ezra Pagel
|
8
|
-
autorequire:
|
9
|
+
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2014-
|
12
|
+
date: 2014-12-30 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
15
|
+
name: netaddr
|
14
16
|
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
15
18
|
requirements:
|
16
19
|
- - ~>
|
17
20
|
- !ruby/object:Gem::Version
|
18
21
|
version: 1.5.0
|
19
|
-
name: netaddr
|
20
|
-
prerelease: false
|
21
22
|
type: :runtime
|
23
|
+
prerelease: false
|
22
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
23
26
|
requirements:
|
24
27
|
- - ~>
|
25
28
|
- !ruby/object:Gem::Version
|
26
29
|
version: 1.5.0
|
27
30
|
- !ruby/object:Gem::Dependency
|
31
|
+
name: chef
|
28
32
|
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
29
34
|
requirements:
|
30
|
-
- - '>='
|
35
|
+
- - ! '>='
|
31
36
|
- !ruby/object:Gem::Version
|
32
37
|
version: 0.10.0
|
33
|
-
name: chef
|
34
|
-
prerelease: false
|
35
38
|
type: :runtime
|
39
|
+
prerelease: false
|
36
40
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
37
42
|
requirements:
|
38
|
-
- - '>='
|
43
|
+
- - ! '>='
|
39
44
|
- !ruby/object:Gem::Version
|
40
45
|
version: 0.10.0
|
41
46
|
- !ruby/object:Gem::Dependency
|
47
|
+
name: rbvmomi
|
42
48
|
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
43
50
|
requirements:
|
44
51
|
- - ~>
|
45
52
|
- !ruby/object:Gem::Version
|
46
53
|
version: 1.5.1
|
47
|
-
name: rbvmomi
|
48
|
-
prerelease: false
|
49
54
|
type: :runtime
|
55
|
+
prerelease: false
|
50
56
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
51
58
|
requirements:
|
52
59
|
- - ~>
|
53
60
|
- !ruby/object:Gem::Version
|
@@ -58,55 +65,59 @@ executables: []
|
|
58
65
|
extensions: []
|
59
66
|
extra_rdoc_files: []
|
60
67
|
files:
|
61
|
-
- lib/chef/knife/
|
62
|
-
- lib/chef/knife/
|
63
|
-
- lib/chef/knife/vshpere_vm_move.rb
|
64
|
-
- lib/chef/knife/vshpere_vm_net.rb
|
65
|
-
- lib/chef/knife/vsphere_customization_list.rb
|
68
|
+
- lib/chef/knife/vsphere_vm_query.rb
|
69
|
+
- lib/chef/knife/vsphere_cpu_ratio.rb
|
66
70
|
- lib/chef/knife/vsphere_datastorecluster_list.rb
|
67
71
|
- lib/chef/knife/vsphere_datastorecluster_maxfree.rb
|
68
|
-
- lib/chef/knife/
|
69
|
-
- lib/chef/knife/
|
72
|
+
- lib/chef/knife/vshpere_vm_migrate.rb
|
73
|
+
- lib/chef/knife/vsphere_vm_property_get.rb
|
74
|
+
- lib/chef/knife/vsphere_vm_state.rb
|
75
|
+
- lib/chef/knife/vshpere_vm_net.rb
|
76
|
+
- lib/chef/knife/vsphere_vm_execute.rb
|
70
77
|
- lib/chef/knife/vsphere_hosts_list.rb
|
71
|
-
- lib/chef/knife/
|
72
|
-
- lib/chef/knife/
|
73
|
-
- lib/chef/knife/
|
74
|
-
- lib/chef/knife/vsphere_vlan_list.rb
|
78
|
+
- lib/chef/knife/vsphere_vm_snapshot.rb
|
79
|
+
- lib/chef/knife/vsphere_vm_toolsconfig.rb
|
80
|
+
- lib/chef/knife/vsphere_vm_markastemplate.rb
|
75
81
|
- lib/chef/knife/vsphere_vm_clone.rb
|
76
|
-
- lib/chef/knife/
|
82
|
+
- lib/chef/knife/base_vsphere_command.rb
|
83
|
+
- lib/chef/knife/vsphere_vm_vmdk_add.rb
|
84
|
+
- lib/chef/knife/vsphere_customization_list.rb
|
85
|
+
- lib/chef/knife/vsphere_template_list.rb
|
77
86
|
- lib/chef/knife/vsphere_vm_delete.rb
|
78
|
-
- lib/chef/knife/
|
87
|
+
- lib/chef/knife/vsphere_datastore_list.rb
|
88
|
+
- lib/chef/knife/vsphere_vm_config.rb
|
89
|
+
- lib/chef/knife/vsphere_vlan_list.rb
|
90
|
+
- lib/chef/knife/vsphere_datastore_maxfree.rb
|
79
91
|
- lib/chef/knife/vsphere_vm_list.rb
|
80
|
-
- lib/chef/knife/
|
81
|
-
- lib/chef/knife/
|
92
|
+
- lib/chef/knife/vsphere_pool_query.rb
|
93
|
+
- lib/chef/knife/vshpere_vm_move.rb
|
94
|
+
- lib/chef/knife/vsphere_pool_list.rb
|
95
|
+
- lib/chef/knife/vsphere_folder_list.rb
|
82
96
|
- lib/chef/knife/vsphere_vm_property_set.rb
|
83
|
-
- lib/chef/knife/vsphere_vm_query.rb
|
84
|
-
- lib/chef/knife/vsphere_vm_snapshot.rb
|
85
|
-
- lib/chef/knife/vsphere_vm_state.rb
|
86
|
-
- lib/chef/knife/vsphere_vm_vmdk_add.rb
|
87
97
|
- lib/knife-vsphere/version.rb
|
88
98
|
homepage: http://github.com/ezrapagel/knife-vsphere
|
89
99
|
licenses:
|
90
100
|
- Apache
|
91
|
-
|
92
|
-
post_install_message:
|
101
|
+
post_install_message:
|
93
102
|
rdoc_options: []
|
94
103
|
require_paths:
|
95
104
|
- lib
|
96
105
|
required_ruby_version: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
97
107
|
requirements:
|
98
|
-
- - '>='
|
108
|
+
- - ! '>='
|
99
109
|
- !ruby/object:Gem::Version
|
100
110
|
version: '0'
|
101
111
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
none: false
|
102
113
|
requirements:
|
103
|
-
- - '>='
|
114
|
+
- - ! '>='
|
104
115
|
- !ruby/object:Gem::Version
|
105
116
|
version: '0'
|
106
117
|
requirements: []
|
107
|
-
rubyforge_project:
|
108
|
-
rubygems_version:
|
109
|
-
signing_key:
|
110
|
-
specification_version:
|
118
|
+
rubyforge_project:
|
119
|
+
rubygems_version: 1.8.23
|
120
|
+
signing_key:
|
121
|
+
specification_version: 3
|
111
122
|
summary: vSphere Support for Knife
|
112
123
|
test_files: []
|
checksums.yaml
DELETED
@@ -1,7 +0,0 @@
|
|
1
|
-
---
|
2
|
-
SHA1:
|
3
|
-
metadata.gz: ca44c31e612974c4085c95cebd7ce9290782a76d
|
4
|
-
data.tar.gz: 03d2eae019a9a4d898e5a9bc94391aa640d28de2
|
5
|
-
SHA512:
|
6
|
-
metadata.gz: f6d164cc5012c7eee2e351f7c241bc1fbd24f47b468fedb6f84e84ce9a7135c30481179eb46c9d2877a8c39dc4a667bc246ec9f8d2bc781677d61c3fa091a287
|
7
|
-
data.tar.gz: 03eeac904d852e5394f1a6d573fae6f664a202e72805a8a87a883d71f112d2677fc6f568b898237f1c2ab07bad3ee46f7a497521b9efd7519078a825633a6de2
|