chronicle-etl 0.5.2 → 0.5.5
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 +4 -4
- data/README.md +72 -46
- data/chronicle-etl.gemspec +7 -1
- data/lib/chronicle/etl/authorization_server.rb +48 -0
- data/lib/chronicle/etl/authorizer.rb +37 -0
- data/lib/chronicle/etl/cli/authorizations.rb +63 -0
- data/lib/chronicle/etl/cli/cli_base.rb +4 -0
- data/lib/chronicle/etl/cli/connectors.rb +2 -2
- data/lib/chronicle/etl/cli/jobs.rb +16 -3
- data/lib/chronicle/etl/cli/main.rb +32 -20
- data/lib/chronicle/etl/cli/plugins.rb +19 -15
- data/lib/chronicle/etl/cli/secrets.rb +1 -1
- data/lib/chronicle/etl/cli.rb +1 -0
- data/lib/chronicle/etl/config.rb +3 -0
- data/lib/chronicle/etl/configurable.rb +4 -0
- data/lib/chronicle/etl/exceptions.rb +5 -0
- data/lib/chronicle/etl/job_definition.rb +4 -4
- data/lib/chronicle/etl/logger.rb +6 -6
- data/lib/chronicle/etl/models/base.rb +1 -1
- data/lib/chronicle/etl/models/entity.rb +3 -1
- data/lib/chronicle/etl/oauth_authorizer.rb +140 -0
- data/lib/chronicle/etl/registry/connectors.rb +60 -0
- data/lib/chronicle/etl/registry/plugin_registration.rb +19 -0
- data/lib/chronicle/etl/registry/plugins.rb +163 -0
- data/lib/chronicle/etl/registry/registry.rb +3 -52
- data/lib/chronicle/etl/registry/self_registering.rb +1 -1
- data/lib/chronicle/etl/runner.rb +53 -26
- data/lib/chronicle/etl/secrets.rb +15 -1
- data/lib/chronicle/etl/transformers/transformer.rb +4 -0
- data/lib/chronicle/etl/version.rb +1 -1
- data/lib/chronicle/etl.rb +1 -0
- metadata +101 -11
- data/lib/chronicle/etl/registry/plugin_registry.rb +0 -84
data/lib/chronicle/etl/runner.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'colorize'
|
2
2
|
require 'chronic_duration'
|
3
|
+
require "tty-spinner"
|
3
4
|
|
4
5
|
class Chronicle::ETL::Runner
|
5
6
|
def initialize(job)
|
@@ -8,76 +9,101 @@ class Chronicle::ETL::Runner
|
|
8
9
|
end
|
9
10
|
|
10
11
|
def run!
|
12
|
+
begin_job
|
11
13
|
validate_job
|
12
14
|
instantiate_connectors
|
13
15
|
prepare_job
|
14
16
|
prepare_ui
|
15
17
|
run_extraction
|
18
|
+
rescue Chronicle::ETL::ExtractionError => e
|
19
|
+
@job_logger&.error
|
20
|
+
raise(Chronicle::ETL::RunnerError, "Extraction failed. #{e.message}")
|
21
|
+
rescue Interrupt
|
22
|
+
@job_logger&.error
|
23
|
+
raise(Chronicle::ETL::RunInterruptedError, "Job interrupted.")
|
24
|
+
rescue StandardError => e
|
25
|
+
# Just throwing this in here until we have better exception handling in
|
26
|
+
# loaders, etc
|
27
|
+
@job_logger&.error
|
28
|
+
raise(Chronicle::ETL::RunnerError, "Error running job. #{e.message}")
|
29
|
+
ensure
|
16
30
|
finish_job
|
17
31
|
end
|
18
32
|
|
19
33
|
private
|
20
34
|
|
35
|
+
def begin_job
|
36
|
+
Chronicle::ETL::Logger.info(tty_log_job_initialize)
|
37
|
+
@initialization_spinner = TTY::Spinner.new(":spinner :title", format: :dots_2)
|
38
|
+
end
|
39
|
+
|
21
40
|
def validate_job
|
41
|
+
@initialization_spinner.update(title: "Validating job")
|
22
42
|
@job.job_definition.validate!
|
23
43
|
end
|
24
44
|
|
25
45
|
def instantiate_connectors
|
46
|
+
@initialization_spinner.update(title: "Initializing connectors")
|
26
47
|
@extractor = @job.instantiate_extractor
|
27
48
|
@loader = @job.instantiate_loader
|
28
49
|
end
|
29
50
|
|
30
51
|
def prepare_job
|
31
|
-
|
52
|
+
@initialization_spinner.update(title: "Preparing job")
|
32
53
|
@job_logger.start
|
33
54
|
@loader.start
|
55
|
+
|
56
|
+
@initialization_spinner.update(title: "Preparing extraction")
|
57
|
+
@initialization_spinner.auto_spin
|
34
58
|
@extractor.prepare
|
59
|
+
@initialization_spinner.success("(#{'successful'.green})")
|
60
|
+
Chronicle::ETL::Logger.info("\n")
|
35
61
|
end
|
36
62
|
|
37
63
|
def prepare_ui
|
38
64
|
total = @extractor.results_count
|
39
65
|
@progress_bar = Chronicle::ETL::Utils::ProgressBar.new(title: 'Running job', total: total)
|
40
|
-
Chronicle::ETL::Logger.
|
66
|
+
Chronicle::ETL::Logger.attach_to_ui(@progress_bar)
|
41
67
|
end
|
42
68
|
|
43
|
-
# TODO: refactor this further
|
44
69
|
def run_extraction
|
45
70
|
@extractor.extract do |extraction|
|
46
|
-
|
47
|
-
raise Chronicle::ETL::RunnerTypeError, "Extracted should be a Chronicle::ETL::Extraction"
|
48
|
-
end
|
49
|
-
|
50
|
-
transformer = @job.instantiate_transformer(extraction)
|
51
|
-
record = transformer.transform
|
52
|
-
|
53
|
-
Chronicle::ETL::Logger.debug(tty_log_transformation(transformer))
|
54
|
-
@job_logger.log_transformation(transformer)
|
55
|
-
|
56
|
-
@loader.load(record) unless @job.dry_run?
|
57
|
-
rescue Chronicle::ETL::TransformationError => e
|
58
|
-
Chronicle::ETL::Logger.error(tty_log_transformation_failure(e, transformer))
|
59
|
-
ensure
|
71
|
+
process_extraction(extraction)
|
60
72
|
@progress_bar.increment
|
61
73
|
end
|
62
74
|
|
63
75
|
@progress_bar.finish
|
76
|
+
|
77
|
+
# This is typically a slow method (writing to stdout, writing a big file, etc)
|
78
|
+
# TODO: consider adding a spinner?
|
64
79
|
@loader.finish
|
65
80
|
@job_logger.finish
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
81
|
+
end
|
82
|
+
|
83
|
+
def process_extraction(extraction)
|
84
|
+
# For each extraction from our extractor, we create a new tarnsformer
|
85
|
+
transformer = @job.instantiate_transformer(extraction)
|
86
|
+
|
87
|
+
# And then transform that record, logging it if we're in debug log level
|
88
|
+
record = transformer.transform
|
89
|
+
Chronicle::ETL::Logger.debug(tty_log_transformation(transformer))
|
90
|
+
@job_logger.log_transformation(transformer)
|
91
|
+
|
92
|
+
# Then send the results to the loader
|
93
|
+
@loader.load(record) unless @job.dry_run?
|
94
|
+
rescue Chronicle::ETL::TransformationError => e
|
95
|
+
# TODO: have an option to cancel job if we encounter an error
|
96
|
+
Chronicle::ETL::Logger.error(tty_log_transformation_failure(e, transformer))
|
71
97
|
end
|
72
98
|
|
73
99
|
def finish_job
|
74
100
|
@job_logger.save
|
75
101
|
@progress_bar&.finish
|
76
|
-
Chronicle::ETL::Logger.
|
102
|
+
Chronicle::ETL::Logger.detach_from_ui
|
77
103
|
Chronicle::ETL::Logger.info(tty_log_completion)
|
78
104
|
end
|
79
105
|
|
80
|
-
def
|
106
|
+
def tty_log_job_initialize
|
81
107
|
output = "Beginning job "
|
82
108
|
output += "'#{@job.name}'".bold if @job.name
|
83
109
|
output
|
@@ -95,8 +121,9 @@ class Chronicle::ETL::Runner
|
|
95
121
|
|
96
122
|
def tty_log_completion
|
97
123
|
status = @job_logger.success ? 'Success' : 'Failed'
|
98
|
-
|
99
|
-
output
|
124
|
+
job_completion = @job_logger.success ? 'Completed' : 'Partially completed'
|
125
|
+
output = "\n#{job_completion} job"
|
126
|
+
output += " '#{@job.name}'".bold if @job.name
|
100
127
|
output += " in #{ChronicDuration.output(@job_logger.duration)}" if @job_logger.duration
|
101
128
|
output += "\n Status:\t".light_black + status
|
102
129
|
output += "\n Completed:\t".light_black + "#{@job_logger.job_log.num_records_processed}"
|
@@ -1,9 +1,16 @@
|
|
1
|
+
require "active_support/core_ext/hash/keys"
|
2
|
+
|
1
3
|
module Chronicle
|
2
4
|
module ETL
|
3
5
|
# Secret management module
|
4
6
|
module Secrets
|
5
7
|
module_function
|
6
8
|
|
9
|
+
# Whether a given namespace exists
|
10
|
+
def exists?(namespace)
|
11
|
+
Chronicle::ETL::Config.exists?("secrets", namespace)
|
12
|
+
end
|
13
|
+
|
7
14
|
# Save a setting to a namespaced config file
|
8
15
|
def set(namespace, key, value)
|
9
16
|
config = read(namespace)
|
@@ -11,6 +18,13 @@ module Chronicle
|
|
11
18
|
write(namespace, config)
|
12
19
|
end
|
13
20
|
|
21
|
+
# Save a hash to a secrets namespace
|
22
|
+
def set_all(namespace, secrets)
|
23
|
+
config = read(namespace)
|
24
|
+
config = config.merge(secrets.deep_stringify_keys)
|
25
|
+
write(namespace, config)
|
26
|
+
end
|
27
|
+
|
14
28
|
# Remove a setting from a namespaced config file
|
15
29
|
def unset(namespace, key)
|
16
30
|
config = read(namespace)
|
@@ -42,7 +56,7 @@ module Chronicle
|
|
42
56
|
data = {
|
43
57
|
secrets: (secrets || {}).transform_keys(&:to_s),
|
44
58
|
chronicle_etl_version: Chronicle::ETL::VERSION
|
45
|
-
}
|
59
|
+
}
|
46
60
|
Chronicle::ETL::Config.write("secrets", namespace, data)
|
47
61
|
end
|
48
62
|
|
@@ -10,6 +10,10 @@ module Chronicle
|
|
10
10
|
# options::
|
11
11
|
# Options for configuring this Transformer
|
12
12
|
def initialize(extraction, options = {})
|
13
|
+
unless extraction.is_a?(Chronicle::ETL::Extraction)
|
14
|
+
raise Chronicle::ETL::RunnerTypeError, "Extracted should be a Chronicle::ETL::Extraction"
|
15
|
+
end
|
16
|
+
|
13
17
|
@extraction = extraction
|
14
18
|
apply_options(options)
|
15
19
|
end
|
data/lib/chronicle/etl.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chronicle-etl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Louis
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-05-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -52,6 +52,34 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: 0.8.1
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: gems
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: launchy
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
55
83
|
- !ruby/object:Gem::Dependency
|
56
84
|
name: marcel
|
57
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,6 +122,20 @@ dependencies:
|
|
94
122
|
- - "~>"
|
95
123
|
- !ruby/object:Gem::Version
|
96
124
|
version: '1.13'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: omniauth
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '2'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '2'
|
97
139
|
- !ruby/object:Gem::Dependency
|
98
140
|
name: sequel
|
99
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,6 +150,20 @@ dependencies:
|
|
108
150
|
- - "~>"
|
109
151
|
- !ruby/object:Gem::Version
|
110
152
|
version: '5.35'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: sinatra
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '2'
|
160
|
+
type: :runtime
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '2'
|
111
167
|
- !ruby/object:Gem::Dependency
|
112
168
|
name: sqlite3
|
113
169
|
requirement: !ruby/object:Gem::Requirement
|
@@ -235,33 +291,33 @@ dependencies:
|
|
235
291
|
- !ruby/object:Gem::Version
|
236
292
|
version: '2.1'
|
237
293
|
- !ruby/object:Gem::Dependency
|
238
|
-
name:
|
294
|
+
name: fakefs
|
239
295
|
requirement: !ruby/object:Gem::Requirement
|
240
296
|
requirements:
|
241
297
|
- - "~>"
|
242
298
|
- !ruby/object:Gem::Version
|
243
|
-
version: 4
|
299
|
+
version: '1.4'
|
244
300
|
type: :development
|
245
301
|
prerelease: false
|
246
302
|
version_requirements: !ruby/object:Gem::Requirement
|
247
303
|
requirements:
|
248
304
|
- - "~>"
|
249
305
|
- !ruby/object:Gem::Version
|
250
|
-
version: 4
|
306
|
+
version: '1.4'
|
251
307
|
- !ruby/object:Gem::Dependency
|
252
|
-
name:
|
308
|
+
name: guard-rspec
|
253
309
|
requirement: !ruby/object:Gem::Requirement
|
254
310
|
requirements:
|
255
|
-
- - "
|
311
|
+
- - "~>"
|
256
312
|
- !ruby/object:Gem::Version
|
257
|
-
version:
|
313
|
+
version: 4.7.3
|
258
314
|
type: :development
|
259
315
|
prerelease: false
|
260
316
|
version_requirements: !ruby/object:Gem::Requirement
|
261
317
|
requirements:
|
262
|
-
- - "
|
318
|
+
- - "~>"
|
263
319
|
- !ruby/object:Gem::Version
|
264
|
-
version:
|
320
|
+
version: 4.7.3
|
265
321
|
- !ruby/object:Gem::Dependency
|
266
322
|
name: pry-byebug
|
267
323
|
requirement: !ruby/object:Gem::Requirement
|
@@ -332,6 +388,34 @@ dependencies:
|
|
332
388
|
- - "~>"
|
333
389
|
- !ruby/object:Gem::Version
|
334
390
|
version: '0.21'
|
391
|
+
- !ruby/object:Gem::Dependency
|
392
|
+
name: vcr
|
393
|
+
requirement: !ruby/object:Gem::Requirement
|
394
|
+
requirements:
|
395
|
+
- - "~>"
|
396
|
+
- !ruby/object:Gem::Version
|
397
|
+
version: '6.1'
|
398
|
+
type: :development
|
399
|
+
prerelease: false
|
400
|
+
version_requirements: !ruby/object:Gem::Requirement
|
401
|
+
requirements:
|
402
|
+
- - "~>"
|
403
|
+
- !ruby/object:Gem::Version
|
404
|
+
version: '6.1'
|
405
|
+
- !ruby/object:Gem::Dependency
|
406
|
+
name: webmock
|
407
|
+
requirement: !ruby/object:Gem::Requirement
|
408
|
+
requirements:
|
409
|
+
- - "~>"
|
410
|
+
- !ruby/object:Gem::Version
|
411
|
+
version: '3'
|
412
|
+
type: :development
|
413
|
+
prerelease: false
|
414
|
+
version_requirements: !ruby/object:Gem::Requirement
|
415
|
+
requirements:
|
416
|
+
- - "~>"
|
417
|
+
- !ruby/object:Gem::Version
|
418
|
+
version: '3'
|
335
419
|
- !ruby/object:Gem::Dependency
|
336
420
|
name: yard
|
337
421
|
requirement: !ruby/object:Gem::Requirement
|
@@ -372,7 +456,10 @@ files:
|
|
372
456
|
- chronicle-etl.gemspec
|
373
457
|
- exe/chronicle-etl
|
374
458
|
- lib/chronicle/etl.rb
|
459
|
+
- lib/chronicle/etl/authorization_server.rb
|
460
|
+
- lib/chronicle/etl/authorizer.rb
|
375
461
|
- lib/chronicle/etl/cli.rb
|
462
|
+
- lib/chronicle/etl/cli/authorizations.rb
|
376
463
|
- lib/chronicle/etl/cli/cli_base.rb
|
377
464
|
- lib/chronicle/etl/cli/connectors.rb
|
378
465
|
- lib/chronicle/etl/cli/jobs.rb
|
@@ -407,8 +494,11 @@ files:
|
|
407
494
|
- lib/chronicle/etl/models/base.rb
|
408
495
|
- lib/chronicle/etl/models/entity.rb
|
409
496
|
- lib/chronicle/etl/models/raw.rb
|
497
|
+
- lib/chronicle/etl/oauth_authorizer.rb
|
410
498
|
- lib/chronicle/etl/registry/connector_registration.rb
|
411
|
-
- lib/chronicle/etl/registry/
|
499
|
+
- lib/chronicle/etl/registry/connectors.rb
|
500
|
+
- lib/chronicle/etl/registry/plugin_registration.rb
|
501
|
+
- lib/chronicle/etl/registry/plugins.rb
|
412
502
|
- lib/chronicle/etl/registry/registry.rb
|
413
503
|
- lib/chronicle/etl/registry/self_registering.rb
|
414
504
|
- lib/chronicle/etl/runner.rb
|
@@ -1,84 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'rubygems/command'
|
3
|
-
require 'rubygems/commands/install_command'
|
4
|
-
require 'rubygems/uninstaller'
|
5
|
-
|
6
|
-
module Chronicle
|
7
|
-
module ETL
|
8
|
-
module Registry
|
9
|
-
# Responsible for managing plugins available to chronicle-etl
|
10
|
-
#
|
11
|
-
# @todo Better validation for whether a gem is actually a plugin
|
12
|
-
# @todo Add ways to load a plugin that don't require a gem on rubygems.org
|
13
|
-
module PluginRegistry
|
14
|
-
# Does this plugin exist?
|
15
|
-
def self.exists?(name)
|
16
|
-
# TODO: implement this. Could query rubygems.org or use a hardcoded
|
17
|
-
# list somewhere
|
18
|
-
true
|
19
|
-
end
|
20
|
-
|
21
|
-
# All versions of all plugins currently installed
|
22
|
-
def self.all_installed
|
23
|
-
# TODO: add check for chronicle-etl dependency
|
24
|
-
Gem::Specification.filter { |s| s.name.match(/^chronicle-/) && s.name != "chronicle-etl" }
|
25
|
-
end
|
26
|
-
|
27
|
-
# Latest version of each installed plugin
|
28
|
-
def self.all_installed_latest
|
29
|
-
all_installed.group_by(&:name)
|
30
|
-
.transform_values { |versions| versions.sort_by(&:version).reverse.first }
|
31
|
-
.values
|
32
|
-
end
|
33
|
-
|
34
|
-
# Check whether a given plugin is installed
|
35
|
-
def self.installed?(name)
|
36
|
-
gem_name = "chronicle-#{name}"
|
37
|
-
all_installed.map(&:name).include?(gem_name)
|
38
|
-
end
|
39
|
-
|
40
|
-
# Activate a plugin with given name by `require`ing it
|
41
|
-
def self.activate(name)
|
42
|
-
# By default, activates the latest available version of a gem
|
43
|
-
# so don't have to run Kernel#gem separately
|
44
|
-
require "chronicle/#{name}"
|
45
|
-
rescue Gem::ConflictError => e
|
46
|
-
# TODO: figure out if there's more we can do here
|
47
|
-
raise Chronicle::ETL::PluginConflictError.new(name), "Plugin '#{name}' couldn't be loaded. #{e.message}"
|
48
|
-
rescue StandardError, LoadError => e
|
49
|
-
# StandardError to catch random non-loading problems that might occur
|
50
|
-
# when requiring the plugin (eg class macro invoked the wrong way)
|
51
|
-
# TODO: decide if this should be separated
|
52
|
-
raise Chronicle::ETL::PluginLoadError.new(name), "Plugin '#{name}' couldn't be loaded"
|
53
|
-
end
|
54
|
-
|
55
|
-
# Install a plugin to local gems
|
56
|
-
def self.install(name)
|
57
|
-
return if installed?(name)
|
58
|
-
|
59
|
-
gem_name = "chronicle-#{name}"
|
60
|
-
raise(Chronicle::ETL::PluginNotAvailableError.new(gem_name), "Plugin #{name} doesn't exist") unless exists?(gem_name)
|
61
|
-
|
62
|
-
Gem::DefaultUserInteraction.ui = Gem::SilentUI.new
|
63
|
-
Gem.install(gem_name)
|
64
|
-
|
65
|
-
activate(name)
|
66
|
-
rescue Gem::UnsatisfiableDependencyError
|
67
|
-
# TODO: we need to catch a lot more than this here
|
68
|
-
raise Chronicle::ETL::PluginNotAvailableError.new(name), "Plugin #{name} could not be installed."
|
69
|
-
end
|
70
|
-
|
71
|
-
# Uninstall a plugin
|
72
|
-
def self.uninstall(name)
|
73
|
-
gem_name = "chronicle-#{name}"
|
74
|
-
Gem::DefaultUserInteraction.ui = Gem::SilentUI.new
|
75
|
-
uninstaller = Gem::Uninstaller.new(gem_name)
|
76
|
-
uninstaller.uninstall
|
77
|
-
rescue Gem::InstallError
|
78
|
-
# TODO: strengthen this exception handling
|
79
|
-
raise(Chronicle::ETL::PluginError.new(name), "Plugin #{name} wasn't uninstalled")
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|