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. Provide CLI options to select one of the transfer agents (FASP/ascp client)

Constant Summary collapse

CP4I_REMOTE_HOST_LB =
'N/A'
TRANSFER_AGENTS =
Agent::Factory.instance.list.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opt_mgr, config_plugin) ⇒ TransferAgent



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
79
80
81
# 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,
    'resume_policy' => 'sparse_csum'
  }
  # 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(',')})", default: FILE_LIST_FROM_ARGS)
  @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.



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

def transfer_info
  @transfer_info
end

Class Method Details

.session_status(statuses) ⇒ Object

else return the first error exception object



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

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



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

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
  agent_options[:progress] = @config.progress_bar
  agent_options[:config_dir] = @config.main_folder
  # special cases
  case agent_type
  when :node
    if !agent_options.key?(:url)
      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.merge!(@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
  # get agent instance
  self.agent_instance = Agent::Factory.instance.create(agent_type, agent_options)
  Log.log.debug{"transfer agent is a #{@agent.class}"}
  return @agent
end

#agent_instance=(instance) ⇒ Object



103
104
105
# File 'lib/aspera/cli/transfer_agent.rb', line 103

def agent_instance=(instance)
  @agent = instance
end

#destination_folder(direction) ⇒ String

Get destination folder



144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/aspera/cli/transfer_agent.rb', line 144

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



167
168
169
170
# File 'lib/aspera/cli/transfer_agent.rb', line 167

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

#list_to_paths(file_list) ⇒ Object

Transform the list of paths to a list of hash with source/dest



174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/aspera/cli/transfer_agent.rb', line 174

def list_to_paths(file_list)
  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?, type: Cli::BadArgument){"When using pair, provide an even number of paths: #{file_list.length}"}
    @transfer_paths = file_list.each_slice(2).map{ |s, d| {'source' => s, 'destination' => d}}
  else Aspera.error_unexpected_value(source_type)
  end
end

#option_transfer_specObject



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

def option_transfer_spec; @transfer_spec_command_line; end

#option_transfer_spec=(value) ⇒ Object

Multiple option are merged



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

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

#option_transfer_spec_deep_merge(value) ⇒ Object

Add other transfer spec parameters



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

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

#shutdownObject

shut down if agent requires it



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

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

#source_listArray



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

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



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

def start(transfer_spec, rest_token: nil)
  # check parameters
  Aspera.assert_type(transfer_spec, Hash){'transfer_spec'}
  raise "Wrong remote host: #{CP4I_REMOTE_HOST_LB}" if transfer_spec['remote_host'].eql?(CP4I_REMOTE_HOST_LB)
  # 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
  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?}
  # if TS from app has content_protection (e.g. F5), that means content is protected: ask password if not provided
  transfer_spec['content_protection_password'] = @opt_mgr.prompt_user_input('content protection password', sensitive: true) if transfer_spec['content_protection'].eql?('decrypt') && !transfer_spec.key?('content_protection_password')
  # 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_paths(default: nil) ⇒ Array?

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



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

def ts_source_paths(default: nil)
  # 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 ?
  sources = @opt_mgr.get_option(:sources)
  @transfer_paths =
    case sources
    when FILE_LIST_FROM_ARGS
      Log.log.debug('getting file list as parameters')
      Aspera.assert_type(default, Array, NilClass)
      # get remaining arguments
      list = @opt_mgr.get_next_argument('source file list', multiple: true, default: default)
      raise Cli::BadArgument, 'specify at least one file on command line or use ' \
        "--sources=#{FILE_LIST_FROM_TRANSFER_SPEC} to use transfer spec" if !list.is_a?(Array) || list.empty?
      list_to_paths(list)
    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
      # can be nil
      @transfer_paths
    when Array
      Log.log.debug('getting file list as extended value')
      Aspera.assert(sources.all?(String), type: Cli::BadArgument){'sources must be a Array of String'}
      list_to_paths(sources)
    else Aspera.error_unexpected_value(sources){'sources'}
    end
  Log.log.debug{"paths=#{@transfer_paths}"}
  return @transfer_paths
end