aspera-cli 4.4.0 → 4.7.0

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