aspera-cli 4.13.0 → 4.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +81 -7
  4. data/CONTRIBUTING.md +22 -6
  5. data/README.md +2038 -1080
  6. data/bin/ascli +18 -9
  7. data/bin/asession +12 -14
  8. data/examples/dascli +1 -1
  9. data/examples/proxy.pac +1 -1
  10. data/examples/rubyc +24 -0
  11. data/lib/aspera/aoc.rb +219 -159
  12. data/lib/aspera/ascmd.rb +25 -14
  13. data/lib/aspera/cli/basic_auth_plugin.rb +12 -9
  14. data/lib/aspera/cli/error.rb +17 -0
  15. data/lib/aspera/cli/extended_value.rb +47 -12
  16. data/lib/aspera/cli/formatter.rb +260 -179
  17. data/lib/aspera/cli/hints.rb +80 -0
  18. data/lib/aspera/cli/main.rb +104 -156
  19. data/lib/aspera/cli/manager.rb +259 -209
  20. data/lib/aspera/cli/plugin.rb +123 -63
  21. data/lib/aspera/cli/plugins/alee.rb +2 -3
  22. data/lib/aspera/cli/plugins/aoc.rb +341 -261
  23. data/lib/aspera/cli/plugins/ats.rb +22 -21
  24. data/lib/aspera/cli/plugins/bss.rb +5 -5
  25. data/lib/aspera/cli/plugins/config.rb +578 -627
  26. data/lib/aspera/cli/plugins/console.rb +44 -6
  27. data/lib/aspera/cli/plugins/cos.rb +15 -17
  28. data/lib/aspera/cli/plugins/faspex.rb +114 -100
  29. data/lib/aspera/cli/plugins/faspex5.rb +411 -264
  30. data/lib/aspera/cli/plugins/node.rb +354 -259
  31. data/lib/aspera/cli/plugins/orchestrator.rb +61 -29
  32. data/lib/aspera/cli/plugins/preview.rb +82 -90
  33. data/lib/aspera/cli/plugins/server.rb +79 -32
  34. data/lib/aspera/cli/plugins/shares.rb +55 -42
  35. data/lib/aspera/cli/sync_actions.rb +68 -0
  36. data/lib/aspera/cli/transfer_agent.rb +66 -73
  37. data/lib/aspera/cli/transfer_progress.rb +74 -0
  38. data/lib/aspera/cli/version.rb +1 -1
  39. data/lib/aspera/colors.rb +12 -8
  40. data/lib/aspera/command_line_builder.rb +14 -11
  41. data/lib/aspera/cos_node.rb +3 -2
  42. data/lib/aspera/data/6 +0 -0
  43. data/lib/aspera/environment.rb +24 -9
  44. data/lib/aspera/fasp/agent_aspera.rb +126 -0
  45. data/lib/aspera/fasp/agent_base.rb +31 -77
  46. data/lib/aspera/fasp/agent_connect.rb +25 -21
  47. data/lib/aspera/fasp/agent_direct.rb +89 -103
  48. data/lib/aspera/fasp/agent_httpgw.rb +231 -149
  49. data/lib/aspera/fasp/agent_node.rb +41 -34
  50. data/lib/aspera/fasp/agent_trsdk.rb +75 -32
  51. data/lib/aspera/fasp/error_info.rb +4 -2
  52. data/lib/aspera/fasp/faux_file.rb +52 -0
  53. data/lib/aspera/fasp/installation.rb +53 -195
  54. data/lib/aspera/fasp/management.rb +244 -0
  55. data/lib/aspera/fasp/parameters.rb +71 -37
  56. data/lib/aspera/fasp/parameters.yaml +76 -8
  57. data/lib/aspera/fasp/products.rb +162 -0
  58. data/lib/aspera/fasp/resume_policy.rb +3 -3
  59. data/lib/aspera/fasp/transfer_spec.rb +7 -6
  60. data/lib/aspera/fasp/uri.rb +26 -24
  61. data/lib/aspera/faspex_gw.rb +2 -2
  62. data/lib/aspera/faspex_postproc.rb +2 -2
  63. data/lib/aspera/hash_ext.rb +14 -4
  64. data/lib/aspera/json_rpc.rb +49 -0
  65. data/lib/aspera/keychain/macos_security.rb +13 -13
  66. data/lib/aspera/line_logger.rb +23 -0
  67. data/lib/aspera/log.rb +58 -16
  68. data/lib/aspera/node.rb +157 -92
  69. data/lib/aspera/oauth.rb +37 -19
  70. data/lib/aspera/open_application.rb +4 -4
  71. data/lib/aspera/persistency_action_once.rb +1 -1
  72. data/lib/aspera/persistency_folder.rb +2 -2
  73. data/lib/aspera/preview/file_types.rb +4 -2
  74. data/lib/aspera/preview/generator.rb +22 -35
  75. data/lib/aspera/preview/options.rb +2 -0
  76. data/lib/aspera/preview/terminal.rb +73 -16
  77. data/lib/aspera/preview/utils.rb +21 -28
  78. data/lib/aspera/proxy_auto_config.js +2 -2
  79. data/lib/aspera/rest.rb +136 -68
  80. data/lib/aspera/rest_call_error.rb +1 -1
  81. data/lib/aspera/rest_error_analyzer.rb +15 -14
  82. data/lib/aspera/rest_errors_aspera.rb +37 -34
  83. data/lib/aspera/secret_hider.rb +18 -15
  84. data/lib/aspera/ssh.rb +5 -2
  85. data/lib/aspera/sync.rb +127 -119
  86. data/lib/aspera/temp_file_manager.rb +10 -3
  87. data/lib/aspera/web_auth.rb +10 -7
  88. data/lib/aspera/web_server_simple.rb +9 -4
  89. data.tar.gz.sig +0 -0
  90. metadata +34 -17
  91. metadata.gz.sig +0 -0
  92. data/docs/test_env.conf +0 -186
  93. data/lib/aspera/cli/listener/line_dump.rb +0 -19
  94. data/lib/aspera/cli/listener/logger.rb +0 -22
  95. data/lib/aspera/cli/listener/progress.rb +0 -50
  96. data/lib/aspera/cli/listener/progress_multi.rb +0 -84
  97. data/lib/aspera/cli/plugins/sync.rb +0 -44
  98. data/lib/aspera/data/7 +0 -0
  99. data/lib/aspera/fasp/listener.rb +0 -13
data/lib/aspera/ascmd.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # cspell:ignore ascmd smode errstr zstr zmode zuid zgid zctime zatime zmtime fcount dcount btype blist codeset lc_ctype ascmdtypes
3
4
  require 'aspera/log'
4
5
 
5
6
  module Aspera
@@ -20,9 +21,16 @@ module Aspera
20
21
  # @param [Symbol] one of OPERATIONS
21
22
  # @param [Array] parameters for "as" command
22
23
  # @return result of command, type depends on command (bool, array, hash)
23
- def execute_single(action_sym, args=nil)
24
- # concatenate arguments, enclose in double quotes, protect backslash and double quotes, add "as_" command and as_exit
25
- stdin_input = (args || []).map{|v| '"' + v.gsub(/["\\]/n) {|s| '\\' + s } + '"'}.unshift('as_' + action_sym.to_s).join(' ') + "\nas_exit\n"
24
+ def execute_single(action_sym, arguments=nil)
25
+ # add "as_" command
26
+ main_command = ["as_#{action_sym}"]
27
+ arguments&.each do |v|
28
+ # enclose arguments in double quotes, protect backslash and double quotes
29
+ main_command.push(%Q{"#{v.gsub(/["\\]/n){|s|"\\#{s}"}}"})
30
+ end
31
+ # execute the main command and then exit
32
+ stdin_input = [main_command.join(' '), 'as_exit', ''].join("\n")
33
+ Log.log.debug{"execute_single:#{stdin_input}"}
26
34
  # execute, get binary output
27
35
  byte_buffer = @command_executor.execute('ascmd', stdin_input).unpack('C*')
28
36
  raise 'ERROR: empty answer from server' if byte_buffer.empty?
@@ -46,27 +54,29 @@ module Aspera
46
54
  # for info, second overrides first, so restore it
47
55
  case result.keys.length; when 0 then result = system_info; when 1 then result = result[result.keys.first]; else raise 'error'; end
48
56
  # raise error as exception
49
- raise Error.new(result[:errno], result[:errstr], action_sym, args) if
57
+ raise Error.new(result[:errno], result[:errstr], action_sym, arguments) if
50
58
  result.is_a?(Hash) && (result.keys.sort == TYPES_DESCR[:error][:fields].map{|i|i[:name]}.sort)
51
59
  return result
52
60
  end # execute_single
53
61
 
54
62
  # This exception is raised when +ascmd+ returns an error.
55
63
  class Error < StandardError
56
- attr_reader :errno, :errstr, :command, :args
64
+ def initialize(errno, errstr, cmd, arguments)
65
+ super(); @errno = errno; @errstr = errstr; @command = cmd; @arguments = arguments; end # rubocop:disable Style/Semicolon
57
66
 
58
- def initialize(errno, errstr, cmd, args); super(); @errno = errno; @errstr = errstr; @command = cmd; @args = args; end # rubocop:disable Style/Semicolon
67
+ def message; "ascmd: #{@errstr} (#{@errno})"; end
59
68
 
60
- def message; "ascmd: (#{errno}) #{errstr}"; end
69
+ # TODO : delete : attr_reader :errno #, :errstr, :command
70
+ # TODO : delete :def args; @arguments; end
61
71
 
62
- def extended_message; "ascmd: errno=#{errno} errstr=\"#{errstr}\" command=\"#{command}\" args=#{args}"; end
72
+ def extended_message; "ascmd: errno=#{@errno} errstr=\"#{@errstr}\" command=#{@command} arguments=#{@arguments.join(',')}"; end
63
73
  end # Error
64
74
 
65
75
  # description of result structures (see ascmdtypes.h). Base types are big endian
66
76
  # key = name of type
67
77
  TYPES_DESCR = {
68
78
  result: {decode: :field_list,
69
- fields: [{name: :file, is_a: :stat}, {name: :dir, is_a: :stat, special: :substruct}, {name: :size, is_a: :size}, {name: :error, is_a: :error},
79
+ fields: [{name: :file, is_a: :stat}, {name: :dir, is_a: :stat, special: :sub_struct}, {name: :size, is_a: :size}, {name: :error, is_a: :error},
70
80
  {name: :info, is_a: :info}, {name: :success, is_a: nil, special: :return_true}, {name: :exit, is_a: nil},
71
81
  {name: :df, is_a: :mnt, special: :restart_on_first}, {name: :md5sum, is_a: :md5sum}]},
72
82
  stat: {decode: :field_list,
@@ -118,7 +128,7 @@ module Aspera
118
128
  indent_level = (indent_level || -1) + 1
119
129
  type_descr = TYPES_DESCR[type_name]
120
130
  raise "Unexpected type #{type_name}" if type_descr.nil?
121
- Log.log.debug{"#{' .' * indent_level}parse:#{type_name}:#{type_descr[:decode]}:#{buffer[0, 16]}...".red}
131
+ Log.log.trace1{"#{' .' * indent_level}parse:#{type_name}:#{type_descr[:decode]}:#{buffer[0, 16]}...".red}
122
132
  result = nil
123
133
  case type_descr[:decode]
124
134
  when :base
@@ -127,7 +137,8 @@ module Aspera
127
137
  byte_array = buffer.shift(num_bytes)
128
138
  byte_array = [byte_array] unless byte_array.is_a?(Array)
129
139
  result = byte_array.pack('C*').unpack1(type_descr[:unpack])
130
- Log.log.debug{"#{' .' * indent_level}-> base:#{byte_array} -> #{result}"}
140
+ result.force_encoding('UTF-8') if type_name.eql?(:zstr)
141
+ Log.log.trace1{"#{' .' * indent_level}-> base:#{byte_array} -> #{result}"}
131
142
  result = Time.at(result) if type_name.eql?(:epoch)
132
143
  when :buffer_list
133
144
  result = []
@@ -137,7 +148,7 @@ module Aspera
137
148
  raise 'ERROR:not enough bytes' if buffer.length < length
138
149
  value = buffer.shift(length)
139
150
  result.push({btype: btype, buffer: value})
140
- Log.log.debug{"#{' .' * indent_level}:buffer_list[#{result.length - 1}] #{result.last}"}
151
+ Log.log.trace1{"#{' .' * indent_level}:buffer_list[#{result.length - 1}] #{result.last}"}
141
152
  end
142
153
  when :field_list
143
154
  # by default the result is one struct
@@ -146,13 +157,13 @@ module Aspera
146
157
  parse(buffer, :blist, indent_level).each do |typed_buffer|
147
158
  # what type of field is it ?
148
159
  field_info = field_description(type_name, typed_buffer)
149
- Log.log.debug{"#{' .' * indent_level}+ field(special=#{field_info[:special]})=#{field_info[:name]}".green}
160
+ Log.log.trace1{"#{' .' * indent_level}+ field(special=#{field_info[:special]})=#{field_info[:name]}".green}
150
161
  case field_info[:special]
151
162
  when nil
152
163
  result[field_info[:name]] = parse(typed_buffer[:buffer], field_info[:is_a], indent_level)
153
164
  when :return_true
154
165
  result[field_info[:name]] = true
155
- when :substruct
166
+ when :sub_struct
156
167
  result[field_info[:name]] = parse(typed_buffer[:buffer], :blist, indent_level).map{|r|parse(r[:buffer], field_info[:is_a], indent_level)}
157
168
  when :multiple
158
169
  result[field_info[:name]] ||= []
@@ -8,29 +8,32 @@ module Aspera
8
8
  # base class for applications supporting basic authentication
9
9
  class BasicAuthPlugin < Aspera::Cli::Plugin
10
10
  class << self
11
- def register_options(env)
12
- env[:options].add_opt_simple(:url, 'URL of application, e.g. https://org.asperafiles.com')
13
- env[:options].add_opt_simple(:username, 'username to log in')
14
- env[:options].add_opt_simple(:password, "user's password")
15
- env[:options].parse_options!
11
+ @@basic_options_declared = false # rubocop:disable Style/ClassVars
12
+ def declare_options(options, force: false)
13
+ return if @@basic_options_declared && !force
14
+ @@basic_options_declared = true # rubocop:disable Style/ClassVars
15
+ options.declare(:url, 'URL of application, e.g. https://faspex.example.com/aspera/faspex')
16
+ options.declare(:username, 'Username to log in')
17
+ options.declare(:password, "User's password")
18
+ options.parse_options!
16
19
  end
17
20
  end
18
21
 
19
22
  def initialize(env)
20
23
  super(env)
21
- self.class.register_options(env) unless env[:skip_basic_auth_options]
24
+ BasicAuthPlugin.declare_options(options, force: env[:all_manuals])
22
25
  end
23
26
 
24
27
  # returns a Rest object with basic auth
25
28
  def basic_auth_params(subpath=nil)
26
- api_url = options.get_option(:url, is_type: :mandatory)
29
+ api_url = options.get_option(:url, mandatory: true)
27
30
  api_url = api_url + '/' + subpath unless subpath.nil?
28
31
  return {
29
32
  base_url: api_url,
30
33
  auth: {
31
34
  type: :basic,
32
- username: options.get_option(:username, is_type: :mandatory),
33
- password: options.get_option(:password, is_type: :mandatory)
35
+ username: options.get_option(:username, mandatory: true),
36
+ password: options.get_option(:password, mandatory: true)
34
37
  }}
35
38
  end
36
39
 
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aspera
4
+ module Cli
5
+ # CLI base exception
6
+ class Error < StandardError; end
7
+
8
+ # raised when an unexpected argument is provided
9
+ class BadArgument < Error; end
10
+
11
+ class NoSuchIdentifier < Error
12
+ def initialize(res_type, res_id)
13
+ super("#{res_type} with identifier #{res_id} not found")
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # cspell:ignore csvt jsonpp
3
4
  require 'aspera/uri_reader'
4
5
  require 'aspera/environment'
6
+ require 'aspera/log'
5
7
  require 'json'
6
8
  require 'base64'
7
9
  require 'zlib'
@@ -14,6 +16,10 @@ module Aspera
14
16
  class ExtendedValue
15
17
  include Singleton
16
18
 
19
+ # special values
20
+ ALL = 'ALL'
21
+ DEF = 'DEF'
22
+
17
23
  class << self
18
24
  # decode comma separated table text
19
25
  def decode_csvt(value)
@@ -32,27 +38,36 @@ module Aspera
32
38
  Log.log.warn('Titled CSV file without any line') if hash_array.empty?
33
39
  return hash_array
34
40
  end
41
+
42
+ def assert_no_value(v, what)
43
+ raise "no value allowed for extended value type: #{what}" unless v.empty?
44
+ end
35
45
  end
36
46
 
37
47
  private
38
48
 
39
49
  def initialize
50
+ # base handlers
51
+ # other handlers can be set using set_handler, e.g. `preset` is reader in config plugin
40
52
  @handlers = {
53
+ val: lambda{|v|v},
41
54
  base64: lambda{|v|Base64.decode64(v)},
42
55
  csvt: lambda{|v|ExtendedValue.decode_csvt(v)},
43
- env: lambda{|v|ENV[v]},
56
+ env: lambda{|v|ENV.fetch(v, nil)},
44
57
  file: lambda{|v|File.read(File.expand_path(v))},
58
+ uri: lambda{|v|UriReader.read(v)},
45
59
  json: lambda{|v|JSON.parse(v)},
46
60
  lines: lambda{|v|v.split("\n")},
47
61
  list: lambda{|v|v[1..-1].split(v[0])},
62
+ none: lambda{|v|ExtendedValue.assert_no_value(v, :none); nil}, # rubocop:disable Style/Semicolon
48
63
  path: lambda{|v|File.expand_path(v)},
49
- ruby: lambda{|v|Environment.secure_eval(v)},
50
- secret: lambda{|v|raise 'no value allowed for secret' unless v.empty?; $stdin.getpass('secret> ')}, # rubocop:disable Style/Semicolon
51
- stdin: lambda{|v|raise 'no value allowed for stdin' unless v.empty?; $stdin.read}, # rubocop:disable Style/Semicolon
52
- uri: lambda{|v|UriReader.read(v)},
53
- val: lambda{|v|v},
54
- zlib: lambda{|v|Zlib::Inflate.inflate(v)}
55
- # other handlers can be set using set_handler, e.g. preset is reader in config plugin
64
+ re: lambda{|v|Regexp.new(v)},
65
+ ruby: lambda{|v|Environment.secure_eval(v, __FILE__, __LINE__)},
66
+ secret: lambda{|v|ExtendedValue.assert_no_value(v, :secret); $stdin.getpass('secret> ')}, # rubocop:disable Style/Semicolon
67
+ stdin: lambda{|v|ExtendedValue.assert_no_value(v, :stdin); $stdin.read}, # rubocop:disable Style/Semicolon
68
+ yaml: lambda{|v|YAML.load(v)},
69
+ zlib: lambda{|v|Zlib::Inflate.inflate(v)},
70
+ extend: lambda{|v|ExtendedValue.instance.evaluate_all(v)}
56
71
  }
57
72
  end
58
73
 
@@ -67,21 +82,41 @@ module Aspera
67
82
  @handlers[name] = method
68
83
  end
69
84
 
85
+ # Regex to match an extended value
86
+ def ext_re
87
+ "@(#{modifiers.join('|')}):"
88
+ end
89
+
70
90
  # parse an option value if it is a String using supported extended value modifiers
71
91
  # other value types are returned as is
72
92
  def evaluate(value)
73
- return value if !value.is_a?(String)
93
+ return value unless value.is_a?(String)
94
+ regex = Regexp.new("^#{ext_re}(.*)$")
74
95
  # first determine decoders, in reversed order
75
96
  handlers_reversed = []
76
- while (m = value.match(/^@([^:]+):(.*)/)) && @handlers.include?(m[1].to_sym)
77
- handlers_reversed.unshift(m[1].to_sym)
97
+ while (m = value.match(regex))
98
+ handler = m[1].to_sym
99
+ handlers_reversed.unshift(handler)
78
100
  value = m[2]
101
+ # stop processing if handler is extend (it will be processed later)
102
+ break if handler.eql?(:extend)
79
103
  end
80
104
  handlers_reversed.each do |handler|
81
105
  value = @handlers[handler].call(value)
82
106
  end
83
107
  return value
84
- end # parse
108
+ end # evaluate
109
+
110
+ # find inner extended values
111
+ def evaluate_all(value)
112
+ regex = Regexp.new("^(.*)#{ext_re}([^@]*)@(.*)$")
113
+ while (m = value.match(regex))
114
+ sub_value = "@#{m[2]}:#{m[3]}"
115
+ Log.log.debug("evaluating #{sub_value}")
116
+ value = m[1] + evaluate(sub_value) + m[4]
117
+ end
118
+ return value
119
+ end
85
120
  end
86
121
  end
87
122
  end