aspera-cli 4.8.0 → 4.9.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 (47) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/README.md +445 -160
  4. data/docs/test_env.conf +1 -5
  5. data/examples/dascli +1 -4
  6. data/examples/{transfer.rb → node.rb} +17 -46
  7. data/examples/server.rb +93 -0
  8. data/lib/aspera/aoc.rb +4 -2
  9. data/lib/aspera/ats_api.rb +3 -1
  10. data/lib/aspera/cli/extended_value.rb +1 -0
  11. data/lib/aspera/cli/formater.rb +3 -1
  12. data/lib/aspera/cli/info.rb +1 -1
  13. data/lib/aspera/cli/main.rb +14 -11
  14. data/lib/aspera/cli/manager.rb +4 -4
  15. data/lib/aspera/cli/plugin.rb +50 -9
  16. data/lib/aspera/cli/plugins/aoc.rb +88 -52
  17. data/lib/aspera/cli/plugins/config.rb +5 -0
  18. data/lib/aspera/cli/plugins/faspex.rb +5 -4
  19. data/lib/aspera/cli/plugins/node.rb +3 -2
  20. data/lib/aspera/cli/plugins/server.rb +7 -108
  21. data/lib/aspera/cli/plugins/shares.rb +21 -1
  22. data/lib/aspera/cli/transfer_agent.rb +21 -14
  23. data/lib/aspera/cli/version.rb +1 -1
  24. data/lib/aspera/environment.rb +15 -2
  25. data/lib/aspera/fasp/agent_base.rb +9 -7
  26. data/lib/aspera/fasp/installation.rb +15 -19
  27. data/lib/aspera/fasp/parameters.rb +38 -30
  28. data/lib/aspera/fasp/parameters.yaml +69 -17
  29. data/lib/aspera/hash_ext.rb +14 -2
  30. data/lib/aspera/id_generator.rb +12 -10
  31. data/lib/aspera/keychain/encrypted_hash.rb +3 -3
  32. data/lib/aspera/log.rb +1 -1
  33. data/lib/aspera/nagios.rb +26 -19
  34. data/lib/aspera/oauth.rb +4 -4
  35. data/lib/aspera/open_application.rb +21 -19
  36. data/lib/aspera/persistency_folder.rb +3 -0
  37. data/lib/aspera/preview/image_error.png +0 -0
  38. data/lib/aspera/preview/video_error.png +0 -0
  39. data/lib/aspera/proxy_auto_config.rb +6 -4
  40. data/lib/aspera/rest_error_analyzer.rb +15 -13
  41. data/lib/aspera/rest_errors_aspera.rb +42 -40
  42. data/lib/aspera/secret_hider.rb +11 -5
  43. data/lib/aspera/ssh.rb +1 -0
  44. data/lib/aspera/uri_reader.rb +15 -13
  45. data.tar.gz.sig +0 -0
  46. metadata +4 -3
  47. metadata.gz.sig +0 -0
data/docs/test_env.conf CHANGED
@@ -61,7 +61,6 @@ tst_faspex5_web:
61
61
  client_secret: your value here
62
62
  tst_faspex5:
63
63
  url: your value here
64
- insecure: your value here
65
64
  auth: your value here
66
65
  client_id: your value here
67
66
  client_secret: your value here
@@ -98,7 +97,6 @@ tst_orch:
98
97
  url: your value here
99
98
  username: your value here
100
99
  password: your value here
101
- insecure: your value here
102
100
  tst_ats:
103
101
  ibm_api_key: your value here
104
102
  ats_key: your value here
@@ -108,7 +106,6 @@ tst_bss:
108
106
  password: your value here
109
107
  tst_ak_preview:
110
108
  url: your value here
111
- insecure: your value here
112
109
  username: your value here
113
110
  password: your value here
114
111
  mimemagic: your value here
@@ -116,7 +113,6 @@ tst_node_preview:
116
113
  url: your value here
117
114
  username: your value here
118
115
  password: your value here
119
- insecure: your value here
120
116
  tst_cos:
121
117
  apikey: your value here
122
118
  crn: your value here
@@ -168,4 +164,4 @@ misc:
168
164
  aoc_workspace2: your value here
169
165
  http_gw_fqdn_port: your value here
170
166
  tst_secrets:
171
- st1: your value here
167
+ eudemo-sedemo: your value here
data/examples/dascli CHANGED
@@ -3,10 +3,8 @@
3
3
  # by default take latest version
4
4
  : ${version:=latest}
5
5
  imgtag=$image:$version
6
- # same location as in Dockerfile: generic top folder for apps
7
- appdir=/usr/src/app
8
6
  # same location as in Dockerfile: main config folder for ascli in container
9
- ascli_home_container=${appdir}/config
7
+ ascli_home_container=/home/cliuser/.aspera/ascli
10
8
  # convenience: special argument to install the image
11
9
  case "$1" in install) docker pull $imgtag; exit 0; esac
12
10
  # set default location for config folder on host if necessary
@@ -20,7 +18,6 @@ exec docker run \
20
18
  --rm \
21
19
  --tty \
22
20
  --interactive \
23
- --env ASCLI_HOME="$ascli_home_container" \
24
21
  --volume "$ASCLI_HOME:$ascli_home_container" \
25
22
  $imgtag \
26
23
  ascli "$@"
@@ -15,11 +15,12 @@ require 'tmpdir'
15
15
 
16
16
  tmpdir = ENV['tmp'] || Dir.tmpdir || '.'
17
17
 
18
- DEMO_CONFIG = [
19
- 'ssh://asperaweb@eudemo.asperademo.com:33001',
20
- 'https://node_asperaweb@eudemo.asperademo.com:9092',
21
- 'demoaspera'
22
- ].freeze
18
+ raise 'Usage: PASSWORD=<password> $0 https://<address>:<port> <node user>' unless ARGV.length.eql?(2) && ENV.has_key?('PASSWORD')
19
+
20
+ # example : https://node_asperaweb@eudemo.asperademo.com:9092
21
+ node_uri = URI.parse(ARGV.shift)
22
+ node_user = ARGV.shift
23
+ node_pass = ENV['PASSWORD']
23
24
 
24
25
  ##############################################################
25
26
  # generic initialisation : configuration of FaspManager
@@ -40,14 +41,14 @@ Aspera::Fasp::Installation.instance.ascp_path = ENV['ascp'] if ENV.has_key?('asc
40
41
  # or install:
41
42
  #
42
43
 
43
- # get FASP Manager singleton based on above ascp location
44
- fasp_manager = Aspera::Fasp::AgentDirect.new
44
+ # get Transfer Agent
45
+ transfer_agent = Aspera::Fasp::AgentDirect.new
45
46
 
46
47
  # Note that it would also be possible to start transfers using other agents
47
48
  #require 'aspera/fasp/connect'
48
- #fasp_manager=Aspera::Fasp::Connect.new
49
+ #transfer_agent=Aspera::Fasp::Connect.new
49
50
  #require 'aspera/fasp/node'
50
- #fasp_manager=Aspera::Fasp::Node.new(Aspera::Rest.new(...))
51
+ #transfer_agent=Aspera::Fasp::Node.new(Aspera::Rest.new(...))
51
52
 
52
53
  ##############################################################
53
54
  # Optional : register an event listener
@@ -60,48 +61,18 @@ class MyListener < Aspera::Fasp::Listener
60
61
  end
61
62
 
62
63
  # register the sample listener to display events
63
- fasp_manager.add_listener(MyListener.new)
64
-
65
- ##############################################################
66
- # first example: download by SSH credentials
67
-
68
- # manually build teansfer spec
69
- transfer_spec = {
70
- #'remote_host' =>'demo.asperasoft.com',
71
- 'remote_host' => URI.parse(DEMO_CONFIG[0]).host,
72
- 'ssh_port' => URI.parse(DEMO_CONFIG[0]).port,
73
- 'remote_user' => URI.parse(DEMO_CONFIG[0]).user,
74
- 'remote_password' => DEMO_CONFIG[2],
75
- 'direction' => 'receive',
76
- 'destination_root' => tmpdir,
77
- 'paths' => [{'source' => 'aspera-test-dir-tiny/200KB.1'}]
78
- }
79
- # start transfer in separate thread
80
- # method returns as soon as transfer thread is created
81
- # it des not wait for completion, or even for session startup
82
- fasp_manager.start_transfer(transfer_spec)
83
-
84
- # optional: helper method: wait for completion of transfers
85
- # here we started a single transfer session (no multisession parameter)
86
- # get array of status, one for each session (so, a single value array)
87
- # each status is either :success or "error message"
88
- transfer_result = fasp_manager.wait_for_transfers_completion
89
- $stdout.puts(JSON.generate(transfer_result))
90
- # get list of errors only
91
- errors = transfer_result.reject{|i|i.eql?(:success)}
92
- # the transfer was not success, as there is at least one error
93
- raise "Error(s) occured: #{errors.join(',')}" if !errors.empty?
64
+ transfer_agent.add_listener(MyListener.new)
94
65
 
95
66
  ##############################################################
96
- # second example: upload with node authorization
67
+ # Upload with node authorization
97
68
 
98
69
  # create rest client for Node API on a public demo system, using public demo credentials
99
70
  node_api = Aspera::Rest.new({
100
- base_url: DEMO_CONFIG[1],
71
+ base_url: node_uri.to_s,
101
72
  auth: {
102
73
  type: :basic,
103
- username: URI.parse(DEMO_CONFIG[1]).user,
104
- password: DEMO_CONFIG[2]
74
+ username: node_user,
75
+ password: node_pass
105
76
  }})
106
77
  # define sample file(s) and destination folder
107
78
  sources = ["#{tmpdir}/sample_file.txt"]
@@ -117,9 +88,9 @@ transfer_spec['paths'] = sources.map{|p|{'source' => p}}
117
88
  # set authentication type to "token" (will trigger use of bypass SSH key)
118
89
  transfer_spec['authentication'] = 'token'
119
90
  # from here : same as example 1
120
- fasp_manager.start_transfer(transfer_spec)
91
+ transfer_agent.start_transfer(transfer_spec)
121
92
  # optional: wait for transfer completion helper function to get events
122
- transfer_result = fasp_manager.wait_for_transfers_completion
93
+ transfer_result = transfer_agent.wait_for_transfers_completion
123
94
  errors = transfer_result.reject{|i|i.eql?(:success)}
124
95
  # the transfer was not success, as there is at least one error
125
96
  raise "Error(s) occured: #{errors.join(',')}" if !errors.empty?
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example: transfer a file using one of the provided transfer agents
5
+ # location of ascp can be specified with env var "ascp"
6
+ # temp folder can be specified with env var "tmp"
7
+ require 'aspera/fasp/agent_direct'
8
+ require 'aspera/fasp/listener'
9
+ require 'aspera/fasp/installation'
10
+ require 'aspera/log'
11
+ require 'aspera/rest'
12
+ require 'aspera/rest_errors_aspera'
13
+ require 'json'
14
+ require 'tmpdir'
15
+
16
+ tmpdir = ENV['tmp'] || Dir.tmpdir || '.'
17
+
18
+ raise 'Usage: PASSWORD=<password> $0 ssh://<address>:<port> <transfer user>' unless ARGV.length.eql?(2) && ENV.has_key?('PASSWORD')
19
+
20
+ # example : ssh://asperaweb@eudemo.asperademo.com:33001
21
+ server_uri = URI.parse(ARGV.shift)
22
+ server_user = ARGV.shift
23
+ server_pass = ENV['PASSWORD']
24
+
25
+ ##############################################################
26
+ # generic initialisation : configuration of FaspManager
27
+
28
+ # set trace level for sample, set to :debug to see complete list of debug information
29
+ Aspera::Log.instance.level = :debug
30
+
31
+ # register aspera REST call error handlers
32
+ Aspera::RestErrorsAspera.register_handlers
33
+
34
+ # some required files are generated here (keys, certs)
35
+ Aspera::Fasp::Installation.instance.folder = tmpdir
36
+ # set path to your copy of ascp binary (else, let the system find)
37
+ Aspera::Fasp::Installation.instance.ascp_path = ENV['ascp'] if ENV.has_key?('ascp')
38
+ # another way is to detect installed products and use one of them
39
+ #Aspera::Fasp::Installation.instance.installed_products.each{|p|puts("found: #{p[:name]}")}
40
+ #Aspera::Fasp::Installation.instance.use_ascp_from_product('Aspera Connect')
41
+ # or install:
42
+ #
43
+
44
+ # get Transfer Agent
45
+ transfer_agent = Aspera::Fasp::AgentDirect.new
46
+
47
+ # Note that it would also be possible to start transfers using other agents
48
+ #require 'aspera/fasp/connect'
49
+ #transfer_agent=Aspera::Fasp::Connect.new
50
+ #require 'aspera/fasp/node'
51
+ #transfer_agent=Aspera::Fasp::Node.new(Aspera::Rest.new(...))
52
+
53
+ ##############################################################
54
+ # Optional : register an event listener
55
+
56
+ # example of event listener that displays events on stdout
57
+ class MyListener < Aspera::Fasp::Listener
58
+ # this is the callback called during transfers, here we only display the received information
59
+ # but it could be used to get detailed error information, check "type" field is "ERROR"
60
+ def event_enhanced(data);$stdout.puts(JSON.generate(data));$stdout.flush;end
61
+ end
62
+
63
+ # register the sample listener to display events
64
+ transfer_agent.add_listener(MyListener.new)
65
+
66
+ ##############################################################
67
+ # first example: download by SSH credentials
68
+
69
+ # manually build teansfer spec
70
+ transfer_spec = {
71
+ 'remote_host' => server_uri.host,
72
+ 'ssh_port' => server_uri.port,
73
+ 'remote_user' => server_user,
74
+ 'remote_password' => server_pass,
75
+ 'direction' => 'receive',
76
+ 'destination_root' => tmpdir,
77
+ 'paths' => [{'source' => 'aspera-test-dir-tiny/200KB.1'}]
78
+ }
79
+ # start transfer in separate thread
80
+ # method returns as soon as transfer thread is created
81
+ # it des not wait for completion, or even for session startup
82
+ transfer_agent.start_transfer(transfer_spec)
83
+
84
+ # optional: helper method: wait for completion of transfers
85
+ # here we started a single transfer session (no multisession parameter)
86
+ # get array of status, one for each session (so, a single value array)
87
+ # each status is either :success or "error message"
88
+ transfer_result = transfer_agent.wait_for_transfers_completion
89
+ $stdout.puts(JSON.generate(transfer_result))
90
+ # get list of errors only
91
+ errors = transfer_result.reject{|i|i.eql?(:success)}
92
+ # the transfer was not success, as there is at least one error
93
+ raise "Error(s) occured: #{errors.join(',')}" if !errors.empty?
data/lib/aspera/aoc.rb CHANGED
@@ -6,6 +6,7 @@ require 'aspera/hash_ext'
6
6
  require 'aspera/data_repository'
7
7
  require 'aspera/fasp/transfer_spec'
8
8
  require 'base64'
9
+ require 'cgi'
9
10
 
10
11
  Aspera::Oauth.register_token_creator(:aoc_pub_link,lambda{|o|
11
12
  o.api.call({
@@ -315,7 +316,8 @@ module Aspera
315
316
  end
316
317
  else
317
318
  # retrieve values from API
318
- std_t_spec = node_api.create('files/download_setup',
319
+ std_t_spec = node_api.create(
320
+ 'files/download_setup',
319
321
  {transfer_requests: [{ transfer_request: {paths: [{'source' => '/'}] } }] }
320
322
  )[:data]['transfer_specs'].first['transfer_spec']
321
323
  %w[remote_host remote_user ssh_port fasp_port].each {|i| transfer_spec[i] = std_t_spec[i]}
@@ -453,7 +455,7 @@ module Aspera
453
455
  # @param options additional search options
454
456
  def lookup_entity_by_name(entity_type,entity_name,options={})
455
457
  # returns entities whose name contains value (case insensitive)
456
- matching_items = read(entity_type,options.merge({'q' => entity_name}))[:data]
458
+ matching_items = read(entity_type,options.merge({'q' => CGI.escape(entity_name)}))[:data]
457
459
  case matching_items.length
458
460
  when 1 then return matching_items.first
459
461
  when 0 then raise %Q{#{ENTITY_NOT_FOUND} #{entity_type}: "#{entity_name}"}
@@ -18,7 +18,9 @@ module Aspera
18
18
 
19
19
  private_constant :CLOUD_NAME
20
20
 
21
- def self.base_url;'https://ats.aspera.io';end
21
+ class << self
22
+ def base_url;'https://ats.aspera.io';end
23
+ end
22
24
 
23
25
  def initialize
24
26
  super({base_url: AtsApi.base_url + '/pub/v1'})
@@ -30,6 +30,7 @@ module Aspera
30
30
  hasharray.push(entry)
31
31
  end
32
32
  end
33
+ Log.log.warn('Titled CSV file without any line') if hasharray.empty?
33
34
  return hasharray
34
35
  end
35
36
  end
@@ -14,7 +14,7 @@ module Aspera
14
14
  CSV_RECORD_SEPARATOR = "\n"
15
15
  CSV_FIELD_SEPARATOR = ','
16
16
  # supported output formats
17
- DISPLAY_FORMATS = %i[table ruby json jsonpp yaml csv nagios].freeze
17
+ DISPLAY_FORMATS = %i[text nagios ruby json jsonpp yaml table csv].freeze
18
18
  # user output levels
19
19
  DISPLAY_LEVELS = %i[info data error].freeze
20
20
  CONF_OVERVIEW_KEYS=%w[config parameter value].freeze
@@ -147,6 +147,8 @@ module Aspera
147
147
  # comma separated list in string format
148
148
  user_asked_fields_list_str = @option_fields
149
149
  case @option_format
150
+ when :text
151
+ display_message(:data,res_data.to_s)
150
152
  when :nagios
151
153
  Nagios.process(res_data)
152
154
  when :ruby
@@ -11,7 +11,7 @@ module Aspera
11
11
  SRC_URL = 'https://github.com/IBM/aspera-cli'
12
12
  # set this to warn in advance when minimum required ruby version will increase
13
13
  # for example currently minimum version is 2.4 in gemspec, but future minimum will be 2.5
14
- # set to currenmt minimum if there is no deprecation
14
+ # set to current minimum if there is no deprecation
15
15
  # the actual current minimum required version is in gemspec at required_ruby_version
16
16
  RUBY_FUTURE_MINIMUM_VERSION = '2.5'
17
17
  end
@@ -16,6 +16,7 @@ require 'aspera/rest'
16
16
  require 'aspera/nagios'
17
17
  require 'aspera/colors'
18
18
  require 'aspera/secret_hider'
19
+ require 'net/ssh'
19
20
 
20
21
  module Aspera
21
22
  module Cli
@@ -266,12 +267,12 @@ module Aspera
266
267
  # early debug for parser
267
268
  # Note: does not accept shortcuts
268
269
  def early_debug_setup(argv)
269
- Log.instance.program_name = PROGRAM_NAME
270
+ Aspera::Log.instance.program_name = PROGRAM_NAME
270
271
  argv.each do |arg|
271
272
  case arg
272
273
  when '--' then break
273
- when /^--log-level=(.*)/ then Log.instance.level = Regexp.last_match(1).to_sym
274
- when /^--logger=(.*)/ then Log.instance.logger_type = Regexp.last_match(1).to_sym
274
+ when /^--log-level=(.*)/ then Aspera::Log.instance.level = Regexp.last_match(1).to_sym
275
+ when /^--logger=(.*)/ then Aspera::Log.instance.logger_type = Regexp.last_match(1).to_sym
275
276
  end
276
277
  end
277
278
  end
@@ -334,19 +335,21 @@ module Aspera
334
335
  @plugin_env[:formater].display_results(command_plugin.execute_action) if execute_command
335
336
  # finish
336
337
  @plugin_env[:transfer].shutdown
337
- rescue CliBadArgument => e; exception_info = {e: e,t: 'Argument',usage: true}
338
- rescue CliNoSuchId => e; exception_info = {e: e,t: 'Identifier'}
339
- rescue CliError => e; exception_info = {e: e,t: 'Tool',usage: true}
340
- rescue Fasp::Error => e; exception_info = {e: e,t: 'FASP(ascp)'}
341
- rescue Aspera::RestCallError => e; exception_info = {e: e,t: 'Rest'}
342
- rescue SocketError => e; exception_info = {e: e,t: 'Network'}
343
- rescue StandardError => e; exception_info = {e: e,t: 'Other',debug: true}
344
- rescue Interrupt => e; exception_info = {e: e,t: 'Interruption',debug: true}
338
+ rescue Net::SSH::AuthenticationFailed => e; exception_info = {e: e,t: 'SSH',security: true}
339
+ rescue CliBadArgument => e; exception_info = {e: e,t: 'Argument',usage: true}
340
+ rescue CliNoSuchId => e; exception_info = {e: e,t: 'Identifier'}
341
+ rescue CliError => e; exception_info = {e: e,t: 'Tool',usage: true}
342
+ rescue Fasp::Error => e; exception_info = {e: e,t: 'FASP(ascp)'}
343
+ rescue Aspera::RestCallError => e; exception_info = {e: e,t: 'Rest'}
344
+ rescue SocketError => e; exception_info = {e: e,t: 'Network'}
345
+ rescue StandardError => e; exception_info = {e: e,t: 'Other',debug: true}
346
+ rescue Interrupt => e; exception_info = {e: e,t: 'Interruption',debug: true}
345
347
  end
346
348
  # cleanup file list files
347
349
  TempFileManager.instance.cleanup
348
350
  # 1- processing of error condition
349
351
  unless exception_info.nil?
352
+ Log.log.warn(exception_info[:e].message) if Aspera::Log.instance.logger_type.eql?(:syslog) && exception_info[:security]
350
353
  @plugin_env[:formater].display_message(:error,"#{ERROR_FLASH} #{exception_info[:t]}: #{exception_info[:e].message}")
351
354
  @plugin_env[:formater].display_message(:error,'Use option -h to get help.') if exception_info[:usage]
352
355
  if exception_info[:e].is_a?(Fasp::Error) && exception_info[:e].message.eql?('Remote host is not who we expected')
@@ -56,7 +56,7 @@ module Aspera
56
56
 
57
57
  class << self
58
58
  def enum_to_bool(enum)
59
- raise "Value not valid for boolean: #{enum}/#{enum.class}" unless BOOLEAN_VALUES.include?(enum)
59
+ raise "Value not valid for boolean: [#{enum}]/#{enum.class}" unless BOOLEAN_VALUES.include?(enum)
60
60
  return TRUE_VALUES.include?(enum)
61
61
  end
62
62
 
@@ -210,7 +210,7 @@ module Aspera
210
210
  end
211
211
  value = ExtendedValue.instance.evaluate(value)
212
212
  value = Manager.enum_to_bool(value) if @declared_options[option_symbol][:values].eql?(BOOLEAN_VALUES)
213
- Log.log.debug("set #{option_symbol}=#{value} (#{@declared_options[option_symbol][:type]}) : #{where}")
213
+ Log.log.debug("(#{@declared_options[option_symbol][:type]}/#{where}) set #{option_symbol}=#{value}")
214
214
  case @declared_options[option_symbol][:type]
215
215
  when :accessor
216
216
  @declared_options[option_symbol][:accessor].value = value
@@ -235,10 +235,10 @@ module Aspera
235
235
  else
236
236
  raise 'unknown type'
237
237
  end
238
- Log.log.debug("get #{option_symbol} (#{@declared_options[option_symbol][:type]}) : #{result}")
238
+ Log.log.debug("(#{@declared_options[option_symbol][:type]}) get #{option_symbol}=#{result}")
239
239
  end
240
240
  # do not fail for manual generation if option mandatory but not set
241
- result = '' if result.nil? && !@fail_on_missing_mandatory
241
+ result = '' if result.nil? && is_type.eql?(:mandatory) && !@fail_on_missing_mandatory
242
242
  #Log.log.debug("interactive=#{@ask_missing_mandatory}")
243
243
  if result.nil?
244
244
  if !@ask_missing_mandatory
@@ -4,9 +4,9 @@ module Aspera
4
4
  module Cli
5
5
  # base class for plugins modules
6
6
  class Plugin
7
- # operation without id
7
+ # operations without id
8
8
  GLOBAL_OPS = %i[create list].freeze
9
- # operation on specific instance
9
+ # operations with id
10
10
  INSTANCE_OPS = %i[modify delete show].freeze
11
11
  ALL_OPS = [GLOBAL_OPS,INSTANCE_OPS].flatten.freeze
12
12
  # max number of items for list command
@@ -34,6 +34,8 @@ module Aspera
34
34
  options.add_opt_simple(:value,'extended value for create, update, list filter')
35
35
  options.add_opt_simple(:property,'name of property to set')
36
36
  options.add_opt_simple(:id,"resource identifier (#{INSTANCE_OPS.join(',')})")
37
+ options.add_opt_boolean(:bulk,'Bulk operation (only some)')
38
+ options.set_option(:bulk,:no)
37
39
  options.parse_options!
38
40
  @@options_created = true # rubocop:disable Style/ClassVars
39
41
  end
@@ -46,8 +48,41 @@ module Aspera
46
48
  end
47
49
 
48
50
  # TODO
49
- def get_next_id_command(instance_ops: INSTANCE_OPS,global_ops: GLOBAL_OPS)
50
- return get_next_argument('command',expected: command_list)
51
+ #def get_next_id_command(instance_ops: INSTANCE_OPS,global_ops: GLOBAL_OPS)
52
+ # return get_next_argument('command',expected: command_list)
53
+ #end
54
+
55
+ # For create and delete operations: execute one actin or multiple if bulk is yes
56
+ # @param params either single id or hash, or array for bulk
57
+ # @param success_msg deleted or created
58
+ def do_bulk_operation(single_or_array,success_msg,id_result: 'id',fields: :default)
59
+ raise 'programming error: missing block' unless block_given?
60
+ params = options.get_option(:bulk) ? single_or_array : [single_or_array]
61
+ raise 'expecting Array for bulk operation' unless params.is_a?(Array)
62
+ Log.log.warn('Empty list given for bulk operation') if params.empty?
63
+ Log.dump(:bulk_create,params)
64
+ result_list = []
65
+ params.each do |param|
66
+ # init for delete
67
+ result = {id_result => param}
68
+ begin
69
+ # execute custom code
70
+ res = yield(param)
71
+ # if block returns a hash, let's use this (create)
72
+ result = res if param.is_a?(Hash)
73
+ result['status'] = success_msg
74
+ rescue StandardError => e
75
+ result['status'] = e.to_s
76
+ end
77
+ result_list.push(result)
78
+ end
79
+ display_fields = [id_result,'status']
80
+ if options.get_option(:bulk)
81
+ return {type: :object_list,data: result_list,fields: display_fields}
82
+ else
83
+ display_fields = fields unless fields.eql?(:default)
84
+ return {type: :single_object,data: result_list.first,fields: display_fields}
85
+ end
51
86
  end
52
87
 
53
88
  # @param command [Symbol] command to execute: create show list modify delete
@@ -56,6 +91,7 @@ module Aspera
56
91
  # @param display_fields [Array] fields to display by default
57
92
  # @param id_default [String] default identifier to use for existing entity commands (show, modify)
58
93
  # @param item_list_key [String] result is in a subkey of the json
94
+ # @return result suitable for CLI result
59
95
  def entity_command(command,rest_api,res_class_path,display_fields: nil,id_default: nil,item_list_key: false)
60
96
  if INSTANCE_OPS.include?(command)
61
97
  begin
@@ -76,7 +112,15 @@ module Aspera
76
112
  end
77
113
  case command
78
114
  when :create
79
- return {type: :single_object, data: rest_api.create(res_class_path,parameters)[:data], fields: display_fields}
115
+ return do_bulk_operation(parameters,'created',fields: display_fields) do |params|
116
+ raise 'expecting Hash' unless params.is_a?(Hash)
117
+ rest_api.create(res_class_path,params)[:data]
118
+ end
119
+ when :delete
120
+ return do_bulk_operation(one_res_id,'deleted') do |one_id|
121
+ rest_api.delete("#{res_class_path}/#{one_id}")
122
+ {'id' => one_id}
123
+ end
80
124
  when :show
81
125
  return {type: :single_object, data: rest_api.read(one_res_path)[:data], fields: display_fields}
82
126
  when :list
@@ -102,9 +146,6 @@ module Aspera
102
146
  parameters = {property => parameters} unless property.nil?
103
147
  rest_api.update(one_res_path,parameters)
104
148
  return Main.result_status('modified')
105
- when :delete
106
- rest_api.delete(one_res_path)
107
- return Main.result_status('deleted')
108
149
  else
109
150
  raise "unknown action: #{command}"
110
151
  end
@@ -117,7 +158,7 @@ module Aspera
117
158
  return entity_command(command,rest_api,res_class_path,**opts)
118
159
  end
119
160
 
120
- # shortcuts for plugin environment
161
+ # shortcuts helpers for plugin environment
121
162
  def options; return @agents[:options];end
122
163
 
123
164
  def transfer; return @agents[:transfer];end