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