aspera-cli 4.13.0 → 4.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +28 -5
  4. data/CONTRIBUTING.md +17 -1
  5. data/README.md +782 -401
  6. data/examples/dascli +1 -1
  7. data/examples/rubyc +24 -0
  8. data/lib/aspera/aoc.rb +21 -32
  9. data/lib/aspera/ascmd.rb +1 -0
  10. data/lib/aspera/cli/basic_auth_plugin.rb +6 -6
  11. data/lib/aspera/cli/formatter.rb +17 -25
  12. data/lib/aspera/cli/main.rb +21 -27
  13. data/lib/aspera/cli/manager.rb +128 -114
  14. data/lib/aspera/cli/plugin.rb +87 -38
  15. data/lib/aspera/cli/plugins/alee.rb +2 -2
  16. data/lib/aspera/cli/plugins/aoc.rb +216 -102
  17. data/lib/aspera/cli/plugins/ats.rb +16 -18
  18. data/lib/aspera/cli/plugins/bss.rb +3 -3
  19. data/lib/aspera/cli/plugins/config.rb +177 -367
  20. data/lib/aspera/cli/plugins/console.rb +4 -6
  21. data/lib/aspera/cli/plugins/cos.rb +12 -13
  22. data/lib/aspera/cli/plugins/faspex.rb +17 -18
  23. data/lib/aspera/cli/plugins/faspex5.rb +332 -216
  24. data/lib/aspera/cli/plugins/node.rb +171 -142
  25. data/lib/aspera/cli/plugins/orchestrator.rb +15 -18
  26. data/lib/aspera/cli/plugins/preview.rb +38 -60
  27. data/lib/aspera/cli/plugins/server.rb +22 -15
  28. data/lib/aspera/cli/plugins/shares.rb +24 -33
  29. data/lib/aspera/cli/plugins/sync.rb +3 -3
  30. data/lib/aspera/cli/transfer_agent.rb +29 -26
  31. data/lib/aspera/cli/version.rb +1 -1
  32. data/lib/aspera/colors.rb +9 -7
  33. data/lib/aspera/data/6 +0 -0
  34. data/lib/aspera/environment.rb +7 -3
  35. data/lib/aspera/fasp/agent_connect.rb +5 -0
  36. data/lib/aspera/fasp/agent_direct.rb +5 -5
  37. data/lib/aspera/fasp/agent_httpgw.rb +138 -60
  38. data/lib/aspera/fasp/agent_trsdk.rb +2 -0
  39. data/lib/aspera/fasp/error_info.rb +2 -0
  40. data/lib/aspera/fasp/installation.rb +18 -19
  41. data/lib/aspera/fasp/parameters.rb +18 -17
  42. data/lib/aspera/fasp/parameters.yaml +2 -1
  43. data/lib/aspera/fasp/resume_policy.rb +3 -3
  44. data/lib/aspera/fasp/transfer_spec.rb +6 -5
  45. data/lib/aspera/fasp/uri.rb +23 -21
  46. data/lib/aspera/faspex_postproc.rb +1 -1
  47. data/lib/aspera/hash_ext.rb +12 -2
  48. data/lib/aspera/keychain/macos_security.rb +13 -13
  49. data/lib/aspera/log.rb +1 -0
  50. data/lib/aspera/node.rb +62 -80
  51. data/lib/aspera/oauth.rb +1 -1
  52. data/lib/aspera/persistency_action_once.rb +1 -1
  53. data/lib/aspera/preview/terminal.rb +61 -15
  54. data/lib/aspera/preview/utils.rb +3 -3
  55. data/lib/aspera/proxy_auto_config.js +2 -2
  56. data/lib/aspera/rest.rb +37 -0
  57. data/lib/aspera/secret_hider.rb +6 -1
  58. data/lib/aspera/ssh.rb +1 -1
  59. data/lib/aspera/sync.rb +2 -0
  60. data.tar.gz.sig +0 -0
  61. metadata +3 -4
  62. metadata.gz.sig +0 -0
  63. data/docs/test_env.conf +0 -186
  64. data/lib/aspera/data/7 +0 -0
@@ -54,43 +54,42 @@ module Aspera
54
54
  # used to trigger periodic processing
55
55
  @periodic = TimerLimiter.new(LOG_LIMITER_SEC)
56
56
  # link CLI options to gen_info attributes
57
- options.set_obj_attr(:skip_format, self, :option_skip_format, []) # no skip
58
- options.set_obj_attr(:folder_reset_cache, self, :option_folder_reset_cache, :no)
59
- options.set_obj_attr(:skip_types, self, :option_skip_types)
60
- options.set_obj_attr(:previews_folder, self, :option_previews_folder, DEFAULT_PREVIEWS_FOLDER)
61
- options.set_obj_attr(:skip_folders, self, :option_skip_folders, []) # no skip
62
- options.set_obj_attr(:overwrite, self, :option_overwrite, :mtime)
63
- options.set_obj_attr(:file_access, self, :option_file_access, :local)
64
- options.add_opt_list(:skip_format, Aspera::Preview::Generator::PREVIEW_FORMATS, 'skip this preview format (multiple possible)')
65
- options.add_opt_list(:folder_reset_cache, %i[no header read], 'force detection of generated preview by refresh cache')
66
- options.add_opt_simple(:skip_types, 'skip types in comma separated list')
67
- options.add_opt_simple(:previews_folder, 'preview folder in storage root')
68
- options.add_opt_simple(:temp_folder, 'path to temp folder')
69
- options.add_opt_simple(:skip_folders, 'list of folder to skip')
70
- options.add_opt_simple(:case, 'basename of output for for test')
71
- options.add_opt_simple(:scan_path, 'subpath in folder id to start scan in (default=/)')
72
- options.add_opt_simple(:scan_id, 'folder id in storage to start scan in, default is access key main folder id')
73
- options.add_opt_boolean(:mimemagic, 'use Mime type detection of gem mimemagic')
74
- options.add_opt_list(:overwrite, %i[always never mtime], 'when to overwrite result file')
75
- options.add_opt_list(:file_access, %i[local remote], 'how to read and write files in repository')
76
- options.set_option(:temp_folder, Dir.tmpdir)
77
- options.set_option(:mimemagic, false)
57
+ options.declare(
58
+ :skip_format, 'Skip this preview format (multiple possible)', values: Aspera::Preview::Generator::PREVIEW_FORMATS,
59
+ handler: {o: self, m: :option_skip_format}, default: [])
60
+ options.declare(
61
+ :folder_reset_cache, 'Force detection of generated preview by refresh cache',
62
+ values: %i[no header read],
63
+ handler: {o: self, m: :option_folder_reset_cache},
64
+ default: :no)
65
+ options.declare(:skip_types, 'Skip types in comma separated list', handler: {o: self, m: :option_skip_types})
66
+ options.declare(:previews_folder, 'Preview folder in storage root', handler: {o: self, m: :option_previews_folder}, default: DEFAULT_PREVIEWS_FOLDER)
67
+ options.declare(:temp_folder, 'Path to temp folder', default: Dir.tmpdir)
68
+ options.declare(:skip_folders, 'List of folder to skip', handler: {o: self, m: :option_skip_folders}, default: [])
69
+ options.declare(:case, 'Basename of output for for test')
70
+ options.declare(:scan_path, 'Subpath in folder id to start scan in (default=/)')
71
+ options.declare(:scan_id, 'Folder id in storage to start scan in, default is access key main folder id')
72
+ options.declare(:mimemagic, 'Use Mime type detection of gem mimemagic', values: :bool, default: false)
73
+ options.declare(:overwrite, 'When to overwrite result file', values: %i[always never mtime], handler: {o: self, m: :option_overwrite}, default: :mtime)
74
+ options.declare(
75
+ :file_access, 'How to read and write files in repository',
76
+ values: %i[local remote],
77
+ handler: {o: self, m: :option_file_access},
78
+ default: :local)
78
79
 
79
80
  # add other options for generator (and set default values)
80
81
  Aspera::Preview::Options::DESCRIPTIONS.each do |opt|
81
- options.set_obj_attr(opt[:name], @gen_options, opt[:name], opt[:default])
82
- if opt.key?(:values)
83
- options.add_opt_list(opt[:name], opt[:values], opt[:description])
82
+ values = if opt.key?(:values)
83
+ opt[:values]
84
84
  elsif Cli::Manager::BOOLEAN_SIMPLE.include?(opt[:default])
85
- options.add_opt_boolean(opt[:name], opt[:description])
86
- else
87
- options.add_opt_simple(opt[:name], opt[:description])
85
+ :bool
88
86
  end
87
+ options.declare(opt[:name], opt[:description].capitalize, values: values, handler: {o: @gen_options, m: opt[:name]}, default: opt[:default])
89
88
  end
90
89
 
91
90
  options.parse_options!
92
91
  raise 'skip_folder shall be an Array, use @json:[...]' unless @option_skip_folders.is_a?(Array)
93
- @tmp_folder = File.join(options.get_option(:temp_folder, is_type: :mandatory), "#{TMP_DIR_PREFIX}.#{SecureRandom.uuid}")
92
+ @tmp_folder = File.join(options.get_option(:temp_folder, mandatory: true), "#{TMP_DIR_PREFIX}.#{SecureRandom.uuid}")
94
93
  FileUtils.mkdir_p(@tmp_folder)
95
94
  Log.log.debug{"tmpdir: #{@tmp_folder}"}
96
95
  end
@@ -206,34 +205,12 @@ module Aspera
206
205
  end
207
206
 
208
207
  def do_transfer(direction, folder_id, source_filename, destination='/')
209
- raise 'error' if destination.nil? && direction.eql?(Fasp::TransferSpec::DIRECTION_RECEIVE)
210
- if @default_transfer_spec.nil?
211
- # make a dummy call to get some default transfer parameters
212
- res = @api_node.create('files/upload_setup', {'transfer_requests' => [{'transfer_request' => {'paths' => [{}], 'destination_root' => '/'}}]})
213
- template_ts = res[:data]['transfer_specs'].first['transfer_spec']
214
- # get ports, anyway that should be 33001 for both. add remote_user ?
215
- @default_transfer_spec = %w[ssh_port fasp_port].each_with_object({}){|e, h|h[e] = template_ts[e]; }
216
- if !@default_transfer_spec['remote_user'].eql?(Aspera::Fasp::TransferSpec::ACCESS_KEY_TRANSFER_USER)
217
- Log.log.warn('remote_user shall be xfer')
218
- @default_transfer_spec['remote_user'] = Aspera::Fasp::TransferSpec::ACCESS_KEY_TRANSFER_USER
219
- end
220
- @api_node.ts_basic_token(@default_transfer_spec)
221
- # NOTE: we use the same address for ascp than for node api instead of the one from upload_setup
222
- # TODO: configurable ? useful ?
223
- @default_transfer_spec['remote_host'] = @transfer_server_address
224
- end
225
- t_spec = @default_transfer_spec.merge({
226
- 'direction' => direction,
227
- 'paths' => [{'source' => source_filename}],
228
- 'tags' => {
229
- Fasp::TransferSpec::TAG_RESERVED => {
230
- PREV_GEN_TAG => true,
231
- 'node' => {
232
- 'access_key' => @access_key_self['id'],
233
- 'file_id' => folder_id }}}
208
+ raise 'Internal ERROR' if destination.nil? && direction.eql?(Fasp::TransferSpec::DIRECTION_RECEIVE)
209
+ t_spec = @api_node.transfer_spec_gen4(folder_id, direction, {
210
+ 'paths' => [{'source' => source_filename}],
211
+ 'tags' => {Fasp::TransferSpec::TAG_RESERVED => {PREV_GEN_TAG => true}}
234
212
  })
235
- # force destination
236
- # t_spec['destination_root']=destination
213
+ # force destination, need to set this in transfer agent else it gets overwritten, not do: t_spec['destination_root']=destination
237
214
  transfer.option_transfer_spec_deep_merge({'destination_root' => destination})
238
215
  Main.result_transfer(transfer.start(t_spec))
239
216
  end
@@ -358,7 +335,7 @@ module Aspera
358
335
  scan_start = '/' + scan_start.split('/').reject(&:empty?).join('/')
359
336
  scan_start = "#{scan_start}/" # unless scan_start.end_with?('/')
360
337
  end
361
- filter_block = Aspera::Node.file_matcher(options.get_option(:value))
338
+ filter_block = Aspera::Node.file_matcher(value_or_query(allowed_types: String))
362
339
  Log.log.debug{"scan: #{top_entry} : #{scan_start}".green}
363
340
  # don't use recursive call, use list instead
364
341
  entries_to_process = [top_entry]
@@ -455,7 +432,7 @@ module Aspera
455
432
  end
456
433
  end
457
434
  end
458
- Aspera::Preview::FileTypes.instance.use_mimemagic = options.get_option(:mimemagic, is_type: :mandatory)
435
+ Aspera::Preview::FileTypes.instance.use_mimemagic = options.get_option(:mimemagic, mandatory: true)
459
436
  # check tools that are anyway required for all cases
460
437
  Aspera::Preview::Utils.check_tools(@skip_types)
461
438
  case command
@@ -477,15 +454,15 @@ module Aspera
477
454
  return Main.result_status('scan finished')
478
455
  when :events, :trevents
479
456
  iteration_persistency = nil
480
- if options.get_option(:once_only, is_type: :mandatory)
457
+ if options.get_option(:once_only, mandatory: true)
481
458
  iteration_persistency = PersistencyActionOnce.new(
482
459
  manager: @agents[:persistency],
483
460
  data: [],
484
461
  id: IdGenerator.from_list([
485
462
  'preview_iteration',
486
463
  command.to_s,
487
- options.get_option(:url, is_type: :mandatory),
488
- options.get_option(:username, is_type: :mandatory)
464
+ options.get_option(:url, mandatory: true),
465
+ options.get_option(:username, mandatory: true)
489
466
  ]))
490
467
  end
491
468
  # call processing method specified by command line command
@@ -506,6 +483,7 @@ module Aspera
506
483
  raise 'error'
507
484
  end
508
485
  ensure
486
+ Log.log.debug{"cleaning up temp folder #{@tmp_folder}"}
509
487
  FileUtils.rm_rf(@tmp_folder)
510
488
  end # execute_action
511
489
  end # Preview
@@ -25,7 +25,9 @@ module Aspera
25
25
 
26
26
  class Server < Aspera::Cli::BasicAuthPlugin
27
27
  SSH_SCHEME = 'ssh'
28
- URI_SCHEMES = %w[https local].push(SSH_SCHEME).freeze
28
+ LOCAL_SCHEME = 'local'
29
+ HTTPS_SCHEME = 'https'
30
+ URI_SCHEMES = [SSH_SCHEME, LOCAL_SCHEME, HTTPS_SCHEME].freeze
29
31
  ASCMD_ALIASES = {
30
32
  browse: :ls,
31
33
  delete: :rm,
@@ -41,7 +43,7 @@ module Aspera
41
43
  cmd = cmd.map{|v|%Q("#{v}")}.join(' ') if cmd.is_a?(Array)
42
44
  Log.log.debug{"Executing: #{cmd} with '#{line}'"}
43
45
  stdout_str, stderr_str, status = Open3.capture3(cmd, stdin_data: line, binmode: true)
44
- Log.log.debug(">> #{status} -> #{stderr_str}")
46
+ Log.log.debug{"exec status: #{status} -> #{stderr_str}"}
45
47
  raise "command #{cmd} failed with code #{status.exitstatus} #{stderr_str}" unless status.success?
46
48
  return stdout_str
47
49
  end
@@ -49,28 +51,32 @@ module Aspera
49
51
 
50
52
  def initialize(env)
51
53
  super(env)
52
- options.add_opt_simple(:ssh_keys, 'SSH key path list (Array or single)')
53
- options.add_opt_simple(:ssh_options, 'SSH options (Hash)')
54
+ options.declare(:ssh_keys, 'SSH key path list (Array or single)')
55
+ options.declare(:passphrase, 'SSH private key passphrase')
56
+ options.declare(:ssh_options, 'SSH options', types: Hash, default: {})
54
57
  options.parse_options!
55
- @ssh_opts = nil
58
+ @ssh_opts = options.get_option(:ssh_options).symbolize_keys
56
59
  end
57
60
 
58
61
  # Read command line options
59
62
  # @return [Hash] transfer specification
60
63
  def options_to_base_transfer_spec
61
- url = options.get_option(:url, is_type: :mandatory)
64
+ url = options.get_option(:url, mandatory: true)
62
65
  server_transfer_spec = {}
63
66
  server_uri = URI.parse(url)
64
- Log.log.debug{"URI : #{server_uri}, port=#{server_uri.port}, scheme:#{server_uri.scheme}"}
67
+ Log.log.debug{"URI=#{server_uri}, host=#{server_uri.hostname}, port=#{server_uri.port}, scheme=#{server_uri.scheme}"}
65
68
  server_transfer_spec['remote_host'] = server_uri.hostname
66
69
  unless URI_SCHEMES.include?(server_uri.scheme)
67
70
  Log.log.warn{"Scheme [#{server_uri.scheme}] not supported in #{url}, use one of: #{URI_SCHEMES.join(', ')}. Defaulting to #{SSH_SCHEME}."}
68
71
  server_uri.scheme = SSH_SCHEME
69
72
  end
70
- if server_uri.scheme.eql?('local')
73
+ if server_uri.scheme.eql?(LOCAL_SCHEME)
71
74
  # Using local execution (mostly for testing)
75
+ server_transfer_spec['remote_host'] = 'localhost'
76
+ # simulate SSH environment, else ascp will fail
77
+ ENV['SSH_CLIENT'] = 'local 0 0'
72
78
  return server_transfer_spec
73
- elsif transfer.option_transfer_spec['token'].is_a?(String) && server_uri.scheme.eql?('https')
79
+ elsif transfer.option_transfer_spec['token'].is_a?(String) && server_uri.scheme.eql?(HTTPS_SCHEME)
74
80
  server_transfer_spec['wss_enabled'] = true
75
81
  server_transfer_spec['wss_port'] = server_uri.port
76
82
  # Using WSS
@@ -88,11 +94,7 @@ module Aspera
88
94
  options.set_option(:username, Aspera::Fasp::TransferSpec::ACCESS_KEY_TRANSFER_USER)
89
95
  Log.log.info{"No username provided: Assuming default transfer user: #{Aspera::Fasp::TransferSpec::ACCESS_KEY_TRANSFER_USER}"}
90
96
  end
91
- server_transfer_spec['remote_user'] = options.get_option(:username, is_type: :mandatory)
92
- ssh_args = options.get_option(:ssh_options)
93
- ssh_args = {} if ssh_args.nil?
94
- raise 'expecting a Hash for ssh_options' unless ssh_args.is_a?(Hash)
95
- @ssh_opts = ssh_args.symbolize_keys
97
+ server_transfer_spec['remote_user'] = options.get_option(:username, mandatory: true)
96
98
  if !server_uri.port.nil?
97
99
  @ssh_opts[:port] = server_uri.port
98
100
  server_transfer_spec['ssh_port'] = server_uri.port
@@ -119,6 +121,11 @@ module Aspera
119
121
  cred_set = true
120
122
  end
121
123
  end
124
+ ssh_passphrase = options.get_option(:passphrase)
125
+ if !ssh_passphrase.nil?
126
+ @ssh_opts[:passphrase] = ssh_passphrase
127
+ server_transfer_spec['ssh_private_key_passphrase'] = ssh_passphrase
128
+ end
122
129
  # if user provided transfer spec has a token, we will use bypass keys
123
130
  cred_set = true if transfer.option_transfer_spec['token'].is_a?(String)
124
131
  raise 'Either password, key , or transfer spec token must be provided' if !cred_set
@@ -143,7 +150,7 @@ module Aspera
143
150
 
144
151
  def execute_action
145
152
  server_transfer_spec = options_to_base_transfer_spec
146
- ascmd_executor = if !@ssh_opts.nil?
153
+ ascmd_executor = if !@ssh_opts.empty?
147
154
  Ssh.new(server_transfer_spec['remote_host'], server_transfer_spec['remote_user'], @ssh_opts)
148
155
  elsif server_transfer_spec.key?('wss_enabled')
149
156
  nil
@@ -26,24 +26,25 @@ module Aspera
26
26
 
27
27
  def initialize(env)
28
28
  super(env)
29
- options.add_opt_list(:type, %i[any local ldap saml], 'Type of user/group for operations')
30
- options.set_option(:type, :any)
29
+ options.declare(:type, 'Type of user/group for operations', values: %i[any local ldap saml], default: :any)
31
30
  options.parse_options!
32
31
  end
33
32
 
34
33
  SAML_IMPORT_MANDATORY = %w[id name_id].freeze
35
34
  SAML_IMPORT_ALLOWED = %w[email given_name surname].concat(SAML_IMPORT_MANDATORY).freeze
36
35
 
37
- ACTIONS = %i[health repository admin].freeze
36
+ ACTIONS = %i[health files admin].freeze
37
+ # common to users and groups
38
+ USR_GRP_SETTINGS = %i[transfer_settings app_authorizations share_permissions].freeze
38
39
 
39
40
  def execute_action
40
- command = options.get_next_command(ACTIONS)
41
+ command = options.get_next_command(ACTIONS, aliases: {repository: :files})
41
42
  case command
42
43
  when :health
43
44
  nagios = Nagios.new
44
45
  begin
45
46
  Rest
46
- .new(base_url: options.get_option(:url, is_type: :mandatory) + '/node_api')
47
+ .new(base_url: options.get_option(:url, mandatory: true) + '/node_api')
47
48
  .call(
48
49
  operation: 'GET',
49
50
  subpath: 'ping',
@@ -54,25 +55,28 @@ module Aspera
54
55
  nagios.add_critical('node api', e.to_s)
55
56
  end
56
57
  return nagios.result
57
- when :repository
58
+ when :repository, :files
58
59
  api_shares_node = basic_auth_api('node_api')
59
60
  repo_command = options.get_next_command(Node::COMMANDS_SHARES)
60
61
  return Node.new(@agents.merge(skip_basic_auth_options: true, node_api: api_shares_node)).execute_action(repo_command)
61
62
  when :admin
62
63
  api_shares_admin = basic_auth_api('api/v1')
63
- admin_command = options.get_next_command(%i[user group share node])
64
+ admin_command = options.get_next_command(%i[user group share node].freeze)
64
65
  case admin_command
65
66
  when :node
66
67
  return entity_action(api_shares_admin, 'data/nodes')
67
68
  when :user, :group
68
69
  entity_type = admin_command
69
- entities_location = options.get_option(:type, is_type: :mandatory)
70
+ entities_location = options.get_option(:type, mandatory: true)
70
71
  entities_path = "data/#{entities_location}_#{entity_type}s"
71
72
  entity_action = nil
72
73
  case entities_location
73
74
  when :any
74
75
  entities_path = "data/#{entity_type}s"
75
- entity_action = %i[list show delete share_permissions app_authorizations].freeze
76
+ entity_action = %i[list show delete]
77
+ entity_action.concat(USR_GRP_SETTINGS)
78
+ entity_action.push(:users) if entity_type.eql?(:group)
79
+ entity_action.freeze
76
80
  when :local
77
81
  entity_action = %i[list show create modify delete].freeze
78
82
  when :ldap
@@ -80,31 +84,15 @@ module Aspera
80
84
  when :saml
81
85
  entity_action = %i[import].freeze
82
86
  end
83
- entity_command = options.get_next_command(entity_action)
84
- entity_path = "#{entities_path}/#{instance_identifier}" if %i[app_authorizations share_permissions].include?(entity_command)
85
- case entity_command
86
- when :list, :show, :create, :delete, :modify
87
+ entity_verb = options.get_next_command(entity_action)
88
+ # entity_path = "#{entities_path}/#{instance_identifier}" if %i[app_authorizations share_permissions].include?(entity_verb)
89
+ case entity_verb
90
+ when *Plugin::ALL_OPS
87
91
  display_fields = entity_type.eql?(:user) ? %w[id username first_name last_name email] : nil
88
92
  display_fields.push(:directory_user) if entity_type.eql?(:user) && entities_location.eql?(:any)
89
- return entity_command(entity_command, api_shares_admin, entities_path, display_fields: display_fields)
90
- when :app_authorizations
91
- case options.get_next_command(%i[modify show])
92
- when :show
93
- return {type: :single_object, data: api_shares_admin.read("#{entity_path}/app_authorizations")[:data]}
94
- when :modify
95
- parameters = options.get_option(:value, is_type: :mandatory)
96
- return {type: :single_object, data: api_shares_admin.update("#{entity_path}/app_authorizations", parameters)[:data]}
97
- end
98
- when :share_permissions
99
- case options.get_next_command(%i[list show])
100
- when :list
101
- return {type: :object_list, data: api_shares_admin.read("#{entity_path}/share_permissions")[:data]}
102
- when :show
103
- return {type: :single_object, data: api_shares_admin.read("#{entity_path}/share_permissions/#{instance_identifier}")[:data]}
104
- end
93
+ return entity_command(entity_verb, api_shares_admin, entities_path, display_fields: display_fields)
105
94
  when :import
106
- parameters = options.get_option(:value, is_type: :mandatory)
107
- return do_bulk_operation(parameters, 'created') do |entity_parameters|
95
+ return do_bulk_operation(value_create_modify(type: :bulk_hash), 'created') do |entity_parameters|
108
96
  entity_parameters = entity_parameters.transform_keys{|k|k.gsub(/\s+/, '_').downcase}
109
97
  raise 'expecting Hash' unless entity_parameters.is_a?(Hash)
110
98
  SAML_IMPORT_MANDATORY.each{|p|raise "missing mandatory field: #{p}" if entity_parameters[p].nil?}
@@ -114,11 +102,14 @@ module Aspera
114
102
  api_shares_admin.create("#{entities_path}/import", entity_parameters)[:data]
115
103
  end
116
104
  when :add
117
- parameters = options.get_option(:value)
118
- return do_bulk_operation(parameters, 'created') do |entity_name|
105
+ return do_bulk_operation(value_create_modify(type: :bulk_hash), 'created') do |entity_name|
119
106
  raise "expecting string (name), have #{entity_name.class}" unless entity_name.is_a?(String)
120
107
  api_shares_admin.create(entities_path, {entity_type=>entity_name})[:data]
121
108
  end
109
+ when *USR_GRP_SETTINGS
110
+ group_id = instance_identifier
111
+ entities_path = "#{entities_path}/#{group_id}/#{entity_verb}"
112
+ return entity_action(api_shares_admin, entities_path, is_singleton: !entity_verb.eql?(:share_permissions))
122
113
  end
123
114
  when :share
124
115
  share_command = options.get_next_command(%i[user_permissions group_permissions].concat(Plugin::ALL_OPS))
@@ -13,11 +13,11 @@ module Aspera
13
13
  class Sync < Aspera::Cli::Plugin
14
14
  def initialize(env, sync_spec: nil)
15
15
  super(env)
16
- options.add_opt_simple(:sync_info, 'Information for sync instance and sessions (Hash)')
17
- options.add_opt_simple(:sync_session, 'Name of session to use for admin commands. default: first in parameters')
16
+ options.declare(:sync_info, 'Information for sync instance and sessions', types: Hash)
17
+ options.declare(:sync_session, 'Name of session to use for admin commands. default: first in parameters')
18
18
  options.parse_options!
19
19
  return if env[:man_only]
20
- @params = options.get_option(:sync_info, is_type: :mandatory)
20
+ @params = options.get_option(:sync_info, mandatory: true)
21
21
  @sync_spec = sync_spec
22
22
  end
23
23
 
@@ -54,18 +54,13 @@ module Aspera
54
54
  @progress_listener = Listener::ProgressMulti.new
55
55
  # source/destination pair, like "paths" of transfer spec
56
56
  @transfer_paths = nil
57
- @opt_mgr.set_obj_attr(:ts, self, :option_transfer_spec)
58
- @opt_mgr.set_obj_attr(:transfer_info, self, :option_transfer_info)
59
- @opt_mgr.add_opt_simple(:ts, "Override transfer spec values (Hash, e.g. use @json: prefix), current=#{@opt_mgr.get_option(:ts)}")
60
- @opt_mgr.add_opt_simple(:to_folder, 'Destination folder for transferred files')
61
- @opt_mgr.add_opt_simple(:sources, "How list of transferred files is provided (#{FILE_LIST_OPTIONS.join(',')})")
62
- @opt_mgr.add_opt_list(:src_type, %i[list pair], 'Type of file list')
63
- @opt_mgr.add_opt_list(:transfer, TRANSFER_AGENTS, 'Type of transfer agent')
64
- @opt_mgr.add_opt_simple(:transfer_info, 'Parameters for transfer agent (Hash)')
65
- @opt_mgr.add_opt_list(:progress, %i[none native multi], 'Type of progress bar')
66
- @opt_mgr.set_option(:transfer, :direct)
67
- @opt_mgr.set_option(:src_type, :list)
68
- @opt_mgr.set_option(:progress, :native) # use native ascp progress bar as it is more reliable
57
+ @opt_mgr.declare(:ts, 'Override transfer spec values', types: Hash, handler: {o: self, m: :option_transfer_spec})
58
+ @opt_mgr.declare(:to_folder, 'Destination folder for transferred files')
59
+ @opt_mgr.declare(:sources, "How list of transferred files is provided (#{FILE_LIST_OPTIONS.join(',')})")
60
+ @opt_mgr.declare(:src_type, 'Type of file list', values: %i[list pair], default: :list)
61
+ @opt_mgr.declare(:transfer, 'Type of transfer agent', values: TRANSFER_AGENTS, default: :direct)
62
+ @opt_mgr.declare(:transfer_info, 'Parameters for transfer agent', types: Hash, handler: {o: self, m: :option_transfer_info})
63
+ @opt_mgr.declare(:progress, 'Type of progress bar', values: %i[none native multi], default: :native)
69
64
  @opt_mgr.parse_options!
70
65
  end
71
66
 
@@ -74,7 +69,18 @@ module Aspera
74
69
  # multiple option are merged
75
70
  def option_transfer_spec=(value)
76
71
  raise 'option ts shall be a Hash' unless value.is_a?(Hash)
77
- @transfer_spec_cmdline.merge!(value)
72
+ @transfer_spec_cmdline.deep_merge!(value)
73
+ end
74
+
75
+ # add other transfer spec parameters
76
+ def option_transfer_spec_deep_merge(ts); @transfer_spec_cmdline.deep_merge!(ts); end
77
+
78
+ # @return [Hash] transfer spec with updated values from command line, including removed values
79
+ def updated_ts(transfer_spec={})
80
+ transfer_spec.deep_merge!(@transfer_spec_cmdline)
81
+ # recursively remove values that are nil (user wants to delete)
82
+ transfer_spec.deep_do { |hash, key, value, _unused| hash.delete(key) if value.nil?}
83
+ return transfer_spec
78
84
  end
79
85
 
80
86
  def option_transfer_info; @transfer_info; end
@@ -82,17 +88,15 @@ module Aspera
82
88
  # multiple option are merged
83
89
  def option_transfer_info=(value)
84
90
  raise 'option transfer_info shall be a Hash' unless value.is_a?(Hash)
85
- @transfer_info.merge!(value)
91
+ @transfer_info.deep_merge!(value)
86
92
  end
87
93
 
88
- def option_transfer_spec_deep_merge(ts); @transfer_spec_cmdline.deep_merge!(ts); end
89
-
90
94
  def agent_instance=(instance)
91
95
  @agent = instance
92
96
  @agent.add_listener(Listener::Logger.new)
93
97
  # use local progress bar if asked so, or if native and non local ascp (because only local ascp has native progress bar)
94
- if @opt_mgr.get_option(:progress, is_type: :mandatory).eql?(:multi) ||
95
- (@opt_mgr.get_option(:progress, is_type: :mandatory).eql?(:native) && !instance.class.to_s.eql?('Aspera::Fasp::AgentDirect'))
98
+ if @opt_mgr.get_option(:progress, mandatory: true).eql?(:multi) ||
99
+ (@opt_mgr.get_option(:progress, mandatory: true).eql?(:native) && !instance.class.to_s.eql?('Aspera::Fasp::AgentDirect'))
96
100
  @agent.add_listener(@progress_listener)
97
101
  end
98
102
  end
@@ -100,7 +104,7 @@ module Aspera
100
104
  # analyze options and create new agent if not already created or set
101
105
  def set_agent_by_options
102
106
  return nil unless @agent.nil?
103
- agent_type = @opt_mgr.get_option(:transfer, is_type: :mandatory)
107
+ agent_type = @opt_mgr.get_option(:transfer, mandatory: true)
104
108
  # agent plugin is loaded on demand to avoid loading unnecessary dependencies
105
109
  require "aspera/fasp/agent_#{agent_type}"
106
110
  agent_options = @opt_mgr.get_option(:transfer_info)
@@ -113,7 +117,7 @@ module Aspera
113
117
  agent_options = @config.preset_by_name(param_set_name)
114
118
  end
115
119
  # special case: native progress bar
116
- if agent_type.eql?(:direct) && @opt_mgr.get_option(:progress, is_type: :mandatory).eql?(:native)
120
+ if agent_type.eql?(:direct) && @opt_mgr.get_option(:progress, mandatory: true).eql?(:native)
117
121
  agent_options[:quiet] = false
118
122
  end
119
123
  # normalize after getting from user or default node
@@ -170,7 +174,7 @@ module Aspera
170
174
  when FILE_LIST_FROM_TRANSFER_SPEC
171
175
  Log.log.debug('assume list provided in transfer spec')
172
176
  special_case_direct_with_list =
173
- @opt_mgr.get_option(:transfer, is_type: :mandatory).eql?(:direct) &&
177
+ @opt_mgr.get_option(:transfer, mandatory: true).eql?(:direct) &&
174
178
  Fasp::Parameters.ts_has_ascp_file_list(@transfer_spec_cmdline, @opt_mgr.get_option(:transfer_info))
175
179
  raise CliBadArgument, 'transfer spec on command line must have sources' if @transfer_paths.nil? && !special_case_direct_with_list
176
180
  # here we assume check of sources is made in transfer agent
@@ -185,7 +189,7 @@ module Aspera
185
189
  if !@transfer_paths.nil?
186
190
  Log.log.warn('--sources overrides paths from --ts')
187
191
  end
188
- case @opt_mgr.get_option(:src_type, is_type: :mandatory)
192
+ case @opt_mgr.get_option(:src_type, mandatory: true)
189
193
  when :list
190
194
  # when providing a list, just specify source
191
195
  @transfer_paths = file_list.map{|i|{'source' => i}}
@@ -225,14 +229,13 @@ module Aspera
225
229
  end
226
230
  # update command line paths, unless destination already has one
227
231
  @transfer_spec_cmdline['paths'] = transfer_spec['paths'] || ts_source_paths
228
- transfer_spec.merge!(@transfer_spec_cmdline)
229
- # remove values that are nil (user wants to delete)
230
- transfer_spec.delete_if { |_key, value| value.nil? }
232
+ # updated transfer spec with command line
233
+ updated_ts(transfer_spec)
231
234
  # create transfer agent
232
235
  set_agent_by_options
233
236
  Log.log.debug{"transfer agent is a #{@agent.class}"}
234
237
  @agent.start_transfer(transfer_spec, token_regenerator: rest_token)
235
- # list of : :success or error message
238
+ # list of: :success or "error message string"
236
239
  result = @agent.wait_for_transfers_completion
237
240
  @progress_listener.reset
238
241
  Fasp::AgentBase.validate_status_list(result)
@@ -4,6 +4,6 @@ module Aspera
4
4
  module Cli
5
5
  # for beta add extension : .beta1
6
6
  # for dev version add extension : .pre
7
- VERSION = '4.13.0'
7
+ VERSION = '4.14.0'
8
8
  end
9
9
  end
data/lib/aspera/colors.rb CHANGED
@@ -1,16 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # cspell:words
4
+
3
5
  # simple vt100 colors
4
6
  class String
5
7
  class << self
6
8
  private
7
9
 
8
- def vtcmd(code); "\e[#{code}m"; end
10
+ def vt_cmd(code); "\e[#{code}m"; end
9
11
  end
10
12
  # see https://en.wikipedia.org/wiki/ANSI_escape_code
11
13
  # symbol is the method name added to String
12
14
  # it adds control chars to set color (and reset at the end).
13
- VTSTYLES = {
15
+ VT_STYLES = {
14
16
  bold: 1,
15
17
  italic: 3,
16
18
  underline: 4,
@@ -33,17 +35,17 @@ class String
33
35
  bg_cyan: 46,
34
36
  bg_gray: 47
35
37
  }.freeze
36
- private_constant :VTSTYLES
37
- # defines methods to String, one per entry in VTSTYLES
38
- VTSTYLES.each do |name, code|
38
+ private_constant :VT_STYLES
39
+ # defines methods to String, one per entry in VT_STYLES
40
+ VT_STYLES.each do |name, code|
39
41
  if $stderr.tty?
40
- begin_seq = vtcmd(code)
42
+ begin_seq = vt_cmd(code)
41
43
  end_code = 0 # by default reset all
42
44
  if code <= 7 then code + 20
43
45
  elsif code <= 37 then 39
44
46
  elsif code <= 47 then 49
45
47
  end
46
- end_seq = vtcmd(end_code)
48
+ end_seq = vt_cmd(end_code)
47
49
  define_method(name){"#{begin_seq}#{self}#{end_seq}"}
48
50
  else
49
51
  define_method(name){self}
data/lib/aspera/data/6 CHANGED
Binary file
@@ -3,6 +3,8 @@
3
3
  require 'aspera/log'
4
4
  require 'rbconfig'
5
5
 
6
+ # cspell:words MEBI mswin bccwin
7
+
6
8
  module Aspera
7
9
  # detect OS, architecture, and specific stuff
8
10
  class Environment
@@ -84,12 +86,14 @@ module Aspera
84
86
  end
85
87
 
86
88
  # value is provided in block
87
- def write_file_restricted(path, force: false)
89
+ def write_file_restricted(path, force: false, mode: nil)
88
90
  raise 'coding error, missing content block' unless block_given?
89
91
  if force || !File.exist?(path)
90
- File.unlink(path) rescue nil # Windows may give error
92
+ # Windows may give error
93
+ File.unlink(path) rescue nil
94
+ # content provided by block
91
95
  File.write(path, yield)
92
- restrict_file_access(path)
96
+ restrict_file_access(path, mode: mode)
93
97
  end
94
98
  return path
95
99
  end
@@ -9,7 +9,9 @@ require 'tty-spinner'
9
9
  module Aspera
10
10
  module Fasp
11
11
  class AgentConnect < Aspera::Fasp::AgentBase
12
+ # try twice the main init url in sequence
12
13
  CONNECT_START_URIS = ['fasp://initialize', 'fasp://initialize', 'aspera-drive://initialize', 'https://test-connect.ibmaspera.com/']
14
+ # delay between each try to start connect
13
15
  SLEEP_SEC_BETWEEN_RETRY = 3
14
16
  private_constant :CONNECT_START_URIS, :SLEEP_SEC_BETWEEN_RETRY
15
17
  def initialize(_options)
@@ -106,6 +108,9 @@ module Aspera
106
108
  when 'failed'
107
109
  spinner&.error
108
110
  raise Fasp::Error, transfer['error_desc']
111
+ when 'cancelled'
112
+ spinner&.error
113
+ raise Fasp::Error, 'Transfer cancelled by user'
109
114
  else
110
115
  raise Fasp::Error, "unknown status: #{transfer['status']}: #{transfer['error_desc']}"
111
116
  end