aspera-cli 4.21.1 → 4.22.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 (105) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +1 -1
  4. data/CHANGELOG.md +52 -22
  5. data/CONTRIBUTING.md +69 -148
  6. data/README.md +929 -668
  7. data/bin/ascli +5 -14
  8. data/bin/asession +1 -3
  9. data/examples/get_proto_file.rb +4 -3
  10. data/examples/proxy.pac +20 -20
  11. data/lib/aspera/agent/base.rb +11 -5
  12. data/lib/aspera/agent/connect.rb +30 -28
  13. data/lib/aspera/agent/{alpha.rb → desktop.rb} +35 -31
  14. data/lib/aspera/agent/direct.rb +141 -121
  15. data/lib/aspera/agent/httpgw.rb +22 -26
  16. data/lib/aspera/agent/node.rb +14 -11
  17. data/lib/aspera/agent/transferd.rb +30 -19
  18. data/lib/aspera/api/alee.rb +1 -1
  19. data/lib/aspera/api/aoc.rb +6 -6
  20. data/lib/aspera/api/cos_node.rb +2 -2
  21. data/lib/aspera/api/httpgw.rb +7 -3
  22. data/lib/aspera/api/node.rb +10 -8
  23. data/lib/aspera/ascmd.rb +3 -3
  24. data/lib/aspera/ascp/installation.rb +53 -72
  25. data/lib/aspera/ascp/management.rb +1 -1
  26. data/lib/aspera/assert.rb +11 -2
  27. data/lib/aspera/cli/error.rb +2 -2
  28. data/lib/aspera/cli/extended_value.rb +46 -21
  29. data/lib/aspera/cli/formatter.rb +55 -48
  30. data/lib/aspera/cli/hints.rb +1 -1
  31. data/lib/aspera/cli/info.rb +1 -0
  32. data/lib/aspera/cli/main.rb +192 -170
  33. data/lib/aspera/cli/manager.rb +18 -18
  34. data/lib/aspera/cli/plugin.rb +23 -20
  35. data/lib/aspera/cli/plugin_factory.rb +1 -1
  36. data/lib/aspera/cli/plugins/alee.rb +1 -1
  37. data/lib/aspera/cli/plugins/aoc.rb +247 -159
  38. data/lib/aspera/cli/plugins/ats.rb +19 -17
  39. data/lib/aspera/cli/plugins/config.rb +76 -113
  40. data/lib/aspera/cli/plugins/console.rb +5 -3
  41. data/lib/aspera/cli/plugins/faspex.rb +39 -35
  42. data/lib/aspera/cli/plugins/faspex5.rb +111 -84
  43. data/lib/aspera/cli/plugins/faspio.rb +13 -1
  44. data/lib/aspera/cli/plugins/httpgw.rb +13 -1
  45. data/lib/aspera/cli/plugins/node.rb +312 -182
  46. data/lib/aspera/cli/plugins/orchestrator.rb +34 -40
  47. data/lib/aspera/cli/plugins/preview.rb +3 -3
  48. data/lib/aspera/cli/plugins/server.rb +6 -6
  49. data/lib/aspera/cli/plugins/shares.rb +5 -5
  50. data/lib/aspera/cli/sync_actions.rb +19 -18
  51. data/lib/aspera/cli/transfer_agent.rb +5 -5
  52. data/lib/aspera/cli/transfer_progress.rb +2 -2
  53. data/lib/aspera/cli/version.rb +1 -1
  54. data/lib/aspera/command_line_builder.rb +116 -95
  55. data/lib/aspera/coverage.rb +8 -5
  56. data/lib/aspera/environment.rb +26 -17
  57. data/lib/aspera/faspex_gw.rb +14 -14
  58. data/lib/aspera/faspex_postproc.rb +10 -11
  59. data/lib/aspera/hash_ext.rb +4 -14
  60. data/lib/aspera/json_rpc.rb +1 -1
  61. data/lib/aspera/keychain/encrypted_hash.rb +47 -34
  62. data/lib/aspera/keychain/factory.rb +41 -0
  63. data/lib/aspera/keychain/hashicorp_vault.rb +71 -0
  64. data/lib/aspera/keychain/macos_security.rb +19 -11
  65. data/lib/aspera/log.rb +28 -34
  66. data/lib/aspera/nagios.rb +6 -6
  67. data/lib/aspera/node_simulator.rb +8 -8
  68. data/lib/aspera/oauth/base.rb +14 -7
  69. data/lib/aspera/oauth/factory.rb +5 -6
  70. data/lib/aspera/oauth/url_json.rb +6 -6
  71. data/lib/aspera/persistency_action_once.rb +6 -4
  72. data/lib/aspera/persistency_folder.rb +2 -2
  73. data/lib/aspera/preview/generator.rb +13 -10
  74. data/lib/aspera/preview/options.rb +16 -16
  75. data/lib/aspera/preview/terminal.rb +4 -4
  76. data/lib/aspera/preview/utils.rb +15 -17
  77. data/lib/aspera/products/connect.rb +35 -1
  78. data/lib/aspera/products/{alpha.rb → desktop.rb} +3 -3
  79. data/lib/aspera/products/transferd.rb +9 -2
  80. data/lib/aspera/proxy_auto_config.rb +2 -2
  81. data/lib/aspera/rest.rb +56 -47
  82. data/lib/aspera/rest_errors_aspera.rb +1 -1
  83. data/lib/aspera/secret_hider.rb +12 -5
  84. data/lib/aspera/ssh.rb +4 -4
  85. data/lib/aspera/temp_file_manager.rb +5 -4
  86. data/lib/aspera/transfer/convert.rb +29 -0
  87. data/lib/aspera/transfer/error_info.rb +66 -66
  88. data/lib/aspera/transfer/parameters.rb +13 -68
  89. data/lib/aspera/transfer/spec.rb +5 -6
  90. data/lib/aspera/transfer/spec.schema.yaml +753 -0
  91. data/lib/aspera/transfer/spec_doc.rb +62 -0
  92. data/lib/aspera/transfer/sync.rb +23 -72
  93. data/lib/aspera/transfer/sync_instance.schema.yaml +13 -0
  94. data/lib/aspera/transfer/sync_session.schema.yaml +79 -0
  95. data/lib/aspera/transfer/uri.rb +6 -6
  96. data/lib/aspera/uri_reader.rb +18 -1
  97. data/lib/aspera/web_auth.rb +1 -1
  98. data/lib/aspera/web_server_simple.rb +53 -44
  99. data.tar.gz.sig +0 -0
  100. metadata +28 -165
  101. metadata.gz.sig +0 -0
  102. data/examples/build_exec +0 -74
  103. data/examples/build_exec_rubyc +0 -40
  104. data/examples/build_package.sh +0 -28
  105. data/lib/aspera/transfer/spec.yaml +0 -718
@@ -14,8 +14,10 @@ module Aspera
14
14
  # Access Aspera Transfer Service
15
15
  # https://developer.ibm.com/aspera/docs/ats-api-reference/creating-ats-api-keys/
16
16
  class Ats < Cli::Plugin
17
+ # columns for list of cloud providers
17
18
  CLOUD_TABLE = %w[id name].freeze
18
- def initialize(**env)
19
+ private_constant :CLOUD_TABLE
20
+ def initialize(**_)
19
21
  super
20
22
  options.declare(:ibm_api_key, 'IBM API key, see https://cloud.ibm.com/iam/apikeys')
21
23
  options.declare(:instance, 'ATS instance in ibm cloud')
@@ -68,7 +70,7 @@ module Aspera
68
70
  when 'ibm-s3'
69
71
  server_data2 = nil
70
72
  if server_data.nil?
71
- server_data2 = @ats_api_pub.all_servers.find{|s|s['id'].eql?(params['transfer_server_id'])}
73
+ server_data2 = @ats_api_pub.all_servers.find{ |s| s['id'].eql?(params['transfer_server_id'])}
72
74
  raise "no such transfer server id: #{params['transfer_server_id']}" if server_data2.nil?
73
75
  else
74
76
  server_data2 = @ats_api_pub.all_servers.find do |s|
@@ -86,15 +88,15 @@ module Aspera
86
88
  end
87
89
  end
88
90
  res = ats_api_pub_v1.create('access_keys', params)
89
- return {type: :single_object, data: res}
91
+ return Main.result_single_object(res)
90
92
  # TODO : action : modify, with "PUT"
91
93
  when :list
92
94
  params = options.get_option(:params) || {'offset' => 0, 'max_results' => 1000}
93
95
  res = ats_api_pub_v1.read('access_keys', params)
94
- return {type: :object_list, data: res['data'], fields: ['name', 'id', 'created.at', 'modified.at']}
96
+ return Main.result_object_list(res['data'], fields: ['name', 'id', 'created.at', 'modified.at'])
95
97
  when :show
96
98
  res = ats_api_pub_v1.read("access_keys/#{access_key_id}")
97
- return {type: :single_object, data: res}
99
+ return Main.result_single_object(res)
98
100
  when :modify
99
101
  params = value_create_modify(command: command)
100
102
  params['id'] = access_key_id
@@ -103,13 +105,13 @@ module Aspera
103
105
  when :entitlement
104
106
  ak = ats_api_pub_v1.read("access_keys/#{access_key_id}")
105
107
  api_bss = Api::Alee.new(ak['license']['entitlement_id'], ak['license']['customer_id'])
106
- return {type: :single_object, data: api_bss.read('entitlement')}
108
+ return Main.result_single_object(api_bss.read('entitlement'))
107
109
  when :delete
108
110
  ats_api_pub_v1.delete("access_keys/#{access_key_id}")
109
111
  return Main.result_status("deleted #{access_key_id}")
110
112
  when :node
111
113
  ak_data = ats_api_pub_v1.read("access_keys/#{access_key_id}")
112
- server_data = @ats_api_pub.all_servers.find {|i| i['id'].start_with?(ak_data['transfer_server_id'])}
114
+ server_data = @ats_api_pub.all_servers.find{ |i| i['id'].start_with?(ak_data['transfer_server_id'])}
113
115
  raise Cli::Error, 'no such server found' if server_data.nil?
114
116
  node_url = server_data['transfer_setup_url']
115
117
  api_node = Api::Node.new(
@@ -130,7 +132,7 @@ module Aspera
130
132
  username: access_key_id,
131
133
  password: config.lookup_secret(url: ats_url, username: access_key_id)
132
134
  })
133
- return {type: :single_object, data: api_ak_auth.read('servers')}
135
+ return Main.result_single_object(api_ak_auth.read('servers'))
134
136
  else Aspera.error_unexpected_value(command)
135
137
  end
136
138
  end
@@ -139,18 +141,18 @@ module Aspera
139
141
  command = options.get_next_command(%i[clouds list show])
140
142
  case command
141
143
  when :clouds
142
- return {type: :object_list, data: @ats_api_pub.cloud_names.map { |k, v| CLOUD_TABLE.zip([k, v]).to_h }}
144
+ return Main.result_object_list(@ats_api_pub.cloud_names.map{ |k, v| CLOUD_TABLE.zip([k, v]).to_h})
143
145
  when :list
144
- return {type: :object_list, data: @ats_api_pub.all_servers, fields: %w[id cloud region]}
146
+ return Main.result_object_list(@ats_api_pub.all_servers, fields: %w[id cloud region])
145
147
  when :show
146
148
  if options.get_option(:cloud) || options.get_option(:region)
147
149
  server_data = server_by_cloud_region
148
150
  else
149
151
  server_id = instance_identifier
150
- server_data = @ats_api_pub.all_servers.find {|i| i['id'].eql?(server_id)}
152
+ server_data = @ats_api_pub.all_servers.find{ |i| i['id'].eql?(server_id)}
151
153
  raise 'no such server id' if server_data.nil?
152
154
  end
153
- return {type: :single_object, data: server_data}
155
+ return Main.result_single_object(server_data)
154
156
  end
155
157
  end
156
158
 
@@ -189,16 +191,16 @@ module Aspera
189
191
  when :instances
190
192
  instances = ats_ibm_api.read('instances')
191
193
  Log.log.warn{"more instances remaining: #{instances['remaining']}"} unless instances['remaining'].to_i.eql?(0)
192
- return {type: :value_list, data: instances['data'], name: 'instance'}
194
+ return Main.result_value_list(instances['data'], name: 'instance')
193
195
  when :create
194
196
  created_key = ats_ibm_api.create('api_keys', value_create_modify(command: command, default: {}))
195
- return {type: :single_object, data: created_key}
197
+ return Main.result_single_object(created_key)
196
198
  when :list # list known api keys in ATS (this require an api_key ...)
197
199
  res = ats_ibm_api.read('api_keys', {'offset' => 0, 'max_results' => 1000})
198
- return {type: :value_list, data: res['data'], name: 'ats_id'}
200
+ return Main.result_value_list(res['data'], name: 'ats_id')
199
201
  when :show # show one of api_key in ATS
200
202
  res = ats_ibm_api.read("api_keys/#{concerned_id}")
201
- return {type: :single_object, data: res}
203
+ return Main.result_single_object(res)
202
204
  when :delete
203
205
  ats_ibm_api.delete("api_keys/#{concerned_id}")
204
206
  return Main.result_status("deleted #{concerned_id}")
@@ -225,7 +227,7 @@ module Aspera
225
227
  return execute_action_api_key
226
228
  when :aws_trust_policy
227
229
  res = ats_api_pub_v1.read('aws/trustpolicy', {region: options.get_option(:region, mandatory: true)})
228
- return {type: :single_object, data: res}
230
+ return Main.result_single_object(res)
229
231
  else Aspera.error_unexpected_value(command)
230
232
  end
231
233
  end
@@ -13,6 +13,7 @@ require 'aspera/products/transferd'
13
13
  require 'aspera/transfer/error_info'
14
14
  require 'aspera/transfer/parameters'
15
15
  require 'aspera/transfer/spec'
16
+ require 'aspera/transfer/spec_doc'
16
17
  require 'aspera/keychain/macos_security'
17
18
  require 'aspera/proxy_auto_config'
18
19
  require 'aspera/environment'
@@ -39,7 +40,6 @@ module Aspera
39
40
  ASPERA_HOME_FOLDER_NAME = '.aspera'
40
41
  # default config file
41
42
  DEFAULT_CONFIG_FILENAME = 'config.yaml'
42
- DEFAULT_VAULT_FILENAME = 'vault.bin'
43
43
  # reserved preset names
44
44
  CONF_PRESET_CONFIG = 'config'
45
45
  CONF_PRESET_VERSION = 'version'
@@ -54,8 +54,6 @@ module Aspera
54
54
  ASPERA = 'aspera'
55
55
  SERVER_COMMAND = 'server'
56
56
  DIR_SDK = 'sdk'
57
- CONNECT_WEB_URL = 'https://d3gcli72yxqn2z.cloudfront.net/connect'
58
- CONNECT_VERSIONS = 'connectversions.js' # cspell: disable-line
59
57
  DEMO_SERVER = 'demo'
60
58
  DEMO_PRESET = 'demoserver' # cspell: disable-line
61
59
  EMAIL_TEST_TEMPLATE = <<~END_OF_TEMPLATE
@@ -78,6 +76,7 @@ module Aspera
78
76
  # for testing only
79
77
  SELF_SIGNED_CERT = OpenSSL::SSL.const_get(:enon_yfirev.to_s.upcase.reverse) # cspell: disable-line
80
78
  CONF_OVERVIEW_KEYS = %w[preset parameter value].freeze
79
+ SMTP_CONF_PARAMS = %i[server tls ssl port domain username password from_name from_email].freeze
81
80
  private_constant :DEFAULT_CONFIG_FILENAME,
82
81
  :CONF_PRESET_CONFIG,
83
82
  :CONF_PRESET_VERSION,
@@ -99,7 +98,8 @@ module Aspera
99
98
  :SELF_SIGNED_CERT,
100
99
  :PERSISTENCY_FOLDER,
101
100
  :DEFAULT_PRIV_KEY_LENGTH,
102
- :CONF_OVERVIEW_KEYS
101
+ :CONF_OVERVIEW_KEYS,
102
+ :SMTP_CONF_PARAMS
103
103
 
104
104
  class << self
105
105
  def generate_rsa_private_key(path:, length: DEFAULT_PRIV_KEY_LENGTH)
@@ -144,14 +144,13 @@ module Aspera
144
144
  end
145
145
  end
146
146
 
147
- def initialize(**env)
147
+ def initialize(**_)
148
148
  # we need to defer parsing of options until we have the config file, so we can use @extend with @preset
149
149
  super
150
150
  @use_plugin_defaults = true
151
151
  @config_presets = nil
152
152
  @config_checksum_on_disk = nil
153
- @connect_versions = nil
154
- @vault = nil
153
+ @vault_instance = nil
155
154
  @pac_exec = nil
156
155
  @sdk_default_location = false
157
156
  @option_insecure = false
@@ -175,8 +174,8 @@ module Aspera
175
174
  default: self.class.default_app_main_folder(app_name: Info::CMD_NAME))
176
175
  options.parse_options!
177
176
  Log.log.debug{"#{Info::CMD_NAME} folder: #{@main_folder}"}
178
- # data persistency manager, created by config plugin
179
- @persistency = PersistencyFolder.new(File.join(@main_folder, PERSISTENCY_FOLDER))
177
+ # data persistency manager, created by config plugin, set for global object
178
+ @broker.persistency = PersistencyFolder.new(File.join(@main_folder, PERSISTENCY_FOLDER))
180
179
  # set folders for plugin lookup
181
180
  PluginFactory.instance.add_lookup_folder(self.class.gem_plugins_folder)
182
181
  PluginFactory.instance.add_lookup_folder(File.join(@main_folder, ASPERA_PLUGINS_FOLDERNAME))
@@ -189,19 +188,19 @@ module Aspera
189
188
  # read config file (set @config_presets)
190
189
  read_config_file
191
190
  # add preset handler (needed for smtp)
192
- ExtendedValue.instance.set_handler(EXTEND_PRESET, lambda{|v|preset_by_name(v)})
193
- ExtendedValue.instance.set_handler(EXTEND_VAULT, lambda{|v|vault_value(v)})
191
+ ExtendedValue.instance.set_handler(EXTEND_PRESET, lambda{ |v| preset_by_name(v)})
192
+ ExtendedValue.instance.set_handler(EXTEND_VAULT, lambda{ |v| vault_value(v)})
194
193
  # load defaults before it can be overridden
195
194
  add_plugin_default_preset(CONF_GLOBAL_SYM)
196
195
  # vault options
197
196
  options.declare(:secret, 'Secret for access keys')
198
- options.declare(:vault, 'Vault for secrets', types: Hash)
197
+ options.declare(:vault, 'Vault for secrets', types: Hash, default: {})
199
198
  options.declare(:vault_password, 'Vault password')
200
199
  options.parse_options!
201
200
  # declare generic plugin options only after handlers are declared
202
201
  Plugin.declare_generic_options(options)
203
202
  # configuration options
204
- options.declare(:no_default, 'Do not load default configuration for plugin', values: :none, short: 'N') { @use_plugin_defaults = false }
203
+ options.declare(:no_default, 'Do not load default configuration for plugin', values: :none, short: 'N'){@use_plugin_defaults = false}
205
204
  options.declare(:preset, 'Load the named option preset from current config file', short: 'P', handler: {o: self, m: :option_preset})
206
205
  options.declare(:version_check_days, 'Period in days to check new version (zero to disable)', coerce: Integer, default: DEFAULT_CHECK_NEW_VERSION_DAYS)
207
206
  options.declare(:plugin_folder, 'Folder where to find additional plugins', handler: {o: self, m: :option_plugin_folder})
@@ -213,7 +212,8 @@ module Aspera
213
212
  # Transfer SDK options
214
213
  options.declare(:ascp_path, 'Path to ascp', handler: {o: Ascp::Installation.instance, m: :ascp_path})
215
214
  options.declare(:use_product, 'Use ascp from specified product', handler: {o: self, m: :option_use_product})
216
- options.declare(:sdk_url, 'URL to get SDK', default: SpecialValues::DEF)
215
+ options.declare(:sdk_url, 'URL to get Aspera Transfer Daemon', default: SpecialValues::DEF)
216
+ options.declare(:locations_url, 'URL to get locations of Aspera Transfer Daemon', handler: {o: Ascp::Installation.instance, m: :transferd_urls})
217
217
  options.declare(:sdk_folder, 'SDK folder path', handler: {o: Products::Transferd, m: :sdk_directory})
218
218
  options.declare(:progress_bar, 'Display progress bar', values: :bool, default: Environment.terminal?)
219
219
  # email options
@@ -263,8 +263,8 @@ module Aspera
263
263
  end
264
264
  RestParameters.instance.user_agent = Info::CMD_NAME
265
265
  RestParameters.instance.progress_bar = @progress_bar
266
- RestParameters.instance.session_cb = lambda{|http_session|update_http_session(http_session)}
267
- @option_http_options.keys.select{|i|RestParameters.instance.respond_to?(i)}.each do |k|
266
+ RestParameters.instance.session_cb = lambda{ |http_session| update_http_session(http_session)}
267
+ @option_http_options.keys.select{ |i| RestParameters.instance.respond_to?(i)}.each do |k|
268
268
  method = "#{k}=".to_sym
269
269
  RestParameters.instance.send(method, @option_http_options[k])
270
270
  @option_http_options.delete(k)
@@ -301,7 +301,7 @@ module Aspera
301
301
  paths_to_add = [OpenSSL::X509::DEFAULT_CERT_DIR]
302
302
  # JRuby cert file seems not to be PEM
303
303
  paths_to_add.push(OpenSSL::X509::DEFAULT_CERT_FILE) unless defined?(JRUBY_VERSION)
304
- paths_to_add.select!{|f|File.exist?(f)}
304
+ paths_to_add.select!{ |f| File.exist?(f)}
305
305
  elsif File.file?(path)
306
306
  @certificate_store.add_file(path)
307
307
  elsif File.directory?(path)
@@ -313,8 +313,8 @@ module Aspera
313
313
  pp = [File.realpath(p)]
314
314
  if File.directory?(p)
315
315
  pp = Dir.entries(p)
316
- .map{|e|File.realpath(File.join(p, e))}
317
- .select{|entry|File.file?(entry)}
316
+ .map{ |e| File.realpath(File.join(p, e))}
317
+ .select{ |entry| File.file?(entry)}
318
318
  end
319
319
  @certificate_paths.concat(pp)
320
320
  end
@@ -439,23 +439,6 @@ module Aspera
439
439
  end if check_data[:need_update]
440
440
  end
441
441
 
442
- # retrieve structure from cloud (CDN) with all versions available
443
- def connect_versions
444
- if @connect_versions.nil?
445
- api_connect_cdn = Rest.new(base_url: CONNECT_WEB_URL)
446
- javascript = api_connect_cdn.call(operation: 'GET', subpath: CONNECT_VERSIONS)
447
- # get result on one line
448
- connect_versions_javascript = javascript[:http].body.gsub(/\r?\n\s*/, '')
449
- Log.log.debug{"javascript=[\n#{connect_versions_javascript}\n]"}
450
- # get javascript object only
451
- found = connect_versions_javascript.match(/^.*? = (.*);/)
452
- raise Cli::Error, 'Problem when getting connect versions from internet' if found.nil?
453
- all_data = JSON.parse(found[1])
454
- @connect_versions = all_data['entries']
455
- end
456
- return @connect_versions
457
- end
458
-
459
442
  # loads default parameters of plugin if no -P parameter
460
443
  # and if there is a section defined for the plugin in the "default" section
461
444
  # try to find: conf[conf["default"][plugin_str]]
@@ -538,7 +521,7 @@ module Aspera
538
521
  Aspera.assert_values(value.class, [String, Array]){'plugin folder'}
539
522
  value = [value] if value.is_a?(String)
540
523
  Aspera.assert(value.all?(String)){'plugin folder'}
541
- value.each{|f|PluginFactory.instance.add_lookup_folder(f)}
524
+ value.each{ |f| PluginFactory.instance.add_lookup_folder(f)}
542
525
  end
543
526
 
544
527
  def option_plugin_folder
@@ -568,7 +551,7 @@ module Aspera
568
551
  # files search for configuration, by default the one given by user
569
552
  search_files = [@option_config_file]
570
553
  # find first existing file (or nil)
571
- conf_file_to_load = search_files.find{|f| File.exist?(f)}
554
+ conf_file_to_load = search_files.find{ |f| File.exist?(f)}
572
555
  # if no file found, create default config
573
556
  if conf_file_to_load.nil?
574
557
  Log.log.warn{"No config file found. New configuration file: #{@option_config_file}"}
@@ -651,7 +634,7 @@ module Aspera
651
634
  found_apps.push({product: plugin_name_sym, name: app_name, url: app_url, version: 'unknown'}.merge(detection_info))
652
635
  end
653
636
  raise "No known application found at #{app_url}" if found_apps.empty?
654
- Aspera.assert(found_apps.all?{|a|a.keys.all?(Symbol)})
637
+ Aspera.assert(found_apps.all?{ |a| a.keys.all?(Symbol)})
655
638
  return found_apps
656
639
  end
657
640
 
@@ -659,12 +642,12 @@ module Aspera
659
642
  command = options.get_next_command(%i[list info version])
660
643
  if %i[info version].include?(command)
661
644
  connect_id = options.get_next_argument('id or title')
662
- one_res = connect_versions.find{|i|i['id'].eql?(connect_id) || i['title'].eql?(connect_id)}
663
- raise Cli::NoSuchIdentifier.new(:connect, connect_id) if one_res.nil?
645
+ one_res = Products::Connect.instance.versions.find{ |i| i['id'].eql?(connect_id) || i['title'].eql?(connect_id)}
646
+ raise Cli::BadIdentifier.new(:connect, connect_id) if one_res.nil?
664
647
  end
665
648
  case command
666
649
  when :list
667
- return Main.result_object_list(connect_versions, fields: %w[id title version])
650
+ return Main.result_object_list(Products::Connect.instance.versions, fields: %w[id title version])
668
651
  when :info
669
652
  one_res.delete('links')
670
653
  return Main.result_single_object(one_res)
@@ -673,19 +656,17 @@ module Aspera
673
656
  command = options.get_next_command(%i[list download open])
674
657
  if %i[download open].include?(command)
675
658
  link_title = options.get_next_argument('title or rel')
676
- one_link = all_links.find {|i| i['title'].eql?(link_title) || i['rel'].eql?(link_title)}
677
- raise 'no such value' if one_link.nil?
659
+ one_link = all_links.find{ |i| i['title'].eql?(link_title) || i['rel'].eql?(link_title)}
660
+ raise "no such value: #{link_title}" if one_link.nil?
678
661
  end
679
662
  case command
680
663
  when :list
681
664
  return Main.result_object_list(all_links)
682
665
  when :download
683
- folder_dest = transfer.destination_folder(Transfer::Spec::DIRECTION_RECEIVE)
684
- api_connect_cdn = Rest.new(base_url: CONNECT_WEB_URL)
685
- file_url = one_link['href']
686
- filename = file_url.gsub(%r{.*/}, '')
687
- api_connect_cdn.call(operation: 'GET', subpath: file_url, save_to_file: File.join(folder_dest, filename))
688
- return Main.result_status("Downloaded: #{filename}")
666
+ archive_path = one_link['href']
667
+ save_to_path = File.join(transfer.destination_folder(Transfer::Spec::DIRECTION_RECEIVE), archive_path.gsub(%r{.*/}, ''))
668
+ Products::Connect.instance.cdn_api.call(operation: 'GET', subpath: archive_path, save_to_file: save_to_path)
669
+ return Main.result_status("Downloaded: #{save_to_path}")
689
670
  when :open
690
671
  Environment.instance.open_uri(one_link['href'])
691
672
  return Main.result_status("Opened: #{one_link['href']}")
@@ -694,7 +675,7 @@ module Aspera
694
675
  end
695
676
 
696
677
  def execute_action_ascp
697
- command = options.get_next_command(%i[connect use show products info install spec errors])
678
+ command = options.get_next_command(%i[connect use show products info install spec schema errors])
698
679
  case command
699
680
  when :connect
700
681
  return execute_connect_action
@@ -712,7 +693,7 @@ module Aspera
712
693
  # add command line transfer spec
713
694
  data['ts'] = transfer.updated_ts
714
695
  # add keys
715
- DataRepository::ELEMENTS.each_with_object(data){|i, h|h[i.to_s] = DataRepository.instance.item(i)}
696
+ DataRepository::ELEMENTS.each_with_object(data){ |i, h| h[i.to_s] = DataRepository.instance.item(i)}
716
697
  # declare those as secrets
717
698
  SecretHider::ADDITIONAL_KEYS_TO_HIDE.concat(DataRepository::ELEMENTS.map(&:to_s))
718
699
  return Main.result_single_object(data)
@@ -735,9 +716,15 @@ module Aspera
735
716
  return Main.result_status("Installed #{n} version #{v}")
736
717
  when :spec
737
718
  return Main.result_object_list(
738
- Transfer::Parameters.man_table(formatter),
739
- fields: [%w[name type], Transfer::Parameters::SUPPORTED_AGENTS_SHORT.map(&:to_s), %w[description]].flatten.freeze
719
+ Transfer::SpecDoc.man_table(formatter, cli: false),
720
+ fields: Transfer::SpecDoc::TABLE_COLUMNS.map(&:to_s)
740
721
  )
722
+ when :schema
723
+ schema = Transfer::Spec::SCHEMA.merge({'$comment'=>'DO NOT EDIT, this file was generated from the YAML.'})
724
+ agent = options.get_next_argument('transfer agent name', mandatory: false)
725
+ schema['properties'] = schema['properties'].select{ |_k, v| CommandLineBuilder.supported_by_agent(agent, v)} unless agent.nil?
726
+ schema['properties'] = schema['properties'].sort.to_h
727
+ return Main.result_single_object(schema)
741
728
  when :errors
742
729
  error_data = []
743
730
  Transfer::ERROR_INFO.each_pair do |code, prop|
@@ -759,7 +746,7 @@ module Aspera
759
746
  n, v = Ascp::Installation.instance.install_sdk(url: options.get_option(:sdk_url, mandatory: true), version: version)
760
747
  return Main.result_status("Installed #{n} version #{v}")
761
748
  when :list
762
- sdk_list = Ascp::Installation.sdk_locations
749
+ sdk_list = Ascp::Installation.instance.sdk_locations
763
750
  return Main.result_object_list(
764
751
  sdk_list,
765
752
  fields: sdk_list.first.keys - ['url']
@@ -775,7 +762,7 @@ module Aspera
775
762
  PRESET_EXIST_ACTIONS = %i[show delete get unset].freeze
776
763
  # require id
777
764
  PRESET_INSTANCE_ACTIONS = %i[initialize update ask set].concat(PRESET_EXIST_ACTIONS).freeze
778
- PRESET_ALL_ACTIONS = [PRESET_GBL_ACTIONS, PRESET_INSTANCE_ACTIONS].flatten.freeze
765
+ PRESET_ALL_ACTIONS = (PRESET_GBL_ACTIONS + PRESET_INSTANCE_ACTIONS).freeze
779
766
 
780
767
  def execute_preset(action: nil, name: nil)
781
768
  action = options.get_next_command(PRESET_ALL_ACTIONS) if action.nil?
@@ -807,7 +794,7 @@ module Aspera
807
794
  value = @config_presets[name][param_name]
808
795
  raise "no such option in preset #{name} : #{param_name}" if value.nil?
809
796
  case value
810
- when Numeric, String then return {type: :text, data: ExtendedValue.instance.evaluate(value.to_s)}
797
+ when Numeric, String then return Main.result_text(ExtendedValue.instance.evaluate(value.to_s))
811
798
  end
812
799
  return Main.result_single_object(value)
813
800
  when :unset
@@ -940,10 +927,10 @@ module Aspera
940
927
  when :only
941
928
  return Main.result_status(remote_chain.first.to_pem)
942
929
  when :name
943
- return Main.result_status(remote_chain.first.subject.to_a.find { |name, _, _| name == 'CN' }[1])
930
+ return Main.result_status(remote_chain.first.subject.to_a.find{ |name, _, _| name == 'CN'}[1])
944
931
  end
945
932
  when :echo # display the content of a value given on command line
946
- return Formatter.auto_type(options.get_next_argument('value', validation: nil))
933
+ return Main.result_auto(options.get_next_argument('value', validation: nil))
947
934
  when :download
948
935
  file_url = options.get_next_argument('source URL').chomp
949
936
  file_dest = options.get_next_argument('file path', mandatory: false)
@@ -1072,9 +1059,10 @@ module Aspera
1072
1059
  else
1073
1060
  formatter.display_status('Multiple applications detected, please select from:')
1074
1061
  formatter.display_results(type: :object_list, data: apps, fields: %w[product url version])
1075
- answer = options.prompt_user_input_in_list('product', apps.map{|a|a[:product]})
1076
- apps.find{|a|a[:product].eql?(answer)}
1062
+ answer = options.prompt_user_input_in_list('product', apps.map{ |a| a[:product]})
1063
+ apps.find{ |a| a[:product].eql?(answer)}
1077
1064
  end
1065
+ wiz_preset_name = options.get_next_argument('preset name', default: '')
1078
1066
  Log.log.debug{Log.dump(:identification, identification)}
1079
1067
  wiz_url = identification[:url]
1080
1068
  formatter.display_status("Using: #{identification[:name]} at #{wiz_url}".bold)
@@ -1099,7 +1087,6 @@ module Aspera
1099
1087
  if private_key_path.nil?
1100
1088
  formatter.display_status('Please provide the path to your private RSA key, or nothing to generate one:')
1101
1089
  private_key_path = options.get_option(:key_path, mandatory: true).to_s
1102
- # private_key_path = File.expand_path(private_key_path)
1103
1090
  end
1104
1091
  # else generate path
1105
1092
  if private_key_path.empty?
@@ -1124,13 +1111,12 @@ module Aspera
1124
1111
  Log.log.debug{"wizard result: #{wizard_result}"}
1125
1112
  Aspera.assert(WIZARD_RESULT_KEYS.eql?(wizard_result.keys.sort)){"missing or extra keys in wizard result: #{wizard_result.keys}"}
1126
1113
  # get preset name from user or default
1127
- wiz_preset_name = nil
1128
- if wiz_preset_name.nil?
1114
+ if wiz_preset_name.empty?
1129
1115
  elements = [
1130
1116
  identification[:product],
1131
1117
  URI.parse(wiz_url).host
1132
1118
  ]
1133
- elements.push(options.get_option(:username, mandatory: true)) unless wizard_result[:preset_value].key?(:link)
1119
+ elements.push(options.get_option(:username, mandatory: true)) unless wizard_result[:preset_value].key?(:link) rescue nil
1134
1120
  wiz_preset_name = elements.join('_').strip.downcase.gsub(/[^a-z0-9]/, '_').squeeze('_')
1135
1121
  end
1136
1122
  # test mode does not change conf file
@@ -1159,9 +1145,12 @@ module Aspera
1159
1145
  # @return [Hash] email server setting with defaults if not defined
1160
1146
  def email_settings
1161
1147
  smtp = options.get_option(:smtp, mandatory: true)
1162
- # change string keys into symbol keys
1148
+ # change keys from string into symbol
1163
1149
  smtp = smtp.symbolize_keys
1150
+ unsupported = smtp.keys - SMTP_CONF_PARAMS
1151
+ raise Cli::Error, "Unsupported SMTP parameter: #{unsupported.join(', ')}, use: #{SMTP_CONF_PARAMS.join(', ')}" unless unsupported.empty?
1164
1152
  # defaults
1153
+ # smtp[:ssl] = nil (false)
1165
1154
  smtp[:tls] = !smtp[:ssl] unless smtp.key?(:tls)
1166
1155
  smtp[:port] ||= if smtp[:tls]
1167
1156
  587
@@ -1190,8 +1179,8 @@ module Aspera
1190
1179
  mail_conf = email_settings
1191
1180
  values[:from_name] ||= mail_conf[:from_name]
1192
1181
  values[:from_email] ||= mail_conf[:from_email]
1193
- %i[from_name from_email].each do |n|
1194
- Aspera.assert(values.key?(n)){"Missing email parameter: #{n}"}
1182
+ %i[to from_email].each do |n|
1183
+ Aspera.assert_type(values[n], String){"Missing email parameter: #{n} in config"}
1195
1184
  end
1196
1185
  start_options = [mail_conf[:domain]]
1197
1186
  start_options.push(mail_conf[:username], mail_conf[:password], :login) if mail_conf.key?(:username) && mail_conf.key?(:password)
@@ -1225,7 +1214,7 @@ module Aspera
1225
1214
  Environment.restrict_file_access(@main_folder)
1226
1215
  Log.log.info{"Writing #{@option_config_file}"}
1227
1216
  formatter.display_status('Saving config file.')
1228
- Environment.write_file_restricted(@option_config_file, force: true) {@config_presets.to_yaml}
1217
+ Environment.write_file_restricted(@option_config_file, force: true){@config_presets.to_yaml}
1229
1218
  @config_checksum_on_disk = current_checksum
1230
1219
  return true
1231
1220
  end
@@ -1255,15 +1244,15 @@ module Aspera
1255
1244
  return nil
1256
1245
  end
1257
1246
 
1258
- # TODO: delete: ALLOWED_KEYS = %i[password username description].freeze
1259
1247
  # @return [Hash] result of execution of vault command
1260
1248
  def execute_vault
1261
1249
  command = options.get_next_command(%i[info list show create delete password])
1262
1250
  case command
1263
1251
  when :info
1264
- return Main.result_single_object(vault_info)
1252
+ return Main.result_single_object(vault.info)
1265
1253
  when :list
1266
- return Main.result_object_list(vault.list, fields: %w(label url username password description))
1254
+ # , fields: %w(label url username password description)
1255
+ return Main.result_object_list(vault.list)
1267
1256
  when :show
1268
1257
  return Main.result_single_object(vault.get(label: options.get_next_argument('label')))
1269
1258
  when :create
@@ -1272,17 +1261,15 @@ module Aspera
1272
1261
  info = info.symbolize_keys
1273
1262
  info[:label] = label
1274
1263
  vault.set(info)
1275
- return Main.result_status('Password added')
1264
+ return Main.result_status('Secret added')
1276
1265
  when :delete
1277
1266
  label_to_delete = options.get_next_argument('label')
1278
1267
  vault.delete(label: label_to_delete)
1279
- return Main.result_status("Entry deleted: #{label_to_delete}")
1268
+ return Main.result_status("Secret deleted: #{label_to_delete}")
1280
1269
  when :password
1281
- Aspera.assert(vault.respond_to?(:password=)){'Vault does not support password change'}
1282
- new_password = options.get_next_argument('new_password')
1283
- vault.password = new_password
1284
- vault.save
1285
- return Main.result_status('Password updated')
1270
+ Aspera.assert(vault.respond_to?(:change_password)){'Vault does not support password change'}
1271
+ vault.change_password(options.get_next_argument('new_password'))
1272
+ return Main.result_status('Vault password updated')
1286
1273
  end
1287
1274
  end
1288
1275
 
@@ -1297,42 +1284,18 @@ module Aspera
1297
1284
  return value
1298
1285
  end
1299
1286
 
1300
- def vault_info
1301
- info = options.get_option(:vault) || {}
1302
- info = info.symbolize_keys
1303
- info[:type] ||= 'file'
1304
- info[:name] ||= (info[:type].eql?('file') ? DEFAULT_VAULT_FILENAME : Info::CMD_NAME)
1305
- Aspera.assert(info.keys.sort == %i[name type]) {"vault info shall have exactly keys 'type' and 'name'"}
1306
- Aspera.assert(info.values.all?(String)){'vault info shall have only string values'}
1307
- info[:password] = options.get_option(:vault_password, mandatory: true)
1308
- return info
1309
- end
1310
-
1311
1287
  # @return [Object] vault, from options or cache
1312
1288
  def vault
1313
- if @vault.nil?
1314
- info = vault_info
1315
- case info[:type]
1316
- when 'file'
1317
- # this module requires comilation, so it is optinal
1318
- require 'aspera/keychain/encrypted_hash'
1319
- # absolute_path? introduced in ruby 2.7
1320
- @vault = Keychain::EncryptedHash.new(
1321
- info[:name].eql?(File.absolute_path(info[:name])) ? info[:name] : File.join(@main_folder, info[:name]),
1322
- info[:password])
1323
- when 'system'
1324
- case Environment.os
1325
- when Environment::OS_MACOS
1326
- @vault = Keychain::MacosSystem.new(info[:name], info[:password])
1327
- else
1328
- raise 'not implemented for this OS'
1329
- end
1330
- else
1331
- raise Cli::BadArgument, "Unknown vault type: #{info[:type]}"
1332
- end
1333
- end
1334
- raise 'No vault defined' if @vault.nil?
1335
- @vault
1289
+ return @vault_instance unless @vault_instance.nil?
1290
+ info = options.get_option(:vault).symbolize_keys
1291
+ info[:type] ||= 'file'
1292
+ require 'aspera/keychain/factory'
1293
+ @vault_instance = Keychain::Factory.create(
1294
+ info,
1295
+ Info::CMD_NAME,
1296
+ @main_folder,
1297
+ options.get_option(:vault_password)
1298
+ )
1336
1299
  end
1337
1300
 
1338
1301
  def execute_test
@@ -40,7 +40,9 @@ module Aspera
40
40
  return nil
41
41
  end
42
42
 
43
- def wizard(object:, private_key_path: nil, pub_key_pem: nil)
43
+ # @param object [Plugin] An instance of this class
44
+ # @return [Hash] :preset_value, :test_args
45
+ def wizard(object:)
44
46
  options = object.options
45
47
  return {
46
48
  preset_value: {
@@ -84,11 +86,11 @@ module Aspera
84
86
  command = options.get_next_command(%i[list submit])
85
87
  case command
86
88
  when :list
87
- return {type: :object_list, data: api_console.read('smart_transfers')}
89
+ return Main.result_object_list(api_console.read('smart_transfers'))
88
90
  when :submit
89
91
  smart_id = options.get_next_argument('smart_id')
90
92
  params = options.get_next_argument('transfer parameters')
91
- return {type: :object_list, data: api_console.create("smart_transfers/#{smart_id}", params)}
93
+ return Main.result_object_list(api_console.create("smart_transfers/#{smart_id}", params))
92
94
  end
93
95
  when :current
94
96
  command = options.get_next_command([:list])