aspera-cli 4.4.0 → 4.7.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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2095 -1503
  3. data/bin/ascli +2 -1
  4. data/bin/asession +4 -5
  5. data/docs/test_env.conf +3 -0
  6. data/examples/aoc.rb +4 -3
  7. data/examples/faspex4.rb +25 -25
  8. data/examples/proxy.pac +1 -1
  9. data/examples/transfer.rb +17 -17
  10. data/lib/aspera/aoc.rb +238 -185
  11. data/lib/aspera/ascmd.rb +93 -83
  12. data/lib/aspera/ats_api.rb +11 -10
  13. data/lib/aspera/cli/basic_auth_plugin.rb +13 -14
  14. data/lib/aspera/cli/extended_value.rb +42 -33
  15. data/lib/aspera/cli/formater.rb +142 -108
  16. data/lib/aspera/cli/info.rb +17 -0
  17. data/lib/aspera/cli/listener/line_dump.rb +3 -2
  18. data/lib/aspera/cli/listener/logger.rb +2 -1
  19. data/lib/aspera/cli/listener/progress.rb +16 -18
  20. data/lib/aspera/cli/listener/progress_multi.rb +18 -21
  21. data/lib/aspera/cli/main.rb +173 -149
  22. data/lib/aspera/cli/manager.rb +163 -168
  23. data/lib/aspera/cli/plugin.rb +43 -31
  24. data/lib/aspera/cli/plugins/alee.rb +6 -6
  25. data/lib/aspera/cli/plugins/aoc.rb +405 -370
  26. data/lib/aspera/cli/plugins/ats.rb +86 -79
  27. data/lib/aspera/cli/plugins/bss.rb +14 -16
  28. data/lib/aspera/cli/plugins/config.rb +580 -362
  29. data/lib/aspera/cli/plugins/console.rb +23 -19
  30. data/lib/aspera/cli/plugins/cos.rb +18 -18
  31. data/lib/aspera/cli/plugins/faspex.rb +201 -158
  32. data/lib/aspera/cli/plugins/faspex5.rb +80 -57
  33. data/lib/aspera/cli/plugins/node.rb +183 -166
  34. data/lib/aspera/cli/plugins/orchestrator.rb +71 -67
  35. data/lib/aspera/cli/plugins/preview.rb +92 -96
  36. data/lib/aspera/cli/plugins/server.rb +79 -75
  37. data/lib/aspera/cli/plugins/shares.rb +35 -19
  38. data/lib/aspera/cli/plugins/sync.rb +20 -22
  39. data/lib/aspera/cli/transfer_agent.rb +76 -113
  40. data/lib/aspera/cli/version.rb +2 -1
  41. data/lib/aspera/colors.rb +35 -27
  42. data/lib/aspera/command_line_builder.rb +48 -34
  43. data/lib/aspera/cos_node.rb +29 -21
  44. data/lib/aspera/data_repository.rb +3 -2
  45. data/lib/aspera/environment.rb +50 -45
  46. data/lib/aspera/fasp/{manager.rb → agent_base.rb} +28 -25
  47. data/lib/aspera/fasp/{connect.rb → agent_connect.rb} +52 -43
  48. data/lib/aspera/fasp/{local.rb → agent_direct.rb} +58 -72
  49. data/lib/aspera/fasp/{http_gw.rb → agent_httpgw.rb} +37 -43
  50. data/lib/aspera/fasp/{node.rb → agent_node.rb} +35 -16
  51. data/lib/aspera/fasp/agent_trsdk.rb +104 -0
  52. data/lib/aspera/fasp/error.rb +2 -1
  53. data/lib/aspera/fasp/error_info.rb +68 -52
  54. data/lib/aspera/fasp/installation.rb +152 -124
  55. data/lib/aspera/fasp/listener.rb +1 -0
  56. data/lib/aspera/fasp/parameters.rb +87 -92
  57. data/lib/aspera/fasp/parameters.yaml +305 -249
  58. data/lib/aspera/fasp/resume_policy.rb +11 -14
  59. data/lib/aspera/fasp/transfer_spec.rb +26 -0
  60. data/lib/aspera/fasp/uri.rb +22 -21
  61. data/lib/aspera/faspex_gw.rb +55 -89
  62. data/lib/aspera/hash_ext.rb +4 -3
  63. data/lib/aspera/id_generator.rb +8 -7
  64. data/lib/aspera/keychain/encrypted_hash.rb +121 -0
  65. data/lib/aspera/keychain/macos_security.rb +90 -0
  66. data/lib/aspera/log.rb +55 -37
  67. data/lib/aspera/nagios.rb +13 -12
  68. data/lib/aspera/node.rb +30 -25
  69. data/lib/aspera/oauth.rb +175 -226
  70. data/lib/aspera/open_application.rb +4 -3
  71. data/lib/aspera/persistency_action_once.rb +6 -6
  72. data/lib/aspera/persistency_folder.rb +5 -9
  73. data/lib/aspera/preview/file_types.rb +6 -5
  74. data/lib/aspera/preview/generator.rb +25 -24
  75. data/lib/aspera/preview/options.rb +16 -14
  76. data/lib/aspera/preview/utils.rb +98 -98
  77. data/lib/aspera/{proxy_auto_config.erb.js → proxy_auto_config.js} +23 -31
  78. data/lib/aspera/proxy_auto_config.rb +111 -20
  79. data/lib/aspera/rest.rb +154 -135
  80. data/lib/aspera/rest_call_error.rb +2 -2
  81. data/lib/aspera/rest_error_analyzer.rb +23 -25
  82. data/lib/aspera/rest_errors_aspera.rb +15 -14
  83. data/lib/aspera/ssh.rb +12 -10
  84. data/lib/aspera/sync.rb +42 -41
  85. data/lib/aspera/temp_file_manager.rb +18 -14
  86. data/lib/aspera/timer_limiter.rb +2 -1
  87. data/lib/aspera/uri_reader.rb +7 -5
  88. data/lib/aspera/web_auth.rb +79 -76
  89. metadata +116 -29
  90. data/docs/Makefile +0 -66
  91. data/docs/README.erb.md +0 -3973
  92. data/docs/README.md +0 -13
  93. data/docs/diagrams.txt +0 -49
  94. data/docs/doc_tools.rb +0 -58
  95. data/lib/aspera/api_detector.rb +0 -60
  96. data/lib/aspera/cli/plugins/shares2.rb +0 -114
  97. data/lib/aspera/secrets.rb +0 -20
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'aspera/cli/basic_auth_plugin'
2
3
  require 'aspera/cli/plugins/node'
3
4
  require 'aspera/cli/plugins/config'
@@ -6,6 +7,7 @@ require 'aspera/cli/transfer_agent'
6
7
  require 'aspera/persistency_action_once'
7
8
  require 'aspera/open_application'
8
9
  require 'aspera/fasp/uri'
10
+ require 'aspera/fasp/transfer_spec'
9
11
  require 'aspera/nagios'
10
12
  require 'aspera/id_generator'
11
13
  require 'xmlsimple'
@@ -16,8 +18,9 @@ module Aspera
16
18
  module Cli
17
19
  module Plugins
18
20
  class Faspex < BasicAuthPlugin
19
- KEY_NODE='node'
20
- KEY_PATH='path'
21
+ # required hash key for source in config
22
+ KEY_NODE='node' # value must be hash with url, username, password
23
+ KEY_PATH='path' # value must be same sub-path as in Faspex's node
21
24
  VAL_ALL='ALL'
22
25
  # added field in result that identifies the package
23
26
  PACKAGE_MATCH_FIELD='package_id'
@@ -25,71 +28,85 @@ module Aspera
25
28
  ATOM_MAILBOXES=[:inbox, :archive, :sent]
26
29
  # allowed parameters for inbox.atom
27
30
  ATOM_PARAMS=['page', 'count', 'startIndex']
28
- # with special parameters (from Plugin class)
31
+ # with special parameters (from Plugin class) : max and pmax
29
32
  ATOM_EXT_PARAMS=ATOM_PARAMS+[MAX_ITEMS, MAX_PAGES]
30
33
  # sub path in url for public link delivery
31
34
  PUB_LINK_EXTERNAL_MATCH='external_deliveries/'
32
35
  private_constant :KEY_NODE,:KEY_PATH,:VAL_ALL,:PACKAGE_MATCH_FIELD,:ATOM_MAILBOXES,
33
36
  :ATOM_PARAMS,:ATOM_EXT_PARAMS,:PUB_LINK_EXTERNAL_MATCH
34
37
 
35
- def initialize(env)
36
- @api_v3=nil
37
- @api_v4=nil
38
- super(env)
39
- self.options.add_opt_simple(:link,'public link for specific operation')
40
- self.options.add_opt_simple(:delivery_info,'package delivery information (extended value)')
41
- self.options.add_opt_simple(:source_name,'create package from remote source (by name)')
42
- self.options.add_opt_simple(:storage,'Faspex local storage definition')
43
- self.options.add_opt_simple(:recipient,'use if recipient is a dropbox (with *)')
44
- self.options.add_opt_list(:box,ATOM_MAILBOXES,'package box')
45
- self.options.set_option(:box,:inbox)
46
- self.options.parse_options!
47
- end
38
+ class << self
39
+ def detect(base_url)
40
+ api=Rest.new({base_url: base_url})
41
+ result=api.call({operation: 'POST',subpath: 'aspera/faspex',headers: {'Accept'=>'application/xrds+xml'},text_body_params: ''})
42
+ # 4.x
43
+ if result[:http].body.start_with?('<?xml')
44
+ res_s=XmlSimple.xml_in(result[:http].body, {'ForceArray' => false})
45
+ version=res_s['XRD']['application']['version']
46
+ return {version: version}
47
+ end
48
+ return nil
49
+ end
48
50
 
49
- # extract elements from anonymous faspex link
50
- def self.get_link_data(publink)
51
- publink_uri=URI.parse(publink)
52
- if m=publink_uri.path.match(/^(.*)\/(external.*)$/)
51
+ # extract elements from anonymous faspex link
52
+ def get_link_data(publink)
53
+ publink_uri=URI.parse(publink)
54
+ raise CliBadArgument, 'public link does not match Faspex format' unless (m=publink_uri.path.match(/^(.*)\/(external.*)$/))
53
55
  base=m[1]
54
56
  subpath=m[2]
55
- else
56
- raise CliBadArgument, 'public link does not match Faspex format'
57
+ port_add=publink_uri.port.eql?(publink_uri.default_port) ? '' : ":#{publink_uri.port}"
58
+ result={
59
+ base_url: "#{publink_uri.scheme}://#{publink_uri.host}#{port_add}#{base}",
60
+ subpath: subpath,
61
+ query: URI.decode_www_form(publink_uri.query).each_with_object({}){|v,h|h[v.first]=v.last;}
62
+ }
63
+ Log.dump('publink',result)
64
+ return result
57
65
  end
58
- port_add=publink_uri.port.eql?(publink_uri.default_port)?'':":#{publink_uri.port}"
59
- result={
60
- :base_url => "#{publink_uri.scheme}://#{publink_uri.host}#{port_add}#{base}",
61
- :subpath => subpath,
62
- :query => URI::decode_www_form(publink_uri.query).inject({}){|h,v|h[v.first]=v.last;h}
63
- }
64
- Log.dump('publink',result)
65
- return result
66
- end
67
66
 
68
- # get faspe: URI from entry in xml, and fix problems..
69
- def self.get_fasp_uri_from_entry(entry)
70
- raise CliBadArgument, 'package has no link (deleted?)' unless entry.has_key?('link')
71
- result=entry['link'].select{|e| e['rel'].eql?('package')}.first['href']
72
- # tags in the end of URL is not well % encoded... there are "=" that should be %3D
73
- # TODO: enter ticket to Faspex ?
74
- ###XXif m=result.match(/(=+)$/);result.gsub!(/=+$/,"#{"%3D"*m[1].length}");end
75
- return result
76
- end
67
+ # get faspe: URI from entry in xml, and fix problems..
68
+ def get_fasp_uri_from_entry(entry, raise_no_link: true)
69
+ unless entry.has_key?('link')
70
+ raise CliBadArgument, 'package has no link (deleted?)' if raise_no_link
71
+ return nil
72
+ end
73
+ result=entry['link'].select{|e| e['rel'].eql?('package')}.first['href']
74
+ # tags in the end of URL is not well % encoded... there are "=" that should be %3D
75
+ # TODO: enter ticket to Faspex ?
76
+ ###XXif m=result.match(/(=+)$/);result.gsub!(/=+$/,"#{"%3D"*m[1].length}");end
77
+ return result
78
+ end
77
79
 
78
- def self.textify_package_list(table_data)
79
- return table_data.map { |e|
80
- e.keys.each {|k| e[k]=e[k].first if e[k].is_a?(Array) and e[k].length == 1}
81
- e['items'] = e.has_key?('link') ? e['link'].length : 0
82
- e
83
- }
84
- end
80
+ def textify_package_list(table_data)
81
+ return table_data.map do |e|
82
+ e.keys.each {|k| e[k]=e[k].first if e[k].is_a?(Array) && (e[k].length == 1)}
83
+ e['items'] = e.has_key?('link') ? e['link'].length : 0
84
+ e
85
+ end
86
+ end
85
87
 
86
- # field_sym : :id or :name
87
- def self.get_source_id(source_list,source_name)
88
- source_ids=source_list.select { |i| i['name'].eql?(source_name) }
89
- if source_ids.empty?
90
- raise CliError,%Q{No such Faspex source "#{source_name}" in [#{source_list.map{|i| %Q{"#{i['name']}"}}.join(', ')}]}
88
+ # field_sym : :id or :name
89
+ def get_source_id(source_list,source_name)
90
+ source_ids=source_list.select { |i| i['name'].eql?(source_name) }
91
+ if source_ids.empty?
92
+ raise CliError,%Q(No such Faspex source "#{source_name}" in [#{source_list.map{|i| %Q("#{i['name']}")}.join(', ')}])
93
+ end
94
+ return source_ids.first['id']
91
95
  end
92
- return source_ids.first['id']
96
+ end
97
+
98
+ def initialize(env)
99
+ @api_v3=nil
100
+ @api_v4=nil
101
+ super(env)
102
+ options.add_opt_simple(:link,'public link for specific operation')
103
+ options.add_opt_simple(:delivery_info,'package delivery information (extended value)')
104
+ options.add_opt_simple(:source_name,'create package from remote source (by name)')
105
+ options.add_opt_simple(:storage,'Faspex local storage definition')
106
+ options.add_opt_simple(:recipient,'use if recipient is a dropbox (with *)')
107
+ options.add_opt_list(:box,ATOM_MAILBOXES,'package box')
108
+ options.set_option(:box,:inbox)
109
+ options.parse_options!
93
110
  end
94
111
 
95
112
  def api_v3
@@ -101,41 +118,45 @@ module Aspera
101
118
 
102
119
  def api_v4
103
120
  if @api_v4.nil?
104
- faspex_api_base=self.options.get_option(:url,:mandatory)
121
+ faspex_api_base=options.get_option(:url,:mandatory)
105
122
  @api_v4=Rest.new({
106
- :base_url => faspex_api_base+'/api',
107
- :auth => {
108
- :type => :oauth2,
109
- :base_url => faspex_api_base+'/auth/oauth2',
110
- :grant => :header_userpass,
111
- :user_name => self.options.get_option(:username,:mandatory),
112
- :user_pass => self.options.get_option(:password,:mandatory),
113
- :scope => 'admin'
123
+ base_url: faspex_api_base+'/api',
124
+ auth: {
125
+ type: :oauth2,
126
+ base_url: faspex_api_base+'/auth/oauth2',
127
+ auth: {type: :basic, username: options.get_option(:username,:mandatory), password: options.get_option(:password,:mandatory)},
128
+ crtype: :generic,
129
+ generic: {grant_type: 'password'},
130
+ scope: 'admin'
114
131
  }})
115
132
  end
116
133
  return @api_v4
117
134
  end
118
135
 
119
- # query supports : {"startIndex":10,"count":1,"page":109}
120
- def mailbox_all_entries
121
- recipient_name=self.options.get_option(:recipient,:optional) || self.options.get_option(:username,:mandatory)
136
+ # query supports : {"startIndex":10,"count":1,"page":109,"max":2,"pmax":1}
137
+ def mailbox_filtered_entries(stop_at_id: nil)
138
+ recipient_names=[options.get_option(:recipient,:optional) || options.get_option(:username,:mandatory)]
139
+ # some workgroup messages have no star in recipient name
140
+ recipient_names.push(recipient_names.first[1..-1]) if recipient_names.first.start_with?('*')
122
141
  # mailbox is in ATOM_MAILBOXES
123
- mailbox=self.options.get_option(:box,:mandatory)
142
+ mailbox=options.get_option(:box,:mandatory)
124
143
  # parameters
125
- mailbox_query=self.options.get_option(:query,:optional)
144
+ mailbox_query=options.get_option(:query,:optional)
126
145
  max_items=nil
127
146
  max_pages=nil
128
147
  result=[]
129
148
  if !mailbox_query.nil?
130
- raise "query: must be Hash or nil" unless mailbox_query.is_a?(Hash)
149
+ raise 'query: must be Hash or nil' unless mailbox_query.is_a?(Hash)
131
150
  raise "query: supported params: #{ATOM_EXT_PARAMS}" unless (mailbox_query.keys-ATOM_EXT_PARAMS).empty?
132
- raise "query: startIndex and page are exclusive" if mailbox_query.has_key?('startIndex') and mailbox_query.has_key?('page')
151
+ raise 'query: startIndex and page are exclusive' if mailbox_query.has_key?('startIndex') && mailbox_query.has_key?('page')
133
152
  max_items=mailbox_query[MAX_ITEMS]
134
153
  mailbox_query.delete(MAX_ITEMS)
135
154
  max_pages=mailbox_query[MAX_PAGES]
136
155
  mailbox_query.delete(MAX_PAGES)
137
156
  end
138
157
  loop do
158
+ # get a batch of package information
159
+ # order: first batch is latest packages, and then in a batch ids are increasing
139
160
  atom_xml=api_v3.call({operation: 'GET',subpath: "#{mailbox}.atom",headers: {'Accept'=>'application/xml'},url_params: mailbox_query})[:http].body
140
161
  box_data=XmlSimple.xml_in(atom_xml, {'ForceArray' => true})
141
162
  Log.dump(:box_data,box_data)
@@ -143,29 +164,39 @@ module Aspera
143
164
  Log.log.debug("new items: #{items.count}")
144
165
  # it is the end if page is empty
145
166
  break if items.empty?
146
- items.each do |package|
147
- package[PACKAGE_MATCH_FIELD]=case mailbox
167
+ stop_condition=false
168
+ # results will be sorted in reverse id
169
+ items.reverse.each do |package|
170
+ package[PACKAGE_MATCH_FIELD]=
171
+ case mailbox
148
172
  when :inbox,:archive
149
- recipient=package['to'].select{|i|i['name'].first.eql?(recipient_name)}.first
173
+ recipient=package['to'].select{|i|recipient_names.include?(i['name'].first)}.first
150
174
  recipient.nil? ? nil : recipient['recipient_delivery_id'].first
151
175
  else # :sent
152
176
  package['delivery_id'].first
153
177
  end
154
- # keep only those for the specified recipient
178
+ # if we look for a specific package
179
+ stop_condition=true if !stop_at_id.nil? && stop_at_id.eql?(package[PACKAGE_MATCH_FIELD])
180
+ # keep only those for the specified recipient,
155
181
  result.push(package) unless package[PACKAGE_MATCH_FIELD].nil?
156
182
  end
183
+ break if stop_condition
184
+ #result.push({PACKAGE_MATCH_FIELD=>'======'})
157
185
  Log.log.debug("total items: #{result.count}")
158
186
  # reach the limit ?
159
- break if !max_items.nil? and result.count > max_items
187
+ if !max_items.nil? && (result.count >= max_items)
188
+ result=result.slice(0,max_items) if result.count > max_items
189
+ break
190
+ end
160
191
  link=box_data['link'].select{|i|i['rel'].eql?('next')}.first
161
192
  Log.log.debug("link: #{link}")
162
193
  # no next link
163
194
  break if link.nil?
164
195
  # replace parameters with the ones from next link
165
196
  params=CGI.parse(URI.parse(link['href']).query)
166
- mailbox_query=params.keys.inject({}){|m,i|;m[i]=params[i].first;m}
197
+ mailbox_query=params.keys.each_with_object({}){|i,m|;m[i]=params[i].first;}
167
198
  Log.log.debug("query: #{mailbox_query}")
168
- break if !max_pages.nil? and mailbox_query['page'].to_i > max_pages
199
+ break if !max_pages.nil? && (mailbox_query['page'].to_i > max_pages)
169
200
  end
170
201
  return result
171
202
  end
@@ -179,15 +210,15 @@ module Aspera
179
210
  raise CliBadArgument,"pub link is #{link_data[:subpath]}, expecting external/submissions/new"
180
211
  end
181
212
  create_path=link_data[:subpath].split('/')[0..-2].join('/')
182
- package_create_params.merge!({:passcode=>link_data[:query]['passcode']})
213
+ package_create_params.merge!({passcode: link_data[:query]['passcode']})
183
214
  delivery_info.merge!({
184
- :transfer_type => 'connect',
185
- :source_paths_list => self.transfer.ts_source_paths.map{|i|i['source']}.join("\r\n")})
186
- api_public_link=Rest.new({:base_url=>link_data[:base_url]})
215
+ transfer_type: 'connect',
216
+ source_paths_list: transfer.ts_source_paths.map{|i|i['source']}.join("\r\n")})
217
+ api_public_link=Rest.new({base_url: link_data[:base_url]})
187
218
  # Hum, as this does not always work (only user, but not dropbox), we get the javascript and need hack
188
219
  #pkg_created=api_public_link.create(create_path,package_create_params)[:data]
189
220
  # so extract data from javascript
190
- pkgdatares=api_public_link.call({:operation=>'POST',:subpath=>create_path,:json_params=>package_create_params,:headers=>{'Accept'=>'text/javascript'}})[:http].body
221
+ pkgdatares=api_public_link.call({operation: 'POST',subpath: create_path,json_params: package_create_params,headers: {'Accept'=>'text/javascript'}})[:http].body
191
222
  # get args of function call
192
223
  pkgdatares.gsub!("\n",'') # one line
193
224
  pkgdatares.gsub!(/^[^"]+\("\{/,'{') # delete header
@@ -197,51 +228,51 @@ module Aspera
197
228
  begin
198
229
  pkgdatares=JSON.parse("[#{pkgdatares}]")
199
230
  rescue JSON::ParserError # => e
200
- raise "Link not valid"
231
+ raise 'Link not valid'
201
232
  end
202
233
  return pkgdatares.first
203
234
  end
204
235
 
205
- ACTIONS=[ :health,:package, :source, :me, :dropbox, :v4, :address_book, :login_methods ]
236
+ ACTIONS=[:health,:package, :source, :me, :dropbox, :v4, :address_book, :login_methods].freeze
206
237
 
207
238
  def execute_action
208
- command=self.options.get_next_command(ACTIONS)
239
+ command=options.get_next_command(ACTIONS)
209
240
  case command
210
241
  when :health
211
242
  nagios=Nagios.new
212
243
  begin
213
244
  api_v3.read('me')
214
245
  nagios.add_ok('faspex api','accessible')
215
- rescue => e
246
+ rescue StandardError => e
216
247
  nagios.add_critical('faspex api',e.to_s)
217
248
  end
218
249
  return nagios.result
219
250
  when :package
220
- command_pkg=self.options.get_next_command([ :send, :recv, :list ])
251
+ command_pkg=options.get_next_command([:send, :recv, :list])
221
252
  case command_pkg
222
253
  when :list
223
- return {:type=>:object_list,:data=>self.mailbox_all_entries,:fields=>[PACKAGE_MATCH_FIELD,'title','items'], :textify => lambda { |table_data| Faspex.textify_package_list(table_data)} }
254
+ return {type: :object_list,data: mailbox_filtered_entries,fields: [PACKAGE_MATCH_FIELD,'title','items'], textify: lambda {|table_data|Faspex.textify_package_list(table_data)} }
224
255
  when :send
225
- delivery_info=self.options.get_option(:delivery_info,:mandatory)
256
+ delivery_info=options.get_option(:delivery_info,:mandatory)
226
257
  raise CliBadArgument,'delivery_info must be hash, refer to doc' unless delivery_info.is_a?(Hash)
227
258
  # actual parameter to faspex API
228
259
  package_create_params={'delivery'=>delivery_info}
229
- public_link_url=self.options.get_option(:link,:optional)
260
+ public_link_url=options.get_option(:link,:optional)
230
261
  if public_link_url.nil?
231
262
  # authenticated user
232
263
  delivery_info['sources']||=[{'paths'=>[]}]
233
264
  first_source=delivery_info['sources'].first
234
- first_source['paths'].push(*self.transfer.ts_source_paths.map{|i|i['source']})
235
- source_name=self.options.get_option(:source_name,:optional)
265
+ first_source['paths'].push(*transfer.ts_source_paths.map{|i|i['source']})
266
+ source_name=options.get_option(:source_name,:optional)
236
267
  if !source_name.nil?
237
- source_list=api_v3.call({:operation=>'GET',:subpath=>'source_shares',:headers=>{'Accept'=>'application/json'}})[:data]['items']
268
+ source_list=api_v3.call({operation: 'GET',subpath: 'source_shares',headers: {'Accept'=>'application/json'}})[:data]['items']
238
269
  source_id=self.class.get_source_id(source_list,source_name)
239
270
  first_source['id']=source_id
240
271
  end
241
- pkg_created=api_v3.call({:operation=>'POST',:subpath=>'send',:json_params=>package_create_params,:headers=>{'Accept'=>'application/json'}})[:data]
272
+ pkg_created=api_v3.call({operation: 'POST',subpath: 'send',json_params: package_create_params,headers: {'Accept'=>'application/json'}})[:data]
242
273
  if !source_name.nil?
243
274
  # no transfer spec if remote source
244
- return {:data=>[pkg_created['links']['status']],:type=>:value_list,:name=>'link'}
275
+ return {data: [pkg_created['links']['status']],type: :value_list,name: 'link'}
245
276
  end
246
277
  raise CliBadArgument,'expecting one session exactly' if pkg_created['xfer_sessions'].length != 1
247
278
  transfer_spec=pkg_created['xfer_sessions'].first
@@ -251,52 +282,55 @@ module Aspera
251
282
  transfer_spec=send_publink_to_ts(public_link_url,package_create_params)
252
283
  end
253
284
  #Log.dump('transfer_spec',transfer_spec)
254
- return Main.result_transfer(self.transfer.start(transfer_spec,{:src=>:node_gen3}))
285
+ return Main.result_transfer(transfer.start(transfer_spec,{src: :node_gen3}))
255
286
  when :recv
256
- link_url=self.options.get_option(:link,:optional)
287
+ link_url=options.get_option(:link,:optional)
257
288
  # list of faspex ID/URI to download
258
289
  pkg_id_uri=nil
259
290
  skip_ids_data=[]
260
291
  skip_ids_persistency=nil
261
292
  case link_url
262
293
  when nil # usual case: no link
263
- if self.options.get_option(:once_only,:mandatory)
294
+ if options.get_option(:once_only,:mandatory)
264
295
  skip_ids_persistency=PersistencyActionOnce.new(
265
296
  manager: @agents[:persistency],
266
297
  data: skip_ids_data,
267
- id: IdGenerator.from_list(['faspex_recv',self.options.get_option(:url,:mandatory),self.options.get_option(:username,:mandatory),self.options.get_option(:box,:mandatory).to_s]))
298
+ id: IdGenerator.from_list(['faspex_recv',options.get_option(:url,:mandatory),options.get_option(:username,:mandatory),options.get_option(:box,:mandatory).to_s]))
268
299
  end
269
300
  # get command line parameters
270
- delivid=self.options.get_option(:id,:mandatory)
301
+ delivid=instance_identifier()
302
+ raise 'empty id' if delivid.empty?
303
+ recipient=options.get_option(:recipient,:optional)
271
304
  if delivid.eql?(VAL_ALL)
272
- pkg_id_uri=mailbox_all_entries.map{|i|{:id=>i[PACKAGE_MATCH_FIELD],:uri=>self.class.get_fasp_uri_from_entry(i)}}
273
- # TODO : remove ids from skip not present in inbox
305
+ 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)}}
306
+ # TODO : remove ids from skip not present in inbox to avoid growing too big
274
307
  # skip_ids_data.select!{|id|pkg_id_uri.select{|p|p[:id].eql?(id)}}
275
- pkg_id_uri.select!{|i|!skip_ids_data.include?(i[:id])}
308
+ pkg_id_uri.reject!{|i|skip_ids_data.include?(i[:id])}
309
+ elsif !recipient.nil? && recipient.start_with?('*')
310
+ found_package_link=mailbox_filtered_entries(stop_at_id: delivid).select{|p|p[PACKAGE_MATCH_FIELD].eql?(delivid)}.first['link'].first['href']
311
+ raise 'Not Found. Dropbox and Workgroup packages can use the link option with faspe:' if found_package_link.nil?
312
+ pkg_id_uri=[{id: delivid,uri: found_package_link}]
276
313
  else
277
- recipient=options.get_option(:recipient,:optional)
278
- if !recipient.nil? and recipient.start_with?('*')
279
- raise "Dropbox and Workgroup packages should use link option with faspe:"
280
- end
281
314
  # TODO: delivery id is the right one if package was receive by workgroup
282
- endpoint=case self.options.get_option(:box,:mandatory)
283
- when :inbox,:archive;'received'
284
- when :sent; 'sent'
315
+ endpoint=
316
+ case options.get_option(:box,:mandatory)
317
+ when :inbox,:archive then'received'
318
+ when :sent then 'sent'
285
319
  end
286
- entry_xml=api_v3.call({:operation=>'GET',:subpath=>"#{endpoint}/#{delivid}",:headers=>{'Accept'=>'application/xml'}})[:http].body
320
+ entry_xml=api_v3.call({operation: 'GET',subpath: "#{endpoint}/#{delivid}",headers: {'Accept'=>'application/xml'}})[:http].body
287
321
  package_entry=XmlSimple.xml_in(entry_xml, {'ForceArray' => true})
288
- pkg_id_uri=[{:id=>delivid,:uri=>self.class.get_fasp_uri_from_entry(package_entry)}]
322
+ pkg_id_uri=[{id: delivid,uri: self.class.get_fasp_uri_from_entry(package_entry)}]
289
323
  end
290
324
  when /^faspe:/
291
- pkg_id_uri=[{:id=>'package',:uri=>link_url}]
325
+ pkg_id_uri=[{id: 'package',uri: link_url}]
292
326
  else
293
327
  link_data=self.class.get_link_data(link_url)
294
328
  if !link_data[:subpath].start_with?(PUB_LINK_EXTERNAL_MATCH)
295
329
  raise CliBadArgument,"Pub link is #{link_data[:subpath]}. Expecting #{PUB_LINK_EXTERNAL_MATCH}"
296
330
  end
297
- # Note: unauthenticated API (autorization is in url params)
298
- api_public_link=Rest.new({:base_url=>link_data[:base_url]})
299
- pkgdatares=api_public_link.call({:operation=>'GET',:subpath=>link_data[:subpath],:url_params=>{:passcode=>link_data[:query]['passcode']},:headers=>{'Accept'=>'application/xml'}})
331
+ # Note: unauthenticated API (authorization is in url params)
332
+ api_public_link=Rest.new({base_url: link_data[:base_url]})
333
+ pkgdatares=api_public_link.call({operation: 'GET',subpath: link_data[:subpath],url_params: {passcode: link_data[:query]['passcode']}, headers: {'Accept'=>'application/xml'}})
300
334
  if !pkgdatares[:http].body.start_with?('<?xml ')
301
335
  OpenApplication.instance.uri(link_url)
302
336
  raise CliError, 'no such package'
@@ -304,21 +338,30 @@ module Aspera
304
338
  package_entry=XmlSimple.xml_in(pkgdatares[:http].body, {'ForceArray' => false})
305
339
  Log.dump(:package_entry,package_entry)
306
340
  transfer_uri=self.class.get_fasp_uri_from_entry(package_entry)
307
- pkg_id_uri=[{:id=>package_entry['id'],:uri=>transfer_uri}]
341
+ pkg_id_uri=[{id: package_entry['id'],uri: transfer_uri}]
308
342
  end # public link
309
343
  Log.dump(:pkg_id_uri,pkg_id_uri)
310
344
  return Main.result_status('no package') if pkg_id_uri.empty?
311
345
  result_transfer=[]
312
346
  pkg_id_uri.each do |id_uri|
313
- transfer_spec=Fasp::Uri.new(id_uri[:uri]).transfer_spec
314
- # NOTE: only external users have token in faspe: link !
315
- if !transfer_spec.has_key?('token')
316
- sanitized=id_uri[:uri].gsub('&','&amp;')
317
- xmlpayload='<?xml version="1.0" encoding="UTF-8"?><url-list xmlns="http://schemas.asperasoft.com/xml/url-list"><url href="'+sanitized+'"/></url-list>'
318
- transfer_spec['token']=api_v3.call({:operation=>'POST',:subpath=>'issue-token?direction=down',:headers=>{'Accept'=>'text/plain','Content-Type'=>'application/vnd.aspera.url-list+xml'},:text_body_params=>xmlpayload})[:http].body
347
+ if id_uri[:uri].nil?
348
+ # skip package with no link: empty or content deleted
349
+ statuses=[:success]
350
+ else
351
+ transfer_spec=Fasp::Uri.new(id_uri[:uri]).transfer_spec
352
+ # NOTE: only external users have token in faspe: link !
353
+ if !transfer_spec.has_key?('token')
354
+ sanitized=id_uri[:uri].gsub('&','&amp;')
355
+ xmlpayload=%Q(<?xml version="1.0" encoding="UTF-8"?><url-list xmlns="http://schemas.asperasoft.com/xml/url-list"><url href="#{sanitized}"/></url-list>)
356
+ transfer_spec['token']=api_v3.call({
357
+ operation: 'POST',
358
+ subpath: 'issue-token?direction=down',
359
+ headers: {'Accept'=>'text/plain','Content-Type'=>'application/vnd.aspera.url-list+xml'},
360
+ text_body_params: xmlpayload})[:http].body
361
+ end
362
+ transfer_spec['direction']=Fasp::TransferSpec::DIRECTION_RECEIVE
363
+ statuses=transfer.start(transfer_spec,{src: :node_gen3})
319
364
  end
320
- transfer_spec['direction']='receive'
321
- statuses=self.transfer.start(transfer_spec,{:src=>:node_gen3})
322
365
  result_transfer.push({'package'=>id_uri[:id],Main::STATUS_FIELD=>statuses})
323
366
  # skip only if all sessions completed
324
367
  skip_ids_data.push(id_uri[:id]) if TransferAgent.session_status(statuses).eql?(:success)
@@ -327,21 +370,21 @@ module Aspera
327
370
  return Main.result_transfer_multiple(result_transfer)
328
371
  end
329
372
  when :source
330
- command_source=self.options.get_next_command([ :list, :id, :name ])
331
- source_list=api_v3.call({:operation=>'GET',:subpath=>'source_shares',:headers=>{'Accept'=>'application/json'}})[:data]['items']
373
+ command_source=options.get_next_command([:list, :id, :name])
374
+ source_list=api_v3.call({operation: 'GET',subpath: 'source_shares',headers: {'Accept'=>'application/json'}})[:data]['items']
332
375
  case command_source
333
376
  when :list
334
- return {:type=>:object_list,:data=>source_list}
377
+ return {type: :object_list,data: source_list}
335
378
  else # :id or :name
336
- source_match_val=self.options.get_next_argument('source id or name')
379
+ source_match_val=options.get_next_argument('source id or name')
337
380
  source_ids=source_list.select { |i| i[command_source.to_s].to_s.eql?(source_match_val) }
338
381
  if source_ids.empty?
339
- raise CliError,"No such Faspex source #{command_source.to_s}: #{source_match_val} in [#{source_list.map{|i| i[command_source.to_s]}.join(', ')}]"
382
+ raise CliError,"No such Faspex source #{command_source}: #{source_match_val} in [#{source_list.map{|i| i[command_source.to_s]}.join(', ')}]"
340
383
  end
341
384
  # get id and name
342
385
  source_name=source_ids.first['name']
343
- source_id=source_ids.first['id']
344
- source_hash=self.options.get_option(:storage,:mandatory)
386
+ #source_id=source_ids.first['id']
387
+ source_hash=options.get_option(:storage,:mandatory)
345
388
  # check value of option
346
389
  raise CliError,'storage option must be a Hash' unless source_hash.is_a?(Hash)
347
390
  source_hash.each do |name,storage|
@@ -355,56 +398,56 @@ module Aspera
355
398
  end
356
399
  source_info=source_hash[source_name]
357
400
  Log.log.debug("source_info: #{source_info}")
358
- command_node=self.options.get_next_command([ :info, :node ])
401
+ command_node=options.get_next_command([:info, :node])
359
402
  case command_node
360
403
  when :info
361
- return {:data=>source_info,:type=>:single_object}
404
+ return {data: source_info,type: :single_object}
362
405
  when :node
363
406
  node_config=ExtendedValue.instance.evaluate(source_info[KEY_NODE])
364
407
  raise CliError,"bad type for: \"#{source_info[KEY_NODE]}\"" unless node_config.is_a?(Hash)
365
408
  Log.log.debug("node=#{node_config}")
366
409
  api_node=Rest.new({
367
- :base_url => node_config['url'],
368
- :auth => {
369
- :type =>:basic,
370
- :username => node_config['username'],
371
- :password => node_config['password']}})
372
- command=self.options.get_next_command(Node::COMMON_ACTIONS)
410
+ base_url: node_config['url'],
411
+ auth: {
412
+ type: :basic,
413
+ username: node_config['username'],
414
+ password: node_config['password']}})
415
+ command=options.get_next_command(Node::COMMON_ACTIONS)
373
416
  return Node.new(@agents.merge(skip_basic_auth_options: true, node_api: api_node)).execute_action(command,source_info[KEY_PATH])
374
417
  end
375
418
  end
376
419
  when :me
377
- my_info=api_v3.call({:operation=>'GET',:subpath=>'me',:headers=>{'Accept'=>'application/json'}})[:data]
378
- return {:data=>my_info, :type=>:single_object}
420
+ my_info=api_v3.call({operation: 'GET',subpath: 'me',headers: {'Accept'=>'application/json'}})[:data]
421
+ return {data: my_info, type: :single_object}
379
422
  when :dropbox
380
- command_pkg=self.options.get_next_command([ :list ])
423
+ command_pkg=options.get_next_command([:list])
381
424
  case command_pkg
382
425
  when :list
383
- dropbox_list=api_v3.call({:operation=>'GET',:subpath=>'dropboxes',:headers=>{'Accept'=>'application/json'}})[:data]
384
- return {:type=>:object_list, :data=>dropbox_list['items'], :fields=>['name','id','description','can_read','can_write']}
426
+ dropbox_list=api_v3.call({operation: 'GET',subpath: 'dropboxes',headers: {'Accept'=>'application/json'}})[:data]
427
+ return {type: :object_list, data: dropbox_list['items'], fields: ['name','id','description','can_read','can_write']}
385
428
  end
386
429
  when :v4
387
- command=self.options.get_next_command([:package,:dropbox, :dmembership, :workgroup,:wmembership,:user,:metadata_profile])
430
+ command=options.get_next_command([:package,:dropbox, :dmembership, :workgroup,:wmembership,:user,:metadata_profile])
388
431
  case command
389
432
  when :dropbox
390
- return self.entity_action(api_v4,'admin/dropboxes',['id','e_wg_name','e_wg_desc','created_at'],:id)
433
+ return entity_action(api_v4,'admin/dropboxes',display_fields: ['id','e_wg_name','e_wg_desc','created_at'])
391
434
  when :dmembership
392
- return self.entity_action(api_v4,'dropbox_memberships',nil,:id)
435
+ return entity_action(api_v4,'dropbox_memberships')
393
436
  when :workgroup
394
- return self.entity_action(api_v4,'admin/workgroups',['id','e_wg_name','e_wg_desc','created_at'],:id)
437
+ return entity_action(api_v4,'admin/workgroups',display_fields: ['id','e_wg_name','e_wg_desc','created_at'])
395
438
  when :wmembership
396
- return self.entity_action(api_v4,'workgroup_memberships',nil,:id)
439
+ return entity_action(api_v4,'workgroup_memberships')
397
440
  when :user
398
- return self.entity_action(api_v4,'users',['id','name','first_name','last_name'],:id)
441
+ return entity_action(api_v4,'users',display_fields: ['id','name','first_name','last_name'])
399
442
  when :metadata_profile
400
- return self.entity_action(api_v4,'metadata_profiles',nil,:id)
443
+ return entity_action(api_v4,'metadata_profiles')
401
444
  when :package
402
- pkg_box_type=self.options.get_next_command([:users])
403
- pkg_box_id=self.options.get_option(:id,:mandatory)
404
- return self.entity_action(api_v4,"#{pkg_box_type}/#{pkg_box_id}/packages",nil,:id)
445
+ pkg_box_type=options.get_next_command([:users])
446
+ pkg_box_id=instance_identifier()
447
+ return entity_action(api_v4,"#{pkg_box_type}/#{pkg_box_id}/packages")
405
448
  end
406
449
  when :address_book
407
- result=api_v3.call({:operation=>'GET',:subpath=>'address-book',:headers=>{'Accept'=>'application/json'},:url_params=>{'format'=>'json','count'=>100000}})[:data]
450
+ result=api_v3.call({operation: 'GET',subpath: 'address-book',headers: {'Accept'=>'application/json'},url_params: {'format'=>'json','count'=>100_000}})[:data]
408
451
  self.format.display_status("users: #{result['itemsPerPage']}/#{result['totalResults']}, start:#{result['startIndex']}")
409
452
  users=result['entry']
410
453
  # add missing entries
@@ -420,11 +463,11 @@ module Aspera
420
463
  u['first_name'],u['last_name'] = u['displayName'].split(' ',2)
421
464
  u['x']=true
422
465
  end
423
- return {:type=>:object_list,:data=>users}
466
+ return {type: :object_list,data: users}
424
467
  when :login_methods
425
- login_meths=api_v3.call({:operation=>'GET',:subpath=>'login/new',:headers=>{'Accept'=>'application/xrds+xml'}})[:http].body
468
+ login_meths=api_v3.call({operation: 'GET',subpath: 'login/new',headers: {'Accept'=>'application/xrds+xml'}})[:http].body
426
469
  login_methods=XmlSimple.xml_in(login_meths, {'ForceArray' => false})
427
- return {:type=>:object_list, :data=>login_methods['XRD']['Service']}
470
+ return {type: :object_list, data: login_methods['XRD']['Service']}
428
471
  end # command
429
472
  end
430
473
  end