aspera-cli 4.13.0 → 4.15.0

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