aspera-cli 4.24.2 → 4.25.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +1070 -758
- data/CONTRIBUTING.md +130 -115
- data/README.md +961 -623
- data/lib/aspera/agent/direct.rb +14 -12
- data/lib/aspera/agent/factory.rb +9 -6
- data/lib/aspera/agent/transferd.rb +8 -8
- data/lib/aspera/api/aoc.rb +104 -67
- data/lib/aspera/api/ats.rb +1 -0
- data/lib/aspera/api/cos_node.rb +3 -2
- data/lib/aspera/api/faspex.rb +17 -10
- data/lib/aspera/api/node.rb +10 -12
- data/lib/aspera/ascmd.rb +2 -3
- data/lib/aspera/ascp/installation.rb +60 -46
- data/lib/aspera/ascp/management.rb +9 -5
- data/lib/aspera/assert.rb +28 -6
- data/lib/aspera/cli/error.rb +4 -2
- data/lib/aspera/cli/extended_value.rb +94 -62
- data/lib/aspera/cli/formatter.rb +44 -58
- data/lib/aspera/cli/main.rb +21 -14
- data/lib/aspera/cli/manager.rb +317 -250
- data/lib/aspera/cli/plugins/alee.rb +3 -3
- data/lib/aspera/cli/plugins/aoc.rb +139 -78
- data/lib/aspera/cli/plugins/ats.rb +30 -36
- data/lib/aspera/cli/plugins/base.rb +68 -55
- data/lib/aspera/cli/plugins/config.rb +90 -100
- data/lib/aspera/cli/plugins/console.rb +15 -9
- data/lib/aspera/cli/plugins/cos.rb +1 -1
- data/lib/aspera/cli/plugins/faspex.rb +39 -30
- data/lib/aspera/cli/plugins/faspex5.rb +57 -52
- data/lib/aspera/cli/plugins/faspio.rb +10 -7
- data/lib/aspera/cli/plugins/httpgw.rb +3 -2
- data/lib/aspera/cli/plugins/node.rb +140 -125
- data/lib/aspera/cli/plugins/oauth.rb +13 -12
- data/lib/aspera/cli/plugins/orchestrator.rb +116 -33
- data/lib/aspera/cli/plugins/preview.rb +28 -48
- data/lib/aspera/cli/plugins/server.rb +9 -10
- data/lib/aspera/cli/plugins/shares.rb +77 -43
- data/lib/aspera/cli/sync_actions.rb +49 -38
- data/lib/aspera/cli/transfer_agent.rb +16 -35
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +8 -5
- data/lib/aspera/command_line_builder.rb +24 -21
- data/lib/aspera/coverage.rb +6 -2
- data/lib/aspera/dot_container.rb +108 -0
- data/lib/aspera/environment.rb +71 -84
- data/lib/aspera/faspex_gw.rb +1 -1
- data/lib/aspera/faspex_postproc.rb +1 -1
- data/lib/aspera/id_generator.rb +7 -10
- data/lib/aspera/keychain/factory.rb +1 -2
- data/lib/aspera/keychain/macos_security.rb +2 -2
- data/lib/aspera/log.rb +2 -1
- data/lib/aspera/markdown.rb +31 -0
- data/lib/aspera/nagios.rb +6 -5
- data/lib/aspera/oauth/base.rb +41 -64
- data/lib/aspera/oauth/factory.rb +6 -7
- data/lib/aspera/oauth/generic.rb +1 -1
- data/lib/aspera/oauth/jwt.rb +1 -1
- data/lib/aspera/oauth/url_json.rb +6 -4
- data/lib/aspera/oauth/web.rb +2 -2
- data/lib/aspera/preview/file_types.rb +24 -38
- data/lib/aspera/preview/terminal.rb +95 -29
- data/lib/aspera/preview/utils.rb +6 -5
- data/lib/aspera/products/connect.rb +3 -3
- data/lib/aspera/rest.rb +54 -39
- data/lib/aspera/rest_error_analyzer.rb +4 -4
- data/lib/aspera/ssh.rb +10 -6
- data/lib/aspera/ssl.rb +41 -0
- data/lib/aspera/sync/conf.schema.yaml +184 -36
- data/lib/aspera/sync/database.rb +2 -1
- data/lib/aspera/sync/operations.rb +128 -72
- data/lib/aspera/transfer/parameters.rb +9 -10
- data/lib/aspera/transfer/spec.rb +2 -3
- data/lib/aspera/transfer/spec.schema.yaml +52 -22
- data/lib/aspera/transfer/spec_doc.rb +20 -30
- data/lib/aspera/uri_reader.rb +18 -4
- data/lib/transferd_pb.rb +2 -2
- data.tar.gz.sig +0 -0
- metadata +34 -6
- metadata.gz.sig +0 -0
|
@@ -18,15 +18,22 @@ module Aspera
|
|
|
18
18
|
MAX_ITEMS = 'max'
|
|
19
19
|
# Special query parameter: `pmax`: max number of pages for list command
|
|
20
20
|
MAX_PAGES = 'pmax'
|
|
21
|
-
# Special identifier format: look for this name to find where supported
|
|
22
|
-
REGEX_LOOKUP_ID_BY_FIELD = /^%([^:]+):(.*)$/
|
|
23
21
|
|
|
24
22
|
class << self
|
|
25
23
|
def declare_options(options)
|
|
26
|
-
options.declare(:query, 'Additional filter for for some commands (list/delete)',
|
|
24
|
+
options.declare(:query, 'Additional filter for for some commands (list/delete)', allowed: [Hash, Array, NilClass])
|
|
27
25
|
options.declare(:property, 'Name of property to set (modify operation)')
|
|
28
|
-
options.declare(:bulk, 'Bulk operation (only some)',
|
|
29
|
-
options.declare(:bfail, 'Bulk operation error handling',
|
|
26
|
+
options.declare(:bulk, 'Bulk operation (only some)', allowed: Allowed::TYPES_BOOLEAN, default: false)
|
|
27
|
+
options.declare(:bfail, 'Bulk operation error handling', allowed: Allowed::TYPES_BOOLEAN, default: true)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @return [Hash,NilClass] `{field:,value:}` if identifier is a percent selector, else `nil`
|
|
31
|
+
def percent_selector(identifier)
|
|
32
|
+
Aspera.assert_type(identifier, String)
|
|
33
|
+
if (m = identifier.match(REGEX_LOOKUP_ID_BY_FIELD))
|
|
34
|
+
return {field: m[1], value: ExtendedValue.instance.evaluate(m[2], context: "percent selector: #{m[1]}")}
|
|
35
|
+
end
|
|
36
|
+
return
|
|
30
37
|
end
|
|
31
38
|
end
|
|
32
39
|
|
|
@@ -41,10 +48,15 @@ module Aspera
|
|
|
41
48
|
# Global objects
|
|
42
49
|
attr_reader :context
|
|
43
50
|
|
|
51
|
+
# @return [Manager]
|
|
44
52
|
def options; @context.options; end
|
|
53
|
+
# @return [TransferAgent]
|
|
45
54
|
def transfer; @context.transfer; end
|
|
55
|
+
# @return [Config]
|
|
46
56
|
def config; @context.config; end
|
|
57
|
+
# @return [Formatter]
|
|
47
58
|
def formatter; @context.formatter; end
|
|
59
|
+
# @return [PersistencyFolder]
|
|
48
60
|
def persistency; @context.persistency; end
|
|
49
61
|
|
|
50
62
|
def add_manual_header(has_options = true)
|
|
@@ -55,26 +67,17 @@ module Aspera
|
|
|
55
67
|
options.parser.separator('OPTIONS:') if has_options
|
|
56
68
|
end
|
|
57
69
|
|
|
58
|
-
#
|
|
59
|
-
# ... folder browse _call_instance_identifier
|
|
70
|
+
# Resource identifier as positional parameter
|
|
60
71
|
#
|
|
61
72
|
# @param description [String] description of the identifier
|
|
62
|
-
# @param as_option [Symbol] option name to use if identifier is an option
|
|
63
73
|
# @param block [Proc] block to search for identifier based on attribute value
|
|
64
74
|
# @return [String, Array] identifier or list of ids
|
|
65
|
-
def instance_identifier(description: 'identifier',
|
|
66
|
-
if
|
|
67
|
-
res_id = options.get_next_argument(description, multiple: options.get_option(:bulk)) if res_id.nil?
|
|
68
|
-
else
|
|
69
|
-
res_id = options.get_option(as_option)
|
|
70
|
-
end
|
|
75
|
+
def instance_identifier(description: 'identifier', &block)
|
|
76
|
+
res_id = options.get_next_argument(description, multiple: options.get_option(:bulk)) if res_id.nil?
|
|
71
77
|
# Can be an Array
|
|
72
|
-
if res_id.is_a?(String) && (m =
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
else
|
|
76
|
-
raise Cli::BadArgument, "Percent syntax for #{description} not supported in this context"
|
|
77
|
-
end
|
|
78
|
+
if res_id.is_a?(String) && (m = Base.percent_selector(res_id))
|
|
79
|
+
Aspera.assert(block, type: Cli::BadArgument){"Percent syntax for #{description} not supported in this context"}
|
|
80
|
+
res_id = yield(m[:field], m[:value])
|
|
78
81
|
end
|
|
79
82
|
return res_id
|
|
80
83
|
end
|
|
@@ -133,12 +136,12 @@ module Aspera
|
|
|
133
136
|
# @param command [Symbol] command to execute: create show list modify delete
|
|
134
137
|
# @param display_fields [Array] fields to display by default
|
|
135
138
|
# @param items_key [String] result is in a sub key of the json
|
|
136
|
-
# @param delete_style [String]
|
|
137
|
-
# @param id_as_arg [String]
|
|
138
|
-
# @param is_singleton [Boolean]
|
|
139
|
-
# @param tclo [
|
|
140
|
-
# @param block [Proc]
|
|
141
|
-
# @return
|
|
139
|
+
# @param delete_style [String] If set, the delete operation by array in payload
|
|
140
|
+
# @param id_as_arg [String] If set, the id is provided as url argument ?<id_as_arg>=<id>
|
|
141
|
+
# @param is_singleton [Boolean] If `true`, entity is the full path to the resource
|
|
142
|
+
# @param tclo [Boolean] If `true`, :list use paging with total_count, limit, offset
|
|
143
|
+
# @param block [Proc] Block to search for identifier based on attribute value
|
|
144
|
+
# @return [Hash] Result suitable for CLI result
|
|
142
145
|
def entity_execute(
|
|
143
146
|
api:,
|
|
144
147
|
entity:,
|
|
@@ -172,12 +175,11 @@ module Aspera
|
|
|
172
175
|
if !delete_style.nil?
|
|
173
176
|
one_res_id = [one_res_id] unless one_res_id.is_a?(Array)
|
|
174
177
|
Aspera.assert_type(one_res_id, Array, type: Cli::BadArgument)
|
|
175
|
-
api.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
+
api.delete(
|
|
179
|
+
entity,
|
|
180
|
+
nil,
|
|
178
181
|
content_type: Rest::MIME_JSON,
|
|
179
|
-
body: {delete_style => one_res_id}
|
|
180
|
-
headers: {'Accept' => Rest::MIME_JSON}
|
|
182
|
+
body: {delete_style => one_res_id}
|
|
181
183
|
)
|
|
182
184
|
return Main.result_status('deleted')
|
|
183
185
|
end
|
|
@@ -192,11 +194,10 @@ module Aspera
|
|
|
192
194
|
data, total = list_entities_limit_offset_total_count(api: api, entity:, items_key: items_key, query: query_read_delete(default: list_query))
|
|
193
195
|
return Main.result_object_list(data, total: total, fields: display_fields)
|
|
194
196
|
end
|
|
195
|
-
|
|
196
|
-
return Main.result_empty if
|
|
197
|
-
data = resp[:data]
|
|
197
|
+
data, http = api.read(entity, query_read_delete, ret: :both)
|
|
198
|
+
return Main.result_empty if http.code == '204'
|
|
198
199
|
# TODO: not generic : which application is this for ?
|
|
199
|
-
if
|
|
200
|
+
if http['Content-Type'].start_with?('application/vnd.api+json')
|
|
200
201
|
Log.log.debug('is vnd.api')
|
|
201
202
|
data = data[entity]
|
|
202
203
|
end
|
|
@@ -223,9 +224,8 @@ module Aspera
|
|
|
223
224
|
|
|
224
225
|
# Query parameters in URL suitable for REST: list/GET and delete/DELETE
|
|
225
226
|
def query_read_delete(default: nil)
|
|
226
|
-
query = options.get_option(:query)
|
|
227
227
|
# Dup default, as it could be frozen
|
|
228
|
-
query =
|
|
228
|
+
query = options.get_option(:query) || default.dup
|
|
229
229
|
Log.log.debug{"query_read_delete=#{query}".bg_red}
|
|
230
230
|
begin
|
|
231
231
|
# Check it is suitable
|
|
@@ -249,7 +249,7 @@ module Aspera
|
|
|
249
249
|
value = default if value.nil?
|
|
250
250
|
unless type.nil?
|
|
251
251
|
type = [type] unless type.is_a?(Array)
|
|
252
|
-
Aspera.
|
|
252
|
+
Aspera.assert_array_all(type, Class){'check types'}
|
|
253
253
|
if bulk
|
|
254
254
|
Aspera.assert_type(value, Array, type: Cli::BadArgument)
|
|
255
255
|
value.each do |v|
|
|
@@ -263,10 +263,10 @@ module Aspera
|
|
|
263
263
|
end
|
|
264
264
|
|
|
265
265
|
# Get a (full or partial) list of all entities of a given type with query: offset/limit
|
|
266
|
-
# @param
|
|
267
|
-
# @param
|
|
268
|
-
# @param
|
|
269
|
-
# @param
|
|
266
|
+
# @param api [Rest] API object
|
|
267
|
+
# @param entity [String,Symbol] API endpoint of entity to list
|
|
268
|
+
# @param items_key [String] Key in the result to get the list of items (Default: same as `entity`)
|
|
269
|
+
# @param query [Hash,nil] Additional query parameters
|
|
270
270
|
# @return [Array] items, total_count
|
|
271
271
|
def list_entities_limit_offset_total_count(
|
|
272
272
|
api:,
|
|
@@ -309,26 +309,39 @@ module Aspera
|
|
|
309
309
|
return result, total_count
|
|
310
310
|
end
|
|
311
311
|
|
|
312
|
-
# Lookup an entity id from its name
|
|
313
|
-
#
|
|
314
|
-
# @param
|
|
315
|
-
# @param
|
|
316
|
-
# @param
|
|
317
|
-
# @param
|
|
312
|
+
# Lookup an entity id from its name.
|
|
313
|
+
# Uses query `q` if `query` is `:default` and `field` is `name`.
|
|
314
|
+
# @param entity [String] Type of entity to lookup, by default it is the path, and it is also the field name in result
|
|
315
|
+
# @param value [String] Value to lookup
|
|
316
|
+
# @param field [String] Field to match, by default it is `'name'`
|
|
317
|
+
# @param items_key [String] Key in the result to get the list of items (override entity)
|
|
318
|
+
# @param query [Hash] Additional query parameters (Default: `:default`)
|
|
318
319
|
def lookup_entity_by_field(api:, entity:, value:, field: 'name', items_key: nil, query: :default)
|
|
319
320
|
if query.eql?(:default)
|
|
320
321
|
Aspera.assert(field.eql?('name')){'Default query is on name only'}
|
|
321
322
|
query = {'q'=> value}
|
|
322
323
|
end
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
324
|
+
lookup_entity_generic(entity: entity, field: field, value: value){list_entities_limit_offset_total_count(api: api, entity: entity, items_key: items_key, query: query).first}
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# Lookup entity by field and value. Extract single result from list of result returned by block.
|
|
328
|
+
# @param entity [String] Type of entity to lookup, by default it is the path, and it is also the field name in result
|
|
329
|
+
# @param value [String] Value to lookup
|
|
330
|
+
# @param field [String] Field to match, by default it is `'name'`
|
|
331
|
+
# @param block [Proc] Get list of entity matching query.
|
|
332
|
+
def lookup_entity_generic(entity:, value:, field: 'name', &block)
|
|
333
|
+
Aspera.assert(block_given?)
|
|
334
|
+
found = yield
|
|
335
|
+
Aspera.assert_array_all(found, Hash)
|
|
336
|
+
found = found.select{ |i| i[field].eql?(value)}
|
|
337
|
+
return found.first if found.length.eql?(1)
|
|
338
|
+
raise Cli::BadIdentifier.new(entity, value, field: field, count: found.length)
|
|
329
339
|
end
|
|
340
|
+
|
|
330
341
|
PER_PAGE_DEFAULT = 1000
|
|
331
|
-
|
|
342
|
+
# Percent selector: select by this field for this value
|
|
343
|
+
REGEX_LOOKUP_ID_BY_FIELD = /^%([^:]+):(.*)$/
|
|
344
|
+
private_constant :PER_PAGE_DEFAULT, :REGEX_LOOKUP_ID_BY_FIELD
|
|
332
345
|
end
|
|
333
346
|
end
|
|
334
347
|
end
|
|
@@ -10,6 +10,7 @@ require 'aspera/cli/formatter'
|
|
|
10
10
|
require 'aspera/cli/info'
|
|
11
11
|
require 'aspera/cli/transfer_progress'
|
|
12
12
|
require 'aspera/cli/wizard'
|
|
13
|
+
require 'aspera/cli/sync_actions'
|
|
13
14
|
require 'aspera/ascp/installation'
|
|
14
15
|
require 'aspera/sync/operations'
|
|
15
16
|
require 'aspera/products/transferd'
|
|
@@ -29,7 +30,9 @@ require 'aspera/oauth/jwt'
|
|
|
29
30
|
require 'aspera/log'
|
|
30
31
|
require 'aspera/assert'
|
|
31
32
|
require 'aspera/oauth'
|
|
33
|
+
require 'aspera/ssl'
|
|
32
34
|
require 'openssl'
|
|
35
|
+
require 'digest'
|
|
33
36
|
require 'open3'
|
|
34
37
|
require 'date'
|
|
35
38
|
require 'erb'
|
|
@@ -39,6 +42,8 @@ module Aspera
|
|
|
39
42
|
module Plugins
|
|
40
43
|
# Manage the CLI config file
|
|
41
44
|
class Config < Base
|
|
45
|
+
include SyncActions
|
|
46
|
+
|
|
42
47
|
class << self
|
|
43
48
|
# Folder containing plugins in the gem's main folder
|
|
44
49
|
def gem_plugins_folder
|
|
@@ -76,7 +81,7 @@ module Aspera
|
|
|
76
81
|
# We need to defer parsing of options until we have the config file, so we can use @extend with @preset
|
|
77
82
|
super
|
|
78
83
|
@use_plugin_defaults = true
|
|
79
|
-
@config_presets =
|
|
84
|
+
@config_presets = {}
|
|
80
85
|
@config_checksum_on_disk = nil
|
|
81
86
|
@vault_instance = nil
|
|
82
87
|
@pac_exec = nil
|
|
@@ -89,7 +94,7 @@ module Aspera
|
|
|
89
94
|
@option_cache_tokens = true
|
|
90
95
|
@main_folder = nil
|
|
91
96
|
@option_config_file = nil
|
|
92
|
-
# Store is used for ruby https
|
|
97
|
+
# Store is used for ruby https (OpenSSL::X509::Store)
|
|
93
98
|
@certificate_store = nil
|
|
94
99
|
# Paths are used for ascp
|
|
95
100
|
@certificate_paths = nil
|
|
@@ -98,7 +103,6 @@ module Aspera
|
|
|
98
103
|
options.declare(
|
|
99
104
|
:home, 'Home folder for tool',
|
|
100
105
|
handler: {o: self, m: :main_folder},
|
|
101
|
-
types: String,
|
|
102
106
|
default: self.class.default_app_main_folder(app_name: Info::CMD_NAME)
|
|
103
107
|
)
|
|
104
108
|
options.parse_options!
|
|
@@ -118,65 +122,49 @@ module Aspera
|
|
|
118
122
|
# Read config file (set @config_presets)
|
|
119
123
|
read_config_file
|
|
120
124
|
# Add preset handler (needed for smtp)
|
|
121
|
-
ExtendedValue.instance.
|
|
122
|
-
ExtendedValue.instance.
|
|
125
|
+
ExtendedValue.instance.on(EXTEND_PRESET){ |v| preset_by_name(v)}
|
|
126
|
+
ExtendedValue.instance.on(EXTEND_VAULT){ |v| vault_value(v)}
|
|
127
|
+
ExtendedValue.instance.on(EXTEND_ARGS){ |v| options.args_as_extended(v)}
|
|
123
128
|
# Load defaults before it can be overridden
|
|
124
129
|
add_plugin_default_preset(CONF_GLOBAL_SYM)
|
|
125
130
|
# Vault options
|
|
126
131
|
options.declare(:secret, 'Secret for access keys')
|
|
127
|
-
options.declare(:vault, 'Vault for secrets',
|
|
132
|
+
options.declare(:vault, 'Vault for secrets', allowed: Hash)
|
|
128
133
|
options.declare(:vault_password, 'Vault password')
|
|
129
134
|
options.parse_options!
|
|
130
135
|
# Declare generic plugin options only after handlers are declared
|
|
131
136
|
Base.declare_options(options)
|
|
132
137
|
# Configuration options
|
|
133
|
-
options.declare(:no_default, 'Do not load default configuration for plugin',
|
|
138
|
+
options.declare(:no_default, 'Do not load default configuration for plugin', allowed: Allowed::TYPES_NONE, short: 'N'){@use_plugin_defaults = false}
|
|
134
139
|
options.declare(:preset, 'Load the named option preset from current config file', short: 'P', handler: {o: self, m: :option_preset})
|
|
135
|
-
options.declare(:version_check_days, 'Period in days to check new version (zero to disable)',
|
|
140
|
+
options.declare(:version_check_days, 'Period in days to check new version (zero to disable)', allowed: Allowed::TYPES_INTEGER, default: DEFAULT_CHECK_NEW_VERSION_DAYS)
|
|
136
141
|
options.declare(:plugin_folder, 'Folder where to find additional plugins', handler: {o: self, m: :option_plugin_folder})
|
|
137
142
|
# Declare wizard options
|
|
138
143
|
@wizard = Wizard.new(self, @main_folder)
|
|
139
144
|
# Transfer SDK options
|
|
140
|
-
options.declare(:ascp_path, 'Ascp: Path to ascp', handler: {o: Ascp::Installation.instance, m: :ascp_path})
|
|
141
|
-
options.declare(:use_product, 'Ascp: Use ascp from specified product', handler: {o: self, m: :option_use_product})
|
|
142
145
|
options.declare(:sdk_url, 'Ascp: URL to get Aspera Transfer Executables', default: SpecialValues::DEF)
|
|
143
|
-
options.
|
|
144
|
-
|
|
145
|
-
options.declare(:
|
|
146
|
+
options.parse_options!
|
|
147
|
+
set_sdk_dir
|
|
148
|
+
options.declare(:ascp_path, 'Ascp: Path to ascp (or product with "product:")', handler: {o: Ascp::Installation.instance, m: :ascp_path}, default: "#{Ascp::Installation::USE_PRODUCT_PREFIX}#{Ascp::Installation::FIRST_FOUND}")
|
|
149
|
+
options.declare(:locations_url, 'Ascp: URL to get download locations of Aspera Transfer Daemon', handler: {o: Ascp::Installation.instance, m: :transferd_urls})
|
|
150
|
+
options.declare(:sdk_folder, 'Ascp: SDK installation folder path', handler: {o: Products::Transferd, m: :sdk_directory})
|
|
151
|
+
options.declare(:progress_bar, 'Display progress bar', allowed: Allowed::TYPES_BOOLEAN, default: Environment.terminal?)
|
|
146
152
|
# Email options
|
|
147
|
-
options.declare(:smtp, 'Email: SMTP configuration',
|
|
153
|
+
options.declare(:smtp, 'Email: SMTP configuration', allowed: Hash)
|
|
148
154
|
options.declare(:notify_to, 'Email: Recipient for notification of transfers')
|
|
149
155
|
options.declare(:notify_template, 'Email: ERB template for notification of transfers')
|
|
150
156
|
# HTTP options
|
|
151
|
-
options.declare(:insecure, 'HTTP/S: Do not validate any certificate',
|
|
152
|
-
options.declare(:ignore_certificate, 'HTTP/S: Do not validate certificate for these URLs',
|
|
153
|
-
options.declare(:warn_insecure, 'HTTP/S: Issue a warning if certificate is ignored',
|
|
154
|
-
options.declare(:cert_stores, 'HTTP/S: List of folder with trusted certificates',
|
|
155
|
-
options.declare(:http_options, 'HTTP/S: Options for HTTP/S socket',
|
|
156
|
-
options.declare(:http_proxy, 'HTTP/S: URL for proxy with optional credentials',
|
|
157
|
-
options.declare(:cache_tokens, 'Save and reuse OAuth tokens',
|
|
157
|
+
options.declare(:insecure, 'HTTP/S: Do not validate any certificate', allowed: Allowed::TYPES_BOOLEAN, handler: {o: self, m: :option_insecure}, default: false)
|
|
158
|
+
options.declare(:ignore_certificate, 'HTTP/S: Do not validate certificate for these URLs', allowed: [Array, NilClass], handler: {o: self, m: :option_ignore_cert_host_port})
|
|
159
|
+
options.declare(:warn_insecure, 'HTTP/S: Issue a warning if certificate is ignored', allowed: Allowed::TYPES_BOOLEAN, handler: {o: self, m: :option_warn_insecure_cert}, default: true)
|
|
160
|
+
options.declare(:cert_stores, 'HTTP/S: List of folder with trusted certificates', allowed: Allowed::TYPES_STRING_ARRAY, handler: {o: self, m: :trusted_cert_locations})
|
|
161
|
+
options.declare(:http_options, 'HTTP/S: Options for HTTP/S socket', allowed: Hash, handler: {o: self, m: :option_http_options}, default: {})
|
|
162
|
+
options.declare(:http_proxy, 'HTTP/S: URL for proxy with optional credentials', handler: {o: self, m: :option_http_proxy})
|
|
163
|
+
options.declare(:cache_tokens, 'Save and reuse OAuth tokens', allowed: Allowed::TYPES_BOOLEAN, handler: {o: self, m: :option_cache_tokens})
|
|
158
164
|
options.declare(:fpac, 'Proxy auto configuration script')
|
|
159
|
-
options.declare(:proxy_credentials, 'HTTP proxy credentials for fpac: user, password',
|
|
165
|
+
options.declare(:proxy_credentials, 'HTTP proxy credentials for fpac: user, password', allowed: [Array, NilClass])
|
|
160
166
|
options.parse_options!
|
|
161
167
|
@progress_bar = TransferProgress.new if options.get_option(:progress_bar)
|
|
162
|
-
# Check SDK folder is set or not, for compatibility, we check in two places
|
|
163
|
-
sdk_dir = Products::Transferd.sdk_directory rescue nil
|
|
164
|
-
if sdk_dir.nil?
|
|
165
|
-
@sdk_default_location = true
|
|
166
|
-
Log.log.debug('SDK folder is not set, checking default')
|
|
167
|
-
# New location
|
|
168
|
-
sdk_dir = self.class.default_app_main_folder(app_name: TRANSFERD_APP_NAME)
|
|
169
|
-
Log.log.debug{"Checking: #{sdk_dir}"}
|
|
170
|
-
if !Dir.exist?(sdk_dir)
|
|
171
|
-
Log.log.debug{"No such folder: #{sdk_dir}"}
|
|
172
|
-
# Former location
|
|
173
|
-
former_sdk_folder = File.join(self.class.default_app_main_folder(app_name: Info::CMD_NAME), TRANSFERD_APP_NAME)
|
|
174
|
-
Log.log.debug{"Checking: #{former_sdk_folder}"}
|
|
175
|
-
sdk_dir = former_sdk_folder if Dir.exist?(former_sdk_folder)
|
|
176
|
-
end
|
|
177
|
-
Log.log.debug{"Using: #{sdk_dir}"}
|
|
178
|
-
Products::Transferd.sdk_directory = sdk_dir
|
|
179
|
-
end
|
|
180
168
|
pac_script = options.get_option(:fpac)
|
|
181
169
|
# Create PAC executor
|
|
182
170
|
if !pac_script.nil?
|
|
@@ -200,27 +188,7 @@ module Aspera
|
|
|
200
188
|
RestParameters.instance.send(method, v)
|
|
201
189
|
elsif k.eql?('ssl_options')
|
|
202
190
|
keys_to_delete.push(k)
|
|
203
|
-
|
|
204
|
-
Aspera.assert_type(v, Array){'ssl_options'}
|
|
205
|
-
# Start with default options
|
|
206
|
-
ssl_options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options]
|
|
207
|
-
v.each do |opt|
|
|
208
|
-
case opt
|
|
209
|
-
when Integer
|
|
210
|
-
ssl_options = opt
|
|
211
|
-
when String
|
|
212
|
-
name = "OP_#{opt.start_with?('-') ? opt[1..] : opt}".upcase
|
|
213
|
-
raise Cli::BadArgument, "Unknown ssl_option: #{name}, use one of: #{OpenSSL::SSL.constants.grep(/^OP_/).map{ |c| c.to_s.sub(/^OP_/, '')}.join(', ')}" if !OpenSSL::SSL.const_defined?(name)
|
|
214
|
-
if opt.start_with?('-')
|
|
215
|
-
ssl_options &= ~OpenSSL::SSL.const_get(name)
|
|
216
|
-
else
|
|
217
|
-
ssl_options |= OpenSSL::SSL.const_get(name)
|
|
218
|
-
end
|
|
219
|
-
else
|
|
220
|
-
Aspera.error_unexpected_value(opt.class.name){'Expected String or Integer in ssl_options'}
|
|
221
|
-
end
|
|
222
|
-
end
|
|
223
|
-
OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options] = ssl_options
|
|
191
|
+
Aspera::SSL.option_list = v
|
|
224
192
|
elsif OAuth::Factory.instance.parameters.key?(k.to_sym)
|
|
225
193
|
keys_to_delete.push(k)
|
|
226
194
|
OAuth::Factory.instance.parameters[k.to_sym] = v
|
|
@@ -238,11 +206,31 @@ module Aspera
|
|
|
238
206
|
attr_accessor :main_folder, :option_cache_tokens, :option_insecure, :option_warn_insecure_cert, :option_http_options
|
|
239
207
|
attr_reader :option_ignore_cert_host_port, :progress_bar
|
|
240
208
|
|
|
209
|
+
def set_sdk_dir
|
|
210
|
+
# Check SDK folder is set or not, for compatibility, we check in two places
|
|
211
|
+
sdk_dir = Products::Transferd.sdk_directory rescue nil
|
|
212
|
+
if sdk_dir.nil?
|
|
213
|
+
@sdk_default_location = true
|
|
214
|
+
Log.log.debug('SDK folder is not set, checking default')
|
|
215
|
+
# New location
|
|
216
|
+
sdk_dir = self.class.default_app_main_folder(app_name: TRANSFERD_APP_NAME)
|
|
217
|
+
Log.log.debug{"Checking: #{sdk_dir}"}
|
|
218
|
+
if !Dir.exist?(sdk_dir)
|
|
219
|
+
Log.log.debug{"No such folder: #{sdk_dir}"}
|
|
220
|
+
# Former location
|
|
221
|
+
former_sdk_folder = File.join(self.class.default_app_main_folder(app_name: Info::CMD_NAME), TRANSFERD_APP_NAME)
|
|
222
|
+
Log.log.debug{"Checking: #{former_sdk_folder}"}
|
|
223
|
+
sdk_dir = former_sdk_folder if Dir.exist?(former_sdk_folder)
|
|
224
|
+
end
|
|
225
|
+
Log.log.debug{"Using: #{sdk_dir}"}
|
|
226
|
+
Products::Transferd.sdk_directory = sdk_dir
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
241
230
|
# Add files, folders or default locations to the certificate store
|
|
242
|
-
# @param path_list [Array<String>]
|
|
231
|
+
# @param path_list [Array<String>] List of paths to add
|
|
243
232
|
# @return the list of paths
|
|
244
233
|
def trusted_cert_locations=(path_list)
|
|
245
|
-
path_list = [path_list] unless path_list.is_a?(Array)
|
|
246
234
|
Aspera.assert_type(path_list, Array){'cert locations'}
|
|
247
235
|
if @certificate_store.nil?
|
|
248
236
|
Log.log.debug('Creating SSL Cert store')
|
|
@@ -273,6 +261,7 @@ module Aspera
|
|
|
273
261
|
pp = Dir.entries(p)
|
|
274
262
|
.map{ |e| File.realpath(File.join(p, e))}
|
|
275
263
|
.select{ |entry| File.file?(entry)}
|
|
264
|
+
.select{ |entry| CERT_EXT.any?{ |ext| entry.end_with?(ext)}}
|
|
276
265
|
end
|
|
277
266
|
@certificate_paths.concat(pp)
|
|
278
267
|
end
|
|
@@ -285,7 +274,7 @@ module Aspera
|
|
|
285
274
|
locations = @certificate_paths
|
|
286
275
|
if locations.nil?
|
|
287
276
|
# Compute default locations
|
|
288
|
-
self.trusted_cert_locations = SpecialValues::DEF
|
|
277
|
+
self.trusted_cert_locations = [SpecialValues::DEF]
|
|
289
278
|
locations = @certificate_paths
|
|
290
279
|
# Restore defaults
|
|
291
280
|
@certificate_paths = @certificate_store = nil
|
|
@@ -447,7 +436,7 @@ module Aspera
|
|
|
447
436
|
Log.log.warn{"keeping same value for #{preset}: #{param_name}: #{param_value}"}
|
|
448
437
|
return
|
|
449
438
|
end
|
|
450
|
-
Log.log.warn{"overwriting value: #{selected_preset[param_name]}"}
|
|
439
|
+
Log.log.warn{"overwriting value for #{param_name}: #{selected_preset[param_name]}"}
|
|
451
440
|
end
|
|
452
441
|
selected_preset[param_name] = param_value
|
|
453
442
|
formatter.display_status("Updated: #{preset}: #{param_name} <- #{param_value}")
|
|
@@ -479,21 +468,12 @@ module Aspera
|
|
|
479
468
|
raise Cli::Error, "Unknown config preset: #{include_path}" if current.nil?
|
|
480
469
|
end
|
|
481
470
|
current = self.class.deep_clone(current) unless current.is_a?(String)
|
|
482
|
-
return ExtendedValue.instance.evaluate(current)
|
|
483
|
-
end
|
|
484
|
-
|
|
485
|
-
def option_use_product=(value)
|
|
486
|
-
Ascp::Installation.instance.use_ascp_from_product(value)
|
|
487
|
-
end
|
|
488
|
-
|
|
489
|
-
def option_use_product
|
|
490
|
-
'write-only option, see value of ascp_path'
|
|
471
|
+
return ExtendedValue.instance.evaluate(current, context: 'preset')
|
|
491
472
|
end
|
|
492
473
|
|
|
493
474
|
def option_plugin_folder=(value)
|
|
494
|
-
|
|
495
|
-
value
|
|
496
|
-
Aspera.assert(value.all?(String)){'plugin folder'}
|
|
475
|
+
value = [value] unless value.is_a?(Array)
|
|
476
|
+
Aspera.assert_array_all(value, String){'plugin folder(s)'}
|
|
497
477
|
value.each{ |f| Plugins::Factory.instance.add_lookup_folder(f)}
|
|
498
478
|
end
|
|
499
479
|
|
|
@@ -514,8 +494,9 @@ module Aspera
|
|
|
514
494
|
end
|
|
515
495
|
end
|
|
516
496
|
|
|
497
|
+
# @return [Integer]
|
|
517
498
|
def config_checksum
|
|
518
|
-
JSON.generate(@config_presets)
|
|
499
|
+
Digest::SHA1.hexdigest(JSON.generate(@config_presets))
|
|
519
500
|
end
|
|
520
501
|
|
|
521
502
|
# Read config file and validate format
|
|
@@ -556,6 +537,7 @@ module Aspera
|
|
|
556
537
|
Log.log.warn{"#{file} -> #{@main_folder}"}
|
|
557
538
|
end
|
|
558
539
|
end
|
|
540
|
+
return
|
|
559
541
|
rescue Psych::SyntaxError => e
|
|
560
542
|
Log.log.error('YAML error in config file')
|
|
561
543
|
raise e
|
|
@@ -605,6 +587,15 @@ module Aspera
|
|
|
605
587
|
return Main.result_status("Opened: #{one_link['href']}")
|
|
606
588
|
end
|
|
607
589
|
end
|
|
590
|
+
Aspera.error_unreachable_line
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
def install_transfer_sdk
|
|
594
|
+
# Reset to default location, if older default was used
|
|
595
|
+
Products::Transferd.sdk_directory = self.class.default_app_main_folder(app_name: TRANSFERD_APP_NAME) if @sdk_default_location
|
|
596
|
+
asked_version = options.get_next_argument('transferd version', mandatory: false)
|
|
597
|
+
name, version, folder = Ascp::Installation.instance.install_sdk(url: options.get_option(:sdk_url, mandatory: true), version: asked_version)
|
|
598
|
+
return Main.result_status("Installed #{name} version #{version} in #{folder}")
|
|
608
599
|
end
|
|
609
600
|
|
|
610
601
|
def execute_action_ascp
|
|
@@ -624,7 +615,7 @@ module Aspera
|
|
|
624
615
|
# Collect info from ascp executable
|
|
625
616
|
data = Ascp::Installation.instance.ascp_info
|
|
626
617
|
# Add command line transfer spec
|
|
627
|
-
data['ts'] = transfer.
|
|
618
|
+
data['ts'] = transfer.user_transfer_spec
|
|
628
619
|
# Add keys
|
|
629
620
|
DataRepository::ELEMENTS.each_with_object(data){ |i, h| h[i.to_s] = DataRepository.instance.item(i)}
|
|
630
621
|
# Declare those as secrets
|
|
@@ -638,15 +629,11 @@ module Aspera
|
|
|
638
629
|
when :use
|
|
639
630
|
default_product = options.get_next_argument('product name')
|
|
640
631
|
Ascp::Installation.instance.use_ascp_from_product(default_product)
|
|
641
|
-
set_global_default(:ascp_path, Ascp::Installation
|
|
632
|
+
set_global_default(:ascp_path, "#{Ascp::Installation::USE_PRODUCT_PREFIX}#{default_product}")
|
|
642
633
|
return Main.result_nothing
|
|
643
634
|
end
|
|
644
635
|
when :install
|
|
645
|
-
|
|
646
|
-
Products::Transferd.sdk_directory = self.class.default_app_main_folder(app_name: TRANSFERD_APP_NAME) if @sdk_default_location
|
|
647
|
-
version = options.get_next_argument('transferd version', mandatory: false)
|
|
648
|
-
n, v = Ascp::Installation.instance.install_sdk(url: options.get_option(:sdk_url, mandatory: true), version: version)
|
|
649
|
-
return Main.result_status("Installed #{n} version #{v}")
|
|
636
|
+
return install_transfer_sdk
|
|
650
637
|
when :spec
|
|
651
638
|
fields, data = Transfer::SpecDoc.man_table(Formatter, include_option: true)
|
|
652
639
|
return Main.result_object_list(data, fields: fields.map(&:to_s))
|
|
@@ -671,11 +658,7 @@ module Aspera
|
|
|
671
658
|
command = options.get_next_command(%i[list install])
|
|
672
659
|
case command
|
|
673
660
|
when :install
|
|
674
|
-
|
|
675
|
-
Products::Transferd.sdk_directory = self.class.default_app_main_folder(app_name: TRANSFERD_APP_NAME) if @sdk_default_location
|
|
676
|
-
version = options.get_next_argument('transferd version', mandatory: false)
|
|
677
|
-
n, v = Ascp::Installation.instance.install_sdk(url: options.get_option(:sdk_url, mandatory: true), version: version)
|
|
678
|
-
return Main.result_status("Installed #{n} version #{v}")
|
|
661
|
+
return install_transfer_sdk
|
|
679
662
|
when :list
|
|
680
663
|
sdk_list = Ascp::Installation.instance.sdk_locations
|
|
681
664
|
return Main.result_object_list(
|
|
@@ -725,7 +708,7 @@ module Aspera
|
|
|
725
708
|
value = @config_presets[name][param_name]
|
|
726
709
|
raise "no such option in preset #{name} : #{param_name}" if value.nil?
|
|
727
710
|
case value
|
|
728
|
-
when Numeric, String then return Main.result_text(ExtendedValue.instance.evaluate(value.to_s))
|
|
711
|
+
when Numeric, String then return Main.result_text(ExtendedValue.instance.evaluate(value.to_s, context: 'preset'))
|
|
729
712
|
end
|
|
730
713
|
return Main.result_single_object(value)
|
|
731
714
|
when :unset
|
|
@@ -754,7 +737,7 @@ module Aspera
|
|
|
754
737
|
options.ask_missing_mandatory = true
|
|
755
738
|
@config_presets[name] ||= {}
|
|
756
739
|
options.get_next_argument('option names', multiple: true).each do |option_name|
|
|
757
|
-
option_value = options.get_interactive(option_name,
|
|
740
|
+
option_value = options.get_interactive(option_name, check_option: true)
|
|
758
741
|
@config_presets[name][option_name] = option_value
|
|
759
742
|
end
|
|
760
743
|
return Main.result_status("Updated: #{name}")
|
|
@@ -889,7 +872,7 @@ module Aspera
|
|
|
889
872
|
result.push({
|
|
890
873
|
plugin: name,
|
|
891
874
|
detect: Formatter.tick(plugin_class.respond_to?(:detect)),
|
|
892
|
-
wizard: Formatter.tick(plugin_class.
|
|
875
|
+
wizard: Formatter.tick(plugin_class.method_defined?(:wizard)),
|
|
893
876
|
path: Plugins::Factory.instance.plugin_source(name)
|
|
894
877
|
})
|
|
895
878
|
end
|
|
@@ -930,10 +913,14 @@ module Aspera
|
|
|
930
913
|
when :ascp
|
|
931
914
|
execute_action_ascp
|
|
932
915
|
when :sync
|
|
933
|
-
case options.get_next_command(%i[spec])
|
|
916
|
+
case options.get_next_command(%i[spec admin translate])
|
|
934
917
|
when :spec
|
|
935
918
|
fields, data = Transfer::SpecDoc.man_table(Formatter, include_option: true, agent_columns: false, schema: Sync::Operations::CONF_SCHEMA)
|
|
936
919
|
return Main.result_object_list(data, fields: fields.map(&:to_s))
|
|
920
|
+
when :admin
|
|
921
|
+
return execute_sync_admin
|
|
922
|
+
when :translate
|
|
923
|
+
return Main.result_single_object(Sync::Operations.args_to_conf(options.get_next_argument('async arguments', multiple: true)))
|
|
937
924
|
else Aspera.error_unreachable_line
|
|
938
925
|
end
|
|
939
926
|
when :transferd
|
|
@@ -1183,16 +1170,17 @@ module Aspera
|
|
|
1183
1170
|
end
|
|
1184
1171
|
|
|
1185
1172
|
# Lookup the corresponding secret for the given URL and usernames
|
|
1186
|
-
# @
|
|
1187
|
-
|
|
1173
|
+
# @param url [String] Server URL
|
|
1174
|
+
# @param username [String] Username
|
|
1175
|
+
# @return [String, nil] Secret if found
|
|
1176
|
+
def lookup_secret(url:, username:)
|
|
1188
1177
|
secret = options.get_option(:secret)
|
|
1189
|
-
if secret.
|
|
1178
|
+
if secret.eql?('PRESET')
|
|
1190
1179
|
conf = lookup_preset(url: url, username: username)
|
|
1191
1180
|
if conf.is_a?(Hash)
|
|
1192
1181
|
Log.log.debug{"Found preset #{conf} with URL and username"}
|
|
1193
1182
|
secret = conf['password']
|
|
1194
1183
|
end
|
|
1195
|
-
raise "Please provide secret for #{username} using option: secret or by setting a preset for #{username}@#{url}." if secret.nil? && mandatory
|
|
1196
1184
|
end
|
|
1197
1185
|
return secret
|
|
1198
1186
|
end
|
|
@@ -1227,6 +1215,7 @@ module Aspera
|
|
|
1227
1215
|
# Special extended values
|
|
1228
1216
|
EXTEND_PRESET = :preset
|
|
1229
1217
|
EXTEND_VAULT = :vault
|
|
1218
|
+
EXTEND_ARGS = :''
|
|
1230
1219
|
PRESET_DIG_SEPARATOR = '.'
|
|
1231
1220
|
DEFAULT_CHECK_NEW_VERSION_DAYS = 7
|
|
1232
1221
|
COFFEE_IMAGE_URL = 'https://enjoyjava.com/wp-content/uploads/2018/01/How-to-make-strong-coffee.jpg'
|
|
@@ -1235,7 +1224,7 @@ module Aspera
|
|
|
1235
1224
|
SELF_SIGNED_CERT = OpenSSL::SSL.const_get(:enon_yfirev.to_s.upcase.reverse) # cspell: disable-line
|
|
1236
1225
|
CONF_OVERVIEW_KEYS = %w[preset parameter value].freeze
|
|
1237
1226
|
SMTP_CONF_PARAMS = %i[server tls ssl port domain username password from_name from_email].freeze
|
|
1238
|
-
|
|
1227
|
+
CERT_EXT = %w[crt cer pem der].freeze
|
|
1239
1228
|
private_constant :ASPERA_HOME_FOLDER_NAME,
|
|
1240
1229
|
:DEFAULT_CONFIG_FILENAME,
|
|
1241
1230
|
:CONF_PRESET_CONFIG,
|
|
@@ -1260,7 +1249,8 @@ module Aspera
|
|
|
1260
1249
|
:TRANSFERD_APP_NAME,
|
|
1261
1250
|
:GLOBAL_DEFAULT_KEYWORD,
|
|
1262
1251
|
:CONF_GLOBAL_SYM,
|
|
1263
|
-
:GEM_CHECK_DATE_FMT
|
|
1252
|
+
:GEM_CHECK_DATE_FMT,
|
|
1253
|
+
:CERT_EXT
|
|
1264
1254
|
end
|
|
1265
1255
|
end
|
|
1266
1256
|
end
|