aspera-cli 4.25.1 → 4.25.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +46 -1
  4. data/CONTRIBUTING.md +6 -11
  5. data/README.md +31 -9742
  6. data/bin/asession +111 -88
  7. data/lib/aspera/agent/connect.rb +1 -1
  8. data/lib/aspera/agent/desktop.rb +1 -1
  9. data/lib/aspera/agent/direct.rb +19 -18
  10. data/lib/aspera/agent/node.rb +1 -1
  11. data/lib/aspera/api/aoc.rb +25 -8
  12. data/lib/aspera/api/node.rb +16 -14
  13. data/lib/aspera/ascp/installation.rb +32 -51
  14. data/lib/aspera/assert.rb +1 -1
  15. data/lib/aspera/cli/extended_value.rb +1 -0
  16. data/lib/aspera/cli/formatter.rb +0 -4
  17. data/lib/aspera/cli/hints.rb +11 -4
  18. data/lib/aspera/cli/main.rb +3 -6
  19. data/lib/aspera/cli/manager.rb +8 -4
  20. data/lib/aspera/cli/plugins/aoc.rb +11 -14
  21. data/lib/aspera/cli/plugins/base.rb +1 -1
  22. data/lib/aspera/cli/plugins/config.rb +50 -48
  23. data/lib/aspera/cli/plugins/factory.rb +2 -2
  24. data/lib/aspera/cli/plugins/faspex5.rb +4 -3
  25. data/lib/aspera/cli/plugins/preview.rb +6 -11
  26. data/lib/aspera/cli/transfer_agent.rb +2 -2
  27. data/lib/aspera/cli/version.rb +1 -1
  28. data/lib/aspera/environment.rb +30 -16
  29. data/lib/aspera/faspex_gw.rb +1 -1
  30. data/lib/aspera/faspex_postproc.rb +16 -10
  31. data/lib/aspera/hash_ext.rb +8 -0
  32. data/lib/aspera/log.rb +3 -4
  33. data/lib/aspera/markdown.rb +17 -0
  34. data/lib/aspera/oauth/base.rb +1 -1
  35. data/lib/aspera/oauth/web.rb +1 -1
  36. data/lib/aspera/preview/generator.rb +9 -9
  37. data/lib/aspera/rest_call_error.rb +16 -8
  38. data/lib/aspera/rest_error_analyzer.rb +1 -1
  39. data/lib/aspera/transfer/resumer.rb +2 -2
  40. data/lib/aspera/yaml.rb +49 -0
  41. data.tar.gz.sig +0 -0
  42. metadata +16 -2
  43. metadata.gz.sig +0 -0
  44. data/release_notes.md +0 -8
@@ -27,8 +27,9 @@ module Aspera
27
27
  # :reek:UncommunicativeMethodName
28
28
  def do_POST(request, response)
29
29
  Log.log.debug{"request=#{request.path}"}
30
+ Log.log.debug{"query=#{request.query}"}
30
31
  begin
31
- # only accept requests on the root
32
+ # Only accept requests on the root
32
33
  if !request.path.start_with?(@parameters[:root])
33
34
  response.status = 400
34
35
  response['Content-Type'] = Rest::MIME_JSON
@@ -48,16 +49,21 @@ module Aspera
48
49
  Log.log.debug{"script=#{script_path}"}
49
50
  webhook_parameters = JSON.parse(request.body)
50
51
  Log.dump(:webhook_parameters, webhook_parameters)
51
- # env expects only strings
52
- environment = webhook_parameters.each_with_object({}){ |(k, v), h| h[k] = v.to_s}
53
- post_proc_pid = Environment.secure_execute(script_path, mode: :background, env: environment)
54
- Timeout.timeout(@parameters[:timeout_seconds]) do
55
- # "wait" for process to avoid zombie
56
- Process.wait(post_proc_pid)
57
- post_proc_pid = nil
52
+ if request.query.key?('lambda')
53
+ # Code can throw exception, source code must return a lambda
54
+ Environment.secure_eval(File.read(script_path), __FILE__, __LINE__).call(webhook_parameters)
55
+ else
56
+ # env expects only strings
57
+ environment = webhook_parameters.each_with_object({}){ |(k, v), h| h[k] = v.to_s}
58
+ post_proc_pid = Environment.secure_execute(script_path, mode: :background, env: environment)
59
+ Timeout.timeout(@parameters[:timeout_seconds]) do
60
+ # "wait" for process to avoid zombie
61
+ Process.wait(post_proc_pid)
62
+ post_proc_pid = nil
63
+ end
64
+ process_status = $CHILD_STATUS
65
+ raise "script #{script_path} failed with code #{process_status.exitstatus}" if !process_status.success? && @parameters[:fail_on_error]
58
66
  end
59
- process_status = $CHILD_STATUS
60
- raise "script #{script_path} failed with code #{process_status.exitstatus}" if !process_status.success? && @parameters[:fail_on_error]
61
67
  response.status = 200
62
68
  response.content_type = Rest::MIME_JSON
63
69
  response.body = JSON.generate({status: 'success', script: script_path, exit_code: process_status.exitstatus})
@@ -26,6 +26,10 @@ unless Hash.method_defined?(:symbolize_keys)
26
26
  def symbolize_keys
27
27
  return transform_keys(&:to_sym)
28
28
  end
29
+
30
+ def symbolize_keys!
31
+ return transform_keys!(&:to_sym)
32
+ end
29
33
  end
30
34
  end
31
35
 
@@ -35,5 +39,9 @@ unless Hash.method_defined?(:stringify_keys)
35
39
  def stringify_keys
36
40
  return transform_keys(&:to_s)
37
41
  end
42
+
43
+ def stringify_keys!
44
+ return transform_keys!(&:to_s)
45
+ end
38
46
  end
39
47
  end
data/lib/aspera/log.rb CHANGED
@@ -174,11 +174,10 @@ module Aspera
174
174
  Logger::SEVERITY_LABEL[@logger.level].downcase
175
175
  end
176
176
 
177
- # Change underlying logger, but keep log level
177
+ # Change underlying logger, but keep log level (default: INFO)
178
178
  def logger_type=(new_log_type)
179
- current_severity_integer = @logger.level unless @logger.nil?
180
- current_severity_integer = ENV.fetch('AS_LOG_LEVEL', nil) if current_severity_integer.nil? && ENV.key?('AS_LOG_LEVEL')
181
- current_severity_integer = Logger::Severity::WARN if current_severity_integer.nil?
179
+ # [Integer]
180
+ current_severity_integer = @logger&.level || ENV['AS_LOG_LEVEL']&.to_i || Logger::Severity::INFO
182
181
  case new_log_type
183
182
  when :stderr
184
183
  @logger = Logger.new($stderr, progname: @program_name, formatter: DEFAULT_FORMATTER)
@@ -26,6 +26,23 @@ module Aspera
26
26
  def list(items)
27
27
  items.map{ |i| "- #{i}"}.join("\n")
28
28
  end
29
+
30
+ def heading(title, level: 1)
31
+ "#{'#' * level} #{title}\n\n"
32
+ end
33
+
34
+ # type: NOTE CAUTION WARNING IMPORTANT TIP INFO
35
+ def admonition(lines, type: 'INFO')
36
+ "> [!{type}]\n#{lines.map{ |l| "> #{l}"}.join("\n")}\n\n"
37
+ end
38
+
39
+ def code(lines, type: 'shell')
40
+ "```#{type}\n#{lines.join("\n")}\n```\n\n"
41
+ end
42
+
43
+ def paragraph(text)
44
+ "#{text}\n\n"
45
+ end
29
46
  end
30
47
  end
31
48
  end
@@ -99,7 +99,7 @@ module Aspera
99
99
  # lets try the existing refresh token
100
100
  # NOTE: AoC admin token has no refresh, and lives by default 1800secs
101
101
  if !refresh_token.nil?
102
- Log.log.info{"refresh token=[#{refresh_token}]".bg_green}
102
+ Log.log.debug{"refresh token=[#{refresh_token}]"}
103
103
  begin
104
104
  http = create_token_call(base_params(add_secret: true).merge(grant_type: 'refresh_token', refresh_token: refresh_token))
105
105
  # Save only if success
@@ -35,7 +35,7 @@ module Aspera
35
35
  base_params.merge(response_type: 'code', redirect_uri: @redirect_uri, state: random_state)
36
36
  )
37
37
  # here, we need a human to authorize on a web page
38
- Log.log.info{"login_page_url=#{login_page_url}".bg_red.gray}
38
+ Log.log.debug{"login_page_url=#{login_page_url}"}
39
39
  # start a web server to receive request code
40
40
  web_server = WebAuth.new(@redirect_uri, self.class.additional_info)
41
41
  # start browser on login page
@@ -23,18 +23,18 @@ module Aspera
23
23
  # one of CONVERSION_TYPES
24
24
  attr_reader :conversion_type
25
25
 
26
- # node API mime types are from: http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
27
- # the resulting preview file type is taken from destination file extension.
28
- # conversion methods are provided by private methods: convert_<conversion_type>_to_<preview_format>
26
+ # Node API MIME types are from: http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
27
+ # The resulting preview file type is taken from destination file extension.
28
+ # Conversion methods are provided by private methods: convert_<conversion_type>_to_<preview_format>
29
29
  # -> conversion_type is one of FileTypes::CONVERSION_TYPES
30
30
  # -> preview_format is one of Generator::PREVIEW_FORMATS
31
- # the conversion video->mp4 is implemented in methods: convert_video_to_mp4_using_<video_conversion>
31
+ # The conversion video->mp4 is implemented in methods: convert_video_to_mp4_using_<video_conversion>
32
32
  # -> conversion method is one of Generator::VIDEO_CONVERSION_METHODS
33
33
  # @param src [String] source file path
34
34
  # @param dst [String] destination file path
35
35
  # @param options [Options] All conversion options
36
36
  # @param main_temp_dir [String] Main temp folder, sub folder will be created for generation
37
- # @param api_mime_type [String,nil] Optional mime type as provided by node api (or nil)
37
+ # @param api_mime_type [String,nil] Optional MIME type as provided by node api (or nil)
38
38
  def initialize(src, dst, options, main_temp_dir, api_mime_type)
39
39
  @source_file_path = src
40
40
  @destination_file_path = dst
@@ -57,9 +57,9 @@ module Aspera
57
57
  Aspera.assert(respond_to?(@processing_method, true)){"no processing known for #{conversion_type} -> #{@preview_format_sym}"}
58
58
  end
59
59
 
60
- # create preview as specified in constructor
60
+ # Create preview as specified in constructor.
61
61
  def generate
62
- Log.log.info{"#{@source_file_path}->#{@destination_file_path} (#{@processing_method})"}
62
+ Log.log.debug{"#{@source_file_path}->#{@destination_file_path} (#{@processing_method})"}
63
63
  begin
64
64
  send(@processing_method)
65
65
  # check that generated size does not exceed maximum
@@ -76,17 +76,17 @@ module Aspera
76
76
 
77
77
  private
78
78
 
79
- # creates a unique temp folder for file
79
+ # Creates a unique temp folder for file.
80
80
  def this_tmpdir
81
81
  FileUtils.mkdir_p(@temp_folder)
82
82
  return @temp_folder
83
83
  end
84
84
 
85
- # @return offset in seconds suitable for ffmpeg -ss option
86
85
  # @param duration of video
87
86
  # @param start_offset of parts
88
87
  # @param total_count of parts
89
88
  # @param index of part (start at 1)
89
+ # @return [Integer] offset in seconds suitable for ffmpeg -ss option
90
90
  def get_offset(duration, start_offset, total_count, index)
91
91
  Aspera.assert_type(duration, Float){'duration'}
92
92
  return start_offset + ((index - 1) * (duration - start_offset) / total_count)
@@ -3,15 +3,23 @@
3
3
  module Aspera
4
4
  # raised on error after REST call
5
5
  class RestCallError < StandardError
6
- attr_reader :request, :response
6
+ def request
7
+ @context[:request]
8
+ end
9
+
10
+ def response
11
+ @context[:response]
12
+ end
13
+
14
+ def data
15
+ @context[:data]
16
+ end
7
17
 
8
- # @param req HTTP Request object
9
- # @param resp HTTP Response object
10
- # @param msg Error message
11
- def initialize(msg, req = nil, resp = nil)
12
- @request = req
13
- @response = resp
14
- super(msg)
18
+ # @param context [Hash,String] with keys :messages, :request, :response, :data
19
+ def initialize(context)
20
+ context = {messages: [context]} if context.is_a?(String)
21
+ @context = context
22
+ super(@context[:messages].join("\n"))
15
23
  end
16
24
  end
17
25
  end
@@ -46,7 +46,7 @@ module Aspera
46
46
  Log.log.error{"ERROR in handler:\n#{e.message}\n#{e.backtrace}"}
47
47
  end
48
48
  end
49
- raise RestCallError.new(call_context[:messages].join("\n"), call_context[:request], call_context[:response]) unless call_context[:messages].empty?
49
+ raise RestCallError.new(call_context) unless call_context[:messages].empty?
50
50
  end
51
51
 
52
52
  # add a new error handler (done at application initialization)
@@ -41,14 +41,14 @@ module Aspera
41
41
  Log.log.debug{"retries=#{remaining_resumes}"}
42
42
  # try to send the file until ascp is successful
43
43
  loop do
44
- Log.log.debug('Transfer session starting')
44
+ Log.log.debug('Starting task execution')
45
45
  begin
46
46
  # Call provided block: execute transfer
47
47
  yield
48
48
  # Exit retry loop if success
49
49
  break
50
50
  rescue Error => e
51
- Log.log.warn{"A transfer error occurred during transfer: #{e.message}"}
51
+ Log.log.warn{"An error occurred during task: #{e.message}"}
52
52
  Log.log.debug{"Retryable ? #{e.retryable?}"}
53
53
  # do not retry non-retryable
54
54
  raise unless e.retryable?
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module Aspera
6
+ module Yaml
7
+ # @param node [Psych::Nodes::Node] YAML node
8
+ # @param parent_path [Array<String>] Path of parent keys
9
+ # @param duplicate_keys [Array<Hash>] Accumulated duplicate keys
10
+ # @return [Array<String>] List of duplicate keys with their paths and occurrences
11
+ def find_duplicate_keys(node, parent_path = nil, duplicate_keys = nil)
12
+ duplicate_keys ||= []
13
+ parent_path ||= []
14
+ return duplicate_keys unless node.respond_to?(:children)
15
+ if node.is_a?(Psych::Nodes::Mapping)
16
+ counts = Hash.new(0)
17
+ key_nodes = Hash.new{ |h, k| h[k] = []}
18
+ node.children.each_slice(2) do |key_node, value_node|
19
+ if key_node&.value
20
+ counts[key_node.value] += 1
21
+ key_nodes[key_node.value] << key_node
22
+ find_duplicate_keys(value_node, parent_path + [key_node.value], duplicate_keys)
23
+ end
24
+ end
25
+ counts.each do |key_str, count|
26
+ next if count <= 1
27
+ path = (parent_path + [key_str]).join('.')
28
+ occurrences = key_nodes[key_str].map{ |kn| kn.start_line ? kn.start_line + 1 : 'unknown'}.map(&:to_s).join(', ')
29
+ duplicate_keys << "#{path}: #{occurrences}"
30
+ end
31
+ else
32
+ node.children.to_a.each{ |child| find_duplicate_keys(child, parent_path, duplicate_keys)}
33
+ end
34
+ duplicate_keys
35
+ end
36
+
37
+ # Safely load YAML content, raising an error if duplicate keys are found
38
+ # @param yaml [String] YAML content
39
+ # @return [Object] Parsed YAML content
40
+ # @raise [RuntimeError] If duplicate keys are found
41
+ def safe_load(yaml)
42
+ duplicate_keys = find_duplicate_keys(Psych.parse_stream(yaml))
43
+ raise "Duplicate keys: #{duplicate_keys.join('; ')}" unless duplicate_keys.empty?
44
+ YAML.safe_load(yaml)
45
+ end
46
+
47
+ module_function :find_duplicate_keys, :safe_load
48
+ end
49
+ end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aspera-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.25.1
4
+ version: 4.25.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Laurent Martin
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '3.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: marcel
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '1.1'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '1.1'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: mime-types
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -430,9 +444,9 @@ files:
430
444
  - lib/aspera/uri_reader.rb
431
445
  - lib/aspera/web_auth.rb
432
446
  - lib/aspera/web_server_simple.rb
447
+ - lib/aspera/yaml.rb
433
448
  - lib/transferd_pb.rb
434
449
  - lib/transferd_services_pb.rb
435
- - release_notes.md
436
450
  homepage: https://github.com/IBM/aspera-cli
437
451
  licenses:
438
452
  - Apache-2.0
metadata.gz.sig CHANGED
Binary file
data/release_notes.md DELETED
@@ -1,8 +0,0 @@
1
- ## 4.25.1
2
-
3
- Released: 2026-01-21
4
-
5
- ### Issues Fixed
6
-
7
- * `build`: Fixed deploy workflow to use global rake instead of bundle exec.
8
- * `build`: Made rspec require conditional in test.rake for deploy environment.