aspera-cli 4.24.2 → 4.25.0.pre2

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 (73) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +1067 -758
  4. data/CONTRIBUTING.md +93 -120
  5. data/README.md +817 -510
  6. data/lib/aspera/agent/direct.rb +14 -12
  7. data/lib/aspera/agent/transferd.rb +4 -4
  8. data/lib/aspera/api/aoc.rb +71 -43
  9. data/lib/aspera/api/cos_node.rb +3 -2
  10. data/lib/aspera/api/faspex.rb +6 -5
  11. data/lib/aspera/api/node.rb +10 -12
  12. data/lib/aspera/ascmd.rb +1 -2
  13. data/lib/aspera/ascp/installation.rb +55 -41
  14. data/lib/aspera/ascp/management.rb +9 -5
  15. data/lib/aspera/assert.rb +28 -6
  16. data/lib/aspera/cli/error.rb +4 -2
  17. data/lib/aspera/cli/extended_value.rb +94 -62
  18. data/lib/aspera/cli/formatter.rb +55 -22
  19. data/lib/aspera/cli/main.rb +21 -14
  20. data/lib/aspera/cli/manager.rb +349 -248
  21. data/lib/aspera/cli/plugins/alee.rb +3 -3
  22. data/lib/aspera/cli/plugins/aoc.rb +94 -51
  23. data/lib/aspera/cli/plugins/base.rb +62 -49
  24. data/lib/aspera/cli/plugins/config.rb +85 -96
  25. data/lib/aspera/cli/plugins/console.rb +15 -9
  26. data/lib/aspera/cli/plugins/cos.rb +1 -1
  27. data/lib/aspera/cli/plugins/faspex.rb +34 -27
  28. data/lib/aspera/cli/plugins/faspex5.rb +47 -44
  29. data/lib/aspera/cli/plugins/faspio.rb +7 -6
  30. data/lib/aspera/cli/plugins/httpgw.rb +3 -2
  31. data/lib/aspera/cli/plugins/node.rb +132 -120
  32. data/lib/aspera/cli/plugins/oauth.rb +1 -1
  33. data/lib/aspera/cli/plugins/orchestrator.rb +116 -33
  34. data/lib/aspera/cli/plugins/preview.rb +26 -46
  35. data/lib/aspera/cli/plugins/server.rb +9 -10
  36. data/lib/aspera/cli/plugins/shares.rb +77 -43
  37. data/lib/aspera/cli/sync_actions.rb +49 -38
  38. data/lib/aspera/cli/transfer_agent.rb +16 -34
  39. data/lib/aspera/cli/version.rb +1 -1
  40. data/lib/aspera/cli/wizard.rb +8 -5
  41. data/lib/aspera/command_line_builder.rb +20 -17
  42. data/lib/aspera/coverage.rb +6 -2
  43. data/lib/aspera/environment.rb +71 -84
  44. data/lib/aspera/faspex_gw.rb +1 -1
  45. data/lib/aspera/faspex_postproc.rb +1 -1
  46. data/lib/aspera/keychain/factory.rb +1 -2
  47. data/lib/aspera/keychain/macos_security.rb +2 -2
  48. data/lib/aspera/log.rb +2 -1
  49. data/lib/aspera/markdown.rb +31 -0
  50. data/lib/aspera/nagios.rb +6 -5
  51. data/lib/aspera/oauth/base.rb +17 -27
  52. data/lib/aspera/oauth/factory.rb +1 -1
  53. data/lib/aspera/oauth/url_json.rb +2 -1
  54. data/lib/aspera/preview/file_types.rb +23 -37
  55. data/lib/aspera/preview/terminal.rb +95 -29
  56. data/lib/aspera/preview/utils.rb +6 -5
  57. data/lib/aspera/products/connect.rb +3 -3
  58. data/lib/aspera/rest.rb +51 -39
  59. data/lib/aspera/rest_error_analyzer.rb +4 -4
  60. data/lib/aspera/ssh.rb +5 -2
  61. data/lib/aspera/ssl.rb +41 -0
  62. data/lib/aspera/sync/conf.schema.yaml +182 -34
  63. data/lib/aspera/sync/database.rb +2 -1
  64. data/lib/aspera/sync/operations.rb +128 -72
  65. data/lib/aspera/transfer/parameters.rb +3 -4
  66. data/lib/aspera/transfer/spec.rb +2 -3
  67. data/lib/aspera/transfer/spec.schema.yaml +49 -19
  68. data/lib/aspera/transfer/spec_doc.rb +14 -14
  69. data/lib/aspera/uri_reader.rb +1 -1
  70. data/lib/transferd_pb.rb +2 -2
  71. data.tar.gz.sig +0 -0
  72. metadata +33 -6
  73. metadata.gz.sig +0 -0
@@ -8,7 +8,7 @@ else
8
8
  require 'sqlite3'
9
9
  end
10
10
 
11
- # A wrapper class that provides common API for Ruby and JRuby
11
+ # A wrapper class that provides common API for sqlite in both Ruby and JRuby
12
12
  class SqLite3Wrapper
13
13
  def initialize(db_path)
14
14
  @db_path = db_path
@@ -52,6 +52,7 @@ end
52
52
 
53
53
  module Aspera
54
54
  module Sync
55
+ # Access `async` sqlite database
55
56
  class Database
56
57
  def initialize(db_path)
57
58
  @db = SqLite3Wrapper.new(db_path)
@@ -23,21 +23,23 @@ module Aspera
23
23
  # Default direction for sync
24
24
  DEFAULT_DIRECTION = DIRECTIONS.first
25
25
 
26
+ SCP_REMOTE_REGEX = /\A(?:(?:(?<user>[^@:\s]+)@)?(?<host>[^:\s]+):)?(?<path>.+)\z/
27
+
26
28
  class << self
27
29
  # Set `remote_dir` in sync parameters based on transfer spec
28
- # @param params [Hash] Sync parameters, in `conf` or `args` format.
30
+ # @param sync_info [Hash] Sync parameters, in `conf` or `args` format.
29
31
  # @param remote_dir_key [String] Key to update in above hash
30
32
  # @param transfer_spec [Hash] Transfer spec
31
- def update_remote_dir(params, remote_dir_key, transfer_spec)
33
+ def update_remote_dir(sync_info, remote_dir_key, transfer_spec)
32
34
  if transfer_spec.dig(*%w[tags aspera node file_id])
33
35
  # in AoC, use gen4
34
- params[remote_dir_key] = '/'
36
+ sync_info[remote_dir_key] = '/'
35
37
  elsif transfer_spec['cookie']&.start_with?('aspera.shares2')
36
38
  # TODO : something more generic, independent of Shares
37
39
  # in Shares, the actual folder on remote end is not always the same as the name of the share
38
40
  remote_key = transfer_spec['direction'].eql?('send') ? 'destination' : 'source'
39
41
  actual_remote = transfer_spec['paths']&.first&.[](remote_key)
40
- params[remote_dir_key] = actual_remote if actual_remote
42
+ sync_info[remote_dir_key] = actual_remote if actual_remote
41
43
  end
42
44
  nil
43
45
  end
@@ -70,36 +72,36 @@ module Aspera
70
72
  end
71
73
 
72
74
  # Get symbol of sync direction, defaulting to :push
73
- # @param params [Hash] Sync parameters, old or new format
75
+ # @param sync_info [Hash] Sync parameters, `conf` or `args` format
74
76
  # @return [Symbol] direction symbol, one of :push, :pull, :bidi
75
- def direction_sym(params)
76
- (params['direction'] || DEFAULT_DIRECTION).to_sym
77
+ def direction_sym(sync_info)
78
+ (sync_info['direction'] || DEFAULT_DIRECTION).to_sym
77
79
  end
78
80
 
79
81
  # Start the sync process
80
- # @param params [Hash] Sync parameters, old or new format
81
- # @param opt_ts [Hash] Optional transfer spec
82
- # @param &block [nil, Proc] block to generate transfer spec, takes: direction (one of DIRECTIONS), local_dir, remote_dir
83
- def start(params, opt_ts = nil)
84
- Log.dump(:sync_params_initial, params)
85
- Aspera.assert_type(params, Hash)
86
- Aspera.assert(PARAM_KEYS.any?{ |k| params.key?(k)}, type: Error){'At least one of `local` or `sessions` must be present in async parameters'}
82
+ # @param sync_info [Hash] Sync parameters, old or new format
83
+ # @param opt_ts [Hash] Optional transfer spec
84
+ # @param &block [nil, Proc] block to generate transfer spec, takes: `direction` (one of DIRECTIONS), `local_dir`, `remote_dir`
85
+ def start(sync_info, opt_ts = nil)
86
+ Log.dump(:sync_params_initial, sync_info)
87
+ Aspera.assert_type(sync_info, Hash)
88
+ Aspera.assert(PARAM_KEYS.any?{ |k| sync_info.key?(k)}, type: Error){'At least one of `local` or `sessions` must be present in async parameters'}
87
89
  env_args = {
88
90
  args: [],
89
91
  env: {}
90
92
  }
91
- if params.key?('local')
92
- # "conf" format
93
- Aspera.assert_type(params['local'], Hash){'local'}
94
- remote = params['remote']
93
+ if sync_info.key?('local')
94
+ # `conf` format
95
+ Aspera.assert_type(sync_info['local'], Hash){'local'}
96
+ remote = sync_info['remote']
95
97
  Aspera.assert_type(remote, Hash){'remote'}
96
98
  Aspera.assert_type(remote['path'], String){'remote path'}
97
99
  # get transfer spec if possible, and feed back to new structure
98
100
  if block_given?
99
- transfer_spec = yield(direction_sym(params), params['local']['path'], remote['path'])
101
+ transfer_spec = yield(direction_sym(sync_info), sync_info['local']['path'], remote['path'])
100
102
  Log.dump(:auth_ts, transfer_spec)
101
103
  transfer_spec.deep_merge!(opt_ts) unless opt_ts.nil?
102
- tspec_to_sync_info(transfer_spec, params, CONF_SCHEMA)
104
+ tspec_to_sync_info(transfer_spec, sync_info, CONF_SCHEMA)
103
105
  update_remote_dir(remote, 'path', transfer_spec)
104
106
  end
105
107
  remote['connect_mode'] ||= transfer_spec['wss_enabled'] ? 'ws' : 'ssh'
@@ -109,18 +111,18 @@ module Aspera
109
111
  remote['private_key_paths'].concat(add_certificates)
110
112
  end
111
113
  # '--exclusive-mgmt-port=12345', '--arg-err-path=-',
112
- env_args[:args] = ["--conf64=#{Base64.strict_encode64(JSON.generate(params))}"]
113
- Log.dump(:sync_conf, params)
114
+ env_args[:args] = ["--conf64=#{Base64.strict_encode64(JSON.generate(sync_info))}"]
115
+ Log.dump(:sync_conf, sync_info)
114
116
  agent = Agent::Direct.new
115
117
  agent.start_and_monitor_process(session: {}, name: :async, **env_args)
116
118
  else
117
- # "args" format
119
+ # `args` format
118
120
  raise StandardError, "Only 'sessions', and optionally 'instance' keys are allowed" unless
119
- params.keys.push('instance').uniq.sort.eql?(CMDLINE_PARAMS_KEYS)
120
- Aspera.assert_type(params['sessions'], Array)
121
- Aspera.assert_type(params['sessions'].first, Hash)
121
+ sync_info.keys.push('instance').uniq.sort.eql?(CMDLINE_PARAMS_KEYS)
122
+ Aspera.assert_type(sync_info['sessions'], Array)
123
+ Aspera.assert_type(sync_info['sessions'].first, Hash)
122
124
  if block_given?
123
- params['sessions'].each do |session|
125
+ sync_info['sessions'].each do |session|
124
126
  Aspera.assert_type(session['local_dir'], String){'local_dir'}
125
127
  Aspera.assert_type(session['remote_dir'], String){'remote_dir'}
126
128
  transfer_spec = yield(direction_sym(session), session['local_dir'], session['remote_dir'])
@@ -131,20 +133,20 @@ module Aspera
131
133
  update_remote_dir(session, 'remote_dir', transfer_spec)
132
134
  end
133
135
  end
134
- if params.key?('instance')
135
- Aspera.assert_type(params['instance'], Hash)
136
- instance_builder = CommandLineBuilder.new(params['instance'], ARGS_INSTANCE_SCHEMA, CommandLineConverter)
136
+ if sync_info.key?('instance')
137
+ Aspera.assert_type(sync_info['instance'], Hash)
138
+ instance_builder = CommandLineBuilder.new(sync_info['instance'], ARGS_INSTANCE_SCHEMA, CommandLineConverter)
137
139
  instance_builder.process_params
138
140
  instance_builder.add_env_args(env_args)
139
141
  end
140
- params['sessions'].each do |session_params|
142
+ sync_info['sessions'].each do |session_params|
141
143
  Aspera.assert_type(session_params, Hash)
142
144
  Aspera.assert(session_params.key?('name')){'session must contain at least: name'}
143
145
  session_builder = CommandLineBuilder.new(session_params, ARGS_SESSION_SCHEMA, CommandLineConverter)
144
146
  session_builder.process_params
145
147
  session_builder.add_env_args(env_args)
146
148
  end
147
- Environment.secure_execute(exec: Ascp::Installation.instance.path(:async), **env_args)
149
+ Environment.secure_execute(Ascp::Installation.instance.path(:async), *env_args[:args], env: env_args[:env])
148
150
  end
149
151
  return
150
152
  end
@@ -168,24 +170,24 @@ module Aspera
168
170
  end
169
171
 
170
172
  # Run `asyncadmin` to get status of sync session
171
- # @param params [Hash] sync parameters in conf or args format
173
+ # @param sync_info [Hash] sync parameters in conf or args format
172
174
  # @return [Hash] parsed output of asyncadmin
173
- def admin_status(params)
174
- Aspera.assert(PARAM_KEYS.any?{ |k| params.key?(k)}, type: Error){'At least one of `local` or `sessions` must be present in async parameters'}
175
- arguments = ['--quiet']
176
- if params.key?('local')
177
- # "conf" format
178
- arguments.push("--name=#{params['name']}")
179
- if params.key?('local_db_dir')
180
- arguments.push("--local-db-dir=#{params['local_db_dir']}")
181
- elsif params.dig('local', 'path')
182
- arguments.push("--local-dir=#{params.dig('local', 'path')}")
175
+ def admin_status(sync_info)
176
+ Aspera.assert(PARAM_KEYS.any?{ |k| sync_info.key?(k)}, type: Error){'At least one of `local` or `sessions` must be present in async parameters'}
177
+ arguments = [ASYNC_ADMIN_EXECUTABLE, '--quiet']
178
+ if sync_info.key?('local')
179
+ # `conf` format
180
+ arguments.push("--name=#{sync_info['name']}")
181
+ if sync_info.key?('local_db_dir')
182
+ arguments.push("--local-db-dir=#{sync_info['local_db_dir']}")
183
+ elsif sync_info.dig('local', 'path')
184
+ arguments.push("--local-dir=#{sync_info.dig('local', 'path')}")
183
185
  else
184
186
  raise Error, 'Missing either local_db_dir or local.path'
185
187
  end
186
188
  else
187
- # "args" format
188
- session = params['sessions'].first
189
+ # `args` format
190
+ session = sync_info['sessions'].first
189
191
  arguments.push("--name=#{session['name']}")
190
192
  if session.key?('local_db_dir')
191
193
  arguments.push("--local-db-dir=#{session['local_db_dir']}")
@@ -195,27 +197,27 @@ module Aspera
195
197
  raise Error, 'Missing either local_db_dir or local_dir'
196
198
  end
197
199
  end
198
- stdout = Environment.secure_capture(exec: ASYNC_ADMIN_EXECUTABLE, args: arguments)
200
+ stdout = Environment.secure_execute(*arguments, mode: :capture)
199
201
  return parse_status(stdout)
200
202
  end
201
203
 
202
- # Find the local database folder based on params
203
- # @param params [Hash] sync parameters in conf or args format
204
- # @return [String, nil] path to "local DB dir", i.e. folder that contains folders that contain snap.db
205
- def local_db_folder(params)
206
- Aspera.assert(PARAM_KEYS.any?{ |k| params.key?(k)}, type: Error){'At least one of `local` or `sessions` must be present in async parameters'}
207
- if params.key?('local')
208
- # "conf" format
209
- if params.key?('local_db_dir')
210
- return params['local_db_dir']
211
- elsif (local_path = params.dig('local', 'path'))
204
+ # Find the local database folder based on sync_info
205
+ # @param sync_info [Hash] sync parameters in conf or args format
206
+ # @return [String, nil] Path to "local DB dir", i.e. folder that contains folders that contain `snap.db`
207
+ def local_db_folder(sync_info)
208
+ Aspera.assert(PARAM_KEYS.any?{ |k| sync_info.key?(k)}, type: Error){'At least one of `local` or `sessions` must be present in async parameters'}
209
+ if sync_info.key?('local')
210
+ # `conf` format
211
+ if sync_info.key?('local_db_dir')
212
+ return sync_info['local_db_dir']
213
+ elsif (local_path = sync_info.dig('local', 'path'))
212
214
  return local_path
213
215
  elsif exception
214
216
  raise Error, 'Missing either local_db_dir or local.path'
215
217
  end
216
218
  else
217
- # "args" format
218
- session = params['sessions'].first
219
+ # `args` format
220
+ session = sync_info['sessions'].first
219
221
  if session.key?('local_db_dir')
220
222
  return session['local_db_dir']
221
223
  elsif session.key?('local_dir')
@@ -227,19 +229,19 @@ module Aspera
227
229
  nil
228
230
  end
229
231
 
230
- def session_name(params)
231
- Aspera.assert(PARAM_KEYS.any?{ |k| params.key?(k)}, type: Error){'At least one of `local` or `sessions` must be present in async parameters'}
232
- if params.key?('local')
233
- # "conf" format
234
- return params['name']
232
+ def session_name(sync_info)
233
+ Aspera.assert(PARAM_KEYS.any?{ |k| sync_info.key?(k)}, type: Error){'At least one of `local` or `sessions` must be present in async parameters'}
234
+ if sync_info.key?('local')
235
+ # `conf` format
236
+ return sync_info['name']
235
237
  else
236
- # "args" format
237
- return params['sessions'].first['name']
238
+ # `args` format
239
+ return sync_info['sessions'].first['name']
238
240
  end
239
241
  end
240
242
 
241
- def session_db_file(params)
242
- db_file = File.join(local_db_folder(params), PRIVATE_FOLDER, session_name(params), ASYNC_DB)
243
+ def session_db_file(sync_info)
244
+ db_file = File.join(local_db_folder(sync_info), PRIVATE_FOLDER, session_name(sync_info), ASYNC_DB)
243
245
  Aspera.assert(File.exist?(db_file)){"Database file #{db_file} does not exist"}
244
246
  db_file
245
247
  end
@@ -274,16 +276,70 @@ module Aspera
274
276
  end
275
277
  end
276
278
  end
279
+
280
+ # Search given option in JSON Schema tree
281
+ # @param schema [Hash] JSON Schema tree (root or sub-tree)
282
+ # @param path [Array] Path to subtree
283
+ # @param option [String] Option to search
284
+ # @return [Array,nil] with path/schema for that option
285
+ def find_option(schema, path, option)
286
+ if %w[x-cli-option x-cli-short].any?{ |i| schema[i].eql?(option)}
287
+ Log.log.debug('Special') if schema['x-cli-special']
288
+ return [path, schema]
289
+ end
290
+ if schema['type'].eql?('object')
291
+ schema['properties']&.each do |name, props|
292
+ res = find_option(props, path + [name], option)
293
+ return res unless res.nil?
294
+ end
295
+ end
296
+ return
297
+ end
298
+
299
+ # Translate `async` native command line arguments to `conf` JSON
300
+ def args_to_conf(args)
301
+ result = {}
302
+ while args.any?
303
+ option = args.shift
304
+ if option =~ /^(--[^=]+)=(.*)$/
305
+ option = ::Regexp.last_match(1) # "--toto"
306
+ args.unshift(::Regexp.last_match(2))
307
+ end
308
+ if option.eql?('--preserve-time') || option.eql?('-t')
309
+ args.unshift('--preserve-creation-time') if Environment.instance.os.eql?(Environment::OS_WINDOWS)
310
+ option = '--preserve-modification-time'
311
+ end
312
+ if option.eql?('--remote') || option.eql?('-r')
313
+ value = args.first
314
+ if (m = SCP_REMOTE_REGEX.match(value))
315
+ if m[:host]
316
+ args.shift
317
+ args.unshift("--host=#{m[:host]}")
318
+ args.unshift("--user=#{m[:user]}") if m[:user]
319
+ args.unshift(m[:path])
320
+ end
321
+ end
322
+ end
323
+ path, props = find_option(CONF_SCHEMA, [], option)
324
+ raise "Option not found: #{option}" if path.nil?
325
+ last_key = path.pop
326
+ # navigate in the current result to insert the value
327
+ current = result
328
+ path.each do |key|
329
+ current[key] ||= {}
330
+ current = current[key]
331
+ end
332
+ current[last_key] = props['x-cli-switch'] ? true : args.shift
333
+ end
334
+ return result
335
+ end
277
336
  end
278
337
  # Private stuff:
279
338
  # Read JSON schema and mapping to command line options
280
- ARGS_INSTANCE_SCHEMA = CommandLineBuilder.read_schema(__FILE__, 'args')
339
+ ARGS_INSTANCE_SCHEMA = CommandLineBuilder.read_schema(__dir__, 'args')
281
340
  ARGS_SESSION_SCHEMA = ARGS_INSTANCE_SCHEMA['properties']['sessions']['items']
282
341
  ARGS_INSTANCE_SCHEMA['properties'].delete('sessions')
283
- CONF_SCHEMA = CommandLineBuilder.read_schema(__FILE__, 'conf')
284
- CommandLineBuilder.validate_schema(ARGS_INSTANCE_SCHEMA)
285
- CommandLineBuilder.validate_schema(ARGS_SESSION_SCHEMA)
286
- CommandLineBuilder.validate_schema(CONF_SCHEMA)
342
+ CONF_SCHEMA = CommandLineBuilder.read_schema(__dir__, 'conf')
287
343
  CMDLINE_PARAMS_KEYS = %w[instance sessions].freeze
288
344
  ASYNC_ADMIN_EXECUTABLE = 'asyncadmin'
289
345
  PRIVATE_FOLDER = '.private-asp'
@@ -63,8 +63,7 @@ module Aspera
63
63
  @job_spec = job_spec
64
64
  Aspera.assert_type(@job_spec, Hash)
65
65
  @ascp_args = ascp_args.nil? ? [] : ascp_args
66
- Aspera.assert_type(@ascp_args, Array){'ascp_args'}
67
- Aspera.assert(@ascp_args.all?(String)){'all ascp arguments must be String'}
66
+ Aspera.assert_array_all(@ascp_args, String){'ascp_args'}
68
67
  @wss = wss
69
68
  @quiet = quiet
70
69
  @trusted_certs = trusted_certs.nil? ? [] : trusted_certs
@@ -208,8 +207,8 @@ module Aspera
208
207
  env_args[:args].unshift('-Y', Ascp::Installation.instance.path(:fallback_private_key))
209
208
  env_args[:args].unshift('-I', Ascp::Installation.instance.path(:fallback_certificate))
210
209
  end
211
- # disable redis in client
212
- env_args[:env]['ASPERA_TEST_REDIS_DISABLE'] = 'true'
210
+ # disable redis in client, only for ascp, this makes ascp4 fail
211
+ env_args[:env]['ASPERA_TEST_REDIS_DISABLE'] = 'true' if env_args[:name].eql?(:ascp)
213
212
  Log.log.debug{"ascp args: #{env_args}"}
214
213
  return env_args
215
214
  end
@@ -22,7 +22,7 @@ module Aspera
22
22
  # fields for WSS
23
23
  WSS_FIELDS = %w[wss_enabled wss_port].freeze
24
24
  # all fields for transport
25
- TRANSPORT_FIELDS = %w[remote_host remote_user ssh_port fasp_port].concat(WSS_FIELDS).freeze
25
+ TRANSPORT_FIELDS = (%w[remote_host] + AK_TSPEC_BASE.keys + WSS_FIELDS).freeze
26
26
  # reserved tag for Aspera
27
27
  TAG_RESERVED = 'aspera'
28
28
  class << self
@@ -49,8 +49,7 @@ module Aspera
49
49
  transfer_spec['resume_policy'] = POLICY_FIX[transfer_spec['resume_policy']] if transfer_spec.key?('resume_policy')
50
50
  end
51
51
  end
52
- SCHEMA = CommandLineBuilder.read_schema(__FILE__, 'spec')
53
- CommandLineBuilder.validate_schema(SCHEMA, ascp: true)
52
+ SCHEMA = CommandLineBuilder.read_schema(__dir__, 'spec', ascp: true)
54
53
  # define constants for enums of parameters: <parameter>_<enum>, e.g. CIPHER_AES_128, DIRECTION_SEND, ...
55
54
  SCHEMA['properties'].each do |name, description|
56
55
  next unless description['enum'].is_a?(Array)
@@ -46,14 +46,22 @@ properties:
46
46
  x-cli-option: -c
47
47
  x-cli-convert: remove_hyphen
48
48
  content_protection:
49
- description: Enable client-side encryption at rest (CSEAR).
49
+ description: >-
50
+ Enable client-side content protection (CSEAR, encryption-at-rest).
51
+
52
+ For uploads, set to `encrypt` to transfer encrypted files and store them
53
+ on the server with the extension `.aspera-env`.
54
+ (`aspera.conf` parameter `transfer_encryption_content_protection_extension`).
55
+ To download and decrypt encrypted files, set to `decrypt`
56
+
57
+ `content_protection_password` must be specified if this option is set.
50
58
  type: string
51
59
  enum:
52
60
  - encrypt
53
61
  - decrypt
54
62
  x-cli-option: --file-crypt
55
63
  content_protection_password:
56
- description: Specifies CSEAR password.
64
+ description: Password for encryption/decryption of transferred assets.
57
65
  type: string
58
66
  x-cli-envvar: ASPERA_SCP_FILEPASS
59
67
  cookie:
@@ -61,7 +69,27 @@ properties:
61
69
  type: string
62
70
  x-cli-envvar: ASPERA_SCP_COOKIE
63
71
  create_dir:
64
- description: Specifies whether to create new directories.
72
+ description: >-
73
+ Create target directory if it doesn't already exist.
74
+
75
+ If **all** the following conditions are met, then the `destination_root` specifies a filename instead of destination folder:
76
+
77
+ - `create_dir` is `false`
78
+
79
+ - A single source file is given on **command line**
80
+
81
+ - The target folder specified by `destination_root` does not exist
82
+
83
+ In all other cases, `destination_root` specifies a folder, and it is created if it does not already exist.
84
+ I.e. if **any** of those conditions is met:
85
+
86
+ - `create_dir` is `true`
87
+
88
+ - Multiple source files are provided
89
+
90
+ - List of source files are provided in a file (list or pair), default for Node API and `ascli`.
91
+
92
+ - The target folder exists
65
93
  type: boolean
66
94
  x-cli-option: -d
67
95
  x-cli-switch: true
@@ -91,6 +119,7 @@ properties:
91
119
  description: Destination root directory.
92
120
  type: string
93
121
  x-cli-special: true
122
+ $comment: Last argument on ascp command line.
94
123
  destination_root_id:
95
124
  description: >-
96
125
  The file ID of the destination root directory.
@@ -676,17 +705,6 @@ properties:
676
705
  - direct
677
706
  - transferd
678
707
  x-cli-option: -i
679
- ssh_args:
680
- $comment: "TODO: Generate multiple options and place as in comment (after -i , before user/host)"
681
- description: >-
682
- Add arguments to the command-line arguments passed to the external ssh program (implies -SSH).
683
- The arguments are inserted before any key file(s) supplied to `ascp` and before the user/host arguments.
684
- type: array
685
- x-agents:
686
- - direct
687
- - transferd
688
- x-cli-option: --ssh-arg
689
- x-cli-special: true
690
708
  symlink_policy:
691
709
  description: Handle source side symbolic links.
692
710
  type: string
@@ -749,16 +767,28 @@ properties:
749
767
  x-cli-special: true
750
768
  use_system_ssh:
751
769
  description: >-
752
- Use an external ssh program instead of the built-in libssh2 implementation to establish
770
+ Use an external `ssh` program instead of the built-in `libssh2` implementation to establish
753
771
  the connection to the remote host.
754
- The desired ssh program must be in the environment's PATH.
772
+ The desired `ssh` program must be in the environment's `PATH`.
755
773
 
756
- To enable debugging of the ssh process, supply `-DD` and `--ssh-arg=-vv` arguments to `ascp`.
757
- type: string
774
+ To enable debugging of the `ssh` process, supply `-DD` and `--ssh-arg=-vv` arguments to `ascp`.
775
+ type: boolean
758
776
  x-agents:
759
777
  - direct
760
778
  - transferd
761
779
  x-cli-option: -SSH
780
+ x-cli-switch: true
781
+ ssh_args:
782
+ $comment: "TODO: Generate multiple options and place as in comment (after -i , before user/host)"
783
+ description: >-
784
+ Add arguments to the command-line arguments passed to the external ssh program (implies -SSH).
785
+ The arguments are inserted before any key file(s) supplied to `ascp` and before the user/host arguments.
786
+ type: array
787
+ x-agents:
788
+ - direct
789
+ - transferd
790
+ x-cli-option: --ssh-arg
791
+ x-cli-special: true
762
792
  keepalive:
763
793
  description: The session is running in persistent session mode.
764
794
  type: boolean
@@ -851,7 +881,7 @@ properties:
851
881
  xfer_max_retries:
852
882
  description: >-
853
883
  Maximum number of retries, for node API initiated transfers.
854
- Shall not exceed aspera.conf `transfer_manager_max_retries` (default 5).
884
+ Shall not exceed `aspera.conf` parameter `transfer_manager_max_retries` (default 5).
855
885
  type: integer
856
886
  x-agents:
857
887
  - node
@@ -1,18 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'aspera/agent/factory'
4
+ require 'aspera/markdown'
4
5
 
5
6
  module Aspera
6
7
  module Transfer
7
- # translate transfer specification to ascp parameter list
8
+ # Generate documentation from Schema, for Transfer Spec, or async Conf spec
8
9
  class SpecDoc
9
10
  class << self
10
- # first letter of agent name symbol
11
+ # First letter of agent name symbol
11
12
  def agent_to_short(agent_sym)
12
13
  agent_sym.to_sym.eql?(:direct) ? :a : agent_sym.to_s[0].to_sym
13
14
  end
14
15
 
15
- # @param formatter [Cli::Formatter] Formatter to use, methods: special_format, check_row
16
+ # @param formatter [Cli::Formatter] Formatter to use, methods: markdown, tick, check_row
16
17
  # @param include_option [Boolean] `true` : include CLI options
17
18
  # @param agent_columns [Boolean] `true` : include agents columns
18
19
  # @param schema [Hash] The JSON spec
@@ -23,23 +24,21 @@ module Aspera
23
24
  cols.insert(-2, *AGENT_LIST.map(&:last)) if agent_columns
24
25
  rows = []
25
26
  schema['properties'].each do |name, info|
26
- if info['type'].eql?('object') && info['properties']
27
- rows.concat(man_table(formatter, include_option: include_option, agent_columns: agent_columns, schema: info).last.map { |h| h.merge(name: "#{name}.#{h[:name]}") })
28
- end
29
- # manual table
27
+ rows.concat(man_table(formatter, include_option: include_option, agent_columns: agent_columns, schema: info).last.map{ |h| h.merge(name: "#{name}.#{h[:name]}")}) if info['type'].eql?('object') && info['properties']
28
+ rows.concat(man_table(formatter, include_option: include_option, agent_columns: agent_columns, schema: info['items']).last.map{ |h| h.merge(name: "#{name}[].#{h[:name]}")}) if info['type'].eql?('array') && info['items'] && info['items']['properties']
29
+ # Manual table
30
30
  columns = {
31
31
  name: name,
32
32
  type: info['type'],
33
33
  description: []
34
34
  }
35
- # replace "back solidus" HTML entity with its text value, highlight keywords, and split lines
35
+ # Render Markdown formatting and split lines
36
36
  columns[:description] =
37
37
  info['description']
38
- .gsub('&bsol;', '\\')
39
- .gsub(/`([a-z0-9_.+-]+)`/){formatter.keyword_highlight(Regexp.last_match(1))}
38
+ .gsub(Markdown::FORMATS){formatter.markdown(Regexp.last_match)}
40
39
  .split("\n") if info.key?('description')
41
40
  columns[:description].unshift("DEPRECATED: #{info['x-deprecation']}") if info.key?('x-deprecation')
42
- # add flags for supported agents in doc
41
+ # Add flags for supported agents in doc
43
42
  agents = []
44
43
  AGENT_LIST.each do |agent_info|
45
44
  agents.push(agent_info.last) if info['x-agents'].nil? || info['x-agents'].include?(agent_info.first.to_s)
@@ -52,9 +51,9 @@ module Aspera
52
51
  else
53
52
  columns[:description].push("(#{agents.map(&:upcase).join(', ')})") unless agents.length.eql?(AGENT_LIST.length)
54
53
  end
55
- # only keep lines that are usable in supported agents
54
+ # Only keep lines that are usable in supported agents
56
55
  next false if agents.empty?
57
- columns[:description].push("Allowed values: #{info['enum'].map{ |v| formatter.keyword_highlight(v)}.join(', ')}") if info.key?('enum')
56
+ columns[:description].push("Allowed values: #{info['enum'].map{ |v| formatter.markdown("`#{v}`")}.join(', ')}") if info.key?('enum')
58
57
  if include_option
59
58
  envvar_prefix = ''
60
59
  cli_option =
@@ -70,7 +69,8 @@ module Aspera
70
69
  sep = info['x-cli-option'].start_with?('--') ? '=' : ' '
71
70
  "#{info['x-cli-option']}#{sep}#{"(#{conversion_tag})" if conversion_tag}#{arg_type}"
72
71
  end
73
- columns[:description].push("(#{'special:' if info['x-cli-special']}#{envvar_prefix}#{formatter.keyword_highlight(cli_option)})") if cli_option
72
+ short = info.key?('x-cli-short') ? "(#{info['x-cli-short']})" : nil
73
+ columns[:description].push("(#{'special:' if info['x-cli-special']}#{envvar_prefix}#{formatter.markdown("`#{cli_option}`")})#{short}") if cli_option
74
74
  end
75
75
  rows.push(formatter.check_row(columns))
76
76
  end
@@ -18,7 +18,7 @@ module Aspera
18
18
  uri = URI.parse(uri_to_read)
19
19
  case uri.scheme
20
20
  when 'http', 'https'
21
- return Rest.new(base_url: uri_to_read, redirect_max: 5).call(operation: 'GET', headers: {'Accept' => '*/*'})[:data]
21
+ return Rest.new(base_url: uri_to_read, redirect_max: 5).read(nil, headers: {'Accept' => '*/*'})
22
22
  when SCHEME_FILE, NilClass
23
23
  local_file_path = uri.path
24
24
  raise Error, 'URL shall have a path, check syntax' if local_file_path.nil?