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,585 @@
|
|
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 'csv'
|
21
|
+
require 'json'
|
22
|
+
require 'set'
|
23
|
+
|
24
|
+
require 'apipie-bindings'
|
25
|
+
require 'hammer_cli'
|
26
|
+
|
27
|
+
module HammerCLIImport
|
28
|
+
class MissingObjectError < RuntimeError
|
29
|
+
end
|
30
|
+
|
31
|
+
class ImportRecoveryError < RuntimeError
|
32
|
+
end
|
33
|
+
|
34
|
+
class BaseCommand < HammerCLI::Apipie::Command
|
35
|
+
extend PersistentMap::Extend
|
36
|
+
extend ImportTools::ImportLogging::Extend
|
37
|
+
extend AsyncTasksReactor::Extend
|
38
|
+
|
39
|
+
include PersistentMap::Include
|
40
|
+
include ImportTools::ImportLogging::Include
|
41
|
+
include ImportTools::Task::Include
|
42
|
+
include ImportTools::Exceptional::Include
|
43
|
+
include AsyncTasksReactor::Include
|
44
|
+
|
45
|
+
def initialize(*list)
|
46
|
+
super(*list)
|
47
|
+
|
48
|
+
# wrap API parameters into extra hash
|
49
|
+
@wrap_out = {
|
50
|
+
:users => :user,
|
51
|
+
:template_snippets => :config_template
|
52
|
+
}
|
53
|
+
# APIs return objects encapsulated in extra hash
|
54
|
+
#@wrap_in = {:organizations => 'organization'}
|
55
|
+
@wrap_in = {}
|
56
|
+
# entities that needs organization to be listed
|
57
|
+
@prerequisite = {
|
58
|
+
:activation_keys => :organizations,
|
59
|
+
:content_views => :organizations,
|
60
|
+
:content_view_versions => :organizations,
|
61
|
+
:host_collections => :organizations,
|
62
|
+
:products => :organizations,
|
63
|
+
:repositories => :organizations,
|
64
|
+
:repository_sets => :products,
|
65
|
+
:systems => :organizations
|
66
|
+
}
|
67
|
+
# cache imported objects (created/lookuped)
|
68
|
+
@cache = {}
|
69
|
+
class << @cache
|
70
|
+
def []=(key, val)
|
71
|
+
raise "@cache: #{val.inspect} is not a hash!" unless val.is_a? Hash
|
72
|
+
super
|
73
|
+
end
|
74
|
+
end
|
75
|
+
@summary = {}
|
76
|
+
# Initialize AsyncTaskReactor
|
77
|
+
atr_init
|
78
|
+
end
|
79
|
+
|
80
|
+
# What spacewalk-report do we expect to use for a given subcommand
|
81
|
+
class << self; attr_accessor :reportname end
|
82
|
+
|
83
|
+
option ['--csv-file'], 'FILE_NAME', 'CSV file with data to be imported', :required => true \
|
84
|
+
do |filename|
|
85
|
+
raise ArgumentError, "File #{filename} does not exist" unless File.exist? filename
|
86
|
+
missing = CSVHelper.csv_missing_columns filename, self.class.csv_columns
|
87
|
+
raise ArgumentError, "Bad CSV file #{filename}, missing columns: #{missing.inspect}" unless missing.empty?
|
88
|
+
filename
|
89
|
+
end
|
90
|
+
|
91
|
+
option ['--delete'], :flag, 'Delete entities from CSV file', :default => false
|
92
|
+
|
93
|
+
# TODO: Implement logic for verify
|
94
|
+
# option ['--verify'], :flag, 'Verify entities from CSV file'
|
95
|
+
|
96
|
+
option ['--recover'], 'RECOVER', 'Recover strategy, can be: rename (default), map, none', :default => :rename \
|
97
|
+
do |strategy|
|
98
|
+
raise ArgumentError, "Unknown '#{strategy}' strategy argument." \
|
99
|
+
unless [:rename, :map, :none].include? strategy.to_sym
|
100
|
+
strategy.to_sym
|
101
|
+
end
|
102
|
+
add_logging_options
|
103
|
+
|
104
|
+
class << self
|
105
|
+
# Which columns have to be be present in CSV.
|
106
|
+
def csv_columns(*list)
|
107
|
+
return @csv_columns if list.empty?
|
108
|
+
raise 'set more than once' if @csv_columns
|
109
|
+
@csv_columns = list
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class << self
|
114
|
+
# Initialize API. Needed to be called before any +api_call+ calls.
|
115
|
+
# If used in shell, it may be called multiple times
|
116
|
+
def api_init
|
117
|
+
@api = HammerCLIForeman.foreman_api_connection.api
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
|
121
|
+
# Call API. Ideally accessed via +api_call+ instance method.
|
122
|
+
# This is supposed to be the only way to access @api.
|
123
|
+
def api_call(resource, action, params = {}, headers = {}, dbg = false)
|
124
|
+
if resource == :organizations && action == :create
|
125
|
+
params[:organization] ||= {}
|
126
|
+
params[:organization][:name] = params[:name]
|
127
|
+
end
|
128
|
+
@api.resource(resource).call(action, params, headers)
|
129
|
+
rescue
|
130
|
+
error("Error on api.resource(#{resource.inspect}).call(#{action.inspect}, #{params.inspect}):") if dbg
|
131
|
+
raise
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Call API. Convenience method for calling +api_call+ class method.
|
136
|
+
def api_call(*list)
|
137
|
+
self.class.api_call(*list)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Call API on corresponding resource (defined by +map_target_entity+).
|
141
|
+
def mapped_api_call(entity_type, *list)
|
142
|
+
api_call(map_target_entity[entity_type], *list)
|
143
|
+
end
|
144
|
+
|
145
|
+
def data_dir
|
146
|
+
File.join(File.expand_path('~'), '.transition_data')
|
147
|
+
end
|
148
|
+
|
149
|
+
# This method is called to process single CSV line when
|
150
|
+
# importing.
|
151
|
+
def import_single_row(_row)
|
152
|
+
error 'Import not implemented.'
|
153
|
+
end
|
154
|
+
|
155
|
+
# This method is called to process single CSV line when
|
156
|
+
# deleting
|
157
|
+
def delete_single_row(_row)
|
158
|
+
error 'Delete not implemented.'
|
159
|
+
end
|
160
|
+
|
161
|
+
def get_cache(entity_type)
|
162
|
+
@cache[map_target_entity[entity_type]]
|
163
|
+
end
|
164
|
+
|
165
|
+
def load_cache
|
166
|
+
maps.collect { |map_sym| map_target_entity[map_sym] } .uniq.each do |entity_type|
|
167
|
+
list_server_entities entity_type
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def lookup_entity(entity_type, entity_id, online_lookup = false)
|
172
|
+
if (!get_cache(entity_type)[entity_id] || online_lookup)
|
173
|
+
get_cache(entity_type)[entity_id] = mapped_api_call(entity_type, :show, {'id' => entity_id})
|
174
|
+
else
|
175
|
+
debug "#{to_singular(entity_type).capitalize} #{entity_id} taken from cache."
|
176
|
+
end
|
177
|
+
return get_cache(entity_type)[entity_id]
|
178
|
+
end
|
179
|
+
|
180
|
+
def was_translated(entity_type, import_id)
|
181
|
+
return @pm[entity_type].to_hash.value?(import_id)
|
182
|
+
end
|
183
|
+
|
184
|
+
def _compare_hash(entity_hash, search_hash)
|
185
|
+
equal = nil
|
186
|
+
search_hash.each do |key, value|
|
187
|
+
if value.is_a? Hash
|
188
|
+
equal = _compare_hash(entity_hash[key], search_hash[key])
|
189
|
+
else
|
190
|
+
equal = entity_hash[key] == value
|
191
|
+
end
|
192
|
+
return false unless equal
|
193
|
+
end
|
194
|
+
return true
|
195
|
+
end
|
196
|
+
|
197
|
+
def lookup_entity_in_cache(entity_type, search_hash)
|
198
|
+
get_cache(entity_type).each do |_entity_id, entity_hash|
|
199
|
+
return entity_hash if _compare_hash(entity_hash, search_hash)
|
200
|
+
end
|
201
|
+
return nil
|
202
|
+
end
|
203
|
+
|
204
|
+
def lookup_entity_in_array(array, search_hash)
|
205
|
+
return nil if array.nil?
|
206
|
+
array.each do |entity_hash|
|
207
|
+
return entity_hash if _compare_hash(entity_hash, search_hash)
|
208
|
+
end
|
209
|
+
return nil
|
210
|
+
end
|
211
|
+
|
212
|
+
def last_in_cache?(entity_type, id)
|
213
|
+
return get_cache(entity_type).size == 1 && get_cache(entity_type).first[0] == id
|
214
|
+
end
|
215
|
+
|
216
|
+
# Method for use when writing messages to user.
|
217
|
+
# > to_singular(:contentveiws)
|
218
|
+
# "contentview"
|
219
|
+
# > to_singular(:repositories)
|
220
|
+
# "repository"
|
221
|
+
def to_singular(plural)
|
222
|
+
return plural.to_s.gsub(/_/, ' ').sub(/s$/, '').sub(/ie$/, 'y')
|
223
|
+
end
|
224
|
+
|
225
|
+
def split_multival(multival, convert_to_int = true, separator = ';')
|
226
|
+
arr = (multival || '').split(separator).delete_if { |v| v == 'None' }
|
227
|
+
arr.map!(&:to_i) if convert_to_int
|
228
|
+
return arr
|
229
|
+
end
|
230
|
+
|
231
|
+
# Method to call when you have created/deleted/found/mapped... something.
|
232
|
+
# Collected data used for summary reporting.
|
233
|
+
#
|
234
|
+
# :found is used for situation, when you want to create something,
|
235
|
+
# but you found out, it is already created.
|
236
|
+
def report_summary(verb, item)
|
237
|
+
raise "Not summary supported action: #{verb}" unless
|
238
|
+
[:created, :deleted, :found, :mapped, :skipped, :uploaded, :wrote, :failed].include? verb
|
239
|
+
@summary[verb] ||= {}
|
240
|
+
@summary[verb][item] = @summary[verb].fetch(item, 0) + 1
|
241
|
+
end
|
242
|
+
|
243
|
+
def print_summary
|
244
|
+
progress 'Summary'
|
245
|
+
@summary.each do |verb, what|
|
246
|
+
what.each do |entity, count|
|
247
|
+
noun = if count == 1
|
248
|
+
to_singular entity
|
249
|
+
else
|
250
|
+
entity
|
251
|
+
end
|
252
|
+
report = " #{verb.to_s.capitalize} #{count} #{noun}."
|
253
|
+
if verb == :found
|
254
|
+
info report
|
255
|
+
else
|
256
|
+
progress report
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
progress ' No action taken.' if (@summary.keys - [:found]).empty?
|
261
|
+
end
|
262
|
+
|
263
|
+
def get_translated_id(entity_type, entity_id)
|
264
|
+
if @pm[entity_type] && @pm[entity_type][entity_id]
|
265
|
+
return @pm[entity_type][entity_id]
|
266
|
+
end
|
267
|
+
raise MissingObjectError, 'Unable to import, first import ' + to_singular(entity_type) + \
|
268
|
+
' with id ' + entity_id.inspect
|
269
|
+
end
|
270
|
+
|
271
|
+
# this method returns a *first* found original_id
|
272
|
+
# (since we're able to map several organizations into one)
|
273
|
+
def get_original_id(entity_type, import_id)
|
274
|
+
if was_translated(entity_type, import_id)
|
275
|
+
# find original_ids
|
276
|
+
@pm[entity_type].to_hash.each do |key, value|
|
277
|
+
return key if value == import_id
|
278
|
+
end
|
279
|
+
else
|
280
|
+
debug "Unknown imported #{to_singular(entity_type)} [#{import_id}]."
|
281
|
+
end
|
282
|
+
return nil
|
283
|
+
end
|
284
|
+
|
285
|
+
def list_server_entities(entity_type, extra_hash = {}, use_cache = false)
|
286
|
+
if @prerequisite[entity_type]
|
287
|
+
list_server_entities(@prerequisite[entity_type]) unless @cache[@prerequisite[entity_type]]
|
288
|
+
end
|
289
|
+
|
290
|
+
@cache[entity_type] ||= {}
|
291
|
+
results = []
|
292
|
+
|
293
|
+
if !extra_hash.empty? || @prerequisite[entity_type].nil?
|
294
|
+
if use_cache
|
295
|
+
@list_cache ||= {}
|
296
|
+
if @list_cache[entity_type]
|
297
|
+
return @list_cache[entity_type][extra_hash] if @list_cache[entity_type][extra_hash]
|
298
|
+
else
|
299
|
+
@list_cache[entity_type] ||= {}
|
300
|
+
end
|
301
|
+
end
|
302
|
+
entities = api_call(entity_type, :index, {'per_page' => 999999}.merge(extra_hash))
|
303
|
+
results = entities['results']
|
304
|
+
@list_cache[entity_type][extra_hash] = results if use_cache
|
305
|
+
elsif @prerequisite[entity_type] == :organizations
|
306
|
+
# check only entities in imported orgs (not all of them)
|
307
|
+
@pm[:organizations].to_hash.values.each do |org_id|
|
308
|
+
entities = api_call(entity_type, :index, {'per_page' => 999999, 'organization_id' => org_id})
|
309
|
+
results += entities['results']
|
310
|
+
end
|
311
|
+
else
|
312
|
+
@cache[@prerequisite[entity_type]].each do |pre_id, _|
|
313
|
+
entities = api_call(
|
314
|
+
entity_type,
|
315
|
+
:index,
|
316
|
+
{
|
317
|
+
'per_page' => 999999,
|
318
|
+
@prerequisite[entity_type].to_s.sub(/s$/, '_id').to_sym => pre_id
|
319
|
+
})
|
320
|
+
results += entities['results']
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
results.each do |entity|
|
325
|
+
entity['id'] = entity['uuid'] if entity_type == :systems
|
326
|
+
@cache[entity_type][entity['id']] = entity
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
def map_entity(entity_type, original_id, id)
|
331
|
+
if @pm[entity_type][original_id]
|
332
|
+
info "#{to_singular(entity_type).capitalize} [#{original_id}->#{@pm[entity_type][original_id]}] already mapped. " \
|
333
|
+
'Skipping.'
|
334
|
+
report_summary :found, entity_type
|
335
|
+
return
|
336
|
+
end
|
337
|
+
info "Mapping #{to_singular(entity_type)} [#{original_id}->#{id}]."
|
338
|
+
@pm[entity_type][original_id] = id
|
339
|
+
report_summary :mapped, entity_type
|
340
|
+
return get_cache(entity_type)[id]
|
341
|
+
end
|
342
|
+
|
343
|
+
def unmap_entity(entity_type, target_id)
|
344
|
+
deleted = @pm[entity_type].delete_value(target_id)
|
345
|
+
info " Unmapped #{to_singular(entity_type)} with id #{target_id}: #{deleted}x" if deleted > 1
|
346
|
+
end
|
347
|
+
|
348
|
+
def find_uniq(arr)
|
349
|
+
uniq = nil
|
350
|
+
uniq = arr[0] if arr[1].is_a?(Array) &&
|
351
|
+
(arr[1][0] =~ /has already been taken/ ||
|
352
|
+
arr[1][0] =~ /already exists/ ||
|
353
|
+
arr[1][0] =~ /must be unique within one organization/)
|
354
|
+
return uniq
|
355
|
+
end
|
356
|
+
|
357
|
+
def found_errors(err)
|
358
|
+
return err && err['errors'] && err['errors'].respond_to?(:each)
|
359
|
+
end
|
360
|
+
|
361
|
+
def recognizable_error(arr)
|
362
|
+
return arr.is_a?(Array) && arr.size >= 2
|
363
|
+
end
|
364
|
+
|
365
|
+
def process_error(err, entity_hash)
|
366
|
+
uniq = nil
|
367
|
+
err['errors'].each do |arr|
|
368
|
+
next unless recognizable_error(arr)
|
369
|
+
uniq = find_uniq(arr)
|
370
|
+
break if uniq && entity_hash.key?(uniq.to_sym)
|
371
|
+
uniq = nil # otherwise uniq is not usable
|
372
|
+
end
|
373
|
+
return uniq
|
374
|
+
end
|
375
|
+
|
376
|
+
# Create entity, with recovery strategy.
|
377
|
+
#
|
378
|
+
# * +:map+ - Use existing entity
|
379
|
+
# * +:rename+ - Change name
|
380
|
+
# * +nil+ - Fail
|
381
|
+
def create_entity(entity_type, entity_hash, original_id, recover = nil, retries = 2)
|
382
|
+
raise ImportRecoveryError, "Creation of #{entity_type} not recovered by " \
|
383
|
+
"'#{recover || option_recover.to_sym}' strategy" if retries < 0
|
384
|
+
uniq = nil
|
385
|
+
begin
|
386
|
+
return _create_entity(entity_type, entity_hash, original_id)
|
387
|
+
rescue RestClient::UnprocessableEntity => ue
|
388
|
+
error " Creation of #{to_singular(entity_type)} failed."
|
389
|
+
uniq = nil
|
390
|
+
err = JSON.parse(ue.response)
|
391
|
+
err = err['error'] if err.key?('error')
|
392
|
+
if found_errors(err)
|
393
|
+
uniq = process_error(err, entity_hash)
|
394
|
+
end
|
395
|
+
raise ue unless uniq
|
396
|
+
end
|
397
|
+
|
398
|
+
uniq = uniq.to_sym
|
399
|
+
|
400
|
+
case recover || option_recover.to_sym
|
401
|
+
when :rename
|
402
|
+
entity_hash[uniq] = original_id.to_s + '-' + entity_hash[uniq]
|
403
|
+
info " Recovering by renaming to: \"#{uniq}\"=\"#{entity_hash[uniq]}\""
|
404
|
+
return create_entity(entity_type, entity_hash, original_id, recover, retries - 1)
|
405
|
+
when :map
|
406
|
+
entity = lookup_entity_in_cache(entity_type, {uniq.to_s => entity_hash[uniq]})
|
407
|
+
if entity
|
408
|
+
info " Recovering by remapping to: #{entity['id']}"
|
409
|
+
return map_entity(entity_type, original_id, entity['id'])
|
410
|
+
else
|
411
|
+
warn "Creation of #{entity_type} not recovered by \'#{recover}\' strategy."
|
412
|
+
raise ImportRecoveryError, "Creation of #{entity_type} not recovered by \'#{recover}\' strategy."
|
413
|
+
end
|
414
|
+
else
|
415
|
+
fatal 'No recover strategy.'
|
416
|
+
raise ue
|
417
|
+
end
|
418
|
+
nil
|
419
|
+
end
|
420
|
+
|
421
|
+
# Use +create_entity+ instead.
|
422
|
+
def _create_entity(entity_type, entity_hash, original_id)
|
423
|
+
type = to_singular(entity_type)
|
424
|
+
if @pm[entity_type][original_id]
|
425
|
+
info type.capitalize + ' [' + original_id.to_s + '->' + @pm[entity_type][original_id].to_s + '] already imported.'
|
426
|
+
report_summary :found, entity_type
|
427
|
+
return get_cache(entity_type)[@pm[entity_type][original_id]]
|
428
|
+
else
|
429
|
+
info 'Creating new ' + type + ': ' + entity_hash.values_at(:name, :label, :login).compact[0]
|
430
|
+
entity_hash = {@wrap_out[entity_type] => entity_hash} if @wrap_out[entity_type]
|
431
|
+
debug "entity_hash: #{entity_hash.inspect}"
|
432
|
+
entity = mapped_api_call(entity_type, :create, entity_hash)
|
433
|
+
debug "created entity: #{entity.inspect}"
|
434
|
+
entity = entity[@wrap_in[entity_type]] if @wrap_in[entity_type]
|
435
|
+
# workaround for Bug
|
436
|
+
entity['id'] = entity['uuid'] if entity_type == :systems
|
437
|
+
@pm[entity_type][original_id] = entity['id']
|
438
|
+
get_cache(entity_type)[entity['id']] = entity
|
439
|
+
debug "@pm[#{entity_type}]: #{@pm[entity_type].inspect}"
|
440
|
+
report_summary :created, entity_type
|
441
|
+
return entity
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
def update_entity(entity_type, id, entity_hash)
|
446
|
+
info "Updating #{to_singular(entity_type)} with id: #{id}"
|
447
|
+
mapped_api_call(entity_type, :update, {:id => id}.merge!(entity_hash))
|
448
|
+
end
|
449
|
+
|
450
|
+
# Delete entity by original (Sat5) id
|
451
|
+
def delete_entity(entity_type, original_id)
|
452
|
+
type = to_singular(entity_type)
|
453
|
+
unless @pm[entity_type][original_id]
|
454
|
+
error 'Unknown ' + type + ' to delete [' + original_id.to_s + '].'
|
455
|
+
return nil
|
456
|
+
end
|
457
|
+
info 'Deleting imported ' + type + ' [' + original_id.to_s + '->' + @pm[entity_type][original_id].to_s + '].'
|
458
|
+
begin
|
459
|
+
mapped_api_call(entity_type, :destroy, {:id => @pm[entity_type][original_id]})
|
460
|
+
# delete from cache
|
461
|
+
get_cache(entity_type).delete(@pm[entity_type][original_id])
|
462
|
+
# delete from pm
|
463
|
+
unmap_entity(entity_type, @pm[entity_type][original_id])
|
464
|
+
report_summary :deleted, entity_type
|
465
|
+
rescue => e
|
466
|
+
warn "Delete of #{to_singular(entity_type)} [#{original_id}] failed with #{e.class}: #{e.message}"
|
467
|
+
report_summary :failed, entity_type
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
# Delete entity by target (Sat6) id
|
472
|
+
def delete_entity_by_import_id(entity_type, import_id, delete_key = 'id')
|
473
|
+
type = to_singular(entity_type)
|
474
|
+
original_id = get_original_id(entity_type, import_id)
|
475
|
+
if original_id.nil?
|
476
|
+
error 'Unknown imported ' + type + ' to delete [' + import_id.to_s + '].'
|
477
|
+
return nil
|
478
|
+
end
|
479
|
+
info "Deleting imported #{type} [#{original_id}->#{@pm[entity_type][original_id]}]."
|
480
|
+
if delete_key == 'id'
|
481
|
+
delete_id = import_id
|
482
|
+
else
|
483
|
+
delete_id = get_cache(entity_type)[import_id][delete_key]
|
484
|
+
end
|
485
|
+
begin
|
486
|
+
mapped_api_call(entity_type, :destroy, {:id => delete_id})
|
487
|
+
# delete from cache
|
488
|
+
get_cache(entity_type).delete(import_id)
|
489
|
+
# delete from pm
|
490
|
+
@pm[entity_type].delete original_id
|
491
|
+
report_summary :deleted, entity_type
|
492
|
+
rescue => e
|
493
|
+
warn "Delete of #{to_singular(entity_type)} [#{delete_id}] failed with #{e.class}: #{e.message}"
|
494
|
+
report_summary :failed, entity_type
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
# Wait for asynchronous task.
|
499
|
+
#
|
500
|
+
# * +uuid+ - UUID of async task.
|
501
|
+
# * +start_wait+ - Seconds to wait before first check.
|
502
|
+
# * +delta_wait+ - How much longer will every next wait be (unless +max_wait+ is reached).
|
503
|
+
# * +max_wait+ - Maximum time to wait between two checks.
|
504
|
+
def wait_for_task(uuid, start_wait = 0, delta_wait = 1, max_wait = 10)
|
505
|
+
wait_time = start_wait
|
506
|
+
if option_quiet?
|
507
|
+
info "Waiting for the task [#{uuid}] "
|
508
|
+
else
|
509
|
+
print "Waiting for the task [#{uuid}] "
|
510
|
+
end
|
511
|
+
|
512
|
+
loop do
|
513
|
+
sleep wait_time
|
514
|
+
wait_time = [wait_time + delta_wait, max_wait].min
|
515
|
+
print '.' unless option_quiet?
|
516
|
+
STDOUT.flush unless option_quiet?
|
517
|
+
task = api_call(:foreman_tasks, :show, {:id => uuid})
|
518
|
+
next unless task['state'] == 'stopped'
|
519
|
+
print "\n" unless option_quiet?
|
520
|
+
return task['return'] == 'success'
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
def cvs_iterate(filename, action)
|
525
|
+
CSVHelper.csv_each filename, self.class.csv_columns do |data|
|
526
|
+
handle_missing_and_supress "processing CSV line:\n#{data.inspect}" do
|
527
|
+
action.call(data)
|
528
|
+
end
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
def import(filename)
|
533
|
+
cvs_iterate(filename, (method :import_single_row))
|
534
|
+
end
|
535
|
+
|
536
|
+
def post_import(_csv_file)
|
537
|
+
# empty by default
|
538
|
+
end
|
539
|
+
|
540
|
+
def post_delete(_csv_file)
|
541
|
+
# empty by default
|
542
|
+
end
|
543
|
+
|
544
|
+
def delete(filename)
|
545
|
+
cvs_iterate(filename, (method :delete_single_row))
|
546
|
+
end
|
547
|
+
|
548
|
+
def execute
|
549
|
+
# Get set up to do logging as soon as reasonably possible
|
550
|
+
setup_logging
|
551
|
+
# create a storage directory if not exists yet
|
552
|
+
Dir.mkdir data_dir unless File.directory? data_dir
|
553
|
+
|
554
|
+
# initialize apipie binding
|
555
|
+
self.class.api_init
|
556
|
+
load_persistent_maps
|
557
|
+
load_cache
|
558
|
+
prune_persistent_maps @cache
|
559
|
+
# TODO: This big ugly thing might need some cleanup
|
560
|
+
begin
|
561
|
+
if option_delete?
|
562
|
+
info "Deleting from #{option_csv_file}"
|
563
|
+
delete option_csv_file
|
564
|
+
handle_missing_and_supress 'post_delete' do
|
565
|
+
post_delete option_csv_file
|
566
|
+
end
|
567
|
+
else
|
568
|
+
info "Importing from #{option_csv_file}"
|
569
|
+
import option_csv_file
|
570
|
+
handle_missing_and_supress 'post_import' do
|
571
|
+
post_import option_csv_file
|
572
|
+
end
|
573
|
+
end
|
574
|
+
atr_exit
|
575
|
+
rescue StandardError, SystemExit, Interrupt => e
|
576
|
+
error "Exiting: #{e}"
|
577
|
+
logtrace e
|
578
|
+
end
|
579
|
+
save_persistent_maps
|
580
|
+
print_summary
|
581
|
+
HammerCLI::EX_OK
|
582
|
+
end
|
583
|
+
end
|
584
|
+
end
|
585
|
+
# vim: autoindent tabstop=2 shiftwidth=2 expandtab softtabstop=2 filetype=ruby
|