aspera-cli 4.5.0 → 4.6.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.
data/docs/doc_tools.rb CHANGED
@@ -32,7 +32,7 @@ def prstt;opprst.capitalize;end
32
32
 
33
33
  def gemspec;Gem::Specification::load(ENV['GEMSPEC']) or raise "error loading #{ENV["GEMSPEC"]}";end
34
34
 
35
- def geminstadd;gemspec.version.to_s.match(/\.[^0-9]/)?' --pre':'';end
35
+ def geminstadd;gemspec.version.to_s.match(/\.[^0-9]/) ? ' --pre' : '' ;end
36
36
 
37
37
  # transfer spec description generation
38
38
  def spec_table
data/docs/test_env.conf CHANGED
@@ -143,6 +143,7 @@ misc:
143
143
  aoc_publink_folder: your value here
144
144
  aoc_shbx_ws: your value here
145
145
  aoc_shbx_name: your value here
146
+ aoc_shbx_meta: your value here
146
147
  aoc_node1_name: your value here
147
148
  aoc_node1_secret: your value here
148
149
  icos_bucket_key: your value here
data/lib/aspera/aoc.rb CHANGED
@@ -428,5 +428,26 @@ module Aspera
428
428
  return result
429
429
  end
430
430
 
431
+ # @param entity_type path of entuty in API
432
+ # @param entity_name name of searched entity
433
+ # @param options additional search options
434
+ def lookup_entity_by_name(entity_type,entity_name,options={})
435
+ # returns entities whose name contains value (case insensitive)
436
+ matching_items=read(entity_type,options.merge({'q'=>entity_name}))[:data]
437
+ case matching_items.length
438
+ when 1; return matching_items.first
439
+ when 0; raise RuntimeError,'not found'
440
+ else
441
+ # multiple case insensitive partial matches, try case insensitive full match
442
+ # (anyway AoC does not allow creation of 2 entities with same case insensitive name)
443
+ icase_matches=matching_items.select{|i|i['name'].casecmp?(entity_name)}
444
+ case icase_matches.length
445
+ when 1; return icase_matches.first
446
+ when 0; raise "#{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."
447
+ else raise "Two entities cannot have the same case insensitive name: #{icase_matches.map{|i|i['name']}}"
448
+ end
449
+ end
450
+ end
451
+
431
452
  end # AoC
432
453
  end # Aspera
data/lib/aspera/ascmd.rb CHANGED
@@ -62,19 +62,19 @@ module Aspera
62
62
  # description of result structures (see ascmdtypes.h). Base types are big endian
63
63
  # key = name of type
64
64
  TYPES_DESCR={
65
- :result =>{:decode=>:field_list,:fields=>[{:name=>:file,:is_a=>:stat},{:name=>:dir,:is_a=>:stat,:special=>:substruct},{:name=>:size,:is_a=>:size},{:name=>:error,:is_a=>:error},{:name=>:info,:is_a=>:info},{:name=>:success,:is_a=>nil,:special=>:return_true},{:name=>:exit,:is_a=>nil},{:name=>:df,:is_a=>:mnt,:special=>:restart_on_first},{:name=>:md5sum,:is_a=>:md5sum}]},
66
- :stat =>{:decode=>:field_list,:fields=>[{:name=>:name,:is_a=>:zstr},{:name=>:size,:is_a=>:int64},{:name=>:mode,:is_a=>:int32,:check=>nil},{:name=>:zmode,:is_a=>:zstr},{:name=>:uid,:is_a=>:int32,:check=>nil},{:name=>:zuid,:is_a=>:zstr},{:name=>:gid,:is_a=>:int32,:check=>nil},{:name=>:zgid,:is_a=>:zstr},{:name=>:ctime,:is_a=>:epoch},{:name=>:zctime,:is_a=>:zstr},{:name=>:mtime,:is_a=>:epoch},{:name=>:zmtime,:is_a=>:zstr},{:name=>:atime,:is_a=>:epoch},{:name=>:zatime,:is_a=>:zstr},{:name=>:symlink,:is_a=>:zstr},{:name=>:errno,:is_a=>:int32},{:name=>:errstr,:is_a=>:zstr}]},
67
- :info =>{:decode=>:field_list,:fields=>[{:name=>:platform,:is_a=>:zstr},{:name=>:version,:is_a=>:zstr},{:name=>:lang,:is_a=>:zstr},{:name=>:territory,:is_a=>:zstr},{:name=>:codeset,:is_a=>:zstr},{:name=>:lc_ctype,:is_a=>:zstr},{:name=>:lc_numeric,:is_a=>:zstr},{:name=>:lc_time,:is_a=>:zstr},{:name=>:lc_all,:is_a=>:zstr},{:name=>:dev,:is_a=>:zstr,:special=>:multiple},{:name=>:browse_caps,:is_a=>:zstr},{:name=>:protocol,:is_a=>:zstr}]},
68
- :size =>{:decode=>:field_list,:fields=>[{:name=>:size,:is_a=>:int64},{:name=>:fcount,:is_a=>:int32},{:name=>:dcount,:is_a=>:int32},{:name=>:failed_fcount,:is_a=>:int32},{:name=>:failed_dcount,:is_a=>:int32}]},
69
- :error =>{:decode=>:field_list,:fields=>[{:name=>:errno,:is_a=>:int32},{:name=>:errstr,:is_a=>:zstr}]},
70
- :mnt =>{:decode=>:field_list,:fields=>[{:name=>:fs,:is_a=>:zstr},{:name=>:dir,:is_a=>:zstr},{:name=>:is_a,:is_a=>:zstr},{:name=>:total,:is_a=>:int64},{:name=>:used,:is_a=>:int64},{:name=>:free,:is_a=>:int64},{:name=>:fcount,:is_a=>:int64},{:name=>:errno,:is_a=>:int32},{:name=>:errstr,:is_a=>:zstr}]},
71
- :md5sum =>{:decode=>:field_list,:fields=>[{:name=>:md5sum,:is_a=>:zstr}]},
72
- :int8 =>{:decode=>:base,:unpack=>'C',:size=>1},
73
- :int32 =>{:decode=>:base,:unpack=>'L>',:size=>4},
74
- :int64 =>{:decode=>:base,:unpack=>'Q>',:size=>8},
75
- :epoch =>{:decode=>:base,:unpack=>'Q>',:size=>8},
76
- :zstr =>{:decode=>:base,:unpack=>'Z*'},
77
- :blist =>{:decode=>:buffer_list}
65
+ result: {decode: :field_list,fields: [{name: :file,is_a: :stat},{name: :dir,is_a: :stat,special: :substruct},{name: :size,is_a: :size},{name: :error,is_a: :error},{name: :info,is_a: :info},{name: :success,is_a: nil,special: :return_true},{name: :exit,is_a: nil},{name: :df,is_a: :mnt,special: :restart_on_first},{name: :md5sum,is_a: :md5sum}]},
66
+ stat: {decode: :field_list,fields: [{name: :name,is_a: :zstr},{name: :size,is_a: :int64},{name: :mode,is_a: :int32,check: nil},{name: :zmode,is_a: :zstr},{name: :uid,is_a: :int32,check: nil},{name: :zuid,is_a: :zstr},{name: :gid,is_a: :int32,check: nil},{name: :zgid,is_a: :zstr},{name: :ctime,is_a: :epoch},{name: :zctime,is_a: :zstr},{name: :mtime,is_a: :epoch},{name: :zmtime,is_a: :zstr},{name: :atime,is_a: :epoch},{name: :zatime,is_a: :zstr},{name: :symlink,is_a: :zstr},{name: :errno,is_a: :int32},{name: :errstr,is_a: :zstr}]},
67
+ info: {decode: :field_list,fields: [{name: :platform,is_a: :zstr},{name: :version,is_a: :zstr},{name: :lang,is_a: :zstr},{name: :territory,is_a: :zstr},{name: :codeset,is_a: :zstr},{name: :lc_ctype,is_a: :zstr},{name: :lc_numeric,is_a: :zstr},{name: :lc_time,is_a: :zstr},{name: :lc_all,is_a: :zstr},{name: :dev,is_a: :zstr,special: :multiple},{name: :browse_caps,is_a: :zstr},{name: :protocol,is_a: :zstr}]},
68
+ size: {decode: :field_list,fields: [{name: :size,is_a: :int64},{name: :fcount,is_a: :int32},{name: :dcount,is_a: :int32},{name: :failed_fcount,is_a: :int32},{name: :failed_dcount,is_a: :int32}]},
69
+ error: {decode: :field_list,fields: [{name: :errno,is_a: :int32},{name: :errstr,is_a: :zstr}]},
70
+ mnt: {decode: :field_list,fields: [{name: :fs,is_a: :zstr},{name: :dir,is_a: :zstr},{name: :is_a,is_a: :zstr},{name: :total,is_a: :int64},{name: :used,is_a: :int64},{name: :free,is_a: :int64},{name: :fcount,is_a: :int64},{name: :errno,is_a: :int32},{name: :errstr,is_a: :zstr}]},
71
+ md5sum: {decode: :field_list,fields: [{name: :md5sum,is_a: :zstr}]},
72
+ int8: {decode: :base,unpack: 'C',size: 1},
73
+ int32: {decode: :base,unpack: 'L>',size: 4},
74
+ int64: {decode: :base,unpack: 'Q>',size: 8},
75
+ epoch: {decode: :base,unpack: 'Q>',size: 8},
76
+ zstr: {decode: :base,unpack: 'Z*'},
77
+ blist: {decode: :buffer_list}
78
78
  }
79
79
 
80
80
  # protocol enum start at one, but array index start at zero
@@ -113,7 +113,7 @@ module Aspera
113
113
  length=parse(buffer,:int32,indent_level)
114
114
  raise "ERROR:not enough bytes" if buffer.length < length
115
115
  value=buffer.shift(length)
116
- result.push({:btype=>btype,:buffer=>value})
116
+ result.push({btype: btype,buffer: value})
117
117
  Log.log.debug("#{" ."*indent_level}:buffer_list[#{result.length-1}] #{result.last}")
118
118
  end
119
119
  when :field_list
@@ -19,18 +19,21 @@ module Aspera
19
19
  CSV_FIELD_SEPARATOR=","
20
20
 
21
21
  private_constant :FIELDS_ALL,:FIELDS_DEFAULT,:DISPLAY_FORMATS,:DISPLAY_LEVELS,:CSV_RECORD_SEPARATOR,:CSV_FIELD_SEPARATOR
22
- attr_accessor :option_flat_hash
22
+ attr_accessor :option_flat_hash,:option_transpose_single
23
23
 
24
24
  def initialize(opt_mgr)
25
25
  @option_flat_hash=true
26
+ @option_transpose_single=true
26
27
  @opt_mgr=opt_mgr
27
28
  @opt_mgr.set_obj_attr(:flat_hash,self,:option_flat_hash)
29
+ @opt_mgr.set_obj_attr(:transpose_single,self,:option_transpose_single)
28
30
  @opt_mgr.add_opt_list(:format,DISPLAY_FORMATS,"output format")
29
31
  @opt_mgr.add_opt_list(:display,DISPLAY_LEVELS,"output only some information")
30
32
  @opt_mgr.add_opt_simple(:fields,"comma separated list of fields, or #{FIELDS_ALL}, or #{FIELDS_DEFAULT}")
31
33
  @opt_mgr.add_opt_simple(:select,"select only some items in lists, extended value: hash (column, value)")
32
34
  @opt_mgr.add_opt_simple(:table_style,"table display style")
33
35
  @opt_mgr.add_opt_boolean(:flat_hash,"display hash values as additional keys")
36
+ @opt_mgr.add_opt_boolean(:transpose_single,"single object fields output vertically")
34
37
  @opt_mgr.set_option(:format,:table)
35
38
  @opt_mgr.set_option(:display,:info)
36
39
  @opt_mgr.set_option(:fields,FIELDS_DEFAULT)
@@ -134,6 +137,10 @@ module Aspera
134
137
  when :yaml
135
138
  display_message(:data,res_data.to_yaml)
136
139
  when :table,:csv
140
+ if !@option_transpose_single and results[:type].eql?(:single_object)
141
+ results[:type]=:object_list
142
+ res_data=[res_data]
143
+ end
137
144
  case results[:type]
138
145
  when :object_list # goes to table display
139
146
  raise "internal error: unexpected type: #{res_data.class}, expecting Array" unless res_data.is_a?(Array)
@@ -42,7 +42,7 @@ module Aspera
42
42
  def set_http_parameters(http)
43
43
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @option_insecure
44
44
  http.set_debug_output($stdout) if @option_rest_debug
45
- raise "http_options expects Hash" unless @option_http_options.is_a?(Hash)
45
+ raise 'http_options expects Hash' unless @option_http_options.is_a?(Hash)
46
46
  @option_http_options.each do |k,v|
47
47
  method="#{k}=".to_sym
48
48
  # check if accessor is a method of Net::HTTP
@@ -122,11 +122,11 @@ module Aspera
122
122
 
123
123
  # define header for manual
124
124
  def init_global_options
125
- Log.log.debug("init_global_options")
126
- @opt_mgr.add_opt_switch(:help,"-h","Show this message.") { @option_help=true }
127
- @opt_mgr.add_opt_switch(:bash_comp,"generate bash completion for command") { @bash_completion=true }
128
- @opt_mgr.add_opt_switch(:show_config, "Display parameters used for the provided action.") { @option_show_config=true }
129
- @opt_mgr.add_opt_switch(:rest_debug,"-r","more debug for HTTP calls") { @option_rest_debug=true }
125
+ Log.log.debug('init_global_options')
126
+ @opt_mgr.add_opt_switch(:help,'-h','Show this message.') { @option_help=true }
127
+ @opt_mgr.add_opt_switch(:bash_comp,'generate bash completion for command') { @bash_completion=true }
128
+ @opt_mgr.add_opt_switch(:show_config, 'Display parameters used for the provided action.') { @option_show_config=true }
129
+ @opt_mgr.add_opt_switch(:rest_debug,'-r','more debug for HTTP calls') { @option_rest_debug=true }
130
130
  @opt_mgr.add_opt_switch(:version,'-v','display version') { @plugin_env[:formater].display_message(:data,Aspera::Cli::VERSION);Process.exit(0) }
131
131
  @opt_mgr.add_opt_switch(:warnings,'-w','check for language warnings') { $VERBOSE=true }
132
132
  # handler must be set before declaration
@@ -137,14 +137,14 @@ module Aspera
137
137
  @opt_mgr.set_obj_attr(:http_options,self,:option_http_options)
138
138
  @opt_mgr.set_obj_attr(:log_passwords,Log.instance,:log_passwords)
139
139
  @opt_mgr.add_opt_list(:ui,OpenApplication.user_interfaces,'method to start browser')
140
- @opt_mgr.add_opt_list(:log_level,Log.levels,"Log level")
141
- @opt_mgr.add_opt_list(:logger,Log.logtypes,"log method")
142
- @opt_mgr.add_opt_simple(:lock_port,"prevent dual execution of a command, e.g. in cron")
143
- @opt_mgr.add_opt_simple(:query,"additional filter for API calls (extended value) (some commands)")
144
- @opt_mgr.add_opt_simple(:http_options,"options for http socket (extended value)")
145
- @opt_mgr.add_opt_boolean(:insecure,"do not validate HTTPS certificate")
146
- @opt_mgr.add_opt_boolean(:once_only,"process only new items (some commands)")
147
- @opt_mgr.add_opt_boolean(:log_passwords,"show passwords in logs")
140
+ @opt_mgr.add_opt_list(:log_level,Log.levels,'Log level')
141
+ @opt_mgr.add_opt_list(:logger,Log.logtypes,'log method')
142
+ @opt_mgr.add_opt_simple(:lock_port,'prevent dual execution of a command, e.g. in cron')
143
+ @opt_mgr.add_opt_simple(:query,'additional filter for API calls (extended value) (some commands)')
144
+ @opt_mgr.add_opt_simple(:http_options,'options for http socket (extended value)')
145
+ @opt_mgr.add_opt_boolean(:insecure,'do not validate HTTPS certificate')
146
+ @opt_mgr.add_opt_boolean(:once_only,'process only new items (some commands)')
147
+ @opt_mgr.add_opt_boolean(:log_passwords,'show passwords in logs')
148
148
  @opt_mgr.set_option(:ui,OpenApplication.default_gui_mode)
149
149
  @opt_mgr.set_option(:once_only,:false)
150
150
  # parse declared options
@@ -167,10 +167,10 @@ module Aspera
167
167
  end
168
168
 
169
169
  def generate_bash_completion
170
- if @opt_mgr.get_next_argument("",:multiple,:optional).nil?
170
+ if @opt_mgr.get_next_argument('',:multiple,:optional).nil?
171
171
  @plugin_env[:config].plugins.keys.each{|p|puts p.to_s}
172
172
  else
173
- Log.log.warn("only first level completion so far")
173
+ Log.log.warn('only first level completion so far')
174
174
  end
175
175
  Process.exit(0)
176
176
  end
@@ -186,7 +186,7 @@ module Aspera
186
186
  def self.result_success; return result_status('complete'); end
187
187
 
188
188
  def exit_with_usage(all_plugins)
189
- Log.log.debug("exit_with_usage".bg_red)
189
+ Log.log.debug('exit_with_usage'.bg_red)
190
190
  # display main plugin options
191
191
  @plugin_env[:formater].display_message(:error,@opt_mgr.parser)
192
192
  if all_plugins
@@ -288,6 +288,8 @@ module Aspera
288
288
  else
289
289
  command_sym=@opt_mgr.get_next_command(@plugin_env[:config].plugins.keys.dup.unshift(:help))
290
290
  end
291
+ # command will not be executed, but we need manual
292
+ @opt_mgr.fail_on_missing_mandatory=false if @option_help
291
293
  # main plugin is not dynamically instanciated
292
294
  case command_sym
293
295
  when :help
@@ -335,8 +337,8 @@ module Aspera
335
337
  TempFileManager.instance.cleanup
336
338
  # 1- processing of error condition
337
339
  unless exception_info.nil?
338
- @plugin_env[:formater].display_message(:error,"ERROR:".bg_red.gray.blink+" "+exception_info[1]+": "+exception_info[0].message)
339
- @plugin_env[:formater].display_message(:error,"Use '-h' option to get help.") if exception_info[2].eql?(:usage)
340
+ @plugin_env[:formater].display_message(:error,"#{'ERROR:'.bg_red.gray.blink} #{exception_info[1]}: #{exception_info[0].message}")
341
+ @plugin_env[:formater].display_message(:error,'Use option -h to get help.') if exception_info[2].eql?(:usage)
340
342
  if exception_info.first.is_a?(Fasp::Error) and exception_info.first.message.eql?('Remote host is not who we expected')
341
343
  @plugin_env[:formater].display_message(:error,"For this specific error, refer to:\n#{SRC_URL}#error-remote-host-is-not-who-we-expected\nAdd this to arguments:\n--ts=@json:'{\"sshfp\":null}'")
342
344
  end
@@ -344,7 +346,7 @@ module Aspera
344
346
  # 2- processing of command not processed (due to exception or bad command line)
345
347
  if execute_command
346
348
  @opt_mgr.final_errors.each do |msg|
347
- @plugin_env[:formater].display_message(:error,"ERROR:".bg_red.gray.blink+" Argument: "+msg)
349
+ @plugin_env[:formater].display_message(:error,"#{'ERROR:'.bg_red.gray.blink} Argument: #{msg}")
348
350
  # add code as exception if there is not already an error
349
351
  exception_info=[Exception.new(msg),'UnusedArg'] if exception_info.nil?
350
352
  end
@@ -355,7 +357,7 @@ module Aspera
355
357
  # will force to show stack trace
356
358
  raise exception_info[0]
357
359
  else
358
- @plugin_env[:formater].display_message(:error,"Use '--log-level=debug' to get more details.") if exception_info[2].eql?(:debug)
360
+ @plugin_env[:formater].display_message(:error,'Use --log-level=debug to get more details.') if exception_info[2].eql?(:debug)
359
361
  Process.exit(1)
360
362
  end
361
363
  end
@@ -77,6 +77,7 @@ module Aspera
77
77
  attr_reader :parser
78
78
  attr_accessor :ask_missing_mandatory
79
79
  attr_accessor :ask_missing_optional
80
+ attr_writer :fail_on_missing_mandatory
80
81
 
81
82
  #
82
83
  def initialize(program_name,argv=nil)
@@ -92,6 +93,7 @@ module Aspera
92
93
  @ask_missing_mandatory=false # STDIN.isatty
93
94
  # ask optional options if not provided and in interactive
94
95
  @ask_missing_optional=false
96
+ @fail_on_missing_mandatory=true
95
97
  # those must be set before parse, parse consumes those defined only
96
98
  @unprocessed_defaults=[]
97
99
  @unprocessed_env=[]
@@ -232,7 +234,7 @@ module Aspera
232
234
  def set_option(option_symbol,value,where="default")
233
235
  if ! @declared_options.has_key?(option_symbol)
234
236
  Log.log.debug("set unknown option: #{option_symbol}")
235
- raise "ERROR"
237
+ raise "ERROR: cannot set undeclared option"
236
238
  #declare_option(option_symbol)
237
239
  end
238
240
  value=ExtendedValue.instance.evaluate(value)
@@ -267,6 +269,8 @@ module Aspera
267
269
  end
268
270
  Log.log.debug("get #{option_symbol} (#{@declared_options[option_symbol][:type]}) : #{result}")
269
271
  end
272
+ # do not fail for manual generation if option mandatory but not set
273
+ result||='' unless @fail_on_missing_mandatory
270
274
  Log.log.debug("interactive=#{@ask_missing_mandatory}")
271
275
  if result.nil?
272
276
  if !@ask_missing_mandatory
@@ -372,34 +372,34 @@ module Aspera
372
372
  return {type: :object_list,data: result_list,fields: [id_result,'status']}
373
373
  end
374
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
375
+ # get identifier or name from command line
376
+ # @return identifier
377
+ def get_resource_id_from_args(resource_class_path)
378
+ l_res_id=self.options.get_option(:id)
379
+ l_res_name=self.options.get_option(:name)
380
+ raise "Provide either option id or name, not both" unless l_res_id.nil? or l_res_name.nil?
381
+ # try to find item by name (single partial match or exact match)
382
+ l_res_id=@api_aoc.lookup_entity_by_name(resource_class_path,l_res_name)['id'] unless l_res_name.nil?
383
+ # if no name or id option, taken on command line (after command)
384
+ if l_res_id.nil?
385
+ l_res_id=self.options.get_next_argument('identifier')
386
+ l_res_id=@api_aoc.lookup_entity_by_name(resource_class_path,self.options.get_next_argument('identifier'))['id'] if l_res_id.eql?('name')
392
387
  end
388
+ return l_res_id
389
+ end
390
+
391
+ def get_resource_path_from_args(resource_class_path)
392
+ return "#{resource_class_path}/#{get_resource_id_from_args(resource_class_path)}"
393
393
  end
394
394
 
395
395
  # package creation params can give just email, and full hash is created
396
- def resolve_package_recipients(package_creation,recipient_list_field)
397
- return unless package_creation.has_key?(recipient_list_field)
398
- raise CliBadArgument,"#{recipient_list_field} must be an Array" unless package_creation[recipient_list_field].is_a?(Array)
396
+ def resolve_package_recipients(package_data,recipient_list_field)
397
+ return unless package_data.has_key?(recipient_list_field)
398
+ raise CliBadArgument,"#{recipient_list_field} must be an Array" unless package_data[recipient_list_field].is_a?(Array)
399
399
  new_user_option=self.options.get_option(:new_user_option,:mandatory)
400
400
  # list with resolved elements
401
401
  resolved_list=[]
402
- package_creation[recipient_list_field].each do |short_recipient_info|
402
+ package_data[recipient_list_field].each do |short_recipient_info|
403
403
  case short_recipient_info
404
404
  when Hash # native api information, check keys
405
405
  raise "#{recipient_list_field} element shall have fields: id and type" unless short_recipient_info.keys.sort.eql?(['id','type'])
@@ -407,9 +407,9 @@ module Aspera
407
407
  # email: user, else dropbox
408
408
  entity_type=short_recipient_info.include?('@') ? 'contacts' : 'dropboxes'
409
409
  begin
410
- full_recipient_info=lookup_single(entity_type,short_recipient_info,{'current_workspace_id'=>@workspace_id})
410
+ full_recipient_info=@api_aoc.lookup_entity_by_name(entity_type,short_recipient_info,{'current_workspace_id'=>@workspace_id})
411
411
  rescue RuntimeError => e
412
- raise e unless e.message.eql?(NOT_FOUND)
412
+ raise e unless e.message.eql?('not found')
413
413
  if entity_type.eql?('contacts')
414
414
  full_recipient_info=@api_aoc.create('contacts',{'current_workspace_id'=>@workspace_id,'email'=>short_recipient_info}.merge(new_user_option))[:data]
415
415
  else
@@ -428,7 +428,27 @@ module Aspera
428
428
  resolved_list.push(short_recipient_info)
429
429
  end
430
430
  # replace with resolved elements
431
- package_creation[recipient_list_field]=resolved_list
431
+ package_data[recipient_list_field]=resolved_list
432
+ end
433
+
434
+ def normalize_metadata(pkg_data)
435
+ case pkg_data['metadata']
436
+ when NilClass;return
437
+ when Array;return
438
+ when Hash
439
+ api_meta=[]
440
+ pkg_data['metadata'].each do |k,v|
441
+ api_meta.push({
442
+ #'input_type' => 'single-dropdown',
443
+ 'name' => k,
444
+ 'values' => v.is_a?(Array) ? v : [v]
445
+ })
446
+ end
447
+ pkg_data['metadata']=api_meta
448
+
449
+ else raise "metadata field if not of expected type: #{pkg_meta.class}"
450
+ end
451
+ nil
432
452
  end
433
453
 
434
454
  # private
@@ -623,16 +643,7 @@ module Aspera
623
643
  command=self.options.get_next_command(supported_operations)
624
644
  # require identifier for non global commands
625
645
  if !singleton_object and !global_operations.include?(command)
626
- res_id=self.options.get_option(:id)
627
- res_name=self.options.get_option(:name)
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')
635
- end
646
+ res_id=get_resource_id_from_args(resource_class_path)
636
647
  resource_instance_path="#{resource_class_path}/#{res_id}"
637
648
  end
638
649
  resource_instance_path=resource_class_path if singleton_object
@@ -697,7 +708,7 @@ module Aspera
697
708
  when :node;{'include'=>['[]','access_level','permission_count'],'created_by_id'=>ID_AK_ADMIN}
698
709
  else raise 'error'
699
710
  end
700
- res_data=@api_aoc.read("#{resource_class_path}/#{res_id}/permissions",read_params)[:data]
711
+ res_data=@api_aoc.read("#{resource_instance_path}/permissions",read_params)[:data]
701
712
  fields=case resource_type
702
713
  when :node;['id','file_id','file.path','access_type']
703
714
  when :workspace;['id','node_id','file_id','node_name','file.path','tags.aspera.files.workspace.share_as']
@@ -743,10 +754,11 @@ module Aspera
743
754
  end
744
755
 
745
756
  # must be public
746
- ACTIONS=[ :reminder, :bearer_token, :organization, :tier_restrictions, :user, :workspace, :packages, :files, :gateway, :admin, :automation, :servers].freeze
757
+ ACTIONS=[ :reminder, :servers, :bearer_token, :organization, :tier_restrictions, :user, :packages, :files, :admin, :automation, :gateway].freeze
747
758
 
748
759
  def execute_action
749
760
  command=self.options.get_next_command(ACTIONS)
761
+ # all commands require to login, but those 2
750
762
  get_api unless [:reminder,:servers].include?(command)
751
763
  case command
752
764
  when :reminder
@@ -754,6 +766,8 @@ module Aspera
754
766
  user_email=options.get_option(:username,:mandatory)
755
767
  Rest.new(base_url: "#{AoC.api_base_url}/#{AoC::API_V1}").create('organization_reminders',{email: user_email})[:data]
756
768
  return Main.result_status("List of organizations user is member of, has been sent by e-mail to #{user_email}")
769
+ when :servers
770
+ return {type: :object_list,data: Rest.new(base_url: "#{AoC.api_base_url}/#{AoC::API_V1}").read('servers')[:data]}
757
771
  when :bearer_token
758
772
  return {type: :text,data: @api_aoc.oauth_token}
759
773
  when :organization
@@ -761,22 +775,19 @@ module Aspera
761
775
  when :tier_restrictions
762
776
  return { type: :single_object, data: @api_aoc.read('tier_restrictions')[:data] }
763
777
  when :user
764
- command=self.options.get_next_command([ :workspaces,:info,:shared_inboxes ])
765
- case command
778
+ case self.options.get_next_command([ :workspaces,:profile ])
779
+ # when :settings
780
+ # return {type: :object_list,data: @api_aoc.read('client_settings/')[:data]}
766
781
  when :workspaces
767
- return {type: :object_list,data: @api_aoc.read('workspaces')[:data],fields: ['id','name']}
768
- # when :settings
769
- # return {type: :object_list,data: @api_aoc.read('client_settings/')[:data]}
770
- when :shared_inboxes
771
- query=option_url_query(nil)
772
- if query.nil?
782
+ case self.options.get_next_command([ :list,:current ])
783
+ when :list
784
+ return {type: :object_list,data: @api_aoc.read('workspaces')[:data],fields: ['id','name']}
785
+ when :current
773
786
  set_workspace_info
774
- query={'embed[]'=>'dropbox','workspace_id'=>@workspace_id,'aggregate_permissions_by_dropbox'=>true,'sort'=>'dropbox_name'}
787
+ return { type: :single_object, data: @workspace_data }
775
788
  end
776
- return {type: :object_list,data: @api_aoc.read('dropbox_memberships',query)[:data],fields: ['dropbox_id','dropbox.name']}
777
- when :info
778
- command=self.options.get_next_command([ :show,:modify ])
779
- case command
789
+ when :profile
790
+ case self.options.get_next_command([ :show,:modify ])
780
791
  when :show
781
792
  return { type: :single_object, data: @api_aoc.user_info }
782
793
  when :modify
@@ -784,45 +795,55 @@ module Aspera
784
795
  return Main.result_status('modified')
785
796
  end
786
797
  end
787
- when :workspace # show current workspace parameters
788
- set_workspace_info
789
- return { type: :single_object, data: @workspace_data }
790
798
  when :packages
791
799
  set_workspace_info if @url_token_data.nil?
792
- command_pkg=self.options.get_next_command([ :send, :recv, :list, :show, :delete ])
793
- case command_pkg
800
+ case self.options.get_next_command([ :shared_inboxes, :send, :recv, :list, :show, :delete ])
801
+ when :shared_inboxes
802
+ case self.options.get_next_command([ :list, :show ])
803
+ when :list
804
+ query=option_url_query(nil)
805
+ if query.nil?
806
+ query={'embed[]'=>'dropbox','workspace_id'=>@workspace_id,'aggregate_permissions_by_dropbox'=>true,'sort'=>'dropbox_name'}
807
+ end
808
+ return {type: :object_list,data: @api_aoc.read('dropbox_memberships',query)[:data],fields: ['dropbox_id','dropbox.name']}
809
+ when :show
810
+ return {type: :single_object,data: @api_aoc.read(get_resource_path_from_args('dropboxes'),query)[:data]}
811
+ end
794
812
  when :send
795
- package_creation=self.options.get_option(:value,:mandatory)
796
- raise CliBadArgument,'value must be hash, refer to doc' unless package_creation.is_a?(Hash)
813
+ package_data=self.options.get_option(:value,:mandatory)
814
+ raise CliBadArgument,'value must be hash, refer to doc' unless package_data.is_a?(Hash)
797
815
 
798
816
  if !@url_token_data.nil?
799
817
  assert_public_link_types(['send_package_to_user','send_package_to_dropbox'])
800
818
  box_type=@url_token_data['purpose'].split('_').last
801
- package_creation['recipients']=[{'id'=>@url_token_data['data']["#{box_type}_id"],'type'=>box_type}]
819
+ package_data['recipients']=[{'id'=>@url_token_data['data']["#{box_type}_id"],'type'=>box_type}]
802
820
  @workspace_id=@url_token_data['data']['workspace_id']
803
821
  end
804
822
 
805
- package_creation['workspace_id']=@workspace_id
823
+ package_data['workspace_id']=@workspace_id
806
824
 
807
825
  # list of files to include in package, optional
808
- #package_creation['file_names']=self.transfer.ts_source_paths.map{|i|File.basename(i['source'])}
826
+ #package_data['file_names']=self.transfer.ts_source_paths.map{|i|File.basename(i['source'])}
809
827
 
810
828
  # lookup users
811
- resolve_package_recipients(package_creation,'recipients')
812
- resolve_package_recipients(package_creation,'bcc_recipients')
829
+ resolve_package_recipients(package_data,'recipients')
830
+ resolve_package_recipients(package_data,'bcc_recipients')
831
+ normalize_metadata(package_data)
813
832
 
814
833
  # create a new package container
815
- package_info=@api_aoc.create('packages',package_creation)[:data]
834
+ package_info=@api_aoc.create('packages',package_data)[:data]
816
835
 
817
836
  # get node information for the node on which package must be created
818
837
  node_info=@api_aoc.read("nodes/#{package_info['node_id']}")[:data]
819
838
 
820
- # tell Aspera what to expect in package: 1 transfer (can also be done after transfer)
839
+ # tell AoC what to expect in package: 1 transfer (can also be done after transfer)
840
+ # TODO: if multisession was used we should probably tell
841
+ # also, currently no "multi-source" , i.e. only from client-side files, unless "node" agent is used
821
842
  @api_aoc.update("packages/#{package_info['id']}",{'sent'=>true,'transfers_expected'=>1})[:data]
822
843
 
823
- # execute transfer
844
+ # get destination: package folder
824
845
  node_file = {node_info: node_info, file_id: package_info['contents_file_id']}
825
- # raise exception if at least one error
846
+ # execute transfer, raise exception if at least one error
826
847
  Main.result_transfer(transfer_start(AoC::PACKAGES_APP,'send',node_file,AoC.package_tags(package_info,'upload')))
827
848
  # return all info on package
828
849
  return { type: :single_object, data: package_info}
@@ -874,6 +895,12 @@ module Aspera
874
895
  return { type: :single_object, data: package_info }
875
896
  when :list
876
897
  query=option_url_query({'archived'=>false,'exclude_dropbox_packages'=>true,'has_content'=>true,'received'=>true})
898
+ if query.has_key?('dropbox_name')
899
+ # convenience: specify name instead of id
900
+ raise 'not both dropbox_name and dropbox_id' if query.has_key?('dropbox_id')
901
+ query['dropbox_id']=@api_aoc.lookup_entity_by_name('dropboxes',query['dropbox_name'])['id']
902
+ query.delete('dropbox_name')
903
+ end
877
904
  raise 'option must be Hash' unless query.is_a?(Hash)
878
905
  query['workspace_id']||=@workspace_id
879
906
  packages=@api_aoc.read('packages',query)[:data]
@@ -991,14 +1018,12 @@ module Aspera
991
1018
  return {type: :single_object,data: wf}
992
1019
  end
993
1020
  end
1021
+ when :admin
1022
+ return execute_admin_action
994
1023
  when :gateway
995
1024
  set_workspace_info
996
1025
  require 'aspera/faspex_gw'
997
1026
  FaspexGW.new(@api_aoc,@workspace_id).start_server
998
- when :admin
999
- return execute_admin_action
1000
- when :servers
1001
- return {type: :object_list,data: Rest.new(base_url: "#{AoC.api_base_url}/#{AoC::API_V1}").read('servers')[:data]}
1002
1027
  else
1003
1028
  raise "internal error: #{command}"
1004
1029
  end # action