aspera-cli 4.13.0 → 4.14.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 (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