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
69
70
71
72
73
74
75
76
77
78
# 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!
  @notification_cb = nil
  if !@opt_mgr.get_option(:notify_to).nil?
    @notification_cb = ->(transfer_spec, global_status) do
      @config.send_email_template(email_template_default: DEFAULT_TRANSFER_NOTIFY_TEMPLATE, values: {
        subject: "#{Info::CMD_NAME} transfer: #{global_status}",
        status:  global_status,
        ts:      transfer_spec
      })
    end
  end
end

Instance Attribute Details

#transfer_infoObject

Returns the value of attribute transfer_info.



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

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



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

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



106
107
108
# File 'lib/aspera/cli/transfer_agent.rb', line 106

def agent_instance=(instance)
  @agent = instance
end

#destination_folder(direction) ⇒ Object

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



147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/aspera/cli/transfer_agent.rb', line 147

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



169
170
171
172
# File 'lib/aspera/cli/transfer_agent.rb', line 169

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



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

def option_transfer_spec; @transfer_spec_command_line; end

#option_transfer_spec=(value) ⇒ Object

multiple option are merged



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

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



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

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

#shutdownObject

shut down if agent requires it



269
270
271
# File 'lib/aspera/cli/transfer_agent.rb', line 269

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

#source_listArray

Returns list of source files.

Returns:

  • (Array)

    list of source files



163
164
165
166
167
# File 'lib/aspera/cli/transfer_agent.rb', line 163

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



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
257
258
259
260
261
262
263
264
265
266
# File 'lib/aspera/cli/transfer_agent.rb', line 227

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
  @notification_cb&.call(transfer_spec, self.class.session_status(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)



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

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



92
93
94
95
96
97
# File 'lib/aspera/cli/transfer_agent.rb', line 92

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