aspera-cli 4.14.0 → 4.16.0

Sign up to get free protection for your applications and to get access to all the features.
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