aspera-cli 4.4.0 → 4.5.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1042 -787
  3. data/bin/ascli +1 -1
  4. data/bin/asession +3 -5
  5. data/docs/Makefile +4 -7
  6. data/docs/README.erb.md +988 -740
  7. data/examples/faspex4.rb +4 -6
  8. data/examples/transfer.rb +2 -2
  9. data/lib/aspera/aoc.rb +139 -118
  10. data/lib/aspera/cli/listener/progress_multi.rb +5 -5
  11. data/lib/aspera/cli/main.rb +64 -34
  12. data/lib/aspera/cli/manager.rb +19 -20
  13. data/lib/aspera/cli/plugin.rb +9 -1
  14. data/lib/aspera/cli/plugins/aoc.rb +156 -143
  15. data/lib/aspera/cli/plugins/ats.rb +11 -10
  16. data/lib/aspera/cli/plugins/bss.rb +2 -2
  17. data/lib/aspera/cli/plugins/config.rb +236 -112
  18. data/lib/aspera/cli/plugins/faspex.rb +29 -7
  19. data/lib/aspera/cli/plugins/faspex5.rb +21 -8
  20. data/lib/aspera/cli/plugins/node.rb +21 -9
  21. data/lib/aspera/cli/plugins/orchestrator.rb +5 -3
  22. data/lib/aspera/cli/plugins/preview.rb +2 -2
  23. data/lib/aspera/cli/plugins/server.rb +3 -3
  24. data/lib/aspera/cli/plugins/shares.rb +17 -0
  25. data/lib/aspera/cli/transfer_agent.rb +47 -85
  26. data/lib/aspera/cli/version.rb +1 -1
  27. data/lib/aspera/environment.rb +4 -4
  28. data/lib/aspera/fasp/{manager.rb → agent_base.rb} +7 -6
  29. data/lib/aspera/fasp/{connect.rb → agent_connect.rb} +46 -39
  30. data/lib/aspera/fasp/{local.rb → agent_direct.rb} +14 -17
  31. data/lib/aspera/fasp/{http_gw.rb → agent_httpgw.rb} +4 -4
  32. data/lib/aspera/fasp/{node.rb → agent_node.rb} +25 -8
  33. data/lib/aspera/fasp/agent_trsdk.rb +106 -0
  34. data/lib/aspera/fasp/default.rb +17 -0
  35. data/lib/aspera/fasp/installation.rb +64 -48
  36. data/lib/aspera/fasp/parameters.rb +7 -3
  37. data/lib/aspera/faspex_gw.rb +6 -6
  38. data/lib/aspera/keychain/encrypted_hash.rb +120 -0
  39. data/lib/aspera/keychain/macos_security.rb +94 -0
  40. data/lib/aspera/log.rb +45 -32
  41. data/lib/aspera/node.rb +3 -6
  42. data/lib/aspera/rest.rb +65 -49
  43. metadata +68 -27
  44. data/lib/aspera/api_detector.rb +0 -60
  45. data/lib/aspera/secrets.rb +0 -20
@@ -2,20 +2,32 @@ require 'aspera/cli/plugins/node'
2
2
  require 'aspera/cli/plugins/ats'
3
3
  require 'aspera/cli/basic_auth_plugin'
4
4
  require 'aspera/cli/transfer_agent'
5
+ require 'aspera/fasp/agent_node'
5
6
  require 'aspera/aoc'
6
7
  require 'aspera/node'
7
8
  require 'aspera/persistency_action_once'
8
9
  require 'aspera/id_generator'
9
10
  require 'securerandom'
10
- require 'resolv'
11
11
  require 'date'
12
12
 
13
13
  module Aspera
14
14
  module Cli
15
15
  module Plugins
16
16
  class Aoc < BasicAuthPlugin
17
+ class << self
18
+ def detect(base_url)
19
+ api=Rest.new({base_url: base_url})
20
+ result=api.call({operation: 'GET',subpath: '',headers: {'Accept'=>'text/html'}})
21
+ if result[:http].body.include?('content="AoC"')
22
+ return {product: :aoc,version: 'unknown'}
23
+ end
24
+ return nil
25
+ end
26
+ end
17
27
  # special value for package id
18
28
  VAL_ALL='ALL'
29
+ ID_AK_ADMIN='ASPERA_ACCESS_KEY_ADMIN'
30
+
19
31
  def initialize(env)
20
32
  super(env)
21
33
  @default_workspace_id=nil
@@ -25,7 +37,6 @@ module Aspera
25
37
  @home_node_file=nil
26
38
  @api_aoc=nil
27
39
  @url_token_data=nil
28
- @user_info=nil
29
40
  @api_aoc=nil
30
41
  self.options.add_opt_list(:auth,Oauth.auth_types,'OAuth type of authentication')
31
42
  self.options.add_opt_list(:operation,[:push,:pull],'client operation for transfers')
@@ -58,32 +69,19 @@ module Aspera
58
69
  if @api_aoc.nil?
59
70
  @api_aoc=AoC.new(aoc_params(AoC::API_V1))
60
71
  # add keychain for access key secrets
61
- @api_aoc.key_chain=@agents[:secret]
72
+ @api_aoc.key_chain=@agents[:config]
62
73
  end
63
74
  return @api_aoc
64
75
  end
65
76
 
66
- # cached user information
67
- def c_user_info
68
- if @user_info.nil?
69
- # get our user's default information
70
- # self?embed[]=default_workspace&embed[]=organization
71
- @user_info=@api_aoc.read('self')[:data] rescue {
72
- 'name' => 'unknown',
73
- 'email' => 'unknown',
74
- }
75
- end
76
- return @user_info
77
- end
78
-
79
77
  # starts transfer using transfer agent
80
78
  def transfer_start(app,direction,node_file,ts_add)
81
79
  ts_add.deep_merge!(AoC.analytics_ts(app,direction,@workspace_id,@workspace_name))
82
- ts_add.deep_merge!(AoC.console_ts(app,c_user_info['name'],c_user_info['email']))
80
+ ts_add.deep_merge!(@api_aoc.console_ts(app))
83
81
  return self.transfer.start(*@api_aoc.tr_spec(app,direction,node_file,ts_add))
84
82
  end
85
83
 
86
- NODE4_COMMANDS=[ :browse, :find, :mkdir, :rename, :delete, :upload, :download, :transfer, :http_node_download, :v3, :file, :bearer_token_node, :node_info ]
84
+ NODE4_COMMANDS=[ :browse, :find, :mkdir, :rename, :delete, :upload, :download, :transfer, :http_node_download, :v3, :file, :bearer_token_node, :node_info ].freeze
87
85
 
88
86
  def execute_node_gen4_command(command_repo,top_node_file)
89
87
  case command_repo
@@ -96,7 +94,7 @@ module Aspera
96
94
  thepath=self.options.get_next_argument('path')
97
95
  node_file = @api_aoc.resolve_node_file(top_node_file,thepath)
98
96
  node_api=@api_aoc.get_node_api(node_file[:node_info],scope: AoC::SCOPE_NODE_USER, use_secret: false)
99
- return {:type=>:single_object,:data=>{
97
+ return {type: :single_object,data: {
100
98
  url: node_file[:node_info]['url'],
101
99
  username: node_file[:node_info]['access_key'],
102
100
  password: node_api.oauth_token,
@@ -114,26 +112,26 @@ module Aspera
114
112
  else
115
113
  items=[file_info]
116
114
  end
117
- return {:type=>:object_list,:data=>items,:fields=>['name','type','recursive_size','size','modified_time','access_level']}
115
+ return {type: :object_list,data: items,fields: ['name','type','recursive_size','size','modified_time','access_level']}
118
116
  when :find
119
117
  thepath=self.options.get_next_argument('path')
120
118
  node_file=@api_aoc.resolve_node_file(top_node_file,thepath)
121
119
  test_block=Aspera::Node.file_matcher(self.options.get_option(:value,:optional))
122
- return {:type=>:object_list,:data=>@api_aoc.find_files(node_file,test_block),:fields=>['path']}
120
+ return {type: :object_list,data: @api_aoc.find_files(node_file,test_block),fields: ['path']}
123
121
  when :mkdir
124
122
  thepath=self.options.get_next_argument('path')
125
123
  containing_folder_path = thepath.split(AoC::PATH_SEPARATOR)
126
124
  new_folder=containing_folder_path.pop
127
125
  node_file = @api_aoc.resolve_node_file(top_node_file,containing_folder_path.join(AoC::PATH_SEPARATOR))
128
126
  node_api=@api_aoc.get_node_api(node_file[:node_info],scope: AoC::SCOPE_NODE_USER)
129
- result=node_api.create("files/#{node_file[:file_id]}/files",{:name=>new_folder,:type=>:folder})[:data]
127
+ result=node_api.create("files/#{node_file[:file_id]}/files",{name: new_folder,type: :folder})[:data]
130
128
  return Main.result_status("created: #{result['name']} (id=#{result['id']})")
131
129
  when :rename
132
130
  thepath=self.options.get_next_argument('source path')
133
131
  newname=self.options.get_next_argument('new name')
134
132
  node_file = @api_aoc.resolve_node_file(top_node_file,thepath)
135
133
  node_api=@api_aoc.get_node_api(node_file[:node_info],scope: AoC::SCOPE_NODE_USER)
136
- result=node_api.update("files/#{node_file[:file_id]}",{:name=>newname})[:data]
134
+ result=node_api.update("files/#{node_file[:file_id]}",{name: newname})[:data]
137
135
  return Main.result_status("renamed #{thepath} to #{newname}")
138
136
  when :delete
139
137
  thepath=self.options.get_next_argument('path')
@@ -163,7 +161,13 @@ module Aspera
163
161
  client_node_file = @api_aoc.resolve_node_file(client_home_node_file,client_folder)
164
162
  server_node_file = @api_aoc.resolve_node_file(server_home_node_file,server_folder)
165
163
  # force node as transfer agent
166
- @agents[:transfer].set_agent_instance(Fasp::Node.new(@api_aoc.get_node_api(client_node_file[:node_info],scope: AoC::SCOPE_NODE_USER)))
164
+ client_node_api=@api_aoc.get_node_api(client_node_file[:node_info],scope: AoC::SCOPE_NODE_USER, use_secret: false)
165
+ @agents[:transfer].set_agent_instance(Fasp::AgentNode.new({
166
+ url: client_node_api.params[:base_url],
167
+ username: client_node_file[:node_info]['access_key'],
168
+ password: client_node_api.oauth_token,
169
+ root_id: client_node_file[:file_id]
170
+ }))
167
171
  # additional node to node TS info
168
172
  add_ts={
169
173
  'remote_access_key' => server_node_file[:node_info]['access_key'],
@@ -202,7 +206,7 @@ module Aspera
202
206
  file_name = source_paths.first['source']
203
207
  node_file = @api_aoc.resolve_node_file(top_node_file,File.join(source_folder,file_name))
204
208
  node_api=@api_aoc.get_node_api(node_file[:node_info],scope: AoC::SCOPE_NODE_USER)
205
- node_api.call({:operation=>'GET',:subpath=>"files/#{node_file[:file_id]}/content",:save_to_file=>File.join(self.transfer.destination_folder('receive'),file_name)})
209
+ node_api.call({operation: 'GET',subpath: "files/#{node_file[:file_id]}/content",save_to_file: File.join(self.transfer.destination_folder('receive'),file_name)})
206
210
  return Main.result_status("downloaded: #{file_name}")
207
211
  when :v3
208
212
  # Note: other common actions are unauthorized with user scope
@@ -211,22 +215,22 @@ module Aspera
211
215
  node_api=@api_aoc.get_node_api(top_node_file[:node_info],scope: AoC::SCOPE_NODE_USER)
212
216
  return Node.new(@agents.merge(skip_basic_auth_options: true, node_api: node_api)).execute_action(command_legacy)
213
217
  when :file
218
+ command_node_file=self.options.get_next_command([:show,:permission,:modify])
214
219
  file_path=self.options.get_option(:path,:optional)
215
220
  node_file = if !file_path.nil?
216
221
  @api_aoc.resolve_node_file(top_node_file,file_path) # TODO: allow follow link ?
217
222
  else
218
- {node_info: top_node_file[:node_info],file_id: self.options.get_option(:id,:mandatory)}
223
+ {node_info: top_node_file[:node_info],file_id: self.instance_identifier()}
219
224
  end
220
225
  node_api=@api_aoc.get_node_api(node_file[:node_info],scope: AoC::SCOPE_NODE_USER)
221
- command_node_file=self.options.get_next_command([:show,:permission,:modify])
222
226
  case command_node_file
223
227
  when :show
224
228
  items=node_api.read("files/#{node_file[:file_id]}")[:data]
225
- return {:type=>:single_object,:data=>items}
229
+ return {type: :single_object,data: items}
226
230
  when :modify
227
231
  update_param=self.options.get_next_argument('update data (Hash)')
228
232
  res=node_api.update("files/#{node_file[:file_id]}",update_param)[:data]
229
- return {:type=>:single_object,:data=>res}
233
+ return {type: :single_object,data: res}
230
234
  when :permission
231
235
  command_perm=self.options.get_next_command([:list,:create])
232
236
  case command_perm
@@ -240,11 +244,11 @@ module Aspera
240
244
  list_options['inherited']||=false
241
245
  end
242
246
  items=node_api.read('permissions',list_options)[:data]
243
- return {:type=>:object_list,:data=>items}
247
+ return {type: :object_list,data: items}
244
248
  when :create
245
249
  #create_param=self.options.get_next_argument('creation data (Hash)')
246
250
  set_workspace_info
247
- access_id="ASPERA_ACCESS_KEY_ADMIN_WS_#{@workspace_id}"
251
+ access_id="#{ID_AK_ADMIN}_WS_#{@workspace_id}"
248
252
  node_file[:node_info]
249
253
  params={
250
254
  'file_id' =>node_file[:file_id], # mandatory
@@ -254,23 +258,21 @@ module Aspera
254
258
  'tags' =>{'aspera'=>{'files'=>{'workspace'=>{
255
259
  'id' =>@workspace_id,
256
260
  'workspace_name' =>@workspace_name,
257
- 'user_name' =>c_user_info['name'],
258
- 'shared_by_user_id'=>c_user_info['id'],
259
- 'shared_by_name' =>c_user_info['name'],
260
- 'shared_by_email' =>c_user_info['email'],
261
+ 'user_name' =>@api_aoc.user_info['name'],
262
+ 'shared_by_user_id'=>@api_aoc.user_info['id'],
263
+ 'shared_by_name' =>@api_aoc.user_info['name'],
264
+ 'shared_by_email' =>@api_aoc.user_info['email'],
261
265
  'shared_with_name' =>access_id,
262
266
  'access_key' =>node_file[:node_info]['access_key'],
263
267
  'node' =>node_file[:node_info]['name']}}}}}
264
268
  item=node_api.create('permissions',params)[:data]
265
- return {:type=>:single_object,:data=>item}
269
+ return {type: :single_object,data: item}
266
270
  else raise "internal error:shall not reach here (#{command_perm})"
267
271
  end
268
272
  raise 'internal error:shall not reach here'
269
273
  else raise "internal error:shall not reach here (#{command_node_file})"
270
274
  end
271
275
  raise 'internal error:shall not reach here'
272
- when :permissions
273
-
274
276
  end # command_repo
275
277
  raise 'ERR'
276
278
  end # execute_node_gen4_command
@@ -297,8 +299,8 @@ module Aspera
297
299
  @default_workspace_id=@url_token_data['data']['workspace_id']
298
300
  @persist_ids=[] # TODO : @url_token_data['id'] ?
299
301
  else
300
- @default_workspace_id=c_user_info['default_workspace_id']
301
- @persist_ids=[c_user_info['id']]
302
+ @default_workspace_id=@api_aoc.user_info['default_workspace_id']
303
+ @persist_ids=[@api_aoc.user_info['id']]
302
304
  end
303
305
 
304
306
  ws_name=self.options.get_option(:workspace,:optional)
@@ -367,7 +369,27 @@ module Aspera
367
369
  end
368
370
  result_list.push(one)
369
371
  end
370
- return {:type=>:object_list,:data=>result_list,:fields=>[id_result,'status']}
372
+ return {type: :object_list,data: result_list,fields: [id_result,'status']}
373
+ end
374
+
375
+ NOT_FOUND="not found"
376
+
377
+ def lookup_single(entity_type,entity_name,options={})
378
+ # returns entities whose name contains value (case insensitive)
379
+ matching_items=@api_aoc.read(entity_type,options.merge({'q'=>entity_name}))[:data]
380
+ case matching_items.length
381
+ when 1; return matching_items.first
382
+ when 0; raise RuntimeError,NOT_FOUND
383
+ else
384
+ # multiple case insensitive partial matches, try case insensitive full match
385
+ # (anyway AoC does not allow creation of 2 entities with same case insensitive name)
386
+ icase_matches=matching_items.select{|i|i['name'].casecmp?(entity_name)}
387
+ case icase_matches.length
388
+ when 1; return icase_matches.first
389
+ when 0; raise CliBadArgument,"#{entity_type}: multiple case insensitive partial match for: \"#{entity_name}\": #{matching_items.map{|i|i['name']}} but no case insensitive full match. Please be more specific or give exact name."
390
+ else raise "Two entities cannot have the same case insensitive name: #{icase_matches.map{|i|i['name']}}"
391
+ end
392
+ end
371
393
  end
372
394
 
373
395
  # package creation params can give just email, and full hash is created
@@ -375,36 +397,37 @@ module Aspera
375
397
  return unless package_creation.has_key?(recipient_list_field)
376
398
  raise CliBadArgument,"#{recipient_list_field} must be an Array" unless package_creation[recipient_list_field].is_a?(Array)
377
399
  new_user_option=self.options.get_option(:new_user_option,:mandatory)
400
+ # list with resolved elements
378
401
  resolved_list=[]
379
- package_creation[recipient_list_field].each do |recipient_email_or_info|
380
- case recipient_email_or_info
381
- when Hash
382
- raise 'recipient element hash shall have field id and type' unless recipient_email_or_info.has_key?('id') and recipient_email_or_info.has_key?('type')
383
- # already provided all information ?
384
- resolved_list.push(recipient_email_or_info)
385
- when String
386
- if recipient_email_or_info.include?('@')
387
- # or need to resolve email
388
- item_lookup=@api_aoc.read('contacts',{'current_workspace_id'=>@workspace_id,'q'=>recipient_email_or_info})[:data]
389
- case item_lookup.length
390
- when 1; recipient_user_id=item_lookup.first
391
- when 0; recipient_user_id=@api_aoc.create('contacts',{'current_workspace_id'=>@workspace_id,'email'=>recipient_email_or_info}.merge(new_user_option))[:data]
392
- else raise CliBadArgument,"multiple match for: #{recipient_email_or_info}"
402
+ package_creation[recipient_list_field].each do |short_recipient_info|
403
+ case short_recipient_info
404
+ when Hash # native api information, check keys
405
+ raise "#{recipient_list_field} element shall have fields: id and type" unless short_recipient_info.keys.sort.eql?(['id','type'])
406
+ when String # need to resolve name to type/id
407
+ # email: user, else dropbox
408
+ entity_type=short_recipient_info.include?('@') ? 'contacts' : 'dropboxes'
409
+ begin
410
+ full_recipient_info=lookup_single(entity_type,short_recipient_info,{'current_workspace_id'=>@workspace_id})
411
+ rescue RuntimeError => e
412
+ raise e unless e.message.eql?(NOT_FOUND)
413
+ if entity_type.eql?('contacts')
414
+ full_recipient_info=@api_aoc.create('contacts',{'current_workspace_id'=>@workspace_id,'email'=>short_recipient_info}.merge(new_user_option))[:data]
415
+ else
416
+ raise "no such shared inbox in workspace #{@workspace_name}"
393
417
  end
394
- resolved_list.push({'id'=>recipient_user_id['source_id'],'type'=>recipient_user_id['source_type']})
418
+ end
419
+ if entity_type.eql?('dropboxes')
420
+ short_recipient_info={'id'=>full_recipient_info['id'],'type'=>'dropbox'}
395
421
  else
396
- item_lookup=@api_aoc.read('dropboxes',{'current_workspace_id'=>@workspace_id,'q'=>recipient_email_or_info})[:data]
397
- case item_lookup.length
398
- when 1; recipient_user_id=item_lookup.first
399
- when 0; raise "no such shared inbox in workspace #{@workspace_name}"
400
- else raise CliBadArgument,"multiple match for: #{recipient_email_or_info}"
401
- end
402
- resolved_list.push({'id'=>recipient_user_id['id'],'type'=>'dropbox'})
422
+ short_recipient_info={'id'=>full_recipient_info['source_id'],'type'=>full_recipient_info['source_type']}
403
423
  end
404
- else
405
- raise "recipient item must be a String (email, shared inboc) or hash (id,type)"
406
- end
424
+ else # unexpected extended value, must be String or Hash
425
+ raise "#{recipient_list_field} item must be a String (email, shared inbox) or Hash (id,type)"
426
+ end # type of recipient info
427
+ # add original or resolved recipient info
428
+ resolved_list.push(short_recipient_info)
407
429
  end
430
+ # replace with resolved elements
408
431
  package_creation[recipient_list_field]=resolved_list
409
432
  end
410
433
 
@@ -468,7 +491,7 @@ module Aspera
468
491
  case command_auth_prov
469
492
  when :list
470
493
  providers=@api_aoc.read('admin/auth_providers')[:data]
471
- return {:type=>:object_list,:data=>providers}
494
+ return {type: :object_list,data: providers}
472
495
  when :update
473
496
  end
474
497
  when :subscription
@@ -523,31 +546,31 @@ module Aspera
523
546
  }
524
547
  "
525
548
  result=bss_api.create('graphql',{'variables'=>{'organization_id'=>org['id']},'query'=>graphql_query})[:data]['data']
526
- return {:type=>:single_object,:data=>result['aoc']['bssSubscription']}
549
+ return {type: :single_object,data: result['aoc']['bssSubscription']}
527
550
  when :ats
528
551
  ats_api = Rest.new(@api_aoc.params.deep_merge({
529
- :base_url => @api_aoc.params[:base_url]+'/admin/ats/pub/v1',
530
- :auth => {:scope => AoC::SCOPE_FILES_ADMIN_USER}
552
+ base_url: @api_aoc.params[:base_url]+'/admin/ats/pub/v1',
553
+ auth: {scope: AoC::SCOPE_FILES_ADMIN_USER}
531
554
  }))
532
555
  return Ats.new(@agents).execute_action_gen(ats_api)
533
556
  when :analytics
534
557
  analytics_api = Rest.new(@api_aoc.params.deep_merge({
535
- :base_url => @api_aoc.params[:base_url].gsub('/api/v1','')+'/analytics/v2',
536
- :auth => {:scope => AoC::SCOPE_FILES_ADMIN_USER}
558
+ base_url: @api_aoc.params[:base_url].gsub('/api/v1','')+'/analytics/v2',
559
+ auth: {scope: AoC::SCOPE_FILES_ADMIN_USER}
537
560
  }))
538
561
  command_analytics=self.options.get_next_command([ :application_events, :transfers ])
539
562
  case command_analytics
540
563
  when :application_events
541
564
  event_type=command_analytics.to_s
542
- events=analytics_api.read("organizations/#{c_user_info['organization_id']}/#{event_type}")[:data][event_type]
543
- return {:type=>:object_list,:data=>events}
565
+ events=analytics_api.read("organizations/#{@api_aoc.user_info['organization_id']}/#{event_type}")[:data][event_type]
566
+ return {type: :object_list,data: events}
544
567
  when :transfers
545
568
  event_type=command_analytics.to_s
546
569
  filter_resource=self.options.get_option(:name,:optional) || 'organizations'
547
570
  filter_id=self.options.get_option(:id,:optional) || case filter_resource
548
- when 'organizations'; c_user_info['organization_id']
549
- when 'users'; c_user_info['id']
550
- when 'nodes'; c_user_info['id']
571
+ when 'organizations'; @api_aoc.user_info['organization_id']
572
+ when 'users'; @api_aoc.user_info['id']
573
+ when 'nodes'; @api_aoc.user_info['id']
551
574
  else raise 'organizations or users for option --name'
552
575
  end
553
576
  #
@@ -575,24 +598,19 @@ module Aspera
575
598
  self.config.send_email_template({ev: tr_event})
576
599
  end
577
600
  end
578
- return {:type=>:object_list,:data=>events}
601
+ return {type: :object_list,data: events}
579
602
  end
580
603
  when :resource
581
604
  resource_type=self.options.get_next_argument('resource',[:self,:organization,:user,:group,:client,:contact,:dropbox,:node,:operation,:package,:saml_configuration, :workspace, :dropbox_membership,:short_link,:workspace_membership,:application,:client_registration_token,:client_access_key,:kms_profile])
582
- # get path on API
605
+ # get path on API, resource type is singular, but api is plural
583
606
  resource_class_path=case resource_type
584
- when :self,:organization
585
- resource_type
586
- when :application
587
- 'admin/apps_new'
588
- when :dropbox
589
- resource_type.to_s+'es'
590
- when :client_registration_token,:client_access_key
591
- "admin/#{resource_type}s"
592
- when :kms_profile
593
- "integrations/#{resource_type}s"
594
- else
595
- resource_type.to_s+'s'
607
+ # special cases: singleton, in admin, with x
608
+ when :self,:organization; resource_type
609
+ when :client_registration_token,:client_access_key; "admin/#{resource_type}s"
610
+ when :application; 'admin/apps_new'
611
+ when :dropbox; resource_type.to_s+'es'
612
+ when :kms_profile; "integrations/#{resource_type}s"
613
+ else resource_type.to_s+'s'
596
614
  end
597
615
  # build list of supported operations
598
616
  singleton_object=[:self,:organization].include?(resource_type)
@@ -607,19 +625,14 @@ module Aspera
607
625
  if !singleton_object and !global_operations.include?(command)
608
626
  res_id=self.options.get_option(:id)
609
627
  res_name=self.options.get_option(:name)
610
- if res_id.nil? and res_name.nil? and resource_type.eql?(:node)
611
- set_workspace_info
612
- set_home_node_file
613
- res_id=@home_node_file[:node_info]['id']
614
- end
615
- if !res_name.nil?
616
- Log.log.warn('name overrides id') unless res_id.nil?
617
- matching=@api_aoc.read(resource_class_path,{:q=>res_name})[:data]
618
- raise CliError,'no resource match name' if matching.empty?
619
- raise CliError,"several resources match name (#{matching.join(',')})" unless matching.length.eql?(1)
620
- res_id=matching.first['id']
628
+ raise "Provide id or name, not both" unless res_id.nil? or res_name.nil?
629
+ # try to find item by name (single partial match or exact match)
630
+ res_id=lookup_single(resource_class_path,res_name)['id'] if !res_name.nil?
631
+ # if no name or id option, taken on command line (after command)
632
+ if res_id.nil?
633
+ res_id=self.options.get_next_argument('identifier')
634
+ res_id=lookup_single(resource_class_path,self.options.get_next_argument('identifier'))['id'] if res_id.eql?('name')
621
635
  end
622
- raise CliBadArgument,'provide either id or name' if res_id.nil?
623
636
  resource_instance_path="#{resource_class_path}/#{res_id}"
624
637
  end
625
638
  resource_instance_path=resource_class_path if singleton_object
@@ -638,7 +651,7 @@ module Aspera
638
651
  default_fields=['id']
639
652
  default_query={}
640
653
  case resource_type
641
- when :application; default_query={:organization_apps=>true};default_fields.push('app_type','app_name','available','direct_authorizations_allowed','workspace_authorizations_allowed')
654
+ when :application; default_query={organization_apps: true};default_fields.push('app_type','app_name','available','direct_authorizations_allowed','workspace_authorizations_allowed')
642
655
  when :client,:client_access_key,:dropbox,:group,:package,:saml_configuration,:workspace; default_fields.push('name')
643
656
  when :client_registration_token; default_fields.push('value','data.client_subject_scopes','created_at')
644
657
  when :contact; default_fields=['email','name','source_id','source_type']
@@ -651,11 +664,11 @@ module Aspera
651
664
  count_msg="Items: #{item_list.length}/#{total_count}"
652
665
  count_msg=count_msg.bg_red unless item_list.length.eql?(total_count.to_i)
653
666
  self.format.display_status(count_msg)
654
- return {:type=>:object_list,:data=>item_list,:fields=>default_fields}
667
+ return {type: :object_list,data: item_list,fields: default_fields}
655
668
  when :show
656
669
  object=@api_aoc.read(resource_instance_path)[:data]
657
670
  fields=object.keys.select{|k|!k.eql?('certificate')}
658
- return { :type=>:single_object, :data =>object, :fields=>fields }
671
+ return { type: :single_object, data: object, fields: fields }
659
672
  when :modify
660
673
  changes=self.options.get_next_argument('modified parameters (hash)')
661
674
  @api_aoc.update(resource_instance_path,changes)
@@ -669,19 +682,19 @@ module Aspera
669
682
  # special : reads private and generate public
670
683
  the_private_key=self.options.get_next_argument('private_key')
671
684
  the_public_key=OpenSSL::PKey::RSA.new(the_private_key).public_key.to_s
672
- @api_aoc.update(resource_instance_path,{:jwt_grant_enabled=>true, :public_key=>the_public_key})
685
+ @api_aoc.update(resource_instance_path,{jwt_grant_enabled: true, public_key: the_public_key})
673
686
  return Main.result_success
674
687
  when :v3,:v4
675
688
  res_data=@api_aoc.read(resource_instance_path)[:data]
676
689
  api_node=@api_aoc.get_node_api(res_data)
677
690
  return Node.new(@agents.merge(skip_basic_auth_options: true, node_api: api_node)).execute_action if command.eql?(:v3)
678
- ak_data=api_node.call({:operation=>'GET',:subpath=>"access_keys/#{res_data['access_key']}",:headers=>{'Accept'=>'application/json'}})[:data]
691
+ ak_data=api_node.call({operation: 'GET',subpath: "access_keys/#{res_data['access_key']}",headers: {'Accept'=>'application/json'}})[:data]
679
692
  command_repo=self.options.get_next_command(NODE4_COMMANDS)
680
693
  return execute_node_gen4_command(command_repo,{node_info: res_data, file_id: ak_data['root_file_id']})
681
694
  when :shared_folders
682
695
  read_params = case resource_type
683
- when :workspace;{'access_id'=>"ASPERA_ACCESS_KEY_ADMIN_WS_#{res_id}",'access_type'=>'user'}
684
- when :node;{'include'=>['[]','access_level','permission_count'],'created_by_id'=>'ASPERA_ACCESS_KEY_ADMIN'}
696
+ when :workspace;{'access_id'=>"#{ID_AK_ADMIN}_WS_#{res_id}",'access_type'=>'user'}
697
+ when :node;{'include'=>['[]','access_level','permission_count'],'created_by_id'=>ID_AK_ADMIN}
685
698
  else raise 'error'
686
699
  end
687
700
  res_data=@api_aoc.read("#{resource_class_path}/#{res_id}/permissions",read_params)[:data]
@@ -690,9 +703,9 @@ module Aspera
690
703
  when :workspace;['id','node_id','file_id','node_name','file.path','tags.aspera.files.workspace.share_as']
691
704
  else raise "unexpected resource type #{resource_type}"
692
705
  end
693
- return { :type=>:object_list, :data =>res_data , :fields=>fields}
706
+ return { type: :object_list, data: res_data , fields: fields}
694
707
  when :shared_create
695
- # TODO
708
+ # TODO: finish implementation
696
709
  folder_path=self.options.get_next_argument('folder path in node')
697
710
  user_create_data=self.options.get_next_argument('creation data (Hash)')
698
711
  res_data=@api_aoc.read(resource_instance_path)[:data]
@@ -701,7 +714,7 @@ module Aspera
701
714
  #node_api=@api_aoc.get_node_api(node_file[:node_info],scope: AoC::SCOPE_NODE_USER)
702
715
  #file_info = node_api.read("files/#{node_file[:file_id]}")[:data]
703
716
 
704
- access_id="ASPERA_ACCESS_KEY_ADMIN_WS_#{@workspace_id}"
717
+ access_id="#{ID_AK_ADMIN}_WS_#{@workspace_id}"
705
718
  create_data={
706
719
  'file_id' =>node_file[:file_id],
707
720
  'access_type' =>'user',
@@ -711,10 +724,10 @@ module Aspera
711
724
  'id' =>@workspace_id,
712
725
  'workspace_name' =>@workspace_name,
713
726
  'share_as' =>File.basename(folder_path),
714
- 'user_name' =>c_user_info['name'],
715
- 'shared_by_user_id'=>c_user_info['id'],
716
- 'shared_by_name' =>c_user_info['name'],
717
- 'shared_by_email' =>c_user_info['email'],
727
+ 'user_name' =>@api_aoc.user_info['name'],
728
+ 'shared_by_user_id'=>@api_aoc.user_info['id'],
729
+ 'shared_by_name' =>@api_aoc.user_info['name'],
730
+ 'shared_by_email' =>@api_aoc.user_info['email'],
718
731
  'shared_with_name' =>access_id,
719
732
  'access_key' =>node_file[:node_info]['access_key'],
720
733
  'node' =>node_file[:node_info]['name']}
@@ -725,12 +738,12 @@ module Aspera
725
738
  else raise 'unknown command'
726
739
  end
727
740
  when :usage_reports
728
- return {:type=>:object_list,:data=>@api_aoc.read('usage_reports',{:workspace_id=>@workspace_id})[:data]}
741
+ return {type: :object_list,data: @api_aoc.read('usage_reports',{workspace_id: @workspace_id})[:data]}
729
742
  end
730
743
  end
731
744
 
732
745
  # must be public
733
- ACTIONS=[ :reminder, :bearer_token, :organization, :tier_restrictions, :user, :workspace, :packages, :files, :gateway, :admin, :automation, :servers]
746
+ ACTIONS=[ :reminder, :bearer_token, :organization, :tier_restrictions, :user, :workspace, :packages, :files, :gateway, :admin, :automation, :servers].freeze
734
747
 
735
748
  def execute_action
736
749
  command=self.options.get_next_command(ACTIONS)
@@ -742,38 +755,38 @@ module Aspera
742
755
  Rest.new(base_url: "#{AoC.api_base_url}/#{AoC::API_V1}").create('organization_reminders',{email: user_email})[:data]
743
756
  return Main.result_status("List of organizations user is member of, has been sent by e-mail to #{user_email}")
744
757
  when :bearer_token
745
- return {:type=>:text,:data=>@api_aoc.oauth_token}
758
+ return {type: :text,data: @api_aoc.oauth_token}
746
759
  when :organization
747
- return { :type=>:single_object, :data =>@api_aoc.read('organization')[:data] }
760
+ return { type: :single_object, data: @api_aoc.read('organization')[:data] }
748
761
  when :tier_restrictions
749
- return { :type=>:single_object, :data =>@api_aoc.read('tier_restrictions')[:data] }
762
+ return { type: :single_object, data: @api_aoc.read('tier_restrictions')[:data] }
750
763
  when :user
751
764
  command=self.options.get_next_command([ :workspaces,:info,:shared_inboxes ])
752
765
  case command
753
766
  when :workspaces
754
- return {:type=>:object_list,:data=>@api_aoc.read('workspaces')[:data],:fields=>['id','name']}
767
+ return {type: :object_list,data: @api_aoc.read('workspaces')[:data],fields: ['id','name']}
755
768
  # when :settings
756
- # return {:type=>:object_list,:data=>@api_aoc.read('client_settings/')[:data]}
769
+ # return {type: :object_list,data: @api_aoc.read('client_settings/')[:data]}
757
770
  when :shared_inboxes
758
771
  query=option_url_query(nil)
759
772
  if query.nil?
760
773
  set_workspace_info
761
774
  query={'embed[]'=>'dropbox','workspace_id'=>@workspace_id,'aggregate_permissions_by_dropbox'=>true,'sort'=>'dropbox_name'}
762
775
  end
763
- return {:type=>:object_list,:data=>@api_aoc.read('dropbox_memberships',query)[:data],:fields=>['dropbox_id','dropbox.name']}
776
+ return {type: :object_list,data: @api_aoc.read('dropbox_memberships',query)[:data],fields: ['dropbox_id','dropbox.name']}
764
777
  when :info
765
778
  command=self.options.get_next_command([ :show,:modify ])
766
779
  case command
767
780
  when :show
768
- return { :type=>:single_object, :data =>c_user_info }
781
+ return { type: :single_object, data: @api_aoc.user_info }
769
782
  when :modify
770
- @api_aoc.update("users/#{c_user_info['id']}",self.options.get_next_argument('modified parameters (hash)'))
783
+ @api_aoc.update("users/#{@api_aoc.user_info['id']}",self.options.get_next_argument('modified parameters (hash)'))
771
784
  return Main.result_status('modified')
772
785
  end
773
786
  end
774
787
  when :workspace # show current workspace parameters
775
788
  set_workspace_info
776
- return { :type=>:single_object, :data =>@workspace_data }
789
+ return { type: :single_object, data: @workspace_data }
777
790
  when :packages
778
791
  set_workspace_info if @url_token_data.nil?
779
792
  command_pkg=self.options.get_next_command([ :send, :recv, :list, :show, :delete ])
@@ -812,14 +825,14 @@ module Aspera
812
825
  # raise exception if at least one error
813
826
  Main.result_transfer(transfer_start(AoC::PACKAGES_APP,'send',node_file,AoC.package_tags(package_info,'upload')))
814
827
  # return all info on package
815
- return { :type=>:single_object, :data =>package_info}
828
+ return { type: :single_object, data: package_info}
816
829
  when :recv
817
830
  if !@url_token_data.nil?
818
831
  assert_public_link_types(['view_received_package'])
819
832
  self.options.set_option(:id,@url_token_data['data']['package_id'])
820
833
  end
821
834
  # scalar here
822
- ids_to_download=self.options.get_option(:id,:mandatory)
835
+ ids_to_download=self.instance_identifier()
823
836
  skip_ids_data=[]
824
837
  skip_ids_persistency=nil
825
838
  if self.options.get_option(:once_only,:mandatory)
@@ -858,15 +871,15 @@ module Aspera
858
871
  when :show
859
872
  package_id=self.options.get_next_argument('package ID')
860
873
  package_info=@api_aoc.read("packages/#{package_id}")[:data]
861
- return { :type=>:single_object, :data =>package_info }
874
+ return { type: :single_object, data: package_info }
862
875
  when :list
863
876
  query=option_url_query({'archived'=>false,'exclude_dropbox_packages'=>true,'has_content'=>true,'received'=>true})
864
877
  raise 'option must be Hash' unless query.is_a?(Hash)
865
878
  query['workspace_id']||=@workspace_id
866
879
  packages=@api_aoc.read('packages',query)[:data]
867
- return {:type=>:object_list,:data=>packages,:fields=>['id','name','bytes_transferred']}
880
+ return {type: :object_list,data: packages,fields: ['id','name','bytes_transferred']}
868
881
  when :delete
869
- list_or_one=self.options.get_option(:id,:mandatory)
882
+ list_or_one=self.instance_identifier()
870
883
  return do_bulk_operation(list_or_one,'deleted')do|id|
871
884
  raise 'expecting String identifier' unless id.is_a?(String) or id.is_a?(Integer)
872
885
  @api_aoc.delete("packages/#{id}")[:data]
@@ -876,7 +889,7 @@ module Aspera
876
889
  # get workspace related information
877
890
  set_workspace_info
878
891
  set_home_node_file
879
- command_repo=self.options.get_next_command(NODE4_COMMANDS.clone.concat([:short_link]))
892
+ command_repo=self.options.get_next_command([NODE4_COMMANDS,:short_link].flatten)
880
893
  case command_repo
881
894
  when *NODE4_COMMANDS; return execute_node_gen4_command(command_repo,@home_node_file)
882
895
  when :short_link
@@ -935,8 +948,8 @@ module Aspera
935
948
  'workspace_id' =>@workspace_id,
936
949
  'workspace_name' =>@workspace_name,
937
950
  'folder_name' =>'my folder',
938
- 'created_by_name' =>c_user_info['name'],
939
- 'created_by_email'=>c_user_info['email'],
951
+ 'created_by_name' =>@api_aoc.user_info['name'],
952
+ 'created_by_email'=>@api_aoc.user_info['email'],
940
953
  'access_key' =>node_file[:node_info]['access_key'],
941
954
  'node' =>node_file[:node_info]['host']
942
955
  }
@@ -964,18 +977,18 @@ module Aspera
964
977
  when *Plugin::ALL_OPS
965
978
  return self.entity_command(wf_command,automation_api,'workflows',nil,:id)
966
979
  when :launch
967
- wf_id=self.options.get_option(:id,:mandatory)
980
+ wf_id=self.instance_identifier()
968
981
  data=automation_api.create("workflows/#{wf_id}/launch",{})[:data]
969
- return {:type=>:single_object,:data=>data}
982
+ return {type: :single_object,data: data}
970
983
  when :action
971
984
  wf_command=self.options.get_next_command([:list,:create,:show])
972
- wf_id=self.options.get_option(:id,:mandatory)
985
+ wf_id=self.instance_identifier()
973
986
  step=automation_api.create('steps',{'workflow_id'=>wf_id})[:data]
974
987
  automation_api.update("workflows/#{wf_id}",{'step_order'=>[step['id']]})
975
988
  action=automation_api.create('actions',{'step_id'=>step['id'],'type'=>'manual'})[:data]
976
989
  automation_api.update("steps/#{step['id']}",{'action_order'=>[action['id']]})
977
990
  wf=automation_api.read("workflows/#{wf_id}")[:data]
978
- return {:type=>:single_object,:data=>wf}
991
+ return {type: :single_object,data: wf}
979
992
  end
980
993
  end
981
994
  when :gateway
@@ -985,15 +998,15 @@ module Aspera
985
998
  when :admin
986
999
  return execute_admin_action
987
1000
  when :servers
988
- return {:type=>:object_list,:data=>Rest.new(base_url: "#{AoC.api_base_url}/#{AoC::API_V1}").read('servers')[:data]}
1001
+ return {type: :object_list,data: Rest.new(base_url: "#{AoC.api_base_url}/#{AoC::API_V1}").read('servers')[:data]}
989
1002
  else
990
1003
  raise "internal error: #{command}"
991
1004
  end # action
992
1005
  raise RuntimeError, 'internal error: command shall return'
993
1006
  end
994
1007
 
995
- private :c_user_info,:aoc_params,:set_workspace_info,:set_home_node_file,:do_bulk_operation,:resolve_package_recipients,:option_url_query,:assert_public_link_types,:execute_admin_action
996
- private_constant :VAL_ALL,:NODE4_COMMANDS
1008
+ private :aoc_params,:set_workspace_info,:set_home_node_file,:do_bulk_operation,:resolve_package_recipients,:option_url_query,:assert_public_link_types,:execute_admin_action
1009
+ private_constant :VAL_ALL,:NODE4_COMMANDS, :ID_AK_ADMIN
997
1010
 
998
1011
  end # AoC
999
1012
  end # Plugins