aspera-cli 4.26.2 → 4.27.1
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 +310 -21
- data/CONTRIBUTING.md +99 -14
- data/TODO.md +50 -0
- data/bin/ascli +3 -3
- data/docs/README.md +10977 -0
- data/docs/test-mcp-with-ai.md +202 -0
- data/lib/aspera/agent/base.rb +29 -7
- data/lib/aspera/agent/connect.rb +58 -21
- data/lib/aspera/agent/desktop.rb +53 -18
- data/lib/aspera/agent/direct.rb +71 -40
- data/lib/aspera/agent/factory.rb +3 -3
- data/lib/aspera/agent/httpgw.rb +55 -14
- data/lib/aspera/agent/node.rb +34 -2
- data/lib/aspera/agent/transferd.rb +48 -12
- data/lib/aspera/api/alee.rb +1 -1
- data/lib/aspera/api/aoc.rb +42 -44
- data/lib/aspera/api/cos_node.rb +10 -9
- data/lib/aspera/api/faspex.rb +16 -15
- data/lib/aspera/api/httpgw.rb +23 -23
- data/lib/aspera/api/node.rb +51 -70
- data/lib/aspera/api/queries/bss_subscription_account.graphql +18 -0
- data/lib/aspera/api/queries/bss_subscription_usage.graphql +18 -0
- data/lib/aspera/ascmd.rb +32 -30
- data/lib/aspera/ascp/installation.rb +75 -69
- data/lib/aspera/ascp/management.rb +7 -8
- data/lib/aspera/assert.rb +12 -13
- data/lib/aspera/cli/ascp_actions.rb +155 -0
- data/lib/aspera/cli/async_transfer_store.rb +125 -0
- data/lib/aspera/cli/bootstrapper.rb +197 -0
- data/lib/aspera/cli/command_registry.rb +145 -0
- data/lib/aspera/cli/command_spec.rb +141 -0
- data/lib/aspera/cli/context.rb +19 -6
- data/lib/aspera/cli/error.rb +16 -0
- data/lib/aspera/cli/extended_value.rb +27 -26
- data/lib/aspera/cli/formatter.rb +95 -59
- data/lib/aspera/cli/gem_checker.rb +65 -0
- data/lib/aspera/cli/hints.rb +3 -3
- data/lib/aspera/cli/http.rb +74 -30
- data/lib/aspera/cli/info.rb +2 -0
- data/lib/aspera/cli/mailer.rb +97 -0
- data/lib/aspera/cli/mcp_tool.rb +225 -0
- data/lib/aspera/cli/option_declarator.rb +77 -0
- data/lib/aspera/cli/options.schema.yaml +634 -7
- data/lib/aspera/cli/parser.rb +1217 -0
- data/lib/aspera/cli/plugins/alee.rb +20 -22
- data/lib/aspera/cli/plugins/aoc.rb +1178 -881
- data/lib/aspera/cli/plugins/ats.rb +205 -162
- data/lib/aspera/cli/plugins/base.rb +703 -175
- data/lib/aspera/cli/plugins/basic_auth.rb +7 -9
- data/lib/aspera/cli/plugins/config.rb +434 -757
- data/lib/aspera/cli/plugins/console.rb +107 -65
- data/lib/aspera/cli/plugins/cos.rb +46 -33
- data/lib/aspera/cli/plugins/factory.rb +6 -6
- data/lib/aspera/cli/plugins/faspex5.rb +627 -388
- data/lib/aspera/cli/plugins/faspio.rb +54 -51
- data/lib/aspera/cli/plugins/httpgw.rb +18 -25
- data/lib/aspera/cli/plugins/mcp.rb +279 -0
- data/lib/aspera/cli/plugins/node.rb +988 -840
- data/lib/aspera/cli/plugins/oauth.rb +7 -10
- data/lib/aspera/cli/plugins/orchestrator.rb +112 -135
- data/lib/aspera/cli/plugins/preview.rb +195 -157
- data/lib/aspera/cli/plugins/server.rb +141 -91
- data/lib/aspera/cli/plugins/shares.rb +343 -110
- data/lib/aspera/cli/preset_actions.rb +159 -0
- data/lib/aspera/cli/preset_manager.rb +82 -39
- data/lib/aspera/cli/result.rb +78 -26
- data/lib/aspera/cli/runner.rb +323 -153
- data/lib/aspera/cli/secret_finder.rb +40 -0
- data/lib/aspera/cli/special_values.rb +1 -0
- data/lib/aspera/cli/sync_actions.rb +89 -64
- data/lib/aspera/cli/terminal_formatter.rb +3 -3
- data/lib/aspera/cli/transfer_actions.rb +90 -0
- data/lib/aspera/cli/transfer_agent.rb +129 -64
- data/lib/aspera/cli/transfer_progress.rb +9 -9
- data/lib/aspera/cli/vault_manager.rb +79 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +28 -30
- data/lib/aspera/colors.rb +3 -3
- data/lib/aspera/command_line_builder.rb +34 -34
- data/lib/aspera/command_line_converter.rb +1 -1
- data/lib/aspera/coverage.rb +1 -2
- data/lib/aspera/data_repository.rb +2 -1
- data/lib/aspera/dot_container.rb +12 -12
- data/lib/aspera/environment.rb +30 -23
- data/lib/aspera/exec_spec.rb +13 -0
- data/lib/aspera/faspex_gw.rb +5 -5
- data/lib/aspera/faspex_postproc.rb +16 -10
- data/lib/aspera/graphql.rb +37 -0
- data/lib/aspera/hash_ext.rb +8 -2
- data/lib/aspera/json_rpc/client.rb +62 -0
- data/lib/aspera/json_rpc/version.rb +7 -0
- data/lib/aspera/keychain/base.rb +10 -3
- data/lib/aspera/keychain/encrypted_hash.rb +92 -17
- data/lib/aspera/keychain/factory.rb +20 -8
- 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 +82 -0
- data/lib/aspera/log.rb +29 -15
- data/lib/aspera/markdown.rb +90 -7
- data/lib/aspera/nagios.rb +8 -8
- data/lib/aspera/node_simulator.rb +35 -27
- data/lib/aspera/oauth/base.rb +9 -10
- data/lib/aspera/oauth/boot.rb +5 -5
- data/lib/aspera/oauth/factory.rb +12 -10
- data/lib/aspera/oauth/jwt.rb +9 -11
- data/lib/aspera/oauth/web.rb +6 -6
- data/lib/aspera/persistency_action_once.rb +12 -12
- data/lib/aspera/persistency_folder.rb +18 -11
- data/lib/aspera/preview/file_types.rb +5 -5
- data/lib/aspera/preview/generator.rb +52 -49
- data/lib/aspera/preview/options.rb +3 -2
- data/lib/aspera/preview/terminal.rb +5 -5
- data/lib/aspera/preview/utils.rb +66 -19
- data/lib/aspera/products/connect.rb +2 -2
- data/lib/aspera/products/desktop.rb +1 -1
- data/lib/aspera/products/other.rb +3 -3
- data/lib/aspera/products/transferd.rb +4 -4
- data/lib/aspera/proxy_auto_config.rb +15 -14
- data/lib/aspera/rest.rb +159 -151
- data/lib/aspera/rest_error_analyzer.rb +6 -6
- data/lib/aspera/rest_errors_aspera.rb +0 -10
- data/lib/aspera/rest_list.rb +11 -10
- 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 +1853 -510
- data/lib/aspera/schema/IBM_Aspera_Shares.yaml +4653 -0
- data/lib/aspera/schema/documentation.rb +65 -28
- data/lib/aspera/schema/reader.rb +136 -13
- data/lib/aspera/schema/registry.rb +60 -8
- data/lib/aspera/secret_hider.rb +12 -7
- data/lib/aspera/ssh.rb +64 -31
- data/lib/aspera/ssl.rb +5 -5
- data/lib/aspera/sync/conf.schema.yaml +2 -2
- data/lib/aspera/sync/database.rb +2 -2
- data/lib/aspera/sync/operations.rb +22 -25
- data/lib/aspera/temp_file_manager.rb +25 -5
- data/lib/aspera/timer_limiter.rb +1 -1
- data/lib/aspera/transfer/faux_file.rb +24 -11
- data/lib/aspera/transfer/parameters.rb +36 -34
- data/lib/aspera/transfer/result.rb +74 -0
- data/lib/aspera/transfer/resumer.rb +10 -10
- data/lib/aspera/transfer/spec.rb +18 -1
- data/lib/aspera/transfer/spec.schema.yaml +12 -3
- data/lib/aspera/uri_reader.rb +52 -15
- data/lib/aspera/web_auth.rb +8 -7
- data/lib/aspera/web_server_simple.rb +15 -12
- data/lib/aspera/yaml.rb +5 -4
- data.tar.gz.sig +0 -0
- metadata +34 -6
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/manager.rb +0 -766
- data/lib/aspera/cli/plugins/faspex.rb +0 -539
- data/lib/aspera/json_rpc.rb +0 -52
- data/lib/aspera/transfer/uri.rb +0 -56
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'aspera/environment'
|
|
4
|
+
require 'aspera/keychain/one_password_base'
|
|
5
|
+
require 'json'
|
|
6
|
+
|
|
7
|
+
module Aspera
|
|
8
|
+
module Keychain
|
|
9
|
+
# Manage secrets using the 1Password CLI (`op`)
|
|
10
|
+
# https://developer.1password.com/docs/cli/
|
|
11
|
+
class OnePasswordCli < Base
|
|
12
|
+
include OnePasswordBase
|
|
13
|
+
|
|
14
|
+
OP_EXECUTABLE = 'op'
|
|
15
|
+
private_constant :OP_EXECUTABLE
|
|
16
|
+
|
|
17
|
+
# @param vault [String, nil] Name or ID of the 1Password vault (optional, uses default vault if omitted)
|
|
18
|
+
# @param account [String, nil] Account shorthand for `op` (optional, uses default account if omitted)
|
|
19
|
+
def initialize(vault: nil, account: nil)
|
|
20
|
+
super()
|
|
21
|
+
@vault = vault
|
|
22
|
+
@account = account
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def info
|
|
26
|
+
h = {}
|
|
27
|
+
h[:vault] = @vault unless @vault.nil?
|
|
28
|
+
h[:account] = @account unless @account.nil?
|
|
29
|
+
h
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def all
|
|
33
|
+
items = op_json('item', 'list', '--categories', ITEM_CATEGORY)
|
|
34
|
+
return items.map do |item|
|
|
35
|
+
item_to_secret(op_json('item', 'get', item['id'])).merge(id: item['id'])
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def ids
|
|
40
|
+
op_json('item', 'list', '--categories', ITEM_CATEGORY).map { |item| {id: item['id'], label: item['title']} }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def set(options)
|
|
44
|
+
validate_set(options)
|
|
45
|
+
args = ['item', 'create', '--category', ITEM_CATEGORY, "--title=#{options[:label]}"]
|
|
46
|
+
args << "#{FIELD_USERNAME}=#{options[:username]}" if options[:username]
|
|
47
|
+
args << "#{FIELD_PASSWORD}[password]=#{options[:password]}" if options[:password]
|
|
48
|
+
args << "#{FIELD_URL}[url]=#{options[:url]}" if options[:url]
|
|
49
|
+
args << "#{FIELD_NOTES_ID}=#{options[:description]}" if options[:description]
|
|
50
|
+
op_run(*args)
|
|
51
|
+
nil
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def get(label:, id: nil, exception: true)
|
|
55
|
+
identifier = id || label
|
|
56
|
+
stdout, _stderr, status = op_capture('item', 'get', identifier, '--format=json')
|
|
57
|
+
if !status.success?
|
|
58
|
+
raise "Secret '#{label}' not found" if exception
|
|
59
|
+
return
|
|
60
|
+
end
|
|
61
|
+
full = JSON.parse(stdout)
|
|
62
|
+
return item_to_secret(full).merge(id: full['id'])
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def delete(label:, id: nil)
|
|
66
|
+
op_run('item', 'delete', id || label)
|
|
67
|
+
nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def common_flags
|
|
73
|
+
flags = []
|
|
74
|
+
flags.push('--vault', @vault) unless @vault.nil?
|
|
75
|
+
flags.push('--account', @account) unless @account.nil?
|
|
76
|
+
flags
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def op_json(*args)
|
|
80
|
+
stdout, = op_capture(*args, '--format=json', exception: true)
|
|
81
|
+
JSON.parse(stdout)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def op_run(*args)
|
|
85
|
+
op_capture(*args, exception: true)
|
|
86
|
+
nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def op_capture(*args, exception: true)
|
|
90
|
+
Environment.instance.class.secure_execute(
|
|
91
|
+
OP_EXECUTABLE, *args, *common_flags,
|
|
92
|
+
mode: :capture,
|
|
93
|
+
exception: exception
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'strscan'
|
|
4
|
+
|
|
5
|
+
module Aspera
|
|
6
|
+
# Parse and represent an HTTP Link header as per RFC 8288.
|
|
7
|
+
# Inspired by the link_header gem, with the following fixes:
|
|
8
|
+
# - rel lookup is case-insensitive (RFC 7230: parameter names are case-insensitive)
|
|
9
|
+
# - uses StringScanner so commas inside <URI> are never mistaken for entry separators
|
|
10
|
+
# - no external dependency
|
|
11
|
+
class LinkHeader
|
|
12
|
+
# A single link entry: one URI and its attribute pairs.
|
|
13
|
+
class Link
|
|
14
|
+
attr_reader :href, :attr_pairs
|
|
15
|
+
|
|
16
|
+
def initialize(href, attr_pairs)
|
|
17
|
+
@href = href
|
|
18
|
+
@attr_pairs = attr_pairs
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Retrieve an attribute value by name, case-insensitively.
|
|
22
|
+
def [](key)
|
|
23
|
+
pair = @attr_pairs.detect { |k, _v| k.casecmp?(key) }
|
|
24
|
+
pair&.last
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
attr_reader :links
|
|
29
|
+
|
|
30
|
+
def initialize(links = [])
|
|
31
|
+
@links = links
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Return the href of the first link whose +rel+ attribute matches +rel+.
|
|
35
|
+
# Comparison is case-insensitive per RFC 7230 s.3.2 and RFC 8288 s.3.
|
|
36
|
+
# Returns nil if no link with that relation exists.
|
|
37
|
+
# @param rel [String]
|
|
38
|
+
# @return [String, nil]
|
|
39
|
+
def find_href(rel: 'next')
|
|
40
|
+
@links.detect { |link| link['rel']&.casecmp?(rel) }&.href
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class << self
|
|
44
|
+
# Parse a raw Link header value into a +LinkHeader+ instance.
|
|
45
|
+
# Uses StringScanner so that commas inside <URI> are not treated as separators.
|
|
46
|
+
# @param raw [String, nil]
|
|
47
|
+
# @return [LinkHeader]
|
|
48
|
+
def parse(raw)
|
|
49
|
+
return new unless raw && !raw.empty?
|
|
50
|
+
|
|
51
|
+
links = []
|
|
52
|
+
scanner = StringScanner.new(raw)
|
|
53
|
+
|
|
54
|
+
while scanner.scan(HREF_RE)
|
|
55
|
+
href = scanner[1].strip
|
|
56
|
+
attrs = []
|
|
57
|
+
while scanner.scan(ATTR_RE)
|
|
58
|
+
key = scanner[1]
|
|
59
|
+
# scanner[2] = full match (token or "quoted"), scanner[3] = content inside double-quotes
|
|
60
|
+
value = scanner[3] || scanner[2]
|
|
61
|
+
attrs << [key, value]
|
|
62
|
+
break unless scanner.scan(SEMI_RE)
|
|
63
|
+
end
|
|
64
|
+
links << Link.new(href, attrs)
|
|
65
|
+
break unless scanner.scan(COMMA_RE)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
new(links)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private :new
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# RFC 2616 token: any char except separators
|
|
75
|
+
TOKEN_RE = /[^()<>@,;:\"\[\]?={}\s]+/ # RFC 2616 token
|
|
76
|
+
QUOTED_RE = /"((?:[^"\\]|\\.)*)"/ # double-quoted string with backslash escapes
|
|
77
|
+
HREF_RE = /\s*<([^>]*)>\s*;?\s*/ # <URI> possibly followed by ;
|
|
78
|
+
ATTR_RE = /(#{TOKEN_RE})\s*=\s*(#{TOKEN_RE}|#{QUOTED_RE})\s*/ # key=value or key="value"
|
|
79
|
+
SEMI_RE = /;\s*/ # parameter separator
|
|
80
|
+
COMMA_RE = /,\s*/ # link entry separator
|
|
81
|
+
end
|
|
82
|
+
end
|
data/lib/aspera/log.rb
CHANGED
|
@@ -20,13 +20,13 @@ class Logger
|
|
|
20
20
|
|
|
21
21
|
# Add custom level to logger severity, below debug level
|
|
22
22
|
module Severity
|
|
23
|
-
1.upto(TRACE_MAX).each{ |level| const_set(:"TRACE#{level}", - level)}
|
|
23
|
+
1.upto(TRACE_MAX).each { |level| const_set(:"TRACE#{level}", - level) }
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
# Hash
|
|
27
27
|
# key [Integer] Log level (e.g. 0 for DEBUG)
|
|
28
28
|
# value [Symbol] Uppercase log level label (e.g. :DEBUG)
|
|
29
|
-
SEVERITY_LABEL = Severity.constants.to_h{ |name| [Severity.const_get(name), name]}
|
|
29
|
+
SEVERITY_LABEL = Severity.constants.to_h { |name| [Severity.const_get(name), name] }
|
|
30
30
|
|
|
31
31
|
# Override
|
|
32
32
|
# @param severity [Integer] Log severity as int
|
|
@@ -36,17 +36,20 @@ class Logger
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
class << self
|
|
39
|
-
# Define methods for a given log level
|
|
39
|
+
# Define logger methods for a given log level (e.g. `debug`, `debug?`, `debug!`)
|
|
40
|
+
# @param str_level [String] Log level name (e.g. "DEBUG", "TRACE1")
|
|
41
|
+
# @return [nil]
|
|
40
42
|
def make_methods(str_level)
|
|
41
43
|
int_level = ::Logger.const_get(str_level.upcase)
|
|
42
44
|
method_base = str_level.downcase
|
|
43
|
-
define_method(method_base, ->(message = nil, &block){add(int_level, message, &block)})
|
|
44
|
-
define_method("#{method_base}?", ->{level <= int_level})
|
|
45
|
-
define_method("#{method_base}!", ->{self.level = int_level})
|
|
45
|
+
define_method(method_base, ->(message = nil, &block) { add(int_level, message, &block) })
|
|
46
|
+
define_method("#{method_base}?", -> { level <= int_level })
|
|
47
|
+
define_method("#{method_base}!", -> { self.level = int_level })
|
|
48
|
+
nil
|
|
46
49
|
end
|
|
47
50
|
end
|
|
48
51
|
# Declare methods for all levels
|
|
49
|
-
Logger::Severity.constants.each{ |severity| make_methods(severity)}
|
|
52
|
+
Logger::Severity.constants.each { |severity| make_methods(severity) }
|
|
50
53
|
end
|
|
51
54
|
|
|
52
55
|
# Restore warnings
|
|
@@ -65,7 +68,7 @@ module Aspera
|
|
|
65
68
|
LOG_TYPES = %i[stderr stdout syslog].freeze
|
|
66
69
|
|
|
67
70
|
# Levels are :trace2,:trace1,:debug,:info,:warn,:error,fatal,:unknown
|
|
68
|
-
LEVELS = Logger::Severity.constants.sort{ |a, b| Logger::Severity.const_get(a) <=> Logger::Severity.const_get(b)}.map{ |c| c.downcase.to_sym}.freeze
|
|
71
|
+
LEVELS = Logger::Severity.constants.sort { |a, b| Logger::Severity.const_get(a) <=> Logger::Severity.const_get(b) }.map { |c| c.downcase.to_sym }.freeze
|
|
69
72
|
|
|
70
73
|
# Class methods
|
|
71
74
|
class << self
|
|
@@ -97,7 +100,7 @@ module Aspera
|
|
|
97
100
|
JSON.pretty_generate(object) rescue PP.pp(object, +'')
|
|
98
101
|
when :ruby
|
|
99
102
|
PP.pp(object, +'')
|
|
100
|
-
else Aspera.error_unexpected_value(instance.dump_format){'dump format'}
|
|
103
|
+
else Aspera.error_unexpected_value(instance.dump_format) { 'dump format' }
|
|
101
104
|
end
|
|
102
105
|
"#{name.to_s.green}(#{instance.dump_format})#{object.class}=\n#{dump_text}"
|
|
103
106
|
end
|
|
@@ -116,7 +119,7 @@ module Aspera
|
|
|
116
119
|
# Returns the last 2 containers (module/class) and method caller
|
|
117
120
|
def caller_method
|
|
118
121
|
stack = caller
|
|
119
|
-
i = stack.rindex{ |line| line.include?('Logger')}
|
|
122
|
+
i = stack.rindex { |line| line.include?('Logger') }
|
|
120
123
|
frame = stack[i + 1] if i && stack[i + 1]
|
|
121
124
|
return '???' unless frame
|
|
122
125
|
# Extract the "Class::Module::Method" or "Class#method" part
|
|
@@ -142,9 +145,13 @@ module Aspera
|
|
|
142
145
|
attr_reader :logger_type, :logger
|
|
143
146
|
attr_accessor :dump_format
|
|
144
147
|
|
|
148
|
+
# Set the program name used in log output
|
|
149
|
+
# @param value [String] Program name
|
|
150
|
+
# @return [nil]
|
|
145
151
|
def program_name=(value)
|
|
146
152
|
@program_name = value
|
|
147
153
|
self.logger_type = @logger_type
|
|
154
|
+
nil
|
|
148
155
|
end
|
|
149
156
|
|
|
150
157
|
# Set log level of underlying logger given symbol level
|
|
@@ -154,15 +161,19 @@ module Aspera
|
|
|
154
161
|
@logger.level = Logger::Severity.const_get(new_level.to_sym.upcase)
|
|
155
162
|
end
|
|
156
163
|
|
|
164
|
+
# Set log formatter; accepts a symbol name, a Proc, or a Logger::Formatter instance
|
|
165
|
+
# @param formatter [String, Proc, Logger::Formatter] Formatter to use; one of: standard, default, caller
|
|
166
|
+
# @return [nil]
|
|
157
167
|
def formatter=(formatter)
|
|
158
168
|
if formatter.is_a?(String)
|
|
159
|
-
|
|
169
|
+
Aspera.assert(FORMATTER_LAMBDAS.key?(formatter.to_sym), type: Error) { "Unknown formatter #{formatter}, use one of: #{FORMATTERS.join(', ')}" }
|
|
160
170
|
formatter = FORMATTER_LAMBDAS[formatter.to_sym]
|
|
161
171
|
elsif !formatter.respond_to?(:call) && !formatter.is_a?(Logger::Formatter)
|
|
162
172
|
raise Error, 'Formatter must be a String, a Logger::Formatter or a Proc'
|
|
163
173
|
end
|
|
164
174
|
# Update formatter with password hiding
|
|
165
175
|
@logger.formatter = SecretHider.instance.log_formatter(formatter)
|
|
176
|
+
nil
|
|
166
177
|
end
|
|
167
178
|
|
|
168
179
|
def formatter
|
|
@@ -176,7 +187,9 @@ module Aspera
|
|
|
176
187
|
Logger::SEVERITY_LABEL[@logger.level].downcase
|
|
177
188
|
end
|
|
178
189
|
|
|
179
|
-
# Change underlying logger,
|
|
190
|
+
# Change underlying logger output destination, keeping current log level
|
|
191
|
+
# @param new_log_type [Symbol] Log destination; one of LOG_TYPES (:stderr, :stdout, :syslog)
|
|
192
|
+
# @return [nil]
|
|
180
193
|
def logger_type=(new_log_type)
|
|
181
194
|
# [Integer]
|
|
182
195
|
current_severity_integer = @logger&.level || ENV['AS_LOG_LEVEL']&.to_i || Logger::Severity::INFO
|
|
@@ -197,12 +210,13 @@ module Aspera
|
|
|
197
210
|
end
|
|
198
211
|
# Use `local2` facility, like other Aspera components
|
|
199
212
|
@logger = Syslog::Logger.new(@program_name, Syslog::LOG_LOCAL2)
|
|
200
|
-
else Aspera.error_unexpected_value(new_log_type){"log type (#{LOG_TYPES.join(', ')})"}
|
|
213
|
+
else Aspera.error_unexpected_value(new_log_type) { "log type (#{LOG_TYPES.join(', ')})" }
|
|
201
214
|
end
|
|
202
215
|
@logger.level = current_severity_integer
|
|
203
216
|
@logger_type = new_log_type
|
|
204
217
|
# add secret hider to default logger
|
|
205
218
|
self.formatter = @logger.formatter
|
|
219
|
+
nil
|
|
206
220
|
end
|
|
207
221
|
|
|
208
222
|
private
|
|
@@ -233,13 +247,13 @@ module Aspera
|
|
|
233
247
|
[k, short_levl(k).apply(*v)]
|
|
234
248
|
end.freeze
|
|
235
249
|
|
|
236
|
-
DEFAULT_FORMATTER = ->(s, _d, _p, m){"#{LVL_COLOR[s]} #{m}\n"}
|
|
250
|
+
DEFAULT_FORMATTER = ->(s, _d, _p, m) { "#{LVL_COLOR[s]} #{m}\n" }
|
|
237
251
|
|
|
238
252
|
# pre-defined formatters
|
|
239
253
|
FORMATTER_LAMBDAS = {
|
|
240
254
|
standard: Logger::Formatter.new,
|
|
241
255
|
default: DEFAULT_FORMATTER,
|
|
242
|
-
caller: ->(s, _d, _p, m){"#{LVL_COLOR[s]} #{Log.caller_method}\n#{m}\n"}
|
|
256
|
+
caller: ->(s, _d, _p, m) { "#{LVL_COLOR[s]} #{Log.caller_method}\n#{m}\n" }
|
|
243
257
|
}.freeze
|
|
244
258
|
|
|
245
259
|
FORMATTERS = FORMATTER_LAMBDAS.keys
|
data/lib/aspera/markdown.rb
CHANGED
|
@@ -9,45 +9,128 @@ module Aspera
|
|
|
9
9
|
|
|
10
10
|
class << self
|
|
11
11
|
COL_WIDTH = 80
|
|
12
|
+
|
|
13
|
+
# Convert a Markdown heading text to a GitHub-flavoured anchor.
|
|
14
|
+
# Rules: downcase, keep letters/digits/spaces/hyphens, replace spaces with hyphens.
|
|
15
|
+
# Duplicate anchors are disambiguated by appending -1, -2, … (pass a seen Hash to track).
|
|
16
|
+
# @param text [String] raw heading text (without leading # and spaces)
|
|
17
|
+
# @param seen [Hash{String=>Integer}, nil] mutable counter; pass the same Hash across a document
|
|
18
|
+
# @return [String] anchor slug (without leading #)
|
|
19
|
+
def heading_to_anchor(text, seen: nil)
|
|
20
|
+
slug = text
|
|
21
|
+
.downcase
|
|
22
|
+
.gsub(/[`*_]/, '') # strip inline code/bold/italic markers
|
|
23
|
+
.gsub(/&[a-z]+;/, '') # strip HTML entities
|
|
24
|
+
.gsub(/[^\w\s-]/, '') # keep word chars, spaces, hyphens
|
|
25
|
+
.gsub(/\s+/, '-') # spaces → hyphens
|
|
26
|
+
.squeeze('-') # collapse consecutive hyphens
|
|
27
|
+
.strip
|
|
28
|
+
if seen
|
|
29
|
+
count = seen[slug].to_i
|
|
30
|
+
seen[slug] = count + 1
|
|
31
|
+
slug = "#{slug}-#{count}" if count > 0
|
|
32
|
+
end
|
|
33
|
+
slug
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Extract the table of contents from a Markdown document.
|
|
37
|
+
# @param content [String] full Markdown source
|
|
38
|
+
# @return [Array<Hash>] array of { level, title, anchor }
|
|
39
|
+
HEADING_RE = /^(\#{1,6})\s+(.+)$/
|
|
40
|
+
|
|
41
|
+
def toc(content)
|
|
42
|
+
seen = {}
|
|
43
|
+
content.each_line.filter_map do |line|
|
|
44
|
+
m = line.match(HEADING_RE)
|
|
45
|
+
next unless m
|
|
46
|
+
title = m[2].strip
|
|
47
|
+
{level: m[1].length, title: title, anchor: heading_to_anchor(title, seen: seen)}
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Extract the content of a single section (heading + body until next heading of same/higher level).
|
|
52
|
+
# @param content [String] full Markdown source
|
|
53
|
+
# @param anchor [String] GitHub anchor slug (without #)
|
|
54
|
+
# @return [String, nil] the section content, or nil if not found
|
|
55
|
+
def extract_section(content, anchor)
|
|
56
|
+
seen = {}
|
|
57
|
+
section_level = nil
|
|
58
|
+
result = []
|
|
59
|
+
content.each_line do |line|
|
|
60
|
+
m = line.match(HEADING_RE)
|
|
61
|
+
if m
|
|
62
|
+
slug = heading_to_anchor(m[2].strip, seen: seen)
|
|
63
|
+
if section_level.nil?
|
|
64
|
+
# not yet found: check if this heading matches
|
|
65
|
+
next unless slug == anchor
|
|
66
|
+
section_level = m[1].length
|
|
67
|
+
elsif m[1].length <= section_level
|
|
68
|
+
# already in section: stop at same/higher level heading
|
|
69
|
+
break
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
result << line if section_level
|
|
73
|
+
end
|
|
74
|
+
result.empty? ? nil : result.join
|
|
75
|
+
end
|
|
76
|
+
|
|
12
77
|
# Generate markdown from the provided 2D table
|
|
13
78
|
# @param table [Array<Array<String>>] 2D array of strings
|
|
14
79
|
# @return [String] markdown table
|
|
15
80
|
def table(table)
|
|
16
81
|
# get max width of each columns
|
|
17
82
|
col_widths = table.transpose.map do |col|
|
|
18
|
-
[col.flat_map{ |c| c.to_s.delete('`').split(HTML_BREAK).map(&:size)}.max, COL_WIDTH].min
|
|
83
|
+
[col.flat_map { |c| c.to_s.delete('`').split(HTML_BREAK).map(&:size) }.max, COL_WIDTH].min
|
|
19
84
|
end
|
|
20
85
|
headings = table.shift
|
|
21
|
-
table.unshift(col_widths.map{ |col_width| '-' * col_width})
|
|
86
|
+
table.unshift(col_widths.map { |col_width| '-' * col_width })
|
|
22
87
|
table.unshift(headings)
|
|
23
|
-
lines = table.map{ |line| "| #{line.map{ |i| i.to_s.gsub('\\', '\\\\').gsub('|', '\|')}.join(' | ')} |\n"}
|
|
88
|
+
lines = table.map { |line| "| #{line.map { |i| i.to_s.gsub('\\', '\\\\').gsub('|', '\|') }.join(' | ')} |\n" }
|
|
24
89
|
lines[1] = lines[1].tr(' ', '-')
|
|
25
90
|
return lines.join.chomp
|
|
26
91
|
end
|
|
27
92
|
|
|
28
93
|
# Generate markdown list from the provided list
|
|
94
|
+
# @param items [Array<String>] list of items
|
|
95
|
+
# @return [String] markdown unordered list
|
|
29
96
|
def list(items)
|
|
30
|
-
items.map{ |i| "- #{i}"}.join("\n")
|
|
97
|
+
items.map { |i| "- #{i}" }.join("\n")
|
|
31
98
|
end
|
|
32
99
|
|
|
100
|
+
# Generate a markdown heading
|
|
101
|
+
# @param title [String] heading text
|
|
102
|
+
# @param level [Integer] heading level (1–6)
|
|
103
|
+
# @return [String] markdown heading
|
|
33
104
|
def heading(title, level: 1)
|
|
34
105
|
"#{'#' * level} #{title}\n\n"
|
|
35
106
|
end
|
|
36
107
|
|
|
37
|
-
#
|
|
108
|
+
# Generate a GitHub-flavoured admonition block
|
|
109
|
+
# @param lines [Array<String>] lines of the admonition body
|
|
110
|
+
# @param type [String] admonition type: NOTE, CAUTION, WARNING, IMPORTANT, TIP, INFO
|
|
111
|
+
# @return [String] markdown admonition block
|
|
38
112
|
def admonition(lines, type: 'INFO')
|
|
39
|
-
"> [!#{type}]\n#{lines.map{ |l| "> #{l}"}.join("\n")}\n\n"
|
|
113
|
+
"> [!#{type}]\n#{lines.map { |l| "> #{l}" }.join("\n")}\n\n"
|
|
40
114
|
end
|
|
41
115
|
|
|
116
|
+
# Generate a fenced code block
|
|
117
|
+
# @param lines [Array<String>] lines of code
|
|
118
|
+
# @param type [String] language identifier for syntax highlighting
|
|
119
|
+
# @return [String] markdown fenced code block
|
|
42
120
|
def code(lines, type: 'shell')
|
|
43
121
|
"```#{type}\n#{lines.join("\n")}\n```\n\n"
|
|
44
122
|
end
|
|
45
123
|
|
|
46
|
-
# inline code
|
|
124
|
+
# Wrap text in inline code backticks
|
|
125
|
+
# @param text [String] text to wrap
|
|
126
|
+
# @return [String] inline code span
|
|
47
127
|
def icode(text)
|
|
48
128
|
"`#{text}`"
|
|
49
129
|
end
|
|
50
130
|
|
|
131
|
+
# Wrap text in a markdown paragraph (trailing blank line)
|
|
132
|
+
# @param text [String] paragraph content
|
|
133
|
+
# @return [String] paragraph with trailing newlines
|
|
51
134
|
def paragraph(text)
|
|
52
135
|
"#{text}\n\n"
|
|
53
136
|
end
|
data/lib/aspera/nagios.rb
CHANGED
|
@@ -17,7 +17,7 @@ module Aspera
|
|
|
17
17
|
# add methods to add nagios error levels, each take component name and message
|
|
18
18
|
LEVELS.each_index do |code|
|
|
19
19
|
name = "#{ADD_PREFIX}#{LEVELS[code]}".to_sym
|
|
20
|
-
define_method(name){ |comp, msg| @data.push({code: code, comp: comp, msg: msg})}
|
|
20
|
+
define_method(name) { |comp, msg| @data.push({code: code, comp: comp, msg: msg}) }
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
class << self
|
|
@@ -26,18 +26,18 @@ module Aspera
|
|
|
26
26
|
Aspera.assert_type(data, Array)
|
|
27
27
|
Aspera.assert(!data.empty?, 'data is empty')
|
|
28
28
|
%w[status component message].each do |c|
|
|
29
|
-
Aspera.assert(data.first.key?(c)){"result must have #{c}"}
|
|
29
|
+
Aspera.assert(data.first.key?(c)) { "result must have #{c}" }
|
|
30
30
|
end
|
|
31
|
-
res_errors = data.reject{ |s| s['status'].eql?('ok')}
|
|
31
|
+
res_errors = data.reject { |s| s['status'].eql?('ok') }
|
|
32
32
|
# keep only errors in case of problem, other ok are assumed so
|
|
33
33
|
data = res_errors unless res_errors.empty?
|
|
34
34
|
# first is most critical
|
|
35
|
-
data.sort!{ |a, b| LEVELS.index(a['status'].to_sym) <=> LEVELS.index(b['status'].to_sym)}
|
|
35
|
+
data.sort! { |a, b| LEVELS.index(a['status'].to_sym) <=> LEVELS.index(b['status'].to_sym) }
|
|
36
36
|
# build message: if multiple components: concatenate
|
|
37
37
|
# message = data.map{|i|"#{i['component']}:#{i['message']}"}.join(', ').gsub("\n",' ')
|
|
38
38
|
message = data
|
|
39
|
-
.group_by{ |d| d['component']}
|
|
40
|
-
.map{ |comp, items| "#{comp}:#{items.map{ |d| d['message']}.join(',')}"}
|
|
39
|
+
.group_by { |d| d['component'] }
|
|
40
|
+
.map { |comp, items| "#{comp}:#{items.map { |d| d['message'] }.join(',')}" }
|
|
41
41
|
.join(', ')
|
|
42
42
|
.tr("\n", ' ')
|
|
43
43
|
status = data.first['status'].upcase
|
|
@@ -60,7 +60,7 @@ module Aspera
|
|
|
60
60
|
remote_time = Time.parse(remote_date)
|
|
61
61
|
diff_time = (remote_time - Time.now).abs
|
|
62
62
|
diff_rounded = diff_time.round(-2)
|
|
63
|
-
Log.log.debug{"DATE: #{remote_date} #{remote_time} diff=#{diff_rounded}"}
|
|
63
|
+
Log.log.debug { "DATE: #{remote_date} #{remote_time} diff=#{diff_rounded}" }
|
|
64
64
|
msg = "offset #{diff_rounded} sec"
|
|
65
65
|
if diff_time >= DATE_CRIT_OFFSET
|
|
66
66
|
add_critical(component, msg)
|
|
@@ -80,7 +80,7 @@ module Aspera
|
|
|
80
80
|
# @return [Array] of Hash
|
|
81
81
|
def status_list
|
|
82
82
|
Aspera.assert(!@data.empty?, 'missing result')
|
|
83
|
-
@data.map{ |i| {'status' => LEVELS[i[:code]].to_s, 'component' => i[:comp], 'message' => i[:msg]}}
|
|
83
|
+
@data.map { |i| {'status' => LEVELS[i[:code]].to_s, 'component' => i[:comp], 'message' => i[:msg]} }
|
|
84
84
|
end
|
|
85
85
|
end
|
|
86
86
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'aspera/assert'
|
|
3
4
|
require 'aspera/ascp/installation'
|
|
4
5
|
require 'aspera/agent/direct'
|
|
5
6
|
require 'aspera/log'
|
|
@@ -9,7 +10,7 @@ require 'json'
|
|
|
9
10
|
module Aspera
|
|
10
11
|
class NodeSimulator
|
|
11
12
|
def initialize
|
|
12
|
-
@agent = Agent::Direct.new(management_cb: ->(event){process_event(event)})
|
|
13
|
+
@agent = Agent::Direct.new(management_cb: ->(event) { process_event(event) })
|
|
13
14
|
@sessions = {}
|
|
14
15
|
end
|
|
15
16
|
|
|
@@ -18,7 +19,7 @@ module Aspera
|
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
def all_sessions
|
|
21
|
-
@agent.sessions.map{ |session| session[:job_id]}.uniq.each.map{ |job_id| job_to_transfer(job_id)}
|
|
22
|
+
@agent.sessions.map { |session| session[:job_id] }.uniq.each.map { |job_id| job_to_transfer(job_id) }
|
|
22
23
|
end
|
|
23
24
|
|
|
24
25
|
# status: ('waiting', 'partially_completed', 'unknown', 'waiting(read error)',] 'running', 'completed', 'failed'
|
|
@@ -128,28 +129,29 @@ module Aspera
|
|
|
128
129
|
|
|
129
130
|
# Process event from management port
|
|
130
131
|
def process_event(event)
|
|
132
|
+
# Code duplicated on purpose, as implementation goes, blocks will be replaced
|
|
131
133
|
case event['Type']
|
|
132
|
-
when 'NOP' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
|
134
|
+
when 'NOP' then Aspera.Log.debug { "event not managed: #{event['Type']}" }
|
|
133
135
|
# rubocop:disable Lint/DuplicateBranch
|
|
134
|
-
when 'START' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
|
135
|
-
when 'QUERY' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
|
136
|
-
when 'QUERYRSP' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
|
137
|
-
when 'STATS' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
|
138
|
-
when 'STOP' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
|
139
|
-
when 'ERROR' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
|
140
|
-
when 'CANCEL' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
|
141
|
-
when 'DONE' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
|
142
|
-
when 'RATE' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
|
143
|
-
when 'FILEERROR' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
|
144
|
-
when 'SESSION' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
|
145
|
-
when 'NOTIFICATION' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
|
146
|
-
when 'INIT' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
|
147
|
-
when 'VLINK' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
|
148
|
-
when 'PUT' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
|
149
|
-
when 'WRITE' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
|
150
|
-
when 'CLOSE' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
|
151
|
-
when 'SKIP' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
|
152
|
-
when 'ARGSTOP' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
|
|
136
|
+
when 'START' then Aspera.Log.debug { "event not managed: #{event['Type']}" }
|
|
137
|
+
when 'QUERY' then Aspera.Log.debug { "event not managed: #{event['Type']}" }
|
|
138
|
+
when 'QUERYRSP' then Aspera.Log.debug { "event not managed: #{event['Type']}" }
|
|
139
|
+
when 'STATS' then Aspera.Log.debug { "event not managed: #{event['Type']}" }
|
|
140
|
+
when 'STOP' then Aspera.Log.debug { "event not managed: #{event['Type']}" }
|
|
141
|
+
when 'ERROR' then Aspera.Log.debug { "event not managed: #{event['Type']}" }
|
|
142
|
+
when 'CANCEL' then Aspera.Log.debug { "event not managed: #{event['Type']}" }
|
|
143
|
+
when 'DONE' then Aspera.Log.debug { "event not managed: #{event['Type']}" }
|
|
144
|
+
when 'RATE' then Aspera.Log.debug { "event not managed: #{event['Type']}" }
|
|
145
|
+
when 'FILEERROR' then Aspera.Log.debug { "event not managed: #{event['Type']}" }
|
|
146
|
+
when 'SESSION' then Aspera.Log.debug { "event not managed: #{event['Type']}" }
|
|
147
|
+
when 'NOTIFICATION' then Aspera.Log.debug { "event not managed: #{event['Type']}" }
|
|
148
|
+
when 'INIT' then Aspera.Log.debug { "event not managed: #{event['Type']}" }
|
|
149
|
+
when 'VLINK' then Aspera.Log.debug { "event not managed: #{event['Type']}" }
|
|
150
|
+
when 'PUT' then Aspera.Log.debug { "event not managed: #{event['Type']}" }
|
|
151
|
+
when 'WRITE' then Aspera.Log.debug { "event not managed: #{event['Type']}" }
|
|
152
|
+
when 'CLOSE' then Aspera.Log.debug { "event not managed: #{event['Type']}" }
|
|
153
|
+
when 'SKIP' then Aspera.Log.debug { "event not managed: #{event['Type']}" }
|
|
154
|
+
when 'ARGSTOP' then Aspera.Log.debug { "event not managed: #{event['Type']}" }
|
|
153
155
|
# rubocop:enable Lint/DuplicateBranch
|
|
154
156
|
else Aspera.error_unreachable_line
|
|
155
157
|
end
|
|
@@ -162,19 +164,25 @@ module Aspera
|
|
|
162
164
|
PATH_TRANSFERS = '/ops/transfers'
|
|
163
165
|
PATH_ONE_TRANSFER = %r{/ops/transfers/(.+)$}
|
|
164
166
|
PATH_BROWSE = '/files/browse'
|
|
165
|
-
# @param
|
|
166
|
-
# @param
|
|
167
|
+
# @param credentials [Hash]
|
|
168
|
+
# @param simulator [NodeSimulator]
|
|
167
169
|
def initialize(server, credentials, simulator)
|
|
168
170
|
super(server)
|
|
169
171
|
@credentials = credentials
|
|
170
172
|
@simulator = simulator
|
|
173
|
+
# Resolve once at startup; default to current working directory
|
|
174
|
+
@browse_root = File.realpath(credentials[:browse_root] || Dir.pwd)
|
|
171
175
|
end
|
|
172
176
|
|
|
173
177
|
require 'json'
|
|
174
178
|
require 'time'
|
|
175
179
|
|
|
176
180
|
def folder_to_structure(folder_path)
|
|
177
|
-
|
|
181
|
+
# Resolve and confine to browse_root (prevents path traversal via client-supplied path)
|
|
182
|
+
resolved = File.realpath(folder_path)
|
|
183
|
+
Aspera.assert(resolved.start_with?("#{@browse_root}/") || resolved.eql?(@browse_root)) { 'Browse path traversal attempt detected' }
|
|
184
|
+
Aspera.assert(Dir.exist?(resolved)) { "Path does not exist or is not a directory: #{resolved}" }
|
|
185
|
+
folder_path = resolved
|
|
178
186
|
|
|
179
187
|
# Build self structure
|
|
180
188
|
folder_stat = File.stat(folder_path)
|
|
@@ -240,7 +248,7 @@ module Aspera
|
|
|
240
248
|
when PATH_BROWSE
|
|
241
249
|
req = JSON.parse(request.body)
|
|
242
250
|
# req['count']
|
|
243
|
-
set_json_response(request, response, folder_to_structure(req['path']))
|
|
251
|
+
set_json_response(request, response, folder_to_structure(req['path'] || @browse_root))
|
|
244
252
|
else
|
|
245
253
|
set_json_response(request, response, [{error: 'Bad request'}], code: 400)
|
|
246
254
|
end
|
|
@@ -330,7 +338,7 @@ module Aspera
|
|
|
330
338
|
response.status = code
|
|
331
339
|
response['Content-Type'] = Mime::JSON
|
|
332
340
|
response.body = json.to_json
|
|
333
|
-
Log.log.trace1{Log.obj_dump("response for #{request.request_method} #{request.path}", json)}
|
|
341
|
+
Log.log.trace1 { Log.obj_dump("response for #{request.request_method} #{request.path}", json) }
|
|
334
342
|
end
|
|
335
343
|
end
|
|
336
344
|
end
|