aspera-cli 4.27.0 → 4.27.2
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 +46 -1
- data/CONTRIBUTING.md +1 -7
- data/TODO.md +50 -0
- data/docs/README.md +709 -489
- data/lib/aspera/agent/base.rb +10 -2
- data/lib/aspera/agent/connect.rb +7 -7
- data/lib/aspera/agent/desktop.rb +3 -3
- data/lib/aspera/agent/direct.rb +48 -19
- data/lib/aspera/agent/factory.rb +3 -3
- data/lib/aspera/agent/httpgw.rb +55 -14
- data/lib/aspera/agent/node.rb +2 -2
- data/lib/aspera/agent/transferd.rb +12 -12
- data/lib/aspera/api/aoc.rb +28 -28
- data/lib/aspera/api/cos_node.rb +10 -9
- data/lib/aspera/api/faspex.rb +2 -2
- data/lib/aspera/api/httpgw.rb +20 -20
- data/lib/aspera/api/node.rb +31 -53
- data/lib/aspera/ascmd.rb +13 -13
- data/lib/aspera/ascp/installation.rb +43 -36
- data/lib/aspera/ascp/management.rb +6 -6
- data/lib/aspera/assert.rb +12 -13
- data/lib/aspera/cli/ascp_actions.rb +7 -7
- data/lib/aspera/cli/async_transfer_store.rb +49 -5
- data/lib/aspera/cli/bootstrapper.rb +9 -9
- data/lib/aspera/cli/command_registry.rb +1 -1
- data/lib/aspera/cli/command_spec.rb +6 -3
- data/lib/aspera/cli/context.rb +3 -3
- data/lib/aspera/cli/extended_value.rb +26 -26
- data/lib/aspera/cli/formatter.rb +76 -54
- data/lib/aspera/cli/gem_checker.rb +1 -1
- data/lib/aspera/cli/hints.rb +2 -2
- data/lib/aspera/cli/http.rb +22 -29
- data/lib/aspera/cli/mailer.rb +7 -7
- data/lib/aspera/cli/mcp_tool.rb +31 -4
- data/lib/aspera/cli/option_declarator.rb +77 -0
- data/lib/aspera/cli/options.schema.yaml +273 -3
- data/lib/aspera/cli/parser.rb +539 -280
- data/lib/aspera/cli/plugins/alee.rb +1 -1
- data/lib/aspera/cli/plugins/aoc.rb +303 -314
- data/lib/aspera/cli/plugins/ats.rb +25 -21
- data/lib/aspera/cli/plugins/base.rb +357 -186
- data/lib/aspera/cli/plugins/basic_auth.rb +0 -12
- data/lib/aspera/cli/plugins/config.rb +76 -73
- data/lib/aspera/cli/plugins/console.rb +8 -8
- data/lib/aspera/cli/plugins/cos.rb +4 -3
- data/lib/aspera/cli/plugins/factory.rb +2 -2
- data/lib/aspera/cli/plugins/faspex5.rb +259 -248
- data/lib/aspera/cli/plugins/faspio.rb +7 -2
- data/lib/aspera/cli/plugins/httpgw.rb +1 -1
- data/lib/aspera/cli/plugins/mcp.rb +11 -11
- data/lib/aspera/cli/plugins/node.rb +227 -283
- data/lib/aspera/cli/plugins/orchestrator.rb +23 -23
- data/lib/aspera/cli/plugins/preview.rb +61 -64
- data/lib/aspera/cli/plugins/server.rb +36 -31
- data/lib/aspera/cli/plugins/shares.rb +174 -135
- data/lib/aspera/cli/preset_actions.rb +39 -19
- data/lib/aspera/cli/preset_manager.rb +74 -32
- data/lib/aspera/cli/result.rb +18 -16
- data/lib/aspera/cli/runner.rb +75 -39
- data/lib/aspera/cli/secret_finder.rb +1 -1
- data/lib/aspera/cli/sync_actions.rb +36 -35
- data/lib/aspera/cli/terminal_formatter.rb +2 -2
- data/lib/aspera/cli/transfer_actions.rb +17 -10
- data/lib/aspera/cli/transfer_agent.rb +44 -44
- data/lib/aspera/cli/transfer_progress.rb +7 -7
- data/lib/aspera/cli/vault_manager.rb +34 -12
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +17 -20
- data/lib/aspera/colors.rb +3 -3
- data/lib/aspera/command_line_builder.rb +17 -16
- data/lib/aspera/command_line_converter.rb +1 -1
- data/lib/aspera/coverage.rb +1 -2
- data/lib/aspera/data_repository.rb +1 -1
- data/lib/aspera/dot_container.rb +8 -8
- data/lib/aspera/environment.rb +13 -13
- data/lib/aspera/faspex_gw.rb +4 -4
- data/lib/aspera/faspex_postproc.rb +14 -9
- data/lib/aspera/graphql.rb +4 -2
- data/lib/aspera/hash_ext.rb +2 -2
- data/lib/aspera/json_rpc/client.rb +1 -1
- data/lib/aspera/keychain/base.rb +10 -3
- data/lib/aspera/keychain/encrypted_hash.rb +92 -17
- data/lib/aspera/keychain/factory.rb +17 -5
- data/lib/aspera/keychain/hashicorp_vault.rb +1 -1
- data/lib/aspera/keychain/macos_security.rb +23 -25
- data/lib/aspera/keychain/one_password_api.rb +86 -0
- data/lib/aspera/keychain/one_password_base.rb +34 -0
- data/lib/aspera/keychain/one_password_cli.rb +98 -0
- data/lib/aspera/link_header.rb +2 -2
- data/lib/aspera/log.rb +13 -13
- data/lib/aspera/markdown.rb +5 -5
- data/lib/aspera/nagios.rb +8 -8
- data/lib/aspera/node_simulator.rb +33 -27
- data/lib/aspera/oauth/base.rb +4 -5
- data/lib/aspera/oauth/boot.rb +3 -3
- data/lib/aspera/oauth/factory.rb +5 -5
- data/lib/aspera/oauth/jwt.rb +5 -7
- data/lib/aspera/oauth/web.rb +4 -4
- data/lib/aspera/persistency_action_once.rb +4 -4
- data/lib/aspera/persistency_folder.rb +8 -8
- data/lib/aspera/preview/file_types.rb +4 -4
- data/lib/aspera/preview/generator.rb +49 -46
- data/lib/aspera/preview/options.rb +3 -2
- data/lib/aspera/preview/terminal.rb +5 -5
- data/lib/aspera/preview/utils.rb +65 -18
- data/lib/aspera/products/connect.rb +2 -2
- data/lib/aspera/products/desktop.rb +1 -1
- data/lib/aspera/products/other.rb +2 -2
- data/lib/aspera/products/transferd.rb +3 -3
- data/lib/aspera/proxy_auto_config.rb +10 -10
- data/lib/aspera/rest.rb +149 -115
- data/lib/aspera/rest_error_analyzer.rb +3 -3
- data/lib/aspera/rest_list.rb +10 -9
- data/lib/aspera/schema/IBM Aspera Node API-4.4.6.yaml +6232 -0
- data/lib/aspera/schema/IBM Aspera faspio Gateway API-1.0.0.yaml +249 -0
- data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +39 -0
- data/lib/aspera/schema/IBM_Aspera_Shares.yaml +4653 -0
- data/lib/aspera/schema/documentation.rb +17 -11
- data/lib/aspera/schema/reader.rb +78 -4
- data/lib/aspera/schema/registry.rb +26 -7
- data/lib/aspera/secret_hider.rb +5 -5
- data/lib/aspera/ssh.rb +64 -31
- data/lib/aspera/ssl.rb +3 -3
- data/lib/aspera/sync/database.rb +2 -2
- data/lib/aspera/sync/operations.rb +13 -13
- data/lib/aspera/temp_file_manager.rb +24 -4
- data/lib/aspera/transfer/faux_file.rb +2 -2
- data/lib/aspera/transfer/parameters.rb +15 -15
- data/lib/aspera/transfer/resumer.rb +9 -9
- data/lib/aspera/transfer/spec.rb +1 -1
- data/lib/aspera/transfer/spec.schema.yaml +1 -0
- data/lib/aspera/uri_reader.rb +52 -15
- data/lib/aspera/web_auth.rb +6 -5
- data/lib/aspera/web_server_simple.rb +9 -9
- data/lib/aspera/yaml.rb +4 -4
- data.tar.gz.sig +0 -0
- metadata +10 -4
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/plugins/faspex.rb +0 -576
- data/lib/aspera/transfer/uri.rb +0 -56
data/lib/aspera/cli/wizard.rb
CHANGED
|
@@ -2,25 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
require 'aspera/oauth/jwt'
|
|
4
4
|
require 'aspera/assert'
|
|
5
|
+
require 'aspera/cli/option_declarator'
|
|
5
6
|
require 'aspera/cli/plugins/factory'
|
|
6
7
|
|
|
7
8
|
module Aspera
|
|
8
9
|
module Cli
|
|
9
10
|
# The wizard detects applications and generates a config
|
|
10
11
|
class Wizard
|
|
12
|
+
extend OptionDeclarator
|
|
13
|
+
|
|
11
14
|
WIZARD_RESULT_KEYS = %i[preset_value test_args].freeze
|
|
12
15
|
DEFAULT_PRIV_KEY_FILENAME = 'my_private_key.pem' # pragma: allowlist secret
|
|
13
16
|
private_constant :WIZARD_RESULT_KEYS, :DEFAULT_PRIV_KEY_FILENAME
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def declare_options(options)
|
|
19
|
-
options.declare(:override, description: 'Wizard: override existing value', allowed: Allowed::TYPES_BOOLEAN, default: false)
|
|
20
|
-
options.declare(:default, description: 'Wizard: set as default configuration for specified plugin (also: update)', allowed: Allowed::TYPES_BOOLEAN, default: true)
|
|
21
|
-
options.declare(:key_path, description: 'Wizard: path to private key for JWT')
|
|
22
|
-
end
|
|
23
|
-
end
|
|
18
|
+
option :override, description: 'Wizard: override existing value', allowed: Type::BOOLEAN, default: false
|
|
19
|
+
option :default, description: 'Wizard: set as default configuration for specified plugin (also: update)', allowed: Type::BOOLEAN, default: true
|
|
20
|
+
option :key_path, description: 'Wizard: path to private key for JWT'
|
|
24
21
|
|
|
25
22
|
def initialize(parent, main_folder)
|
|
26
23
|
@parent = parent
|
|
@@ -42,7 +39,7 @@ module Aspera
|
|
|
42
39
|
end
|
|
43
40
|
|
|
44
41
|
def check_email(email)
|
|
45
|
-
Aspera.assert(email =~ /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i, type: ParameterError){"Username shall be an email: #{email}"}
|
|
42
|
+
Aspera.assert(email =~ /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i, type: ParameterError) { "Username shall be an email: #{email}" }
|
|
46
43
|
end
|
|
47
44
|
|
|
48
45
|
# Find a plugin, and issue the "require"
|
|
@@ -62,14 +59,14 @@ module Aspera
|
|
|
62
59
|
next unless plugin_klass.respond_to?(:detect)
|
|
63
60
|
detection_info = nil
|
|
64
61
|
begin
|
|
65
|
-
Log.log.debug{"detecting #{plugin_name_sym} at #{app_url}"}
|
|
62
|
+
Log.log.debug { "detecting #{plugin_name_sym} at #{app_url}" }
|
|
66
63
|
RestParameters.instance.spinner_cb.call(plugin_name_sym.to_s)
|
|
67
64
|
detection_info = plugin_klass.detect(app_url)
|
|
68
65
|
rescue OpenSSL::SSL::SSLError => e
|
|
69
66
|
Log.log.warn(e.message)
|
|
70
67
|
Log.log.warn('Use option --insecure=yes to allow unchecked certificate') if e.message.include?('cert')
|
|
71
68
|
rescue StandardError => e
|
|
72
|
-
Log.log.debug{"detect error: [#{e.class}] #{e}"}
|
|
69
|
+
Log.log.debug { "detect error: [#{e.class}] #{e}" }
|
|
73
70
|
next
|
|
74
71
|
end
|
|
75
72
|
next if detection_info.nil?
|
|
@@ -80,8 +77,8 @@ module Aspera
|
|
|
80
77
|
found_apps.push({product: plugin_name_sym, name: app_name, url: app_url, version: 'unknown'}.merge(detection_info))
|
|
81
78
|
end
|
|
82
79
|
RestParameters.instance.spinner_cb.call(action: :success)
|
|
83
|
-
Aspera.assert(!found_apps.empty?){"No known application found at #{app_url}"}
|
|
84
|
-
Aspera.assert(found_apps.all?{ |a| a.keys.all?(Symbol)}, 'all app info keys must be symbols')
|
|
80
|
+
Aspera.assert(!found_apps.empty?) { "No known application found at #{app_url}" }
|
|
81
|
+
Aspera.assert(found_apps.all? { |a| a.keys.all?(Symbol) }, 'all app info keys must be symbols')
|
|
85
82
|
return found_apps
|
|
86
83
|
end
|
|
87
84
|
|
|
@@ -125,14 +122,14 @@ module Aspera
|
|
|
125
122
|
# @param apps [Array] list of detected apps
|
|
126
123
|
def find(apps, preset_name: '')
|
|
127
124
|
identification = if apps.length.eql?(1)
|
|
128
|
-
Log.log.debug{"Detected: #{identification}"}
|
|
125
|
+
Log.log.debug { "Detected: #{identification}" }
|
|
129
126
|
apps.first
|
|
130
127
|
else
|
|
131
128
|
formatter.display_status('Multiple applications detected, please select from:')
|
|
132
129
|
require 'aspera/cli/result'
|
|
133
130
|
formatter.display_results(Result::ObjectList.new(apps, fields: %w[product url version]))
|
|
134
|
-
answer = options.prompt_user_input_in_list('product', apps.map{ |a| a[:product]})
|
|
135
|
-
apps.find{ |a| a[:product].eql?(answer)}
|
|
131
|
+
answer = options.prompt_user_input_in_list('product', apps.map { |a| a[:product] })
|
|
132
|
+
apps.find { |a| a[:product].eql?(answer) }
|
|
136
133
|
end
|
|
137
134
|
wiz_preset_name = preset_name
|
|
138
135
|
Log.dump(:identification, identification)
|
|
@@ -142,11 +139,11 @@ module Aspera
|
|
|
142
139
|
options.add_option_preset({url: wiz_url}, 'wizard')
|
|
143
140
|
# Instantiate plugin: command line options will be known, e.g. private_key, and wizard can be called
|
|
144
141
|
plugin_instance = Plugins::Factory.instance.plugin_class(identification[:product]).new(context: @parent.context)
|
|
145
|
-
Aspera.assert(plugin_instance.respond_to?(:wizard), type: Cli::BadArgument){"Detected: #{identification[:product]}, but this application has no wizard"}
|
|
142
|
+
Aspera.assert(plugin_instance.respond_to?(:wizard), type: Cli::BadArgument) { "Detected: #{identification[:product]}, but this application has no wizard" }
|
|
146
143
|
# Call the wizard
|
|
147
144
|
wizard_result = plugin_instance.wizard(self, wiz_url)
|
|
148
|
-
Log.log.debug{"wizard result: #{wizard_result}"}
|
|
149
|
-
Aspera.assert(WIZARD_RESULT_KEYS.eql?(wizard_result.keys.sort)){"missing or extra keys in wizard result: #{wizard_result.keys}"}
|
|
145
|
+
Log.log.debug { "wizard result: #{wizard_result}" }
|
|
146
|
+
Aspera.assert(WIZARD_RESULT_KEYS.eql?(wizard_result.keys.sort)) { "missing or extra keys in wizard result: #{wizard_result.keys}" }
|
|
150
147
|
# Get preset name from user or default
|
|
151
148
|
if wiz_preset_name.empty?
|
|
152
149
|
elements = [
|
data/lib/aspera/colors.rb
CHANGED
|
@@ -52,9 +52,9 @@ class String
|
|
|
52
52
|
0 # by default reset all
|
|
53
53
|
end
|
|
54
54
|
)
|
|
55
|
-
define_method(name){"#{begin_seq}#{self}#{end_seq}"}
|
|
55
|
+
define_method(name) { "#{begin_seq}#{self}#{end_seq}" }
|
|
56
56
|
else
|
|
57
|
-
define_method(name){self}
|
|
57
|
+
define_method(name) { self }
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
60
|
|
|
@@ -62,7 +62,7 @@ class String
|
|
|
62
62
|
# @param colors [Array<Symbol>] List of decorations.
|
|
63
63
|
# @return [String] Enhanced String.
|
|
64
64
|
def apply(*colors)
|
|
65
|
-
colors.reduce(self){ |s, c| s.public_send(c)}
|
|
65
|
+
colors.reduce(self) { |s, c| s.public_send(c) }
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
# Transform capitalized to snake case
|
|
@@ -62,16 +62,17 @@ module Aspera
|
|
|
62
62
|
# @param ascp [Boolean] `true` if `ascp`
|
|
63
63
|
# @return [Schema::Reader] The JSON schema
|
|
64
64
|
def validate_schema(schema, ascp: false)
|
|
65
|
-
Aspera.assert_type(schema, Schema::Reader){'schema'}
|
|
66
|
-
Aspera.assert(schema.current.key?('properties')){"Schema must have 'properties': #{schema}"}
|
|
65
|
+
Aspera.assert_type(schema, Schema::Reader) { 'schema' }
|
|
66
|
+
Aspera.assert(schema.current.key?('properties')) { "Schema must have 'properties': #{schema}" }
|
|
67
67
|
schema.each_property do |property_schema, name, _full_name|
|
|
68
68
|
node = property_schema.current
|
|
69
69
|
unsupported_keys = node.keys - PROPERTY_KEYS
|
|
70
|
-
Aspera.assert(unsupported_keys.empty?){"Unsupported definition keys: #{unsupported_keys}"}
|
|
71
|
-
Aspera.assert(node.key?('type') || node.key?('enum')){"Missing type for #{name} in #{schema.current.dig('description').current}"}
|
|
72
|
-
Aspera.assert(node['type'].eql?('boolean')){"switch must be bool: #{name}"} if node['x-cli-switch'] && !node['x-cli-special']
|
|
70
|
+
Aspera.assert(unsupported_keys.empty?) { "Unsupported definition keys: #{unsupported_keys}" }
|
|
71
|
+
Aspera.assert(node.key?('type') || node.key?('enum')) { "Missing type for #{name} in #{schema.current.dig('description').current}" }
|
|
72
|
+
Aspera.assert(node['type'].eql?('boolean')) { "switch must be bool: #{name}" } if node['x-cli-switch'] && !node['x-cli-special']
|
|
73
|
+
Aspera.assert(node['x-cli-switch']) { "boolean direct option must have x-cli-switch: #{name}" } if node['type'].eql?('boolean') && node.key?('x-cli-option') && supported_by_agent(:direct, node) && !node['x-cli-special']
|
|
73
74
|
node['x-cli-option'] = "--#{name.to_s.tr('_', '-')}" if node['x-cli-option'].eql?(true) || (node['x-cli-switch'].eql?(true) && !node.key?('x-cli-option'))
|
|
74
|
-
Aspera.assert(DIRECT_PROPERTIES.any?{ |i| node.key?(i)}, name, type: :warn) if ascp && supported_by_agent(:direct, node)
|
|
75
|
+
Aspera.assert(DIRECT_PROPERTIES.any? { |i| node.key?(i) }, name, type: :warn) if ascp && supported_by_agent(:direct, node)
|
|
75
76
|
node.freeze
|
|
76
77
|
end
|
|
77
78
|
schema
|
|
@@ -104,7 +105,7 @@ module Aspera
|
|
|
104
105
|
Log.dump(:exec_spec, @result)
|
|
105
106
|
# warn about non translated arguments
|
|
106
107
|
@object.each_pair do |name, value|
|
|
107
|
-
Log.log.warn{"Unknown transfer spec parameter: #{name} = \"#{value}\""} unless @processed_parameters.include?(name)
|
|
108
|
+
Log.log.warn { "Unknown transfer spec parameter: #{name} = \"#{value}\"" } unless @processed_parameters.include?(name)
|
|
108
109
|
end
|
|
109
110
|
# set result
|
|
110
111
|
exec_spec.env.merge!(@result.env)
|
|
@@ -116,7 +117,7 @@ module Aspera
|
|
|
116
117
|
def add_command_line_options(*options)
|
|
117
118
|
options = options.first if options.first.is_a?(Array) && options.length.eql?(1)
|
|
118
119
|
Aspera.assert_type(options, Array)
|
|
119
|
-
options.each{ |o| @result.args.push(o.to_s)}
|
|
120
|
+
options.each { |o| @result.args.push(o.to_s) }
|
|
120
121
|
end
|
|
121
122
|
|
|
122
123
|
def process_params
|
|
@@ -138,11 +139,11 @@ module Aspera
|
|
|
138
139
|
properties = @schema['properties'][name]
|
|
139
140
|
# should not happen
|
|
140
141
|
if properties.nil?
|
|
141
|
-
Log.log.warn{"Unknown parameter #{name}"}
|
|
142
|
+
Log.log.warn { "Unknown parameter #{name}" }
|
|
142
143
|
return
|
|
143
144
|
end
|
|
144
145
|
# check mandatory parameter (nil is valid value), TODO: change exception ?
|
|
145
|
-
Aspera.assert(!(@schema['required']&.include?(name) && !properties['x-cli-special'] && !@object.key?(name)), type: Transfer::Error){"Missing mandatory parameter: #{name}"}
|
|
146
|
+
Aspera.assert(!(@schema['required']&.include?(name) && !properties['x-cli-special'] && !@object.key?(name)), type: Transfer::Error) { "Missing mandatory parameter: #{name}" }
|
|
146
147
|
parameter_value = @object[name]
|
|
147
148
|
# no default setting
|
|
148
149
|
# parameter_value=properties['default'] if parameter_value.nil? and properties.has_key?('default')
|
|
@@ -155,7 +156,7 @@ module Aspera
|
|
|
155
156
|
when 'object' then [Hash]
|
|
156
157
|
when 'integer' then [Integer]
|
|
157
158
|
when 'boolean' then [TrueClass, FalseClass]
|
|
158
|
-
else Aspera.error_unexpected_value(properties['type']){"Property #{name}"}
|
|
159
|
+
else Aspera.error_unexpected_value(properties['type']) { "Property #{name}" }
|
|
159
160
|
end
|
|
160
161
|
end.flatten
|
|
161
162
|
# check that value is of expected type
|
|
@@ -168,7 +169,7 @@ module Aspera
|
|
|
168
169
|
return if parameter_value.nil?
|
|
169
170
|
|
|
170
171
|
# check that value is of an accepted type (string, integer, boolean)
|
|
171
|
-
Aspera.assert(!(properties.key?('enum') && !properties['enum'].include?(parameter_value))){"Enum value #{parameter_value} is not allowed for #{name}"}
|
|
172
|
+
Aspera.assert(!(properties.key?('enum') && !properties['enum'].include?(parameter_value))) { "Enum value #{parameter_value} is not allowed for #{name}" }
|
|
172
173
|
|
|
173
174
|
# convert some values if value on command line needs processing from value in structure
|
|
174
175
|
if (convert = properties['x-cli-convert'])
|
|
@@ -176,9 +177,9 @@ module Aspera
|
|
|
176
177
|
case convert
|
|
177
178
|
when Hash then convert[parameter_value]
|
|
178
179
|
when String then @convert.send(convert, parameter_value)
|
|
179
|
-
else Aspera.error_unexpected_value(convert){"Conversion type for #{name} is Hash or String only."}
|
|
180
|
+
else Aspera.error_unexpected_value(convert) { "Conversion type for #{name} is Hash or String only." }
|
|
180
181
|
end
|
|
181
|
-
Aspera.assert(!converted_value.nil?){"No conversion for: #{name}=#{parameter_value}"}
|
|
182
|
+
Aspera.assert(!converted_value.nil?) { "No conversion for: #{name}=#{parameter_value}" }
|
|
182
183
|
parameter_value = converted_value
|
|
183
184
|
end
|
|
184
185
|
|
|
@@ -199,14 +200,14 @@ module Aspera
|
|
|
199
200
|
add_command_line_options(properties['x-cli-option'])
|
|
200
201
|
when false
|
|
201
202
|
add_command_line_options(properties['x-cli-false']) if properties.key?('x-cli-false')
|
|
202
|
-
else Aspera.error_unexpected_value(parameter_value){name}
|
|
203
|
+
else Aspera.error_unexpected_value(parameter_value) { name }
|
|
203
204
|
end
|
|
204
205
|
else
|
|
205
206
|
# transform into command line option with value
|
|
206
207
|
# parameter_value=parameter_value.to_s if parameter_value.is_a?(Integer)
|
|
207
208
|
parameter_value = [parameter_value] unless parameter_value.is_a?(Array)
|
|
208
209
|
# if transfer_spec value is an array, applies option many times
|
|
209
|
-
parameter_value.each{ |v| add_command_line_options(properties['x-cli-option'], v)}
|
|
210
|
+
parameter_value.each { |v| add_command_line_options(properties['x-cli-option'], v) }
|
|
210
211
|
end
|
|
211
212
|
end
|
|
212
213
|
end
|
data/lib/aspera/coverage.rb
CHANGED
|
@@ -27,12 +27,11 @@ if ENV.key?('ENABLE_COVERAGE')
|
|
|
27
27
|
# assert usually do not trigger
|
|
28
28
|
add_filter do |source_file|
|
|
29
29
|
source_file.lines.each do |line|
|
|
30
|
-
line.skipped! if no_cov_functions.any?{ |i| line.src.include?(i)}
|
|
30
|
+
line.skipped! if no_cov_functions.any? { |i| line.src.include?(i) }
|
|
31
31
|
end
|
|
32
32
|
false
|
|
33
33
|
end
|
|
34
34
|
# no coverage in these files
|
|
35
|
-
add_filter 'lib/aspera/cli/plugins/faspex.rb'
|
|
36
35
|
add_filter 'lib/aspera/node_simulator.rb'
|
|
37
36
|
add_filter 'lib/aspera/keychain/macos_security.rb'
|
|
38
37
|
add_filter 'lib/aspera/assert.rb'
|
|
@@ -24,7 +24,7 @@ module Aspera
|
|
|
24
24
|
# @return [String] decoded data
|
|
25
25
|
def item(name)
|
|
26
26
|
index = ELEMENTS.index(name)
|
|
27
|
-
Aspera.assert(index, type: ParameterError){"Unknown data item #{name} (#{name.class})"}
|
|
27
|
+
Aspera.assert(index, type: ParameterError) { "Unknown data item #{name} (#{name.class})" }
|
|
28
28
|
raw_data = data(START_INDEX + index)
|
|
29
29
|
case name
|
|
30
30
|
when :dsa, :rsa
|
data/lib/aspera/dot_container.rb
CHANGED
|
@@ -14,7 +14,7 @@ module Aspera
|
|
|
14
14
|
def dotted_to_container(path, value, result = nil)
|
|
15
15
|
Aspera.assert_array_all(path, String)
|
|
16
16
|
# Typed keys
|
|
17
|
-
keys = path.map{ |k| int_or_string(k)}
|
|
17
|
+
keys = path.map { |k| int_or_string(k) }
|
|
18
18
|
# Create, or re-use first level container
|
|
19
19
|
current = (result ||= new_hash_or_array_from_key(keys.first))
|
|
20
20
|
# walk the path, and create sub-containers if necessary
|
|
@@ -73,19 +73,19 @@ module Aspera
|
|
|
73
73
|
add_elements(path, current)
|
|
74
74
|
when Array
|
|
75
75
|
# Array has no nested structures: list of Strings
|
|
76
|
-
if current.none?{ |i| i.is_a?(Array) || i.is_a?(Hash)}
|
|
76
|
+
if current.none? { |i| i.is_a?(Array) || i.is_a?(Hash) }
|
|
77
77
|
to_insert = current.map(&:to_s)
|
|
78
78
|
# Array of Hashes with only 'name' keys: list of Strings
|
|
79
|
-
elsif current.all?{ |i| i.is_a?(Hash) && i.keys == ['name']}
|
|
80
|
-
to_insert = current.map{ |i| i['name']}
|
|
79
|
+
elsif current.all? { |i| i.is_a?(Hash) && i.keys == ['name'] }
|
|
80
|
+
to_insert = current.map { |i| i['name'] }
|
|
81
81
|
# Array of Hashes with only 'name' and 'value' keys: Hash of key/values
|
|
82
|
-
elsif current.all?{ |i| i.is_a?(Hash) && i.key?('name') && i.key?('value') && i.length <= 3}
|
|
82
|
+
elsif current.all? { |i| i.is_a?(Hash) && i.key?('name') && i.key?('value') && i.length <= 3 }
|
|
83
83
|
# if there is an extra key, other than 'name' and 'value', insert that key as is
|
|
84
|
-
add_elements(path, current.flat_map{ |h| h.except('name', 'value').to_a})
|
|
84
|
+
add_elements(path, current.flat_map { |h| h.except('name', 'value').to_a })
|
|
85
85
|
# Insert name/value pairs as Hash
|
|
86
|
-
add_elements(path, current.to_h{ |h| h.values_at('name', 'value')})
|
|
86
|
+
add_elements(path, current.to_h { |h| h.values_at('name', 'value') })
|
|
87
87
|
else
|
|
88
|
-
add_elements(path, current.each_with_index.map{ |v, i| [i, v]})
|
|
88
|
+
add_elements(path, current.each_with_index.map { |v, i| [i, v] })
|
|
89
89
|
end
|
|
90
90
|
else
|
|
91
91
|
to_insert = current
|
data/lib/aspera/environment.rb
CHANGED
|
@@ -118,11 +118,11 @@ module Aspera
|
|
|
118
118
|
def secure_execute(*cmd, mode: :execute, **kwargs)
|
|
119
119
|
cmd = cmd.map(&:to_s)
|
|
120
120
|
Aspera.assert(cmd.size.positive?, 'executable must be present', type: ArgumentError)
|
|
121
|
-
Aspera.assert_values(mode, PROCESS_MODES, type: ArgumentError){'mode'}
|
|
121
|
+
Aspera.assert_values(mode, PROCESS_MODES, type: ArgumentError) { 'mode' }
|
|
122
122
|
Log.log.debug do
|
|
123
123
|
parts = [mode.to_s, 'command:']
|
|
124
|
-
kwargs[:env]&.each{ |k, v| parts << "#{k}=#{shell_escape_pretty(v.to_s)}"}
|
|
125
|
-
cmd.each{ |a| parts << shell_escape_pretty(a)}
|
|
124
|
+
kwargs[:env]&.each { |k, v| parts << "#{k}=#{shell_escape_pretty(v.to_s)}" }
|
|
125
|
+
cmd.each { |a| parts << shell_escape_pretty(a) }
|
|
126
126
|
parts.join(' ')
|
|
127
127
|
end
|
|
128
128
|
case mode
|
|
@@ -142,7 +142,7 @@ module Aspera
|
|
|
142
142
|
# https://docs.ruby-lang.org/en/master/Open3.html#method-c-capture3
|
|
143
143
|
# https://docs.ruby-lang.org/en/master/Process.html#module-Process-label-Execution+Options
|
|
144
144
|
argv = [kwargs.delete(:env)].compact + cmd
|
|
145
|
-
exception = kwargs.delete(:exception){true}
|
|
145
|
+
exception = kwargs.delete(:exception) { true }
|
|
146
146
|
result = Open3.capture3(*argv, **kwargs)
|
|
147
147
|
Log.dump(:stdout, result[0], level: :trace1)
|
|
148
148
|
Log.dump(:stderr, result[1], level: :trace1)
|
|
@@ -182,7 +182,7 @@ module Aspera
|
|
|
182
182
|
elsif File.directory?(path)
|
|
183
183
|
mode = 0o700
|
|
184
184
|
else
|
|
185
|
-
Log.log.debug{"No restriction can be set for #{path}"}
|
|
185
|
+
Log.log.debug { "No restriction can be set for #{path}" }
|
|
186
186
|
end
|
|
187
187
|
end
|
|
188
188
|
File.chmod(mode, path) unless mode.nil?
|
|
@@ -198,7 +198,7 @@ module Aspera
|
|
|
198
198
|
|
|
199
199
|
# force locale to C so that unicode characters are not used
|
|
200
200
|
def force_terminal_c
|
|
201
|
-
I18N_VARS.each{ |var| ENV[var] = 'C'}
|
|
201
|
+
I18N_VARS.each { |var| ENV[var] = 'C' }
|
|
202
202
|
end
|
|
203
203
|
|
|
204
204
|
# @return [Boolean] true if we can display Unicode characters
|
|
@@ -208,7 +208,7 @@ module Aspera
|
|
|
208
208
|
# https://pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html
|
|
209
209
|
def terminal_supports_unicode?
|
|
210
210
|
return false unless terminal?
|
|
211
|
-
locale_charmap_utf8? || I18N_VARS.any?{ |var| ENV[var]&.include?('UTF-8')}
|
|
211
|
+
locale_charmap_utf8? || I18N_VARS.any? { |var| ENV[var]&.include?('UTF-8') }
|
|
212
212
|
end
|
|
213
213
|
|
|
214
214
|
private
|
|
@@ -237,11 +237,11 @@ module Aspera
|
|
|
237
237
|
OS_WINDOWS
|
|
238
238
|
when /darwin/, /mac os/
|
|
239
239
|
OS_MACOS
|
|
240
|
-
when /linux/
|
|
240
|
+
when /linux/, /cosmo/
|
|
241
241
|
OS_LINUX
|
|
242
242
|
when /aix/
|
|
243
243
|
OS_AIX
|
|
244
|
-
else Aspera.error_unexpected_value(RbConfig::CONFIG['host_os']){'host_os'}
|
|
244
|
+
else Aspera.error_unexpected_value(RbConfig::CONFIG['host_os']) { 'host_os' }
|
|
245
245
|
end
|
|
246
246
|
@cpu =
|
|
247
247
|
case RbConfig::CONFIG['host_cpu']
|
|
@@ -253,7 +253,7 @@ module Aspera
|
|
|
253
253
|
CPU_S390
|
|
254
254
|
when /arm/, /aarch64/
|
|
255
255
|
CPU_ARM64
|
|
256
|
-
else Aspera.error_unexpected_value(RbConfig::CONFIG['host_cpu']){'host_cpu'}
|
|
256
|
+
else Aspera.error_unexpected_value(RbConfig::CONFIG['host_cpu']) { 'host_cpu' }
|
|
257
257
|
end
|
|
258
258
|
@executable_extension = @os.eql?(OS_WINDOWS) ? '.exe' : nil
|
|
259
259
|
# :text or :graphical depending on the environment
|
|
@@ -289,7 +289,7 @@ module Aspera
|
|
|
289
289
|
def fix_home
|
|
290
290
|
return unless @os.eql?(OS_WINDOWS) && ENV.key?('USERPROFILE') && Dir.exist?(ENV.fetch('USERPROFILE', nil))
|
|
291
291
|
ENV['HOME'] = ENV.fetch('USERPROFILE', nil)
|
|
292
|
-
Log.log.debug{"Windows: set HOME to USERPROFILE: #{Dir.home}"}
|
|
292
|
+
Log.log.debug { "Windows: set HOME to USERPROFILE: #{Dir.home}" }
|
|
293
293
|
nil
|
|
294
294
|
end
|
|
295
295
|
|
|
@@ -305,7 +305,7 @@ module Aspera
|
|
|
305
305
|
when Environment::OS_MACOS then self.class.secure_execute('open', uri.to_s)
|
|
306
306
|
when Environment::OS_WINDOWS then self.class.secure_execute('start', 'explorer', %Q{"#{uri}"})
|
|
307
307
|
when Environment::OS_LINUX then self.class.secure_execute('xdg-open', uri.to_s)
|
|
308
|
-
else Aspera.error_unexpected_value(os){'no graphical open method'}
|
|
308
|
+
else Aspera.error_unexpected_value(os) { 'no graphical open method' }
|
|
309
309
|
end
|
|
310
310
|
nil
|
|
311
311
|
end
|
|
@@ -336,7 +336,7 @@ module Aspera
|
|
|
336
336
|
else
|
|
337
337
|
puts "USER ACTION: open this:\n#{the_url.to_s.red}\n"
|
|
338
338
|
end
|
|
339
|
-
else Aspera.error_unexpected_value(@url_method){'URL open method'}
|
|
339
|
+
else Aspera.error_unexpected_value(@url_method) { 'URL open method' }
|
|
340
340
|
end
|
|
341
341
|
end
|
|
342
342
|
|
data/lib/aspera/faspex_gw.rb
CHANGED
|
@@ -44,7 +44,7 @@ module Aspera
|
|
|
44
44
|
package_data = {
|
|
45
45
|
'title' => faspex_pkg_delivery['title'],
|
|
46
46
|
'note' => faspex_pkg_delivery['note'],
|
|
47
|
-
'recipients' => faspex_pkg_delivery['recipients'].map{ |name| {'name'=>name}}
|
|
47
|
+
'recipients' => faspex_pkg_delivery['recipients'].map { |name| {'name'=>name} }
|
|
48
48
|
}
|
|
49
49
|
package = @app_api.create('packages', package_data)
|
|
50
50
|
# TODO: option to send from remote source or httpgw
|
|
@@ -70,7 +70,7 @@ module Aspera
|
|
|
70
70
|
begin
|
|
71
71
|
Aspera.assert(!request.body.nil?, 'payload missing')
|
|
72
72
|
faspex_pkg_parameters = JSON.parse(request.body)
|
|
73
|
-
Log.
|
|
73
|
+
Log.dump(:faspex_pkg_parameters, faspex_pkg_parameters)
|
|
74
74
|
# compare string, as class is not yet known here
|
|
75
75
|
faspex_package_create_result =
|
|
76
76
|
case @app_api.class.name
|
|
@@ -80,7 +80,7 @@ module Aspera
|
|
|
80
80
|
faspex4_send_to_faspex5(faspex_pkg_parameters)
|
|
81
81
|
else Aspera.error_unexpected_value(@app_api.class.name)
|
|
82
82
|
end
|
|
83
|
-
Log.
|
|
83
|
+
Log.dump(:faspex_package_create_result, faspex_package_create_result)
|
|
84
84
|
response.status = 200
|
|
85
85
|
response.content_type = Mime::JSON
|
|
86
86
|
response.body = JSON.generate(faspex_package_create_result)
|
|
@@ -89,7 +89,7 @@ module Aspera
|
|
|
89
89
|
response['Content-Type'] = Mime::JSON
|
|
90
90
|
response.body = {error: e.message, stacktrace: e.backtrace}.to_json
|
|
91
91
|
Log.log.error(e.message)
|
|
92
|
-
Log.log.debug{e.backtrace.join("\n")}
|
|
92
|
+
Log.log.debug { e.backtrace.join("\n") }
|
|
93
93
|
end
|
|
94
94
|
else
|
|
95
95
|
response.status = 400
|
|
@@ -16,18 +16,18 @@ module Aspera
|
|
|
16
16
|
@parameters = parameters.symbolize_keys
|
|
17
17
|
Log.dump(:post_proc_parameters, @parameters)
|
|
18
18
|
not_allowed = @parameters.keys - ALLOWED_PARAMETERS
|
|
19
|
-
Aspera.assert(not_allowed.empty?){"unsupported parameters: #{not_allowed.join(', ')}"}
|
|
19
|
+
Aspera.assert(not_allowed.empty?) { "unsupported parameters: #{not_allowed.join(', ')}" }
|
|
20
20
|
@parameters[:script_folder] ||= '.'
|
|
21
21
|
@parameters[:fail_on_error] ||= false
|
|
22
22
|
@parameters[:timeout_seconds] ||= 60
|
|
23
23
|
super(server)
|
|
24
|
-
Log.log.debug{'Faspex4PostProcServlet initialized'}
|
|
24
|
+
Log.log.debug { 'Faspex4PostProcServlet initialized' }
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
# :reek:UncommunicativeMethodName
|
|
28
28
|
def do_POST(request, response)
|
|
29
|
-
Log.log.debug{"request=#{request.path}"}
|
|
30
|
-
Log.
|
|
29
|
+
Log.log.debug { "request=#{request.path}" }
|
|
30
|
+
Log.dump(:query, request.query)
|
|
31
31
|
begin
|
|
32
32
|
# Only accept requests on the root
|
|
33
33
|
if !request.path.start_with?(@parameters[:root])
|
|
@@ -44,9 +44,14 @@ module Aspera
|
|
|
44
44
|
end
|
|
45
45
|
# build script path by removing domain and adding script folder
|
|
46
46
|
script_file = request.path[@parameters[:root].size..]
|
|
47
|
-
Log.log.debug{"script file=#{script_file}"}
|
|
47
|
+
Log.log.debug { "script file=#{script_file}" }
|
|
48
48
|
script_path = File.join(@parameters[:script_folder], script_file)
|
|
49
|
-
|
|
49
|
+
# Resolve the real path and ensure it stays within script_folder (prevents path traversal)
|
|
50
|
+
resolved_script_folder = File.realpath(@parameters[:script_folder])
|
|
51
|
+
resolved_script_path = File.realpath(script_path)
|
|
52
|
+
Aspera.assert(resolved_script_path.start_with?("#{resolved_script_folder}/")) { 'Script path traversal attempt detected' }
|
|
53
|
+
script_path = resolved_script_path
|
|
54
|
+
Log.log.debug { "script=#{script_path}" }
|
|
50
55
|
webhook_parameters = JSON.parse(request.body)
|
|
51
56
|
Log.dump(:webhook_parameters, webhook_parameters)
|
|
52
57
|
process_status = nil
|
|
@@ -55,7 +60,7 @@ module Aspera
|
|
|
55
60
|
Environment.secure_eval(File.read(script_path), __FILE__, __LINE__).call(webhook_parameters)
|
|
56
61
|
else
|
|
57
62
|
# env expects only strings
|
|
58
|
-
environment = webhook_parameters.each_with_object({}){ |(k, v), h| h[k] = v.to_s}
|
|
63
|
+
environment = webhook_parameters.each_with_object({}) { |(k, v), h| h[k] = v.to_s }
|
|
59
64
|
post_proc_pid = Environment.secure_execute(script_path, mode: :background, env: environment)
|
|
60
65
|
Timeout.timeout(@parameters[:timeout_seconds]) do
|
|
61
66
|
# "wait" for process to avoid zombie
|
|
@@ -63,12 +68,12 @@ module Aspera
|
|
|
63
68
|
post_proc_pid = nil
|
|
64
69
|
end
|
|
65
70
|
process_status = $CHILD_STATUS
|
|
66
|
-
Aspera.assert(process_status.success? || !@parameters[:fail_on_error]){"script #{script_path} failed with code #{process_status.exitstatus}"}
|
|
71
|
+
Aspera.assert(process_status.success? || !@parameters[:fail_on_error]) { "script #{script_path} failed with code #{process_status.exitstatus}" }
|
|
67
72
|
end
|
|
68
73
|
response.status = 200
|
|
69
74
|
response.content_type = Mime::JSON
|
|
70
75
|
response.body = JSON.generate({status: 'success', script: script_path, exit_code: process_status&.exitstatus})
|
|
71
|
-
Log.log.debug{'Script executed successfully'}
|
|
76
|
+
Log.log.debug { 'Script executed successfully' }
|
|
72
77
|
rescue => e
|
|
73
78
|
Log.log.error("Script failed: #{e.class}:#{e.message}")
|
|
74
79
|
if !post_proc_pid.nil?
|
data/lib/aspera/graphql.rb
CHANGED
|
@@ -21,14 +21,16 @@ module Aspera
|
|
|
21
21
|
query = load_query(name)
|
|
22
22
|
response = rest.create(nil, {query: query, variables: variables})
|
|
23
23
|
errors = response['errors']
|
|
24
|
-
raise RestCallError, errors.map{ |e| e['message']}.join("\n") if errors.is_a?(Array) && !errors.empty?
|
|
24
|
+
raise RestCallError, errors.map { |e| e['message'] }.join("\n") if errors.is_a?(Array) && !errors.empty?
|
|
25
25
|
response['data']
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
private
|
|
29
29
|
|
|
30
30
|
def load_query(name)
|
|
31
|
-
File.
|
|
31
|
+
candidate = File.realpath(File.join(QUERIES_FOLDER, "#{name}.graphql"))
|
|
32
|
+
Aspera.assert(candidate.start_with?("#{QUERIES_FOLDER}/")) { "Invalid GraphQL query name: #{name}" }
|
|
33
|
+
File.read(candidate)
|
|
32
34
|
end
|
|
33
35
|
end
|
|
34
36
|
end
|
data/lib/aspera/hash_ext.rb
CHANGED
|
@@ -5,14 +5,14 @@ class ::Hash
|
|
|
5
5
|
# @param second [Hash] Hash to merge into self
|
|
6
6
|
# @return [Hash] New merged hash
|
|
7
7
|
def deep_merge(second)
|
|
8
|
-
merge(second){ |_key, v1, v2| v1.is_a?(Hash) && v2.is_a?(Hash) ? v1.deep_merge(v2) : v2}
|
|
8
|
+
merge(second) { |_key, v1, v2| v1.is_a?(Hash) && v2.is_a?(Hash) ? v1.deep_merge(v2) : v2 }
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
# Recursively merge another hash in-place, with nested hashes merged deeply
|
|
12
12
|
# @param second [Hash] Hash to merge into self
|
|
13
13
|
# @return [Hash] self
|
|
14
14
|
def deep_merge!(second)
|
|
15
|
-
merge!(second){ |_key, v1, v2| v1.is_a?(Hash) && v2.is_a?(Hash) ? v1.deep_merge!(v2) : v2}
|
|
15
|
+
merge!(second) { |_key, v1, v2| v1.is_a?(Hash) && v2.is_a?(Hash) ? v1.deep_merge!(v2) : v2 }
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
# Recursively iterate through hash and execute block on leaf values
|
|
@@ -44,7 +44,7 @@ module Aspera
|
|
|
44
44
|
params: args,
|
|
45
45
|
id: @request_id += 1
|
|
46
46
|
})
|
|
47
|
-
Aspera.assert_type(data, Hash){'response'}
|
|
47
|
+
Aspera.assert_type(data, Hash) { 'response' }
|
|
48
48
|
Aspera.assert(data['jsonrpc'] == VERSION, 'bad version in response')
|
|
49
49
|
Aspera.assert(data.key?('id'), 'missing id in response')
|
|
50
50
|
Aspera.assert(!(data.key?('error') && data.key?('result')), 'both error and response')
|
data/lib/aspera/keychain/base.rb
CHANGED
|
@@ -4,12 +4,19 @@ module Aspera
|
|
|
4
4
|
module Keychain
|
|
5
5
|
class Base
|
|
6
6
|
CONTENT_KEYS = %i[label username password url description].freeze
|
|
7
|
+
|
|
8
|
+
# Returns a lightweight list: one Hash per secret with :label (and :id if the backend has one).
|
|
9
|
+
# Override in subclasses that can provide this cheaply without fetching full item details.
|
|
10
|
+
def ids
|
|
11
|
+
all.map { |s| s.slice(:id, :label).compact }
|
|
12
|
+
end
|
|
13
|
+
|
|
7
14
|
def validate_set(options)
|
|
8
|
-
Aspera.assert_type(options, Hash){'options'}
|
|
15
|
+
Aspera.assert_type(options, Hash) { 'options' }
|
|
9
16
|
unsupported = options.keys - CONTENT_KEYS
|
|
10
|
-
Aspera.assert(unsupported.empty?){"unsupported options: #{unsupported}, use #{CONTENT_KEYS.join(', ')}"}
|
|
17
|
+
Aspera.assert(unsupported.empty?) { "unsupported options: #{unsupported}, use #{CONTENT_KEYS.join(', ')}" }
|
|
11
18
|
options.each_pair do |k, v|
|
|
12
|
-
Aspera.assert_type(v, String){k.to_s}
|
|
19
|
+
Aspera.assert_type(v, String) { k.to_s }
|
|
13
20
|
end
|
|
14
21
|
Aspera.assert(options.key?(:label), 'label is required')
|
|
15
22
|
end
|