aspera-cli 4.14.0 → 4.16.0

Sign up to get free protection for your applications and to get access to all the features.
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