Class: Aspera::Cli::TransferAgent

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

Overview

The Transfer agent is a common interface to start a transfer using one of the supported transfer agents provides CLI options to select one of the transfer agents (FASP/ascp client)

Constant Summary collapse

CP4I_REMOTE_HOST_LB =
'N/A'
TRANSFER_AGENTS =
Agent::Base.agent_list.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opt_mgr, config_plugin) ⇒ TransferAgent

Returns a new instance of TransferAgent.

Parameters:

  • env

    external objects: option manager, config file manager



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/aspera/cli/transfer_agent.rb', line 48

def initialize(opt_mgr, config_plugin)
  @opt_mgr = opt_mgr
  @config = config_plugin
  # command line can override transfer spec
  @transfer_spec_command_line = {'create_dir' => true}
  # options for transfer agent
  @transfer_info = {}
  # the currently selected transfer agent
  @agent = nil
  # source/destination pair, like "paths" of transfer spec
  @transfer_paths = nil
  # HTTPGW URL provided by webapp
  @httpgw_url_lambda = nil
  @opt_mgr.declare(:ts, 'Override transfer spec values', types: Hash, handler: {o: self, m: :option_transfer_spec})
  @opt_mgr.declare(:to_folder, 'Destination folder for transferred files')
  @opt_mgr.declare(:sources, "How list of transferred files is provided (#{FILE_LIST_OPTIONS.join(',')})")
  @opt_mgr.declare(:src_type, 'Type of file list', values: %i[list pair], default: :list)
  @opt_mgr.declare(:transfer, 'Type of transfer agent', values: TRANSFER_AGENTS, default: :direct)
  @opt_mgr.declare(:transfer_info, 'Parameters for transfer agent', types: Hash, handler: {o: self, m: :transfer_info})
  @opt_mgr.parse_options!
end

Instance Attribute Details

#transfer_infoObject

Returns the value of attribute transfer_info.



89
90
91
# File 'lib/aspera/cli/transfer_agent.rb', line 89

def transfer_info
  @transfer_info
end

Class Method Details

.session_status(statuses) ⇒ Object

else return the first error exception object

Returns:

  • :success if all sessions statuses returned by “start” are success



40
41
42
43
44
# File 'lib/aspera/cli/transfer_agent.rb', line 40

def session_status(statuses)
  error_statuses = statuses.reject{|i|i.eql?(:success)}
  return :success if error_statuses.empty?
  return error_statuses.first
end

Instance Method Details

#agent_instanceObject

analyze options and create new agent if not already created or set TODO: make a Factory pattern



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
# File 'lib/aspera/cli/transfer_agent.rb', line 102

def agent_instance
  return @agent unless @agent.nil?
  agent_type = @opt_mgr.get_option(:transfer, mandatory: true)
  # set keys as symbols
  agent_options = @opt_mgr.get_option(:transfer_info).symbolize_keys
  # special cases
  case agent_type
  when :node
    if agent_options.empty?
      param_set_name = @config.get_plugin_default_config_name(:node)
      raise Cli::BadArgument, "No default node configured. Please specify #{Manager.option_name_to_line(:transfer_info)}" if param_set_name.nil?
      agent_options = @config.preset_by_name(param_set_name).symbolize_keys
    end
  when :direct
    # by default do not display ascp native progress bar
    agent_options[:quiet] = true unless agent_options.key?(:quiet)
    agent_options[:check_ignore_cb] = ->(host, port){@config.ignore_cert?(host, port)}
    # JRuby
    agent_options[:trusted_certs] = @config.trusted_cert_locations unless agent_options.key?(:trusted_certs)
  when :httpgw
    unless agent_options.key?(:url) || @httpgw_url_lambda.nil?
      Log.log.debug('retrieving HTTPGW URL from webapp')
      agent_options[:url] = @httpgw_url_lambda.call
    end
  end
  agent_options[:progress] = @config.progress_bar
  # get agent instance
  self.agent_instance = Agent::Base.factory_create(agent_type, agent_options)
  Log.log.debug{"transfer agent is a #{@agent.class}"}
  return @agent
end

#agent_instance=(instance) ⇒ Object



96
97
98
# File 'lib/aspera/cli/transfer_agent.rb', line 96

def agent_instance=(instance)
  @agent = instance
end

#destination_folder(direction) ⇒ Object

return destination folder for transfers sets default if needed param: ‘send’ or ‘receive’



137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/aspera/cli/transfer_agent.rb', line 137

def destination_folder(direction)
  dest_folder = @opt_mgr.get_option(:to_folder)
  # do not expand path, if user wants to expand path: user @path:
  return dest_folder unless dest_folder.nil?
  dest_folder = @transfer_spec_command_line['destination_root']
  return dest_folder unless dest_folder.nil?
  # default: / on remote, . on local
  case direction.to_s
  when Transfer::Spec::DIRECTION_SEND then dest_folder = '/'
  when Transfer::Spec::DIRECTION_RECEIVE then dest_folder = '.'
  else Aspera.error_unexpected_value(direction)
  end
  return dest_folder
end

#httpgw_url_cb=(httpgw_url_proc) ⇒ Object



159
160
161
162
# File 'lib/aspera/cli/transfer_agent.rb', line 159

def httpgw_url_cb=(httpgw_url_proc)
  Aspera.assert_type(httpgw_url_proc, Proc){'httpgw_url_cb'}
  @httpgw_url_lambda = httpgw_url_proc
end

#option_transfer_specObject



70
# File 'lib/aspera/cli/transfer_agent.rb', line 70

def option_transfer_spec; @transfer_spec_command_line; end

#option_transfer_spec=(value) ⇒ Object

multiple option are merged



73
74
75
76
# File 'lib/aspera/cli/transfer_agent.rb', line 73

def option_transfer_spec=(value)
  Aspera.assert_type(value, Hash){'ts'}
  @transfer_spec_command_line.deep_merge!(value)
end

#option_transfer_spec_deep_merge(ts) ⇒ Object

add other transfer spec parameters



79
# File 'lib/aspera/cli/transfer_agent.rb', line 79

def option_transfer_spec_deep_merge(ts); @transfer_spec_command_line.deep_merge!(ts); end

#send_email_transfer_notification(transfer_spec, statuses) ⇒ Object



258
259
260
261
262
263
264
265
266
267
268
# File 'lib/aspera/cli/transfer_agent.rb', line 258

def send_email_transfer_notification(transfer_spec, statuses)
  return if @opt_mgr.get_option(:notify_to).nil?
  global_status = self.class.session_status(statuses)
  email_vars = {
    global_transfer_status: global_status,
    subject:                "#{PROGRAM_NAME} transfer: #{global_status}",
    body:                   "Transfer is: #{global_status}",
    ts:                     transfer_spec
  }
  @config.send_email_template(email_template_default: DEFAULT_TRANSFER_NOTIFY_TEMPLATE, values: email_vars)
end

#shutdownObject

shut down if agent requires it



271
272
273
# File 'lib/aspera/cli/transfer_agent.rb', line 271

def shutdown
  @agent.shutdown if @agent.respond_to?(:shutdown)
end

#source_listArray

Returns list of source files.

Returns:

  • (Array)

    list of source files



153
154
155
156
157
# File 'lib/aspera/cli/transfer_agent.rb', line 153

def source_list
  return ts_source_paths.map do |i|
    i['source']
  end
end

#start(transfer_spec, rest_token: nil) ⇒ Object

start a transfer and wait for completion, plugins shall use this method

Parameters:

  • transfer_spec (Hash)
  • rest_token (Rest) (defaults to: nil)

    if oauth token regeneration supported



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/aspera/cli/transfer_agent.rb', line 217

def start(transfer_spec, rest_token: nil)
  # check parameters
  Aspera.assert_type(transfer_spec, Hash){'transfer_spec'}
  if transfer_spec['remote_host'].eql?(CP4I_REMOTE_HOST_LB)
    raise "Wrong remote host: #{CP4I_REMOTE_HOST_LB}"
  end
  # process :src option
  case transfer_spec['direction']
  when Transfer::Spec::DIRECTION_RECEIVE
    # init default if required in any case
    @transfer_spec_command_line['destination_root'] ||= destination_folder(transfer_spec['direction'])
  when Transfer::Spec::DIRECTION_SEND
    if transfer_spec.dig('tags', Transfer::Spec::TAG_RESERVED, 'node', 'access_key')
      # gen4
      @transfer_spec_command_line.delete('destination_root') if @transfer_spec_command_line.key?('destination_root_id')
    elsif transfer_spec.key?('token')
      # gen3
      # in that case, destination is set in return by application (API/upload_setup)
      # but to_folder was used in initial API call
      @transfer_spec_command_line.delete('destination_root')
    else
      # init default if required
      @transfer_spec_command_line['destination_root'] ||= destination_folder(transfer_spec['direction'])
    end
  end
  # update command line paths, unless destination already has one
  @transfer_spec_command_line['paths'] = transfer_spec['paths'] || ts_source_paths
  # updated transfer spec with command line
  updated_ts(transfer_spec)
  # if TS from app has content_protection (e.g. F5), that means content is protected: ask password if not provided
  if transfer_spec['content_protection'].eql?('decrypt') && !transfer_spec.key?('content_protection_password')
    transfer_spec['content_protection_password'] = @opt_mgr.prompt_user_input('content protection password', sensitive: true)
  end
  # create transfer agent
  agent_instance.start_transfer(transfer_spec, token_regenerator: rest_token)
  # list of: :success or "error message string"
  result = agent_instance.wait_for_completion
  send_email_transfer_notification(transfer_spec, result)
  return result
end

#ts_source_pathsHash

This is how the list of files to be transferred is specified get paths suitable for transfer spec from command line computation is done only once, cache is kept in @transfer_paths

Returns:

  • (Hash)

    (mandatory), destination: (optional)



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
# File 'lib/aspera/cli/transfer_agent.rb', line 168

def ts_source_paths
  # return cache if set
  return @transfer_paths unless @transfer_paths.nil?
  # start with lower priority : get paths from transfer spec on command line
  @transfer_paths = @transfer_spec_command_line['paths'] if @transfer_spec_command_line.key?('paths')
  # is there a source list option ?
  file_list = @opt_mgr.get_option(:sources)
  case file_list
  when nil, FILE_LIST_FROM_ARGS
    Log.log.debug('getting file list as parameters')
    # get remaining arguments
    file_list = @opt_mgr.get_next_argument('source file list', multiple: true)
    raise Cli::BadArgument, 'specify at least one file on command line or use ' \
      "--sources=#{FILE_LIST_FROM_TRANSFER_SPEC} to use transfer spec" if !file_list.is_a?(Array) || file_list.empty?
  when FILE_LIST_FROM_TRANSFER_SPEC
    Log.log.debug('assume list provided in transfer spec')
    special_case_direct_with_list =
      @opt_mgr.get_option(:transfer, mandatory: true).eql?(:direct) &&
      Transfer::Parameters.ascp_args_file_list?(@opt_mgr.get_option(:transfer_info)['ascp_args'])
    raise Cli::BadArgument, 'transfer spec on command line must have sources' if @transfer_paths.nil? && !special_case_direct_with_list
    # here we assume check of sources is made in transfer agent
    return @transfer_paths
  when Array
    Log.log.debug('getting file list as extended value')
    raise Cli::BadArgument, 'sources must be a Array of String' if !file_list.reject{|f|f.is_a?(String)}.empty?
  else
    raise Cli::BadArgument, "sources must be a Array, not #{file_list.class}"
  end
  # here, file_list is an Array or String
  if !@transfer_paths.nil?
    Log.log.warn('--sources overrides paths from --ts')
  end
  source_type = @opt_mgr.get_option(:src_type, mandatory: true)
  case source_type
  when :list
    # when providing a list, just specify source
    @transfer_paths = file_list.map{|i|{'source' => i}}
  when :pair
    Aspera.assert(file_list.length.even?, exception_class: Cli::BadArgument){"When using pair, provide an even number of paths: #{file_list.length}"}
    @transfer_paths = file_list.each_slice(2).to_a.map{|s, d|{'source' => s, 'destination' => d}}
  else Aspera.error_unexpected_value(source_type)
  end
  Log.log.debug{"paths=#{@transfer_paths}"}
  return @transfer_paths
end

#updated_ts(transfer_spec = {}) ⇒ Hash

Returns transfer spec with updated values from command line, including removed values.

Returns:

  • (Hash)

    transfer spec with updated values from command line, including removed values



82
83
84
85
86
87
# File 'lib/aspera/cli/transfer_agent.rb', line 82

def updated_ts(transfer_spec={})
  transfer_spec.deep_merge!(@transfer_spec_command_line)
  # recursively remove values that are nil (user wants to delete)
  transfer_spec.deep_do { |hash, key, value, _unused| hash.delete(key) if value.nil?}
  return transfer_spec
end