hammer_cli_import 0.10.21
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +674 -0
- data/README.md +115 -0
- data/channel_data_pretty.json +10316 -0
- data/config/import/config_macros.yml +16 -0
- data/config/import/interview_answers.yml +13 -0
- data/config/import/role_map.yml +10 -0
- data/config/import.yml +2 -0
- data/lib/hammer_cli_import/activationkey.rb +156 -0
- data/lib/hammer_cli_import/all.rb +253 -0
- data/lib/hammer_cli_import/asynctasksreactor.rb +187 -0
- data/lib/hammer_cli_import/autoload.rb +27 -0
- data/lib/hammer_cli_import/base.rb +585 -0
- data/lib/hammer_cli_import/configfile.rb +392 -0
- data/lib/hammer_cli_import/contenthost.rb +243 -0
- data/lib/hammer_cli_import/contentview.rb +198 -0
- data/lib/hammer_cli_import/csvhelper.rb +68 -0
- data/lib/hammer_cli_import/deltahash.rb +86 -0
- data/lib/hammer_cli_import/fixtime.rb +27 -0
- data/lib/hammer_cli_import/hostcollection.rb +52 -0
- data/lib/hammer_cli_import/import.rb +31 -0
- data/lib/hammer_cli_import/importtools.rb +351 -0
- data/lib/hammer_cli_import/organization.rb +110 -0
- data/lib/hammer_cli_import/persistentmap.rb +225 -0
- data/lib/hammer_cli_import/repository.rb +91 -0
- data/lib/hammer_cli_import/repositoryenable.rb +250 -0
- data/lib/hammer_cli_import/templatesnippet.rb +67 -0
- data/lib/hammer_cli_import/user.rb +155 -0
- data/lib/hammer_cli_import/version.rb +25 -0
- data/lib/hammer_cli_import.rb +53 -0
- metadata +117 -0
@@ -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,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
|