hammer_cli_csv 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +15 -0
  2. data/lib/hammer_cli_csv.rb +48 -0
  3. data/lib/hammer_cli_csv/activation_keys.rb +170 -0
  4. data/lib/hammer_cli_csv/architectures.rb +95 -0
  5. data/lib/hammer_cli_csv/base.rb +706 -0
  6. data/lib/hammer_cli_csv/compute_profiles.rb +94 -0
  7. data/lib/hammer_cli_csv/compute_resources.rb +92 -0
  8. data/lib/hammer_cli_csv/content_hosts.rb +357 -0
  9. data/lib/hammer_cli_csv/content_view_filters.rb +158 -0
  10. data/lib/hammer_cli_csv/content_views.rb +86 -0
  11. data/lib/hammer_cli_csv/csv.rb +23 -0
  12. data/lib/hammer_cli_csv/domains.rb +103 -0
  13. data/lib/hammer_cli_csv/exception_handler.rb +53 -0
  14. data/lib/hammer_cli_csv/host_collections.rb +108 -0
  15. data/lib/hammer_cli_csv/hosts.rb +118 -0
  16. data/lib/hammer_cli_csv/import.rb +82 -0
  17. data/lib/hammer_cli_csv/installation_medias.rb +88 -0
  18. data/lib/hammer_cli_csv/lifecycle_environments.rb +116 -0
  19. data/lib/hammer_cli_csv/locations.rb +84 -0
  20. data/lib/hammer_cli_csv/operating_systems.rb +95 -0
  21. data/lib/hammer_cli_csv/organizations.rb +107 -0
  22. data/lib/hammer_cli_csv/partition_tables.rb +98 -0
  23. data/lib/hammer_cli_csv/products.rb +179 -0
  24. data/lib/hammer_cli_csv/provisioning_templates.rb +96 -0
  25. data/lib/hammer_cli_csv/puppet_environments.rb +105 -0
  26. data/lib/hammer_cli_csv/puppet_facts.rb +99 -0
  27. data/lib/hammer_cli_csv/puppet_reports.rb +244 -0
  28. data/lib/hammer_cli_csv/reports.rb +91 -0
  29. data/lib/hammer_cli_csv/roles.rb +115 -0
  30. data/lib/hammer_cli_csv/smart_proxies.rb +88 -0
  31. data/lib/hammer_cli_csv/subnets.rb +121 -0
  32. data/lib/hammer_cli_csv/subscriptions.rb +135 -0
  33. data/lib/hammer_cli_csv/users.rb +133 -0
  34. data/lib/hammer_cli_csv/version.rb +16 -0
  35. data/test/activation_keys_test.rb +17 -0
  36. data/test/apipie_resource_mock.rb +77 -0
  37. data/test/config.template.yml +17 -0
  38. data/test/csv_test_helper.rb +65 -0
  39. data/test/data/activation-keys.csv +119 -0
  40. data/test/data/architectures.csv +5 -0
  41. data/test/data/content-hosts.csv +4 -0
  42. data/test/data/content-view-filters.csv +2 -0
  43. data/test/data/content-views.csv +6 -0
  44. data/test/data/domains.csv +8 -0
  45. data/test/data/host-collections.csv +16 -0
  46. data/test/data/hosts.csv +12 -0
  47. data/test/data/installation-medias.csv +7 -0
  48. data/test/data/lifecycle-environments.csv +6 -0
  49. data/test/data/locations.csv +10 -0
  50. data/test/data/operating-systems.csv +16 -0
  51. data/test/data/organizations.csv +5 -0
  52. data/test/data/partition-tables.csv +187 -0
  53. data/test/data/products.csv +19 -0
  54. data/test/data/puppet-environments.csv +6 -0
  55. data/test/data/puppet-facts.csv +9 -0
  56. data/test/data/reports.csv +4 -0
  57. data/test/data/roles.csv +159 -0
  58. data/test/data/smart-proxies.csv +2 -0
  59. data/test/data/subscriptions.csv +19 -0
  60. data/test/data/users.csv +119 -0
  61. data/test/helpers/command.rb +67 -0
  62. data/test/helpers/resource_disabled.rb +69 -0
  63. data/test/hosts_test.rb +47 -0
  64. data/test/organizations_test.rb +74 -0
  65. data/test/roles_test.rb +39 -0
  66. data/test/setup_test.rb +164 -0
  67. data/test/systems_test.rb +71 -0
  68. data/test/users_test.rb +35 -0
  69. metadata +174 -0
@@ -0,0 +1,118 @@
1
+ # Copyright 2013-2014 Red Hat, Inc.
2
+ #
3
+ # This software is licensed to you under the GNU General Public
4
+ # License as published by the Free Software Foundation; either version
5
+ # 2 of the License (GPLv2) or (at your option) any later version.
6
+ # There is NO WARRANTY for this software, express or implied,
7
+ # including the implied warranties of MERCHANTABILITY,
8
+ # NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
9
+ # have received a copy of GPLv2 along with this software; if not, see
10
+ # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
11
+
12
+ #
13
+ # -= Hosts CSV =-
14
+ #
15
+ # Columns
16
+ # Name
17
+ # - Host name
18
+ # - May contain '%d' which will be replaced with current iteration number of Count
19
+ # - eg. "os%d" -> "os1"
20
+ # Count
21
+ # - Number of times to iterate on this line of the CSV file
22
+ # MAC Address
23
+ # - MAC address
24
+ # - May contain '%d' which will be replaced with current iteration number of Count
25
+ # - eg. "FF:FF:FF:FF:FF:%02x" -> "FF:FF:FF:FF:FF:0A"
26
+ # - Warning: be sure to keep count below 255 or MAC hex will exceed limit
27
+ #
28
+
29
+ require 'hammer_cli'
30
+ require 'json'
31
+ require 'csv'
32
+ require 'uri'
33
+
34
+ module HammerCLICsv
35
+ class CsvCommand
36
+ class HostsCommand < BaseCommand
37
+ command_name 'hosts'
38
+ desc 'import or export hosts'
39
+
40
+ ORGANIZATION = 'Organization'
41
+ ENVIRONMENT = 'Environment'
42
+ OPERATINGSYSTEM = 'Operating System'
43
+ ARCHITECTURE = 'Architecture'
44
+ MACADDRESS = 'MAC Address'
45
+ DOMAIN = 'Domain'
46
+ PARTITIONTABLE = 'Partition Table'
47
+
48
+ def export
49
+ CSV.open(option_csv_file || '/dev/stdout', 'wb', {:force_quotes => true}) do |csv|
50
+ csv << [NAME, COUNT, ORGANIZATION, ENVIRONMENT, OPERATINGSYSTEM, ARCHITECTURE, MACADDRESS, DOMAIN, PARTITIONTABLE]
51
+ @api.resource(:hosts).call(:index, {:per_page => 999999})['results'].each do |host|
52
+ host = @api.resource(:hosts).call(:show, {'id' => host['id']})
53
+ raise "Host 'id=#{host['id']}' not found" if !host || host.empty?
54
+
55
+ name = host['name']
56
+ count = 1
57
+ organization = foreman_organization(:id => host['organization_id'])
58
+ environment = foreman_environment(:id => host['environment_id'])
59
+ operatingsystem = foreman_operatingsystem(:id => host['operatingsystem_id'])
60
+ architecture = foreman_architecture(:id => host['architecture_id'])
61
+ mac = host['mac']
62
+ domain = foreman_domain(:id => host['domain_id'])
63
+ ptable = foreman_partitiontable(:id => host['ptable_id'])
64
+
65
+ csv << [name, count, organization, environment, operatingsystem, architecture, mac, domain, ptable]
66
+ end
67
+ end
68
+ end
69
+
70
+ def import
71
+ @existing = {}
72
+ @api.resource(:hosts).call(:index, {:per_page => 999999})['results'].each do |host|
73
+ @existing[host['name']] = host['id'] if host
74
+ end
75
+
76
+ thread_import do |line|
77
+ create_hosts_from_csv(line)
78
+ end
79
+ end
80
+
81
+ def create_hosts_from_csv(line)
82
+ line[COUNT].to_i.times do |number|
83
+ name = namify(line[NAME], number)
84
+ if !@existing.include? name
85
+ print "Creating host '#{name}'..." if option_verbose?
86
+ @api.resource(:hosts).call(:create, {
87
+ 'name' => name,
88
+ 'root_pass' => 'changeme',
89
+ 'mac' => namify(line[MACADDRESS], number),
90
+ 'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
91
+ 'environment_id' => foreman_environment(:name => line[ENVIRONMENT]),
92
+ 'operatingsystem_id' => foreman_operatingsystem(:name => line[OPERATINGSYSTEM]),
93
+ 'architecture_id' => foreman_architecture(:name => line[ARCHITECTURE]),
94
+ 'domain_id' => foreman_domain(:name => line[DOMAIN]),
95
+ 'ptable_id' => foreman_partitiontable(:name => line[PARTITIONTABLE])
96
+ })
97
+ else
98
+ print "Updating host '#{name}'..." if option_verbose?
99
+ @api.resource(:hosts).call(:update, {
100
+ 'id' => @existing[name],
101
+ 'name' => name,
102
+ 'mac' => namify(line[MACADDRESS], number),
103
+ 'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
104
+ 'environment_id' => foreman_environment(:name => line[ENVIRONMENT]),
105
+ 'operatingsystem_id' => foreman_operatingsystem(:name => line[OPERATINGSYSTEM]),
106
+ 'architecture_id' => foreman_architecture(:name => line[ARCHITECTURE]),
107
+ 'domain_id' => foreman_domain(:name => line[DOMAIN]),
108
+ 'ptable_id' => foreman_partitiontable(:name => line[PARTITIONTABLE])
109
+ })
110
+ end
111
+ print "done\n" if option_verbose?
112
+ end
113
+ rescue RuntimeError => e
114
+ raise "#{e}\n #{line}"
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,82 @@
1
+ # Copyright 2013-2014 Red Hat, Inc.
2
+ #
3
+ # This software is licensed to you under the GNU General Public
4
+ # License as published by the Free Software Foundation; either version
5
+ # 2 of the License (GPLv2) or (at your option) any later version.
6
+ # There is NO WARRANTY for this software, express or implied,
7
+ # including the implied warranties of MERCHANTABILITY,
8
+ # NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
9
+ # have received a copy of GPLv2 along with this software; if not, see
10
+ # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
11
+
12
+ require 'hammer_cli'
13
+ require 'hammer_cli_csv'
14
+ require 'hammer_cli_foreman'
15
+ require 'hammer_cli_katello'
16
+ require 'json'
17
+ require 'csv'
18
+ require 'uri'
19
+
20
+ module HammerCLICsv
21
+ class CsvCommand
22
+ class ImportCommand < HammerCLI::Apipie::Command
23
+ command_name 'import'
24
+ desc 'import by directory'
25
+
26
+ option %w(-v --verbose), :flag, 'be verbose'
27
+ option %w(--threads), 'THREAD_COUNT', 'Number of threads to hammer with', :default => 1
28
+ option %w(--server), 'SERVER', 'Server URL'
29
+ option %w(-u --username), 'USERNAME', 'Username to access server'
30
+ option %w(-p --password), 'PASSWORD', 'Password to access server'
31
+ option '--dir', 'DIRECTORY', 'directory to import from'
32
+
33
+ RESOURCES = %w( organizations locations roles users puppet_environments operating_systems
34
+ domains architectures partition_tables lifecycle_environments host_collections
35
+ subscriptions activation_keys hosts content_hosts reports )
36
+ RESOURCES.each do |resource|
37
+ dashed = resource.sub('_', '-')
38
+ option "--#{dashed}", 'FILE', "csv file for #{dashed}"
39
+ end
40
+
41
+ def execute
42
+ @api = ApipieBindings::API.new({
43
+ :uri => option_server || HammerCLI::Settings.get(:csv, :host),
44
+ :username => option_username || HammerCLI::Settings.get(:csv, :username),
45
+ :password => option_password || HammerCLI::Settings.get(:csv, :password),
46
+ :api_version => 2
47
+ })
48
+
49
+ # Swing the hammers
50
+ RESOURCES.each do |resource|
51
+ hammer_resource(resource)
52
+ end
53
+
54
+ HammerCLI::EX_OK
55
+ end
56
+
57
+ def hammer(context = nil)
58
+ context ||= {
59
+ :interactive => false,
60
+ :username => 'admin', # TODO: this needs to come from config/settings
61
+ :password => 'changeme' # TODO: this needs to come from config/settings
62
+ }
63
+
64
+ HammerCLI::MainCommand.new('', context)
65
+ end
66
+
67
+ def hammer_resource(resource)
68
+ return if !self.send("option_#{resource}") && !option_dir
69
+ options_file = self.send("option_#{resource}") || "#{option_dir}/#{resource.sub('_', '-')}.csv"
70
+ if !File.exists? options_file
71
+ return if option_dir
72
+ raise "File for #{resource} '#{options_file}' does not exist"
73
+ end
74
+
75
+ args = %W( csv #{resource.sub('_', '-')} --csv-file #{options_file} )
76
+ args << '-v' if option_verbose?
77
+ args += %W( --threads #{option_threads} )
78
+ hammer.run(args)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,88 @@
1
+ # Copyright 2013-2014 Red Hat, Inc.
2
+ #
3
+ # This software is licensed to you under the GNU General Public
4
+ # License as published by the Free Software Foundation; either version
5
+ # 2 of the License (GPLv2) or (at your option) any later version.
6
+ # There is NO WARRANTY for this software, express or implied,
7
+ # including the implied warranties of MERCHANTABILITY,
8
+ # NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
9
+ # have received a copy of GPLv2 along with this software; if not, see
10
+ # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
11
+
12
+ module HammerCLICsv
13
+ class CsvCommand
14
+ class InstallationMediasCommand < BaseCommand
15
+ command_name 'installation-medias'
16
+ desc 'import or export installation media'
17
+
18
+ OSFAMILY = 'OS Family'
19
+ PATH = 'Path'
20
+ ORGANIZATIONS = 'Organizations'
21
+
22
+ def export
23
+ CSV.open(option_csv_file || '/dev/stdout', 'wb', {:force_quotes => true}) do |csv|
24
+ csv << [NAME, COUNT, PATH, OSFAMILY]
25
+ @api.resource(:media).call(:index, {:per_page => 999999})['results'].each do |installation_media|
26
+ name = installation_media['name']
27
+ count = 1
28
+ path = installation_media['path']
29
+ os_family = installation_media['os_family']
30
+ csv << [name, count, path, os_family]
31
+ end
32
+ end
33
+ end
34
+
35
+ def import
36
+ @existing = {}
37
+ @api.resource(:media).call(:index, {:per_page => 999999})['results'].each do |installation_media|
38
+ @existing[installation_media['name']] = installation_media['id'] if installation_media
39
+ end
40
+
41
+ thread_import do |line|
42
+ create_installation_medias_from_csv(line)
43
+ end
44
+ end
45
+
46
+ def create_installation_medias_from_csv(line)
47
+ line[COUNT].to_i.times do |number|
48
+ name = namify(line[NAME], number)
49
+ if !@existing.include? name
50
+ print "Creating installation_media '#{name}'..." if option_verbose?
51
+ installation_media_id = @api.resource(:media).call(:create, {
52
+ 'name' => name
53
+ })['id']
54
+ else
55
+ print "Updating installation_media '#{name}'..." if option_verbose?
56
+ installation_media_id = @api.resource(:media).call(:update, {
57
+ 'id' => @existing[name],
58
+ 'name' => name
59
+ })['id']
60
+ end
61
+
62
+ # Update associated resources
63
+ installation_medias ||= {}
64
+ CSV.parse_line(line[ORGANIZATIONS]).each do |organization|
65
+ organization_id = foreman_organization(:name => organization)
66
+ if installation_medias[organization].nil?
67
+ installation_medias[organization] = @api.resource(:organizations).call(:show, {'id' => organization_id})['installation_medias'].collect do |installation_media|
68
+ installation_media['id']
69
+ end
70
+ end
71
+ installation_medias[organization] += [installation_media_id] if !installation_medias[organization].include? installation_media_id
72
+
73
+ @api.resource(:organizations).call(:update, {
74
+ 'id' => organization_id,
75
+ 'organization' => {
76
+ 'installation_media_ids' => installation_medias[organization]
77
+ }
78
+ })
79
+ end
80
+
81
+ print "done\n" if option_verbose?
82
+ end
83
+ rescue RuntimeError => e
84
+ raise "#{e}\n #{line}"
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,116 @@
1
+ # Copyright 2013-2014 Red Hat, Inc.
2
+ #
3
+ # This software is licensed to you under the GNU General Public
4
+ # License as published by the Free Software Foundation; either version
5
+ # 2 of the License (GPLv2) or (at your option) any later version.
6
+ # There is NO WARRANTY for this software, express or implied,
7
+ # including the implied warranties of MERCHANTABILITY,
8
+ # NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
9
+ # have received a copy of GPLv2 along with this software; if not, see
10
+ # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
11
+
12
+ #
13
+ # -= Environments CSV =-
14
+ #
15
+ # Columns
16
+ # Name
17
+ # - Environment name
18
+ # - May contain '%d' which will be replaced with current iteration number of Count
19
+ # - eg. "os%d" -> "os1"
20
+ # Count
21
+ # - Number of times to iterate on this line of the CSV file
22
+ #
23
+
24
+ require 'hammer_cli'
25
+ require 'json'
26
+ require 'csv'
27
+
28
+ module HammerCLICsv
29
+ class CsvCommand
30
+ class LifecycleEnvironmentsCommand < BaseCommand
31
+ command_name 'lifecycle-environments'
32
+ desc 'import or export lifecycle environments'
33
+
34
+ ORGANIZATION = 'Organization'
35
+ PRIORENVIRONMENT = 'Prior Environment'
36
+ DESCRIPTION = 'Description'
37
+
38
+ def export
39
+ CSV.open(option_csv_file || '/dev/stdout', 'wb', {:force_quotes => true}) do |csv|
40
+ csv << [NAME, COUNT, ORGANIZATION, PRIORENVIRONMENT, DESCRIPTION]
41
+ @api.resource(:organizations)
42
+ .call(:index, {
43
+ 'per_page' => 999999
44
+ })['results'].each do |organization|
45
+ @api.resource(:lifecycle_environments)
46
+ .call(:index, {
47
+ 'per_page' => 999999,
48
+ 'organization_id' => organization['id']
49
+ })['results'].each do |environment|
50
+ if environment['name'] != 'Library'
51
+ name = environment['name']
52
+ count = 1
53
+ prior = environment['prior']
54
+ description = environment['description']
55
+ csv << [name, count, organization['name'], prior, description]
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ def import
63
+ @existing = {}
64
+ @api.resource(:organizations)
65
+ .call(:index, {
66
+ 'per_page' => 999999
67
+ })['results'].each do |organization|
68
+ @api.resource(:lifecycle_environments)
69
+ .call(:index, {
70
+ 'per_page' => 999999,
71
+ 'organization_id' => foreman_organization(:name => organization['name'])
72
+ })['results'].each do |environment|
73
+ @existing[organization['name']] ||= {}
74
+ @existing[organization['name']][environment['name']] = environment['id'] if environment
75
+ end
76
+ end
77
+
78
+ thread_import do |line|
79
+ create_environments_from_csv(line)
80
+ end
81
+ end
82
+
83
+ def create_environments_from_csv(line)
84
+ line[COUNT].to_i.times do |number|
85
+ name = namify(line[NAME], number)
86
+ prior = namify(line[PRIORENVIRONMENT], number)
87
+ raise "Organization '#{line[ORGANIZATION]}' does not exist" if !@existing.include? line[ORGANIZATION]
88
+ if !@existing[line[ORGANIZATION]].include? name
89
+ print "Creating environment '#{name}'..." if option_verbose?
90
+ @api.resource(:lifecycle_environments)
91
+ .call(:create, {
92
+ 'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
93
+ 'name' => name,
94
+ 'prior' => lifecycle_environment(line[ORGANIZATION], :name => prior),
95
+ 'description' => line[DESCRIPTION]
96
+ })
97
+ else
98
+ print "Updating environment '#{name}'..." if option_verbose?
99
+ @api.resource(:lifecycle_environments)
100
+ .call(:update, {
101
+ 'id' => @existing[line[ORGANIZATION]][name],
102
+ 'name' => name,
103
+ 'new_name' => name,
104
+ 'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
105
+ 'prior' => prior,
106
+ 'description' => line[DESCRIPTION]
107
+ })
108
+ end
109
+ print "done\n" if option_verbose?
110
+ end
111
+ rescue RuntimeError => e
112
+ raise "#{e}\n #{line}"
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,84 @@
1
+ # Copyright 2013-2014 Red Hat, Inc.
2
+ #
3
+ # This software is licensed to you under the GNU General Public
4
+ # License as published by the Free Software Foundation; either version
5
+ # 2 of the License (GPLv2) or (at your option) any later version.
6
+ # There is NO WARRANTY for this software, express or implied,
7
+ # including the implied warranties of MERCHANTABILITY,
8
+ # NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
9
+ # have received a copy of GPLv2 along with this software; if not, see
10
+ # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
11
+
12
+ #
13
+ # -= Locations CSV =-
14
+ #
15
+ # Columns
16
+ # Name
17
+ # - Name of the location.
18
+ # - May contain '%d' which will be replaced with current iteration number of Count
19
+ # - eg. "location%d" -> "location1"
20
+ # Count
21
+ # - Number of times to iterate on this line of the CSV file
22
+ # Parent
23
+ # - Parent location
24
+ #
25
+
26
+ require 'hammer_cli'
27
+ require 'json'
28
+ require 'csv'
29
+
30
+ module HammerCLICsv
31
+ class CsvCommand
32
+ class LocationsCommand < BaseCommand
33
+ command_name 'locations'
34
+ desc 'import or export locations'
35
+
36
+ PARENT = 'Parent Location'
37
+
38
+ def export
39
+ CSV.open(option_csv_file || '/dev/stdout', 'wb', {:force_quotes => true}) do |csv|
40
+ csv << [NAME, COUNT, PARENT]
41
+ @api.resource(:locations).call(:index, {:per_page => 999999})['results'].each do |location|
42
+ csv << [location['name'], 1]
43
+ end
44
+ end
45
+ end
46
+
47
+ def import
48
+ @existing = {}
49
+ @api.resource(:locations).call(:index, {:per_page => 999999})['results'].each do |location|
50
+ @existing[location['name']] = location['id'] if location
51
+ end
52
+
53
+ thread_import do |line|
54
+ create_locations_from_csv(line)
55
+ end
56
+ end
57
+
58
+ def create_locations_from_csv(line)
59
+ line[COUNT].to_i.times do |number|
60
+ name = namify(line[NAME], number)
61
+ location_id = @existing[name]
62
+ if !location_id
63
+ print "Creating location '#{name}'... " if option_verbose?
64
+ @api.resource(:locations).call(:create, {
65
+ 'location' => {
66
+ 'name' => name,
67
+ 'parent_id' => foreman_location(:name => line[PARENT])
68
+ }
69
+ })
70
+ else
71
+ print "Updating location '#{name}'... " if option_verbose?
72
+ @api.resource(:locations).call(:update, {
73
+ 'id' => location_id,
74
+ 'location' => {
75
+ 'parent_id' => foreman_location(:name => line[PARENT])
76
+ }
77
+ })
78
+ end
79
+ print "done\n" if option_verbose?
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end