aspera-cli 4.0.0.pre1

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 (88) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +3592 -0
  3. data/bin/ascli +7 -0
  4. data/bin/asession +89 -0
  5. data/docs/Makefile +59 -0
  6. data/docs/README.erb.md +3012 -0
  7. data/docs/README.md +13 -0
  8. data/docs/diagrams.txt +49 -0
  9. data/docs/secrets.make +38 -0
  10. data/docs/test_env.conf +117 -0
  11. data/docs/transfer_spec.html +99 -0
  12. data/examples/aoc.rb +17 -0
  13. data/examples/proxy.pac +60 -0
  14. data/examples/transfer.rb +115 -0
  15. data/lib/aspera/api_detector.rb +60 -0
  16. data/lib/aspera/ascmd.rb +151 -0
  17. data/lib/aspera/ats_api.rb +43 -0
  18. data/lib/aspera/cli/basic_auth_plugin.rb +38 -0
  19. data/lib/aspera/cli/extended_value.rb +88 -0
  20. data/lib/aspera/cli/formater.rb +238 -0
  21. data/lib/aspera/cli/listener/line_dump.rb +17 -0
  22. data/lib/aspera/cli/listener/logger.rb +20 -0
  23. data/lib/aspera/cli/listener/progress.rb +52 -0
  24. data/lib/aspera/cli/listener/progress_multi.rb +91 -0
  25. data/lib/aspera/cli/main.rb +304 -0
  26. data/lib/aspera/cli/manager.rb +440 -0
  27. data/lib/aspera/cli/plugin.rb +90 -0
  28. data/lib/aspera/cli/plugins/alee.rb +24 -0
  29. data/lib/aspera/cli/plugins/ats.rb +231 -0
  30. data/lib/aspera/cli/plugins/bss.rb +71 -0
  31. data/lib/aspera/cli/plugins/config.rb +806 -0
  32. data/lib/aspera/cli/plugins/console.rb +62 -0
  33. data/lib/aspera/cli/plugins/cos.rb +106 -0
  34. data/lib/aspera/cli/plugins/faspex.rb +377 -0
  35. data/lib/aspera/cli/plugins/faspex5.rb +93 -0
  36. data/lib/aspera/cli/plugins/node.rb +438 -0
  37. data/lib/aspera/cli/plugins/oncloud.rb +937 -0
  38. data/lib/aspera/cli/plugins/orchestrator.rb +169 -0
  39. data/lib/aspera/cli/plugins/preview.rb +464 -0
  40. data/lib/aspera/cli/plugins/server.rb +216 -0
  41. data/lib/aspera/cli/plugins/shares.rb +63 -0
  42. data/lib/aspera/cli/plugins/shares2.rb +114 -0
  43. data/lib/aspera/cli/plugins/sync.rb +65 -0
  44. data/lib/aspera/cli/plugins/xnode.rb +115 -0
  45. data/lib/aspera/cli/transfer_agent.rb +251 -0
  46. data/lib/aspera/cli/version.rb +5 -0
  47. data/lib/aspera/colors.rb +39 -0
  48. data/lib/aspera/command_line_builder.rb +137 -0
  49. data/lib/aspera/fasp/aoc.rb +24 -0
  50. data/lib/aspera/fasp/connect.rb +99 -0
  51. data/lib/aspera/fasp/error.rb +21 -0
  52. data/lib/aspera/fasp/error_info.rb +60 -0
  53. data/lib/aspera/fasp/http_gw.rb +81 -0
  54. data/lib/aspera/fasp/installation.rb +240 -0
  55. data/lib/aspera/fasp/listener.rb +11 -0
  56. data/lib/aspera/fasp/local.rb +377 -0
  57. data/lib/aspera/fasp/manager.rb +69 -0
  58. data/lib/aspera/fasp/node.rb +88 -0
  59. data/lib/aspera/fasp/parameters.rb +235 -0
  60. data/lib/aspera/fasp/resume_policy.rb +76 -0
  61. data/lib/aspera/fasp/uri.rb +51 -0
  62. data/lib/aspera/faspex_gw.rb +196 -0
  63. data/lib/aspera/hash_ext.rb +28 -0
  64. data/lib/aspera/log.rb +80 -0
  65. data/lib/aspera/nagios.rb +71 -0
  66. data/lib/aspera/node.rb +14 -0
  67. data/lib/aspera/oauth.rb +319 -0
  68. data/lib/aspera/on_cloud.rb +421 -0
  69. data/lib/aspera/open_application.rb +72 -0
  70. data/lib/aspera/persistency_action_once.rb +42 -0
  71. data/lib/aspera/persistency_folder.rb +91 -0
  72. data/lib/aspera/preview/file_types.rb +300 -0
  73. data/lib/aspera/preview/generator.rb +258 -0
  74. data/lib/aspera/preview/image_error.png +0 -0
  75. data/lib/aspera/preview/options.rb +35 -0
  76. data/lib/aspera/preview/utils.rb +131 -0
  77. data/lib/aspera/preview/video_error.png +0 -0
  78. data/lib/aspera/proxy_auto_config.erb.js +287 -0
  79. data/lib/aspera/proxy_auto_config.rb +34 -0
  80. data/lib/aspera/rest.rb +296 -0
  81. data/lib/aspera/rest_call_error.rb +13 -0
  82. data/lib/aspera/rest_error_analyzer.rb +98 -0
  83. data/lib/aspera/rest_errors_aspera.rb +58 -0
  84. data/lib/aspera/ssh.rb +53 -0
  85. data/lib/aspera/sync.rb +82 -0
  86. data/lib/aspera/temp_file_manager.rb +37 -0
  87. data/lib/aspera/uri_reader.rb +25 -0
  88. metadata +288 -0
@@ -0,0 +1,62 @@
1
+ require 'aspera/cli/basic_auth_plugin'
2
+ require 'aspera/nagios'
3
+
4
+ module Aspera
5
+ module Cli
6
+ module Plugins
7
+ class Console < BasicAuthPlugin
8
+ def initialize(env)
9
+ super(env)
10
+ self.options.add_opt_date(:filter_from,"only after date")
11
+ self.options.add_opt_date(:filter_to,"only before date")
12
+ self.options.set_option(:filter_from,Manager.time_to_string(Time.now - 3*3600))
13
+ self.options.set_option(:filter_to,Manager.time_to_string(Time.now))
14
+ self.options.parse_options!
15
+ end
16
+
17
+ ACTIONS=[:transfer,:nagios_check]
18
+
19
+ def execute_action
20
+ api_console=basic_auth_api('api')
21
+ command=self.options.get_next_command(ACTIONS)
22
+ case command
23
+ when :nagios_check
24
+ nagios=Nagios.new
25
+ begin
26
+ api_console.read('ssh_keys')
27
+ nagios.add_ok('console api','accessible')
28
+ rescue => e
29
+ nagios.add_critical('console api',e.to_s)
30
+ end
31
+ return nagios.result
32
+ when :transfer
33
+ command=self.options.get_next_command([ :current, :smart ])
34
+ case command
35
+ when :smart
36
+ command=self.options.get_next_command([:list,:submit])
37
+ case command
38
+ when :list
39
+ return {:type=>:object_list,:data=>api_console.read('smart_transfers')[:data]}
40
+ when :submit
41
+ smart_id = self.options.get_next_argument("smart_id")
42
+ params = self.options.get_next_argument("transfer parameters")
43
+ return {:type=>:object_list,:data=>api_console.create('smart_transfers/'+smart_id,params)[:data]}
44
+ end
45
+ when :current
46
+ command=self.options.get_next_command([ :list ])
47
+ case command
48
+ when :list
49
+ return {:type=>:object_list,
50
+ :data=>api_console.read('transfers',{
51
+ 'from'=>self.options.get_option(:filter_from,:mandatory),
52
+ 'to'=>self.options.get_option(:filter_to,:mandatory)
53
+ })[:data],
54
+ :fields=>['id','contact','name','status']}
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end # Console
60
+ end # Plugins
61
+ end # Cli
62
+ end # Aspera
@@ -0,0 +1,106 @@
1
+ require 'aspera/cli/plugins/node'
2
+ require 'aspera/cli/plugin'
3
+ require 'xmlsimple'
4
+
5
+ module Aspera
6
+ module Cli
7
+ module Plugins
8
+ class Cos < Plugin
9
+ # IBM Cloud authentication : + token
10
+ IBM_CLOUD_OAUTH_URL='https://iam.cloud.ibm.com/identity'
11
+ private_constant :IBM_CLOUD_OAUTH_URL
12
+ def initialize(env)
13
+ super(env)
14
+ @service_creds=nil
15
+ self.options.add_opt_simple(:bucket,'IBM Cloud Object storage bucket')
16
+ self.options.add_opt_simple(:endpoint,'storage endpoint url')
17
+ self.options.add_opt_simple(:apikey,'storage API key')
18
+ self.options.add_opt_simple(:crn,'ressource instance id')
19
+ #self.options.add_opt_simple(:oauth,'Oauth url')
20
+ self.options.add_opt_simple(:service_credentials,'IBM Cloud service credentials (Hash)')
21
+ self.options.add_opt_simple(:region,'IBM Cloud Object storage region')
22
+ end
23
+
24
+ ACTIONS=[:node]
25
+
26
+ def execute_action
27
+ command=self.options.get_next_command(ACTIONS)
28
+ case command
29
+ when :node
30
+ bucket_name=self.options.get_option(:bucket,:mandatory)
31
+ # get service credentials, Hash, e.g. @json:@file:...
32
+ service_credentials=self.options.get_option(:service_credentials,:optional)
33
+ storage_endpoint=self.options.get_option(:endpoint,:optional)
34
+ raise "one of: endpoint or service_credentials is required" if service_credentials.nil? and storage_endpoint.nil?
35
+ raise "endpoint and service_credentials are mutually exclusive" unless service_credentials.nil? or storage_endpoint.nil?
36
+ if service_credentials.nil?
37
+ serv_cred_storage_api_key = self.options.get_option(:apikey,:mandatory)
38
+ instance_id = self.options.get_option(:crn,:mandatory)
39
+ else
40
+ # check necessary contents
41
+ raise CliBadArgument,'service_credentials must be a Hash' unless service_credentials.is_a?(Hash)
42
+ ['apikey','resource_instance_id','endpoints'].each do |field|
43
+ raise CliBadArgument,"service_credentials must have a field: #{field}" unless service_credentials.has_key?(field)
44
+ end
45
+ Aspera::Log.dump('service_credentials',service_credentials)
46
+ # get options
47
+ bucket_region=self.options.get_option(:region,:mandatory)
48
+ # get API key from service credentials
49
+ serv_cred_storage_api_key=service_credentials['apikey']
50
+ instance_id=service_credentials['resource_instance_id']
51
+ # read endpoints from service provided in service credentials
52
+ endpoints=Aspera::Rest.new({:base_url=>service_credentials['endpoints']}).read('')[:data]
53
+ Aspera::Log.dump('endpoints',endpoints)
54
+ storage_endpoint='https://'+endpoints['service-endpoints']['regional'][bucket_region]['public'][bucket_region]
55
+ end
56
+
57
+ s3_api=Aspera::Rest.new({
58
+ :base_url => storage_endpoint,
59
+ :not_auth_codes => ['401','403'],
60
+ :headers => {'ibm-service-instance-id' => instance_id},
61
+ :auth => {
62
+ :type => :oauth2,
63
+ :base_url => IBM_CLOUD_OAUTH_URL,
64
+ :grant => :ibm_apikey,
65
+ :api_key => serv_cred_storage_api_key
66
+ }})
67
+ # read FASP connection information for bucket
68
+ xml_result_text=s3_api.call({:operation=>'GET',:subpath=>bucket_name,:headers=>{'Accept'=>'application/xml'},:url_params=>{'faspConnectionInfo'=>nil}})[:http].body
69
+ ats_info=XmlSimple.xml_in(xml_result_text, {'ForceArray' => false})
70
+ Aspera::Log.dump('ats_info',ats_info)
71
+ # get delegated token
72
+ delegated_oauth=Oauth.new({
73
+ :type => :oauth2,
74
+ :base_url => IBM_CLOUD_OAUTH_URL,
75
+ :grant => :delegated_refresh,
76
+ :api_key => serv_cred_storage_api_key,
77
+ :token_field=> 'delegated_refresh_token'
78
+ })
79
+ # to be placed in rest call header and in transfer tags
80
+ aspera_storage_credentials={
81
+ 'type' => 'token',
82
+ 'token' => {'delegated_refresh_token'=>delegated_oauth.get_authorization().gsub(/^Bearer /,'')}
83
+ }
84
+ # transfer spec addition
85
+ add_ts={'tags'=>{'aspera'=>{'node'=>{'storage_credentials'=>aspera_storage_credentials}}}}
86
+ # set a general addon to transfer spec
87
+ # here we choose to use the add_request_param
88
+ #self.transfer.option_transfer_spec_deep_merge(add_ts)
89
+ api_node=Rest.new({
90
+ :base_url => ats_info['ATSEndpoint'],
91
+ :headers => {'X-Aspera-Storage-Credentials'=>JSON.generate(aspera_storage_credentials)},
92
+ :auth => {
93
+ :type => :basic,
94
+ :username => ats_info['AccessKey']['Id'],
95
+ :password => ats_info['AccessKey']['Secret']}})
96
+ #command=self.options.get_next_command(Node::ACTIONS)
97
+ #command=self.options.get_next_command(Node::COMMON_ACTIONS)
98
+ command=self.options.get_next_command([:upload,:download,:info,:access_key,:api_details])
99
+ node_plugin=Node.new(@agents.merge(skip_basic_auth_options: true, node_api: api_node, add_request_param: add_ts))
100
+ return node_plugin.execute_action(command)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,377 @@
1
+ require 'aspera/cli/basic_auth_plugin'
2
+ require 'aspera/cli/plugins/node'
3
+ require 'aspera/cli/plugins/config'
4
+ require 'aspera/cli/extended_value'
5
+ require 'aspera/cli/transfer_agent'
6
+ require 'aspera/persistency_action_once'
7
+ require 'aspera/open_application'
8
+ require 'aspera/fasp/uri'
9
+ require 'aspera/nagios'
10
+ require 'xmlsimple'
11
+ require 'json'
12
+
13
+ module Aspera
14
+ module Cli
15
+ module Plugins
16
+ class Faspex < BasicAuthPlugin
17
+ KEY_NODE='node'
18
+ KEY_PATH='path'
19
+ VAL_ALL='ALL'
20
+ private_constant :KEY_NODE,:KEY_PATH,:VAL_ALL
21
+ def initialize(env)
22
+ @api_v3=nil
23
+ @api_v4=nil
24
+ super(env)
25
+ self.options.add_opt_simple(:link,'public link for specific operation')
26
+ self.options.add_opt_simple(:delivery_info,'package delivery information (extended value)')
27
+ self.options.add_opt_simple(:source_name,'create package from remote source (by name)')
28
+ self.options.add_opt_simple(:storage,'Faspex local storage definition')
29
+ self.options.add_opt_list(:box,[:inbox,:sent,:archive],'package box')
30
+ self.options.set_option(:box,:inbox)
31
+ self.options.parse_options!
32
+ end
33
+
34
+ # extract elements from anonymous faspex link
35
+ def self.get_link_data(publink)
36
+ publink_uri=URI.parse(publink)
37
+ if m=publink_uri.path.match(/^(.*)\/(external.*)$/)
38
+ base=m[1]
39
+ subpath=m[2]
40
+ else
41
+ raise CliBadArgument, 'public link does not match Faspex format'
42
+ end
43
+ port_add=publink_uri.port.eql?(publink_uri.default_port)?'':":#{publink_uri.port}"
44
+ result={
45
+ :base_url => "#{publink_uri.scheme}://#{publink_uri.host}#{port_add}#{base}",
46
+ :subpath => subpath,
47
+ :query => URI::decode_www_form(publink_uri.query).inject({}){|m,v|m[v.first]=v.last;m}
48
+ }
49
+ Log.dump('publink',result)
50
+ return result
51
+ end
52
+
53
+ # get faspe: URI from entry in xml, and fix problems..
54
+ def self.get_fasp_uri_from_entry(entry)
55
+ raise CliBadArgument, 'package is empty' unless entry.has_key?('link')
56
+ result=entry['link'].select{|e| e['rel'].eql?('package')}.first['href']
57
+ # tags in the end of URL is not well % encoded... there are "=" that should be %3D
58
+ # TODO: enter ticket to Faspex ?
59
+ ###XXif m=result.match(/(=+)$/);result.gsub!(/=+$/,"#{"%3D"*m[1].length}");end
60
+ return result
61
+ end
62
+
63
+ def self.textify_package_list(table_data)
64
+ return table_data.map { |e|
65
+ e.keys.each {|k| e[k]=e[k].first if e[k].is_a?(Array) and e[k].length == 1}
66
+ e['items'] = e.has_key?('link') ? e['link'].length : 0
67
+ e
68
+ }
69
+ end
70
+
71
+ # field_sym : :id or :name
72
+ def self.get_source_id(source_list,source_name)
73
+ source_ids=source_list.select { |i| i['name'].eql?(source_name) }
74
+ if source_ids.empty?
75
+ raise CliError,"No such Faspex source #{field_sym.to_s}: #{field_value} in [#{source_list.map{|i| i[field_sym.to_s]}.join(', ')}]"
76
+ end
77
+ return source_ids.first['id']
78
+ end
79
+
80
+ def api_v3
81
+ if @api_v3.nil?
82
+ @api_v3=basic_auth_api
83
+ end
84
+ return @api_v3
85
+ end
86
+
87
+ def api_v4
88
+ if @api_v4.nil?
89
+ faspex_api_base=self.options.get_option(:url,:mandatory)
90
+ @api_v4=Rest.new({
91
+ :base_url => faspex_api_base+'/api',
92
+ :auth => {
93
+ :type => :oauth2,
94
+ :base_url => faspex_api_base+'/auth/oauth2',
95
+ :grant => :header_userpass,
96
+ :user_name => self.options.get_option(:username,:mandatory),
97
+ :user_pass => self.options.get_option(:password,:mandatory),
98
+ :scope => 'admin'
99
+ }})
100
+ end
101
+ return @api_v4
102
+ end
103
+
104
+ ACTIONS=[ :nagios_check,:package, :source, :me, :dropbox, :v4, :address_book, :login_methods ]
105
+
106
+ # we match recv command on atom feed on this field
107
+ PACKAGE_MATCH_FIELD='package_id'
108
+
109
+ def mailbox_all_entries
110
+ mailbox=self.options.get_option(:box,:mandatory).to_s
111
+ all_inbox_xml=api_v3.call({:operation=>'GET',:subpath=>"#{mailbox}.atom",:headers=>{'Accept'=>'application/xml'}})[:http].body
112
+ all_inbox_data=XmlSimple.xml_in(all_inbox_xml, {'ForceArray' => true})
113
+ Log.dump(:all_inbox_data,all_inbox_data)
114
+ result=all_inbox_data.has_key?('entry') ? all_inbox_data['entry'] : []
115
+ result.each do |e|
116
+ e[PACKAGE_MATCH_FIELD]=e['to'].first['recipient_delivery_id'].first
117
+ end
118
+ # remove dropbox packages
119
+ result.select!{|p|p['metadata'].first['field'].select{|j|j['name'].eql?('_dropbox_name')}.empty? rescue false}
120
+ return result
121
+ end
122
+
123
+ # retrieve transfer spec from pub link for send package
124
+ def send_publink_to_ts(public_link_url,package_create_params)
125
+ delivery_info=package_create_params['delivery']
126
+ # pub link user
127
+ link_data=self.class.get_link_data(public_link_url)
128
+ if !['external/submissions/new','external/dropbox_submissions/new'].include?(link_data[:subpath])
129
+ raise CliBadArgument,"pub link is #{link_data[:subpath]}, expecting external/submissions/new"
130
+ end
131
+ create_path=link_data[:subpath].split('/')[0..-2].join('/')
132
+ package_create_params.merge!({:passcode=>link_data[:query]['passcode']})
133
+ delivery_info.merge!({
134
+ :transfer_type => 'connect',
135
+ :source_paths_list => self.transfer.ts_source_paths.map{|i|i['source']}.join("\r\n")})
136
+ api_public_link=Rest.new({:base_url=>link_data[:base_url]})
137
+ # Hum, as this does not always work (only user, but not dropbox), we get the javascript and need hack
138
+ #pkg_created=api_public_link.create(create_path,package_create_params)[:data]
139
+ # so extract data from javascript
140
+ pkgdatares=api_public_link.call({:operation=>'POST',:subpath=>create_path,:json_params=>package_create_params,:headers=>{'Accept'=>'text/javascript'}})[:http].body
141
+ # get args of function call
142
+ pkgdatares.gsub!("\n",'') # one line
143
+ pkgdatares.gsub!(/^[^"]+\("\{/,'{') # delete header
144
+ pkgdatares.gsub!(/"\);[^"]+$/,'"') # delete trailer
145
+ pkgdatares.gsub!(/\}", *"/,'},"') # between two args
146
+ pkgdatares.gsub!('\\"','"') # remove protecting quote
147
+ begin
148
+ pkgdatares=JSON.parse("[#{pkgdatares}]")
149
+ rescue JSON::ParserError # => e
150
+ raise "Link not valid"
151
+ end
152
+ return pkgdatares.first
153
+ end
154
+
155
+ def execute_action
156
+ command=self.options.get_next_command(ACTIONS)
157
+ case command
158
+ when :nagios_check
159
+ nagios=Nagios.new
160
+ begin
161
+ api_v3.read('me')
162
+ nagios.add_ok('faspex api','accessible')
163
+ rescue => e
164
+ nagios.add_critical('faspex api',e.to_s)
165
+ end
166
+ return nagios.result
167
+ when :package
168
+ command_pkg=self.options.get_next_command([ :send, :recv, :list ])
169
+ case command_pkg
170
+ when :list
171
+ 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)} }
172
+ when :send
173
+ delivery_info=self.options.get_option(:delivery_info,:mandatory)
174
+ raise CliBadArgument,'delivery_info must be hash, refer to doc' unless delivery_info.is_a?(Hash)
175
+ # actual parameter to faspex API
176
+ package_create_params={'delivery'=>delivery_info}
177
+ public_link_url=self.options.get_option(:link,:optional)
178
+ if public_link_url.nil?
179
+ # authenticated user
180
+ delivery_info['sources']||=[{'paths'=>[]}]
181
+ first_source=delivery_info['sources'].first
182
+ first_source['paths'].push(*self.transfer.ts_source_paths.map{|i|i['source']})
183
+ source_name=self.options.get_option(:source_name,:optional)
184
+ if !source_name.nil?
185
+ source_list=api_v3.call({:operation=>'GET',:subpath=>'source_shares',:headers=>{'Accept'=>'application/json'}})[:data]['items']
186
+ source_id=self.class.get_source_id(source_list,source_name)
187
+ first_source['id']=source_id
188
+ end
189
+ pkg_created=api_v3.call({:operation=>'POST',:subpath=>'send',:json_params=>package_create_params,:headers=>{'Accept'=>'application/json'}})[:data]
190
+ if !source_name.nil?
191
+ # no transfer spec if remote source
192
+ return {:data=>[pkg_created['links']['status']],:type=>:value_list,:name=>'link'}
193
+ end
194
+ raise CliBadArgument,'expecting one session exactly' if pkg_created['xfer_sessions'].length != 1
195
+ transfer_spec=pkg_created['xfer_sessions'].first
196
+ # use source from cmd line, this one only contains destination (already in dest root)
197
+ transfer_spec.delete('paths')
198
+ else # publink
199
+ transfer_spec=send_publink_to_ts(public_link_url,package_create_params)
200
+ end
201
+ #Log.dump('transfer_spec',transfer_spec)
202
+ return Main.result_transfer(self.transfer.start(transfer_spec,{:src=>:node_gen3}))
203
+ when :recv
204
+ public_link_url=self.options.get_option(:link,:optional)
205
+ if !public_link_url.nil?
206
+ link_data=self.class.get_link_data(public_link_url)
207
+ if !link_data[:subpath].match(%r{external_deliveries/})
208
+ raise CliBadArgument,"pub link is #{link_data[:subpath]}, expecting external_deliveries/"
209
+ end
210
+ # Note: unauthenticated API
211
+ api_public_link=Rest.new({:base_url=>link_data[:base_url]})
212
+ pkgdatares=api_public_link.call({:operation=>'GET',:subpath=>link_data[:subpath],:url_params=>{:passcode=>link_data[:query]['passcode']},:headers=>{'Accept'=>'application/xml'}})
213
+ if !pkgdatares[:http].body.start_with?('<?xml ')
214
+ OpenApplication.instance.uri(public_link_url)
215
+ raise CliError, 'no such package'
216
+ end
217
+ package_entry=XmlSimple.xml_in(pkgdatares[:http].body, {'ForceArray' => false})
218
+ transfer_uri=self.class.get_fasp_uri_from_entry(package_entry)
219
+ transfer_spec=Fasp::Uri.new(transfer_uri).transfer_spec
220
+ transfer_spec['direction']='receive'
221
+ return Main.result_transfer(self.transfer.start(transfer_spec,{:src=>:node_gen3}))
222
+ end # public link
223
+ # get command line parameters
224
+ delivid=self.options.get_option(:id,:mandatory)
225
+ # list of faspex ID/URI to download
226
+ pkg_id_uri=nil
227
+ skip_ids_data=[]
228
+ skip_ids_persistency=nil
229
+ if self.options.get_option(:once_only,:mandatory)
230
+ skip_ids_persistency=PersistencyActionOnce.new(
231
+ manager: @agents[:persistency],
232
+ data: skip_ids_data,
233
+ ids: ['faspex_recv',self.options.get_option(:url,:mandatory),self.options.get_option(:username,:mandatory),self.options.get_option(:box,:mandatory).to_s])
234
+ end
235
+ if delivid.eql?(VAL_ALL)
236
+ pkg_id_uri=mailbox_all_entries.map{|i|{:id=>i[PACKAGE_MATCH_FIELD],:uri=>self.class.get_fasp_uri_from_entry(i)}}
237
+ # TODO : remove ids from skip not present in inbox
238
+ # skip_ids_data.select!{|id|pkg_id_uri.select{|p|p[:id].eql?(id)}}
239
+ pkg_id_uri.select!{|i|!skip_ids_data.include?(i[:id])}
240
+ else
241
+ # TODO: delivery id is the right one if package was receive by group
242
+ endpoint=case self.options.get_option(:box,:mandatory)
243
+ when :inbox,:archive;'received'
244
+ when :sent;'sent'
245
+ end
246
+ entry_xml=api_v3.call({:operation=>'GET',:subpath=>"#{endpoint}/#{delivid}",:headers=>{'Accept'=>'application/xml'}})[:http].body
247
+ package_entry=XmlSimple.xml_in(entry_xml, {'ForceArray' => true})
248
+ pkg_id_uri=[{:id=>delivid,:uri=>self.class.get_fasp_uri_from_entry(package_entry)}]
249
+ end
250
+ Log.dump(:pkg_id_uri,pkg_id_uri)
251
+ return Main.result_status('no package') if pkg_id_uri.empty?
252
+ result_transfer=[]
253
+ pkg_id_uri.each do |id_uri|
254
+ transfer_spec=Fasp::Uri.new(id_uri[:uri]).transfer_spec
255
+ # NOTE: only external users have token in faspe: link !
256
+ if !transfer_spec.has_key?('token')
257
+ sanitized=id_uri[:uri].gsub('&','&amp;')
258
+ # TODO: file jira
259
+ #XXsanitized.gsub!(/%3D%3D$/,'==')
260
+ #XXsanitized.gsub!(/%3D$/,'=')
261
+ xmlpayload='<?xml version="1.0" encoding="UTF-8"?><url-list xmlns="http://schemas.asperasoft.com/xml/url-list"><url href="'+sanitized+'"/></url-list>'
262
+ 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
263
+ end
264
+ transfer_spec['direction']='receive'
265
+ statuses=self.transfer.start(transfer_spec,{:src=>:node_gen3})
266
+ result_transfer.push({'package'=>id_uri[:id],'status'=>statuses.map{|i|i.to_s}.join(',')})
267
+ # skip only if all sessions completed
268
+ skip_ids_data.push(id_uri[:id]) if TransferAgent.session_status(statuses).eql?(:success)
269
+ end
270
+ skip_ids_persistency.save unless skip_ids_persistency.nil?
271
+ return {:type=>:object_list,:data=>result_transfer}
272
+ end
273
+ when :source
274
+ command_source=self.options.get_next_command([ :list, :id, :name ])
275
+ source_list=api_v3.call({:operation=>'GET',:subpath=>'source_shares',:headers=>{'Accept'=>'application/json'}})[:data]['items']
276
+ case command_source
277
+ when :list
278
+ return {:type=>:object_list,:data=>source_list}
279
+ else # :id or :name
280
+ source_match_val=self.options.get_next_argument('source id or name')
281
+ source_ids=source_list.select { |i| i[command_source.to_s].to_s.eql?(source_match_val) }
282
+ if source_ids.empty?
283
+ raise CliError,"No such Faspex source #{command_source.to_s}: #{source_match_val} in [#{source_list.map{|i| i[command_source.to_s]}.join(', ')}]"
284
+ end
285
+ # get id and name
286
+ source_name=source_ids.first['name']
287
+ source_id=source_ids.first['id']
288
+ source_hash=self.options.get_option(:storage,:mandatory)
289
+ # check value of option
290
+ raise CliError,'storage option must be a Hash' unless source_hash.is_a?(Hash)
291
+ source_hash.each do |name,storage|
292
+ raise CliError,"storage '#{name}' must be a Hash" unless storage.is_a?(Hash)
293
+ [KEY_NODE,KEY_PATH].each do |key|
294
+ raise CliError,"storage '#{name}' must have a '#{key}'" unless storage.has_key?(key)
295
+ end
296
+ end
297
+ if !source_hash.has_key?(source_name)
298
+ raise CliError,"No such storage in config file: \"#{source_name}\" in [#{source_hash.keys.join(', ')}]"
299
+ end
300
+ source_info=source_hash[source_name]
301
+ Log.log.debug("source_info: #{source_info}")
302
+ command_node=self.options.get_next_command([ :info, :node ])
303
+ case command_node
304
+ when :info
305
+ return {:data=>source_info,:type=>:single_object}
306
+ when :node
307
+ node_config=ExtendedValue.instance.evaluate(source_info[KEY_NODE])
308
+ raise CliError,"bad type for: \"#{source_info[KEY_NODE]}\"" unless node_config.is_a?(Hash)
309
+ Log.log.debug("node=#{node_config}")
310
+ api_node=Rest.new({
311
+ :base_url => node_config['url'],
312
+ :auth => {
313
+ :type =>:basic,
314
+ :username => node_config['username'],
315
+ :password => node_config['password']}})
316
+ command=self.options.get_next_command(Node::COMMON_ACTIONS)
317
+ return Node.new(@agents.merge(skip_basic_auth_options: true, node_api: api_node)).execute_action(command,source_info[KEY_PATH])
318
+ end
319
+ end
320
+ when :me
321
+ my_info=api_v3.call({:operation=>'GET',:subpath=>'me',:headers=>{'Accept'=>'application/json'}})[:data]
322
+ return {:data=>my_info, :type=>:single_object}
323
+ when :dropbox
324
+ command_pkg=self.options.get_next_command([ :list ])
325
+ case command_pkg
326
+ when :list
327
+ dropbox_list=api_v3.call({:operation=>'GET',:subpath=>'dropboxes',:headers=>{'Accept'=>'application/json'}})[:data]
328
+ return {:type=>:object_list, :data=>dropbox_list['items'], :fields=>['name','id','description','can_read','can_write']}
329
+ end
330
+ when :v4
331
+ command=self.options.get_next_command([:package,:dropbox, :dmembership, :workgroup,:wmembership,:user,:metadata_profile])
332
+ case command
333
+ when :dropbox
334
+ return self.entity_action(api_v4,'admin/dropboxes',['id','e_wg_name','e_wg_desc','created_at'],:id)
335
+ when :dmembership
336
+ return self.entity_action(api_v4,'dropbox_memberships',nil,:id)
337
+ when :workgroup
338
+ return self.entity_action(api_v4,'admin/workgroups',['id','e_wg_name','e_wg_desc','created_at'],:id)
339
+ when :wmembership
340
+ return self.entity_action(api_v4,'workgroup_memberships',nil,:id)
341
+ when :user
342
+ return self.entity_action(api_v4,'users',['id','name','first_name','last_name'],:id)
343
+ when :metadata_profile
344
+ return self.entity_action(api_v4,'metadata_profiles',nil,:id)
345
+ when :package
346
+ pkg_box_type=self.options.get_next_command([:users])
347
+ pkg_box_id=self.options.get_option(:id,:mandatory)
348
+ return self.entity_action(api_v4,"#{pkg_box_type}/#{pkg_box_id}/packages",nil,:id)
349
+ end
350
+ when :address_book
351
+ result=api_v3.call({:operation=>'GET',:subpath=>'address-book',:headers=>{'Accept'=>'application/json'},:url_params=>{'format'=>'json','count'=>100000}})[:data]
352
+ self.format.display_status("users: #{result['itemsPerPage']}/#{result['totalResults']}, start:#{result['startIndex']}")
353
+ users=result['entry']
354
+ # add missing entries
355
+ users.each do |u|
356
+ unless u['emails'].nil?
357
+ email=u['emails'].find{|i|i['primary'].eql?('true')}
358
+ u['email'] = email['value'] unless email.nil?
359
+ end
360
+ if u['email'].nil?
361
+ Log.log.warn("Skip user without email: #{u}")
362
+ next
363
+ end
364
+ u['first_name'],u['last_name'] = u['displayName'].split(' ',2)
365
+ u['x']=true
366
+ end
367
+ return {:type=>:object_list,:data=>users}
368
+ when :login_methods
369
+ login_meths=api_v3.call({:operation=>'GET',:subpath=>'login/new',:headers=>{'Accept'=>'application/xrds+xml'}})[:http].body
370
+ login_methods=XmlSimple.xml_in(login_meths, {'ForceArray' => false})
371
+ return {:type=>:object_list, :data=>login_methods['XRD']['Service']}
372
+ end # command
373
+ end
374
+ end
375
+ end
376
+ end # Cli
377
+ end # Aspera