aspera-cli 4.8.0 → 4.9.0

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