chronicle-etl 0.4.2 → 0.4.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f041e90fc6019ecbc2424e8e75e7f21fa5ba715c2cdbde73d61c298e9ca82e07
4
- data.tar.gz: 2feda7669f8fefc7ad80fe7918530a86ad2ced431f75e70ad42653c871b67d90
3
+ metadata.gz: f2b6fdca3723ec52287c070a0dd08d0cfaf825f5e8f46da0d5a34172c0008573
4
+ data.tar.gz: e15181ba7edc1698404af8ff8c05d5367786ea809360393e825ca5ee5eef6c75
5
5
  SHA512:
6
- metadata.gz: 4a40c72dcb6514037c6e53214dc0af3bfba20c272b959c3e83496658b4b8dc3f841d7399aa215a5fcc5c5cd494278223666b8b881f645ed2c61b667351ccde94
7
- data.tar.gz: 5b5450b76a8c03d7cb8405888b2e059390c1585a66524dd7403b458c828a1936422e03a9654ec6d270b634bf6bfe17d6dafda54e05aca8a8732207366d03ffd2
6
+ metadata.gz: c5508dcfc5e1367122ebbc191dc60a76cc7b1f088ae9ebd52bae41e07420f54ea7977a35f2b25839933b06d4673e2a120feecbd958efee07e0d313eaa7a5d167
7
+ data.tar.gz: adcb90549af364189c5ae3b0811c039277aba7dc6fbf2fbd6de8b89c572948d0dc00ba8da59db03252f4770737423e62c3a1175ef018742ef8bd7aee14837f63
@@ -0,0 +1,31 @@
1
+ module Chronicle
2
+ module ETL
3
+ module CLI
4
+ # Base class for CLI commands
5
+ class CLIBase < ::Thor
6
+ no_commands do
7
+ # Shorthand for cli_exit(status: :failure)
8
+ def cli_fail(message: nil, exception: nil)
9
+ cli_exit(status: :failure, message: message, exception: exception)
10
+ end
11
+
12
+ # Exit from CLI
13
+ #
14
+ # @params status Can be eitiher :success or :failure
15
+ # @params message to print
16
+ # @params exception stacktrace if log_level is set to debug
17
+ def cli_exit(status: :success, message: nil, exception: nil)
18
+ exit_code = status == :success ? 0 : 1
19
+ log_level = status == :success ? :info : :fatal
20
+
21
+ message = message.red if status != :success
22
+
23
+ Chronicle::ETL::Logger.debug(exception.full_message) if exception
24
+ Chronicle::ETL::Logger.send(log_level, message) if message
25
+ exit(exit_code)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -39,15 +39,13 @@ module Chronicle
39
39
  desc "show PHASE IDENTIFIER", "Show information about a connector"
40
40
  def show(phase, identifier)
41
41
  unless ['extractor', 'transformer', 'loader'].include?(phase)
42
- Chronicle::ETL::Logger.fatal("Phase argument must be one of: [extractor, transformer, loader]")
43
- exit 1
42
+ cli_fail(message: "Phase argument must be one of: [extractor, transformer, loader]")
44
43
  end
45
44
 
46
45
  begin
47
46
  connector = Chronicle::ETL::Registry.find_by_phase_and_identifier(phase.to_sym, identifier)
48
- rescue Chronicle::ETL::ConnectorNotAvailableError, Chronicle::ETL::PluginError
49
- Chronicle::ETL::Logger.fatal("Could not find #{phase} #{identifier}")
50
- exit 1
47
+ rescue Chronicle::ETL::ConnectorNotAvailableError, Chronicle::ETL::PluginError => e
48
+ cli_fail(message: "Could not find #{phase} #{identifier}", exception: e)
51
49
  end
52
50
 
53
51
  puts connector.klass.to_s.bold
@@ -45,15 +45,19 @@ module Chronicle
45
45
  LONG_DESC
46
46
  # Run an ETL job
47
47
  def start
48
- run_job(options)
49
- rescue Chronicle::ETL::JobDefinitionError => e
50
- missing_plugins = e.job_definition.errors
51
- .select { |error| error.is_a?(Chronicle::ETL::PluginLoadError) }
52
- .map(&:name)
53
- .uniq
48
+ job_definition = build_job_definition(options)
49
+
50
+ if job_definition.plugins_missing?
51
+ missing_plugins = job_definition.errors[:plugins]
52
+ .select { |error| error.is_a?(Chronicle::ETL::PluginLoadError) }
53
+ .map(&:name)
54
+ .uniq
55
+ install_missing_plugins(missing_plugins)
56
+ end
54
57
 
55
- install_missing_plugins(missing_plugins)
56
- run_job(options)
58
+ run_job(job_definition)
59
+ rescue Chronicle::ETL::JobDefinitionError => e
60
+ cli_fail(message: "Error running job.\n#{job_definition.errors}", exception: e)
57
61
  end
58
62
 
59
63
  desc "create", "Create a job"
@@ -65,8 +69,7 @@ LONG_DESC
65
69
  path = File.join('chronicle', 'etl', 'jobs', options[:name])
66
70
  Chronicle::ETL::Config.write(path, job_definition.definition)
67
71
  rescue Chronicle::ETL::JobDefinitionError => e
68
- Chronicle::ETL::Logger.debug(e.full_message)
69
- Chronicle::ETL::Logger.fatal("Job definition error".red)
72
+ cli_fail(message: "Job definition error", exception: e)
70
73
  end
71
74
 
72
75
  desc "show", "Show details about a job"
@@ -76,9 +79,7 @@ LONG_DESC
76
79
  job_definition.validate!
77
80
  puts Chronicle::ETL::Job.new(job_definition)
78
81
  rescue Chronicle::ETL::JobDefinitionError => e
79
- Chronicle::ETL::Logger.debug(e.full_message)
80
- Chronicle::ETL::Logger.fatal("Job definition error".red)
81
- exit 1
82
+ cli_fail(message: "Job definition error", exception: e)
82
83
  end
83
84
 
84
85
  desc "list", "List all available jobs"
@@ -102,15 +103,12 @@ LONG_DESC
102
103
  table = TTY::Table.new(headers, job_details)
103
104
  puts table.render(indent: 0, padding: [0, 2])
104
105
  rescue Chronicle::ETL::ConfigError => e
105
- Chronicle::ETL::Logger.debug(e.full_message)
106
- Chronicle::ETL::Logger.fatal("Error reading config. #{e.message}".red)
107
- exit 1
106
+ cli_fail(message: "Config error. #{e.message}", exception: e)
108
107
  end
109
108
 
110
109
  private
111
110
 
112
- def run_job(options)
113
- job_definition = build_job_definition(options)
111
+ def run_job(job_definition)
114
112
  job = Chronicle::ETL::Job.new(job_definition)
115
113
  runner = Chronicle::ETL::Runner.new(job)
116
114
  runner.run!
@@ -123,18 +121,10 @@ LONG_DESC
123
121
  message += "Do you want to install "
124
122
  message += missing_plugins.map { |name| "chronicle-#{name}".bold}.join(", ")
125
123
  message += " and start the job?"
126
- install = prompt.yes?(message)
127
- return unless install
124
+ will_install = prompt.yes?(message)
125
+ cli_fail(message: "Must install #{missing_plugins.join(", ")} plugin to run job") unless will_install
128
126
 
129
- spinner = TTY::Spinner.new("[:spinner] Installing plugins...", format: :dots_2)
130
- spinner.auto_spin
131
- missing_plugins.each do |plugin|
132
- Chronicle::ETL::Registry::PluginRegistry.install(plugin)
133
- end
134
- spinner.success("(#{'successful'.green})")
135
- rescue Chronicle::ETL::PluginNotAvailableError => e
136
- spinner.error("Error".red)
137
- Chronicle::ETL::Logger.fatal("Plugin '#{e.name}' could not be installed".red)
127
+ Chronicle::ETL::CLI::Plugins.new.install(*missing_plugins)
138
128
  end
139
129
 
140
130
  # Create job definition by reading config file and then overwriting with flag options
@@ -4,7 +4,7 @@ module Chronicle
4
4
  module ETL
5
5
  module CLI
6
6
  # Main entrypoint for CLI app
7
- class Main < ::Thor
7
+ class Main < Chronicle::ETL::CLI::CLIBase
8
8
  class_before :set_log_level
9
9
  class_before :set_color_output
10
10
 
@@ -92,6 +92,9 @@ module Chronicle
92
92
  end
93
93
 
94
94
  no_commands do
95
+ def testb
96
+ puts "hi"
97
+ end
95
98
  def set_color_output
96
99
  String.disable_colorization true if options[:'no-color'] || ENV['NO_COLOR']
97
100
  end
@@ -3,7 +3,6 @@
3
3
  require "tty-prompt"
4
4
  require "tty-spinner"
5
5
 
6
-
7
6
  module Chronicle
8
7
  module ETL
9
8
  module CLI
@@ -13,16 +12,19 @@ module Chronicle
13
12
  namespace :plugins
14
13
 
15
14
  desc "install", "Install a plugin"
16
- def install(name)
17
- spinner = TTY::Spinner.new("[:spinner] Installing plugin #{name}...", format: :dots_2)
15
+ def install(*plugins)
16
+ cli_fail(message: "Please specify a plugin to install") unless plugins.any?
17
+
18
+ spinner = TTY::Spinner.new("[:spinner] Installing #{plugins.join(", ")}...", format: :dots_2)
18
19
  spinner.auto_spin
19
- Chronicle::ETL::Registry::PluginRegistry.install(name)
20
+ plugins.each do |plugin|
21
+ spinner.update(title: "Installing #{plugin}")
22
+ Chronicle::ETL::Registry::PluginRegistry.install(plugin)
23
+ rescue Chronicle::ETL::PluginError => e
24
+ spinner.error("Error".red)
25
+ cli_fail(message: "Plugin '#{plugin}' could not be installed", exception: e)
26
+ end
20
27
  spinner.success("(#{'successful'.green})")
21
- rescue Chronicle::ETL::PluginError => e
22
- spinner.error("Error".red)
23
- Chronicle::ETL::Logger.debug(e.full_message)
24
- Chronicle::ETL::Logger.fatal("Plugin '#{name}' could not be installed".red)
25
- exit 1
26
28
  end
27
29
 
28
30
  desc "uninstall", "Unintall a plugin"
@@ -33,9 +35,7 @@ module Chronicle
33
35
  spinner.success("(#{'successful'.green})")
34
36
  rescue Chronicle::ETL::PluginError => e
35
37
  spinner.error("Error".red)
36
- Chronicle::ETL::Logger.debug(e.full_message)
37
- Chronicle::ETL::Logger.fatal("Plugin '#{name}' could not be uninstalled (was it installed?)".red)
38
- exit 1
38
+ cli_fail(message: "Plugin '#{name}' could not be uninstalled (was it installed?)", exception: e)
39
39
  end
40
40
 
41
41
  desc "list", "Lists available plugins"
@@ -2,7 +2,7 @@ module Chronicle
2
2
  module ETL
3
3
  module CLI
4
4
  # Base class for CLI subcommands. Overrides Thor methods so we can use command:subcommand syntax
5
- class SubcommandBase < ::Thor
5
+ class SubcommandBase < Chronicle::ETL::CLI::CLIBase
6
6
  # Print usage instructions for a subcommand
7
7
  def self.help(shell, subcommand = false)
8
8
  list = printable_commands(true, subcommand)
@@ -2,6 +2,7 @@ require 'thor'
2
2
  require 'thor/hollaback'
3
3
  require 'chronicle/etl'
4
4
 
5
+ require 'chronicle/etl/cli/cli_base'
5
6
  require 'chronicle/etl/cli/subcommand_base'
6
7
  require 'chronicle/etl/cli/connectors'
7
8
  require 'chronicle/etl/cli/jobs'
@@ -23,6 +23,7 @@ module Chronicle
23
23
  end
24
24
  end
25
25
 
26
+ class PluginConflictError < PluginError; end
26
27
  class PluginNotAvailableError < PluginError; end
27
28
  class PluginLoadError < PluginError; end
28
29
 
@@ -26,20 +26,32 @@ module Chronicle
26
26
  @definition = SKELETON_DEFINITION
27
27
  end
28
28
 
29
+ def valid?
30
+ validate
31
+ @errors.empty?
32
+ end
33
+
29
34
  def validate
30
- @errors = []
35
+ @errors = {}
31
36
 
32
37
  Chronicle::ETL::Registry::PHASES.each do |phase|
33
38
  __send__("#{phase}_klass".to_sym)
34
39
  rescue Chronicle::ETL::PluginError => e
35
- @errors << e
40
+ @errors[:plugins] ||= []
41
+ @errors[:plugins] << e
36
42
  end
43
+ end
37
44
 
38
- @errors.empty?
45
+ def plugins_missing?
46
+ validate
47
+
48
+ @errors[:plugins] || []
49
+ .filter { |e| e.instance_of?(Chronicle::ETL::PluginLoadError) }
50
+ .any?
39
51
  end
40
52
 
41
53
  def validate!
42
- raise(Chronicle::ETL::JobDefinitionError.new(self), "Job definition is invalid") unless validate
54
+ raise(Chronicle::ETL::JobDefinitionError.new(self), "Job definition is invalid") unless valid?
43
55
 
44
56
  true
45
57
  end
@@ -48,7 +60,6 @@ module Chronicle
48
60
  def add_config(config = {})
49
61
  @definition = @definition.deep_merge(config)
50
62
  load_credentials
51
- validate
52
63
  end
53
64
 
54
65
  # Is this job continuing from a previous run?
@@ -36,8 +36,11 @@ module Chronicle
36
36
  # By default, activates the latest available version of a gem
37
37
  # so don't have to run Kernel#gem separately
38
38
  require "chronicle/#{name}"
39
- rescue LoadError
40
- raise Chronicle::ETL::PluginLoadError.new(name), "Plugin #{name} couldn't be loaded" if exists?(name)
39
+ rescue Gem::ConflictError => e
40
+ # TODO: figure out if there's more we can do here
41
+ raise Chronicle::ETL::PluginConflictError.new(name), "Plugin '#{name}' couldn't be loaded. #{e.message}"
42
+ rescue LoadError => e
43
+ raise Chronicle::ETL::PluginLoadError.new(name), "Plugin '#{name}' couldn't be loaded" if exists?(name)
41
44
 
42
45
  raise Chronicle::ETL::PluginNotAvailableError.new(name), "Plugin #{name} doesn't exist"
43
46
  end
@@ -49,9 +52,11 @@ module Chronicle
49
52
 
50
53
  Gem::DefaultUserInteraction.ui = Gem::SilentUI.new
51
54
  Gem.install(gem_name)
55
+
56
+ activate(name)
52
57
  rescue Gem::UnsatisfiableDependencyError
53
58
  # TODO: we need to catch a lot more than this here
54
- raise Chronicle::ETL::PluginNotAvailableError.new(name), "Plugin #{name} doesn't exist"
59
+ raise Chronicle::ETL::PluginNotAvailableError.new(name), "Plugin #{name} could not be installed."
55
60
  end
56
61
 
57
62
  # Uninstall a plugin
@@ -1,5 +1,5 @@
1
1
  module Chronicle
2
2
  module ETL
3
- VERSION = "0.4.2"
3
+ VERSION = "0.4.3"
4
4
  end
5
5
  end
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.4.2
4
+ version: 0.4.3
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-03-12 00:00:00.000000000 Z
11
+ date: 2022-03-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -359,6 +359,7 @@ files:
359
359
  - exe/chronicle-etl
360
360
  - lib/chronicle/etl.rb
361
361
  - lib/chronicle/etl/cli.rb
362
+ - lib/chronicle/etl/cli/cli_base.rb
362
363
  - lib/chronicle/etl/cli/connectors.rb
363
364
  - lib/chronicle/etl/cli/jobs.rb
364
365
  - lib/chronicle/etl/cli/main.rb