aspera-cli 4.14.0 → 4.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +29 -3
  4. data/CHANGELOG.md +300 -185
  5. data/CONTRIBUTING.md +74 -23
  6. data/README.md +2346 -1619
  7. data/bin/ascli +16 -25
  8. data/bin/asession +15 -15
  9. data/examples/dascli +2 -2
  10. data/examples/proxy.pac +1 -1
  11. data/lib/aspera/aoc.rb +216 -150
  12. data/lib/aspera/ascmd.rb +25 -18
  13. data/lib/aspera/assert.rb +45 -0
  14. data/lib/aspera/cli/basic_auth_plugin.rb +9 -6
  15. data/lib/aspera/cli/error.rb +17 -0
  16. data/lib/aspera/cli/extended_value.rb +51 -16
  17. data/lib/aspera/cli/formatter.rb +276 -174
  18. data/lib/aspera/cli/hints.rb +81 -0
  19. data/lib/aspera/cli/main.rb +114 -147
  20. data/lib/aspera/cli/manager.rb +181 -136
  21. data/lib/aspera/cli/plugin.rb +82 -64
  22. data/lib/aspera/cli/plugins/alee.rb +0 -1
  23. data/lib/aspera/cli/plugins/aoc.rb +327 -331
  24. data/lib/aspera/cli/plugins/ats.rb +12 -8
  25. data/lib/aspera/cli/plugins/bss.rb +2 -2
  26. data/lib/aspera/cli/plugins/config.rb +575 -439
  27. data/lib/aspera/cli/plugins/console.rb +40 -0
  28. data/lib/aspera/cli/plugins/cos.rb +4 -5
  29. data/lib/aspera/cli/plugins/faspex.rb +111 -92
  30. data/lib/aspera/cli/plugins/faspex5.rb +245 -182
  31. data/lib/aspera/cli/plugins/node.rb +239 -160
  32. data/lib/aspera/cli/plugins/orchestrator.rb +56 -19
  33. data/lib/aspera/cli/plugins/preview.rb +54 -38
  34. data/lib/aspera/cli/plugins/server.rb +63 -20
  35. data/lib/aspera/cli/plugins/shares.rb +64 -38
  36. data/lib/aspera/cli/sync_actions.rb +68 -0
  37. data/lib/aspera/cli/transfer_agent.rb +64 -67
  38. data/lib/aspera/cli/transfer_progress.rb +73 -0
  39. data/lib/aspera/cli/version.rb +1 -1
  40. data/lib/aspera/colors.rb +3 -1
  41. data/lib/aspera/command_line_builder.rb +27 -22
  42. data/lib/aspera/cos_node.rb +6 -4
  43. data/lib/aspera/coverage.rb +22 -0
  44. data/lib/aspera/data_repository.rb +33 -2
  45. data/lib/aspera/environment.rb +21 -8
  46. data/lib/aspera/fasp/agent_alpha.rb +116 -0
  47. data/lib/aspera/fasp/agent_base.rb +40 -76
  48. data/lib/aspera/fasp/agent_connect.rb +21 -22
  49. data/lib/aspera/fasp/agent_direct.rb +169 -179
  50. data/lib/aspera/fasp/agent_httpgw.rb +200 -195
  51. data/lib/aspera/fasp/agent_node.rb +43 -35
  52. data/lib/aspera/fasp/agent_trsdk.rb +124 -41
  53. data/lib/aspera/fasp/error_info.rb +2 -2
  54. data/lib/aspera/fasp/faux_file.rb +52 -0
  55. data/lib/aspera/fasp/installation.rb +89 -191
  56. data/lib/aspera/fasp/management.rb +249 -0
  57. data/lib/aspera/fasp/parameters.rb +86 -47
  58. data/lib/aspera/fasp/parameters.yaml +75 -8
  59. data/lib/aspera/fasp/products.rb +162 -0
  60. data/lib/aspera/fasp/resume_policy.rb +7 -5
  61. data/lib/aspera/fasp/sync.rb +273 -0
  62. data/lib/aspera/fasp/transfer_spec.rb +10 -8
  63. data/lib/aspera/fasp/uri.rb +6 -6
  64. data/lib/aspera/faspex_gw.rb +11 -8
  65. data/lib/aspera/faspex_postproc.rb +8 -7
  66. data/lib/aspera/hash_ext.rb +2 -2
  67. data/lib/aspera/id_generator.rb +3 -1
  68. data/lib/aspera/json_rpc.rb +51 -0
  69. data/lib/aspera/keychain/encrypted_hash.rb +46 -11
  70. data/lib/aspera/keychain/macos_security.rb +15 -13
  71. data/lib/aspera/line_logger.rb +23 -0
  72. data/lib/aspera/log.rb +61 -19
  73. data/lib/aspera/nagios.rb +7 -2
  74. data/lib/aspera/node.rb +105 -21
  75. data/lib/aspera/node_simulator.rb +214 -0
  76. data/lib/aspera/oauth.rb +57 -36
  77. data/lib/aspera/open_application.rb +4 -4
  78. data/lib/aspera/persistency_action_once.rb +13 -14
  79. data/lib/aspera/persistency_folder.rb +5 -4
  80. data/lib/aspera/preview/file_types.rb +56 -268
  81. data/lib/aspera/preview/generator.rb +28 -39
  82. data/lib/aspera/preview/options.rb +2 -0
  83. data/lib/aspera/preview/terminal.rb +36 -16
  84. data/lib/aspera/preview/utils.rb +23 -29
  85. data/lib/aspera/proxy_auto_config.rb +6 -3
  86. data/lib/aspera/rest.rb +127 -80
  87. data/lib/aspera/rest_call_error.rb +1 -1
  88. data/lib/aspera/rest_error_analyzer.rb +16 -14
  89. data/lib/aspera/rest_errors_aspera.rb +39 -34
  90. data/lib/aspera/secret_hider.rb +18 -17
  91. data/lib/aspera/ssh.rb +10 -5
  92. data/lib/aspera/temp_file_manager.rb +11 -4
  93. data/lib/aspera/web_auth.rb +10 -7
  94. data/lib/aspera/web_server_simple.rb +11 -5
  95. data.tar.gz.sig +0 -0
  96. metadata +108 -39
  97. metadata.gz.sig +0 -0
  98. data/lib/aspera/cli/listener/line_dump.rb +0 -19
  99. data/lib/aspera/cli/listener/logger.rb +0 -22
  100. data/lib/aspera/cli/listener/progress.rb +0 -50
  101. data/lib/aspera/cli/listener/progress_multi.rb +0 -84
  102. data/lib/aspera/cli/plugins/sync.rb +0 -44
  103. data/lib/aspera/fasp/listener.rb +0 -13
  104. data/lib/aspera/sync.rb +0 -213
@@ -7,6 +7,46 @@ module Aspera
7
7
  module Cli
8
8
  module Plugins
9
9
  class Console < Aspera::Cli::BasicAuthPlugin
10
+ STANDARD_PATH = '/aspera/console'
11
+ class << self
12
+ def detect(address_or_url)
13
+ address_or_url = "https://#{address_or_url}" unless address_or_url.match?(%r{^[a-z]{1,6}://})
14
+ urls = [address_or_url]
15
+ urls.push("#{address_or_url}#{STANDARD_PATH}") unless address_or_url.end_with?(STANDARD_PATH)
16
+
17
+ urls.each do |base_url|
18
+ next unless base_url.start_with?('https://')
19
+ api = Rest.new(base_url: base_url, redirect_max: 2)
20
+ test_endpoint = 'login'
21
+ test_page = api.call({operation: 'GET', subpath: test_endpoint, url_params: {local: true}})
22
+ next unless test_page[:http].body.include?('Aspera Console')
23
+ version = 'unknown'
24
+ if (m = test_page[:http].body.match(/\(v([1-9]\..*)\)/))
25
+ version = m[1]
26
+ end
27
+ url = test_page[:http].uri.to_s
28
+ return {
29
+ version: version,
30
+ url: url[0..url.index(test_endpoint) - 2]
31
+ }
32
+ rescue StandardError => e
33
+ Log.log.debug{"detect error: #{e}"}
34
+ end
35
+ return nil
36
+ end
37
+
38
+ def wizard(object:, private_key_path: nil, pub_key_pem: nil)
39
+ options = object.options
40
+ return {
41
+ preset_value: {
42
+ url: options.get_option(:url, mandatory: true),
43
+ username: options.get_option(:username, mandatory: true),
44
+ password: options.get_option(:password, mandatory: true)
45
+ },
46
+ test_args: 'transfer list'
47
+ }
48
+ end
49
+ end
10
50
  DEFAULT_FILTER_AGE_SECONDS = 3 * 3600
11
51
  private_constant :DEFAULT_FILTER_AGE_SECONDS
12
52
  def initialize(env)
@@ -3,6 +3,7 @@
3
3
  require 'aspera/cli/plugin'
4
4
  require 'aspera/cli/plugins/node'
5
5
  require 'aspera/cos_node'
6
+ require 'aspera/assert'
6
7
 
7
8
  module Aspera
8
9
  module Cli
@@ -10,7 +11,6 @@ module Aspera
10
11
  class Cos < Aspera::Cli::Plugin
11
12
  def initialize(env)
12
13
  super(env)
13
- @service_creds = nil
14
14
  options.declare(:bucket, 'Bucket name')
15
15
  options.declare(:endpoint, 'Storage endpoint url')
16
16
  options.declare(:apikey, 'Storage API key')
@@ -31,19 +31,18 @@ module Aspera
31
31
  # get service credentials, Hash, e.g. @json:@file:...
32
32
  service_credentials = options.get_option(:service_credentials)
33
33
  storage_endpoint = options.get_option(:endpoint)
34
- raise CliBadArgument, 'one of: endpoint or service_credentials is required' if service_credentials.nil? && storage_endpoint.nil?
35
- raise CliBadArgument, 'endpoint and service_credentials are mutually exclusive' unless service_credentials.nil? || storage_endpoint.nil?
34
+ assert(service_credentials.nil? ^ storage_endpoint.nil?, exception_class: Cli::BadArgument){'endpoint and service_credentials are mutually exclusive'}
36
35
  if service_credentials.nil?
37
36
  service_api_key = options.get_option(:apikey, mandatory: true)
38
37
  instance_id = options.get_option(:crn, mandatory: true)
39
38
  else
40
- params = CosNode.parameters_from_svc_creds(service_credentials, options.get_option(:region, mandatory: true))
39
+ params = CosNode.parameters_from_svc_credentials(service_credentials, options.get_option(:region, mandatory: true))
41
40
  storage_endpoint = params[:storage_endpoint]
42
41
  service_api_key = params[:service_api_key]
43
42
  instance_id = params[:instance_id]
44
43
  end
45
44
  api_node = CosNode.new(bucket_name, storage_endpoint, instance_id, service_api_key, options.get_option(:identity, mandatory: true))
46
- node_plugin = Node.new(@agents.merge(skip_basic_auth_options: true, node_api: api_node))
45
+ node_plugin = Node.new(@agents, api: api_node)
47
46
  command = options.get_next_command(Node::COMMANDS_COS)
48
47
  return node_plugin.execute_action(command)
49
48
  end
@@ -1,16 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # cspell:ignore passcode xrds workgroups dmembership wmembership
3
4
  require 'aspera/cli/basic_auth_plugin'
4
5
  require 'aspera/cli/plugins/node'
5
6
  require 'aspera/cli/plugins/config'
6
7
  require 'aspera/cli/extended_value'
7
8
  require 'aspera/cli/transfer_agent'
8
- require 'aspera/persistency_action_once'
9
- require 'aspera/open_application'
10
9
  require 'aspera/fasp/uri'
11
10
  require 'aspera/fasp/transfer_spec'
11
+ require 'aspera/persistency_action_once'
12
+ require 'aspera/open_application'
12
13
  require 'aspera/nagios'
13
14
  require 'aspera/id_generator'
15
+ require 'aspera/log'
16
+ require 'aspera/assert'
14
17
  require 'xmlsimple'
15
18
  require 'json'
16
19
  require 'cgi'
@@ -32,69 +35,79 @@ module Aspera
32
35
  ATOM_EXT_PARAMS = [MAX_ITEMS, MAX_PAGES].concat(ATOM_PARAMS).freeze
33
36
  # sub path in url for public link delivery
34
37
  PUB_LINK_EXTERNAL_MATCH = 'external_deliveries/'
38
+ STANDARD_PATH = '/aspera/faspex'
35
39
  private_constant(*%i[KEY_NODE KEY_PATH PACKAGE_MATCH_FIELD ATOM_MAILBOXES ATOM_PARAMS ATOM_EXT_PARAMS PUB_LINK_EXTERNAL_MATCH])
36
40
 
37
41
  class << self
38
- def detect(base_url)
39
- api = Rest.new(base_url: base_url)
40
- result = api.call(
41
- operation: 'POST',
42
- subpath: 'aspera/faspex',
43
- headers: {'Accept' => 'application/xrds+xml', 'Content-type' => 'text/plain'},
44
- text_body_params: '')
45
- # 4.x
46
- if result[:http].body.start_with?('<?xml')
42
+ def detect(address_or_url)
43
+ address_or_url = "https://#{address_or_url}" unless address_or_url.match?(%r{^[a-z]{1,6}://})
44
+ urls = [address_or_url]
45
+ urls.push("#{address_or_url}#{STANDARD_PATH}") unless address_or_url.end_with?(STANDARD_PATH)
46
+
47
+ urls.each do |base_url|
48
+ next unless base_url.start_with?('https://')
49
+ api = Rest.new(base_url: base_url, redirect_max: 1)
50
+ result = api.call(
51
+ operation: 'POST',
52
+ subpath: '',
53
+ headers: {'Accept' => 'application/xrds+xml', 'Content-type' => 'text/plain'},
54
+ text_body_params: '')
55
+ # 4.x
56
+ next unless result[:http].body.start_with?('<?xml')
47
57
  res_s = XmlSimple.xml_in(result[:http].body, {'ForceArray' => false})
58
+ Log.log.debug{"version: #{result[:http]['X-IBM-Aspera']}"}
48
59
  version = res_s['XRD']['application']['version']
49
- return {version: version, url: result[:http].uri}
60
+ # take redirect if any
61
+ return {version: version, url: result[:http].uri.to_s}
62
+ rescue StandardError => e
63
+ Log.log.debug{"detect error: #{e}"}
50
64
  end
51
65
  return nil
52
66
  end
53
67
 
54
- # extract elements from anonymous faspex link
68
+ def wizard(object:, private_key_path: nil, pub_key_pem: nil)
69
+ options = object.options
70
+ return {
71
+ preset_value: {
72
+ url: options.get_option(:url, mandatory: true),
73
+ username: options.get_option(:username, mandatory: true),
74
+ password: options.get_option(:password, mandatory: true)
75
+ },
76
+ test_args: 'me'
77
+ }
78
+ end
79
+
80
+ # extract elements from faspex public link
55
81
  def get_link_data(public_url)
56
82
  public_uri = URI.parse(public_url)
57
- raise CliBadArgument, 'Public link does not match Faspex format' unless (m = public_uri.path.match(%r{^(.*)/(external.*)$}))
83
+ assert((m = public_uri.path.match(%r{^(.*)/(external.*)$})), exception_class: Cli::BadArgument){'Public link does not match Faspex format'}
58
84
  base = m[1]
59
85
  subpath = m[2]
60
86
  port_add = public_uri.port.eql?(public_uri.default_port) ? '' : ":#{public_uri.port}"
61
87
  result = {
62
88
  base_url: "#{public_uri.scheme}://#{public_uri.host}#{port_add}#{base}",
63
89
  subpath: subpath,
64
- query: URI.decode_www_form(public_uri.query).each_with_object({}){|v, h|h[v.first] = v.last; }
90
+ query: Rest.decode_query(public_uri.query)
65
91
  }
66
- Log.dump('link data', result)
92
+ Log.log.debug{Log.dump('link data', result)}
67
93
  return result
68
94
  end
69
95
 
70
- # get faspe: URI from entry in xml, and fix problems..
96
+ # get Fasp::Uri::SCHEME URI from entry in xml, and fix problems.
71
97
  def get_fasp_uri_from_entry(entry, raise_no_link: true)
72
98
  unless entry.key?('link')
73
- raise CliBadArgument, 'package has no link (deleted?)' if raise_no_link
99
+ raise Cli::BadArgument, 'package has no link (deleted?)' if raise_no_link
74
100
  return nil
75
101
  end
76
102
  result = entry['link'].find{|e| e['rel'].eql?('package')}['href']
77
- # tags in the end of URL is not well % encoded... there are "=" that should be %3D
78
- # TODO: enter ticket to Faspex ?
79
- # ##XXif m=result.match(/(=+)$/);result.gsub!(/=+$/,"#{"%3D"*m[1].length}");end
80
103
  return result
81
104
  end
82
105
 
83
- def textify_package_list(table_data)
84
- return table_data.map do |e|
85
- e.each_key {|k| e[k] = e[k].first if e[k].is_a?(Array) && (e[k].length == 1)}
86
- e['items'] = e.key?('link') ? e['link'].length : 0
87
- e
88
- end
89
- end
90
-
91
- # field_sym : :id or :name
92
- def get_source_id(source_list, source_name)
93
- source_ids = source_list.select { |i| i['name'].eql?(source_name) }
94
- if source_ids.empty?
95
- raise CliError, %Q(No such Faspex source "#{source_name}" in [#{source_list.map{|i| %Q("#{i['name']}")}.join(', ')}])
96
- end
97
- return source_ids.first['id']
106
+ # @return [Integer] identifier of source
107
+ def get_source_id_by_name(source_name, source_list)
108
+ match_source = source_list.find { |i| i['name'].eql?(source_name) }
109
+ return match_source['id'] unless match_source.nil?
110
+ raise Cli::Error, %Q(No such Faspex source: "#{source_name}" in [#{source_list.map{|i| %Q("#{i['name']}")}.join(', ')}])
98
111
  end
99
112
  end
100
113
 
@@ -104,8 +117,8 @@ module Aspera
104
117
  super(env)
105
118
  options.declare(:link, 'Public link for specific operation')
106
119
  options.declare(:delivery_info, 'Package delivery information', types: Hash)
107
- options.declare(:source_name, 'Create package from remote source (by name)')
108
- options.declare(:storage, 'Faspex local storage definition')
120
+ options.declare(:remote_source, 'Remote source for package send (id or %name:)')
121
+ options.declare(:storage, 'Faspex local storage definition (for browsing source)')
109
122
  options.declare(:recipient, 'Use if recipient is a dropbox (with *)')
110
123
  options.declare(:box, 'Package box', values: ATOM_MAILBOXES, default: :inbox)
111
124
  options.parse_options!
@@ -148,9 +161,9 @@ module Aspera
148
161
  max_pages = nil
149
162
  result = []
150
163
  if !mailbox_query.nil?
151
- raise 'query: must be Hash or nil' unless mailbox_query.is_a?(Hash)
152
- raise "query: supported params: #{ATOM_EXT_PARAMS}" unless (mailbox_query.keys - ATOM_EXT_PARAMS).empty?
153
- raise 'query: startIndex and page are exclusive' if mailbox_query.key?('startIndex') && mailbox_query.key?('page')
164
+ assert_type(mailbox_query, Hash){'query'}
165
+ assert((mailbox_query.keys - ATOM_EXT_PARAMS).empty?){"query: supported params: #{ATOM_EXT_PARAMS}"}
166
+ assert(!(mailbox_query.key?('startIndex') && mailbox_query.key?('page'))){'query: startIndex and page are exclusive'}
154
167
  max_items = mailbox_query[MAX_ITEMS]
155
168
  mailbox_query.delete(MAX_ITEMS)
156
169
  max_pages = mailbox_query[MAX_PAGES]
@@ -160,8 +173,8 @@ module Aspera
160
173
  # get a batch of package information
161
174
  # order: first batch is latest packages, and then in a batch ids are increasing
162
175
  atom_xml = api_v3.call({operation: 'GET', subpath: "#{mailbox}.atom", headers: {'Accept' => 'application/xml'}, url_params: mailbox_query})[:http].body
163
- box_data = XmlSimple.xml_in(atom_xml, {'ForceArray' => true})
164
- Log.dump(:box_data, box_data)
176
+ box_data = XmlSimple.xml_in(atom_xml, {'ForceArray' => %w[entry field link to]})
177
+ Log.log.debug{Log.dump(:box_data, box_data)}
165
178
  items = box_data.key?('entry') ? box_data['entry'] : []
166
179
  Log.log.debug{"new items: #{items.count}"}
167
180
  # it is the end if page is empty
@@ -173,11 +186,14 @@ module Aspera
173
186
  package[PACKAGE_MATCH_FIELD] =
174
187
  case mailbox
175
188
  when :inbox, :archive
176
- recipient = package['to'].find{|i|recipient_names.include?(i['name'].first)}
177
- recipient.nil? ? nil : recipient['recipient_delivery_id'].first
189
+ recipient = package['to'].find{|i|recipient_names.include?(i['name'])}
190
+ recipient.nil? ? nil : recipient['recipient_delivery_id']
178
191
  else # :sent
179
- package['delivery_id'].first
192
+ package['delivery_id']
180
193
  end
194
+ # add special key
195
+ package['items'] = package['link'].is_a?(Array) ? package['link'].length : 0
196
+ package['metadata'] = package['metadata']['field'].each_with_object({}){|i, m| m[i['name']] = i['content'] }
181
197
  # if we look for a specific package
182
198
  stop_condition = true if !stop_at_id.nil? && stop_at_id.eql?(package[PACKAGE_MATCH_FIELD])
183
199
  # keep only those for the specified recipient
@@ -197,7 +213,7 @@ module Aspera
197
213
  break if link.nil?
198
214
  # replace parameters with the ones from next link
199
215
  params = CGI.parse(URI.parse(link['href']).query)
200
- mailbox_query = params.keys.each_with_object({}){|i, m|; m[i] = params[i].first; }
216
+ mailbox_query = params.keys.each_with_object({}){|i, m| m[i] = params[i].first }
201
217
  Log.log.debug{"query: #{mailbox_query}"}
202
218
  break if !max_pages.nil? && (mailbox_query['page'].to_i > max_pages)
203
219
  end
@@ -210,7 +226,7 @@ module Aspera
210
226
  # pub link user
211
227
  link_data = self.class.get_link_data(public_link_url)
212
228
  if !['external/submissions/new', 'external/dropbox_submissions/new'].include?(link_data[:subpath])
213
- raise CliBadArgument, "pub link is #{link_data[:subpath]}, expecting external/submissions/new"
229
+ raise Cli::BadArgument, "pub link is #{link_data[:subpath]}, expecting external/submissions/new"
214
230
  end
215
231
  create_path = link_data[:subpath].split('/')[0..-2].join('/')
216
232
  package_create_params[:passcode] = link_data[:query]['passcode']
@@ -225,11 +241,11 @@ module Aspera
225
241
  subpath: create_path,
226
242
  json_params: package_create_params,
227
243
  headers: {'Accept' => 'text/javascript'}})[:http].body
228
- # get args of function call
244
+ # get arguments of function call
229
245
  package_creation_data.delete!("\n") # one line
230
246
  package_creation_data.gsub!(/^[^"]+\("\{/, '{') # delete header
231
247
  package_creation_data.gsub!(/"\);[^"]+$/, '"') # delete trailer
232
- package_creation_data.gsub!(/\}", *"/, '},"') # between two args
248
+ package_creation_data.gsub!(/\}", *"/, '},"') # between two arguments
233
249
  package_creation_data.gsub!('\\"', '"') # remove protecting quote
234
250
  begin
235
251
  package_creation_data = JSON.parse("[#{package_creation_data}]")
@@ -254,18 +270,20 @@ module Aspera
254
270
  end
255
271
  return nagios.result
256
272
  when :package
257
- command_pkg = options.get_next_command(%i[send recv list])
273
+ command_pkg = options.get_next_command(%i[send receive list show], aliases: {recv: :receive})
258
274
  case command_pkg
275
+ when :show
276
+ delivery_id = instance_identifier
277
+ return {type: :single_object, data: mailbox_filtered_entries(stop_at_id: delivery_id).find{|p|p[PACKAGE_MATCH_FIELD].eql?(delivery_id)} }
259
278
  when :list
260
279
  return {
261
- type: :object_list,
262
- data: mailbox_filtered_entries,
263
- fields: [PACKAGE_MATCH_FIELD, 'title', 'items'],
264
- textify: lambda {|table_data|Faspex.textify_package_list(table_data)}
280
+ type: :object_list,
281
+ data: mailbox_filtered_entries,
282
+ fields: [PACKAGE_MATCH_FIELD, 'title', 'items']
265
283
  }
266
284
  when :send
267
285
  delivery_info = options.get_option(:delivery_info, mandatory: true)
268
- raise CliBadArgument, 'delivery_info must be hash, refer to doc' unless delivery_info.is_a?(Hash)
286
+ assert_type(delivery_info, Hash, exception_class: Cli::BadArgument){'delivery_info'}
269
287
  # actual parameter to faspex API
270
288
  package_create_params = {'delivery' => delivery_info}
271
289
  public_link_url = options.get_option(:link)
@@ -274,32 +292,32 @@ module Aspera
274
292
  delivery_info['sources'] ||= [{'paths' => []}]
275
293
  first_source = delivery_info['sources'].first
276
294
  first_source['paths'].push(*transfer.source_list)
277
- source_name = options.get_option(:source_name)
278
- if !source_name.nil?
295
+ source_id = instance_identifier(as_option: :remote_source) do |field, value|
296
+ assert(field.eql?('name'), exception_class: Cli::BadArgument){'only name as selector, or give id'}
279
297
  source_list = api_v3.call({operation: 'GET', subpath: 'source_shares', headers: {'Accept' => 'application/json'}})[:data]['items']
280
- source_id = self.class.get_source_id(source_list, source_name)
281
- first_source['id'] = source_id
298
+ self.class.get_source_id_by_name(value, source_list)
282
299
  end
300
+ first_source['id'] = source_id.to_i unless source_id.nil?
283
301
  pkg_created = api_v3.call({
284
302
  operation: 'POST',
285
303
  subpath: 'send',
286
304
  json_params: package_create_params,
287
305
  headers: {'Accept' => 'application/json'}
288
306
  })[:data]
289
- if !source_name.nil?
290
- # no transfer spec if remote source
307
+ if first_source.key?('id')
308
+ # no transfer spec if remote source: handled by faspex
291
309
  return {data: [pkg_created['links']['status']], type: :value_list, name: 'link'}
292
310
  end
293
- raise CliBadArgument, 'expecting one session exactly' if pkg_created['xfer_sessions'].length != 1
311
+ raise Cli::BadArgument, 'expecting one session exactly' if pkg_created['xfer_sessions'].length != 1
294
312
  transfer_spec = pkg_created['xfer_sessions'].first
295
313
  # use source from cmd line, this one only contains destination (already in dest root)
296
314
  transfer_spec.delete('paths')
297
315
  else # public link
298
316
  transfer_spec = send_public_link_to_ts(public_link_url, package_create_params)
299
317
  end
300
- # Log.dump('transfer_spec',transfer_spec)
318
+ # Log.log.debug{Log.dump('transfer_spec',transfer_spec)}
301
319
  return Main.result_transfer(transfer.start(transfer_spec))
302
- when :recv
320
+ when :receive
303
321
  link_url = options.get_option(:link)
304
322
  # list of faspex ID/URI to download
305
323
  pkg_id_uri = nil
@@ -322,11 +340,16 @@ module Aspera
322
340
  delivery_id = instance_identifier
323
341
  raise 'empty id' if delivery_id.empty?
324
342
  recipient = options.get_option(:recipient)
325
- if VAL_ALL.eql?(delivery_id)
343
+ if delivery_id.eql?(ExtendedValue::ALL)
326
344
  pkg_id_uri = mailbox_filtered_entries.map{|i|{id: i[PACKAGE_MATCH_FIELD], uri: self.class.get_fasp_uri_from_entry(i, raise_no_link: false)}}
345
+ elsif delivery_id.eql?(ExtendedValue::INIT)
346
+ assert(skip_ids_persistency){'Only with option once_only'}
347
+ skip_ids_persistency.data.clear.concat(mailbox_filtered_entries.map{|i|{id: i[PACKAGE_MATCH_FIELD]}})
348
+ skip_ids_persistency.save
349
+ return Main.result_status("Initialized skip for #{skip_ids_persistency.data.count} package(s)")
327
350
  elsif !recipient.nil? && recipient.start_with?('*')
328
351
  found_package_link = mailbox_filtered_entries(stop_at_id: delivery_id).find{|p|p[PACKAGE_MATCH_FIELD].eql?(delivery_id)}['link'].first['href']
329
- raise 'Not Found. Dropbox and Workgroup packages can use the link option with faspe:' if found_package_link.nil?
352
+ raise "Not Found. Dropbox and Workgroup packages can use the link option with #{Fasp::Uri::SCHEME}" if found_package_link.nil?
330
353
  pkg_id_uri = [{id: delivery_id, uri: found_package_link}]
331
354
  else
332
355
  # TODO: delivery id is the right one if package was receive by workgroup
@@ -339,12 +362,12 @@ module Aspera
339
362
  package_entry = XmlSimple.xml_in(entry_xml, {'ForceArray' => true})
340
363
  pkg_id_uri = [{id: delivery_id, uri: self.class.get_fasp_uri_from_entry(package_entry)}]
341
364
  end
342
- when /^faspe:/
365
+ when /^#{Fasp::Uri::SCHEME}:/o
343
366
  pkg_id_uri = [{id: 'package', uri: link_url}]
344
367
  else
345
368
  link_data = self.class.get_link_data(link_url)
346
369
  if !link_data[:subpath].start_with?(PUB_LINK_EXTERNAL_MATCH)
347
- raise CliBadArgument, "Pub link is #{link_data[:subpath]}. Expecting #{PUB_LINK_EXTERNAL_MATCH}"
370
+ raise Cli::BadArgument, "Pub link is #{link_data[:subpath]}. Expecting #{PUB_LINK_EXTERNAL_MATCH}"
348
371
  end
349
372
  # NOTE: unauthenticated API (authorization is in url params)
350
373
  api_public_link = Rest.new({base_url: link_data[:base_url]})
@@ -355,10 +378,10 @@ module Aspera
355
378
  headers: {'Accept' => 'application/xml'})
356
379
  if !package_creation_data[:http].body.start_with?('<?xml ')
357
380
  OpenApplication.instance.uri(link_url)
358
- raise CliError, 'Unexpected response: package not found ?'
381
+ raise Cli::Error, 'Unexpected response: package not found ?'
359
382
  end
360
383
  package_entry = XmlSimple.xml_in(package_creation_data[:http].body, {'ForceArray' => false})
361
- Log.dump(:package_entry, package_entry)
384
+ Log.log.debug{Log.dump(:package_entry, package_entry)}
362
385
  transfer_uri = self.class.get_fasp_uri_from_entry(package_entry)
363
386
  pkg_id_uri = [{id: package_entry['id'], uri: transfer_uri}]
364
387
  end # public link
@@ -366,7 +389,7 @@ module Aspera
366
389
  # TODO : remove ids from skip not present in inbox to avoid growing too big
367
390
  # skip_ids_data.select!{|id|pkg_id_uri.select{|p|p[:id].eql?(id)}}
368
391
  pkg_id_uri.reject!{|i|skip_ids_data.include?(i[:id])}
369
- Log.dump(:pkg_id_uri, pkg_id_uri)
392
+ Log.log.debug{Log.dump(:pkg_id_uri, pkg_id_uri)}
370
393
  return Main.result_status('no new package') if pkg_id_uri.empty?
371
394
  result_transfer = []
372
395
  pkg_id_uri.each do |id_uri|
@@ -375,7 +398,7 @@ module Aspera
375
398
  statuses = [:success]
376
399
  else
377
400
  transfer_spec = Fasp::Uri.new(id_uri[:uri]).transfer_spec
378
- # NOTE: only external users have token in faspe: link !
401
+ # NOTE: only external users have token in Fasp::Uri::SCHEME link !
379
402
  if !transfer_spec.key?('token')
380
403
  sanitized = id_uri[:uri].gsub('&', '&amp;')
381
404
  xml_payload =
@@ -397,42 +420,38 @@ module Aspera
397
420
  return Main.result_transfer_multiple(result_transfer)
398
421
  end
399
422
  when :source
400
- command_source = options.get_next_command(%i[list id name])
423
+ command_source = options.get_next_command(%i[list info node])
401
424
  source_list = api_v3.call({operation: 'GET', subpath: 'source_shares', headers: {'Accept' => 'application/json'}})[:data]['items']
402
425
  case command_source
403
426
  when :list
404
427
  return {type: :object_list, data: source_list}
405
- else # :id or :name
406
- source_match_val = options.get_next_argument('source id or name')
407
- source_ids = source_list.select { |i| i[command_source.to_s].to_s.eql?(source_match_val) }
408
- if source_ids.empty?
409
- raise CliError, "No such Faspex source #{command_source}: #{source_match_val} in [#{source_list.map{|i| i[command_source.to_s]}.join(', ')}]"
410
- end
411
- # get id and name
412
- source_name = source_ids.first['name']
413
- # source_id=source_ids.first['id']
428
+ else # :info :node
429
+ source_id = instance_identifier do |field, value|
430
+ assert(field.eql?('name'), exception_class: Cli::BadArgument){'only name as selector, or give id'}
431
+ self.class.get_source_id_by_name(value, source_list)
432
+ end.to_i
433
+ source_name = source_list.find{|i|i['id'].eql?(source_id)}['name']
414
434
  source_hash = options.get_option(:storage, mandatory: true)
415
435
  # check value of option
416
- raise CliError, 'storage option must be a Hash' unless source_hash.is_a?(Hash)
436
+ assert_type(source_hash, Hash, exception_class: Cli::Error){'storage option'}
417
437
  source_hash.each do |name, storage|
418
- raise CliError, "storage '#{name}' must be a Hash" unless storage.is_a?(Hash)
438
+ assert_type(storage, Hash, exception_class: Cli::Error){"storage '#{name}'"}
419
439
  [KEY_NODE, KEY_PATH].each do |key|
420
- raise CliError, "storage '#{name}' must have a '#{key}'" unless storage.key?(key)
440
+ assert(storage.key?(key), exception_class: Cli::Error){"storage '#{name}' must have a '#{key}'"}
421
441
  end
422
442
  end
423
443
  if !source_hash.key?(source_name)
424
- raise CliError, "No such storage in config file: \"#{source_name}\" in [#{source_hash.keys.join(', ')}]"
444
+ raise Cli::Error, "No such storage in config file: \"#{source_name}\" in [#{source_hash.keys.join(', ')}]"
425
445
  end
426
446
  source_info = source_hash[source_name]
427
- Log.log.debug{"source_info: #{source_info}"}
428
- command_node = options.get_next_command(%i[info node])
429
- case command_node
447
+ Log.log.debug{Log.dump(:source_info, source_info)}
448
+ case command_source
430
449
  when :info
431
450
  return {data: source_info, type: :single_object}
432
451
  when :node
433
452
  node_config = ExtendedValue.instance.evaluate(source_info[KEY_NODE])
434
- raise CliError, "bad type for: \"#{source_info[KEY_NODE]}\"" unless node_config.is_a?(Hash)
435
453
  Log.log.debug{"node=#{node_config}"}
454
+ assert_type(node_config, Hash, exception_class: Cli::Error){source_info[KEY_NODE]}
436
455
  api_node = Rest.new({
437
456
  base_url: node_config['url'],
438
457
  auth: {
@@ -440,7 +459,7 @@ module Aspera
440
459
  username: node_config['username'],
441
460
  password: node_config['password']}})
442
461
  command = options.get_next_command(Node::COMMANDS_FASPEX)
443
- return Node.new(@agents.merge(skip_basic_auth_options: true, node_api: api_node)).execute_action(command, source_info[KEY_PATH])
462
+ return Node.new(@agents, api: api_node).execute_action(command, source_info[KEY_PATH])
444
463
  end
445
464
  end
446
465
  when :me