aspera-cli 4.14.0 → 4.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +29 -3
  4. data/CHANGELOG.md +300 -185
  5. data/CONTRIBUTING.md +74 -23
  6. data/README.md +2346 -1619
  7. data/bin/ascli +16 -25
  8. data/bin/asession +15 -15
  9. data/examples/dascli +2 -2
  10. data/examples/proxy.pac +1 -1
  11. data/lib/aspera/aoc.rb +216 -150
  12. data/lib/aspera/ascmd.rb +25 -18
  13. data/lib/aspera/assert.rb +45 -0
  14. data/lib/aspera/cli/basic_auth_plugin.rb +9 -6
  15. data/lib/aspera/cli/error.rb +17 -0
  16. data/lib/aspera/cli/extended_value.rb +51 -16
  17. data/lib/aspera/cli/formatter.rb +276 -174
  18. data/lib/aspera/cli/hints.rb +81 -0
  19. data/lib/aspera/cli/main.rb +114 -147
  20. data/lib/aspera/cli/manager.rb +181 -136
  21. data/lib/aspera/cli/plugin.rb +82 -64
  22. data/lib/aspera/cli/plugins/alee.rb +0 -1
  23. data/lib/aspera/cli/plugins/aoc.rb +327 -331
  24. data/lib/aspera/cli/plugins/ats.rb +12 -8
  25. data/lib/aspera/cli/plugins/bss.rb +2 -2
  26. data/lib/aspera/cli/plugins/config.rb +575 -439
  27. data/lib/aspera/cli/plugins/console.rb +40 -0
  28. data/lib/aspera/cli/plugins/cos.rb +4 -5
  29. data/lib/aspera/cli/plugins/faspex.rb +111 -92
  30. data/lib/aspera/cli/plugins/faspex5.rb +245 -182
  31. data/lib/aspera/cli/plugins/node.rb +239 -160
  32. data/lib/aspera/cli/plugins/orchestrator.rb +56 -19
  33. data/lib/aspera/cli/plugins/preview.rb +54 -38
  34. data/lib/aspera/cli/plugins/server.rb +63 -20
  35. data/lib/aspera/cli/plugins/shares.rb +64 -38
  36. data/lib/aspera/cli/sync_actions.rb +68 -0
  37. data/lib/aspera/cli/transfer_agent.rb +64 -67
  38. data/lib/aspera/cli/transfer_progress.rb +73 -0
  39. data/lib/aspera/cli/version.rb +1 -1
  40. data/lib/aspera/colors.rb +3 -1
  41. data/lib/aspera/command_line_builder.rb +27 -22
  42. data/lib/aspera/cos_node.rb +6 -4
  43. data/lib/aspera/coverage.rb +22 -0
  44. data/lib/aspera/data_repository.rb +33 -2
  45. data/lib/aspera/environment.rb +21 -8
  46. data/lib/aspera/fasp/agent_alpha.rb +116 -0
  47. data/lib/aspera/fasp/agent_base.rb +40 -76
  48. data/lib/aspera/fasp/agent_connect.rb +21 -22
  49. data/lib/aspera/fasp/agent_direct.rb +169 -179
  50. data/lib/aspera/fasp/agent_httpgw.rb +200 -195
  51. data/lib/aspera/fasp/agent_node.rb +43 -35
  52. data/lib/aspera/fasp/agent_trsdk.rb +124 -41
  53. data/lib/aspera/fasp/error_info.rb +2 -2
  54. data/lib/aspera/fasp/faux_file.rb +52 -0
  55. data/lib/aspera/fasp/installation.rb +89 -191
  56. data/lib/aspera/fasp/management.rb +249 -0
  57. data/lib/aspera/fasp/parameters.rb +86 -47
  58. data/lib/aspera/fasp/parameters.yaml +75 -8
  59. data/lib/aspera/fasp/products.rb +162 -0
  60. data/lib/aspera/fasp/resume_policy.rb +7 -5
  61. data/lib/aspera/fasp/sync.rb +273 -0
  62. data/lib/aspera/fasp/transfer_spec.rb +10 -8
  63. data/lib/aspera/fasp/uri.rb +6 -6
  64. data/lib/aspera/faspex_gw.rb +11 -8
  65. data/lib/aspera/faspex_postproc.rb +8 -7
  66. data/lib/aspera/hash_ext.rb +2 -2
  67. data/lib/aspera/id_generator.rb +3 -1
  68. data/lib/aspera/json_rpc.rb +51 -0
  69. data/lib/aspera/keychain/encrypted_hash.rb +46 -11
  70. data/lib/aspera/keychain/macos_security.rb +15 -13
  71. data/lib/aspera/line_logger.rb +23 -0
  72. data/lib/aspera/log.rb +61 -19
  73. data/lib/aspera/nagios.rb +7 -2
  74. data/lib/aspera/node.rb +105 -21
  75. data/lib/aspera/node_simulator.rb +214 -0
  76. data/lib/aspera/oauth.rb +57 -36
  77. data/lib/aspera/open_application.rb +4 -4
  78. data/lib/aspera/persistency_action_once.rb +13 -14
  79. data/lib/aspera/persistency_folder.rb +5 -4
  80. data/lib/aspera/preview/file_types.rb +56 -268
  81. data/lib/aspera/preview/generator.rb +28 -39
  82. data/lib/aspera/preview/options.rb +2 -0
  83. data/lib/aspera/preview/terminal.rb +36 -16
  84. data/lib/aspera/preview/utils.rb +23 -29
  85. data/lib/aspera/proxy_auto_config.rb +6 -3
  86. data/lib/aspera/rest.rb +127 -80
  87. data/lib/aspera/rest_call_error.rb +1 -1
  88. data/lib/aspera/rest_error_analyzer.rb +16 -14
  89. data/lib/aspera/rest_errors_aspera.rb +39 -34
  90. data/lib/aspera/secret_hider.rb +18 -17
  91. data/lib/aspera/ssh.rb +10 -5
  92. data/lib/aspera/temp_file_manager.rb +11 -4
  93. data/lib/aspera/web_auth.rb +10 -7
  94. data/lib/aspera/web_server_simple.rb +11 -5
  95. data.tar.gz.sig +0 -0
  96. metadata +108 -39
  97. metadata.gz.sig +0 -0
  98. data/lib/aspera/cli/listener/line_dump.rb +0 -19
  99. data/lib/aspera/cli/listener/logger.rb +0 -22
  100. data/lib/aspera/cli/listener/progress.rb +0 -50
  101. data/lib/aspera/cli/listener/progress_multi.rb +0 -84
  102. data/lib/aspera/cli/plugins/sync.rb +0 -44
  103. data/lib/aspera/fasp/listener.rb +0 -13
  104. data/lib/aspera/sync.rb +0 -213
data/lib/aspera/ascmd.rb CHANGED
@@ -1,6 +1,8 @@
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'
5
+ require 'aspera/assert'
4
6
 
5
7
  module Aspera
6
8
  # Run +ascmd+ commands using specified executor (usually, remotely on transfer node)
@@ -20,9 +22,16 @@ module Aspera
20
22
  # @param [Symbol] one of OPERATIONS
21
23
  # @param [Array] parameters for "as" command
22
24
  # @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"
25
+ def execute_single(action_sym, arguments=nil)
26
+ # add "as_" command
27
+ main_command = ["as_#{action_sym}"]
28
+ arguments&.each do |v|
29
+ # enclose arguments in double quotes, protect backslash and double quotes
30
+ main_command.push(%Q{"#{v.gsub(/["\\]/n){|s|"\\#{s}"}}"})
31
+ end
32
+ # execute the main command and then exit
33
+ stdin_input = [main_command.join(' '), 'as_exit', ''].join("\n")
34
+ Log.log.debug{"execute_single:#{stdin_input}"}
26
35
  # execute, get binary output
27
36
  byte_buffer = @command_executor.execute('ascmd', stdin_input).unpack('C*')
28
37
  raise 'ERROR: empty answer from server' if byte_buffer.empty?
@@ -44,29 +53,27 @@ module Aspera
44
53
  end
45
54
  end
46
55
  # for info, second overrides first, so restore it
47
- case result.keys.length; when 0 then result = system_info; when 1 then result = result[result.keys.first]; else raise 'error'; end
56
+ case result.keys.length; when 0 then result = system_info; when 1 then result = result[result.keys.first]; else error_unexpected_value(result.keys.length); end
48
57
  # raise error as exception
49
- raise Error.new(result[:errno], result[:errstr], action_sym, args) if
58
+ raise Error.new(result[:errno], result[:errstr], action_sym, arguments) if
50
59
  result.is_a?(Hash) && (result.keys.sort == TYPES_DESCR[:error][:fields].map{|i|i[:name]}.sort)
51
60
  return result
52
61
  end # execute_single
53
62
 
54
63
  # This exception is raised when +ascmd+ returns an error.
55
64
  class Error < StandardError
56
- attr_reader :errno, :errstr, :command, :args
57
-
58
- def initialize(errno, errstr, cmd, args); super(); @errno = errno; @errstr = errstr; @command = cmd; @args = args; end # rubocop:disable Style/Semicolon
59
-
60
- def message; "ascmd: (#{errno}) #{errstr}"; end
65
+ def initialize(errno, errstr, cmd, arguments)
66
+ super(); @errno = errno; @errstr = errstr; @command = cmd; @arguments = arguments; end # rubocop:disable Style/Semicolon
61
67
 
62
- def extended_message; "ascmd: errno=#{errno} errstr=\"#{errstr}\" command=\"#{command}\" args=#{args}"; end
68
+ def message; "ascmd: #{@errstr} (#{@errno})"; end
69
+ def extended_message; "ascmd: errno=#{@errno} errstr=\"#{@errstr}\" command=#{@command} arguments=#{@arguments.join(',')}"; end
63
70
  end # Error
64
71
 
65
72
  # description of result structures (see ascmdtypes.h). Base types are big endian
66
73
  # key = name of type
67
74
  TYPES_DESCR = {
68
75
  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},
76
+ 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
77
  {name: :info, is_a: :info}, {name: :success, is_a: nil, special: :return_true}, {name: :exit, is_a: nil},
71
78
  {name: :df, is_a: :mnt, special: :restart_on_first}, {name: :md5sum, is_a: :md5sum}]},
72
79
  stat: {decode: :field_list,
@@ -118,7 +125,7 @@ module Aspera
118
125
  indent_level = (indent_level || -1) + 1
119
126
  type_descr = TYPES_DESCR[type_name]
120
127
  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}
128
+ Log.log.trace1{"#{' .' * indent_level}parse:#{type_name}:#{type_descr[:decode]}:#{buffer[0, 16]}...".red}
122
129
  result = nil
123
130
  case type_descr[:decode]
124
131
  when :base
@@ -128,7 +135,7 @@ module Aspera
128
135
  byte_array = [byte_array] unless byte_array.is_a?(Array)
129
136
  result = byte_array.pack('C*').unpack1(type_descr[:unpack])
130
137
  result.force_encoding('UTF-8') if type_name.eql?(:zstr)
131
- Log.log.debug{"#{' .' * indent_level}-> base:#{byte_array} -> #{result}"}
138
+ Log.log.trace1{"#{' .' * indent_level}-> base:#{byte_array} -> #{result}"}
132
139
  result = Time.at(result) if type_name.eql?(:epoch)
133
140
  when :buffer_list
134
141
  result = []
@@ -138,7 +145,7 @@ module Aspera
138
145
  raise 'ERROR:not enough bytes' if buffer.length < length
139
146
  value = buffer.shift(length)
140
147
  result.push({btype: btype, buffer: value})
141
- Log.log.debug{"#{' .' * indent_level}:buffer_list[#{result.length - 1}] #{result.last}"}
148
+ Log.log.trace1{"#{' .' * indent_level}:buffer_list[#{result.length - 1}] #{result.last}"}
142
149
  end
143
150
  when :field_list
144
151
  # by default the result is one struct
@@ -147,13 +154,13 @@ module Aspera
147
154
  parse(buffer, :blist, indent_level).each do |typed_buffer|
148
155
  # what type of field is it ?
149
156
  field_info = field_description(type_name, typed_buffer)
150
- Log.log.debug{"#{' .' * indent_level}+ field(special=#{field_info[:special]})=#{field_info[:name]}".green}
157
+ Log.log.trace1{"#{' .' * indent_level}+ field(special=#{field_info[:special]})=#{field_info[:name]}".green}
151
158
  case field_info[:special]
152
159
  when nil
153
160
  result[field_info[:name]] = parse(typed_buffer[:buffer], field_info[:is_a], indent_level)
154
161
  when :return_true
155
162
  result[field_info[:name]] = true
156
- when :substruct
163
+ when :sub_struct
157
164
  result[field_info[:name]] = parse(typed_buffer[:buffer], :blist, indent_level).map{|r|parse(r[:buffer], field_info[:is_a], indent_level)}
158
165
  when :multiple
159
166
  result[field_info[:name]] ||= []
@@ -167,7 +174,7 @@ module Aspera
167
174
  end
168
175
  end
169
176
  end
170
- else raise "error: unknown decode:#{type_descr[:decode]}"
177
+ else error_unexpected_value(type_descr[:decode])
171
178
  end # is_a
172
179
  return result
173
180
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aspera
4
+ class InternalError < StandardError
5
+ end
6
+
7
+ class AssertError < StandardError
8
+ end
9
+ end
10
+
11
+ class Object
12
+ def assert(assertion, info = nil, level: 2, exception_class: Aspera::AssertError)
13
+ raise Aspera::InternalError, 'bad assert: both info and block given' unless info.nil? || !block_given?
14
+ return if assertion
15
+ message = 'assertion failed'
16
+ info = yield if block_given?
17
+ message = "#{message}: #{info}" if info
18
+ message = "#{message}: #{caller(level..level).first}"
19
+ raise exception_class, message
20
+ end
21
+
22
+ # assert that value has the given type
23
+ # @param value [Object] the value to check
24
+ # @param type [Class] the expected type
25
+ def assert_type(value, type, exception_class: Aspera::AssertError)
26
+ assert(value.is_a?(type), level: 3, exception_class: exception_class){"#{block_given? ? "#{yield}: " : nil}expecting #{type}, but have #{value.inspect}"}
27
+ end
28
+
29
+ # assert that value is one of the given values
30
+ def assert_values(value, values, exception_class: Aspera::AssertError)
31
+ assert(values.include?(value), level: 3, exception_class: exception_class) do
32
+ "#{block_given? ? "#{yield}: " : nil}expecting one of #{values.inspect}, but have #{value.inspect}"
33
+ end
34
+ end
35
+
36
+ # the line with this shall never be reached
37
+ def error_unreachable_line
38
+ raise Aspera::InternalError, "unreachable line reached: #{caller(2..2).first}"
39
+ end
40
+
41
+ # the value is not one of the expected values
42
+ def error_unexpected_value(value, exception_class: Aspera::InternalError)
43
+ raise exception_class, "#{block_given? ? "#{yield}: " : nil}unexpected value: #{value.inspect}"
44
+ end
45
+ end
@@ -8,17 +8,20 @@ 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].declare(:url, 'URL of application, e.g. https://org.asperafiles.com')
13
- env[:options].declare(:username, 'Username to log in')
14
- env[:options].declare(: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
@@ -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,10 @@
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'
7
+ require 'aspera/assert'
5
8
  require 'json'
6
9
  require 'base64'
7
10
  require 'zlib'
@@ -14,6 +17,11 @@ module Aspera
14
17
  class ExtendedValue
15
18
  include Singleton
16
19
 
20
+ # special values
21
+ INIT = 'INIT'
22
+ ALL = 'ALL'
23
+ DEF = 'DEF'
24
+
17
25
  class << self
18
26
  # decode comma separated table text
19
27
  def decode_csvt(value)
@@ -24,35 +32,42 @@ module Aspera
24
32
  if col_titles.nil?
25
33
  col_titles = values
26
34
  else
27
- entry = {}
28
- col_titles.each{|title|entry[title] = values.shift}
29
- hash_array.push(entry)
35
+ hash_array.push(col_titles.zip(values).to_h)
30
36
  end
31
37
  end
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|prompt = v.empty? ? 'secret' : v; $stdin.getpass("#{prompt}> ")}, # 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
 
@@ -63,25 +78,45 @@ module Aspera
63
78
  # add a new handler
64
79
  def set_handler(name, method)
65
80
  Log.log.debug{"setting handler for #{name}"}
66
- raise 'name must be Symbol' unless name.is_a?(Symbol)
81
+ assert_type(name, Symbol){'name'}
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