Class: Aspera::Cli::Main

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

Overview

The main CLI class

Constant Summary collapse

STATUS_FIELD =

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

'status'

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(argv) ⇒ Main

minimum initialization, no exception raised



122
123
124
125
126
127
128
129
130
# File 'lib/aspera/cli/main.rb', line 122

def initialize(argv)
  @argv = argv
  early_debug_setup
  Log.log.trace2{Log.dump(:argv, @argv)}
  @option_help = false
  @option_show_config = false
  @bash_completion = false
  @env = Objects.new
end

Class Method Details

.result_auto(data) ⇒ Object

Determines type of result based on data



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/aspera/cli/main.rb', line 104

def result_auto(data)
  case data
  when Hash
    return result_single_object(data)
  when Array
    all_types = data.map(&:class).uniq
    return result_object_list(data) if all_types.eql?([Hash])
    unsupported_types = all_types - SCALAR_TYPES
    return result_value_list(data, 'list') if unsupported_types.empty?
    Aspera.error_unexpected_value(unsupported_types){'list item types'}
  when *SCALAR_TYPES
    return result_text(data)
  else Aspera.error_unexpected_value(data.class.name){'result type'}
  end
end

.result_emptyObject

expect some list, but nothing to display



51
# File 'lib/aspera/cli/main.rb', line 51

def result_empty; return {type: :empty, data: :nil}; end

.result_image(blob, formatter:) ⇒ Object



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

def result_image(blob, formatter:)
  return Main.result_status(formatter.status_image(blob))
end

.result_nothingObject

nothing expected



54
# File 'lib/aspera/cli/main.rb', line 54

def result_nothing; return {type: :nothing, data: :nil}; end

.result_object_list(data, fields: nil, total: nil) ⇒ Object



95
96
97
# File 'lib/aspera/cli/main.rb', line 95

def result_object_list(data, fields: nil, total: nil)
  return {type: :object_list, data: data, fields: fields, total: total}
end

.result_single_object(data, fields: nil) ⇒ Object



91
92
93
# File 'lib/aspera/cli/main.rb', line 91

def result_single_object(data, fields: nil)
  return {type: :single_object, data: data, fields: fields}
end

.result_status(status) ⇒ Object



56
# File 'lib/aspera/cli/main.rb', line 56

def result_status(status); return {type: :status, data: status}; end

.result_successObject



60
# File 'lib/aspera/cli/main.rb', line 60

def result_success; return result_status('complete'); end

.result_text(data) ⇒ Object



58
# File 'lib/aspera/cli/main.rb', line 58

def result_text(data); return {type: :text, data: data}; end

.result_transfer(statuses) ⇒ Object

Process statuses of finished transfer sessions raise exception if there is one error else returns an empty status



65
66
67
68
69
# File 'lib/aspera/cli/main.rb', line 65

def result_transfer(statuses)
  worst = TransferAgent.session_status(statuses)
  raise worst unless worst.eql?(:success)
  return Main.result_nothing
end

.result_transfer_multiple(status_table) ⇒ Object

used when one command executes several transfer jobs (each job being possibly multi session) each element has a key STATUS_FIELD which contains the result of possibly multiple sessions

Parameters:

  • status_table (Array)
    array],…,…

Returns:

  • a status object suitable as command result



75
76
77
78
79
80
81
82
83
84
85
# File 'lib/aspera/cli/main.rb', line 75

def result_transfer_multiple(status_table)
  global_status = :success
  # transform status array into string and find if there was problem
  status_table.each do |item|
    worst = TransferAgent.session_status(item[STATUS_FIELD])
    global_status = worst unless worst.eql?(:success)
    item[STATUS_FIELD] = item[STATUS_FIELD].map(&:to_s).join(',')
  end
  raise global_status unless global_status.eql?(:success)
  return result_object_list(status_table)
end

.result_value_list(data, name) ⇒ Object



99
100
101
# File 'lib/aspera/cli/main.rb', line 99

def result_value_list(data, name)
  return {type: :value_list, data: data, name: name}
end

Instance Method Details

#process_command_lineObject

this is the main function called by initial script just after constructor



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/aspera/cli/main.rb', line 133

def process_command_line
  # catch exception information , if any
  exception_info = nil
  # false if command shall not be executed (e.g. --show-config)
  execute_command = true
  # catch exceptions
  begin
    init_agents_and_options
    # find plugins, shall be after parse! ?
    PluginFactory.instance.add_plugins_from_lookup_folders
    # help requested without command ? (plugins must be known here)
    exit_with_usage(true) if @option_help && @env.options.command_or_arg_empty?
    generate_bash_completion if @bash_completion
    @env.config.periodic_check_newer_gem_version
    command_sym =
      if @option_show_config && @env.options.command_or_arg_empty?
        COMMAND_CONFIG
      else
        @env.options.get_next_command(PluginFactory.instance.plugin_list.unshift(COMMAND_HELP))
      end
    # command will not be executed, but we need manual
    @env.options.fail_on_missing_mandatory = false if @option_help || @option_show_config
    # main plugin is not dynamically instantiated
    case command_sym
    when COMMAND_HELP
      exit_with_usage(true)
    when COMMAND_CONFIG
      command_plugin = @env.config
    else
      # get plugin, set options, etc
      command_plugin = get_plugin_instance_with_options(command_sym)
      # parse plugin specific options
      @env.options.parse_options!
    end
    # help requested for current plugin
    exit_with_usage(false) if @option_help
    if @option_show_config
      @env.formatter.display_results(type: :single_object, data: @env.options.known_options(only_defined: true).stringify_keys)
      execute_command = false
    end
    # locking for single execution (only after "per plugin" option, in case lock port is there)
    lock_port = @env.options.get_option(:lock_port)
    if !lock_port.nil?
      begin
        # no need to close later, will be freed on process exit. must save in member else it is garbage collected
        Log.log.debug{"Opening lock port #{lock_port}"}
        # loopback address, could also be 'localhost'
        @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 = @env.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
    # execute and display (if not exclusive execution)
    @env.formatter.display_results(**command_plugin.execute_action) if execute_command
    # save config file if command modified it
    @env.config.save_config_file_if_needed
    # finish
    @env.transfer.shutdown
  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::BadIdentifier => e;          exception_info = {e: e, t: 'Identifier'}
  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
  # cleanup file list files
  TempFileManager.instance.cleanup
  # 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]
    @env.formatter.display_message(:error, "#{Formatter::ERROR_FLASH} #{exception_info[:t]}: #{exception_info[:e].message}")
    @env.formatter.display_message(:error, 'Use option -h to get help.') if exception_info[:usage]
    # Is that a known error condition with proposal for remediation ?
    Hints.hint_for(exception_info[:e], @env.formatter)
  end
  # 2- processing of command not processed (due to exception or bad command line)
  if execute_command || @option_show_config
    @env.options.final_errors.each do |msg|
      @env.formatter.display_message(:error, "#{Formatter::ERROR_FLASH} Argument: #{msg}")
      # add code as exception if there is not already an error
      exception_info = {e: Exception.new(msg), t: 'UnusedArg'} if exception_info.nil?
    end
  end
  # 3- in case of error, fail the process status
  unless exception_info.nil?
    # show stack trace in debug mode
    raise exception_info[:e] if Log.log.debug?
    # else give hint and exit
    @env.formatter.display_message(:error, 'Use --log-level=debug to get more details.') if exception_info[:debug]
    Process.exit(1)
  end
  return nil
end