Class: Aspera::Cli::Runner

Inherits:
Object
  • Object
show all
Defined in:
lib/aspera/cli/runner.rb

Overview

The main CLI class

Constant Summary collapse

STATUS_FIELD =

Plugins store transfer result using this key and use result_transfer_multiple()

'status'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(argv) ⇒ nil

Minimum initialization, no exception raised

Parameters:

  • argv (Array<String>)

    command line arguments



79
80
81
82
83
84
85
# File 'lib/aspera/cli/runner.rb', line 79

def initialize(argv)
  @argv = argv
  Log.dump(:argv, @argv, level: :trace2)
  @option_help = false
  @option_show_config = false
  @context = Context.new
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



87
88
89
# File 'lib/aspera/cli/runner.rb', line 87

def context
  @context
end

Class Method Details

.result_transfer(transfer_result) ⇒ Result

Process the typed result of a finished (or submitted) transfer.

Parameters:

Returns:

  • (Result)

    CLI result object

Raises:

  • (StandardError)

    if the transfer failed



39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/aspera/cli/runner.rb', line 39

def result_transfer(transfer_result)
  case transfer_result
  when Transfer::Result::Async
    return Result::SingleObject.new(transfer_result.to_h)
  when Transfer::Result::Error
    raise transfer_result.exception
  when Transfer::Result::Success
    return Result::Nothing.new
  else
    raise "Unexpected transfer result type: #{transfer_result.class}"
  end
end

.result_transfer_multiple(status_table) ⇒ Result

Used when one command executes several transfer jobs (each job being possibly multi session)

Parameters:

  • status_table (Array)

    [STATUS_FIELD=>Transfer::Result,...,...]

Returns:

  • (Result)

    a status object suitable as command result



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/aspera/cli/runner.rb', line 55

def result_transfer_multiple(status_table)
  failed_result = nil
  status_table.each do |item|
    tr = item[STATUS_FIELD]
    case tr
    when Transfer::Result::Error
      failed_result ||= tr
      item[STATUS_FIELD] = tr.exception.message
    when Transfer::Result::Success
      item[STATUS_FIELD] = 'success'
    when Transfer::Result::Async
      item[STATUS_FIELD] = "async:#{tr.job_id}"
    else
      item[STATUS_FIELD] = tr.to_s
    end
  end
  raise failed_result.exception unless failed_result.nil?
  return Result::ObjectList.new(status_table)
end

Instance Method Details

#option_log(_option_sym, operation, value = nil) ⇒ Object

Composite option handler for the log option (dot-notation sub-properties). Supported sub-properties: level, type, format

Parameters:

  • _option_sym (Symbol)

    Option name (unused, always :log)

  • operation (Symbol)

    :set or :get

  • value (Hash, nil) (defaults to: nil)

    Hash of sub-properties to set (only for :set)



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/aspera/cli/runner.rb', line 228

def option_log(_option_sym, operation, value = nil)
  Aspera.assert_values(operation, %i[set get])
  case operation
  when :set
    Aspera.assert_type(value, Hash)
    value.each do |k, v|
      case k.to_sym
      when :level   then Log.instance.level = v.to_sym
      when :type    then Log.instance.logger_type = v.to_sym
      when :format  then Log.instance.formatter = v
      when :secrets then SecretHider.instance.log_secrets = BoolValue.true?(v)
      else Aspera.error_unexpected_value(k){'log sub-option (level, type, format, secrets)'}
      end
    end
  when :get
    return {level: Log.instance.level, type: Log.instance.logger_type, format: Log.instance.formatter, secrets: SecretHider.instance.log_secrets}
  end
  nil
end

#result_usage(plugin: nil) ⇒ Result::Text

Return usage as a Result::Text (used by run_with_result, no display, no exit).

Parameters:

  • plugin (Plugins::Base, nil) (defaults to: nil)

    plugin instance to show subcommands for

Returns:



219
220
221
# File 'lib/aspera/cli/runner.rb', line 219

def result_usage(plugin: nil)
  Result::Text.new(usage_text(plugin: plugin))
end

#runnil

Main entry point: execute the command, display results, exit on error.

Returns:

  • (nil)


156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/aspera/cli/runner.rb', line 156

def run
  exception_info = nil
  begin
    result = run_with_result
    @context.formatter.display_results(result) if result
  rescue Net::SSH::AuthenticationFailed => e; exception_info = {e: e, t: 'SSH', security: true}
  rescue OpenSSL::SSL::SSLError => e;         exception_info = {e: e, t: 'SSL'}
  rescue Cli::BadArgument => e;               exception_info = {e: e, t: 'Argument', usage: true}
  rescue Cli::MissingArgument => e;           exception_info = {e: e, t: 'Missing'}
  rescue Cli::BadIdentifier => e;             exception_info = {e: e, t: 'Identifier'}
  rescue Cli::SchemaRequest => e;             exception_info = {e: e, t: 'Schema'}
  rescue Cli::Error => e;                     exception_info = {e: e, t: 'Tool', usage: true}
  rescue Transfer::Error => e;                exception_info = {e: e, t: 'Transfer'}
  rescue RestCallError => e;                  exception_info = {e: e, t: 'Rest'}
  rescue SocketError => e;                    exception_info = {e: e, t: 'Network'}
  rescue StandardError => e;                  exception_info = {e: e, t: "Other(#{e.class.name})", debug: true}
  rescue Interrupt => e;                      exception_info = {e: e, t: 'Interruption', debug: true}
  end
  # 1- processing of error condition
  unless exception_info.nil?
    Log.log.warn(exception_info[:e].message) if Log.instance.logger_type.eql?(:syslog) && exception_info[:security]
    Log.log.error{"#{exception_info[:t]}: #{exception_info[:e].message}"} unless exception_info[:e].is_a?(Cli::SchemaRequest)
    Log.log.debug{(['Backtrace:'] + exception_info[:e].backtrace).join("\n")} if exception_info[:debug]
    @context.formatter.display_message(:error, 'Use option -h to get help.') if exception_info[:usage]
    Hints.hint_for(exception_info[:e], @context.formatter)
    if exception_info[:e].is_a?(Cli::SchemaRequest)
      Log.log.info{"#{exception_info[:t]}: #{exception_info[:e].message}"}
      schema_path = exception_info[:e].path
      if schema_path.nil?
        Log.log.warn{'Sorry, no schema provided yet. Please refer to the manual or API.'}
      else
        builder = Schema::Documentation.new(TerminalFormatter, Schema::Registry.instance.reader(schema_path)).build
        @context.formatter.display_results(Result::ObjectList.new(builder.rows, fields: builder.columns))
      end
    end
  end
  # 2- processing of unprocessed arguments (skip when help was displayed: sub-commands are not consumed)
  unless @option_help
    @context.options&.final_errors&.each do |msg|
      Log.log.error{"Argument: #{msg}"}
      exception_info = {e: Exception.new(msg), t: 'UnusedArg'} if exception_info.nil?
    end
  end
  # 3- exit on error
  unless exception_info.nil?
    raise exception_info[:e] if Log.log.debug?
    @context.formatter.display_message(:error, 'Use --log-level=debug to get more details.') if exception_info[:debug]
    Process.exit(1)
  end
  return
end

#run_with_resultResult?

Execute the command and return the raw Result object. Pure computation: no display, no Process.exit - raises on any error.

Returns:

  • (Result, nil)

    the result of the command, or nil if nothing to execute



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/aspera/cli/runner.rb', line 92

def run_with_result
  init_agents_and_options
  Plugins::Factory.instance.add_plugins_from_lookup_folders
  # Help requested without command? Show global options + plugin list
  return result_usage if @option_help && @context.options.command_or_arg_empty?
  @context.config.periodic_check_newer_gem_version
  command_sym =
    if @option_show_config && @context.options.command_or_arg_empty?
      COMMAND_CONFIG
    else
      @context.options.get_next_command(Plugins::Factory.instance.plugin_list.unshift(COMMAND_HELP))
    end
  @context.options.fail_on_missing_mandatory = false if @option_help || @option_show_config
  case command_sym
  when COMMAND_HELP
    return result_usage
  when COMMAND_CONFIG
    command_plugin = @context.config
  else
    command_plugin = get_plugin_instance_with_options(command_sym)
    @context.options.parse_options!
  end
  # --help after a plugin name: if no positional args remain, show plugin-level help now.
  # If args remain (e.g. `ascli aoc files -h`), let the dispatch consume them and
  # intercept --help at the right depth via Cli::HelpRequest.
  return result_usage(plugin: command_plugin) if @option_help && @context.options.command_or_arg_empty?
  if @option_show_config
    result = Result::SingleObject.new(@context.options.known_options(only_defined: true).stringify_keys)
    @context.presets.save_if_needed
    @context.transfer.shutdown
    TempFileManager.instance.cleanup
    return result
  end
  execute_command = true
  lock_port = @context.options.get_option(:lock_port)
  if !lock_port.nil?
    begin
      Log.log.debug{"Opening lock port #{lock_port}"}
      @tcp_server = TCPServer.new('127.0.0.1', lock_port)
    rescue StandardError => e
      execute_command = false
      Log.log.warn{"Another instance is already running (#{e.message})."}
    end
  end
  pid_file = @context.options.get_option(:pid_file)
  if !pid_file.nil?
    File.write(pid_file, Process.pid)
    Log.log.debug{"Wrote pid #{Process.pid} to #{pid_file}"}
    at_exit{File.delete(pid_file)}
  end
  begin
    result = command_plugin.execute_action if execute_command
  rescue Cli::HelpRequest => e
    return result_usage(plugin: e.plugin)
  ensure
    @context.presets.save_if_needed
    @context.transfer.shutdown
    TempFileManager.instance.cleanup
  end
  return result
end

#show_usage(plugin: nil) ⇒ nil

Display usage information and exit (used by the interactive CLI).

Parameters:

  • plugin (Plugins::Base, nil) (defaults to: nil)

    plugin instance to show subcommands for

Returns:

  • (nil)


211
212
213
214
# File 'lib/aspera/cli/runner.rb', line 211

def show_usage(plugin: nil)
  @context.formatter.display_message(:error, usage_text(plugin: plugin))
  Process.exit(0)
end