hammer_cli_import 0.10.21

Sign up to get free protection for your applications and to get access to all the features.
@@ -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