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