hammer_cli_import 0.10.21

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,16 @@
1
+ # spacewalk-macro-name : puppet-fact
2
+ rhn.system.sid :
3
+ rhn.system.profile_name :
4
+ rhn.system.description :
5
+ rhn.system.hostname : '@fqdn'
6
+ rhn.system.ip_address : '@ipaddress'
7
+ rhn.system.ip6_address : '@ipaddress6'
8
+ rhn.system.custom_info(key_name) :
9
+ rhn.system.net_interface.ip_address(eth_device) : '@ipaddress_{NETWORK INTERFACE}'
10
+ rhn.system.net_interface.netmask(eth_device) : '@netmask_{NETWORK INTERFACE}'
11
+ rhn.system.net_interface.ip6_address(eth_device) : '@ipaddress6_{NETWORK INTERFACE}'
12
+ rhn.system.net_interface.ip6_netmask(eth_device) : '@netmask_{NETWORK INTERFACE}'
13
+ rhn.system.net_interface.broadcast(eth_device) :
14
+ rhn.system.net_interface.hardware_address(eth_device) : '@macaddress_{NETWORK INTERFACE}'
15
+ rhn.system.net_interface.driver_module(eth_device) :
16
+
@@ -0,0 +1,13 @@
1
+ # interview-question-key : answer
2
+ # special-strings:
3
+ # #{module_name} == the name of the module being generated
4
+ #
5
+ version : '1.0.0'
6
+ author : 'Red Hat'
7
+ license : 'GPL3'
8
+ summary : "#{module_name}"
9
+ description : "Module #{module_name} created from Sat5 config-channel"
10
+ srcrepo : ""
11
+ learnmore : ""
12
+ fileissues : ""
13
+
@@ -0,0 +1,10 @@
1
+ # sat5-role-label,sat6-role-name-list
2
+ # _admin_ means "flag any user with this role as an Administrator"
3
+ #
4
+ Satellite-Administrator : [ _admin_ ]
5
+ Organization-Administrator : [ _admin_ ]
6
+ Channel-Administrator : [ ]
7
+ Configuration-Administrator : [ ]
8
+ Monitoring-Administrator : [ ]
9
+ System-Group-Administrator : [ ]
10
+ Activation-Key-Administrator : [ ]
data/config/import.yml ADDED
@@ -0,0 +1,2 @@
1
+ :import:
2
+ :enable_module: true
@@ -0,0 +1,156 @@
1
+ #
2
+ # Copyright (c) 2014 Red Hat Inc.
3
+ #
4
+ # This file is part of hammer-cli-import.
5
+ #
6
+ # hammer-cli-import is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # hammer-cli-import is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with hammer-cli-import. If not, see <http://www.gnu.org/licenses/>.
18
+ #
19
+
20
+ require 'hammer_cli'
21
+ require 'apipie-bindings'
22
+
23
+ module HammerCLIImport
24
+ class ImportCommand
25
+ class ActivationKeyImportCommand < BaseCommand
26
+ include ImportTools::ContentView::Include
27
+ include ImportTools::LifecycleEnvironment::Include
28
+
29
+ command_name 'activation-key'
30
+ reportname = 'activation-keys'
31
+ desc "Import Activation Keys (from spacewalk-report #{reportname})."
32
+
33
+ csv_columns 'token', 'org_id', 'note', 'usage_limit', 'base_channel_id', 'child_channel_id', 'server_group_id'
34
+
35
+ persistent_maps :organizations, :host_collections, :content_views, :redhat_content_views,
36
+ :ak_content_views, :activation_keys
37
+
38
+ def mk_ak_hash(data)
39
+ usage_limit = 'unlimited'
40
+ usage_limit = data['usage_limit'].to_i if data['usage_limit']
41
+ debug " Activation key usage_limit: #{usage_limit}"
42
+ {
43
+ :name => data['token'],
44
+ :organization_id => get_translated_id(:organizations, data['org_id'].to_i),
45
+ :label => data['token'],
46
+ :description => data['note'],
47
+ :usage_limit => usage_limit
48
+ }
49
+ end
50
+
51
+ def associate_host_collections(ak_id, server_group_ids)
52
+ translated_ids = server_group_ids.collect { |sg_id| get_translated_id(:host_collections, sg_id) }
53
+ info " Associating activation key [#{ak_id}] with host collections [#{translated_ids.join(', ')}]"
54
+ api_call(
55
+ :activation_keys,
56
+ :add_host_collections,
57
+ {
58
+ :id => ak_id,
59
+ :host_collection_ids => translated_ids
60
+ })
61
+ end
62
+
63
+ def import_single_row(data)
64
+ if data['base_channel_id'].nil?
65
+ # if base channel id is empty,
66
+ # 'Spacewalk Default' was used on Sat5
67
+ info "Skipping activation-key #{data['token']}: Migrating activation-keys with " \
68
+ "'Red Hat Satellite Default' as base channel is not supported."
69
+ report_summary :skipped, :activation_keys
70
+ return
71
+ end
72
+ ak_hash = mk_ak_hash data
73
+ ak = create_entity(:activation_keys, ak_hash, data['token'])
74
+ if (data['server_group_id'])
75
+ associate_host_collections(ak['id'], split_multival(data['server_group_id']))
76
+ end
77
+ @ak_content_views ||= {}
78
+ @ak_content_views[ak['id'].to_i] ||= Set.new
79
+ if data['base_channel_id']
80
+ split_multival(data['base_channel_id']).each do |base_channel_id|
81
+ @ak_content_views[ak['id'].to_i] << begin
82
+ get_translated_id(:redhat_content_views, [data['org_id'].to_i, base_channel_id])
83
+ rescue HammerCLIImport::MissingObjectError
84
+ begin
85
+ get_translated_id(:content_views, base_channel_id)
86
+ rescue HammerCLIImport::MissingObjectError
87
+ error "Can't find content view for channel ID [#{base_channel_id}] for key [#{data['token']}]"
88
+ end
89
+ end
90
+ end
91
+ else
92
+ # if base channel id is empty,
93
+ # 'Spacewalk Default' was used on Sat5
94
+ # Since we can not migrate them due to
95
+ # bug 1126924, we skip it right at the beggining
96
+ # of this function.
97
+ debug ' Red Hat Satellite Default activation keys are not supported.'
98
+ end
99
+ split_multival(data['child_channel_id']).each do |child_ch|
100
+ @ak_content_views[ak['id'].to_i] << begin
101
+ get_translated_id(:redhat_content_views, [data['org_id'].to_i, child_ch])
102
+ rescue HammerCLIImport::MissingObjectError
103
+ begin
104
+ get_translated_id(:content_views, child_ch)
105
+ rescue HammerCLIImport::MissingObjectError
106
+ error "Can't find content view for channel ID [#{child_ch}] for key [#{data['token']}]"
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ def post_import(_csv_file)
113
+ return unless @ak_content_views
114
+ @ak_content_views.each do |ak_id, cvs|
115
+ if cvs.include? nil
116
+ warn "Skipping content view association for activation key [#{ak_id}]. Dependent content views not ready."
117
+ next
118
+ end
119
+ handle_missing_and_supress "processing activation key #{ak_id}" do
120
+ ak = lookup_entity(:activation_keys, ak_id)
121
+ ak_cv_hash = {}
122
+ org_id = lookup_entity_in_cache(:organizations, {'label' => ak['organization']['label']})['id']
123
+ ak_cv_hash[:content_view_id] = create_composite_content_view(
124
+ :ak_content_views,
125
+ org_id,
126
+ "ak_#{ak_id}",
127
+ "Composite content view for activation key #{ak['name']}",
128
+ cvs)
129
+ ak_cv_hash[:environment_id] = get_env(org_id, 'Library')['id']
130
+ ak_cv_hash[:organization_id] = org_id
131
+ if ak_cv_hash[:content_view_id]
132
+ info " Associating activation key [#{ak_id}] with content view [#{ak_cv_hash[:content_view_id]}]"
133
+ # associate the content view with the activation key
134
+ update_entity(:activation_keys, ak_id, ak_cv_hash)
135
+ else
136
+ info ' Skipping content-view associations.'
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ def delete_single_row(data)
143
+ unless @pm[:activation_keys][data['token']]
144
+ info to_singular(:activation_keys).capitalize + ' with id ' + data['token'] +
145
+ " wasn't imported. Skipping deletion."
146
+ return
147
+ end
148
+ ak = @cache[:activation_keys][get_translated_id(:activation_keys, data['token'])]
149
+ delete_entity(:activation_keys, data['token'])
150
+ delete_content_view(ak['content_view']['id'].to_i, :ak_content_views) if
151
+ ak['content_view'] && was_translated(:ak_content_views, ak['content_view']['id'])
152
+ end
153
+ end
154
+ end
155
+ end
156
+ # vim: autoindent tabstop=2 shiftwidth=2 expandtab softtabstop=2 filetype=ruby
@@ -0,0 +1,253 @@
1
+ #
2
+ # Copyright (c) 2014 Red Hat Inc.
3
+ #
4
+ # This file is part of hammer-cli-import.
5
+ #
6
+ # hammer-cli-import is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # hammer-cli-import is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with hammer-cli-import. If not, see <http://www.gnu.org/licenses/>.
18
+ #
19
+
20
+ require 'hammer_cli'
21
+ require 'hammer_cli_import'
22
+
23
+ module HammerCLIImport
24
+ class ImportCommand
25
+ class AllCommand < HammerCLI::AbstractCommand
26
+ extend ImportTools::Repository::Extend
27
+ extend ImportTools::ImportLogging::Extend
28
+ extend AsyncTasksReactor::Extend
29
+ include ImportTools::ImportLogging::Include
30
+
31
+ command_name 'all'
32
+ desc 'Load ALL data from a specified directory that is in spacewalk-export format.'
33
+
34
+ option ['--directory'], 'DIR_PATH', 'stargate-export directory', :default => '/tmp/exports'
35
+ option ['--delete'], :flag, 'Delete entities instead of importing them', :default => false
36
+ option ['--macro_mapping'], 'FILE', 'Mapping of Satellite-5 config-file-macros to puppet facts'
37
+ option ['--manifest-directory'], 'DIR_PATH', 'Directory holding manifests'
38
+ option ['--entities'], 'entity[,entity...]', 'Import specific entities', :default => 'all'
39
+ option ['--list-entities'], :flag, 'List entities we understand', :default => false
40
+ option ['--into-org-id'], 'ORG_ID', 'Import all organizations into one specified by id'
41
+ option ['--merge-users'], :flag, 'Merge pre-created users (except admin)', :default => false
42
+ option ['--dry-run'], :flag, 'Show what we would have done, if we\'d been allowed', :default => false
43
+
44
+ add_repo_options
45
+ add_logging_options
46
+
47
+ # An ordered-list of the entities we know how to import
48
+ class << self; attr_accessor :entity_order end
49
+ @entity_order = %w(organization user host-collection repository-enable repository
50
+ content-view activation-key template-snippet config-file content-host)
51
+
52
+ #
53
+ # A list of what we know how to do.
54
+ # The map has entries of
55
+ # import-entity => {sat5-export-name, import-classname, entities-we-are-dependent-on, should-import}
56
+ # The code will look for classes HammerCLIImport::ImportCommand::<import-classname>
57
+ # It will look in ~/exports/<Sat5-export-name>.csv for data
58
+ #
59
+ class << self; attr_accessor :known end
60
+ @known = {
61
+ 'activation-key' =>
62
+ {'export-file' => 'activation-keys',
63
+ 'import-class' => 'ActivationKeyImportCommand',
64
+ 'depends-on' => 'organization',
65
+ 'import' => false },
66
+ 'config-file' =>
67
+ {'export-file' => 'config-files-latest',
68
+ 'import-class' => 'ConfigFileImportCommand',
69
+ 'depends-on' => 'organization',
70
+ 'import' => false },
71
+ 'content-host' =>
72
+ {'export-file' => 'system-profiles',
73
+ 'import-class' => 'ContentHostImportCommand',
74
+ 'depends-on' => 'content-view,host-collection,repository,organization',
75
+ 'import' => false },
76
+ 'content-view' =>
77
+ {'export-file' => 'CHANNELS/export',
78
+ 'import-class' => 'LocalRepositoryImportCommand',
79
+ 'depends-on' => 'repository,organization',
80
+ 'import' => false },
81
+ 'repository' =>
82
+ {'export-file' => 'repositories',
83
+ 'import-class' => 'RepositoryImportCommand',
84
+ 'depends-on' => 'organization',
85
+ 'import' => false },
86
+ 'host-collection' =>
87
+ {'export-file' => 'system-groups',
88
+ 'import-class' => 'SystemGroupImportCommand',
89
+ 'depends-on' => 'organization',
90
+ 'import' => false },
91
+ 'organization' =>
92
+ {'export-file' => 'users',
93
+ 'import-class' => 'OrganizationImportCommand',
94
+ 'depends-on' => '',
95
+ 'import' => false },
96
+ 'repository-enable' =>
97
+ {'export-file' => 'channels',
98
+ 'import-class' => 'RepositoryEnableCommand',
99
+ 'depends-on' => 'organization',
100
+ 'import' => false },
101
+ 'template-snippet' =>
102
+ {'export-file' => 'kickstart-scripts',
103
+ 'import-class' => 'TemplateSnippetImportCommand',
104
+ 'import' => false },
105
+ 'user' =>
106
+ {'export-file' => 'users',
107
+ 'import-class' => 'UserImportCommand',
108
+ 'depends-on' => 'organization',
109
+ 'import' => false }
110
+ }
111
+
112
+ def do_list
113
+ puts 'Entities I understand:'
114
+ AllCommand.entity_order.each do |an_entity|
115
+ puts " #{an_entity}"
116
+ end
117
+ end
118
+
119
+ # What are we being asked to import?
120
+ # Marks what we asked for, and whatever those things are dependent on, to import
121
+ def set_import_targets
122
+ to_import = option_entities.split(',')
123
+ AllCommand.known.each_key do |key|
124
+ AllCommand.known[key]['import'] = (to_import.include?(key) || to_import.include?('all'))
125
+ next if AllCommand.known[key]['depends-on'].nil? || !AllCommand.known[key]['import']
126
+
127
+ depends_on = AllCommand.known[key]['depends-on'].split(',')
128
+ depends_on.each do |entity_name|
129
+ AllCommand.known[entity_name]['import'] = true
130
+ end
131
+ end
132
+ end
133
+
134
+ # config-file may need --macro-mapping
135
+ def config_file_args(args)
136
+ args << '--macro-mapping' << "#{option_macro_mapping}" unless option_macro_mapping.nil?
137
+ return args
138
+ end
139
+
140
+ # 'content-host needs --export-directory
141
+ def content_host_args(args)
142
+ args << '--export-directory' << File.join(File.expand_path('~'), 'rpm-working-dir')
143
+ return args
144
+ end
145
+
146
+ # 'content-view needs --dir, knows its own --csv-file in that dir, and may need --no-async
147
+ def content_view_args(args)
148
+ args << '--dir' << "#{option_directory}/CHANNELS"
149
+ args << '--no-async' if option_no_async?
150
+ args << '--synchronize' if option_synchronize?
151
+ args << '--wait' if option_wait?
152
+ return args
153
+ end
154
+
155
+ # 'organization' may need --into-org-id
156
+ def organization_args(args)
157
+ args << '--into-org-id' << option_into_org_id unless option_into_org_id.nil?
158
+ args << '--upload-manifests-from' << option_manifest_directory unless option_manifest_directory.nil?
159
+ return args
160
+ end
161
+
162
+ # repository and repo-enable may need --synch, --wait, and --no-async
163
+ def repository_args(args)
164
+ args << '--synchronize' if option_synchronize?
165
+ args << '--wait' if option_wait?
166
+ args << '--no-async' if option_no_async?
167
+ return args
168
+ end
169
+
170
+ # 'user' needs --new-passwords and may need --merge-users
171
+ def user_args(args)
172
+ pwd_filename = "passwords_#{Time.now.utc.iso8601}.csv"
173
+ args << '--new-passwords' << pwd_filename
174
+ args << '--merge-users' if option_merge_users?
175
+ return args
176
+ end
177
+
178
+ # Some subcommands have their own special args
179
+ # This is the function that will know them all
180
+ def build_args(key, filename)
181
+ if key == 'content-view'
182
+ csv = ['--csv-file', "#{option_directory}/CHANNELS/export.csv"]
183
+ else
184
+ csv = ['--csv-file', filename]
185
+ end
186
+ return csv << '--delete' if option_delete?
187
+
188
+ case key
189
+ when 'config-file'
190
+ args = config_file_args(csv)
191
+ when 'content-host'
192
+ args = content_host_args(csv)
193
+ when 'content-view'
194
+ args = content_view_args(csv)
195
+ when 'organization'
196
+ args = organization_args(csv)
197
+ when 'repository', 'repository-enable'
198
+ args = repository_args(csv)
199
+ when 'user'
200
+ args = user_args(csv)
201
+ else
202
+ args = csv
203
+ end
204
+ return args
205
+ end
206
+
207
+ # Get entities-to-be-processed, in the right order (reversed if deleting)
208
+ def entities
209
+ if option_delete?
210
+ return AllCommand.entity_order.reverse
211
+ else
212
+ return AllCommand.entity_order
213
+ end
214
+ end
215
+
216
+ # Do the import(s)
217
+ def import_from
218
+ entities.each do |key|
219
+ a_map = AllCommand.known[key]
220
+ if a_map['import']
221
+ import_file = "#{option_directory}/#{a_map['export-file']}.csv"
222
+ args = build_args(key, import_file)
223
+ if File.exist? import_file
224
+ progress format('Import %-20s with arguments %s', key, args.join(' '))
225
+
226
+ #############################################################
227
+ # MAGIC! We create a class from the class-name-string here! #
228
+ #############################################################
229
+ import_class = HammerCLIImport::ImportCommand.const_get(a_map['import-class'])
230
+ unless option_dry_run?
231
+ import_class.new(args).run(args)
232
+ end
233
+ else
234
+ progress "...SKIPPING, no file #{import_file} available."
235
+ end
236
+ end
237
+ end
238
+ end
239
+
240
+ def execute
241
+ setup_logging
242
+ if option_list_entities?
243
+ do_list
244
+ else
245
+ set_import_targets
246
+ import_from
247
+ end
248
+ HammerCLI::EX_OK
249
+ end
250
+ end
251
+ end
252
+ end
253
+ # vim: autoindent tabstop=2 shiftwidth=2 expandtab softtabstop=2 filetype=ruby
@@ -0,0 +1,187 @@
1
+ #
2
+ # Copyright (c) 2014 Red Hat Inc.
3
+ #
4
+ # This file is part of hammer-cli-import.
5
+ #
6
+ # hammer-cli-import is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # hammer-cli-import is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with hammer-cli-import. If not, see <http://www.gnu.org/licenses/>.
18
+ #
19
+
20
+ # Note to future self (and possibly others): unless there is a
21
+ # (serious) bug here, you probably do not want to modify this code.
22
+
23
+ # Main thread Thread for async tasks
24
+ # | |
25
+ # | enq task deq task |<---.
26
+ # |'----------> Queue -----------> | |
27
+ # | |'---'
28
+ # | |
29
+ # V V
30
+ require 'thread'
31
+
32
+ module HammerCLIImport
33
+ # Reactor for async tasks
34
+ # Include submodule should be included in class that
35
+ # implements @annotate_tasks@ that takes list
36
+ # of UUIDS and returns map, annotating every
37
+ # given UUID with :finished bool and :progress float.
38
+ module AsyncTasksReactor
39
+ module Extend
40
+ def add_async_tasks_reactor_options
41
+ option ['--no-async'], :flag, 'Wait for async tasks in foreground', :default => false
42
+ end
43
+ end
44
+
45
+ module Include
46
+ # Call from init
47
+ def atr_init
48
+ # Will create thread on demand
49
+ @thread = nil
50
+
51
+ @mutex = Mutex.new
52
+ @queue = Queue.new
53
+ @task_map = {}
54
+ @thread_finish = false
55
+ @async_tasks_todo = 0
56
+ @async_tasks_done = 0
57
+ end
58
+
59
+ # Call to pospone execution of @block@ till all tasks are finished
60
+ # Never ever use @return@ inside provided do block.
61
+ def postpone_till(uuids, &block)
62
+ if option_no_async?
63
+ wait_for uuids, &block
64
+ return
65
+ end
66
+ if uuids.empty?
67
+ info 'Nothing to wait for, running in main thread.'
68
+ block.call
69
+ return
70
+ end
71
+ info "Registering tasks for uuids: #{uuids.inspect}."
72
+ uuids.sort!
73
+ @queue.enq([uuids, block])
74
+ start_async_task_thread
75
+ nil
76
+ end
77
+
78
+ # Variant for case when we do not want run thing in async
79
+ def wait_for(uuids, &block)
80
+ info "Waiting for uuids (non async): #{uuids.inspect}."
81
+ n = 1
82
+ loop do
83
+ annotated = annotate_tasks uuids
84
+ break if annotated.all? { |_, v| v[:finished] }
85
+ sleep n
86
+ n = [n + 1, 10].min
87
+ end
88
+ block.call
89
+ end
90
+
91
+ # Has to be called before main thread ends.
92
+ def atr_exit
93
+ info 'Waiting for async tasks to finish' unless @task_map.empty?
94
+ @mutex.synchronize do
95
+ @thread_finish = true
96
+ @thread.run
97
+ end
98
+ @thread.join
99
+ rescue NoMethodError
100
+ nil
101
+ end
102
+
103
+ private
104
+
105
+ def add_task(uuids, p)
106
+ @async_tasks_todo += 1
107
+ @task_map[uuids] ||= []
108
+ @task_map[uuids] << p
109
+ end
110
+
111
+ def pick_up_tasks_from_the_queue
112
+ Thread.stop if @mutex.synchronize do
113
+ if @task_map.empty? && @queue.empty?
114
+ if @thread_finish
115
+ info 'Exiting thread (exit requested, all tasks done).'
116
+ Thread.exit
117
+ else
118
+ true
119
+ end
120
+ else
121
+ false
122
+ end
123
+ end
124
+
125
+ # Do at most what we have at some point
126
+ @queue.size.times do
127
+ begin
128
+ uuids, p = @queue.deq true
129
+ add_task uuids, p
130
+ rescue ThreadError
131
+ break
132
+ end
133
+ end
134
+ end
135
+
136
+ def start_async_task_thread
137
+ puts 'Starting thread for async tasks' unless @thread
138
+ @thread ||= Thread.new do
139
+ loop do
140
+ some_tasks_done = false
141
+ pick_up_tasks_from_the_queue
142
+
143
+ next if @task_map.empty?
144
+
145
+ all_uuids = @task_map.keys.flatten.uniq
146
+ annotated = annotate_tasks all_uuids
147
+ finished = []
148
+ progresses = []
149
+ annotated.each do |uuid, info|
150
+ finished << uuid if info[:finished]
151
+ progresses << info[:progress]
152
+ end
153
+ avg = progresses.instance_eval { reduce(0, :+) / size.to_f }
154
+ progress = format '%5.2f', (avg * 100)
155
+
156
+ @task_map.keys.each do |uuids|
157
+ next unless (uuids - finished).empty?
158
+ info "Condition #{uuids.inspect} met"
159
+ @task_map[uuids].each do |task|
160
+ begin
161
+ task.call
162
+ rescue => e
163
+ info "Exception caught while executing post-#{uuids.inspect}:"
164
+ info e.inspect
165
+ logtrace e
166
+ end
167
+ @async_tasks_done += 1
168
+ some_tasks_done = true
169
+ end
170
+ @task_map.delete uuids
171
+ end
172
+
173
+ print = @mutex.synchronize do
174
+ some_tasks_done || @thread_finish
175
+ end
176
+ self.progress "Asynchronous tasks: #{@async_tasks_done} " \
177
+ "of #{@async_tasks_todo + @queue.size} done (~#{progress}%)" \
178
+ if print
179
+
180
+ sleep 1 unless @task_map.empty?
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
187
+ # vim: autoindent tabstop=2 shiftwidth=2 expandtab softtabstop=2 filetype=ruby
@@ -0,0 +1,27 @@
1
+ #
2
+ # Copyright (c) 2014 Red Hat Inc.
3
+ #
4
+ # This file is part of hammer-cli-import.
5
+ #
6
+ # hammer-cli-import is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # hammer-cli-import is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with hammer-cli-import. If not, see <http://www.gnu.org/licenses/>.
18
+ #
19
+
20
+ # This has to be included after all other subcommands were loaded
21
+ # to work properly.
22
+ module HammerCLIImport
23
+ class ImportCommand
24
+ autoload_subcommands
25
+ end
26
+ end
27
+ # vim: autoindent tabstop=2 shiftwidth=2 expandtab softtabstop=2 filetype=ruby