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.
- 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
|